├── icons ├── png │ ├── moon.png │ ├── rain.png │ ├── snow.png │ ├── sun.png │ ├── clouds.png │ ├── lightning.png │ ├── moonCloud.png │ ├── rainMoon.png │ └── sunClouds.png └── jpg white │ ├── sun.jpg │ ├── clouds.jpg │ ├── moon.jpg │ ├── rain.jpg │ ├── snow.jpg │ ├── rainMoon.jpg │ ├── lightning.jpg │ ├── moonCloud.jpg │ └── sunClouds.jpg ├── references ├── stocks.png ├── time.png ├── weather.png └── wiringDiag.png ├── Info-Orbs ├── icons │ ├── sun.jpg │ ├── clouds.jpg │ ├── moon.jpg │ ├── rain.jpg │ ├── snow.jpg │ ├── moonCloud.jpg │ └── sunClouds.jpg ├── .gitignore ├── lib │ ├── widget │ │ ├── screenWidget.cpp │ │ ├── widget.cpp │ │ ├── screenWidget.h │ │ ├── widget.h │ │ ├── widgetSet.h │ │ └── widgetSet.cpp │ ├── button │ │ ├── Button.h │ │ └── Button.cpp │ ├── screenManager │ │ ├── screenManager.h │ │ └── screenManager.cpp │ ├── README │ ├── globalTime │ │ ├── globalTime.h │ │ └── globalTime.cpp │ └── config │ │ └── config.h.template ├── src │ ├── model │ │ ├── webDataElement.cpp │ │ ├── webDataElementLineModel.cpp │ │ ├── webDataElementCircleModel.cpp │ │ ├── stockDataModel.cpp │ │ ├── webDataElementModel.cpp │ │ ├── webDataElementRectangleModel.cpp │ │ ├── webDataElementTriangleModel.cpp │ │ ├── webDataElementArcModel.cpp │ │ ├── webDataElementTextModel.cpp │ │ ├── webDataElementCharacterModel.cpp │ │ ├── webDataElementImageModel.cpp │ │ ├── weatherDataModel.cpp │ │ └── webDataModel.cpp │ ├── widgets │ │ ├── webDataWidget.cpp │ │ ├── stockWidget.cpp │ │ ├── clockWidget.cpp │ │ └── weatherWidget.cpp │ ├── main.cpp │ └── core │ │ ├── wifiWidget.cpp │ │ └── utils.cpp ├── .vscode │ └── extensions.json ├── include │ ├── model │ │ ├── webDataElement.h │ │ ├── webDataElementImageModel.h │ │ ├── webDataElementModel.h │ │ ├── webDataElementLineModel.h │ │ ├── webDataElementCircleModel.h │ │ ├── webDataElementRectangleModel.h │ │ ├── stockDataModel.h │ │ ├── webDataElementTriangleModel.h │ │ ├── webDataElementArcModel.h │ │ ├── webDataElementTextModel.h │ │ ├── webDataElementCharacterModel.h │ │ ├── webDataModel.h │ │ └── weatherDataModel.h │ ├── utils.h │ ├── widgets │ │ ├── webDataWidget.h │ │ ├── stockWidget.h │ │ ├── clockWidget.h │ │ └── weatherWidget.h │ ├── core │ │ └── wifiWidget.h │ ├── icons.h │ └── README └── platformio.ini ├── PCBFiles+Schematics ├── Wiring Diagram.png ├── Stand │ ├── StandOrbs.kicad_sch │ ├── StandOrbs.kicad_prl │ ├── StandOrbs.kicad_pro │ └── StandOrbs.kicad_pcb └── Main PCB │ ├── info_orbs.kicad_prl │ ├── ESP32-DEVKITC-32D.kicad_sym │ └── info_orbs.kicad_pro ├── .vscode └── settings.json ├── .gitignore ├── info-orbs.code-workspace ├── .github └── workflows │ └── platformio.yml └── README.md /icons/png/moon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SensorsIot/info-orbs/main/icons/png/moon.png -------------------------------------------------------------------------------- /icons/png/rain.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SensorsIot/info-orbs/main/icons/png/rain.png -------------------------------------------------------------------------------- /icons/png/snow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SensorsIot/info-orbs/main/icons/png/snow.png -------------------------------------------------------------------------------- /icons/png/sun.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SensorsIot/info-orbs/main/icons/png/sun.png -------------------------------------------------------------------------------- /icons/png/clouds.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SensorsIot/info-orbs/main/icons/png/clouds.png -------------------------------------------------------------------------------- /references/stocks.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SensorsIot/info-orbs/main/references/stocks.png -------------------------------------------------------------------------------- /references/time.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SensorsIot/info-orbs/main/references/time.png -------------------------------------------------------------------------------- /Info-Orbs/icons/sun.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SensorsIot/info-orbs/main/Info-Orbs/icons/sun.jpg -------------------------------------------------------------------------------- /icons/jpg white/sun.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SensorsIot/info-orbs/main/icons/jpg white/sun.jpg -------------------------------------------------------------------------------- /icons/png/lightning.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SensorsIot/info-orbs/main/icons/png/lightning.png -------------------------------------------------------------------------------- /icons/png/moonCloud.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SensorsIot/info-orbs/main/icons/png/moonCloud.png -------------------------------------------------------------------------------- /icons/png/rainMoon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SensorsIot/info-orbs/main/icons/png/rainMoon.png -------------------------------------------------------------------------------- /icons/png/sunClouds.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SensorsIot/info-orbs/main/icons/png/sunClouds.png -------------------------------------------------------------------------------- /references/weather.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SensorsIot/info-orbs/main/references/weather.png -------------------------------------------------------------------------------- /Info-Orbs/icons/clouds.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SensorsIot/info-orbs/main/Info-Orbs/icons/clouds.jpg -------------------------------------------------------------------------------- /Info-Orbs/icons/moon.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SensorsIot/info-orbs/main/Info-Orbs/icons/moon.jpg -------------------------------------------------------------------------------- /Info-Orbs/icons/rain.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SensorsIot/info-orbs/main/Info-Orbs/icons/rain.jpg -------------------------------------------------------------------------------- /Info-Orbs/icons/snow.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SensorsIot/info-orbs/main/Info-Orbs/icons/snow.jpg -------------------------------------------------------------------------------- /icons/jpg white/clouds.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SensorsIot/info-orbs/main/icons/jpg white/clouds.jpg -------------------------------------------------------------------------------- /icons/jpg white/moon.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SensorsIot/info-orbs/main/icons/jpg white/moon.jpg -------------------------------------------------------------------------------- /icons/jpg white/rain.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SensorsIot/info-orbs/main/icons/jpg white/rain.jpg -------------------------------------------------------------------------------- /icons/jpg white/snow.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SensorsIot/info-orbs/main/icons/jpg white/snow.jpg -------------------------------------------------------------------------------- /references/wiringDiag.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SensorsIot/info-orbs/main/references/wiringDiag.png -------------------------------------------------------------------------------- /icons/jpg white/rainMoon.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SensorsIot/info-orbs/main/icons/jpg white/rainMoon.jpg -------------------------------------------------------------------------------- /Info-Orbs/icons/moonCloud.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SensorsIot/info-orbs/main/Info-Orbs/icons/moonCloud.jpg -------------------------------------------------------------------------------- /Info-Orbs/icons/sunClouds.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SensorsIot/info-orbs/main/Info-Orbs/icons/sunClouds.jpg -------------------------------------------------------------------------------- /icons/jpg white/lightning.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SensorsIot/info-orbs/main/icons/jpg white/lightning.jpg -------------------------------------------------------------------------------- /icons/jpg white/moonCloud.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SensorsIot/info-orbs/main/icons/jpg white/moonCloud.jpg -------------------------------------------------------------------------------- /icons/jpg white/sunClouds.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SensorsIot/info-orbs/main/icons/jpg white/sunClouds.jpg -------------------------------------------------------------------------------- /PCBFiles+Schematics/Wiring Diagram.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SensorsIot/info-orbs/main/PCBFiles+Schematics/Wiring Diagram.png -------------------------------------------------------------------------------- /Info-Orbs/.gitignore: -------------------------------------------------------------------------------- 1 | .pio 2 | .vscode/.browse.c_cpp.db* 3 | .vscode/c_cpp_properties.json 4 | .vscode/launch.json 5 | .vscode/ipch 6 | 7 | lib/config/user.h 8 | -------------------------------------------------------------------------------- /Info-Orbs/lib/widget/screenWidget.cpp: -------------------------------------------------------------------------------- 1 | #include "screenWidget.h" 2 | 3 | ScreenWidget::ScreenWidget(ScreenManager& manager, int screenIndex): m_ScreenIndex(screenIndex), m_ScreenManager(manager) {} -------------------------------------------------------------------------------- /PCBFiles+Schematics/Stand/StandOrbs.kicad_sch: -------------------------------------------------------------------------------- 1 | (kicad_sch (version 20231120) (generator "eeschema") (generator_version "8.0") 2 | (paper "A4") 3 | (lib_symbols) 4 | (symbol_instances) 5 | ) 6 | -------------------------------------------------------------------------------- /Info-Orbs/src/model/webDataElement.cpp: -------------------------------------------------------------------------------- 1 | #include "model/webDataElement.h" 2 | 3 | bool WebDataElement::isChanged() { 4 | return m_changed; 5 | } 6 | void WebDataElement::setChangedStatus(bool changed) { 7 | m_changed = changed; 8 | } 9 | -------------------------------------------------------------------------------- /Info-Orbs/lib/widget/widget.cpp: -------------------------------------------------------------------------------- 1 | #include "widget.h" 2 | 3 | Widget::Widget(ScreenManager &manager): m_manager(manager) 4 | {} 5 | 6 | void Widget::setBusy(bool busy) { 7 | if (busy) { 8 | digitalWrite(BUSY_PIN, HIGH); 9 | } else { 10 | digitalWrite(BUSY_PIN, LOW); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "workbench.colorCustomizations": { 3 | "activityBar.background": "#023148", 4 | "titleBar.activeBackground": "#034465", 5 | "titleBar.activeForeground": "#F5FCFF" 6 | }, 7 | "files.associations": { 8 | "*.ejs": "html", 9 | "ostream": "cpp" 10 | } 11 | } -------------------------------------------------------------------------------- /Info-Orbs/.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | // See http://go.microsoft.com/fwlink/?LinkId=827846 3 | // for the documentation about the extensions.json format 4 | "recommendations": [ 5 | "platformio.platformio-ide" 6 | ], 7 | "unwantedRecommendations": [ 8 | "ms-vscode.cpptools-extension-pack" 9 | ] 10 | } 11 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # macOS system files 2 | .DS_Store 3 | 4 | # Compiled binary files and executables 5 | *.o 6 | *.elf 7 | *.hex 8 | *.exe 9 | *.out 10 | 11 | # Build directory and Arduino IDE specific files 12 | /build/ 13 | *.d 14 | 15 | # Arduino libraries (if installed in the project directory) 16 | libraries/ 17 | Info-Orbs/lib/config/config.h 18 | -------------------------------------------------------------------------------- /Info-Orbs/lib/widget/screenWidget.h: -------------------------------------------------------------------------------- 1 | #ifndef SCREENWIDGET_H 2 | #define SCREENWIDGET_H 3 | 4 | #include 5 | 6 | class ScreenWidget { 7 | public: 8 | ScreenWidget(ScreenManager& manager, int screenIndex); 9 | virtual void setup() = 0; 10 | virtual void draw() = 0; 11 | virtual void update() = 0; 12 | 13 | protected: 14 | int m_ScreenIndex; 15 | ScreenManager& m_ScreenManager; 16 | }; 17 | 18 | #endif // SCREENWIDGET_H -------------------------------------------------------------------------------- /Info-Orbs/lib/widget/widget.h: -------------------------------------------------------------------------------- 1 | #ifndef WIDGET_H 2 | #define WIDGET_H 3 | 4 | #include 5 | #include 6 | 7 | class Widget { 8 | public: 9 | Widget(ScreenManager& manager); 10 | virtual ~Widget() = default; 11 | virtual void setup() = 0; 12 | virtual void update(bool force = false) = 0; 13 | virtual void draw(bool force = false) = 0; 14 | virtual void changeMode() = 0; 15 | void setBusy(bool busy); 16 | 17 | protected: 18 | ScreenManager& m_manager; 19 | }; 20 | #endif // WIDGET_H -------------------------------------------------------------------------------- /Info-Orbs/lib/button/Button.h: -------------------------------------------------------------------------------- 1 | #ifndef Button_h 2 | #define Button_h 3 | #include 4 | 5 | class Button 6 | { 7 | public: 8 | Button(uint8_t pin); 9 | void begin(); 10 | bool read(); 11 | bool toggled(); 12 | bool pressed(); 13 | bool released(); 14 | bool has_changed(); 15 | 16 | const static bool PRESSED = LOW; 17 | const static bool RELEASED = HIGH; 18 | 19 | private: 20 | uint8_t m_pin; 21 | uint16_t m_delay; 22 | bool m_state; 23 | bool m_hasChanged; 24 | uint32_t m_ignoreUntil; 25 | }; 26 | 27 | #endif 28 | -------------------------------------------------------------------------------- /Info-Orbs/include/model/webDataElement.h: -------------------------------------------------------------------------------- 1 | #ifndef WEB_DATA_ELEMENT_H 2 | #define WEB_DATA_ELEMENT_H 3 | 4 | #include 5 | #include 6 | 7 | class WebDataElement { 8 | public: 9 | virtual ~WebDataElement() = default; 10 | 11 | bool isChanged(); 12 | void setChangedStatus(bool changed); 13 | 14 | virtual void parseData(const JsonObject& doc, int32_t defaultColor, int32_t defaultBackground); 15 | 16 | virtual void draw(TFT_eSPI& display); 17 | 18 | protected: 19 | bool m_changed = false; 20 | }; 21 | #endif 22 | -------------------------------------------------------------------------------- /Info-Orbs/include/utils.h: -------------------------------------------------------------------------------- 1 | #ifndef UTILS_H 2 | #define UTILS_H 3 | 4 | #include 5 | #include 6 | 7 | #define MAX_WRAPPED_LINES 10 8 | 9 | class Utils { 10 | public: 11 | static int getWrappedLines(String (&lines)[MAX_WRAPPED_LINES], String str, int limit); 12 | static String getWrappedLine(String str, int limit, int lineNum, int maxLines); 13 | static int32_t stringToColor(String color); 14 | static String formatFloat(float value, int8_t digits); 15 | static int32_t stringToAlignment(String alignment); 16 | }; 17 | 18 | #endif 19 | -------------------------------------------------------------------------------- /info-orbs.code-workspace: -------------------------------------------------------------------------------- 1 | { 2 | "folders": [ 3 | { 4 | "path": "." 5 | } 6 | ], 7 | "settings": { 8 | "[cpp]": { 9 | "editor.defaultFormatter": "ms-vscode.cpptools" 10 | }, 11 | "[markdown]": { 12 | "editor.defaultFormatter": "esbenp.prettier-vscode" 13 | }, 14 | "[jsonc]": { 15 | "editor.defaultFormatter": "esbenp.prettier-vscode" 16 | }, 17 | "editor.formatOnSave": true 18 | }, 19 | "extensions": { 20 | "recommendations": [ 21 | "vsciot-vscode.vscode-arduino", 22 | "ms-vscode.cpptools", 23 | "ms-vscode.vscode-serial-monitor", 24 | "esbenp.prettier-vscode", 25 | "wokwi.wokwi-vscode" 26 | ] 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /Info-Orbs/include/model/webDataElementImageModel.h: -------------------------------------------------------------------------------- 1 | #ifndef WEB_DATA_ELEMENT_IMAGE_MODEL_H 2 | #define WEB_DATA_ELEMENT_IMAGE_MODEL_H 3 | 4 | #include 5 | #include 6 | 7 | #include "utils.h" 8 | #include "webDataElement.h" 9 | 10 | class WebDataElementImageModel : public WebDataElement { 11 | public: 12 | int32_t getX(); 13 | void setX(int32_t x); 14 | int32_t getY(); 15 | void setY(int32_t y); 16 | 17 | void setImage(String image); 18 | String getImage(); 19 | 20 | void parseData(const JsonObject& doc, int32_t defaultColor, int32_t defaultBackground) override; 21 | void draw(TFT_eSPI& display) override; 22 | 23 | private: 24 | int32_t m_x = 0; 25 | int32_t m_y = 0; 26 | String m_image = ""; 27 | }; 28 | #endif 29 | -------------------------------------------------------------------------------- /Info-Orbs/lib/screenManager/screenManager.h: -------------------------------------------------------------------------------- 1 | #ifndef SCREENMANAGER_H 2 | #define SCREENMANAGER_H 3 | 4 | // Include any necessary libraries here 5 | #include 6 | #include 7 | #include 8 | 9 | #define NUM_SCREENS 5 10 | 11 | // Define your class or functions here 12 | 13 | class ScreenManager { 14 | public: 15 | ScreenManager(TFT_eSPI& tft); 16 | 17 | TFT_eSPI& getDisplay(); 18 | 19 | void selectScreen(int screen); 20 | void selectAllScreens(); 21 | void reset(); 22 | 23 | void fillAllScreens(uint32_t color); 24 | void clearAllScreens(); 25 | 26 | void clearScreen(int screen); 27 | 28 | private: 29 | uint8_t m_screen_cs[5] = {SCREEN_1_CS, SCREEN_2_CS, SCREEN_3_CS, SCREEN_4_CS, SCREEN_5_CS}; 30 | TFT_eSPI& m_tft; 31 | }; 32 | 33 | #endif // SCREENMANAGER_H -------------------------------------------------------------------------------- /Info-Orbs/include/widgets/webDataWidget.h: -------------------------------------------------------------------------------- 1 | #ifndef WEB_DATA_WIDGET_H 2 | #define WEB_DATA_WIDGET_H 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | #include "model/webDataModel.h" 9 | #include "utils.h" 10 | 11 | class WebDataWidget : public Widget { 12 | public: 13 | WebDataWidget(ScreenManager &manager, String url); 14 | ~WebDataWidget() override; 15 | void setup() override; 16 | void update(bool force = false) override; 17 | void draw(bool force = false) override; 18 | void changeMode() override; 19 | 20 | private: 21 | int m_lastUpdate = 0; 22 | int m_updateDelay = 1000; 23 | String httpRequestAddress; 24 | WebDataModel m_obj[5]; 25 | int32_t m_defaultColor = TFT_WHITE; 26 | int32_t m_defaultBackground = TFT_BLACK; 27 | }; 28 | #endif // WEB_DATA_WIDGET_H 29 | -------------------------------------------------------------------------------- /Info-Orbs/include/core/wifiWidget.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #ifndef WIFIWIDGET_H 3 | #define WIFIWIDGET_H 4 | #include "widget.h" 5 | 6 | class WifiWidget : public Widget{ 7 | public: 8 | WifiWidget(ScreenManager& manager); 9 | ~WifiWidget() override; 10 | void setup() override; 11 | void update(bool force = false) override; 12 | void draw(bool force = false) override; 13 | void changeMode() override; 14 | 15 | bool isConnected() { return m_isConnected; } 16 | 17 | private: 18 | 19 | void connectionTimedOut(); 20 | 21 | bool m_isConnected{ false }; 22 | bool m_connectionFailed{ false }; 23 | 24 | bool m_hasDisplayedError{ false }; 25 | bool m_hasDisplayedSuccess{ false }; 26 | 27 | 28 | String m_connectionString{ "" }; 29 | String m_dotsString{ "" }; 30 | int m_connectionTimer{ 0 }; 31 | const int m_connectionTimeout{ 10000 }; 32 | 33 | }; 34 | 35 | #endif // WIFIWIDGET_H -------------------------------------------------------------------------------- /Info-Orbs/lib/widget/widgetSet.h: -------------------------------------------------------------------------------- 1 | #ifndef WIDGET_SET_H 2 | #define WIDGET_SET_H 3 | 4 | #include 5 | #include 6 | 7 | #define MAX_WIDGETS 5 8 | 9 | class WidgetSet { 10 | public: 11 | WidgetSet(ScreenManager *sm); 12 | void add(Widget *widget); 13 | void drawCurrent(); 14 | void updateCurrent(); 15 | Widget *getCurrent(); 16 | void next(); 17 | void prev(); 18 | void changeMode(); 19 | void showLoading(); 20 | void updateAll(); 21 | bool initialUpdateDone(); 22 | void initializeAllWidgetsData(); 23 | void setClearScreensOnDrawCurrent(); 24 | 25 | private: 26 | ScreenManager *m_screenManager; 27 | bool m_clearScreensOnDrawCurrent = true; 28 | Widget *m_widgets[MAX_WIDGETS]; 29 | int8_t m_widgetCount = 0; 30 | int8_t m_currentWidget = 0; 31 | 32 | bool m_initialized = false; 33 | 34 | void switchWidget(); 35 | }; 36 | #endif // WIDGET_SET_H -------------------------------------------------------------------------------- /Info-Orbs/include/model/webDataElementModel.h: -------------------------------------------------------------------------------- 1 | #ifndef WEB_DATA_ELEMENT_MODEL_H 2 | #define WEB_DATA_ELEMENT_MODEL_H 3 | 4 | #include 5 | #include 6 | 7 | #include "utils.h" 8 | #include "webDataElement.h" 9 | 10 | enum WebDataElementModelTypes { 11 | TEXT, 12 | CHARACTER, 13 | LINE, 14 | RECTANGLE, 15 | TRIANGLE, 16 | CIRCLE, 17 | ARC, 18 | IMAGE, 19 | OTHER 20 | }; 21 | 22 | class WebDataElementModel { 23 | public: 24 | void setType(const String& type); 25 | WebDataElementModelTypes getType(); 26 | 27 | bool isChanged(); 28 | void setChangedStatus(bool changed); 29 | 30 | void parseData(JsonObject doc, int32_t defaultColor, int32_t defaultBackground); 31 | void draw(TFT_eSPI& display); 32 | 33 | private: 34 | WebDataElementModelTypes m_type = OTHER; 35 | WebDataElement* m_element = nullptr; 36 | 37 | bool m_changed = false; 38 | }; 39 | #endif 40 | -------------------------------------------------------------------------------- /Info-Orbs/include/widgets/stockWidget.h: -------------------------------------------------------------------------------- 1 | #ifndef STOCK_WIDGET_H 2 | #define STOCK_WIDGET_H 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | #include "model/stockDataModel.h" 9 | #include "widget.h" 10 | 11 | #define MAX_STOCKS 5 12 | 13 | class StockWidget : public Widget { 14 | public: 15 | StockWidget(ScreenManager &manager); 16 | void setup() override; 17 | void update(bool force = false) override; 18 | void draw(bool force = false) override; 19 | void changeMode() override; 20 | 21 | private: 22 | void getStockData(StockDataModel &stock); 23 | void displayStock(int8_t displayIndex, StockDataModel &stock, uint32_t backgroundColor, uint32_t textColor); 24 | 25 | 26 | unsigned long m_stockDelay = 900000; //default to 15m between updates 27 | unsigned long m_stockDelayPrev = 0; 28 | 29 | StockDataModel m_stocks[MAX_STOCKS]; 30 | int8_t m_stockCount; 31 | }; 32 | #endif // STOCK_WIDGET_H 33 | -------------------------------------------------------------------------------- /Info-Orbs/include/model/webDataElementLineModel.h: -------------------------------------------------------------------------------- 1 | #ifndef WEB_DATA_ELEMENT_LINE_MODEL_H 2 | #define WEB_DATA_ELEMENT_LINE_MODEL_H 3 | 4 | #include 5 | #include 6 | 7 | #include "utils.h" 8 | #include "webDataElement.h" 9 | 10 | class WebDataElementLineModel : public WebDataElement { 11 | public: 12 | int32_t getX(); 13 | void setX(int32_t x); 14 | int32_t getY(); 15 | void setY(int32_t y); 16 | 17 | int32_t getX2(); 18 | void setX2(int32_t x); 19 | int32_t getY2(); 20 | void setY2(int32_t y); 21 | 22 | void setColor(int32_t color); 23 | void setColor(String color); 24 | int32_t getColor(); 25 | 26 | void parseData(const JsonObject& doc, int32_t defaultColor, int32_t defaultBackground) override; 27 | void draw(TFT_eSPI& display) override; 28 | 29 | private: 30 | int32_t m_x = 0; 31 | int32_t m_y = 0; 32 | int32_t m_x2 = 0; 33 | int32_t m_y2 = 0; 34 | int32_t m_color = -1; 35 | }; 36 | #endif 37 | -------------------------------------------------------------------------------- /.github/workflows/platformio.yml: -------------------------------------------------------------------------------- 1 | name: PlatformIO CI 2 | 3 | on: 4 | push: 5 | branches: [ "main" ] 6 | pull_request: 7 | branches: [ "main" ] 8 | 9 | jobs: 10 | build: 11 | runs-on: ${{ matrix.os }} 12 | strategy: 13 | matrix: 14 | os: [ubuntu-latest, macos-latest, windows-latest] 15 | 16 | steps: 17 | - uses: actions/checkout@v4 18 | - uses: actions/cache@v4 19 | with: 20 | path: | 21 | ~/.cache/pip 22 | ~/.platformio/.cache 23 | key: ${{ runner.os }}-pio 24 | - uses: actions/setup-python@v5 25 | with: 26 | python-version: '3.11' 27 | - name: Install PlatformIO Core 28 | run: pip install --upgrade platformio 29 | 30 | - name: Create config.h 31 | working-directory: ./Info-Orbs 32 | run: cp lib/config/config.h.template lib/config/config.h 33 | 34 | - name: Build PlatformIO Project 35 | working-directory: ./Info-Orbs 36 | run: pio run 37 | -------------------------------------------------------------------------------- /Info-Orbs/include/model/webDataElementCircleModel.h: -------------------------------------------------------------------------------- 1 | #ifndef WEB_DATA_ELEMENT_CIRCLE_MODEL_H 2 | #define WEB_DATA_ELEMENT_CIRCLE_MODEL_H 3 | 4 | #include 5 | #include 6 | 7 | #include "utils.h" 8 | #include "webDataElement.h" 9 | 10 | class WebDataElementCircleModel : public WebDataElement { 11 | public: 12 | int32_t getX(); 13 | void setX(int32_t x); 14 | int32_t getY(); 15 | void setY(int32_t y); 16 | 17 | int32_t getRadius(); 18 | void setRadius(int32_t radius); 19 | 20 | bool getFilled(); 21 | void setFilled(bool filled); 22 | 23 | void setColor(int32_t color); 24 | void setColor(String color); 25 | int32_t getColor(); 26 | 27 | void parseData(const JsonObject& doc, int32_t defaultColor, int32_t defaultBackground) override; 28 | void draw(TFT_eSPI& display) override; 29 | 30 | private: 31 | int32_t m_x = 0; 32 | int32_t m_y = 0; 33 | int32_t m_radius = 0; 34 | bool m_filled = false; 35 | int32_t m_color = -1; 36 | }; 37 | #endif 38 | -------------------------------------------------------------------------------- /Info-Orbs/include/model/webDataElementRectangleModel.h: -------------------------------------------------------------------------------- 1 | #ifndef WEB_DATA_ELEMENT_RECTANGLE_MODEL_H 2 | #define WEB_DATA_ELEMENT_RECTANGLE_MODEL_H 3 | 4 | #include 5 | #include 6 | 7 | #include "utils.h" 8 | #include "webDataElement.h" 9 | 10 | class WebDataElementRectangleModel : public WebDataElement { 11 | public: 12 | int32_t getX(); 13 | void setX(int32_t x); 14 | int32_t getY(); 15 | void setY(int32_t y); 16 | 17 | int32_t getWidth(); 18 | void setWidth(int32_t width); 19 | int32_t getHeight(); 20 | void setHeight(int32_t height); 21 | 22 | bool getFilled(); 23 | void setFilled(bool filled); 24 | 25 | void setColor(int32_t color); 26 | void setColor(String color); 27 | int32_t getColor(); 28 | 29 | void parseData(const JsonObject& doc, int32_t defaultColor, int32_t defaultBackground) override; 30 | void draw(TFT_eSPI& display) override; 31 | 32 | private: 33 | int32_t m_x = 0; 34 | int32_t m_y = 0; 35 | int32_t m_width = 0; 36 | int32_t m_height = 0; 37 | bool m_filled = false; 38 | int32_t m_color = -1; 39 | }; 40 | #endif 41 | -------------------------------------------------------------------------------- /Info-Orbs/platformio.ini: -------------------------------------------------------------------------------- 1 | ; PlatformIO Project Configuration File 2 | ; 3 | ; Build options: build flags, source filter 4 | ; Upload options: custom upload port, speed and extra flags 5 | ; Library options: dependencies, extra library storages 6 | ; Advanced options: extra scripting 7 | ; 8 | ; Please visit documentation for the other options and examples 9 | ; https://docs.platformio.org/page/projectconf.html 10 | 11 | [env:esp32doit-devkit-v1] 12 | platform = espressif32 13 | board = esp32doit-devkit-v1 14 | board_build.embed_files = 15 | icons/moonCloud.jpg 16 | icons/sunClouds.jpg 17 | icons/sun.jpg 18 | icons/moon.jpg 19 | icons/snow.jpg 20 | icons/rain.jpg 21 | icons/clouds.jpg 22 | framework = arduino 23 | lib_deps = 24 | SPI 25 | FS 26 | SPIFFS 27 | LittleFS 28 | SD 29 | arduino-libraries/NTPClient@^3.2.1 30 | bblanchon/ArduinoJson@^7.0.4 31 | bodmer/TFT_eSPI@^2.5.43 32 | bodmer/TJpg_Decoder@^1.1.0 33 | paulstoffregen/Time@^1.6.1 34 | arduino-libraries/NTPClient @ ^3.2.1 35 | 36 | monitor_speed = 115200 37 | build_flags = 38 | -D DISABLE_ALL_LIBRARY_WARNINGS 39 | -D USER_SETUP_LOADED=1 40 | -include "lib/config/config.h" -------------------------------------------------------------------------------- /Info-Orbs/include/model/stockDataModel.h: -------------------------------------------------------------------------------- 1 | #ifndef STOCK_DATA_MODEL_H 2 | #define STOCK_DATA_MODEL_H 3 | 4 | #include 5 | 6 | #include 7 | 8 | class StockDataModel { 9 | public: 10 | StockDataModel(); 11 | StockDataModel &setSymbol(String symbol); 12 | String getSymbol(); 13 | StockDataModel &setCurrentPrice(float currentPrice); 14 | float getCurrentPrice(); 15 | String getCurrentPrice(int8_t digits); 16 | StockDataModel &setVolume(float volume); 17 | float getVolume(); 18 | String getVolume(int8_t digits); 19 | StockDataModel &setPriceChange(float change); 20 | float getPriceChange(); 21 | String getPriceChange(int8_t digits); 22 | StockDataModel &setPercentChange(float percentChange); 23 | float getPercentChange(); 24 | String getPercentChange(int8_t digits); 25 | 26 | bool isChanged(); 27 | StockDataModel &setChangedStatus(bool changed); 28 | 29 | private: 30 | String m_symbol = ""; 31 | float m_currentPrice = 0.0; 32 | float m_volume = 0.0; 33 | float m_priceChange = 0.0; 34 | float m_percentChange = 0.0; 35 | bool m_changed = false; 36 | }; 37 | 38 | #endif // STOCK_DATA_MODEL_H 39 | -------------------------------------------------------------------------------- /Info-Orbs/lib/button/Button.cpp: -------------------------------------------------------------------------------- 1 | #include "Button.h" 2 | #include "config.h" 3 | 4 | 5 | Button::Button(uint8_t pin) 6 | : m_pin(pin), m_delay(100), m_ignoreUntil(0), 7 | m_hasChanged(false) {} 8 | 9 | void Button::begin() { 10 | #if BUTTON_MODE == INPUT_PULLDOWN 11 | m_state = LOW; 12 | #else 13 | m_state = HIGH; 14 | #endif 15 | pinMode(m_pin, BUTTON_MODE); 16 | } 17 | 18 | bool Button::read() { 19 | if (m_ignoreUntil > millis()) { 20 | return m_state; 21 | } 22 | 23 | if (digitalRead(m_pin) != m_state) { 24 | m_ignoreUntil = millis() + m_delay; 25 | m_state = !m_state; 26 | m_hasChanged = true; 27 | } 28 | 29 | return m_state; 30 | } 31 | 32 | bool Button::toggled() { 33 | read(); 34 | return has_changed(); 35 | } 36 | 37 | bool Button::has_changed() { 38 | if (m_hasChanged == true) { 39 | m_hasChanged = false; 40 | return true; 41 | } 42 | return false; 43 | } 44 | 45 | bool Button::pressed() { 46 | if (read() == PRESSED && has_changed() == true) 47 | return true; 48 | else 49 | return false; 50 | } 51 | 52 | bool Button::released() { 53 | if (read() == RELEASED && has_changed() == true) 54 | return true; 55 | else 56 | return false; 57 | } 58 | -------------------------------------------------------------------------------- /Info-Orbs/lib/README: -------------------------------------------------------------------------------- 1 | 2 | This directory is intended for project specific (private) libraries. 3 | PlatformIO will compile them to static libraries and link into executable file. 4 | 5 | The source code of each library should be placed in an own separate directory 6 | ("lib/your_library_name/[here are source files]"). 7 | 8 | For example, see a structure of the following two libraries `Foo` and `Bar`: 9 | 10 | |--lib 11 | | | 12 | | |--Bar 13 | | | |--docs 14 | | | |--examples 15 | | | |--src 16 | | | |- Bar.c 17 | | | |- Bar.h 18 | | | |- library.json (optional, custom build options, etc) https://docs.platformio.org/page/librarymanager/config.html 19 | | | 20 | | |--Foo 21 | | | |- Foo.c 22 | | | |- Foo.h 23 | | | 24 | | |- README --> THIS FILE 25 | | 26 | |- platformio.ini 27 | |--src 28 | |- main.c 29 | 30 | and a contents of `src/main.c`: 31 | ``` 32 | #include 33 | #include 34 | 35 | int main (void) 36 | { 37 | ... 38 | } 39 | 40 | ``` 41 | 42 | PlatformIO Library Dependency Finder will find automatically dependent 43 | libraries scanning project source files. 44 | 45 | More information about PlatformIO Library Dependency Finder 46 | - https://docs.platformio.org/page/librarymanager/ldf.html 47 | -------------------------------------------------------------------------------- /Info-Orbs/include/model/webDataElementTriangleModel.h: -------------------------------------------------------------------------------- 1 | #ifndef WEB_DATA_ELEMENT_TRIANGLE_MODEL_H 2 | #define WEB_DATA_ELEMENT_TRIANGLE_MODEL_H 3 | 4 | #include 5 | #include 6 | 7 | #include "utils.h" 8 | #include "webDataElement.h" 9 | 10 | class WebDataElementTriangleModel : public WebDataElement { 11 | public: 12 | int32_t getX(); 13 | void setX(int32_t x); 14 | int32_t getY(); 15 | void setY(int32_t y); 16 | 17 | int32_t getX2(); 18 | void setX2(int32_t x); 19 | int32_t getY2(); 20 | void setY2(int32_t y); 21 | int32_t getX3(); 22 | void setX3(int32_t x); 23 | int32_t getY3(); 24 | void setY3(int32_t y); 25 | 26 | bool getFilled(); 27 | void setFilled(bool filled); 28 | 29 | void setColor(int32_t color); 30 | void setColor(String color); 31 | int32_t getColor(); 32 | 33 | void parseData(const JsonObject& doc, int32_t defaultColor, int32_t defaultBackground) override; 34 | void draw(TFT_eSPI& display) override; 35 | 36 | private: 37 | int32_t m_x = 0; 38 | int32_t m_y = 0; 39 | int32_t m_x2 = 0; 40 | int32_t m_y2 = 0; 41 | int32_t m_x3 = 0; 42 | int32_t m_y3 = 0; 43 | bool m_filled = false; 44 | int32_t m_color = -1; 45 | }; 46 | #endif 47 | -------------------------------------------------------------------------------- /Info-Orbs/include/icons.h: -------------------------------------------------------------------------------- 1 | #ifndef ICONS_H 2 | #define ICONS_H 3 | 4 | #include 5 | 6 | // These symbols are generated from the files specified in platformio.ini under 'board_build.embed_files' 7 | // See https://docs.platformio.org/en/latest/platforms/espressif32.html#embedding-binary-data for more info 8 | 9 | extern const byte moonCloud_start[] asm("_binary_icons_moonCloud_jpg_start"); 10 | extern const byte moonCloud_end[] asm("_binary_icons_moonCloud_jpg_end"); 11 | extern const byte sunClouds_start[] asm("_binary_icons_sunClouds_jpg_start"); 12 | extern const byte sunClouds_end[] asm("_binary_icons_sunClouds_jpg_end"); 13 | extern const byte sun_start[] asm("_binary_icons_sun_jpg_start"); 14 | extern const byte sun_end[] asm("_binary_icons_sun_jpg_end"); 15 | extern const byte moon_start[] asm("_binary_icons_moon_jpg_start"); 16 | extern const byte moon_end[] asm("_binary_icons_moon_jpg_end"); 17 | extern const byte snow_start[] asm("_binary_icons_snow_jpg_start"); 18 | extern const byte snow_end[] asm("_binary_icons_snow_jpg_end"); 19 | extern const byte rain_start[] asm("_binary_icons_rain_jpg_start"); 20 | extern const byte rain_end[] asm("_binary_icons_rain_jpg_end"); 21 | extern const byte clouds_start[] asm("_binary_icons_clouds_jpg_start"); 22 | extern const byte clouds_end[] asm("_binary_icons_clouds_jpg_end"); 23 | 24 | #endif 25 | -------------------------------------------------------------------------------- /Info-Orbs/include/model/webDataElementArcModel.h: -------------------------------------------------------------------------------- 1 | #ifndef WEB_DATA_ELEMENT_Arc_MODEL_H 2 | #define WEB_DATA_ELEMENT_Arc_MODEL_H 3 | 4 | #include "webDataElement.h" 5 | #include 6 | #include 7 | #include "utils.h" 8 | 9 | class WebDataElementArcModel: public WebDataElement { 10 | public: 11 | int32_t getX(); 12 | void setX(int32_t x); 13 | int32_t getY(); 14 | void setY(int32_t y); 15 | 16 | int32_t getRadius(); 17 | void setRadius(int32_t radius); 18 | int32_t getInnerRadius(); 19 | void setInnerRadius(int32_t radius); 20 | 21 | uint32_t getAngleStart(); 22 | void setAngleStart(uint32_t angle); 23 | 24 | uint32_t getAngleEnd(); 25 | void setAngleEnd(uint32_t angle); 26 | 27 | void setColor(int32_t color); 28 | void setColor(String color); 29 | int32_t getColor(); 30 | 31 | void setBackgroundColor(int32_t background); 32 | void setBackgroundColor(String background); 33 | int32_t getBackgroundColor(); 34 | 35 | void parseData(const JsonObject& doc, int32_t defaultColor, int32_t defaultBackground) override; 36 | void draw(TFT_eSPI& display) override; 37 | 38 | private: 39 | int32_t m_x = 0; 40 | int32_t m_y = 0; 41 | int32_t m_radius = 0; 42 | int32_t m_innerRadius = 0; 43 | uint32_t m_angleStart = 0; 44 | uint32_t m_angleEnd = 0; 45 | int32_t m_color = -1; 46 | int32_t m_background = -1; 47 | }; 48 | #endif 49 | -------------------------------------------------------------------------------- /Info-Orbs/include/model/webDataElementTextModel.h: -------------------------------------------------------------------------------- 1 | #ifndef WEB_DATA_ELEMENT_TEXT_MODEL_H 2 | #define WEB_DATA_ELEMENT_TEXT_MODEL_H 3 | 4 | #include 5 | #include 6 | 7 | #include "utils.h" 8 | #include "webDataElement.h" 9 | 10 | class WebDataElementTextModel : public WebDataElement { 11 | public: 12 | int32_t getX(); 13 | void setX(int32_t x); 14 | int32_t getY(); 15 | void setY(int32_t y); 16 | 17 | String getText(); 18 | void setText(String text); 19 | 20 | int32_t getFont(); 21 | void setFont(int32_t font); 22 | 23 | int32_t getSize(); 24 | void setSize(int32_t size); 25 | 26 | int32_t getAlignment(); 27 | void setAlignment(int32_t alignment); 28 | void setAlignment(String alignment); 29 | 30 | int32_t getBackgroundColor(); 31 | void setBackgroundColor(int32_t background); 32 | void setBackgroundColor(String background); 33 | 34 | void setColor(int32_t color); 35 | void setColor(String color); 36 | int32_t getColor(); 37 | 38 | void parseData(const JsonObject& doc, int32_t defaultColor, int32_t defaultBackground) override; 39 | void draw(TFT_eSPI& display) override; 40 | 41 | private: 42 | int32_t m_x = 0; 43 | int32_t m_y = 0; 44 | String m_text; 45 | int32_t m_font = 2; 46 | int32_t m_size = 2; 47 | int32_t m_alignment = MC_DATUM; 48 | int32_t m_color = -1; 49 | int32_t m_background = -1; 50 | }; 51 | #endif 52 | -------------------------------------------------------------------------------- /Info-Orbs/include/model/webDataElementCharacterModel.h: -------------------------------------------------------------------------------- 1 | #ifndef WEB_DATA_ELEMENT_CHARACTER_MODEL_H 2 | #define WEB_DATA_ELEMENT_CHARACTER_MODEL_H 3 | 4 | #include 5 | #include 6 | 7 | #include "utils.h" 8 | #include "webDataElement.h" 9 | 10 | class WebDataElementCharacterModel : public WebDataElement { 11 | public: 12 | int32_t getX(); 13 | void setX(int32_t x); 14 | int32_t getY(); 15 | void setY(int32_t y); 16 | 17 | String getCharacter(); 18 | void setCharacter(String character); 19 | 20 | int32_t getFont(); 21 | 22 | void setFont(int32_t font); 23 | int32_t getSize(); 24 | void setSize(int32_t size); 25 | 26 | int32_t getAlignment(); 27 | void setAlignment(int32_t alignment); 28 | void setAlignment(String alignment); 29 | 30 | int32_t getBackgroundColor(); 31 | void setBackgroundColor(int32_t background); 32 | void setBackgroundColor(String background); 33 | 34 | void setColor(int32_t color); 35 | void setColor(String color); 36 | int32_t getColor(); 37 | 38 | void parseData(const JsonObject& doc, int32_t defaultColor, int32_t defaultBackground) override; 39 | void draw(TFT_eSPI& display) override; 40 | 41 | private: 42 | int32_t m_x = 0; 43 | int32_t m_y = 0; 44 | String m_character; 45 | int32_t m_font = 2; 46 | int32_t m_size = 2; 47 | int32_t m_alignment = MC_DATUM; 48 | int32_t m_color = -1; 49 | int32_t m_background = -1; 50 | }; 51 | #endif 52 | -------------------------------------------------------------------------------- /Info-Orbs/lib/globalTime/globalTime.h: -------------------------------------------------------------------------------- 1 | #ifndef TIME_H 2 | #define TIME_H 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | class GlobalTime { 11 | public: 12 | static GlobalTime *getInstance(); 13 | 14 | void updateTime(); 15 | void getHourAndMinute(int &hour, int &minute); 16 | int getHour(); 17 | String getHourPadded(); 18 | int getMinute(); 19 | String getMinutePadded(); 20 | time_t getUnixEpoch(); 21 | int getSecond(); 22 | int getDay(); 23 | int getMonth(); 24 | String getMonthName(); 25 | int getYear(); 26 | String getTime(); 27 | String getWeekday(); 28 | bool isPM(); 29 | bool getFormat24Hour(); 30 | bool setFormat24Hour(bool format24hour); 31 | 32 | private: 33 | GlobalTime(); 34 | ~GlobalTime(); 35 | 36 | static GlobalTime *m_instance; 37 | 38 | time_t m_unixEpoch; 39 | int m_hour = 0; 40 | int m_minute = 0; 41 | int m_second = 0; 42 | int m_day = 0; 43 | int m_month = 0; 44 | String m_monthName; 45 | int m_year = 0; 46 | String m_time; 47 | String m_weekday; 48 | int m_timeZoneOffset = -1; // a value that will be overwritten by the API 49 | 50 | WiFiUDP m_udp; 51 | NTPClient *m_timeClient{nullptr}; 52 | 53 | const int m_oneSecond{1000}; 54 | int m_updateTimer{0}; 55 | 56 | bool m_format24hour{FORMAT_24_HOUR}; 57 | 58 | void getTimeZoneOffsetFromAPI(); 59 | }; 60 | 61 | #endif 62 | -------------------------------------------------------------------------------- /Info-Orbs/include/README: -------------------------------------------------------------------------------- 1 | 2 | This directory is intended for project header files. 3 | 4 | A header file is a file containing C declarations and macro definitions 5 | to be shared between several project source files. You request the use of a 6 | header file in your project source file (C, C++, etc) located in `src` folder 7 | by including it, with the C preprocessing directive `#include'. 8 | 9 | ```src/main.c 10 | 11 | #include "header.h" 12 | 13 | int main (void) 14 | { 15 | ... 16 | } 17 | ``` 18 | 19 | Including a header file produces the same results as copying the header file 20 | into each source file that needs it. Such copying would be time-consuming 21 | and error-prone. With a header file, the related declarations appear 22 | in only one place. If they need to be changed, they can be changed in one 23 | place, and programs that include the header file will automatically use the 24 | new version when next recompiled. The header file eliminates the labor of 25 | finding and changing all the copies as well as the risk that a failure to 26 | find one copy will result in inconsistencies within a program. 27 | 28 | In C, the usual convention is to give header files names that end with `.h'. 29 | It is most portable to use only letters, digits, dashes, and underscores in 30 | header file names, and at most one dot. 31 | 32 | Read more about using header files in official GCC documentation: 33 | 34 | * Include Syntax 35 | * Include Operation 36 | * Once-Only Headers 37 | * Computed Includes 38 | 39 | https://gcc.gnu.org/onlinedocs/cpp/Header-Files.html 40 | -------------------------------------------------------------------------------- /PCBFiles+Schematics/Stand/StandOrbs.kicad_prl: -------------------------------------------------------------------------------- 1 | { 2 | "board": { 3 | "active_layer": 44, 4 | "active_layer_preset": "", 5 | "auto_track_width": true, 6 | "hidden_netclasses": [], 7 | "hidden_nets": [], 8 | "high_contrast_mode": 0, 9 | "net_color_mode": 1, 10 | "opacity": { 11 | "images": 0.6, 12 | "pads": 1.0, 13 | "tracks": 1.0, 14 | "vias": 1.0, 15 | "zones": 0.6 16 | }, 17 | "selection_filter": { 18 | "dimensions": true, 19 | "footprints": true, 20 | "graphics": true, 21 | "keepouts": true, 22 | "lockedItems": false, 23 | "otherItems": true, 24 | "pads": true, 25 | "text": true, 26 | "tracks": true, 27 | "vias": true, 28 | "zones": true 29 | }, 30 | "visible_items": [ 31 | 0, 32 | 1, 33 | 2, 34 | 3, 35 | 4, 36 | 5, 37 | 8, 38 | 9, 39 | 10, 40 | 11, 41 | 12, 42 | 13, 43 | 15, 44 | 16, 45 | 17, 46 | 18, 47 | 19, 48 | 20, 49 | 21, 50 | 22, 51 | 23, 52 | 24, 53 | 25, 54 | 26, 55 | 27, 56 | 28, 57 | 29, 58 | 30, 59 | 32, 60 | 33, 61 | 34, 62 | 35, 63 | 36, 64 | 39, 65 | 40 66 | ], 67 | "visible_layers": "fffdfff_ffffffff", 68 | "zone_display_mode": 0 69 | }, 70 | "git": { 71 | "repo_password": "", 72 | "repo_type": "", 73 | "repo_username": "", 74 | "ssh_key": "" 75 | }, 76 | "meta": { 77 | "filename": "StandOrbs.kicad_prl", 78 | "version": 3 79 | }, 80 | "project": { 81 | "files": [] 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /PCBFiles+Schematics/Main PCB/info_orbs.kicad_prl: -------------------------------------------------------------------------------- 1 | { 2 | "board": { 3 | "active_layer": 0, 4 | "active_layer_preset": "All Layers", 5 | "auto_track_width": true, 6 | "hidden_netclasses": [], 7 | "hidden_nets": [], 8 | "high_contrast_mode": 0, 9 | "net_color_mode": 1, 10 | "opacity": { 11 | "images": 0.6, 12 | "pads": 1.0, 13 | "tracks": 1.0, 14 | "vias": 1.0, 15 | "zones": 0.6 16 | }, 17 | "selection_filter": { 18 | "dimensions": true, 19 | "footprints": true, 20 | "graphics": true, 21 | "keepouts": true, 22 | "lockedItems": false, 23 | "otherItems": true, 24 | "pads": true, 25 | "text": true, 26 | "tracks": true, 27 | "vias": true, 28 | "zones": true 29 | }, 30 | "visible_items": [ 31 | 0, 32 | 1, 33 | 2, 34 | 3, 35 | 4, 36 | 5, 37 | 8, 38 | 9, 39 | 10, 40 | 11, 41 | 12, 42 | 13, 43 | 15, 44 | 16, 45 | 17, 46 | 18, 47 | 19, 48 | 20, 49 | 21, 50 | 22, 51 | 23, 52 | 24, 53 | 25, 54 | 26, 55 | 27, 56 | 28, 57 | 29, 58 | 30, 59 | 32, 60 | 33, 61 | 34, 62 | 35, 63 | 36, 64 | 39, 65 | 40 66 | ], 67 | "visible_layers": "fffffff_ffffffff", 68 | "zone_display_mode": 0 69 | }, 70 | "git": { 71 | "repo_password": "", 72 | "repo_type": "", 73 | "repo_username": "", 74 | "ssh_key": "" 75 | }, 76 | "meta": { 77 | "filename": "info_orbs.kicad_prl", 78 | "version": 3 79 | }, 80 | "project": { 81 | "files": [] 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /Info-Orbs/include/widgets/clockWidget.h: -------------------------------------------------------------------------------- 1 | #ifndef CLOCKWIDGET_H 2 | #define CLOCKWIDGET_H 3 | 4 | #include 5 | #include 6 | 7 | class ClockWidget : public Widget { 8 | public: 9 | ClockWidget(ScreenManager& manager); 10 | ~ClockWidget() override; 11 | void setup() override; 12 | void update(bool force = false) override; 13 | void draw(bool force = false) override; 14 | void changeMode() override; 15 | 16 | private: 17 | void displayDidget(int displayIndex, const String& didget, int font, int fontSize, uint32_t color, bool shadowing); 18 | void displayDidget(int displayIndex, const String& didget, int font, int fontSize, uint32_t color); 19 | void displaySeconds(int displayIndex, int seconds, int color); 20 | void displayAmPm(uint32_t color); 21 | 22 | time_t m_unixEpoch; 23 | int m_timeZoneOffset; 24 | 25 | // Delays for setting how often certain screens/functions are refreshed/checked. These include both the frequency which they need to be checked and a varibale to store the last checked value. 26 | long m_secondTimer = 2000; // this time is used to refressh/check the clock every second. 27 | long m_secondTimerPrev = 0; 28 | 29 | // WiFiUDP m_udp; 30 | // NTPClient* m_timeClient{ nullptr }; 31 | 32 | int m_minuteSingle; 33 | int m_hourSingle; 34 | int m_secondSingle; 35 | 36 | int m_lastMinuteSingle{-1}; 37 | int m_lastHourSingle{-1}; 38 | int m_lastSecondSingle{-1}; 39 | 40 | // Didgets 41 | String m_display1Didget; 42 | String m_lastDisplay1Didget{"-1"}; 43 | String m_display2Didget; 44 | String m_lastDisplay2Didget{"-1"}; 45 | // Display 3 is : 46 | String m_display4Didget; 47 | String m_lastDisplay4Didget{"-1"}; 48 | String m_display5Didget; 49 | String m_lastDisplay5Didget{"-1"}; 50 | }; 51 | #endif // CLOCKWIDGET_H 52 | -------------------------------------------------------------------------------- /Info-Orbs/include/model/webDataModel.h: -------------------------------------------------------------------------------- 1 | #ifndef WEB_DATA_MODEL_H 2 | #define WEB_DATA_MODEL_H 3 | 4 | #include 5 | #include 6 | 7 | #include "webDataElementModel.h" 8 | 9 | class WebDataModel { 10 | public: 11 | virtual ~WebDataModel() = default; 12 | String getLabel(); 13 | void setLabel(String label); 14 | String getData(); 15 | void setData(String data, int32_t defaultColor, int32_t defaultBackground); 16 | void setData(JsonArray data, int32_t defaultColor, int32_t defaultBackground); 17 | const WebDataElementModel& getElement(int index); 18 | int32_t getElementsCount(); 19 | void setElementsCount(int32_t elementsCount); 20 | void initElements(int32_t count); 21 | // void setElements(WebDataElementModel *element); 22 | int32_t getLabelColor(); 23 | void setLabelColor(int32_t color); 24 | void setLabelColor(String color); 25 | int32_t getDataColor(); 26 | void setDataColor(int32_t color); 27 | void setDataColor(String color); 28 | int32_t getBackgroundColor(); 29 | void setBackgroundColor(int32_t background); 30 | void setBackgroundColor(String background); 31 | 32 | bool isFullDraw(); 33 | void setFullDrawStatus(bool fullDraw); 34 | 35 | bool isChanged(); 36 | void setChangedStatus(bool changed); 37 | 38 | bool isInitialized(); 39 | void setInitializedStatus(bool initialized); 40 | 41 | void parseData(const JsonObject& doc, int32_t defaultColor, int32_t defaultBackground); 42 | void draw(TFT_eSPI& display); 43 | 44 | private: 45 | bool m_isInitialized = false; 46 | String m_label = ""; 47 | String m_data = ""; 48 | WebDataElementModel* m_elements = nullptr; 49 | int m_elementsCount = 0; 50 | int32_t m_labelColor = -1; 51 | int32_t m_color = -1; 52 | int32_t m_background = -1; 53 | bool m_fullDraw = false; 54 | bool m_changed = false; 55 | }; 56 | #endif 57 | -------------------------------------------------------------------------------- /Info-Orbs/src/model/webDataElementLineModel.cpp: -------------------------------------------------------------------------------- 1 | #include "model/webDataElementLineModel.h" 2 | 3 | int32_t WebDataElementLineModel::getX() { 4 | return m_x; 5 | } 6 | 7 | void WebDataElementLineModel::setX(int32_t x) { 8 | if (m_x != x) { 9 | m_x = x; 10 | m_changed = true; 11 | } 12 | } 13 | 14 | int32_t WebDataElementLineModel::getY() { 15 | return m_y; 16 | } 17 | 18 | void WebDataElementLineModel::setY(int32_t y) { 19 | if (m_y != y) { 20 | m_y = y; 21 | m_changed = true; 22 | } 23 | } 24 | 25 | int32_t WebDataElementLineModel::getX2() { 26 | return m_x2; 27 | } 28 | 29 | void WebDataElementLineModel::setX2(int32_t x) { 30 | if (m_x2 != x) { 31 | m_x2 = x; 32 | m_changed = true; 33 | } 34 | } 35 | 36 | int32_t WebDataElementLineModel::getY2() { 37 | return m_y2; 38 | } 39 | 40 | void WebDataElementLineModel::setY2(int32_t y) { 41 | if (m_y2 != y) { 42 | m_y2 = y; 43 | m_changed = true; 44 | } 45 | } 46 | 47 | void WebDataElementLineModel::setColor(int32_t color) { 48 | if (m_color != color) { 49 | m_color = color; 50 | m_changed = true; 51 | } 52 | } 53 | 54 | void WebDataElementLineModel::setColor(String color) { 55 | setColor(Utils::stringToColor(color)); 56 | } 57 | 58 | int32_t WebDataElementLineModel::getColor() { 59 | return m_color; 60 | } 61 | 62 | void WebDataElementLineModel::parseData(const JsonObject& doc, int32_t defaultColor, int32_t defaultBackground) { 63 | if (doc["x"].is()) { 64 | setX(doc["x"].as()); 65 | } 66 | if (doc["y"].is()) { 67 | setY(doc["y"].as()); 68 | } 69 | if (doc["x2"].is()) { 70 | setX2(doc["x2"].as()); 71 | } 72 | if (doc["y2"].is()) { 73 | setY2(doc["y2"].as()); 74 | } 75 | if (const char* color = doc["color"]) { 76 | setColor(color); 77 | } else { 78 | setColor(defaultColor); 79 | } 80 | } 81 | 82 | void WebDataElementLineModel::draw(TFT_eSPI& display) { 83 | display.drawLine(getX(), getY(), getX2(), getY2(), getColor()); 84 | } 85 | -------------------------------------------------------------------------------- /Info-Orbs/include/model/weatherDataModel.h: -------------------------------------------------------------------------------- 1 | #ifndef WEAHTERDATA_MODEL_H 2 | #define WEAHTERDATA_MODEL_H 3 | 4 | #include 5 | #include 6 | 7 | #define NaN -1024.0 8 | class WeatherDataModel { 9 | public: 10 | WeatherDataModel(); 11 | WeatherDataModel &setCityName(String city); 12 | String getCityName(); 13 | WeatherDataModel &setCurrentText(String text); 14 | String getCurrentText(); 15 | WeatherDataModel &setCurrentIcon(String icon); 16 | String getCurrentIcon(); 17 | WeatherDataModel &setCurrentTemperature(float degrees); 18 | float getCurrentTemperature(); 19 | String getCurrentTemperature(int8_t digits); 20 | WeatherDataModel &setTodayHigh(float high); 21 | float getTodayHigh(); 22 | String getTodayHigh(int8_t digits); 23 | WeatherDataModel &setTodayLow(float low); 24 | float getTodayLow(); 25 | String getTodayLow(int8_t digits); 26 | 27 | WeatherDataModel &setDaysIcons(String icons[3]); 28 | String &getDaysIcons(); 29 | WeatherDataModel &setDayIcon(int num, String icon); 30 | String getDayIcon(int num); 31 | 32 | WeatherDataModel &setDaysHighs(float highs[3]); 33 | float &getDaysHighs(); 34 | WeatherDataModel &setDayHigh(int num, float high); 35 | float getDayHigh(int num); 36 | String getDayHigh(int8_t num, int8_t digits); 37 | 38 | WeatherDataModel &setDaysLows(float lows[3]); 39 | float &getDaysLows(); 40 | WeatherDataModel &setDayLow(int num, float low); 41 | float getDayLow(int num); 42 | String getDayLow(int8_t num, int8_t digits); 43 | 44 | bool isChanged(); 45 | WeatherDataModel &setChangedStatus(bool changed); 46 | 47 | private: 48 | String m_cityName; 49 | String m_currentWeatherText = ""; // Weather Description 50 | String m_currentWeatherIcon = ""; // Text refrence for weather icon 51 | float m_currentWeatherDeg = 0.0; 52 | float m_todayHigh = 0.0; 53 | float m_todayLow = 0.0; 54 | 55 | String m_daysIcons[3] = { "", "", "" }; 56 | float m_daysHigh[3] = { NaN, NaN, NaN }; 57 | float m_daysLow[3] = { NaN, NaN, NaN }; 58 | 59 | bool m_changed = false; 60 | }; 61 | #endif // WEAHTER_DATA_MODEL_H 62 | -------------------------------------------------------------------------------- /Info-Orbs/include/widgets/weatherWidget.h: -------------------------------------------------------------------------------- 1 | #ifndef WEAHTERWIDGET_H 2 | #define WEAHTERWIDGET_H 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | #include "model/weatherDataModel.h" 13 | 14 | class WeatherWidget : public Widget { 15 | public: 16 | WeatherWidget(ScreenManager& manager); 17 | ~WeatherWidget() override; 18 | void setup() override; 19 | void update(bool force = false) override; 20 | void draw(bool force = false) override; 21 | void changeMode() override; 22 | 23 | private: 24 | void displayClock(int displayIndex, uint32_t background, uint32_t textColor); 25 | 26 | void showJPG(int displayIndex, int x, int y, const byte jpgData[], int size, int scale); 27 | void drawWeatherIcon(String condition, int displayIndex, int x, int y, int scale); 28 | void singleWeatherDeg(int displayIndex, uint32_t background, uint32_t textColor); 29 | void weatherText(int displayIndex, int16_t background, int16_t textColor); 30 | void threeDayWeather(int displayIndex); 31 | bool getWeatherData(); 32 | int getClockStamp(); 33 | int drawDegrees(String number, int x, int y, uint8_t font, uint8_t size, uint8_t outerRadius, uint8_t innerRadius, int16_t textColor, int16_t backgroundColor); 34 | 35 | GlobalTime* m_time; 36 | int8_t m_mode; 37 | 38 | const long m_updateDelay = 600000; // weather refresh rate 39 | unsigned long m_lastUpdate = 0; 40 | 41 | const int centre = 120; // centre location of the screen(240x240) 42 | 43 | int m_clockStamp = 0; 44 | 45 | WeatherDataModel model; 46 | 47 | String weatherLocation = WEATHER_LOCAION; 48 | #ifdef WEATHER_UNITS_METRIC 49 | String weatherUnits = "metric"; 50 | #else 51 | String weatherUnits = "us"; 52 | #endif 53 | String weatherApiKey = WEATHER_API_KEY; 54 | 55 | String httpRequestAddress = "https://weather.visualcrossing.com/VisualCrossingWebServices/rest/services/timeline/" + 56 | weatherLocation + "/next3days?key=" + weatherApiKey + "&unitGroup=" + weatherUnits + 57 | "&include=days,current&iconSet=icons1"; 58 | 59 | const int MODE_HIGHS = 0; 60 | const int MODE_LOWS = 1; 61 | }; 62 | #endif // WEAHTERWIDGET_H 63 | -------------------------------------------------------------------------------- /Info-Orbs/src/model/webDataElementCircleModel.cpp: -------------------------------------------------------------------------------- 1 | #include "model/webDataElementCircleModel.h" 2 | 3 | int32_t WebDataElementCircleModel::getX() { 4 | return m_x; 5 | } 6 | 7 | void WebDataElementCircleModel::setX(int32_t x) { 8 | if (m_x != x) { 9 | m_x = x; 10 | m_changed = true; 11 | } 12 | } 13 | 14 | int32_t WebDataElementCircleModel::getY() { 15 | return m_y; 16 | } 17 | 18 | void WebDataElementCircleModel::setY(int32_t y) { 19 | if (m_y != y) { 20 | m_y = y; 21 | m_changed = true; 22 | } 23 | } 24 | 25 | int32_t WebDataElementCircleModel::getRadius() { 26 | return m_radius; 27 | } 28 | 29 | void WebDataElementCircleModel::setRadius(int32_t radius) { 30 | if (m_radius != radius) { 31 | m_radius = radius; 32 | m_changed = true; 33 | } 34 | } 35 | 36 | bool WebDataElementCircleModel::getFilled() { 37 | return m_filled; 38 | } 39 | 40 | void WebDataElementCircleModel::setFilled(bool filled) { 41 | if (m_filled != filled) { 42 | m_filled = filled; 43 | m_changed = true; 44 | } 45 | } 46 | void WebDataElementCircleModel::setColor(int32_t color) { 47 | if (m_color != color) { 48 | m_color = color; 49 | m_changed = true; 50 | } 51 | } 52 | 53 | void WebDataElementCircleModel::setColor(String color) { 54 | setColor(Utils::stringToColor(color)); 55 | } 56 | 57 | int32_t WebDataElementCircleModel::getColor() { 58 | return m_color; 59 | } 60 | 61 | void WebDataElementCircleModel::parseData(const JsonObject& doc, int32_t defaultColor, int32_t defaultBackground) { 62 | if (doc["x"].is()) { 63 | setX(doc["x"].as()); 64 | } 65 | if (doc["y"].is()) { 66 | setY(doc["y"].as()); 67 | } 68 | if (doc["radius"].is()) { 69 | setRadius(doc["radius"].as()); 70 | } 71 | if (doc["filled"].is()) { 72 | setFilled(doc["filled"].as()); 73 | } 74 | if (const char* color = doc["color"]) { 75 | setColor(color); 76 | } else { 77 | setColor(defaultColor); 78 | } 79 | } 80 | 81 | void WebDataElementCircleModel::draw(TFT_eSPI& display) { 82 | if (getFilled()) { 83 | display.fillCircle(getX(), getY(), getRadius(), getColor()); 84 | } else { 85 | display.drawCircle(getX(), getY(), getRadius(), getColor()); 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /Info-Orbs/lib/screenManager/screenManager.cpp: -------------------------------------------------------------------------------- 1 | #include "screenManager.h" 2 | #include 3 | 4 | ScreenManager::ScreenManager(TFT_eSPI &tft) : m_tft(tft) { 5 | 6 | for (int i = 0; i < NUM_SCREENS; i++) { 7 | pinMode(m_screen_cs[i], OUTPUT); 8 | digitalWrite(m_screen_cs[i], LOW); 9 | } 10 | 11 | m_tft.init(); 12 | m_tft.setRotation(INVERTED_ORBS ? 2 : 0); 13 | m_tft.fillScreen(TFT_WHITE); 14 | m_tft.setTextDatum(MC_DATUM); 15 | reset(); 16 | 17 | Serial.println("ScreenManager initialized"); 18 | Serial.println("TFT_MOSI:" + String(TFT_MOSI)); 19 | Serial.println("TFT_MISO:" + String(TFT_MISO)); 20 | Serial.println("TFT_SCLK:" + String(TFT_SCLK)); 21 | Serial.println("TFT_CS:" + String(TFT_CS)); 22 | Serial.println("TFT_DC:" + String(TFT_DC)); 23 | Serial.println("TFT_RST:" + String(TFT_RST)); 24 | Serial.println("SCREEN_1_CS:" + String(SCREEN_1_CS)); 25 | Serial.println("SCREEN_2_CS:" + String(SCREEN_2_CS)); 26 | Serial.println("SCREEN_3_CS:" + String(SCREEN_3_CS)); 27 | Serial.println("SCREEN_4_CS:" + String(SCREEN_4_CS)); 28 | Serial.println("SCREEN_5_CS:" + String(SCREEN_5_CS)); 29 | } 30 | 31 | TFT_eSPI &ScreenManager::getDisplay() { 32 | return m_tft; 33 | } 34 | 35 | // Selects a single screen 36 | void ScreenManager::selectScreen(int screen) { 37 | for (int i = 0; i < NUM_SCREENS; i++) { 38 | int currentDisplay = INVERTED_ORBS ? NUM_SCREENS - i - 1 : i; 39 | digitalWrite(m_screen_cs[currentDisplay], i == screen ? LOW : HIGH); 40 | } 41 | } 42 | 43 | // Fills all screens with a color 44 | void ScreenManager::fillAllScreens(uint32_t color) { 45 | selectAllScreens(); 46 | m_tft.fillScreen(color); 47 | reset(); 48 | } 49 | 50 | // clears all screens by resetting them to black 51 | void ScreenManager::clearAllScreens() { 52 | fillAllScreens(TFT_BLACK); 53 | } 54 | 55 | // clears one screens by resetting it to black 56 | void ScreenManager::clearScreen(int screen) { 57 | selectScreen(screen); 58 | m_tft.fillScreen(TFT_BLACK); 59 | } 60 | 61 | // Selects all screens 62 | // I don't think that state should be used, It's kinda wierd saying "ow select 63 | // all the screens to "off" 64 | void ScreenManager::selectAllScreens() { 65 | for (int i = 0; i < NUM_SCREENS; i++) { 66 | digitalWrite(m_screen_cs[i], LOW); 67 | } 68 | } 69 | 70 | void ScreenManager::reset() { 71 | for (int i = 0; i < NUM_SCREENS; i++) { 72 | digitalWrite(m_screen_cs[i], HIGH); 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /Info-Orbs/src/model/stockDataModel.cpp: -------------------------------------------------------------------------------- 1 | #include "model/stockDataModel.h" 2 | #include "utils.h" 3 | #include 4 | 5 | StockDataModel::StockDataModel() { 6 | } 7 | 8 | StockDataModel &StockDataModel::setSymbol(String symbol) { 9 | if (m_symbol != symbol) { 10 | m_symbol = String(symbol); 11 | // this is not a regular data field so do not mark changed when set 12 | } 13 | return *this; 14 | } 15 | String StockDataModel::getSymbol() { 16 | return m_symbol; 17 | } 18 | StockDataModel &StockDataModel::setCurrentPrice(float currentPrice) { 19 | if (m_currentPrice != currentPrice) { 20 | m_currentPrice = currentPrice; 21 | m_changed = true; 22 | } 23 | return *this; 24 | } 25 | float StockDataModel::getCurrentPrice() { 26 | return m_currentPrice; 27 | } 28 | String StockDataModel::getCurrentPrice(int8_t digits) { 29 | return Utils::formatFloat(m_currentPrice, digits); 30 | } 31 | 32 | StockDataModel &StockDataModel::setVolume(float volume) { 33 | if (m_volume != volume) { 34 | m_volume = volume; 35 | m_changed = true; 36 | } 37 | return *this; 38 | } 39 | 40 | float StockDataModel::getVolume() { 41 | return m_volume; 42 | } 43 | 44 | String StockDataModel::getVolume(int8_t digits) { 45 | return Utils::formatFloat(m_volume, digits); 46 | } 47 | 48 | StockDataModel &StockDataModel::setPriceChange(float priceChange) { 49 | if (m_priceChange != priceChange) { 50 | m_priceChange = priceChange; 51 | m_changed = true; 52 | } 53 | return *this; 54 | } 55 | float StockDataModel::getPriceChange() { 56 | return m_priceChange; 57 | } 58 | String StockDataModel::getPriceChange(int8_t digits) { 59 | return Utils::formatFloat(m_priceChange, digits); 60 | } 61 | 62 | StockDataModel &StockDataModel::setPercentChange(float percentChange) { 63 | if (m_percentChange != percentChange) { 64 | m_percentChange = percentChange; 65 | m_changed = true; 66 | } 67 | return *this; 68 | } 69 | float StockDataModel::getPercentChange() { 70 | return m_percentChange; 71 | } 72 | 73 | String StockDataModel::getPercentChange(int8_t digits) { 74 | return Utils::formatFloat(m_percentChange*100, digits); 75 | } 76 | 77 | bool StockDataModel::isChanged() { 78 | return m_changed; 79 | } 80 | StockDataModel &StockDataModel::setChangedStatus(bool changed) { 81 | m_changed = changed; 82 | return *this; 83 | } 84 | -------------------------------------------------------------------------------- /Info-Orbs/lib/widget/widgetSet.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | WidgetSet::WidgetSet(ScreenManager *sm) : m_screenManager(sm) { 4 | 5 | } 6 | void WidgetSet::add(Widget *widget) { 7 | if (m_widgetCount == MAX_WIDGETS) { 8 | Serial.println("MAX WIDGETS UNABLE TO ADD"); 9 | return; 10 | } 11 | m_widgets[m_widgetCount] = widget; 12 | m_widgets[m_widgetCount]->setup(); 13 | m_widgetCount++; 14 | 15 | } 16 | 17 | void WidgetSet::drawCurrent() { 18 | if (m_clearScreensOnDrawCurrent) { 19 | m_screenManager->clearAllScreens(); 20 | m_clearScreensOnDrawCurrent = false; 21 | } 22 | m_widgets[m_currentWidget]->draw(); 23 | } 24 | void WidgetSet::updateCurrent() { 25 | m_widgets[m_currentWidget]->update(); 26 | } 27 | 28 | Widget *WidgetSet::getCurrent() { 29 | return m_widgets[m_currentWidget]; 30 | } 31 | 32 | void WidgetSet::changeMode() { 33 | m_widgets[m_currentWidget]->changeMode(); 34 | } 35 | 36 | void WidgetSet::setClearScreensOnDrawCurrent() { 37 | m_clearScreensOnDrawCurrent = true; 38 | } 39 | 40 | void WidgetSet::next() { 41 | m_currentWidget++; 42 | if (m_currentWidget >= m_widgetCount) { 43 | m_currentWidget = 0; 44 | } 45 | switchWidget(); 46 | } 47 | 48 | void WidgetSet::prev() { 49 | m_currentWidget--; 50 | if (m_currentWidget < 0) { 51 | m_currentWidget = m_widgetCount-1; 52 | } 53 | switchWidget(); 54 | } 55 | 56 | void WidgetSet::switchWidget() { 57 | m_screenManager->clearAllScreens(); 58 | getCurrent()->setup(); 59 | getCurrent()->draw(true); 60 | } 61 | 62 | void WidgetSet::showLoading() { 63 | // display loading screen here 64 | m_screenManager->fillAllScreens(TFT_BLACK); 65 | m_screenManager->selectScreen(2); 66 | 67 | TFT_eSPI &display = m_screenManager->getDisplay(); 68 | 69 | display.fillScreen(TFT_BLACK); 70 | display.setTextColor(TFT_WHITE); 71 | display.setTextSize(3); // Set text size 72 | 73 | // Calculate center positions 74 | int centre = display.width() / 2; 75 | display.drawString("Loading Data", centre, centre, 1); 76 | } 77 | 78 | void WidgetSet::updateAll() { 79 | for (int8_t i; iupdate(); 82 | } 83 | } 84 | 85 | bool WidgetSet::initialUpdateDone() { 86 | return m_initialized; 87 | } 88 | 89 | void WidgetSet::initializeAllWidgetsData() { 90 | showLoading(); 91 | updateAll(); 92 | m_initialized = true; 93 | } 94 | -------------------------------------------------------------------------------- /Info-Orbs/src/widgets/webDataWidget.cpp: -------------------------------------------------------------------------------- 1 | 2 | #include "widgets/webDataWidget.h" 3 | 4 | WebDataWidget::WebDataWidget(ScreenManager &manager, String url) : Widget(manager) { 5 | httpRequestAddress = url; 6 | 7 | m_lastUpdate = 0; 8 | for (int i = 0; i < 5; i++) { 9 | m_obj[i] = WebDataModel(); 10 | } 11 | } 12 | 13 | WebDataWidget::~WebDataWidget() { 14 | } 15 | 16 | void WebDataWidget::setup() { 17 | } 18 | 19 | void WebDataWidget::changeMode() { 20 | } 21 | 22 | void WebDataWidget::draw(bool force) { 23 | for (int i = 0; i < 5; i++) { 24 | WebDataModel *data = &m_obj[i]; 25 | if (force) { 26 | data->setInitializedStatus(false); 27 | } 28 | if (data->isChanged() || force) { 29 | m_manager.selectScreen(i); 30 | data->draw(m_manager.getDisplay()); 31 | 32 | data->setChangedStatus(false); 33 | } 34 | } 35 | } 36 | 37 | void WebDataWidget::update(bool force) { 38 | if (force || m_lastUpdate == 0 || (millis() - m_lastUpdate) >= m_updateDelay) { 39 | HTTPClient http; 40 | http.begin(httpRequestAddress); 41 | int httpCode = http.GET(); 42 | 43 | if (httpCode > 0) { // Check for the returning code 44 | JsonDocument doc; 45 | DeserializationError error = deserializeJson(doc, http.getString()); 46 | if (!error) { 47 | if (doc["interval"].is()) { 48 | m_updateDelay = doc["interval"]; 49 | } 50 | JsonVariant array; 51 | if (doc["displays"].is()) { 52 | array = doc["displays"].as(); 53 | } else { 54 | // handle legacy response that doesn't have response level data 55 | array = doc.as(); 56 | } 57 | for (int i = 0; i < array.size(); i++) { 58 | m_obj[i].parseData(array[i].as(), m_defaultColor, m_defaultBackground); 59 | } 60 | m_lastUpdate = millis(); 61 | } else { 62 | // Handle JSON deserialization error 63 | Serial.println("deserializeJson() failed"); 64 | } 65 | } else { 66 | // Handle HTTP request error 67 | Serial.printf("HTTP request failed, error: %s\n", http.errorToString(httpCode).c_str()); 68 | } 69 | http.end(); 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /Info-Orbs/src/model/webDataElementModel.cpp: -------------------------------------------------------------------------------- 1 | #include "model/webDataElementModel.h" 2 | 3 | #include "model/webDataElementArcModel.h" 4 | #include "model/webDataElementCharacterModel.h" 5 | #include "model/webDataElementCircleModel.h" 6 | #include "model/webDataElementImageModel.h" 7 | #include "model/webDataElementLineModel.h" 8 | #include "model/webDataElementRectangleModel.h" 9 | #include "model/webDataElementTextModel.h" 10 | #include "model/webDataElementTriangleModel.h" 11 | 12 | void WebDataElementModel::setType(const String& type) { 13 | if (type == "text") { 14 | m_type = TEXT; 15 | } else if (type == "rectangle") { 16 | m_type = RECTANGLE; 17 | } else if (type == "character") { 18 | m_type = CHARACTER; 19 | } else if (type == "triangle") { 20 | m_type = TRIANGLE; 21 | } else if (type == "circle") { 22 | m_type = CIRCLE; 23 | } else if (type == "arc") { 24 | m_type = ARC; 25 | } else if (type == "line") { 26 | m_type = LINE; 27 | } else if (type == "image") { 28 | m_type = IMAGE; 29 | } else { 30 | m_type = OTHER; 31 | } 32 | } 33 | 34 | WebDataElementModelTypes WebDataElementModel::getType() { 35 | return m_type; 36 | } 37 | 38 | bool WebDataElementModel::isChanged() { 39 | return m_changed; 40 | } 41 | void WebDataElementModel::setChangedStatus(bool changed) { 42 | m_changed = changed; 43 | } 44 | 45 | void WebDataElementModel::parseData(JsonObject doc, int32_t defaultColor, int32_t defaultBackground) { 46 | delete m_element; 47 | m_element = nullptr; 48 | 49 | if (const char* type = doc["type"]) { 50 | setType(type); 51 | switch (getType()) { 52 | case TEXT: 53 | m_element = new WebDataElementTextModel(); 54 | break; 55 | case CHARACTER: 56 | m_element = new WebDataElementCharacterModel(); 57 | break; 58 | case LINE: 59 | m_element = new WebDataElementLineModel(); 60 | break; 61 | case RECTANGLE: 62 | m_element = new WebDataElementRectangleModel(); 63 | break; 64 | case TRIANGLE: 65 | m_element = new WebDataElementTriangleModel(); 66 | break; 67 | case CIRCLE: 68 | m_element = new WebDataElementCircleModel(); 69 | break; 70 | case ARC: 71 | m_element = new WebDataElementArcModel(); 72 | break; 73 | case IMAGE: 74 | m_element = new WebDataElementImageModel(); 75 | break; 76 | } 77 | } 78 | if (m_element != nullptr) { 79 | m_element->parseData(doc, defaultColor, defaultBackground); 80 | } 81 | } 82 | 83 | void WebDataElementModel::draw(TFT_eSPI& display) { 84 | if (m_element != nullptr && getType() != OTHER) { 85 | m_element->draw(display); 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /Info-Orbs/lib/config/config.h.template: -------------------------------------------------------------------------------- 1 | #ifndef CONFIG_H 2 | #define CONFIG_H 3 | 4 | 5 | // ============= CONFIGURE THESE FIELDS BEFORE FLASHING ==================================================== 6 | #define WIFI_SSID "WIFINAME" // wifi name (please use 2.4gz network) 7 | #define WIFI_PASS "WIFIPASS" // wifi password 8 | #define TIMEZONE_API_LOCATION "America/Vancouver" // Use timezone from this list: https://timezonedb.com/time-zones 9 | #define WEATHER_LOCAION "Victoria, BC" //city/state for the weather 10 | #define STOCK_TICKER_LIST "SPY,VT,GOOG,TSLA,GME" // Choose your 5 stokcs to display on the stock tracker 11 | #define WEATHER_UNITS_METRIC //Comment this line out(or delete it) if you want imperial units for the weather 12 | #define FORMAT_24_HOUR false // toggle 24 hour clock vs 12 hour clock, chnage between true/false 13 | #define SHOW_AM_PM_INDICATOR false // am/pm on the clock if using 12 hour 14 | #define SHOW_SECOND_TICKS true // ticking indeicator on the centre clock 15 | #define INVERTED_ORBS false // Set to true if using InfoOrbs upside down. Inverts screens and re-orders screens and buttons. 16 | //#define WEB_DATA_WIDGET_URL "" // use this to make your own widgets using an API/Webdata source 17 | //#define WEB_DATA_STOCK_WIDGET_URL "http:///stocks.php?stocks=SPY,VT,GOOG,TSLA,GME" // use this as an alternative to the stock ticker widget 18 | // ============= END CONFIG ============================================================================== 19 | 20 | 21 | #undef TFT_MOSI 22 | #undef TFT_MISO 23 | #undef TFT_SCLK 24 | #undef TFT_CS 25 | #undef TFT_DC 26 | 27 | 28 | #undef ILI9341_DRIVER 29 | #define GC9A01_DRIVER 30 | 31 | #define TFT_SDA_READ 32 | 33 | #define TFT_MOSI 17 34 | #define TFT_MISO -1 35 | #define TFT_SCLK 23 36 | #define TFT_CS 15 37 | #define TFT_DC 19 38 | #define TFT_RST 18 39 | 40 | #define SCREEN_1_CS 13 41 | #define SCREEN_2_CS 33 42 | #define SCREEN_3_CS 32 43 | #define SCREEN_4_CS 25 44 | #define SCREEN_5_CS 21 45 | 46 | #if INVERTED_ORBS 47 | #define BUTTON_OK 27 48 | #define BUTTON_LEFT 14 49 | #define BUTTON_RIGHT 26 50 | #else 51 | #define BUTTON_OK 27 52 | #define BUTTON_LEFT 26 53 | #define BUTTON_RIGHT 14 54 | #endif 55 | 56 | #define BUTTON_MODE INPUT_PULLDOWN 57 | #define BUSY_PIN 2 58 | 59 | 60 | #define NTP_SERVER "pool.ntp.org" 61 | 62 | #define SCREEN_SIZE 240 63 | #define TFT_WIDTH SCREEN_SIZE 64 | #define TFT_HEIGHT SCREEN_SIZE 65 | #define LOAD_GLCD 66 | #define LOAD_FONT2 67 | #define LOAD_FONT4 68 | #define LOAD_FONT6 69 | #define LOAD_FONT7 70 | #define LOAD_FONT8N 71 | #define LOAD_GFXFF 72 | #define SMOOTH_FONT 73 | #define SPI_FREQUENCY 27000000 74 | 75 | #define SHADOWING 1 76 | 77 | #define TIMEZONE_API_KEY "97R9WKDPBLIO" 78 | #define TIMEZONE_API_URL "http://api.timezonedb.com/v2.1/get-time-zone" 79 | #define WEATHER_API_KEY "XW2RDGD6XK432AF25BNK2A3C7" 80 | 81 | 82 | #define BG_COLOR 0x20a1 // clock shadow colour(Light brown) 83 | #define FOREGROUND_COLOR 0xfc80 // orange for clock 84 | 85 | #define MAX_RETRIES 3 86 | 87 | #endif 88 | -------------------------------------------------------------------------------- /Info-Orbs/src/model/webDataElementRectangleModel.cpp: -------------------------------------------------------------------------------- 1 | #include "model/webDataElementRectangleModel.h" 2 | 3 | int32_t WebDataElementRectangleModel::getX() { 4 | return m_x; 5 | } 6 | 7 | void WebDataElementRectangleModel::setX(int32_t x) { 8 | if (m_x != x) { 9 | m_x = x; 10 | m_changed = true; 11 | } 12 | } 13 | 14 | int32_t WebDataElementRectangleModel::getY() { 15 | return m_y; 16 | } 17 | 18 | void WebDataElementRectangleModel::setY(int32_t y) { 19 | if (m_y != y) { 20 | m_y = y; 21 | m_changed = true; 22 | } 23 | } 24 | 25 | int32_t WebDataElementRectangleModel::getWidth() { 26 | return m_width; 27 | } 28 | 29 | void WebDataElementRectangleModel::setWidth(int32_t width) { 30 | if (m_width != width) { 31 | m_width = width; 32 | m_changed = true; 33 | } 34 | } 35 | 36 | int32_t WebDataElementRectangleModel::getHeight() { 37 | return m_height; 38 | } 39 | 40 | void WebDataElementRectangleModel::setHeight(int32_t height) { 41 | if (m_height != height) { 42 | m_height = height; 43 | m_changed = true; 44 | } 45 | } 46 | 47 | bool WebDataElementRectangleModel::getFilled() { 48 | return m_filled; 49 | } 50 | 51 | void WebDataElementRectangleModel::setFilled(bool filled) { 52 | if (m_filled != filled) { 53 | m_filled = filled; 54 | m_changed = true; 55 | } 56 | } 57 | 58 | void WebDataElementRectangleModel::setColor(int32_t color) { 59 | if (m_color != color) { 60 | m_color = color; 61 | m_changed = true; 62 | } 63 | } 64 | 65 | void WebDataElementRectangleModel::setColor(String color) { 66 | setColor(Utils::stringToColor(color)); 67 | } 68 | 69 | int32_t WebDataElementRectangleModel::getColor() { 70 | return m_color; 71 | } 72 | 73 | void WebDataElementRectangleModel::parseData(const JsonObject& doc, int32_t defaultColor, int32_t defaultBackground) { 74 | if (doc["x1"].is()) { 75 | setX(doc["x1"].as()); 76 | } else if (doc["x"].is()) { 77 | setX(doc["x"].as()); 78 | } 79 | if (doc["y1"].is()) { 80 | setY(doc["y1"].as()); 81 | } else if (doc["y"].is()) { 82 | setY(doc["y"].as()); 83 | } 84 | if (doc["x2"].is()) { 85 | setWidth(doc["x2"].as() - getX()); 86 | } 87 | if (doc["y2"].is()) { 88 | setWidth(doc["y2"].as() - getX()); 89 | } 90 | if (int32_t y2 = doc["y2"]) { 91 | setHeight(y2 - getY()); 92 | } 93 | if (doc["height"].is()) { 94 | setHeight(doc["height"].as()); 95 | } 96 | if (doc["width"].is()) { 97 | setWidth(doc["width"].as()); 98 | } 99 | if (doc["filled"].is()) { 100 | setFilled(doc["filled"].as()); 101 | } 102 | if (const char* color = doc["color"]) { 103 | setColor(color); 104 | } else { 105 | setColor(defaultColor); 106 | } 107 | } 108 | 109 | void WebDataElementRectangleModel::draw(TFT_eSPI& display) { 110 | if (getFilled()) { 111 | display.fillRect(getX(), getY(), getWidth(), getHeight(), getColor()); 112 | } else { 113 | display.drawRect(getX(), getY(), getWidth(), getHeight(), getColor()); 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /Info-Orbs/src/main.cpp: -------------------------------------------------------------------------------- 1 | #include "core/wifiWidget.h" 2 | #include "widgetSet.h" 3 | #include "screenManager.h" 4 | #include "widgets/clockWidget.h" 5 | #include "widgets/weatherWidget.h" 6 | #include "widgets/webDataWidget.h" 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | TFT_eSPI tft = TFT_eSPI(); 14 | 15 | // Button states 16 | bool lastButtonOKState = HIGH; 17 | bool lastButtonLeftState = HIGH; 18 | bool lastButtonRightState = HIGH; 19 | 20 | Button buttonOK(BUTTON_OK); 21 | Button buttonLeft(BUTTON_LEFT); 22 | Button buttonRight(BUTTON_RIGHT); 23 | 24 | GlobalTime *globalTime; // Initialize the global time 25 | 26 | String connectingString{""}; 27 | 28 | WifiWidget *wifiWidget{ nullptr }; 29 | 30 | int connectionTimer{0}; 31 | const int connectionTimeout{10000}; 32 | bool isConnected{true}; 33 | 34 | bool tft_output(int16_t x, int16_t y, uint16_t w, uint16_t h, uint16_t *bitmap) { 35 | if (y >= tft.height()) 36 | return 0; 37 | tft.pushImage(x, y, w, h, bitmap); 38 | return 1; 39 | } 40 | 41 | ScreenManager* sm; 42 | WidgetSet* widgetSet; 43 | 44 | void setup() { 45 | 46 | buttonLeft.begin(); 47 | buttonOK.begin(); 48 | buttonRight.begin(); 49 | 50 | Serial.begin(115200); 51 | Serial.println(); 52 | Serial.println("Starting up..."); 53 | 54 | sm = new ScreenManager(tft); 55 | sm->selectAllScreens(); 56 | sm->getDisplay().fillScreen(TFT_WHITE); 57 | sm->reset(); 58 | widgetSet = new WidgetSet(sm); 59 | 60 | TJpgDec.setSwapBytes(true); // jpeg rendering setup 61 | TJpgDec.setCallback(tft_output); 62 | 63 | #ifdef GC9A01_DRIVER 64 | Serial.println("GC9A01 Driver"); 65 | #endif 66 | #ifdef ILI9341_DRIVER 67 | Serial.println("ILI9341 Driver"); 68 | #endif 69 | #if HARDWARE == WOKWI 70 | Serial.println("Wokwi Build"); 71 | #endif 72 | 73 | pinMode(BUSY_PIN, OUTPUT); 74 | Serial.println("Connecting to: " + String(WIFI_SSID)); 75 | 76 | wifiWidget = new WifiWidget(*sm); 77 | wifiWidget->setup(); 78 | 79 | globalTime = GlobalTime::getInstance(); 80 | 81 | widgetSet->add(new ClockWidget(*sm)); 82 | widgetSet->add(new StockWidget(*sm)); 83 | widgetSet->add(new WeatherWidget(*sm)); 84 | #ifdef WEB_DATA_WIDGET_URL 85 | widgetSet->add(new WebDataWidget(*sm, WEB_DATA_WIDGET_URL)); 86 | #endif 87 | #ifdef WEB_DATA_STOCK_WIDGET_URL 88 | widgetSet->add(new WebDataWidget(*sm, WEB_DATA_STOCK_WIDGET_URL)); 89 | #endif 90 | } 91 | 92 | void loop() { 93 | if (wifiWidget->isConnected() == false) { 94 | wifiWidget->update(); 95 | wifiWidget->draw(); 96 | widgetSet->setClearScreensOnDrawCurrent(); //clear screen after wifiWidget 97 | delay(100); 98 | } else { 99 | if (!widgetSet->initialUpdateDone()) { 100 | widgetSet->initializeAllWidgetsData(); 101 | } 102 | globalTime->updateTime(); 103 | 104 | if (buttonLeft.pressed()) { 105 | Serial.println("Left button pressed"); 106 | widgetSet->prev(); 107 | } 108 | if (buttonOK.pressed()) { 109 | Serial.println("OK button pressed"); 110 | widgetSet->changeMode(); 111 | } 112 | if (buttonRight.pressed()) { 113 | Serial.println("Right button pressed"); 114 | widgetSet->next(); 115 | } 116 | 117 | widgetSet->updateCurrent(); 118 | widgetSet->drawCurrent(); 119 | } 120 | } 121 | -------------------------------------------------------------------------------- /Info-Orbs/src/core/wifiWidget.cpp: -------------------------------------------------------------------------------- 1 | #include "core/wifiWidget.h" 2 | #include 3 | 4 | WifiWidget::WifiWidget(ScreenManager& manager) : Widget(manager) {} 5 | 6 | WifiWidget::~WifiWidget() {} 7 | 8 | void WifiWidget::setup() { 9 | TFT_eSPI &display = m_manager.getDisplay(); 10 | m_manager.selectAllScreens(); 11 | display.fillScreen(TFT_BLACK); 12 | display.setTextSize(2); 13 | display.setTextColor(TFT_WHITE); 14 | 15 | m_manager.selectScreen(0); 16 | display.drawCentreString("Connecting" + m_connectionString, 120, 80, 1); 17 | 18 | 19 | m_manager.selectScreen(1); 20 | display.drawCentreString("Connecting to", 120, 80, 1); 21 | display.drawCentreString("WiFi..", 120, 100, 1); 22 | display.drawCentreString(WIFI_SSID, 120, 130, 1); 23 | 24 | // Serial.println("Connecting to WiFi.."); 25 | 26 | WiFi.begin(WIFI_SSID, WIFI_PASS); 27 | 28 | Serial.println("Connecting to WiFi.."); 29 | 30 | 31 | 32 | } 33 | 34 | void WifiWidget::update(bool force) { 35 | //force is currently an unhandled due to not knowing what behavior it would change 36 | 37 | if(WiFi.status() == WL_CONNECTED) { 38 | m_isConnected = true; 39 | m_connectionString = "Connected"; 40 | } else { 41 | m_connectionTimer += 500; 42 | m_dotsString += "."; 43 | Serial.print("."); 44 | if(m_dotsString.length() > 3) { 45 | m_dotsString = ""; 46 | } 47 | if(m_connectionTimer > m_connectionTimeout) { 48 | m_connectionFailed = true; 49 | connectionTimedOut(); 50 | } 51 | } 52 | } 53 | 54 | void WifiWidget::draw(bool force) { 55 | //force is currently an unhandled due to not knowing what behavior it would change 56 | 57 | if(!m_isConnected && !m_connectionFailed) { 58 | TFT_eSPI &display = m_manager.getDisplay(); 59 | m_manager.selectScreen(0); 60 | display.fillRect(0, 100, 240, 100, TFT_BLACK); 61 | display.drawCentreString(m_dotsString, 120, 100, 1); 62 | } else if(m_isConnected && !m_hasDisplayedSuccess) { 63 | m_hasDisplayedSuccess = true; 64 | TFT_eSPI &display = m_manager.getDisplay(); 65 | m_manager.selectScreen(0); 66 | display.fillScreen(TFT_BLACK); 67 | display.drawCentreString("Connected", 120, 100, 1); 68 | Serial.println(); 69 | Serial.println("Connected to WiFi"); 70 | m_isConnected = true; 71 | } else if(m_connectionFailed && !m_hasDisplayedError) { 72 | m_hasDisplayedError = true; 73 | TFT_eSPI &display = m_manager.getDisplay(); 74 | m_manager.selectScreen(0); 75 | display.drawCentreString("Connection", 120, 80, 1); 76 | display.fillRect(0, 100, 240, 100, TFT_BLACK); 77 | display.drawCentreString(m_connectionString, 120, 100, 1); 78 | } 79 | } 80 | 81 | void WifiWidget::changeMode() {} 82 | 83 | void WifiWidget::connectionTimedOut() { 84 | switch (WiFi.status()) { 85 | case WL_CONNECTED: 86 | m_connectionString = "Connected"; 87 | break; 88 | case WL_NO_SSID_AVAIL: 89 | m_connectionString = "No SSID available"; 90 | break; 91 | case WL_CONNECT_FAILED: 92 | m_connectionString = "Connection failed"; 93 | break; 94 | case WL_IDLE_STATUS: 95 | m_connectionString = "Idle status"; 96 | break; 97 | case WL_DISCONNECTED: 98 | m_connectionString = "Disconnected"; 99 | break; 100 | default: 101 | m_connectionString = "Unknown"; 102 | break; 103 | } 104 | 105 | // Serial.println("Connection timed out"); 106 | // TFT_eSPI &display = m_manager.getDisplay(); 107 | // m_manager.selectScreen(0); 108 | // display.drawCentreString("Connection", 120, 80, 1); 109 | // display.drawCentreString(m_connectionString, 120, 100, 1); 110 | } 111 | -------------------------------------------------------------------------------- /Info-Orbs/src/model/webDataElementTriangleModel.cpp: -------------------------------------------------------------------------------- 1 | #include "model/webDataElementTriangleModel.h" 2 | 3 | int32_t WebDataElementTriangleModel::getX() { 4 | return m_x; 5 | } 6 | 7 | void WebDataElementTriangleModel::setX(int32_t x) { 8 | if (m_x != x) { 9 | m_x = x; 10 | m_changed = true; 11 | } 12 | } 13 | 14 | int32_t WebDataElementTriangleModel::getY() { 15 | return m_y; 16 | } 17 | 18 | void WebDataElementTriangleModel::setY(int32_t y) { 19 | if (m_y != y) { 20 | m_y = y; 21 | m_changed = true; 22 | } 23 | } 24 | 25 | int32_t WebDataElementTriangleModel::getX2() { 26 | return m_x2; 27 | } 28 | 29 | void WebDataElementTriangleModel::setX2(int32_t x) { 30 | if (m_x2 != x) { 31 | m_x2 = x; 32 | m_changed = true; 33 | } 34 | } 35 | 36 | int32_t WebDataElementTriangleModel::getY2() { 37 | return m_y2; 38 | } 39 | 40 | void WebDataElementTriangleModel::setY2(int32_t y) { 41 | if (m_y2 != y) { 42 | m_y2 = y; 43 | m_changed = true; 44 | } 45 | } 46 | 47 | int32_t WebDataElementTriangleModel::getX3() { 48 | return m_x3; 49 | } 50 | 51 | void WebDataElementTriangleModel::setX3(int32_t x) { 52 | if (m_x3 != x) { 53 | m_x3 = x; 54 | m_changed = true; 55 | } 56 | } 57 | 58 | int32_t WebDataElementTriangleModel::getY3() { 59 | return m_y3; 60 | } 61 | 62 | void WebDataElementTriangleModel::setY3(int32_t y) { 63 | if (m_y3 != y) { 64 | m_y3 = y; 65 | m_changed = true; 66 | } 67 | } 68 | 69 | void WebDataElementTriangleModel::setColor(int32_t color) { 70 | if (m_color != color) { 71 | m_color = color; 72 | m_changed = true; 73 | } 74 | } 75 | 76 | bool WebDataElementTriangleModel::getFilled() { 77 | return m_filled; 78 | } 79 | 80 | void WebDataElementTriangleModel::setFilled(bool filled) { 81 | if (m_filled != filled) { 82 | m_filled = filled; 83 | m_changed = true; 84 | } 85 | } 86 | 87 | void WebDataElementTriangleModel::setColor(String color) { 88 | setColor(Utils::stringToColor(color)); 89 | } 90 | 91 | int32_t WebDataElementTriangleModel::getColor() { 92 | return m_color; 93 | } 94 | 95 | void WebDataElementTriangleModel::parseData(const JsonObject& doc, int32_t defaultColor, int32_t defaultBackground) { 96 | if (doc["x1"].is()) { 97 | setX(doc["x1"].as()); 98 | } else if (doc["x"].is()) { 99 | setX(doc["x"].as()); 100 | } 101 | if (doc["y1"].is()) { 102 | setY(doc["y1"].as()); 103 | } else if (doc["y"].is()) { 104 | setY(doc["y"].as()); 105 | } 106 | if (doc["x2"].is()) { 107 | setX2(doc["x2"].as()); 108 | } 109 | if (doc["y2"].is()) { 110 | setY2(doc["y2"].as()); 111 | } 112 | if (doc["x3"].is()) { 113 | setX3(doc["x3"].as()); 114 | } 115 | if (doc["y3"].is()) { 116 | setY3(doc["y3"].as()); 117 | } 118 | if (doc["filled"].is()) { 119 | setFilled(doc["filled"].as()); 120 | } 121 | if (const char* color = doc["color"]) { 122 | setColor(color); 123 | } else { 124 | setColor(defaultColor); 125 | } 126 | } 127 | 128 | void WebDataElementTriangleModel::draw(TFT_eSPI& display) { 129 | if (getFilled()) { 130 | display.fillTriangle(getX(), getY(), getX2(), getY2(), getX3(), getY3(), getColor()); 131 | } else { 132 | display.drawTriangle(getX(), getY(), getX2(), getY2(), getX3(), getY3(), getColor()); 133 | } 134 | } 135 | -------------------------------------------------------------------------------- /Info-Orbs/src/model/webDataElementArcModel.cpp: -------------------------------------------------------------------------------- 1 | #include "model/webDataElementArcModel.h" 2 | 3 | int32_t WebDataElementArcModel::getX() { 4 | return m_x; 5 | } 6 | 7 | void WebDataElementArcModel::setX(int32_t x) { 8 | if (m_x != x) { 9 | m_x = x; 10 | m_changed = true; 11 | } 12 | } 13 | 14 | int32_t WebDataElementArcModel::getY() { 15 | return m_y; 16 | } 17 | 18 | void WebDataElementArcModel::setY(int32_t y) { 19 | if (m_y != y) { 20 | m_y = y; 21 | m_changed = true; 22 | } 23 | } 24 | 25 | int32_t WebDataElementArcModel::getRadius() { 26 | return m_radius; 27 | } 28 | 29 | void WebDataElementArcModel::setRadius(int32_t radius) { 30 | if (m_radius != radius) { 31 | m_radius = radius; 32 | m_changed = true; 33 | } 34 | } 35 | 36 | int32_t WebDataElementArcModel::getInnerRadius() { 37 | return m_innerRadius; 38 | } 39 | 40 | void WebDataElementArcModel::setInnerRadius(int32_t radius) { 41 | if (m_innerRadius != radius) { 42 | m_innerRadius = radius; 43 | m_changed = true; 44 | } 45 | } 46 | 47 | uint32_t WebDataElementArcModel::getAngleStart() { 48 | return m_angleStart; 49 | } 50 | 51 | void WebDataElementArcModel::setAngleStart(uint32_t angle) { 52 | if (m_angleStart != angle) { 53 | m_angleStart = angle; 54 | m_changed = true; 55 | } 56 | } 57 | 58 | uint32_t WebDataElementArcModel::getAngleEnd() { 59 | return m_angleEnd; 60 | } 61 | 62 | void WebDataElementArcModel::setAngleEnd(uint32_t angle) { 63 | if (m_angleEnd != angle) { 64 | m_angleEnd = angle; 65 | m_changed = true; 66 | } 67 | } 68 | 69 | int32_t WebDataElementArcModel::getBackgroundColor() { 70 | return m_background; 71 | } 72 | 73 | void WebDataElementArcModel::setBackgroundColor(int32_t background) { 74 | if (m_background != background) { 75 | m_background = background; 76 | m_changed = true; 77 | } 78 | } 79 | 80 | void WebDataElementArcModel::setBackgroundColor(String background) { 81 | setBackgroundColor(Utils::stringToColor(background)); 82 | } 83 | 84 | void WebDataElementArcModel::setColor(int32_t color) { 85 | if (m_color != color) { 86 | m_color = color; 87 | m_changed = true; 88 | } 89 | } 90 | 91 | void WebDataElementArcModel::setColor(String color) { 92 | setColor(Utils::stringToColor(color)); 93 | } 94 | 95 | int32_t WebDataElementArcModel::getColor() { 96 | return m_color; 97 | } 98 | 99 | void WebDataElementArcModel::parseData(const JsonObject& doc, int32_t defaultColor, int32_t defaultBackground) { 100 | if (doc["x"].is()) { 101 | setX(doc["x"].as()); 102 | } 103 | if (doc["y"].is()) { 104 | setY(doc["y"].as()); 105 | } 106 | if (doc["radius"].is()) { 107 | setRadius(doc["radius"].as()); 108 | } 109 | if (doc["innerRadius"].is()) { 110 | setInnerRadius(doc["innerRadius"].as()); 111 | } 112 | if (doc["angleStart"].is()) { 113 | setAngleStart(doc["angleStart"].as()); 114 | } 115 | if (doc["angleEnd"].is()) { 116 | setAngleEnd(doc["angleEnd"].as()); 117 | } 118 | if (const char* color = doc["color"]) { 119 | setColor(color); 120 | } else { 121 | setColor(defaultColor); 122 | } 123 | if (const char* background = doc["background"]) { 124 | setBackgroundColor(background); 125 | } else { 126 | setBackgroundColor(defaultBackground); 127 | } 128 | } 129 | 130 | void WebDataElementArcModel::draw(TFT_eSPI& display) { 131 | display.drawArc(getX(), getY(), getRadius(), getInnerRadius(), getAngleStart(), getAngleEnd(), getColor(), getBackgroundColor(), true); 132 | } 133 | -------------------------------------------------------------------------------- /Info-Orbs/lib/globalTime/globalTime.cpp: -------------------------------------------------------------------------------- 1 | #include "globalTime.h" 2 | 3 | #include 4 | #include 5 | 6 | GlobalTime *GlobalTime::m_instance = nullptr; 7 | 8 | GlobalTime::GlobalTime() { 9 | m_timeClient = new NTPClient(m_udp); 10 | m_timeClient->begin(); 11 | m_timeClient->setPoolServerName(NTP_SERVER); 12 | } 13 | 14 | GlobalTime::~GlobalTime() { 15 | delete m_timeClient; 16 | } 17 | 18 | GlobalTime *GlobalTime::getInstance() { 19 | if (m_instance == nullptr) { 20 | m_instance = new GlobalTime(); 21 | } 22 | return m_instance; 23 | } 24 | 25 | void GlobalTime::updateTime() { 26 | if (millis() - m_updateTimer > m_oneSecond) { 27 | if (m_timeZoneOffset == -1) { 28 | getTimeZoneOffsetFromAPI(); 29 | } 30 | m_timeClient->update(); 31 | m_unixEpoch = m_timeClient->getEpochTime(); 32 | m_updateTimer = millis(); 33 | m_minute = minute(m_unixEpoch); 34 | if (m_format24hour) { 35 | m_hour = hour(m_unixEpoch); 36 | } else { 37 | m_hour = hourFormat12(m_unixEpoch); 38 | } 39 | m_second = second(m_unixEpoch); 40 | 41 | m_day = day(m_unixEpoch); 42 | m_month = month(m_unixEpoch); 43 | m_monthName = monthStr(m_month); 44 | m_year = year(m_unixEpoch); 45 | m_weekday = dayStr(weekday(m_unixEpoch)); 46 | m_time = String(m_hour) + ":" + (m_minute < 10 ? "0" + String(m_minute) : String(m_minute)); 47 | } 48 | } 49 | 50 | void GlobalTime::getHourAndMinute(int &hour, int &minute) { 51 | hour = m_hour; 52 | minute = m_minute; 53 | } 54 | 55 | int GlobalTime::getHour() { 56 | return m_hour; 57 | } 58 | 59 | String GlobalTime::getHourPadded() { 60 | if (m_hour < 10) { 61 | return "0" + String(m_hour); 62 | } else { 63 | return String(m_hour); 64 | } 65 | } 66 | 67 | int GlobalTime::getMinute() { 68 | return m_minute; 69 | } 70 | 71 | String GlobalTime::getMinutePadded() { 72 | if (m_minute < 10) { 73 | return "0" + String(m_minute); 74 | } else { 75 | return String(m_minute); 76 | } 77 | } 78 | 79 | int GlobalTime::getSecond() { 80 | return m_second; 81 | } 82 | 83 | time_t GlobalTime::getUnixEpoch() { 84 | return m_unixEpoch; 85 | } 86 | 87 | int GlobalTime::getDay() { 88 | return m_day; 89 | } 90 | 91 | int GlobalTime::getMonth() { 92 | return m_month; 93 | } 94 | 95 | String GlobalTime::getMonthName() { 96 | return m_monthName; 97 | } 98 | 99 | int GlobalTime::getYear() { 100 | return m_year; 101 | } 102 | 103 | String GlobalTime::getTime() { 104 | return m_time; 105 | } 106 | 107 | String GlobalTime::getWeekday() { 108 | return m_weekday; 109 | } 110 | 111 | #include // Include the necessary header file 112 | 113 | bool GlobalTime::isPM() { 114 | return hour(m_unixEpoch) >= 12; 115 | } 116 | 117 | void GlobalTime::getTimeZoneOffsetFromAPI() { 118 | HTTPClient http; 119 | http.begin(String(TIMEZONE_API_URL) + "?key=" + TIMEZONE_API_KEY + "&format=json&fields=gmtOffset&by=zone&zone=" + String(TIMEZONE_API_LOCATION)); 120 | int httpCode = http.GET(); 121 | 122 | if (httpCode > 0) { 123 | JsonDocument doc; 124 | DeserializationError error = deserializeJson(doc, http.getString()); 125 | if (!error) { 126 | m_timeZoneOffset = doc["gmtOffset"].as(); 127 | Serial.print("Timezone Offset from API: "); 128 | Serial.println(m_timeZoneOffset); 129 | m_timeClient->setTimeOffset(m_timeZoneOffset); 130 | } else { 131 | Serial.println("Deserialization error on timezone offset API response"); 132 | } 133 | } else { 134 | Serial.println("Failed to get timezone offset from API"); 135 | } 136 | } 137 | 138 | bool GlobalTime::getFormat24Hour() { 139 | return m_format24hour; 140 | } 141 | 142 | bool GlobalTime::setFormat24Hour(bool format24hour) { 143 | m_format24hour = format24hour; 144 | return m_format24hour; 145 | } -------------------------------------------------------------------------------- /Info-Orbs/src/model/webDataElementTextModel.cpp: -------------------------------------------------------------------------------- 1 | #include "model/webDataElementTextModel.h" 2 | 3 | int32_t WebDataElementTextModel::getX() { 4 | return m_x; 5 | } 6 | 7 | void WebDataElementTextModel::setX(int32_t x) { 8 | if (m_x != x) { 9 | m_x = x; 10 | m_changed = true; 11 | } 12 | } 13 | 14 | int32_t WebDataElementTextModel::getY() { 15 | return m_y; 16 | } 17 | 18 | void WebDataElementTextModel::setY(int32_t y) { 19 | if (m_y != y) { 20 | m_y = y; 21 | m_changed = true; 22 | } 23 | } 24 | 25 | String WebDataElementTextModel::getText() { 26 | return m_text; 27 | } 28 | 29 | void WebDataElementTextModel::setText(String text) { 30 | if (m_text != text) { 31 | m_text = text; 32 | m_changed = true; 33 | } 34 | } 35 | 36 | int32_t WebDataElementTextModel::getFont() { 37 | return m_font; 38 | } 39 | 40 | void WebDataElementTextModel::setFont(int32_t font) { 41 | if (m_font != font) { 42 | m_font = font; 43 | m_changed = true; 44 | } 45 | } 46 | 47 | int32_t WebDataElementTextModel::getSize() { 48 | return m_size; 49 | } 50 | void WebDataElementTextModel::setSize(int32_t size) { 51 | if (m_size != size) { 52 | m_size = size; 53 | m_changed = true; 54 | } 55 | } 56 | int32_t WebDataElementTextModel::getAlignment() { 57 | return m_alignment; 58 | } 59 | void WebDataElementTextModel::setAlignment(int32_t alignment) { 60 | if (m_alignment != alignment) { 61 | m_alignment = alignment; 62 | m_changed = true; 63 | } 64 | } 65 | 66 | void WebDataElementTextModel::setAlignment(String alignment) { 67 | int32_t newAlignment = Utils::stringToAlignment(alignment); 68 | if (m_alignment != newAlignment) { 69 | m_alignment = newAlignment; 70 | m_changed = true; 71 | } 72 | } 73 | 74 | int32_t WebDataElementTextModel::getBackgroundColor() { 75 | return m_background; 76 | } 77 | 78 | void WebDataElementTextModel::setBackgroundColor(int32_t background) { 79 | if (m_background != background) { 80 | m_background = background; 81 | m_changed = true; 82 | } 83 | } 84 | 85 | void WebDataElementTextModel::setBackgroundColor(String background) { 86 | setBackgroundColor(Utils::stringToColor(background)); 87 | } 88 | 89 | void WebDataElementTextModel::setColor(int32_t color) { 90 | if (m_color != color) { 91 | m_color = color; 92 | m_changed = true; 93 | } 94 | } 95 | 96 | void WebDataElementTextModel::setColor(String color) { 97 | setColor(Utils::stringToColor(color)); 98 | } 99 | 100 | int32_t WebDataElementTextModel::getColor() { 101 | return m_color; 102 | } 103 | 104 | void WebDataElementTextModel::parseData(const JsonObject &doc, int32_t defaultColor, int32_t defaultBackground) { 105 | if (doc["x"].is()) { 106 | setX(doc["x"].as()); 107 | } 108 | if (doc["y"].is()) { 109 | setY(doc["y"].as()); 110 | } 111 | if (const char *text = doc["text"]) { 112 | setText(text); 113 | } 114 | if (doc["font"].is()) { 115 | setFont(doc["font"].as()); 116 | } 117 | if (doc["size"].is()) { 118 | setSize(doc["size"].as()); 119 | } 120 | if (const char *alignment = doc["alignment"]) { 121 | setAlignment(alignment); 122 | } 123 | if (const char *color = doc["color"]) { 124 | setColor(color); 125 | } else { 126 | setColor(defaultColor); 127 | } 128 | if (const char *background = doc["background"]) { 129 | setBackgroundColor(background); 130 | } else { 131 | setBackgroundColor(defaultBackground); 132 | } 133 | } 134 | 135 | void WebDataElementTextModel::draw(TFT_eSPI &display) { 136 | display.setTextFont(getFont()); 137 | display.setTextDatum(getAlignment()); 138 | display.setTextSize(getSize()); 139 | display.setTextColor(getColor(), getBackgroundColor()); 140 | display.drawString(getText(), getX(), getY(), getFont()); 141 | } 142 | -------------------------------------------------------------------------------- /Info-Orbs/src/model/webDataElementCharacterModel.cpp: -------------------------------------------------------------------------------- 1 | #include "model/webDataElementCharacterModel.h" 2 | 3 | int32_t WebDataElementCharacterModel::getX() { 4 | return m_x; 5 | } 6 | 7 | void WebDataElementCharacterModel::setX(int32_t x) { 8 | if (m_x != x) { 9 | m_x = x; 10 | m_changed = true; 11 | } 12 | } 13 | 14 | int32_t WebDataElementCharacterModel::getY() { 15 | return m_y; 16 | } 17 | 18 | void WebDataElementCharacterModel::setY(int32_t y) { 19 | if (m_y != y) { 20 | m_y = y; 21 | m_changed = true; 22 | } 23 | } 24 | 25 | String WebDataElementCharacterModel::getCharacter() { 26 | return m_character; 27 | } 28 | 29 | void WebDataElementCharacterModel::setCharacter(String character) { 30 | if (m_character != character) { 31 | m_character = character[0]; 32 | m_changed = true; 33 | } 34 | } 35 | 36 | int32_t WebDataElementCharacterModel::getFont() { 37 | return m_font; 38 | } 39 | 40 | void WebDataElementCharacterModel::setFont(int32_t font) { 41 | if (m_font != font) { 42 | m_font = font; 43 | m_changed = true; 44 | } 45 | } 46 | 47 | int32_t WebDataElementCharacterModel::getSize() { 48 | return m_size; 49 | } 50 | void WebDataElementCharacterModel::setSize(int32_t size) { 51 | if (m_size != size) { 52 | m_size = size; 53 | m_changed = true; 54 | } 55 | } 56 | int32_t WebDataElementCharacterModel::getAlignment() { 57 | return m_alignment; 58 | } 59 | void WebDataElementCharacterModel::setAlignment(int32_t alignment) { 60 | if (m_alignment != alignment) { 61 | m_alignment = alignment; 62 | m_changed = true; 63 | } 64 | } 65 | 66 | void WebDataElementCharacterModel::setAlignment(String alignment) { 67 | int32_t newAlignment = Utils::stringToAlignment(alignment); 68 | if (m_alignment != newAlignment) { 69 | m_alignment = newAlignment; 70 | m_changed = true; 71 | } 72 | } 73 | 74 | int32_t WebDataElementCharacterModel::getBackgroundColor() { 75 | return m_background; 76 | } 77 | 78 | void WebDataElementCharacterModel::setBackgroundColor(int32_t background) { 79 | if (m_background != background) { 80 | m_background = background; 81 | m_changed = true; 82 | } 83 | } 84 | 85 | void WebDataElementCharacterModel::setBackgroundColor(String background) { 86 | setBackgroundColor(Utils::stringToColor(background)); 87 | } 88 | 89 | void WebDataElementCharacterModel::setColor(int32_t color) { 90 | if (m_color != color) { 91 | m_color = color; 92 | m_changed = true; 93 | } 94 | } 95 | 96 | void WebDataElementCharacterModel::setColor(String color) { 97 | setColor(Utils::stringToColor(color)); 98 | } 99 | 100 | int32_t WebDataElementCharacterModel::getColor() { 101 | return m_color; 102 | } 103 | 104 | void WebDataElementCharacterModel::parseData(const JsonObject &doc, int32_t defaultColor, int32_t defaultBackground) { 105 | if (doc["x"].is()) { 106 | setX(doc["x"].as()); 107 | } 108 | if (doc["y"].is()) { 109 | setY(doc["y"].as()); 110 | } 111 | if (const char *character = doc["character"]) { 112 | setCharacter(character); 113 | } 114 | if (doc["font"].is()) { 115 | setFont(doc["font"].as()); 116 | } 117 | if (doc["size"].is()) { 118 | setSize(doc["size"].as()); 119 | } 120 | if (const char *alignment = doc["alignment"]) { 121 | setAlignment(alignment); 122 | } 123 | if (const char *color = doc["color"]) { 124 | setColor(color); 125 | } else { 126 | setColor(defaultColor); 127 | } 128 | if (const char *background = doc["background"]) { 129 | setBackgroundColor(background); 130 | } else { 131 | setBackgroundColor(defaultBackground); 132 | } 133 | } 134 | 135 | void WebDataElementCharacterModel::draw(TFT_eSPI &display) { 136 | display.setTextDatum(getAlignment()); 137 | display.setTextSize(getSize()); 138 | display.setTextColor(getColor(), getBackgroundColor()); 139 | display.drawChar(getCharacter()[0], getX(), getY(), getFont()); 140 | } 141 | -------------------------------------------------------------------------------- /Info-Orbs/src/model/webDataElementImageModel.cpp: -------------------------------------------------------------------------------- 1 | #include "model/webDataElementImageModel.h" 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | int32_t WebDataElementImageModel::getX() { 8 | return m_x; 9 | } 10 | 11 | void WebDataElementImageModel::setX(int32_t x) { 12 | if (m_x != x) { 13 | m_x = x; 14 | m_changed = true; 15 | } 16 | } 17 | 18 | int32_t WebDataElementImageModel::getY() { 19 | return m_y; 20 | } 21 | 22 | void WebDataElementImageModel::setY(int32_t y) { 23 | if (m_y != y) { 24 | m_y = y; 25 | m_changed = true; 26 | } 27 | } 28 | 29 | String WebDataElementImageModel::getImage() { 30 | return m_image; 31 | } 32 | 33 | void WebDataElementImageModel::setImage(String image) { 34 | if (m_image != image) { 35 | m_image = image; 36 | m_changed = true; 37 | } 38 | } 39 | 40 | void WebDataElementImageModel::parseData(const JsonObject& doc, int32_t defaultColor, int32_t defaultBackground) { 41 | if (doc["x"].is()) { 42 | setX(doc["x"].as()); 43 | } 44 | if (doc["y"].is()) { 45 | setY(doc["y"].as()); 46 | } 47 | if (const char* image = doc["image"]) { 48 | setImage(image); 49 | } 50 | // if (const char* imageUrl = doc["imageUrl"]) { 51 | // setImageUrl(imageUrl); 52 | // } 53 | // if (const char* imageBytes = doc["imageBytes"]) { 54 | // setImageBytes(imageBytes); 55 | // } 56 | } 57 | 58 | void WebDataElementImageModel::draw(TFT_eSPI& display) { 59 | // TODO implement displaying an image 60 | // display.drawImage(getImage(), getX(), getY()); 61 | } 62 | 63 | // Fetch a file from the URL given and save it in LittleFS 64 | // Return 1 if a web fetch was needed or 0 if file already exists 65 | bool getFile(String url, String filename) { 66 | // If it exists then no need to fetch it 67 | if (LittleFS.exists(filename) == true) { 68 | Serial.println("Found " + filename); 69 | return 0; 70 | } 71 | 72 | Serial.println("Downloading " + filename + " from " + url); 73 | 74 | // Check WiFi connection 75 | if ((WiFi.status() == WL_CONNECTED)) { 76 | Serial.print("[HTTP] begin...\n"); 77 | 78 | HTTPClient http; 79 | // Configure server and url 80 | http.begin(url); 81 | 82 | Serial.print("[HTTP] GET...\n"); 83 | // Start connection and send HTTP header 84 | int httpCode = http.GET(); 85 | if (httpCode == 200) { 86 | fs::File f = LittleFS.open(filename, "w+"); 87 | if (!f) { 88 | Serial.println("file open failed"); 89 | return 0; 90 | } 91 | // HTTP header has been send and Server response header has been handled 92 | Serial.printf("[HTTP] GET... code: %d\n", httpCode); 93 | 94 | // File found at server 95 | if (httpCode == HTTP_CODE_OK) { 96 | // Get length of document (is -1 when Server sends no Content-Length header) 97 | int total = http.getSize(); 98 | int len = total; 99 | 100 | // Create buffer for read 101 | uint8_t buff[128] = {0}; 102 | 103 | // Get tcp stream 104 | WiFiClient* stream = http.getStreamPtr(); 105 | 106 | // Read all data from server 107 | while (http.connected() && (len > 0 || len == -1)) { 108 | // Get available data size 109 | size_t size = stream->available(); 110 | 111 | if (size) { 112 | // Read up to 128 bytes 113 | int c = stream->readBytes(buff, ((size > sizeof(buff)) ? sizeof(buff) : size)); 114 | 115 | // Write it to file 116 | f.write(buff, c); 117 | 118 | // Calculate remaining bytes 119 | if (len > 0) { 120 | len -= c; 121 | } 122 | } 123 | yield(); 124 | } 125 | Serial.println(); 126 | Serial.print("[HTTP] connection closed or file end.\n"); 127 | } 128 | f.close(); 129 | } else { 130 | Serial.printf("[HTTP] GET... failed, error: %s\n", http.errorToString(httpCode).c_str()); 131 | } 132 | http.end(); 133 | } 134 | return 1; // File was fetched from web 135 | } 136 | -------------------------------------------------------------------------------- /Info-Orbs/src/widgets/stockWidget.cpp: -------------------------------------------------------------------------------- 1 | #include "widgets/stockWidget.h" 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | #include 8 | 9 | StockWidget::StockWidget(ScreenManager &manager) : Widget(manager) { 10 | char stockList[strlen(STOCK_TICKER_LIST) + 1]; 11 | strcpy(stockList, STOCK_TICKER_LIST); 12 | 13 | char *symbol = strtok(stockList, ","); 14 | m_stockCount = 0; 15 | do { 16 | StockDataModel stockModel = StockDataModel(); 17 | stockModel.setSymbol(String(symbol)); 18 | m_stocks[m_stockCount] = stockModel; 19 | m_stockCount++; 20 | if (m_stockCount > MAX_STOCKS) { 21 | Serial.println("MAX STOCKS UNABLE TO ADD MORE"); 22 | break; 23 | } 24 | } while (symbol = strtok(nullptr, ",")); 25 | } 26 | 27 | void StockWidget::setup() { 28 | if (m_stockCount == 0) { 29 | Serial.println("No stock tickers available"); 30 | return; 31 | } 32 | } 33 | 34 | void StockWidget::draw(bool force) { 35 | for (int8_t i = 0; i < m_stockCount; i++) { 36 | if (m_stocks[i].isChanged() || force) { 37 | displayStock(i, m_stocks[i], TFT_WHITE, TFT_BLACK); 38 | m_stocks[i].setChangedStatus(false); 39 | } 40 | } 41 | } 42 | 43 | void StockWidget::update(bool force) { 44 | if (force || m_stockDelayPrev == 0 || (millis() - m_stockDelayPrev) >= m_stockDelay) { 45 | setBusy(true); 46 | for (int8_t i = 0; i < m_stockCount; i++) { 47 | getStockData(m_stocks[i]); 48 | } 49 | setBusy(false); 50 | m_stockDelayPrev = millis(); 51 | } 52 | } 53 | 54 | void StockWidget::changeMode() { 55 | update(true); 56 | } 57 | 58 | void StockWidget::getStockData(StockDataModel &stock) { 59 | String httpRequestAddress = "https://api.marketdata.app/v1/stocks/quotes/" + stock.getSymbol() + "/?token=aVhwT1NWWkhIZVBRZlIwOUlHb01keWFrMEI5Ql9QM1ZIZndtay1ub0V3OD0"; 60 | 61 | HTTPClient http; 62 | http.begin(httpRequestAddress); 63 | int httpCode = http.GET(); 64 | 65 | if (httpCode > 0) { // Check for the returning code 66 | String payload = http.getString(); 67 | JsonDocument doc; 68 | DeserializationError error = deserializeJson(doc, payload); 69 | 70 | if (!error) { 71 | float currentPrice = doc["last"][0].as(); 72 | if (currentPrice > 0.0) { 73 | stock.setCurrentPrice(doc["last"][0].as()); 74 | stock.setPercentChange(doc["changepct"][0].as()); 75 | stock.setPriceChange(doc["change"][0].as()); 76 | stock.setVolume(doc["volume"][0].as()); 77 | } else { 78 | Serial.println("skipping invalid data for: " + stock.getSymbol()); 79 | } 80 | } else { 81 | // Handle JSON deserialization error 82 | Serial.println("deserializeJson() failed"); 83 | } 84 | } else { 85 | // Handle HTTP request error 86 | Serial.printf("HTTP request failed, error: %s\n", http.errorToString(httpCode).c_str()); 87 | } 88 | 89 | http.end(); 90 | } 91 | 92 | void StockWidget::displayStock(int8_t displayIndex, StockDataModel &stock, uint32_t backgroundColor, uint32_t textColor) { 93 | Serial.println("displayStock - " + stock.getSymbol() + " ~ " + stock.getCurrentPrice()); 94 | if (stock.getCurrentPrice() == 0.0) { 95 | // there isn't any data to display yet 96 | return; 97 | } 98 | m_manager.selectScreen(displayIndex); 99 | 100 | TFT_eSPI &display = m_manager.getDisplay(); 101 | 102 | display.fillScreen(TFT_BLACK); 103 | display.setTextColor(TFT_WHITE); 104 | display.setTextSize(4); // Set text size 105 | 106 | // Calculate center positions 107 | int screenWidth = display.width(); 108 | int centre = 120; 109 | 110 | // Draw stock data 111 | display.fillRect(0, 0, screenWidth, 50, 0x0256); // rgb565 colors 112 | display.drawString(stock.getSymbol(), centre, 27, 1); 113 | display.drawString("$" + stock.getCurrentPrice(2), centre, 51 + display.fontHeight(1), 1); 114 | 115 | if (stock.getPercentChange() < 0.0) { 116 | display.setTextColor(TFT_RED, TFT_BLACK); 117 | display.fillTriangle(120, 220, 140, 185, 100, 185, TFT_RED); 118 | } else { 119 | display.setTextColor(TFT_GREEN, TFT_BLACK); 120 | display.fillTriangle(120, 185, 140, 220, 100, 220, TFT_GREEN); 121 | } 122 | 123 | display.drawString(stock.getPercentChange(2) + "%", centre, 147, 1); 124 | } 125 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | # Welcome to Info Orbs! 3 | 4 | Info Orbs is a desk display widget built on top of an ESP32 with the intention of creating a large library of widgets/functionality! 5 | 6 | Quick Links: [Get A Dev Kit Here](https://brett.tech/collections/electronics-projects/products/info-orbs-full-dev-kit) | [Discord](https://link.brett.tech/discord) | [Youtube Assembly/Flashing Video](https://link.brett.tech/orbsYT) 7 |

8 | Weather Widget 9 | Clock Widget 10 | Stock Widget 11 |

12 | 13 | ## First, a few housekeeping items for anyone interested in helping with this project, or building one for themselves 14 | 15 | - If you want to contribute and or need a hand with setup, please pop over to the [Discord](https://link.brett.tech/discord). Make sure to select you're there for info orbs when filling out the onboarding questionnaire in order to get placed in the right channels 16 | 17 | - I've put together dev kits consisting of all the parts you need to build this project [you can buy them here.](https://brett.tech/collections/electronics-projects/products/info-orbs-full-dev-kit) They're $55 and will save you a bunch of time and hassle, and are a great way to support the project (: 18 | 19 | - I've put together a brief [Youtube Video](https://link.brett.tech/orbsYT) walking through the soldering and flashing for anyone that needs a hand assembling. 20 | ## Getting Up And Running 21 | 22 | ### 1. Hardware/Wiring 23 | If you use the PCB soldering should be straight forward, however if you want to wire thing up yourself the pinouts are below: 24 | 25 | DSP-----ESP 26 | SDA -> G17 27 | SCLK -> G23 28 | DC -> G19 29 | RST -> G18 30 | VCC - >5V/VCC 31 | GND -> GND 32 | Screen1 CS -> G13 33 | Screen2 CS -> G33 34 | Screen3 CS -> G32 35 | Screen4 CS -> G25 36 | Screen5 CS -> G21 37 | 38 | Lastly, three pushbuttons between `VCC/5V` and `G14,G26,G27.` 39 | Diagram can be seen below: 40 | Wiring Diagram 41 | 42 | 43 | ### 2. Dev Environment Setup/Project Config 44 | **IDE Setup** 45 | Start by downloading the most recent codebase from the `main` branch. 46 | While built on Arduino, the codebase for this project has been built using the [Platform IO IDE](https://platformio.org/), which allows you to compile/run Arduino code in VSC as well as quite a few other creature comfort items. 47 | Once you have platform.io installed and configured in VSC, you'll want to select "Open Project", and select & open the main "Info-Orbs" directory from the codebase you downloaded. 48 | 49 | **Project Configuration** 50 | Before compiling/flashing, you'll need to navigate into `Info-Orbs` >>` lib `>> `config` directory and make a copy of the file `config.h.template` in the same folder and rename that copy to `config.h` **THIS STEP IS CRITICAL AND YOUR CODE WILL NOT COMPILE IF YOU DONT COPY THIS FILE AND CHANGE THE NAME** 51 | 52 | Lastly, open up the `config.h` file you just copied/renamed and adjust the below parameters in the code to fit your needs. 53 | ``` 54 | // ============= CONFIGURE THESE FIELDS BEFORE FLASHING ==================================================== 55 | #define WIFI_SSID "WIFINAME" // wifi name (please use 2.4gz network) 56 | #define WIFI_PASS "WIFIPASS" // wifi password 57 | #define TIMEZONE_API_LOCATION "America/Vancouver" // Use timezone from this list: https://timezonedb.com/time-zones 58 | #define WEATHER_LOCAION "Victoria, BC" //city/state for the weather 59 | #define STOCK_TICKER_LIST "SPY,VT,GOOG,TSLA,GME" // Choose your 5 stokcs to display on the stock tracker 60 | #define WEATHER_UNITS_METRIC //Comment this line out(or delete it) if you want imperial units for the weather 61 | #define FORMAT_24_HOUR false // toggle 24 hour clock vs 12 hour clock, chnage between true/false 62 | #define SHOW_AM_PM_INDICATOR false // am/pm on the clock if using 12 hour 63 | #define SHOW_SECOND_TICKS true // ticking indeicator on the centre clock 64 | //#define WEB_DATA_WIDGET_URL "" // use this to make your own widgets using an API/Webdata source 65 | // ============= END CONFIG ============================================================================== 66 | ``` 67 | 68 | 69 | The code should now compile and flash to your ESP by clicking the flash button at the bottom of your IDE (: 70 | The left and right buttons will change between widgets, and the middle button will toggle the widgets "mode"(24/48 hour clock, high/low for weather, etc) 71 | 72 | Goodluck & happy orb-in 73 | 74 | 75 | ## Contributors 76 | A massive thank you to the community that has helped, this is my first open source project(honestly first project of any sort) so thy help of all you super smart people has just been so so incredible and I couldn't have got this anywhere near where it is now without everyone. Thanks for building this with me. Love ya'll! ♥️ 77 | 78 | 79 | 80 | 81 | 82 | 83 | -------------------------------------------------------------------------------- /Info-Orbs/src/model/weatherDataModel.cpp: -------------------------------------------------------------------------------- 1 | #include "model/weatherDataModel.h" 2 | #include "utils.h" 3 | 4 | WeatherDataModel::WeatherDataModel() { 5 | } 6 | 7 | WeatherDataModel &WeatherDataModel::setCityName(String city) { 8 | if (m_cityName != city) { 9 | m_cityName = city; 10 | m_changed = true; 11 | } 12 | return *this; 13 | } 14 | 15 | String WeatherDataModel::getCityName() { 16 | return m_cityName; 17 | } 18 | 19 | WeatherDataModel &WeatherDataModel::setCurrentText(String text) { 20 | if (m_currentWeatherText != text) { 21 | m_currentWeatherText = text; 22 | m_changed = true; 23 | } 24 | return *this; 25 | } 26 | 27 | String WeatherDataModel::getCurrentText() { 28 | return m_currentWeatherText; 29 | } 30 | 31 | WeatherDataModel &WeatherDataModel::setCurrentIcon(String icon) { 32 | if (m_currentWeatherIcon != icon) { 33 | m_currentWeatherIcon = icon; 34 | m_changed = true; 35 | } 36 | return *this; 37 | } 38 | 39 | String WeatherDataModel::getCurrentIcon() { 40 | return m_currentWeatherIcon; 41 | } 42 | 43 | WeatherDataModel &WeatherDataModel::setCurrentTemperature(float degrees) { 44 | if (m_currentWeatherDeg != degrees) { 45 | m_currentWeatherDeg = degrees; 46 | m_changed = true; 47 | } 48 | return *this; 49 | } 50 | 51 | float WeatherDataModel::getCurrentTemperature() { 52 | return m_currentWeatherDeg; 53 | } 54 | 55 | String WeatherDataModel::getCurrentTemperature(int8_t digits) { 56 | return Utils::formatFloat(m_currentWeatherDeg, digits); 57 | } 58 | 59 | WeatherDataModel &WeatherDataModel::setTodayHigh(float high) { 60 | if (m_todayHigh != high) { 61 | m_todayHigh = high; 62 | m_changed = true; 63 | } 64 | return *this; 65 | } 66 | 67 | float WeatherDataModel::getTodayHigh() { 68 | return m_todayHigh; 69 | } 70 | 71 | String WeatherDataModel::getTodayHigh(int8_t digits) { 72 | return Utils::formatFloat(m_todayHigh, digits); 73 | } 74 | 75 | WeatherDataModel &WeatherDataModel::setTodayLow(float low) { 76 | if (m_todayLow != low) { 77 | m_todayLow = low; 78 | m_changed = true; 79 | } 80 | return *this; 81 | } 82 | 83 | float WeatherDataModel::getTodayLow() { 84 | return m_todayLow; 85 | } 86 | 87 | String WeatherDataModel::getTodayLow(int8_t digits) { 88 | return Utils::formatFloat(m_todayLow, digits); 89 | } 90 | 91 | WeatherDataModel &WeatherDataModel::setDaysIcons(String *icons) { 92 | for (int i; i < 3; i++) { 93 | setDayIcon(i, icons[i]); 94 | } 95 | return *this; 96 | } 97 | 98 | String &WeatherDataModel::getDaysIcons() { 99 | return *m_daysIcons; 100 | } 101 | 102 | WeatherDataModel &WeatherDataModel::setDayIcon(int num, String icon) { 103 | if (num < 3 && m_daysIcons[num] != icon) { 104 | m_daysIcons[num] = icon; 105 | m_changed = true; 106 | } 107 | return *this; 108 | } 109 | 110 | String WeatherDataModel::getDayIcon(int num) { 111 | if (num >= 3) { 112 | return ""; 113 | } 114 | return m_daysIcons[num]; 115 | } 116 | 117 | WeatherDataModel &WeatherDataModel::setDaysLows(float *lows) { 118 | for (int i; i < 3; i++) { 119 | setDayLow(i, lows[i]); 120 | } 121 | return *this; 122 | } 123 | 124 | WeatherDataModel &WeatherDataModel::setDayLow(int num, float low) { 125 | if (num < 3 && m_daysLow[num] != low) { 126 | m_daysLow[num] = low; 127 | m_changed = true; 128 | } 129 | return *this; 130 | } 131 | 132 | float &WeatherDataModel::getDaysLows() { 133 | return *m_daysLow; 134 | } 135 | 136 | float WeatherDataModel::getDayLow(int num) { 137 | if (num >= 3) { 138 | return NaN; 139 | } 140 | return m_daysLow[num]; 141 | } 142 | 143 | String WeatherDataModel::getDayLow(int8_t num, int8_t digits) { 144 | if (m_daysLow[num] == NaN) { 145 | return ""; 146 | } 147 | return Utils::formatFloat(m_daysLow[num], digits); 148 | } 149 | 150 | WeatherDataModel &WeatherDataModel::setDaysHighs(float highs[3]) { 151 | for (int i; i < 3; i++) { 152 | setDayHigh(i, highs[i]); 153 | } 154 | return *this; 155 | } 156 | 157 | float &WeatherDataModel::getDaysHighs() { 158 | return *m_daysHigh; 159 | } 160 | 161 | float WeatherDataModel::getDayHigh(int num) { 162 | if (num >= 3) { 163 | return NaN; 164 | } 165 | return m_daysHigh[num]; 166 | } 167 | 168 | String WeatherDataModel::getDayHigh(int8_t num, int8_t digits) { 169 | if (m_daysHigh[num] == NaN) { 170 | return ""; 171 | } 172 | return Utils::formatFloat(m_daysHigh[num], digits); 173 | } 174 | 175 | WeatherDataModel &WeatherDataModel::setDayHigh(int num, float high) { 176 | if (num < 3 && m_daysHigh[num] != high) { 177 | if (m_daysHigh[num] != high) { 178 | m_daysHigh[num] = high; 179 | m_changed = true; 180 | } 181 | } 182 | return *this; 183 | } 184 | 185 | bool WeatherDataModel::isChanged() { 186 | return m_changed; 187 | } 188 | WeatherDataModel &WeatherDataModel::setChangedStatus(bool changed) { 189 | m_changed = changed; 190 | return *this; 191 | } 192 | -------------------------------------------------------------------------------- /Info-Orbs/src/widgets/clockWidget.cpp: -------------------------------------------------------------------------------- 1 | #include "widgets/clockWidget.h" 2 | 3 | #include 4 | #include 5 | 6 | ClockWidget::ClockWidget(ScreenManager& manager) : Widget(manager) { 7 | } 8 | 9 | ClockWidget::~ClockWidget() { 10 | } 11 | 12 | void ClockWidget::setup() { 13 | m_lastDisplay1Didget = "-1"; 14 | m_lastDisplay2Didget = "-1"; 15 | m_lastDisplay4Didget = "-1"; 16 | m_lastDisplay5Didget = "-1"; 17 | } 18 | 19 | void ClockWidget::draw(bool force) { 20 | GlobalTime* time = GlobalTime::getInstance(); 21 | 22 | if (m_lastDisplay1Didget != m_display1Didget || force) { 23 | displayDidget(0, m_display1Didget, 7, 5, FOREGROUND_COLOR); 24 | m_lastDisplay1Didget = m_display1Didget; 25 | if (SHADOWING != 1 &&m_display1Didget == " ") { 26 | m_manager.clearScreen(0); 27 | } 28 | } 29 | if (m_lastDisplay2Didget != m_display2Didget || force) { 30 | displayDidget(1, m_display2Didget, 7, 5, FOREGROUND_COLOR); 31 | m_lastDisplay2Didget = m_display2Didget; 32 | } 33 | if (m_lastDisplay4Didget != m_display4Didget || force) { 34 | displayDidget(3, m_display4Didget, 7, 5, FOREGROUND_COLOR); 35 | m_lastDisplay4Didget = m_display4Didget; 36 | } 37 | if (m_lastDisplay5Didget != m_display5Didget || force) { 38 | displayDidget(4, m_display5Didget, 7, 5, FOREGROUND_COLOR); 39 | m_lastDisplay5Didget = m_display5Didget; 40 | } 41 | 42 | if (m_secondSingle != m_lastSecondSingle || force) { 43 | if (m_secondSingle % 2 == 0) { 44 | displayDidget(2, ":", 7, 5, FOREGROUND_COLOR, false); 45 | } else { 46 | displayDidget(2, ":", 7, 5, BG_COLOR, false); 47 | } 48 | #if SHOW_SECOND_TICKS == true 49 | displaySeconds(2, m_lastSecondSingle, TFT_BLACK); 50 | displaySeconds(2, m_secondSingle, FOREGROUND_COLOR); 51 | #endif 52 | m_lastSecondSingle = m_secondSingle; 53 | if (!FORMAT_24_HOUR && SHOW_AM_PM_INDICATOR) { 54 | displayAmPm(FOREGROUND_COLOR); 55 | } 56 | } 57 | } 58 | 59 | void ClockWidget::displayAmPm(uint32_t color) { 60 | GlobalTime* time = GlobalTime::getInstance(); 61 | m_manager.selectScreen(2); 62 | TFT_eSPI& display = m_manager.getDisplay(); 63 | display.setTextSize(4); 64 | display.setTextColor(color, TFT_BLACK, true); 65 | String am_pm = time->isPM() ? "PM" : "AM"; 66 | display.drawString(am_pm, SCREEN_SIZE - 50, SCREEN_SIZE / 2, 1); 67 | } 68 | 69 | void ClockWidget::update(bool force) { 70 | if (millis() - m_secondTimerPrev < m_secondTimer && !force) { 71 | return; 72 | } 73 | 74 | GlobalTime* time = GlobalTime::getInstance(); 75 | m_hourSingle = time->getHour(); 76 | 77 | m_minuteSingle = time->getMinute(); 78 | m_secondSingle = time->getSecond(); 79 | 80 | if (m_lastHourSingle != m_hourSingle || force) { 81 | if (m_hourSingle < 10) { 82 | if (FORMAT_24_HOUR) { 83 | m_display1Didget = "0"; 84 | } else { 85 | m_display1Didget = " "; 86 | } 87 | } else { 88 | m_display1Didget = int(m_hourSingle/10); 89 | } 90 | m_display2Didget = m_hourSingle % 10; 91 | 92 | m_lastHourSingle = m_hourSingle; 93 | } 94 | 95 | if (m_lastMinuteSingle != m_minuteSingle || force) { 96 | String currentMinutePadded = String(m_minuteSingle).length() == 1 ? "0" + String(m_minuteSingle) : String(m_minuteSingle); 97 | 98 | m_display4Didget = currentMinutePadded.substring(0, 1); 99 | m_display5Didget = currentMinutePadded.substring(1, 2); 100 | 101 | m_lastMinuteSingle = m_minuteSingle; 102 | } 103 | } 104 | 105 | void ClockWidget::changeMode() { 106 | GlobalTime* time = GlobalTime::getInstance(); 107 | time->setFormat24Hour( !time->getFormat24Hour() ); 108 | draw(true); 109 | } 110 | 111 | void ClockWidget::displayDidget(int displayIndex, const String& didget, int font, int fontSize, uint32_t color, bool shadowing) { 112 | m_manager.selectScreen(displayIndex); 113 | TFT_eSPI& display = m_manager.getDisplay(); 114 | display.setTextSize(fontSize); 115 | if (shadowing && font == 7) { 116 | display.setTextColor(BG_COLOR, TFT_BLACK); 117 | display.drawString("8", SCREEN_SIZE / 2, SCREEN_SIZE / 2, font); 118 | display.setTextColor(color); 119 | } else { 120 | display.setTextColor(color, TFT_BLACK); 121 | } 122 | display.drawString(didget, SCREEN_SIZE / 2, SCREEN_SIZE / 2, font); 123 | } 124 | 125 | void ClockWidget::displayDidget(int displayIndex, const String& didget, int font, int fontSize, uint32_t color) { 126 | this->displayDidget(displayIndex, didget, font, fontSize, color, SHADOWING); 127 | } 128 | 129 | void ClockWidget::displaySeconds(int displayIndex, int seconds, int color) { 130 | m_manager.reset(); 131 | m_manager.selectScreen(displayIndex); 132 | TFT_eSPI& display = m_manager.getDisplay(); 133 | if (seconds < 30) { 134 | display.drawSmoothArc(SCREEN_SIZE / 2, SCREEN_SIZE / 2, 120, 110, 6 * seconds + 180, 6 * seconds + 180 + 6, color, TFT_BLACK); 135 | } else { 136 | display.drawSmoothArc(SCREEN_SIZE / 2, SCREEN_SIZE / 2, 120, 110, 6 * seconds - 180, 6 * seconds - 180 + 6, color, TFT_BLACK); 137 | } 138 | } 139 | -------------------------------------------------------------------------------- /Info-Orbs/src/model/webDataModel.cpp: -------------------------------------------------------------------------------- 1 | #include "model/webDataModel.h" 2 | 3 | String WebDataModel::getLabel() { 4 | return m_label; 5 | } 6 | 7 | void WebDataModel::setLabel(String label) { 8 | if (m_label != label) { 9 | m_label = label; 10 | m_changed = true; 11 | } 12 | } 13 | 14 | String WebDataModel::getData() { 15 | return m_data; 16 | } 17 | 18 | void WebDataModel::setData(String data, int32_t defaultColor, int32_t defaultBackground) { 19 | if (m_data != data) { 20 | m_data = data; 21 | setElementsCount(0); 22 | m_changed = true; 23 | } 24 | } 25 | void WebDataModel::setData(JsonArray data, int32_t defaultColor, int32_t defaultBackground) { 26 | setElementsCount(data.size()); 27 | for (int i = 0; i < data.size(); i++) { 28 | m_elements[i].parseData(data[i], defaultColor, defaultBackground); 29 | } 30 | m_changed = true; 31 | } 32 | 33 | const WebDataElementModel &WebDataModel::getElement(int index) { 34 | return m_elements[index]; 35 | } 36 | 37 | int32_t WebDataModel::getElementsCount() { 38 | return m_elementsCount; 39 | } 40 | 41 | void WebDataModel::initElements(int32_t count) { 42 | if (m_elementsCount > 0) { 43 | delete[] m_elements; 44 | } 45 | m_elements = new WebDataElementModel[count]; 46 | } 47 | void WebDataModel::setElementsCount(int32_t count) { 48 | if (m_elementsCount != count) { 49 | initElements(count); 50 | m_elementsCount = count; 51 | } 52 | } 53 | 54 | int32_t WebDataModel::getLabelColor() { 55 | return m_labelColor; 56 | } 57 | 58 | void WebDataModel::setLabelColor(int32_t color) { 59 | if (m_labelColor != color) { 60 | m_labelColor = color; 61 | m_changed = true; 62 | } 63 | } 64 | 65 | void WebDataModel::setLabelColor(String color) { 66 | setLabelColor(Utils::stringToColor(color)); 67 | } 68 | 69 | int32_t WebDataModel::getDataColor() { 70 | return m_color; 71 | } 72 | 73 | void WebDataModel::setDataColor(int32_t color) { 74 | if (m_color != color) { 75 | m_color = color; 76 | m_changed = true; 77 | } 78 | } 79 | 80 | void WebDataModel::setDataColor(String color) { 81 | setDataColor(Utils::stringToColor(color)); 82 | } 83 | 84 | int32_t WebDataModel::getBackgroundColor() { 85 | return m_background; 86 | } 87 | 88 | void WebDataModel::setBackgroundColor(int32_t background) { 89 | if (m_background != background) { 90 | m_background = background; 91 | m_changed = true; 92 | } 93 | } 94 | 95 | void WebDataModel::setBackgroundColor(String background) { 96 | setBackgroundColor(Utils::stringToColor(background)); 97 | } 98 | 99 | bool WebDataModel::isFullDraw() { 100 | return m_fullDraw; 101 | } 102 | 103 | void WebDataModel::setFullDrawStatus(bool fullDraw) { 104 | m_fullDraw = fullDraw; 105 | } 106 | 107 | bool WebDataModel::isChanged() { 108 | return m_changed; 109 | } 110 | 111 | void WebDataModel::setChangedStatus(bool changed) { 112 | m_changed = changed; 113 | } 114 | 115 | void WebDataModel::parseData(const JsonObject &doc, int32_t defaultColor, int32_t defaultBackground) { 116 | if (const char *label = doc["label"]) { 117 | setLabel(label); 118 | } 119 | if (doc["data"].is()) { 120 | setData(doc["data"].as(), defaultColor, defaultBackground); 121 | } else if (const char *data = doc["data"]) { 122 | setData(data, defaultColor, defaultBackground); 123 | } else if (String data = doc["data"].as()) { 124 | setData(data, defaultColor, defaultBackground); 125 | } 126 | if (const char *color = doc["color"]) { 127 | setDataColor(color); 128 | } else { 129 | setDataColor(defaultColor); 130 | } 131 | if (const char *labelColor = doc["labelColor"]) { 132 | setLabelColor(labelColor); 133 | } else { 134 | setLabelColor(defaultColor); 135 | } 136 | if (const char *background = doc["background"]) { 137 | setBackgroundColor(background); 138 | } else { 139 | setBackgroundColor(defaultBackground); 140 | } 141 | if (doc["fullDraw"].is()) { 142 | setFullDrawStatus(doc["fullDraw"].as()); 143 | } else { 144 | setFullDrawStatus(false); 145 | } 146 | } 147 | 148 | bool WebDataModel::isInitialized() { 149 | return m_isInitialized; 150 | } 151 | 152 | void WebDataModel::setInitializedStatus(bool initialized) { 153 | m_isInitialized = initialized; 154 | } 155 | 156 | void WebDataModel::draw(TFT_eSPI &display) { 157 | if (!m_isInitialized || isFullDraw()) { 158 | display.fillScreen(getBackgroundColor()); 159 | m_isInitialized = true; 160 | } 161 | if (getLabel()) { 162 | display.setTextColor(getLabelColor()); 163 | display.setTextSize(2); 164 | display.setTextDatum(MC_DATUM); 165 | display.drawString(getLabel(), 120, 70, 2); 166 | } 167 | display.setTextDatum(MC_DATUM); 168 | 169 | if (getElementsCount() > 0) { 170 | for (int i = 0; i < getElementsCount(); i++) { 171 | WebDataElementModel element = getElement(i); 172 | element.draw(display); 173 | } 174 | } else { 175 | display.setTextColor(getDataColor(), getBackgroundColor()); 176 | 177 | String wrappedLines[MAX_WRAPPED_LINES]; 178 | String dataValues = getData(); 179 | int yOffset = 110; 180 | int lineCount = Utils::getWrappedLines(wrappedLines, dataValues, 10); 181 | int height = display.fontHeight() + 10; 182 | for (int i = 0; i < lineCount; i++) { 183 | display.drawString(wrappedLines[i], 120, yOffset + (height * i), 2); 184 | } 185 | } 186 | } 187 | -------------------------------------------------------------------------------- /Info-Orbs/src/core/utils.cpp: -------------------------------------------------------------------------------- 1 | #include "utils.h" 2 | 3 | int Utils::getWrappedLines(String (&lines)[MAX_WRAPPED_LINES], String str, int limit) { 4 | char buf[str.length() + 1]; 5 | char lineBuf[limit + 1]; 6 | str.toCharArray(buf, str.length() + 1); 7 | 8 | char *p = buf; 9 | char *eol; 10 | int lineCount = 0; 11 | for (int i = 0; i < MAX_WRAPPED_LINES; i++) { 12 | if (p - buf > strlen(buf)) { 13 | lines[i] = ""; 14 | continue; 15 | } 16 | eol = strchr(p, '\n'); 17 | if (eol == NULL) { 18 | eol = p + strlen(p); 19 | } 20 | 21 | if (eol - p > limit) { 22 | eol = p + limit; 23 | while (*eol != ' ' && *eol != '\n' && eol > p) { 24 | eol--; 25 | } 26 | } 27 | strncpy(lineBuf, p, eol - p); 28 | lineBuf[eol - p] = '\0'; 29 | 30 | lines[i] = String(lineBuf); 31 | lineCount++; 32 | p = eol + 1; 33 | } 34 | return lineCount; 35 | } 36 | 37 | String Utils::getWrappedLine(String str, int limit, int lineNum, int maxLines) { 38 | if (lineNum > maxLines) { 39 | return ""; 40 | } 41 | char buf[str.length() + 1]; 42 | char lineBuf[limit + 1]; 43 | str.toCharArray(buf, str.length() + 1); 44 | 45 | String lines[maxLines]; 46 | 47 | char *p = buf; 48 | char *eol; 49 | 50 | for (int i = 0; i < maxLines && i <= lineNum; i++) { 51 | if (p - buf > strlen(buf)) { 52 | lines[i] = ""; 53 | continue; 54 | } 55 | eol = strchr(p, '\n'); 56 | if (eol == NULL) { 57 | eol = p + strlen(p); 58 | } 59 | 60 | if (eol - p > limit) { 61 | eol = p + limit; 62 | while (*eol != ' ' && *eol != '\n' && eol > p) { 63 | eol--; 64 | } 65 | } 66 | strncpy(lineBuf, p, eol - p); 67 | lineBuf[eol - p] = '\0'; 68 | 69 | lines[i] = String(lineBuf); 70 | p = eol + 1; 71 | } 72 | return lines[lineNum]; 73 | } 74 | 75 | int32_t Utils::stringToColor(String color) { 76 | color.toLowerCase(); 77 | color.replace(" ", ""); 78 | if (color == "black") { 79 | return TFT_BLACK; 80 | } else if (color == "navy") { 81 | return TFT_NAVY; 82 | } else if (color == "darkgreen") { 83 | return TFT_DARKGREEN; 84 | } else if (color == "darkcyan") { 85 | return TFT_DARKCYAN; 86 | } else if (color == "maroon") { 87 | return TFT_MAROON; 88 | } else if (color == "purple") { 89 | return TFT_PURPLE; 90 | } else if (color == "olive") { 91 | return TFT_OLIVE; 92 | } else if (color == "lightgrey" || color == "grey") { 93 | return TFT_LIGHTGREY; 94 | } else if (color == "darkgrey") { 95 | return TFT_DARKGREY; 96 | } else if (color == "blue") { 97 | return TFT_BLUE; 98 | } else if (color == "green") { 99 | return TFT_GREEN; 100 | } else if (color == "cyan") { 101 | return TFT_CYAN; 102 | } else if (color == "red") { 103 | return TFT_RED; 104 | } else if (color == "magenta") { 105 | return TFT_MAGENTA; 106 | } else if (color == "yellow") { 107 | return TFT_YELLOW; 108 | } else if (color == "white") { 109 | return TFT_WHITE; 110 | } else if (color == "orange") { 111 | return TFT_ORANGE; 112 | } else if (color == "greenyellow") { 113 | return TFT_GREENYELLOW; 114 | } else if (color == "pink") { 115 | return TFT_PINK; 116 | } else if (color == "brown") { 117 | return TFT_BROWN; 118 | } else if (color == "gold") { 119 | return TFT_GOLD; 120 | } else if (color == "silver") { 121 | return TFT_SILVER; 122 | } else if (color == "skyblue") { 123 | return TFT_SKYBLUE; 124 | } else if (color == "vilolet") { 125 | return TFT_VIOLET; 126 | } else { 127 | Serial.print("Invalid color: "); 128 | Serial.println(color); 129 | return TFT_BLACK; 130 | } 131 | } 132 | 133 | String Utils::formatFloat(float value, int8_t digits) 134 | { 135 | char tmp[30] = {}; 136 | dtostrf(value, 1, digits, tmp); 137 | return tmp; 138 | } 139 | 140 | int32_t Utils::stringToAlignment(String alignment) { 141 | alignment.toLowerCase(); 142 | if (alignment.indexOf(" ") != -1) { 143 | alignment = alignment[0] + alignment.substring(alignment.indexOf(" ") + 1, 1); 144 | } 145 | alignment.replace(" ", ""); 146 | if (alignment == "tl") { 147 | return TL_DATUM; 148 | } else if (alignment == "tc") { 149 | return TC_DATUM; 150 | } else if (alignment == "tr") { 151 | return TR_DATUM; 152 | } else if (alignment == "ml") { 153 | return ML_DATUM; 154 | } else if (alignment == "mc") { 155 | return MC_DATUM; 156 | } else if (alignment == "mr") { 157 | return MR_DATUM; 158 | } else if (alignment == "bl") { 159 | return BL_DATUM; 160 | } else if (alignment == "bc") { 161 | return BC_DATUM; 162 | } else if (alignment == "br") { 163 | return BR_DATUM; 164 | } else if (alignment == "cl") { 165 | return CL_DATUM; 166 | } else if (alignment == "cc") { 167 | return CC_DATUM; 168 | } else if (alignment == "cr") { 169 | return CR_DATUM; 170 | } else if (alignment == "bl") { 171 | return BL_DATUM; 172 | } else if (alignment == "bc") { 173 | return BC_DATUM; 174 | } else if (alignment == "br") { 175 | return BR_DATUM; 176 | } else if (alignment == "l") { 177 | return L_BASELINE; 178 | } else if (alignment == "c") { 179 | return C_BASELINE; 180 | } else if (alignment == "r") { 181 | return R_BASELINE; 182 | } else { 183 | return TL_DATUM; 184 | } 185 | } 186 | 187 | -------------------------------------------------------------------------------- /PCBFiles+Schematics/Stand/StandOrbs.kicad_pro: -------------------------------------------------------------------------------- 1 | { 2 | "board": { 3 | "3dviewports": [], 4 | "design_settings": { 5 | "defaults": { 6 | "apply_defaults_to_fp_fields": false, 7 | "apply_defaults_to_fp_shapes": false, 8 | "apply_defaults_to_fp_text": false, 9 | "board_outline_line_width": 0.05, 10 | "copper_line_width": 0.2, 11 | "copper_text_italic": false, 12 | "copper_text_size_h": 1.5, 13 | "copper_text_size_v": 1.5, 14 | "copper_text_thickness": 0.3, 15 | "copper_text_upright": false, 16 | "courtyard_line_width": 0.05, 17 | "dimension_precision": 4, 18 | "dimension_units": 3, 19 | "dimensions": { 20 | "arrow_length": 1270000, 21 | "extension_offset": 500000, 22 | "keep_text_aligned": true, 23 | "suppress_zeroes": false, 24 | "text_position": 0, 25 | "units_format": 1 26 | }, 27 | "fab_line_width": 0.1, 28 | "fab_text_italic": false, 29 | "fab_text_size_h": 1.0, 30 | "fab_text_size_v": 1.0, 31 | "fab_text_thickness": 0.15, 32 | "fab_text_upright": false, 33 | "other_line_width": 0.1, 34 | "other_text_italic": false, 35 | "other_text_size_h": 1.0, 36 | "other_text_size_v": 1.0, 37 | "other_text_thickness": 0.15, 38 | "other_text_upright": false, 39 | "pads": { 40 | "drill": 0.762, 41 | "height": 1.524, 42 | "width": 1.524 43 | }, 44 | "silk_line_width": 0.1, 45 | "silk_text_italic": false, 46 | "silk_text_size_h": 1.0, 47 | "silk_text_size_v": 1.0, 48 | "silk_text_thickness": 0.1, 49 | "silk_text_upright": false, 50 | "zones": { 51 | "min_clearance": 0.5 52 | } 53 | }, 54 | "diff_pair_dimensions": [], 55 | "drc_exclusions": [], 56 | "meta": { 57 | "version": 2 58 | }, 59 | "rule_severities": { 60 | "annular_width": "error", 61 | "clearance": "error", 62 | "connection_width": "warning", 63 | "copper_edge_clearance": "error", 64 | "copper_sliver": "warning", 65 | "courtyards_overlap": "error", 66 | "diff_pair_gap_out_of_range": "error", 67 | "diff_pair_uncoupled_length_too_long": "error", 68 | "drill_out_of_range": "error", 69 | "duplicate_footprints": "warning", 70 | "extra_footprint": "warning", 71 | "footprint": "error", 72 | "footprint_symbol_mismatch": "warning", 73 | "footprint_type_mismatch": "ignore", 74 | "hole_clearance": "error", 75 | "hole_near_hole": "error", 76 | "invalid_outline": "error", 77 | "isolated_copper": "warning", 78 | "item_on_disabled_layer": "error", 79 | "items_not_allowed": "error", 80 | "length_out_of_range": "error", 81 | "lib_footprint_issues": "warning", 82 | "lib_footprint_mismatch": "warning", 83 | "malformed_courtyard": "error", 84 | "microvia_drill_out_of_range": "error", 85 | "missing_courtyard": "ignore", 86 | "missing_footprint": "warning", 87 | "net_conflict": "warning", 88 | "npth_inside_courtyard": "ignore", 89 | "padstack": "warning", 90 | "pth_inside_courtyard": "ignore", 91 | "shorting_items": "error", 92 | "silk_edge_clearance": "warning", 93 | "silk_over_copper": "warning", 94 | "silk_overlap": "warning", 95 | "skew_out_of_range": "error", 96 | "solder_mask_bridge": "error", 97 | "starved_thermal": "error", 98 | "text_height": "warning", 99 | "text_thickness": "warning", 100 | "through_hole_pad_without_hole": "error", 101 | "too_many_vias": "error", 102 | "track_dangling": "warning", 103 | "track_width": "error", 104 | "tracks_crossing": "error", 105 | "unconnected_items": "error", 106 | "unresolved_variable": "error", 107 | "via_dangling": "warning", 108 | "zones_intersect": "error" 109 | }, 110 | "rules": { 111 | "max_error": 0.005, 112 | "min_clearance": 0.0, 113 | "min_connection": 0.0, 114 | "min_copper_edge_clearance": 0.5, 115 | "min_hole_clearance": 0.25, 116 | "min_hole_to_hole": 0.25, 117 | "min_microvia_diameter": 0.2, 118 | "min_microvia_drill": 0.1, 119 | "min_resolved_spokes": 2, 120 | "min_silk_clearance": 0.0, 121 | "min_text_height": 0.8, 122 | "min_text_thickness": 0.08, 123 | "min_through_hole_diameter": 0.3, 124 | "min_track_width": 0.0, 125 | "min_via_annular_width": 0.1, 126 | "min_via_diameter": 0.5, 127 | "solder_mask_to_copper_clearance": 0.0, 128 | "use_height_for_length_calcs": true 129 | }, 130 | "teardrop_options": [ 131 | { 132 | "td_onpadsmd": true, 133 | "td_onroundshapesonly": false, 134 | "td_ontrackend": false, 135 | "td_onviapad": true 136 | } 137 | ], 138 | "teardrop_parameters": [ 139 | { 140 | "td_allow_use_two_tracks": true, 141 | "td_curve_segcount": 0, 142 | "td_height_ratio": 1.0, 143 | "td_length_ratio": 0.5, 144 | "td_maxheight": 2.0, 145 | "td_maxlen": 1.0, 146 | "td_on_pad_in_zone": false, 147 | "td_target_name": "td_round_shape", 148 | "td_width_to_size_filter_ratio": 0.9 149 | }, 150 | { 151 | "td_allow_use_two_tracks": true, 152 | "td_curve_segcount": 0, 153 | "td_height_ratio": 1.0, 154 | "td_length_ratio": 0.5, 155 | "td_maxheight": 2.0, 156 | "td_maxlen": 1.0, 157 | "td_on_pad_in_zone": false, 158 | "td_target_name": "td_rect_shape", 159 | "td_width_to_size_filter_ratio": 0.9 160 | }, 161 | { 162 | "td_allow_use_two_tracks": true, 163 | "td_curve_segcount": 0, 164 | "td_height_ratio": 1.0, 165 | "td_length_ratio": 0.5, 166 | "td_maxheight": 2.0, 167 | "td_maxlen": 1.0, 168 | "td_on_pad_in_zone": false, 169 | "td_target_name": "td_track_end", 170 | "td_width_to_size_filter_ratio": 0.9 171 | } 172 | ], 173 | "track_widths": [], 174 | "tuning_pattern_settings": { 175 | "diff_pair_defaults": { 176 | "corner_radius_percentage": 80, 177 | "corner_style": 1, 178 | "max_amplitude": 1.0, 179 | "min_amplitude": 0.2, 180 | "single_sided": false, 181 | "spacing": 1.0 182 | }, 183 | "diff_pair_skew_defaults": { 184 | "corner_radius_percentage": 80, 185 | "corner_style": 1, 186 | "max_amplitude": 1.0, 187 | "min_amplitude": 0.2, 188 | "single_sided": false, 189 | "spacing": 0.6 190 | }, 191 | "single_track_defaults": { 192 | "corner_radius_percentage": 80, 193 | "corner_style": 1, 194 | "max_amplitude": 1.0, 195 | "min_amplitude": 0.2, 196 | "single_sided": false, 197 | "spacing": 0.6 198 | } 199 | }, 200 | "via_dimensions": [], 201 | "zones_allow_external_fillets": false 202 | }, 203 | "ipc2581": { 204 | "dist": "", 205 | "distpn": "", 206 | "internal_id": "", 207 | "mfg": "", 208 | "mpn": "" 209 | }, 210 | "layer_presets": [], 211 | "viewports": [] 212 | }, 213 | "boards": [], 214 | "cvpcb": { 215 | "equivalence_files": [] 216 | }, 217 | "libraries": { 218 | "pinned_footprint_libs": [], 219 | "pinned_symbol_libs": [] 220 | }, 221 | "meta": { 222 | "filename": "StandOrbs.kicad_pro", 223 | "version": 1 224 | }, 225 | "net_settings": { 226 | "classes": [ 227 | { 228 | "bus_width": 12, 229 | "clearance": 0.2, 230 | "diff_pair_gap": 0.25, 231 | "diff_pair_via_gap": 0.25, 232 | "diff_pair_width": 0.2, 233 | "line_style": 0, 234 | "microvia_diameter": 0.3, 235 | "microvia_drill": 0.1, 236 | "name": "Default", 237 | "pcb_color": "rgba(0, 0, 0, 0.000)", 238 | "schematic_color": "rgba(0, 0, 0, 0.000)", 239 | "track_width": 0.2, 240 | "via_diameter": 0.6, 241 | "via_drill": 0.3, 242 | "wire_width": 6 243 | } 244 | ], 245 | "meta": { 246 | "version": 3 247 | }, 248 | "net_colors": null, 249 | "netclass_assignments": null, 250 | "netclass_patterns": [] 251 | }, 252 | "pcbnew": { 253 | "last_paths": { 254 | "gencad": "", 255 | "idf": "", 256 | "netlist": "", 257 | "plot": "../../../", 258 | "pos_files": "", 259 | "specctra_dsn": "", 260 | "step": "", 261 | "svg": "", 262 | "vrml": "" 263 | }, 264 | "page_layout_descr_file": "" 265 | }, 266 | "schematic": { 267 | "legacy_lib_dir": "", 268 | "legacy_lib_list": [] 269 | }, 270 | "sheets": [], 271 | "text_variables": {} 272 | } 273 | -------------------------------------------------------------------------------- /PCBFiles+Schematics/Stand/StandOrbs.kicad_pcb: -------------------------------------------------------------------------------- 1 | (kicad_pcb 2 | (version 20240108) 3 | (generator "pcbnew") 4 | (generator_version "8.0") 5 | (general 6 | (thickness 1.6) 7 | (legacy_teardrops no) 8 | ) 9 | (paper "A4") 10 | (layers 11 | (0 "F.Cu" signal) 12 | (31 "B.Cu" signal) 13 | (32 "B.Adhes" user "B.Adhesive") 14 | (33 "F.Adhes" user "F.Adhesive") 15 | (34 "B.Paste" user) 16 | (35 "F.Paste" user) 17 | (36 "B.SilkS" user "B.Silkscreen") 18 | (37 "F.SilkS" user "F.Silkscreen") 19 | (38 "B.Mask" user) 20 | (39 "F.Mask" user) 21 | (40 "Dwgs.User" user "User.Drawings") 22 | (41 "Cmts.User" user "User.Comments") 23 | (42 "Eco1.User" user "User.Eco1") 24 | (43 "Eco2.User" user "User.Eco2") 25 | (44 "Edge.Cuts" user) 26 | (45 "Margin" user) 27 | (46 "B.CrtYd" user "B.Courtyard") 28 | (47 "F.CrtYd" user "F.Courtyard") 29 | (48 "B.Fab" user) 30 | (49 "F.Fab" user) 31 | (50 "User.1" user) 32 | (51 "User.2" user) 33 | (52 "User.3" user) 34 | (53 "User.4" user) 35 | (54 "User.5" user) 36 | (55 "User.6" user) 37 | (56 "User.7" user) 38 | (57 "User.8" user) 39 | (58 "User.9" user) 40 | ) 41 | (setup 42 | (stackup 43 | (layer "F.SilkS" 44 | (type "Top Silk Screen") 45 | ) 46 | (layer "F.Paste" 47 | (type "Top Solder Paste") 48 | ) 49 | (layer "F.Mask" 50 | (type "Top Solder Mask") 51 | (thickness 0.01) 52 | ) 53 | (layer "F.Cu" 54 | (type "copper") 55 | (thickness 0.035) 56 | ) 57 | (layer "dielectric 1" 58 | (type "core") 59 | (thickness 1.51) 60 | (material "FR4") 61 | (epsilon_r 4.5) 62 | (loss_tangent 0.02) 63 | ) 64 | (layer "B.Cu" 65 | (type "copper") 66 | (thickness 0.035) 67 | ) 68 | (layer "B.Mask" 69 | (type "Bottom Solder Mask") 70 | (thickness 0.01) 71 | ) 72 | (layer "B.Paste" 73 | (type "Bottom Solder Paste") 74 | ) 75 | (layer "B.SilkS" 76 | (type "Bottom Silk Screen") 77 | ) 78 | (copper_finish "None") 79 | (dielectric_constraints no) 80 | ) 81 | (pad_to_mask_clearance 0) 82 | (allow_soldermask_bridges_in_footprints no) 83 | (pcbplotparams 84 | (layerselection 0x00010fc_ffffffff) 85 | (plot_on_all_layers_selection 0x0000000_00000000) 86 | (disableapertmacros no) 87 | (usegerberextensions no) 88 | (usegerberattributes yes) 89 | (usegerberadvancedattributes yes) 90 | (creategerberjobfile yes) 91 | (dashed_line_dash_ratio 12.000000) 92 | (dashed_line_gap_ratio 3.000000) 93 | (svgprecision 4) 94 | (plotframeref no) 95 | (viasonmask no) 96 | (mode 1) 97 | (useauxorigin no) 98 | (hpglpennumber 1) 99 | (hpglpenspeed 20) 100 | (hpglpendiameter 15.000000) 101 | (pdf_front_fp_property_popups yes) 102 | (pdf_back_fp_property_popups yes) 103 | (dxfpolygonmode yes) 104 | (dxfimperialunits yes) 105 | (dxfusepcbnewfont yes) 106 | (psnegative no) 107 | (psa4output no) 108 | (plotreference yes) 109 | (plotvalue yes) 110 | (plotfptext yes) 111 | (plotinvisibletext no) 112 | (sketchpadsonfab no) 113 | (subtractmaskfromsilk no) 114 | (outputformat 1) 115 | (mirror no) 116 | (drillshape 0) 117 | (scaleselection 1) 118 | (outputdirectory "../../../") 119 | ) 120 | ) 121 | (net 0 "") 122 | (gr_line 123 | (start 93.175 83.28) 124 | (end 88.875 100.44) 125 | (stroke 126 | (width 0.05) 127 | (type default) 128 | ) 129 | (layer "Edge.Cuts") 130 | (uuid "01dad912-4815-41d0-ad21-10ae53cf4ab6") 131 | ) 132 | (gr_line 133 | (start 101.03 83.27) 134 | (end 106.805 83.28) 135 | (stroke 136 | (width 0.05) 137 | (type default) 138 | ) 139 | (layer "Edge.Cuts") 140 | (uuid "0dfab223-23d9-4df5-8caa-2ea779d13740") 141 | ) 142 | (gr_line 143 | (start 83.12 76.895) 144 | (end 74.875 100.44) 145 | (stroke 146 | (width 0.05) 147 | (type default) 148 | ) 149 | (layer "Edge.Cuts") 150 | (uuid "0e30b31f-b163-4e87-a7e2-c597a7e2fc23") 151 | ) 152 | (gr_line 153 | (start 98.97 80.44) 154 | (end 98.97 83.27) 155 | (stroke 156 | (width 0.05) 157 | (type default) 158 | ) 159 | (layer "Edge.Cuts") 160 | (uuid "151577ac-696b-4c13-9e6b-91ae5c8d4e7e") 161 | ) 162 | (gr_line 163 | (start 101.03 80.44) 164 | (end 101.03 83.27) 165 | (stroke 166 | (width 0.05) 167 | (type default) 168 | ) 169 | (layer "Edge.Cuts") 170 | (uuid "1a10f71e-7506-4c79-892d-51adcece3875") 171 | ) 172 | (gr_line 173 | (start 85.49 79.04) 174 | (end 114.49 79.04) 175 | (stroke 176 | (width 0.05) 177 | (type default) 178 | ) 179 | (layer "Edge.Cuts") 180 | (uuid "1a9cbb8b-9600-4093-a7c3-31ebba4240f5") 181 | ) 182 | (gr_line 183 | (start 98.97 83.27) 184 | (end 93.175 83.28) 185 | (stroke 186 | (width 0.05) 187 | (type default) 188 | ) 189 | (layer "Edge.Cuts") 190 | (uuid "24ce8d7f-a024-4b76-9db9-c07752c66ae7") 191 | ) 192 | (gr_line 193 | (start 116.86 76.895) 194 | (end 83.12 76.895) 195 | (stroke 196 | (width 0.05) 197 | (type default) 198 | ) 199 | (layer "Edge.Cuts") 200 | (uuid "35378fb2-9c5c-4c34-b03e-074af2939ebe") 201 | ) 202 | (gr_line 203 | (start 101.03 83.27) 204 | (end 101.03 80.44) 205 | (stroke 206 | (width 0.05) 207 | (type default) 208 | ) 209 | (layer "Edge.Cuts") 210 | (uuid "389109f5-7191-4319-95aa-ed875dd7700f") 211 | ) 212 | (gr_line 213 | (start 116.86 76.895) 214 | (end 125.125 100.44) 215 | (stroke 216 | (width 0.05) 217 | (type default) 218 | ) 219 | (layer "Edge.Cuts") 220 | (uuid "41a37730-5643-4271-8e69-3f4de8ef4165") 221 | ) 222 | (gr_line 223 | (start 125.125 100.44) 224 | (end 111.125 100.44) 225 | (stroke 226 | (width 0.05) 227 | (type default) 228 | ) 229 | (layer "Edge.Cuts") 230 | (uuid "4b153c5c-62b0-42d0-9015-c0c4c92f9bfe") 231 | ) 232 | (gr_line 233 | (start 111.125 100.44) 234 | (end 125.125 100.44) 235 | (stroke 236 | (width 0.05) 237 | (type default) 238 | ) 239 | (layer "Edge.Cuts") 240 | (uuid "4df70e83-1c80-46c6-8b10-2379fe6d401d") 241 | ) 242 | (gr_line 243 | (start 114.49 79.04) 244 | (end 114.49 80.44) 245 | (stroke 246 | (width 0.05) 247 | (type default) 248 | ) 249 | (layer "Edge.Cuts") 250 | (uuid "59203f73-2807-4b6d-b6c6-1420991fa667") 251 | ) 252 | (gr_line 253 | (start 83.12 76.895) 254 | (end 116.86 76.895) 255 | (stroke 256 | (width 0.05) 257 | (type default) 258 | ) 259 | (layer "Edge.Cuts") 260 | (uuid "5c07afc8-47d9-42a8-b31e-92bda3b97760") 261 | ) 262 | (gr_line 263 | (start 74.875 100.44) 264 | (end 83.12 76.895) 265 | (stroke 266 | (width 0.05) 267 | (type default) 268 | ) 269 | (layer "Edge.Cuts") 270 | (uuid "5cfb0365-fd19-48c8-8fc5-01de8061ba8d") 271 | ) 272 | (gr_line 273 | (start 88.875 100.44) 274 | (end 93.175 83.28) 275 | (stroke 276 | (width 0.05) 277 | (type default) 278 | ) 279 | (layer "Edge.Cuts") 280 | (uuid "6376df53-38e6-470c-8770-d8f980d74a36") 281 | ) 282 | (gr_line 283 | (start 85.49 79.04) 284 | (end 85.49 80.44) 285 | (stroke 286 | (width 0.05) 287 | (type default) 288 | ) 289 | (layer "Edge.Cuts") 290 | (uuid "861f0f0a-3765-4a06-9f15-b71a200ebced") 291 | ) 292 | (gr_line 293 | (start 98.97 80.44) 294 | (end 98.97 83.27) 295 | (stroke 296 | (width 0.05) 297 | (type default) 298 | ) 299 | (layer "Edge.Cuts") 300 | (uuid "8c9ec1c2-bfb2-497c-9b36-eb52f3dd2e8d") 301 | ) 302 | (gr_line 303 | (start 74.875 100.44) 304 | (end 83.12 76.895) 305 | (stroke 306 | (width 0.05) 307 | (type default) 308 | ) 309 | (layer "Edge.Cuts") 310 | (uuid "8e085b8a-9289-4e36-9b6e-45bb0019d247") 311 | ) 312 | (gr_line 313 | (start 93.175 83.28) 314 | (end 98.97 83.27) 315 | (stroke 316 | (width 0.05) 317 | (type default) 318 | ) 319 | (layer "Edge.Cuts") 320 | (uuid "a9f5e100-4232-405c-b858-0faa6f3d189b") 321 | ) 322 | (gr_line 323 | (start 85.49 80.44) 324 | (end 98.97 80.44) 325 | (stroke 326 | (width 0.05) 327 | (type default) 328 | ) 329 | (layer "Edge.Cuts") 330 | (uuid "abb25195-918d-4f7d-9ccb-8335e5b7e939") 331 | ) 332 | (gr_line 333 | (start 106.805 83.28) 334 | (end 101.03 83.27) 335 | (stroke 336 | (width 0.05) 337 | (type default) 338 | ) 339 | (layer "Edge.Cuts") 340 | (uuid "ac58dc5a-cf5d-46c8-bbe8-03e1d8bb33ef") 341 | ) 342 | (gr_line 343 | (start 116.86 76.895) 344 | (end 125.125 100.44) 345 | (stroke 346 | (width 0.05) 347 | (type default) 348 | ) 349 | (layer "Edge.Cuts") 350 | (uuid "bf690ff6-19de-4f92-b4c4-f43b03ea3e96") 351 | ) 352 | (gr_line 353 | (start 111.125 100.44) 354 | (end 106.805 83.28) 355 | (stroke 356 | (width 0.05) 357 | (type default) 358 | ) 359 | (layer "Edge.Cuts") 360 | (uuid "bfbd5113-fb32-455f-b7e6-48d9c6bb9a75") 361 | ) 362 | (gr_line 363 | (start 106.805 83.28) 364 | (end 111.125 100.44) 365 | (stroke 366 | (width 0.05) 367 | (type default) 368 | ) 369 | (layer "Edge.Cuts") 370 | (uuid "d515dd03-87f1-4834-91e6-d425765fa943") 371 | ) 372 | (gr_line 373 | (start 101.03 80.44) 374 | (end 114.49 80.44) 375 | (stroke 376 | (width 0.05) 377 | (type default) 378 | ) 379 | (layer "Edge.Cuts") 380 | (uuid "e0b30410-7afd-4c95-8a00-79736e86db84") 381 | ) 382 | (gr_line 383 | (start 74.875 100.44) 384 | (end 88.875 100.44) 385 | (stroke 386 | (width 0.05) 387 | (type default) 388 | ) 389 | (layer "Edge.Cuts") 390 | (uuid "e4193bd9-b1cf-4f48-8c8d-d6c5fe7b8381") 391 | ) 392 | (gr_line 393 | (start 88.875 100.44) 394 | (end 74.875 100.44) 395 | (stroke 396 | (width 0.05) 397 | (type default) 398 | ) 399 | (layer "Edge.Cuts") 400 | (uuid "fdcff59c-fcd8-44b1-af7a-a846f5ad836f") 401 | ) 402 | (gr_text "die by the orbs" 403 | (at 111.96 99.68 0) 404 | (layer "F.SilkS") 405 | (uuid "2f9bd70b-4b55-4493-8765-5545cf70345b") 406 | (effects 407 | (font 408 | (size 0.8 1) 409 | (thickness 0.1) 410 | ) 411 | (justify left bottom) 412 | ) 413 | ) 414 | (gr_text "live by the orbs" 415 | (at 76.15 99.72 0) 416 | (layer "F.SilkS") 417 | (uuid "5e0de8b9-ec2d-472d-9136-86612a92b5f2") 418 | (effects 419 | (font 420 | (size 0.8 1) 421 | (thickness 0.1) 422 | ) 423 | (justify left bottom) 424 | ) 425 | ) 426 | ) -------------------------------------------------------------------------------- /PCBFiles+Schematics/Main PCB/ESP32-DEVKITC-32D.kicad_sym: -------------------------------------------------------------------------------- 1 | 2 | (kicad_symbol_lib (version 20211014) (generator kicad_symbol_editor) 3 | (symbol "ESP32-DEVKITC-32D" (pin_names (offset 1.016)) (in_bom yes) (on_board yes) 4 | (property "Reference" "U" (id 0) (at -15.2572 26.0643 0) 5 | (effects (font (size 1.27 1.27)) (justify bottom left)) 6 | ) 7 | (property "Value" "ESP32-DEVKITC-32D" (id 1) (at -15.2563 -27.9698 0) 8 | (effects (font (size 1.27 1.27)) (justify bottom left)) 9 | ) 10 | (property "Footprint" "ESP32-DEVKITC-32D:MODULE_ESP32-DEVKITC-32D" (id 2) (at 0 0 0) 11 | (effects (font (size 1.27 1.27)) (justify bottom) hide) 12 | ) 13 | (property "MF" "Espressif Systems" (id 4) (at 0 0 0) 14 | (effects (font (size 1.27 1.27)) (justify bottom) hide) 15 | ) 16 | (property "MAXIMUM_PACKAGE_HEIGHT" "N/A" (id 5) (at 0 0 0) 17 | (effects (font (size 1.27 1.27)) (justify bottom) hide) 18 | ) 19 | (property "Package" "None" (id 6) (at 0 0 0) 20 | (effects (font (size 1.27 1.27)) (justify bottom) hide) 21 | ) 22 | (property "Price" "None" (id 7) (at 0 0 0) 23 | (effects (font (size 1.27 1.27)) (justify bottom) hide) 24 | ) 25 | (property "Check_prices" "https://www.snapeda.com/parts/ESP32-DEVKITC-32D/Espressif+Systems/view-part/?ref=eda" (id 8) (at 0 0 0) 26 | (effects (font (size 1.27 1.27)) (justify bottom) hide) 27 | ) 28 | (property "STANDARD" "Manufacturer Recommendations" (id 9) (at 0 0 0) 29 | (effects (font (size 1.27 1.27)) (justify bottom) hide) 30 | ) 31 | (property "PARTREV" "V4" (id 10) (at 0 0 0) 32 | (effects (font (size 1.27 1.27)) (justify bottom) hide) 33 | ) 34 | (property "SnapEDA_Link" "https://www.snapeda.com/parts/ESP32-DEVKITC-32D/Espressif+Systems/view-part/?ref=snap" (id 11) (at 0 0 0) 35 | (effects (font (size 1.27 1.27)) (justify bottom) hide) 36 | ) 37 | (property "MP" "ESP32-DEVKITC-32D" (id 12) (at 0 0 0) 38 | (effects (font (size 1.27 1.27)) (justify bottom) hide) 39 | ) 40 | (property "Description" "\nWiFi Development Tools (802.11) ESP32 General Development Kit, ESP32-WROOM-32D on the board\n" (id 13) (at 0 0 0) 41 | (effects (font (size 1.27 1.27)) (justify bottom) hide) 42 | ) 43 | (property "MANUFACTURER" "Espressif Systems" (id 14) (at 0 0 0) 44 | (effects (font (size 1.27 1.27)) (justify bottom) hide) 45 | ) 46 | (property "Availability" "In Stock" (id 15) (at 0 0 0) 47 | (effects (font (size 1.27 1.27)) (justify bottom) hide) 48 | ) 49 | (property "SNAPEDA_PN" "ESP32-DEVKITC-32D" (id 16) (at 0 0 0) 50 | (effects (font (size 1.27 1.27)) (justify bottom) hide) 51 | ) 52 | (symbol "ESP32-DEVKITC-32D_0_0" 53 | (rectangle (start -15.24 -25.4) (end 15.24 25.4) 54 | (stroke (width 0.254)) (fill (type background)) 55 | ) 56 | (pin power_in line (at -20.32 22.86 0) (length 5.08) 57 | (name "3V3" 58 | (effects (font (size 1.016 1.016))) 59 | ) 60 | (number "J2-1" 61 | (effects (font (size 1.016 1.016))) 62 | ) 63 | ) 64 | (pin input line (at -20.32 20.32 0) (length 5.08) 65 | (name "EN" 66 | (effects (font (size 1.016 1.016))) 67 | ) 68 | (number "J2-2" 69 | (effects (font (size 1.016 1.016))) 70 | ) 71 | ) 72 | (pin input line (at -20.32 17.78 0) (length 5.08) 73 | (name "SENSOR_VP" 74 | (effects (font (size 1.016 1.016))) 75 | ) 76 | (number "J2-3" 77 | (effects (font (size 1.016 1.016))) 78 | ) 79 | ) 80 | (pin input line (at -20.32 15.24 0) (length 5.08) 81 | (name "SENSOR_VN" 82 | (effects (font (size 1.016 1.016))) 83 | ) 84 | (number "J2-4" 85 | (effects (font (size 1.016 1.016))) 86 | ) 87 | ) 88 | (pin bidirectional line (at -20.32 12.7 0) (length 5.08) 89 | (name "IO34" 90 | (effects (font (size 1.016 1.016))) 91 | ) 92 | (number "J2-5" 93 | (effects (font (size 1.016 1.016))) 94 | ) 95 | ) 96 | (pin bidirectional line (at -20.32 10.16 0) (length 5.08) 97 | (name "IO35" 98 | (effects (font (size 1.016 1.016))) 99 | ) 100 | (number "J2-6" 101 | (effects (font (size 1.016 1.016))) 102 | ) 103 | ) 104 | (pin bidirectional line (at -20.32 7.62 0) (length 5.08) 105 | (name "IO32" 106 | (effects (font (size 1.016 1.016))) 107 | ) 108 | (number "J2-7" 109 | (effects (font (size 1.016 1.016))) 110 | ) 111 | ) 112 | (pin bidirectional line (at -20.32 5.08 0) (length 5.08) 113 | (name "IO33" 114 | (effects (font (size 1.016 1.016))) 115 | ) 116 | (number "J2-8" 117 | (effects (font (size 1.016 1.016))) 118 | ) 119 | ) 120 | (pin bidirectional line (at -20.32 2.54 0) (length 5.08) 121 | (name "IO25" 122 | (effects (font (size 1.016 1.016))) 123 | ) 124 | (number "J2-9" 125 | (effects (font (size 1.016 1.016))) 126 | ) 127 | ) 128 | (pin bidirectional line (at -20.32 0.0 0) (length 5.08) 129 | (name "IO26" 130 | (effects (font (size 1.016 1.016))) 131 | ) 132 | (number "J2-10" 133 | (effects (font (size 1.016 1.016))) 134 | ) 135 | ) 136 | (pin bidirectional line (at -20.32 -2.54 0) (length 5.08) 137 | (name "IO27" 138 | (effects (font (size 1.016 1.016))) 139 | ) 140 | (number "J2-11" 141 | (effects (font (size 1.016 1.016))) 142 | ) 143 | ) 144 | (pin bidirectional line (at -20.32 -5.08 0) (length 5.08) 145 | (name "IO14" 146 | (effects (font (size 1.016 1.016))) 147 | ) 148 | (number "J2-12" 149 | (effects (font (size 1.016 1.016))) 150 | ) 151 | ) 152 | (pin bidirectional line (at -20.32 -7.62 0) (length 5.08) 153 | (name "IO12" 154 | (effects (font (size 1.016 1.016))) 155 | ) 156 | (number "J2-13" 157 | (effects (font (size 1.016 1.016))) 158 | ) 159 | ) 160 | (pin power_in line (at -20.32 -10.16 0) (length 5.08) 161 | (name "GND1" 162 | (effects (font (size 1.016 1.016))) 163 | ) 164 | (number "J2-14" 165 | (effects (font (size 1.016 1.016))) 166 | ) 167 | ) 168 | (pin bidirectional line (at -20.32 -12.7 0) (length 5.08) 169 | (name "IO13" 170 | (effects (font (size 1.016 1.016))) 171 | ) 172 | (number "J2-15" 173 | (effects (font (size 1.016 1.016))) 174 | ) 175 | ) 176 | (pin bidirectional line (at -20.32 -15.24 0) (length 5.08) 177 | (name "SD2" 178 | (effects (font (size 1.016 1.016))) 179 | ) 180 | (number "J2-16" 181 | (effects (font (size 1.016 1.016))) 182 | ) 183 | ) 184 | (pin bidirectional line (at -20.32 -17.78 0) (length 5.08) 185 | (name "SD3" 186 | (effects (font (size 1.016 1.016))) 187 | ) 188 | (number "J2-17" 189 | (effects (font (size 1.016 1.016))) 190 | ) 191 | ) 192 | (pin bidirectional line (at -20.32 -20.32 0) (length 5.08) 193 | (name "CMD" 194 | (effects (font (size 1.016 1.016))) 195 | ) 196 | (number "J2-18" 197 | (effects (font (size 1.016 1.016))) 198 | ) 199 | ) 200 | (pin power_in line (at -20.32 -22.86 0) (length 5.08) 201 | (name "EXT_5V" 202 | (effects (font (size 1.016 1.016))) 203 | ) 204 | (number "J2-19" 205 | (effects (font (size 1.016 1.016))) 206 | ) 207 | ) 208 | (pin input clock (at 20.32 -22.86 180.0) (length 5.08) 209 | (name "CLK" 210 | (effects (font (size 1.016 1.016))) 211 | ) 212 | (number "J3-19" 213 | (effects (font (size 1.016 1.016))) 214 | ) 215 | ) 216 | (pin bidirectional line (at 20.32 -20.32 180.0) (length 5.08) 217 | (name "SD0" 218 | (effects (font (size 1.016 1.016))) 219 | ) 220 | (number "J3-18" 221 | (effects (font (size 1.016 1.016))) 222 | ) 223 | ) 224 | (pin bidirectional line (at 20.32 -17.78 180.0) (length 5.08) 225 | (name "SD1" 226 | (effects (font (size 1.016 1.016))) 227 | ) 228 | (number "J3-17" 229 | (effects (font (size 1.016 1.016))) 230 | ) 231 | ) 232 | (pin bidirectional line (at 20.32 -15.24 180.0) (length 5.08) 233 | (name "IO15" 234 | (effects (font (size 1.016 1.016))) 235 | ) 236 | (number "J3-16" 237 | (effects (font (size 1.016 1.016))) 238 | ) 239 | ) 240 | (pin bidirectional line (at 20.32 -12.7 180.0) (length 5.08) 241 | (name "IO2" 242 | (effects (font (size 1.016 1.016))) 243 | ) 244 | (number "J3-15" 245 | (effects (font (size 1.016 1.016))) 246 | ) 247 | ) 248 | (pin bidirectional line (at 20.32 -10.16 180.0) (length 5.08) 249 | (name "IO0" 250 | (effects (font (size 1.016 1.016))) 251 | ) 252 | (number "J3-14" 253 | (effects (font (size 1.016 1.016))) 254 | ) 255 | ) 256 | (pin bidirectional line (at 20.32 -7.62 180.0) (length 5.08) 257 | (name "IO4" 258 | (effects (font (size 1.016 1.016))) 259 | ) 260 | (number "J3-13" 261 | (effects (font (size 1.016 1.016))) 262 | ) 263 | ) 264 | (pin bidirectional line (at 20.32 -5.08 180.0) (length 5.08) 265 | (name "IO16" 266 | (effects (font (size 1.016 1.016))) 267 | ) 268 | (number "J3-12" 269 | (effects (font (size 1.016 1.016))) 270 | ) 271 | ) 272 | (pin bidirectional line (at 20.32 -2.54 180.0) (length 5.08) 273 | (name "IO17" 274 | (effects (font (size 1.016 1.016))) 275 | ) 276 | (number "J3-11" 277 | (effects (font (size 1.016 1.016))) 278 | ) 279 | ) 280 | (pin bidirectional line (at 20.32 0.0 180.0) (length 5.08) 281 | (name "IO5" 282 | (effects (font (size 1.016 1.016))) 283 | ) 284 | (number "J3-10" 285 | (effects (font (size 1.016 1.016))) 286 | ) 287 | ) 288 | (pin bidirectional line (at 20.32 2.54 180.0) (length 5.08) 289 | (name "IO18" 290 | (effects (font (size 1.016 1.016))) 291 | ) 292 | (number "J3-9" 293 | (effects (font (size 1.016 1.016))) 294 | ) 295 | ) 296 | (pin bidirectional line (at 20.32 5.08 180.0) (length 5.08) 297 | (name "IO19" 298 | (effects (font (size 1.016 1.016))) 299 | ) 300 | (number "J3-8" 301 | (effects (font (size 1.016 1.016))) 302 | ) 303 | ) 304 | (pin power_in line (at 20.32 7.62 180.0) (length 5.08) 305 | (name "GND2" 306 | (effects (font (size 1.016 1.016))) 307 | ) 308 | (number "J3-7" 309 | (effects (font (size 1.016 1.016))) 310 | ) 311 | ) 312 | (pin bidirectional line (at 20.32 10.16 180.0) (length 5.08) 313 | (name "IO21" 314 | (effects (font (size 1.016 1.016))) 315 | ) 316 | (number "J3-6" 317 | (effects (font (size 1.016 1.016))) 318 | ) 319 | ) 320 | (pin input line (at 20.32 12.7 180.0) (length 5.08) 321 | (name "RXD0" 322 | (effects (font (size 1.016 1.016))) 323 | ) 324 | (number "J3-5" 325 | (effects (font (size 1.016 1.016))) 326 | ) 327 | ) 328 | (pin output line (at 20.32 15.24 180.0) (length 5.08) 329 | (name "TXD0" 330 | (effects (font (size 1.016 1.016))) 331 | ) 332 | (number "J3-4" 333 | (effects (font (size 1.016 1.016))) 334 | ) 335 | ) 336 | (pin bidirectional line (at 20.32 17.78 180.0) (length 5.08) 337 | (name "IO22" 338 | (effects (font (size 1.016 1.016))) 339 | ) 340 | (number "J3-3" 341 | (effects (font (size 1.016 1.016))) 342 | ) 343 | ) 344 | (pin bidirectional line (at 20.32 20.32 180.0) (length 5.08) 345 | (name "IO23" 346 | (effects (font (size 1.016 1.016))) 347 | ) 348 | (number "J3-2" 349 | (effects (font (size 1.016 1.016))) 350 | ) 351 | ) 352 | (pin power_in line (at 20.32 22.86 180.0) (length 5.08) 353 | (name "GND3" 354 | (effects (font (size 1.016 1.016))) 355 | ) 356 | (number "J3-1" 357 | (effects (font (size 1.016 1.016))) 358 | ) 359 | ) 360 | ) 361 | ) 362 | ) -------------------------------------------------------------------------------- /Info-Orbs/src/widgets/weatherWidget.cpp: -------------------------------------------------------------------------------- 1 | 2 | #include "widgets/weatherWidget.h" 3 | #include "icons.h" 4 | 5 | #include 6 | 7 | WeatherWidget::WeatherWidget(ScreenManager &manager) : Widget(manager) { 8 | m_mode = MODE_HIGHS; 9 | } 10 | 11 | WeatherWidget::~WeatherWidget() { 12 | } 13 | 14 | void WeatherWidget::changeMode() { 15 | m_mode++; 16 | if (m_mode > MODE_LOWS) { 17 | m_mode = MODE_HIGHS; 18 | } 19 | draw(true); 20 | } 21 | 22 | void WeatherWidget::setup() { 23 | m_lastUpdate = millis() - m_updateDelay + 1000; 24 | m_time = GlobalTime::getInstance(); 25 | } 26 | 27 | void WeatherWidget::draw(bool force) { 28 | m_time->updateTime(); 29 | int clockStamp = getClockStamp(); 30 | if (clockStamp != m_clockStamp || force) { 31 | displayClock(0, TFT_WHITE, TFT_BLACK); 32 | m_clockStamp = clockStamp; 33 | } 34 | 35 | // Weather, displays a clock, city & text weather discription, weather icon, temp, 3 day forecast 36 | if (force || model.isChanged()) { 37 | weatherText(1, TFT_WHITE, TFT_BLACK); 38 | drawWeatherIcon(model.getCurrentIcon(), 2, 0, 0, 1); 39 | singleWeatherDeg(3, TFT_WHITE, TFT_BLACK); 40 | threeDayWeather(4); 41 | model.setChangedStatus(false); 42 | } 43 | } 44 | 45 | void WeatherWidget::update(bool force) { 46 | if (force || m_lastUpdate == 0 || (millis() - m_lastUpdate) >= m_updateDelay) { 47 | setBusy(true); 48 | if (force) { 49 | int retry = 0; 50 | while (!getWeatherData() && retry++ < MAX_RETRIES); 51 | } else { 52 | getWeatherData(); 53 | } 54 | setBusy(false); 55 | m_lastUpdate = millis(); 56 | } 57 | } 58 | 59 | bool WeatherWidget::getWeatherData() { 60 | HTTPClient http; 61 | http.begin(httpRequestAddress); 62 | int httpCode = http.GET(); 63 | if (httpCode > 0) { // Check for the returning code 64 | JsonDocument doc; 65 | DeserializationError error = deserializeJson(doc, http.getString()); 66 | http.end(); 67 | 68 | if (!error) { 69 | model.setCityName(doc["resolvedAddress"].as()); 70 | model.setCurrentTemperature(doc["currentConditions"]["temp"].as()); 71 | model.setCurrentText(doc["days"][0]["description"].as()); 72 | 73 | model.setCurrentIcon(doc["currentConditions"]["icon"].as()); 74 | model.setTodayHigh(doc["days"][0]["tempmax"].as()); 75 | model.setTodayLow(doc["days"][0]["tempmin"].as()); 76 | for (int i = 0; i < 3; i++) { 77 | model.setDayIcon(i, doc["days"][i + 1]["icon"].as()); 78 | model.setDayHigh(i, doc["days"][i + 1]["tempmax"].as()); 79 | model.setDayLow(i, doc["days"][i + 1]["tempmin"].as()); 80 | } 81 | } else { 82 | // Handle JSON deserialization error 83 | switch (error.code()) { 84 | case DeserializationError::Ok: 85 | Serial.print(F("Deserialization succeeded")); 86 | break; 87 | case DeserializationError::InvalidInput: 88 | Serial.print(F("Invalid input!")); 89 | break; 90 | case DeserializationError::NoMemory: 91 | Serial.print(F("Not enough memory")); 92 | break; 93 | default: 94 | Serial.print(F("Deserialization failed")); 95 | break; 96 | } 97 | 98 | return false; 99 | } 100 | } else { 101 | // Handle HTTP request error 102 | Serial.printf("HTTP request failed, error: %s\n", http.errorToString(httpCode).c_str()); 103 | http.end(); 104 | return false; 105 | } 106 | return true; 107 | } 108 | 109 | void WeatherWidget::displayClock(int displayIndex, uint32_t background, uint32_t color) { 110 | m_manager.selectScreen(displayIndex); 111 | 112 | TFT_eSPI &display = m_manager.getDisplay(); 113 | 114 | int clky = 95; 115 | display.setTextColor(color); 116 | display.setTextSize(1); 117 | display.setTextDatum(MC_DATUM); 118 | 119 | display.fillScreen(background); 120 | display.setTextColor(color); 121 | display.setTextSize(2); 122 | display.setTextDatum(MC_DATUM); 123 | #ifdef WEATHER_UNITS_METRIC 124 | display.drawString(String(m_time->getDay()) + " " + m_time->getMonthName(), centre, 151, 2); 125 | #else 126 | display.drawString(m_time->getMonthName() + " " + String(m_time->getDay()), centre, 151, 2); 127 | #endif 128 | display.setTextSize(3); 129 | display.drawString(m_time->getWeekday(), centre, 178, 2); 130 | display.setTextColor(color); 131 | display.setTextDatum(MR_DATUM); 132 | display.setTextSize(1); 133 | 134 | display.drawString(m_time->getHourPadded(), centre - 5, clky, 8); 135 | 136 | display.setTextColor(color); 137 | display.setTextDatum(ML_DATUM); 138 | display.setTextSize(1); 139 | display.drawString(m_time->getMinutePadded(), centre + 5, clky, 8); 140 | display.setTextDatum(MC_DATUM); 141 | display.setTextColor(color); 142 | display.drawString(":", centre, clky, 8); 143 | } 144 | 145 | // This will write an image to the screen when called from a hex array. Pass in: 146 | // Screen #, X, Y coords, Bye Array To Pass, the sizeof that array, scale of the image(1= full size, then multiples of 2 to scale down) 147 | // getting the byte array size is very annoying as its computed on compile so you cant do it dynamicly. 148 | void WeatherWidget::showJPG(int displayIndex, int x, int y, const byte jpgData[], int jpgDataSize, int scale) { 149 | m_manager.selectScreen(displayIndex); 150 | 151 | TJpgDec.setJpgScale(scale); 152 | uint16_t w = 0, h = 0; 153 | TJpgDec.getJpgSize(&w, &h, jpgData, jpgDataSize); 154 | TJpgDec.drawJpg(x, y, jpgData, jpgDataSize); 155 | } 156 | 157 | // This takes the text output form the weatehr API and maps it to arespective icon/byte aarray, then displays it, 158 | void WeatherWidget::drawWeatherIcon(String condition, int displayIndex, int x, int y, int scale) { 159 | const byte *icon = NULL; 160 | int size = 0; 161 | if (condition == "partly-cloudy-night") { 162 | icon = moonCloud_start; 163 | size = moonCloud_end - moonCloud_start; 164 | } else if (condition == "partly-cloudy-day") { 165 | icon = sunClouds_start; 166 | size = sunClouds_end - sunClouds_start; 167 | } else if (condition == "clear-day") { 168 | icon = sun_start; 169 | size = sun_end - sun_start; 170 | } else if (condition == "clear-night") { 171 | icon = moon_start; 172 | size = moon_end - moon_start; 173 | } else if (condition == "snow") { 174 | icon = snow_start; 175 | size = snow_end - snow_start; 176 | } else if (condition == "rain") { 177 | icon = rain_start; 178 | size = rain_end - rain_start; 179 | } else if (condition == "fog" || condition == "wind" || condition == "cloudy") { 180 | icon = clouds_start; 181 | size = clouds_end - clouds_start; 182 | } else { 183 | Serial.println("unknown weather icon:" + condition); 184 | } 185 | if (icon != NULL && size > 0) { 186 | showJPG(displayIndex, x, y, icon, size, scale); 187 | } 188 | } 189 | 190 | // This displays the current weather temp on a single screen. Pass in display number, background color, text color 191 | // dosent round deg, just removes all text after the decimil, should prob be fixed 192 | void WeatherWidget::singleWeatherDeg(int displayIndex, uint32_t backgroundColor, uint32_t textColor) { 193 | m_manager.selectScreen(displayIndex); 194 | 195 | TFT_eSPI &display = m_manager.getDisplay(); 196 | display.fillScreen(backgroundColor); 197 | 198 | drawDegrees(model.getCurrentTemperature(0), centre, 100, 8, 1, 15, 8, textColor, backgroundColor); 199 | 200 | display.fillRect(0, 170, 240, 70, TFT_BLACK); 201 | 202 | display.fillRect(centre - 1, 170, 2, 240, TFT_WHITE); 203 | 204 | display.setTextColor(TFT_WHITE); 205 | display.setTextSize(2); 206 | display.drawString("High", 80, 190, 1); 207 | drawDegrees(model.getTodayHigh(0), 80, 210, 1, 2, 4, 2, TFT_WHITE, TFT_BLACK); 208 | display.drawString("Low", 160, 190, 1); 209 | drawDegrees(model.getTodayLow(0), 160, 210, 1, 2, 4, 2, TFT_WHITE, TFT_BLACK); 210 | } 211 | 212 | // This displays the users current city and the text desctiption of the weather. Pass in display number, background color, text color 213 | void WeatherWidget::weatherText(int displayIndex, int16_t b, int16_t t) { 214 | m_manager.selectScreen(displayIndex); 215 | TFT_eSPI &display = m_manager.getDisplay(); 216 | //=== TEXT OVERFLOW ============================ 217 | // this takes a given string a and breaks it down in max x character long strings ensuring not to break it only at a space. 218 | // given the small width of the screens this will porbablly be needed to this project again so making sure to outline it 219 | // clearly as this should liekly eventually be turned into a fucntion. Before use the array size should be made to be dynamic. 220 | // In this case its used for the weather text description 221 | 222 | String message = model.getCurrentText() + " "; 223 | String messageArr[4]; 224 | int variableRangeS = 0; 225 | int variableRangeE = 18; 226 | for (int i = 0; i < 4; i++) { 227 | while (message.substring(variableRangeE - 1, variableRangeE) != " ") { 228 | variableRangeE--; 229 | } 230 | messageArr[i] = message.substring(variableRangeS, variableRangeE); 231 | variableRangeS = variableRangeE; 232 | variableRangeE = variableRangeS + 18; 233 | } 234 | //=== OVERFLOW END ============================== 235 | 236 | display.fillScreen(b); 237 | display.setTextColor(t); 238 | display.setTextSize(3); 239 | display.setTextDatum(MC_DATUM); 240 | String cityName = model.getCityName(); 241 | cityName.remove(cityName.indexOf(",", 0)); 242 | display.drawString(cityName, centre, 80, 2); 243 | display.setTextSize(2); 244 | display.setTextFont(1); 245 | display.drawString(messageArr[0], centre, 120); 246 | display.drawString(messageArr[1], centre, 140); 247 | display.drawString(messageArr[2], centre, 160); 248 | display.drawString(messageArr[3], centre, 180); 249 | } 250 | 251 | // This displays the next 3 days weather forecast 252 | void WeatherWidget::threeDayWeather(int displayIndex) { 253 | m_manager.selectScreen(displayIndex); 254 | TFT_eSPI &display = m_manager.getDisplay(); 255 | 256 | display.setTextDatum(MC_DATUM); 257 | display.fillScreen(TFT_WHITE); 258 | display.setTextSize(2); 259 | 260 | display.fillRect(78, 0, 3, 240, TFT_BLACK); 261 | display.fillRect(157, 0, 3, 240, TFT_BLACK); 262 | 263 | display.fillRect(0, 170, 240, 70, TFT_BLACK); 264 | display.setTextColor(TFT_WHITE); 265 | display.drawString("Next 3 Days", centre, 191, 1); 266 | 267 | for (int i = 0; i < 3; i++) { 268 | int xOffset = (centre - 75) + i * 75; 269 | String temperature; 270 | display.setTextColor(TFT_WHITE); 271 | if (m_mode == MODE_HIGHS) { 272 | temperature = model.getDayHigh(i, 0); 273 | if (temperature != "") { 274 | display.drawString("Highs", centre, 215, 1); 275 | } 276 | } else if (m_mode == MODE_LOWS) { 277 | temperature = model.getDayLow(i, 0); 278 | if (temperature != "") { 279 | display.drawString("Lows", centre, 215, 1); 280 | } 281 | } 282 | drawWeatherIcon(model.getDayIcon(i), displayIndex, xOffset - 30, 47, 4); 283 | display.setTextColor(TFT_BLACK); 284 | if (temperature != "") { 285 | drawDegrees(temperature, xOffset, centre, 2, 2, 4, 2, TFT_BLACK, TFT_WHITE); 286 | } 287 | 288 | String weekUpdate = dayStr(weekday(m_time->getUnixEpoch() + (86400 * (i + 1)))); 289 | weekUpdate.remove(3); 290 | weekUpdate.toUpperCase(); 291 | display.drawString(weekUpdate, xOffset, 150, 2); 292 | } 293 | } 294 | 295 | int WeatherWidget::drawDegrees(String number, int x, int y, uint8_t font, uint8_t size, uint8_t outerRadius, uint8_t innerRadius, int16_t textColor, int16_t backgroundColor) { 296 | TFT_eSPI &display = m_manager.getDisplay(); 297 | 298 | display.setTextColor(textColor); 299 | display.setTextFont(font); 300 | display.setTextSize(size); 301 | display.setTextDatum(MC_DATUM); 302 | 303 | int16_t textWidth = display.textWidth(number); 304 | int16_t fontHeight = display.fontHeight(font); 305 | int offset = ceil(fontHeight * 0.15); 306 | int circleX = textWidth / 2 + x + offset; 307 | int circleY = y - fontHeight / 2 + floor(fontHeight / 10); 308 | 309 | display.drawString(number, x, y, font); 310 | display.fillCircle(circleX, circleY, outerRadius, textColor); 311 | display.fillCircle(circleX, circleY, innerRadius, backgroundColor); 312 | 313 | return textWidth + offset; 314 | } 315 | 316 | int WeatherWidget::getClockStamp() { 317 | return m_time->getHour() * 60 + m_time->getMinute(); 318 | } 319 | -------------------------------------------------------------------------------- /PCBFiles+Schematics/Main PCB/info_orbs.kicad_pro: -------------------------------------------------------------------------------- 1 | { 2 | "board": { 3 | "3dviewports": [], 4 | "design_settings": { 5 | "defaults": { 6 | "apply_defaults_to_fp_fields": false, 7 | "apply_defaults_to_fp_shapes": false, 8 | "apply_defaults_to_fp_text": false, 9 | "board_outline_line_width": 0.05, 10 | "copper_line_width": 0.2, 11 | "copper_text_italic": false, 12 | "copper_text_size_h": 1.5, 13 | "copper_text_size_v": 1.5, 14 | "copper_text_thickness": 0.3, 15 | "copper_text_upright": false, 16 | "courtyard_line_width": 0.05, 17 | "dimension_precision": 4, 18 | "dimension_units": 3, 19 | "dimensions": { 20 | "arrow_length": 1270000, 21 | "extension_offset": 500000, 22 | "keep_text_aligned": true, 23 | "suppress_zeroes": false, 24 | "text_position": 0, 25 | "units_format": 1 26 | }, 27 | "fab_line_width": 0.1, 28 | "fab_text_italic": false, 29 | "fab_text_size_h": 1.0, 30 | "fab_text_size_v": 1.0, 31 | "fab_text_thickness": 0.15, 32 | "fab_text_upright": false, 33 | "other_line_width": 0.1, 34 | "other_text_italic": false, 35 | "other_text_size_h": 1.0, 36 | "other_text_size_v": 1.0, 37 | "other_text_thickness": 0.15, 38 | "other_text_upright": false, 39 | "pads": { 40 | "drill": 0.762, 41 | "height": 1.524, 42 | "width": 1.524 43 | }, 44 | "silk_line_width": 0.1, 45 | "silk_text_italic": false, 46 | "silk_text_size_h": 1.0, 47 | "silk_text_size_v": 1.0, 48 | "silk_text_thickness": 0.1, 49 | "silk_text_upright": false, 50 | "zones": { 51 | "min_clearance": 0.5 52 | } 53 | }, 54 | "diff_pair_dimensions": [], 55 | "drc_exclusions": [], 56 | "meta": { 57 | "version": 2 58 | }, 59 | "rule_severities": { 60 | "annular_width": "error", 61 | "clearance": "error", 62 | "connection_width": "warning", 63 | "copper_edge_clearance": "error", 64 | "copper_sliver": "warning", 65 | "courtyards_overlap": "error", 66 | "diff_pair_gap_out_of_range": "error", 67 | "diff_pair_uncoupled_length_too_long": "error", 68 | "drill_out_of_range": "error", 69 | "duplicate_footprints": "warning", 70 | "extra_footprint": "warning", 71 | "footprint": "error", 72 | "footprint_symbol_mismatch": "warning", 73 | "footprint_type_mismatch": "ignore", 74 | "hole_clearance": "error", 75 | "hole_near_hole": "error", 76 | "invalid_outline": "error", 77 | "isolated_copper": "warning", 78 | "item_on_disabled_layer": "error", 79 | "items_not_allowed": "error", 80 | "length_out_of_range": "error", 81 | "lib_footprint_issues": "warning", 82 | "lib_footprint_mismatch": "warning", 83 | "malformed_courtyard": "error", 84 | "microvia_drill_out_of_range": "error", 85 | "missing_courtyard": "ignore", 86 | "missing_footprint": "warning", 87 | "net_conflict": "warning", 88 | "npth_inside_courtyard": "ignore", 89 | "padstack": "warning", 90 | "pth_inside_courtyard": "ignore", 91 | "shorting_items": "error", 92 | "silk_edge_clearance": "warning", 93 | "silk_over_copper": "warning", 94 | "silk_overlap": "warning", 95 | "skew_out_of_range": "error", 96 | "solder_mask_bridge": "error", 97 | "starved_thermal": "error", 98 | "text_height": "warning", 99 | "text_thickness": "warning", 100 | "through_hole_pad_without_hole": "error", 101 | "too_many_vias": "error", 102 | "track_dangling": "warning", 103 | "track_width": "error", 104 | "tracks_crossing": "error", 105 | "unconnected_items": "error", 106 | "unresolved_variable": "error", 107 | "via_dangling": "warning", 108 | "zones_intersect": "error" 109 | }, 110 | "rules": { 111 | "max_error": 0.005, 112 | "min_clearance": 0.0, 113 | "min_connection": 0.0, 114 | "min_copper_edge_clearance": 0.5, 115 | "min_hole_clearance": 0.25, 116 | "min_hole_to_hole": 0.25, 117 | "min_microvia_diameter": 0.2, 118 | "min_microvia_drill": 0.1, 119 | "min_resolved_spokes": 2, 120 | "min_silk_clearance": 0.0, 121 | "min_text_height": 0.8, 122 | "min_text_thickness": 0.08, 123 | "min_through_hole_diameter": 0.3, 124 | "min_track_width": 0.0, 125 | "min_via_annular_width": 0.1, 126 | "min_via_diameter": 0.5, 127 | "solder_mask_to_copper_clearance": 0.0, 128 | "use_height_for_length_calcs": true 129 | }, 130 | "teardrop_options": [ 131 | { 132 | "td_onpadsmd": true, 133 | "td_onroundshapesonly": false, 134 | "td_ontrackend": false, 135 | "td_onviapad": true 136 | } 137 | ], 138 | "teardrop_parameters": [ 139 | { 140 | "td_allow_use_two_tracks": true, 141 | "td_curve_segcount": 0, 142 | "td_height_ratio": 1.0, 143 | "td_length_ratio": 0.5, 144 | "td_maxheight": 2.0, 145 | "td_maxlen": 1.0, 146 | "td_on_pad_in_zone": false, 147 | "td_target_name": "td_round_shape", 148 | "td_width_to_size_filter_ratio": 0.9 149 | }, 150 | { 151 | "td_allow_use_two_tracks": true, 152 | "td_curve_segcount": 0, 153 | "td_height_ratio": 1.0, 154 | "td_length_ratio": 0.5, 155 | "td_maxheight": 2.0, 156 | "td_maxlen": 1.0, 157 | "td_on_pad_in_zone": false, 158 | "td_target_name": "td_rect_shape", 159 | "td_width_to_size_filter_ratio": 0.9 160 | }, 161 | { 162 | "td_allow_use_two_tracks": true, 163 | "td_curve_segcount": 0, 164 | "td_height_ratio": 1.0, 165 | "td_length_ratio": 0.5, 166 | "td_maxheight": 2.0, 167 | "td_maxlen": 1.0, 168 | "td_on_pad_in_zone": false, 169 | "td_target_name": "td_track_end", 170 | "td_width_to_size_filter_ratio": 0.9 171 | } 172 | ], 173 | "track_widths": [], 174 | "tuning_pattern_settings": { 175 | "diff_pair_defaults": { 176 | "corner_radius_percentage": 80, 177 | "corner_style": 1, 178 | "max_amplitude": 1.0, 179 | "min_amplitude": 0.2, 180 | "single_sided": false, 181 | "spacing": 1.0 182 | }, 183 | "diff_pair_skew_defaults": { 184 | "corner_radius_percentage": 80, 185 | "corner_style": 1, 186 | "max_amplitude": 1.0, 187 | "min_amplitude": 0.2, 188 | "single_sided": false, 189 | "spacing": 0.6 190 | }, 191 | "single_track_defaults": { 192 | "corner_radius_percentage": 80, 193 | "corner_style": 1, 194 | "max_amplitude": 1.0, 195 | "min_amplitude": 0.2, 196 | "single_sided": false, 197 | "spacing": 0.6 198 | } 199 | }, 200 | "via_dimensions": [], 201 | "zones_allow_external_fillets": false 202 | }, 203 | "ipc2581": { 204 | "dist": "", 205 | "distpn": "", 206 | "internal_id": "", 207 | "mfg": "", 208 | "mpn": "" 209 | }, 210 | "layer_presets": [], 211 | "viewports": [] 212 | }, 213 | "boards": [], 214 | "cvpcb": { 215 | "equivalence_files": [] 216 | }, 217 | "erc": { 218 | "erc_exclusions": [], 219 | "meta": { 220 | "version": 0 221 | }, 222 | "pin_map": [ 223 | [ 224 | 0, 225 | 0, 226 | 0, 227 | 0, 228 | 0, 229 | 0, 230 | 1, 231 | 0, 232 | 0, 233 | 0, 234 | 0, 235 | 2 236 | ], 237 | [ 238 | 0, 239 | 2, 240 | 0, 241 | 1, 242 | 0, 243 | 0, 244 | 1, 245 | 0, 246 | 2, 247 | 2, 248 | 2, 249 | 2 250 | ], 251 | [ 252 | 0, 253 | 0, 254 | 0, 255 | 0, 256 | 0, 257 | 0, 258 | 1, 259 | 0, 260 | 1, 261 | 0, 262 | 1, 263 | 2 264 | ], 265 | [ 266 | 0, 267 | 1, 268 | 0, 269 | 0, 270 | 0, 271 | 0, 272 | 1, 273 | 1, 274 | 2, 275 | 1, 276 | 1, 277 | 2 278 | ], 279 | [ 280 | 0, 281 | 0, 282 | 0, 283 | 0, 284 | 0, 285 | 0, 286 | 1, 287 | 0, 288 | 0, 289 | 0, 290 | 0, 291 | 2 292 | ], 293 | [ 294 | 0, 295 | 0, 296 | 0, 297 | 0, 298 | 0, 299 | 0, 300 | 0, 301 | 0, 302 | 0, 303 | 0, 304 | 0, 305 | 2 306 | ], 307 | [ 308 | 1, 309 | 1, 310 | 1, 311 | 1, 312 | 1, 313 | 0, 314 | 1, 315 | 1, 316 | 1, 317 | 1, 318 | 1, 319 | 2 320 | ], 321 | [ 322 | 0, 323 | 0, 324 | 0, 325 | 1, 326 | 0, 327 | 0, 328 | 1, 329 | 0, 330 | 0, 331 | 0, 332 | 0, 333 | 2 334 | ], 335 | [ 336 | 0, 337 | 2, 338 | 1, 339 | 2, 340 | 0, 341 | 0, 342 | 1, 343 | 0, 344 | 2, 345 | 2, 346 | 2, 347 | 2 348 | ], 349 | [ 350 | 0, 351 | 2, 352 | 0, 353 | 1, 354 | 0, 355 | 0, 356 | 1, 357 | 0, 358 | 2, 359 | 0, 360 | 0, 361 | 2 362 | ], 363 | [ 364 | 0, 365 | 2, 366 | 1, 367 | 1, 368 | 0, 369 | 0, 370 | 1, 371 | 0, 372 | 2, 373 | 0, 374 | 0, 375 | 2 376 | ], 377 | [ 378 | 2, 379 | 2, 380 | 2, 381 | 2, 382 | 2, 383 | 2, 384 | 2, 385 | 2, 386 | 2, 387 | 2, 388 | 2, 389 | 2 390 | ] 391 | ], 392 | "rule_severities": { 393 | "bus_definition_conflict": "error", 394 | "bus_entry_needed": "error", 395 | "bus_to_bus_conflict": "error", 396 | "bus_to_net_conflict": "error", 397 | "conflicting_netclasses": "error", 398 | "different_unit_footprint": "error", 399 | "different_unit_net": "error", 400 | "duplicate_reference": "error", 401 | "duplicate_sheet_names": "error", 402 | "endpoint_off_grid": "warning", 403 | "extra_units": "error", 404 | "global_label_dangling": "warning", 405 | "hier_label_mismatch": "error", 406 | "label_dangling": "error", 407 | "lib_symbol_issues": "warning", 408 | "missing_bidi_pin": "warning", 409 | "missing_input_pin": "warning", 410 | "missing_power_pin": "error", 411 | "missing_unit": "warning", 412 | "multiple_net_names": "warning", 413 | "net_not_bus_member": "warning", 414 | "no_connect_connected": "warning", 415 | "no_connect_dangling": "warning", 416 | "pin_not_connected": "error", 417 | "pin_not_driven": "error", 418 | "pin_to_pin": "warning", 419 | "power_pin_not_driven": "error", 420 | "similar_labels": "warning", 421 | "simulation_model_issue": "ignore", 422 | "unannotated": "error", 423 | "unit_value_mismatch": "error", 424 | "unresolved_variable": "error", 425 | "wire_dangling": "error" 426 | } 427 | }, 428 | "libraries": { 429 | "pinned_footprint_libs": [], 430 | "pinned_symbol_libs": [] 431 | }, 432 | "meta": { 433 | "filename": "info_orbs.kicad_pro", 434 | "version": 1 435 | }, 436 | "net_settings": { 437 | "classes": [ 438 | { 439 | "bus_width": 12, 440 | "clearance": 0.2, 441 | "diff_pair_gap": 0.25, 442 | "diff_pair_via_gap": 0.25, 443 | "diff_pair_width": 0.2, 444 | "line_style": 0, 445 | "microvia_diameter": 0.3, 446 | "microvia_drill": 0.1, 447 | "name": "Default", 448 | "pcb_color": "rgba(0, 0, 0, 0.000)", 449 | "schematic_color": "rgba(0, 0, 0, 0.000)", 450 | "track_width": 0.2, 451 | "via_diameter": 0.6, 452 | "via_drill": 0.3, 453 | "wire_width": 6 454 | } 455 | ], 456 | "meta": { 457 | "version": 3 458 | }, 459 | "net_colors": null, 460 | "netclass_assignments": null, 461 | "netclass_patterns": [] 462 | }, 463 | "pcbnew": { 464 | "last_paths": { 465 | "gencad": "", 466 | "idf": "", 467 | "netlist": "", 468 | "plot": "C:/Users/Btdub/OneDrive/Desktop/", 469 | "pos_files": "", 470 | "specctra_dsn": "", 471 | "step": "", 472 | "svg": "", 473 | "vrml": "" 474 | }, 475 | "page_layout_descr_file": "" 476 | }, 477 | "schematic": { 478 | "annotate_start_num": 0, 479 | "bom_fmt_presets": [], 480 | "bom_fmt_settings": { 481 | "field_delimiter": ",", 482 | "keep_line_breaks": false, 483 | "keep_tabs": false, 484 | "name": "CSV", 485 | "ref_delimiter": ",", 486 | "ref_range_delimiter": "", 487 | "string_delimiter": "\"" 488 | }, 489 | "bom_presets": [], 490 | "bom_settings": { 491 | "exclude_dnp": false, 492 | "fields_ordered": [ 493 | { 494 | "group_by": false, 495 | "label": "Reference", 496 | "name": "Reference", 497 | "show": true 498 | }, 499 | { 500 | "group_by": true, 501 | "label": "Value", 502 | "name": "Value", 503 | "show": true 504 | }, 505 | { 506 | "group_by": false, 507 | "label": "Datasheet", 508 | "name": "Datasheet", 509 | "show": true 510 | }, 511 | { 512 | "group_by": false, 513 | "label": "Footprint", 514 | "name": "Footprint", 515 | "show": true 516 | }, 517 | { 518 | "group_by": false, 519 | "label": "Qty", 520 | "name": "${QUANTITY}", 521 | "show": true 522 | }, 523 | { 524 | "group_by": true, 525 | "label": "DNP", 526 | "name": "${DNP}", 527 | "show": true 528 | }, 529 | { 530 | "group_by": false, 531 | "label": "#", 532 | "name": "${ITEM_NUMBER}", 533 | "show": false 534 | }, 535 | { 536 | "group_by": false, 537 | "label": "Availability", 538 | "name": "Availability", 539 | "show": false 540 | }, 541 | { 542 | "group_by": false, 543 | "label": "Check_prices", 544 | "name": "Check_prices", 545 | "show": false 546 | }, 547 | { 548 | "group_by": false, 549 | "label": "Description_1", 550 | "name": "Description_1", 551 | "show": false 552 | }, 553 | { 554 | "group_by": false, 555 | "label": "MANUFACTURER", 556 | "name": "MANUFACTURER", 557 | "show": false 558 | }, 559 | { 560 | "group_by": false, 561 | "label": "MAXIMUM_PACKAGE_HEIGHT", 562 | "name": "MAXIMUM_PACKAGE_HEIGHT", 563 | "show": false 564 | }, 565 | { 566 | "group_by": false, 567 | "label": "MF", 568 | "name": "MF", 569 | "show": false 570 | }, 571 | { 572 | "group_by": false, 573 | "label": "MP", 574 | "name": "MP", 575 | "show": false 576 | }, 577 | { 578 | "group_by": false, 579 | "label": "PARTREV", 580 | "name": "PARTREV", 581 | "show": false 582 | }, 583 | { 584 | "group_by": false, 585 | "label": "Package", 586 | "name": "Package", 587 | "show": false 588 | }, 589 | { 590 | "group_by": false, 591 | "label": "Price", 592 | "name": "Price", 593 | "show": false 594 | }, 595 | { 596 | "group_by": false, 597 | "label": "SNAPEDA_PN", 598 | "name": "SNAPEDA_PN", 599 | "show": false 600 | }, 601 | { 602 | "group_by": false, 603 | "label": "STANDARD", 604 | "name": "STANDARD", 605 | "show": false 606 | }, 607 | { 608 | "group_by": false, 609 | "label": "SnapEDA_Link", 610 | "name": "SnapEDA_Link", 611 | "show": false 612 | }, 613 | { 614 | "group_by": false, 615 | "label": "Description", 616 | "name": "Description", 617 | "show": false 618 | } 619 | ], 620 | "filter_string": "", 621 | "group_symbols": true, 622 | "name": "", 623 | "sort_asc": true, 624 | "sort_field": "Reference" 625 | }, 626 | "connection_grid_size": 50.0, 627 | "drawing": { 628 | "dashed_lines_dash_length_ratio": 12.0, 629 | "dashed_lines_gap_length_ratio": 3.0, 630 | "default_line_thickness": 6.0, 631 | "default_text_size": 50.0, 632 | "field_names": [], 633 | "intersheets_ref_own_page": false, 634 | "intersheets_ref_prefix": "", 635 | "intersheets_ref_short": false, 636 | "intersheets_ref_show": false, 637 | "intersheets_ref_suffix": "", 638 | "junction_size_choice": 3, 639 | "label_size_ratio": 0.375, 640 | "operating_point_overlay_i_precision": 3, 641 | "operating_point_overlay_i_range": "~A", 642 | "operating_point_overlay_v_precision": 3, 643 | "operating_point_overlay_v_range": "~V", 644 | "overbar_offset_ratio": 1.23, 645 | "pin_symbol_size": 25.0, 646 | "text_offset_ratio": 0.15 647 | }, 648 | "legacy_lib_dir": "", 649 | "legacy_lib_list": [], 650 | "meta": { 651 | "version": 1 652 | }, 653 | "net_format_name": "", 654 | "page_layout_descr_file": "", 655 | "plot_directory": "", 656 | "spice_current_sheet_as_root": false, 657 | "spice_external_command": "spice \"%I\"", 658 | "spice_model_current_sheet_as_root": true, 659 | "spice_save_all_currents": false, 660 | "spice_save_all_dissipations": false, 661 | "spice_save_all_voltages": false, 662 | "subpart_first_id": 65, 663 | "subpart_id_separator": 0 664 | }, 665 | "sheets": [ 666 | [ 667 | "529f227b-dda3-4d44-98c7-ef3cb8bfc72d", 668 | "Root" 669 | ] 670 | ], 671 | "text_variables": {} 672 | } 673 | --------------------------------------------------------------------------------