├── .gitmodules ├── include └── .gitkeep ├── pics ├── iGate.png ├── display-right.jpg └── display-wrong.jpg ├── .github ├── FUNDING.yml ├── ISSUE_TEMPLATE │ ├── feature_request.md │ ├── bug_report.md │ └── bug_report_beta.md └── workflows │ ├── tweet_release.yml │ ├── release.yml │ └── build_check.yml ├── .gitignore ├── .devcontainer ├── Dockerfile └── devcontainer.json ├── .vscode ├── extensions.json └── settings.json ├── lib ├── Display │ ├── Fonts │ │ ├── FontDesc.h │ │ ├── Terminal_8.h │ │ └── HoloLens_12.h │ ├── SSD1306.cpp │ ├── Bitmap.h │ ├── Display.h │ ├── SSD1306.h │ ├── FontConfig.cpp │ ├── FontConfig.h │ ├── Display.cpp │ ├── OLEDDisplay.h │ ├── OLEDDisplay.cpp │ └── Bitmap.cpp ├── System │ ├── Timer.h │ ├── TaskQueue.h │ ├── Timer.cpp │ ├── System.cpp │ ├── System.h │ ├── TaskManager.h │ └── TaskManager.cpp ├── PowerManagement │ ├── power_management.h │ └── power_management.cpp ├── ConfigurationManagement │ ├── configuration.h │ └── configuration.cpp ├── APRS-IS │ ├── APRS-IS.h │ └── APRS-IS.cpp ├── TimeLib │ ├── TimeLibString.cpp │ ├── TimeLib.h │ └── TimeLib.cpp ├── BoardFinder │ ├── BoardFinder.h │ └── BoardFinder.cpp └── NTPClient │ ├── NTPClient.h │ └── NTPClient.cpp ├── src ├── TaskDisplay.h ├── TaskNTP.h ├── TaskEth.h ├── TaskFTP.h ├── TaskOTA.h ├── TaskWifi.h ├── TaskAprsIs.h ├── TaskMQTT.h ├── Task.h ├── TaskRouter.h ├── TaskNTP.cpp ├── TaskBeacon.h ├── TaskRadiolib.h ├── TaskDisplay.cpp ├── TaskFTP.cpp ├── TaskOTA.cpp ├── TaskWifi.cpp ├── TaskAprsIs.cpp ├── TaskMQTT.cpp ├── TaskRouter.cpp ├── TaskBeacon.cpp ├── project_configuration.h ├── TaskEth.cpp ├── LoRa_APRS_iGate.cpp ├── project_configuration.cpp └── TaskRadiolib.cpp ├── scripts ├── create_version_tag.py └── check_version.py ├── LICENSE ├── platformio.ini ├── data └── is-cfg.json ├── .clang-format └── README.md /.gitmodules: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /include/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /pics/iGate.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SensorsIot/LoRa_APRS_iGate/master/pics/iGate.png -------------------------------------------------------------------------------- /pics/display-right.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SensorsIot/LoRa_APRS_iGate/master/pics/display-right.jpg -------------------------------------------------------------------------------- /pics/display-wrong.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SensorsIot/LoRa_APRS_iGate/master/pics/display-wrong.jpg -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: ['peterus'] 4 | custom: ['paypal.me/peterus07'] 5 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .pio 2 | .vscode/.browse.c_cpp.db* 3 | .vscode/c_cpp_properties.json 4 | .vscode/launch.json 5 | .vscode/ipch 6 | report.xml 7 | output 8 | -------------------------------------------------------------------------------- /.devcontainer/Dockerfile: -------------------------------------------------------------------------------- 1 | ARG VARIANT="3.10-bullseye" 2 | FROM mcr.microsoft.com/vscode/devcontainers/python:0-${VARIANT} 3 | 4 | RUN apt-get update && apt-get install -y clang-format-11 5 | 6 | RUN pip3 --disable-pip-version-check --no-cache-dir install platformio 7 | -------------------------------------------------------------------------------- /.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 | "xaver.clang-format" 7 | ] 8 | } 9 | -------------------------------------------------------------------------------- /lib/Display/Fonts/FontDesc.h: -------------------------------------------------------------------------------- 1 | #ifndef FONT_DESC_H 2 | #define FONT_DESC_H 3 | 4 | #include 5 | 6 | struct fontDesc_t 7 | { 8 | uint16_t totalSize; 9 | uint8_t widthInPixel; 10 | uint8_t heightInPixel; 11 | uint8_t bitsPerPixel; 12 | uint8_t firstChar; 13 | uint8_t lastChar; 14 | 15 | unsigned char const * const pData; 16 | }; 17 | 18 | #endif 19 | -------------------------------------------------------------------------------- /src/TaskDisplay.h: -------------------------------------------------------------------------------- 1 | #ifndef TASK_DISPLAY_H_ 2 | #define TASK_DISPLAY_H_ 3 | 4 | #include 5 | #include 6 | 7 | class DisplayTask : public Task { 8 | public: 9 | DisplayTask(); 10 | virtual ~DisplayTask(); 11 | 12 | virtual bool setup(System &system) override; 13 | virtual bool loop(System &system) override; 14 | }; 15 | 16 | #endif 17 | -------------------------------------------------------------------------------- /lib/System/Timer.h: -------------------------------------------------------------------------------- 1 | #ifndef TIMER_H_ 2 | #define TIMER_H_ 3 | 4 | class Timer { 5 | public: 6 | Timer(); 7 | 8 | void setTimeout(const uint32_t timeout_ms); 9 | uint32_t getTriggerTimeInSec() const; 10 | 11 | bool isActive() const; 12 | 13 | void reset(); 14 | 15 | bool check(); 16 | void start(); 17 | 18 | private: 19 | uint32_t _timeout_ms; 20 | uint32_t _nextTimeout; 21 | }; 22 | 23 | #endif 24 | -------------------------------------------------------------------------------- /src/TaskNTP.h: -------------------------------------------------------------------------------- 1 | #ifndef TASK_NTP_H_ 2 | #define TASK_NTP_H_ 3 | 4 | #include 5 | #include 6 | 7 | class NTPTask : public Task { 8 | public: 9 | NTPTask(); 10 | virtual ~NTPTask(); 11 | 12 | virtual bool setup(System &system) override; 13 | virtual bool loop(System &system) override; 14 | 15 | private: 16 | NTPClient _ntpClient; 17 | bool _beginCalled; 18 | }; 19 | 20 | #endif 21 | -------------------------------------------------------------------------------- /src/TaskEth.h: -------------------------------------------------------------------------------- 1 | #ifndef TASK_ETH_H_ 2 | #define TASK_ETH_H_ 3 | 4 | #include 5 | 6 | void setWiFiLogger(logging::Logger *logger); 7 | void WiFiEvent(WiFiEvent_t event); 8 | 9 | class EthTask : public Task { 10 | public: 11 | EthTask(); 12 | virtual ~EthTask(); 13 | 14 | virtual bool setup(System &system) override; 15 | virtual bool loop(System &system) override; 16 | 17 | private: 18 | }; 19 | 20 | #endif 21 | -------------------------------------------------------------------------------- /src/TaskFTP.h: -------------------------------------------------------------------------------- 1 | #ifndef TASK_FTP_H_ 2 | #define TASK_FTP_H_ 3 | 4 | #include 5 | #include 6 | 7 | class FTPTask : public Task { 8 | public: 9 | FTPTask(); 10 | virtual ~FTPTask(); 11 | 12 | virtual bool setup(System &system) override; 13 | virtual bool loop(System &system) override; 14 | 15 | private: 16 | FTPServer _ftpServer; 17 | bool _beginCalled; 18 | }; 19 | 20 | #endif 21 | -------------------------------------------------------------------------------- /src/TaskOTA.h: -------------------------------------------------------------------------------- 1 | #ifndef TASK_OTA_H_ 2 | #define TASK_OTA_H_ 3 | 4 | #include 5 | #include 6 | 7 | class OTATask : public Task { 8 | public: 9 | OTATask(); 10 | virtual ~OTATask(); 11 | 12 | virtual bool setup(System &system) override; 13 | virtual bool loop(System &system) override; 14 | 15 | private: 16 | ArduinoOTAClass _ota; 17 | bool _beginCalled; 18 | }; 19 | 20 | #endif 21 | -------------------------------------------------------------------------------- /src/TaskWifi.h: -------------------------------------------------------------------------------- 1 | #ifndef TASK_WIFI_H_ 2 | #define TASK_WIFI_H_ 3 | 4 | #include 5 | #include 6 | 7 | class WifiTask : public Task { 8 | public: 9 | WifiTask(); 10 | virtual ~WifiTask(); 11 | 12 | virtual bool setup(System &system) override; 13 | virtual bool loop(System &system) override; 14 | 15 | private: 16 | WiFiMulti _wiFiMulti; 17 | uint8_t _oldWifiStatus; 18 | }; 19 | 20 | #endif 21 | -------------------------------------------------------------------------------- /lib/PowerManagement/power_management.h: -------------------------------------------------------------------------------- 1 | #ifndef POWER_MANAGEMENT_H_ 2 | #define POWER_MANAGEMENT_H_ 3 | 4 | #include 5 | #include 6 | 7 | class PowerManagement { 8 | public: 9 | PowerManagement(); 10 | bool begin(TwoWire &port); 11 | 12 | void activateLoRa(); 13 | void deactivateLoRa(); 14 | 15 | void activateGPS(); 16 | void deactivateGPS(); 17 | 18 | void activateOLED(); 19 | void decativateOLED(); 20 | 21 | private: 22 | AXP20X_Class axp; 23 | }; 24 | 25 | #endif 26 | -------------------------------------------------------------------------------- /lib/System/TaskQueue.h: -------------------------------------------------------------------------------- 1 | #ifndef TASK_QUEUE_H_ 2 | #define TASK_QUEUE_H_ 3 | 4 | #include 5 | 6 | template class TaskQueue { 7 | public: 8 | TaskQueue() { 9 | } 10 | 11 | void addElement(T elem) { 12 | _elements.push_back(elem); 13 | } 14 | 15 | T getElement() { 16 | T elem = _elements.front(); 17 | _elements.pop_front(); 18 | return elem; 19 | } 20 | 21 | bool empty() const { 22 | return _elements.empty(); 23 | } 24 | 25 | private: 26 | std::list _elements; 27 | }; 28 | 29 | #endif 30 | -------------------------------------------------------------------------------- /src/TaskAprsIs.h: -------------------------------------------------------------------------------- 1 | #ifndef TASK_APRS_IS_H_ 2 | #define TASK_APRS_IS_H_ 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | class AprsIsTask : public Task { 10 | public: 11 | explicit AprsIsTask(TaskQueue> &toAprsIs); 12 | virtual ~AprsIsTask(); 13 | 14 | virtual bool setup(System &system) override; 15 | virtual bool loop(System &system) override; 16 | 17 | private: 18 | APRS_IS _aprs_is; 19 | 20 | TaskQueue> &_toAprsIs; 21 | 22 | bool connect(System &system); 23 | }; 24 | 25 | #endif 26 | -------------------------------------------------------------------------------- /src/TaskMQTT.h: -------------------------------------------------------------------------------- 1 | #ifndef TASK_MQTT_H_ 2 | #define TASK_MQTT_H_ 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | class MQTTTask : public Task { 10 | public: 11 | MQTTTask(TaskQueue> &toMQTT); 12 | virtual ~MQTTTask(); 13 | 14 | virtual bool setup(System &system) override; 15 | virtual bool loop(System &system) override; 16 | 17 | private: 18 | TaskQueue> &_toMQTT; 19 | 20 | WiFiClient _client; 21 | PubSubClient _MQTT; 22 | 23 | bool connect(System &system); 24 | }; 25 | 26 | #endif 27 | -------------------------------------------------------------------------------- /scripts/create_version_tag.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | from datetime import date 4 | 5 | today = date.today() 6 | 7 | current_year = int(str(today.isocalendar()[0])[2:]) 8 | current_week = int(today.isocalendar()[1]) 9 | 10 | version = None 11 | with open("src/LoRa_APRS_iGate.cpp") as f: 12 | for line in f: 13 | if line.startswith("#define VERSION"): 14 | version = line.strip().split(" ")[-1].replace('"', "") 15 | 16 | version_split = version.split(".") 17 | version_year = int(version_split[0]) 18 | version_week = int(version_split[1]) 19 | version_vers = int(version_split[2]) 20 | 21 | print(f"v{version_year}.{version_week}.{version_vers}") 22 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: enhancement 6 | assignees: peterus 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 12 | 13 | **Describe the solution you'd like** 14 | A clear and concise description of what you want to happen. 15 | 16 | **Describe alternatives you've considered** 17 | A clear and concise description of any alternative solutions or features you've considered. 18 | 19 | **Additional context** 20 | Add any other context or screenshots about the feature request here. 21 | -------------------------------------------------------------------------------- /lib/System/Timer.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include "Timer.h" 4 | 5 | Timer::Timer() : _timeout_ms(0), _nextTimeout(0) { 6 | } 7 | 8 | void Timer::setTimeout(const uint32_t timeout_ms) { 9 | _timeout_ms = timeout_ms; 10 | } 11 | 12 | uint32_t Timer::getTriggerTimeInSec() const { 13 | return (_nextTimeout - millis()) / 1000; 14 | } 15 | 16 | // cppcheck-suppress unusedFunction 17 | bool Timer::isActive() const { 18 | return _nextTimeout != 0; 19 | } 20 | 21 | // cppcheck-suppress unusedFunction 22 | void Timer::reset() { 23 | _nextTimeout = 0; 24 | } 25 | 26 | bool Timer::check() { 27 | return millis() > _nextTimeout; 28 | } 29 | 30 | void Timer::start() { 31 | _nextTimeout = millis() + _timeout_ms; 32 | } 33 | -------------------------------------------------------------------------------- /src/Task.h: -------------------------------------------------------------------------------- 1 | #ifndef TASK_H_ 2 | #define TASK_H_ 3 | 4 | enum TaskNames 5 | { 6 | TaskAprsIs = 1, 7 | TaskEth, 8 | TaskFtp, 9 | TaskModem, 10 | TaskRadiolib, 11 | TaskNtp, 12 | TaskOta, 13 | TaskWifi, 14 | TaskRouter, 15 | TaskMQTT, 16 | TaskBeacon, 17 | TaskSize 18 | }; 19 | 20 | #define TASK_APRS_IS "AprsIsTask" 21 | #define TASK_ETH "EthTask" 22 | #define TASK_FTP "FTPTask" 23 | #define TASK_MODEM "ModemTask" 24 | #define TASK_RADIOLIB "RadiolibTask" 25 | #define TASK_NTP "NTPTask" 26 | #define TASK_OTA "OTATask" 27 | #define TASK_WIFI "WifiTask" 28 | #define TASK_ROUTER "RouterTask" 29 | #define TASK_MQTT "MQTTTask" 30 | #define TASK_BEACON "BeaconTask" 31 | 32 | #endif 33 | -------------------------------------------------------------------------------- /.github/workflows/tweet_release.yml: -------------------------------------------------------------------------------- 1 | name: tweet-release 2 | 3 | on: 4 | release: 5 | types: [published] 6 | 7 | jobs: 8 | tweet: 9 | runs-on: ubuntu-latest 10 | steps: 11 | - uses: Eomm/why-don-t-you-tweet@v1 12 | if: ${{ !github.event.repository.private }} 13 | with: 14 | tweet-message: "New ${{ github.event.repository.name }} release ${{ github.event.release.tag_name }}! ${{ github.event.release.html_url }} #LoRa #APRS #HAM #hamradio #iGate" 15 | env: 16 | TWITTER_CONSUMER_API_KEY: ${{ secrets.TWITTER_CONSUMER_API_KEY }} 17 | TWITTER_CONSUMER_API_SECRET: ${{ secrets.TWITTER_CONSUMER_API_SECRET }} 18 | TWITTER_ACCESS_TOKEN: ${{ secrets.TWITTER_ACCESS_TOKEN }} 19 | TWITTER_ACCESS_TOKEN_SECRET: ${{ secrets.TWITTER_ACCESS_TOKEN_SECRET }} 20 | -------------------------------------------------------------------------------- /src/TaskRouter.h: -------------------------------------------------------------------------------- 1 | #ifndef TASK_ROUTER_H_ 2 | #define TASK_ROUTER_H_ 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | class RouterTask : public Task { 9 | public: 10 | RouterTask(TaskQueue> &fromModem, TaskQueue> &toModem, TaskQueue> &toAprsIs, TaskQueue> &toMQTT); 11 | virtual ~RouterTask(); 12 | 13 | virtual bool setup(System &system) override; 14 | virtual bool loop(System &system) override; 15 | 16 | private: 17 | TaskQueue> &_fromModem; 18 | TaskQueue> &_toModem; 19 | TaskQueue> &_toAprsIs; 20 | TaskQueue> &_toMQTT; 21 | }; 22 | 23 | #endif 24 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: bug 6 | assignees: peterus 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of what the bug is. 12 | 13 | **To Reproduce** 14 | Steps to reproduce the behavior: 15 | 1. Go to '...' 16 | 2. Click on '....' 17 | 3. Scroll down to '....' 18 | 4. See error 19 | 20 | **Expected behavior** 21 | A clear and concise description of what you expected to happen. 22 | 23 | **Screenshots** 24 | If applicable, add screenshots to help explain your problem. 25 | 26 | **Desktop (please complete the following information):** 27 | - OS: 28 | - PlatformIO Version: 29 | - Firmware Version: 30 | 31 | **Board name:** 32 | - 33 | 34 | **Additional context** 35 | Add any other context about the problem here. 36 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report_beta.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report for Beta-Version 3 | about: Create a report to help us improve the Beta-Version 4 | title: '' 5 | labels: bug, beta 6 | assignees: peterus 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of what the bug is. 12 | 13 | **To Reproduce** 14 | Steps to reproduce the behavior: 15 | 1. Go to '...' 16 | 2. Click on '....' 17 | 3. Scroll down to '....' 18 | 4. See error 19 | 20 | **Expected behavior** 21 | A clear and concise description of what you expected to happen. 22 | 23 | **Screenshots** 24 | If applicable, add screenshots to help explain your problem. 25 | 26 | **Desktop (please complete the following information):** 27 | - OS: 28 | - PlatformIO Version: 29 | - Firmware Version: 30 | 31 | **Board name:** 32 | - 33 | 34 | **Additional context** 35 | Add any other context about the problem here. 36 | -------------------------------------------------------------------------------- /lib/ConfigurationManagement/configuration.h: -------------------------------------------------------------------------------- 1 | #ifndef CONFIGURATION_H_ 2 | #define CONFIGURATION_H_ 3 | 4 | #include 5 | #include 6 | 7 | #include 8 | #ifndef CPPCHECK 9 | #include 10 | #endif 11 | 12 | #include 13 | 14 | class Configuration; 15 | 16 | class ConfigurationManagement { 17 | public: 18 | explicit ConfigurationManagement(logging::Logger &logger, String FilePath); 19 | virtual ~ConfigurationManagement(); 20 | 21 | void readConfiguration(logging::Logger &logger, Configuration &conf); 22 | void writeConfiguration(logging::Logger &logger, Configuration &conf); 23 | 24 | private: 25 | virtual void readProjectConfiguration(DynamicJsonDocument &data, Configuration &conf) = 0; 26 | virtual void writeProjectConfiguration(Configuration &conf, DynamicJsonDocument &data) = 0; 27 | 28 | const String mFilePath; 29 | }; 30 | 31 | #endif 32 | -------------------------------------------------------------------------------- /lib/Display/SSD1306.cpp: -------------------------------------------------------------------------------- 1 | #include "SSD1306.h" 2 | 3 | SSD1306::SSD1306(TwoWire *wire, uint8_t address, OLEDDISPLAY_GEOMETRY g) : OLEDDisplay(g), _wire(wire), _address(address) { 4 | sendInitCommands(); 5 | } 6 | 7 | SSD1306::~SSD1306() { 8 | } 9 | 10 | void SSD1306::internDisplay(Bitmap *bitmap) { 11 | sendCommand(PAGEADDR); 12 | sendCommand(0x0); 13 | sendCommand(0xFF); 14 | 15 | sendCommand(COLUMNADDR); 16 | sendCommand(0x0); 17 | sendCommand(getWidth() - 1); 18 | 19 | for (int i = 0; i < getWidth() * getHeight() / 8;) { 20 | Wire.beginTransmission(_address); 21 | Wire.write(0x40); 22 | for (uint8_t x = 0; x < 16; x++) { 23 | Wire.write(bitmap->_buffer[i]); 24 | i++; 25 | } 26 | Wire.endTransmission(); 27 | } 28 | } 29 | 30 | void SSD1306::sendCommand(uint8_t command) { 31 | _wire->beginTransmission(_address); 32 | _wire->write(0x80); 33 | _wire->write(command); 34 | _wire->endTransmission(); 35 | } 36 | -------------------------------------------------------------------------------- /src/TaskNTP.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | 5 | #include "Task.h" 6 | #include "TaskNTP.h" 7 | #include "project_configuration.h" 8 | 9 | NTPTask::NTPTask() : Task(TASK_NTP, TaskNtp), _beginCalled(false) { 10 | } 11 | 12 | NTPTask::~NTPTask() { 13 | } 14 | 15 | bool NTPTask::setup(System &system) { 16 | _ntpClient.setPoolServerName(system.getUserConfig()->ntpServer.c_str()); 17 | return true; 18 | } 19 | 20 | bool NTPTask::loop(System &system) { 21 | if (!system.isWifiOrEthConnected()) { 22 | return false; 23 | } 24 | if (!_beginCalled) { 25 | _ntpClient.begin(); 26 | _beginCalled = true; 27 | } 28 | if (_ntpClient.update()) { 29 | setTime(_ntpClient.getEpochTime()); 30 | system.getLogger().log(logging::LoggerLevel::LOGGER_LEVEL_INFO, getName(), "Current time: %s", _ntpClient.getFormattedTime().c_str()); 31 | } 32 | _stateInfo = _ntpClient.getFormattedTime(); 33 | _state = Okay; 34 | return true; 35 | } 36 | -------------------------------------------------------------------------------- /src/TaskBeacon.h: -------------------------------------------------------------------------------- 1 | #ifndef TASK_BEACON_H_ 2 | #define TASK_BEACON_H_ 3 | 4 | #include 5 | #include 6 | 7 | #include 8 | #include 9 | #include 10 | 11 | class BeaconTask : public Task { 12 | public: 13 | BeaconTask(TaskQueue> &toModem, TaskQueue> &toAprsIs); 14 | virtual ~BeaconTask(); 15 | 16 | virtual bool setup(System &system) override; 17 | virtual bool loop(System &system) override; 18 | bool sendBeacon(System &system); 19 | 20 | private: 21 | TaskQueue> &_toModem; 22 | TaskQueue> &_toAprsIs; 23 | 24 | std::shared_ptr _beaconMsg; 25 | Timer _beacon_timer; 26 | 27 | HardwareSerial _ss; 28 | TinyGPSPlus _gps; 29 | bool _useGps; 30 | 31 | static uint _instances; 32 | static OneButton _userButton; 33 | static bool _send_update; 34 | static void pushButton(); 35 | }; 36 | 37 | #endif 38 | -------------------------------------------------------------------------------- /lib/PowerManagement/power_management.cpp: -------------------------------------------------------------------------------- 1 | 2 | #include "power_management.h" 3 | 4 | PowerManagement::PowerManagement() { 5 | } 6 | 7 | bool PowerManagement::begin(TwoWire &port) { 8 | bool result = axp.begin(port, AXP192_SLAVE_ADDRESS); 9 | if (!result) { 10 | axp.setDCDC1Voltage(3300); 11 | } 12 | return result; 13 | } 14 | 15 | void PowerManagement::activateLoRa() { 16 | axp.setPowerOutPut(AXP192_LDO2, AXP202_ON); 17 | } 18 | 19 | // cppcheck-suppress unusedFunction 20 | void PowerManagement::deactivateLoRa() { 21 | axp.setPowerOutPut(AXP192_LDO2, AXP202_OFF); 22 | } 23 | 24 | // cppcheck-suppress unusedFunction 25 | void PowerManagement::activateGPS() { 26 | axp.setPowerOutPut(AXP192_LDO3, AXP202_ON); 27 | } 28 | 29 | void PowerManagement::deactivateGPS() { 30 | axp.setPowerOutPut(AXP192_LDO3, AXP202_OFF); 31 | } 32 | 33 | void PowerManagement::activateOLED() { 34 | axp.setPowerOutPut(AXP192_DCDC1, AXP202_ON); 35 | } 36 | 37 | // cppcheck-suppress unusedFunction 38 | void PowerManagement::decativateOLED() { 39 | axp.setPowerOutPut(AXP192_DCDC1, AXP202_OFF); 40 | } 41 | -------------------------------------------------------------------------------- /lib/APRS-IS/APRS-IS.h: -------------------------------------------------------------------------------- 1 | 2 | #ifndef APRS_IS_Lib_h_ 3 | #define APRS_IS_Lib_h_ 4 | 5 | #include 6 | #include 7 | 8 | class APRS_IS { 9 | public: 10 | void setup(const String &user, const String &passcode, const String &tool_name, const String &version); 11 | 12 | enum ConnectionStatus 13 | { 14 | SUCCESS, 15 | ERROR_CONNECTION, 16 | ERROR_PASSCODE, 17 | }; 18 | 19 | ConnectionStatus connect(const String &server, const int port); 20 | ConnectionStatus connect(const String &server, const int port, const String &filter); 21 | bool connected(); 22 | 23 | bool sendMessage(const String &message); 24 | bool sendMessage(const std::shared_ptr message); 25 | 26 | int available(); 27 | 28 | String getMessage(); 29 | std::shared_ptr getAPRSMessage(); 30 | 31 | private: 32 | String _user; 33 | String _passcode; 34 | String _tool_name; 35 | String _version; 36 | WiFiClient _client; 37 | 38 | ConnectionStatus _connect(const String &server, const int port, const String &login_line); 39 | }; 40 | 41 | #endif 42 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Peter Buchegger 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "files.insertFinalNewline": true, 3 | "editor.formatOnSave": true, 4 | "files.associations": { 5 | "*.h": "cpp", 6 | "array": "cpp", 7 | "*.tcc": "cpp", 8 | "cctype": "cpp", 9 | "clocale": "cpp", 10 | "cmath": "cpp", 11 | "cstdarg": "cpp", 12 | "cstddef": "cpp", 13 | "cstdint": "cpp", 14 | "cstdio": "cpp", 15 | "cstdlib": "cpp", 16 | "cstring": "cpp", 17 | "ctime": "cpp", 18 | "cwchar": "cpp", 19 | "cwctype": "cpp", 20 | "deque": "cpp", 21 | "list": "cpp", 22 | "unordered_map": "cpp", 23 | "unordered_set": "cpp", 24 | "vector": "cpp", 25 | "exception": "cpp", 26 | "algorithm": "cpp", 27 | "functional": "cpp", 28 | "system_error": "cpp", 29 | "tuple": "cpp", 30 | "type_traits": "cpp", 31 | "fstream": "cpp", 32 | "initializer_list": "cpp", 33 | "iomanip": "cpp", 34 | "iosfwd": "cpp", 35 | "istream": "cpp", 36 | "limits": "cpp", 37 | "memory": "cpp", 38 | "new": "cpp", 39 | "ostream": "cpp", 40 | "numeric": "cpp", 41 | "sstream": "cpp", 42 | "stdexcept": "cpp", 43 | "streambuf": "cpp", 44 | "cinttypes": "cpp", 45 | "utility": "cpp", 46 | "typeinfo": "cpp" 47 | } 48 | } -------------------------------------------------------------------------------- /lib/System/System.cpp: -------------------------------------------------------------------------------- 1 | 2 | #include "System.h" 3 | 4 | System::System() : _boardConfig(0), _userConfig(0), _isEthConnected(false), _isWifiConnected(false) { 5 | } 6 | 7 | System::~System() { 8 | } 9 | 10 | void System::setBoardConfig(BoardConfig const *const boardConfig) { 11 | _boardConfig = boardConfig; 12 | } 13 | 14 | void System::setUserConfig(Configuration const *const userConfig) { 15 | _userConfig = userConfig; 16 | } 17 | 18 | BoardConfig const *const System::getBoardConfig() const { 19 | return _boardConfig; 20 | } 21 | 22 | Configuration const *const System::getUserConfig() const { 23 | return _userConfig; 24 | } 25 | 26 | TaskManager &System::getTaskManager() { 27 | return _taskManager; 28 | } 29 | 30 | Display &System::getDisplay() { 31 | return _display; 32 | } 33 | 34 | bool System::isWifiOrEthConnected() const { 35 | return _isEthConnected || _isWifiConnected; 36 | } 37 | 38 | void System::connectedViaEth(bool status) { 39 | _isEthConnected = status; 40 | } 41 | 42 | void System::connectedViaWifi(bool status) { 43 | _isWifiConnected = status; 44 | } 45 | 46 | logging::Logger &System::getLogger() { 47 | return _logger; 48 | } 49 | -------------------------------------------------------------------------------- /src/TaskRadiolib.h: -------------------------------------------------------------------------------- 1 | #ifndef TASK_LORA_H_ 2 | #define TASK_LORA_H_ 3 | 4 | #include "project_configuration.h" 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | class RadiolibTask : public Task { 11 | public: 12 | explicit RadiolibTask(TaskQueue> &fromModem, TaskQueue> &_toModem); 13 | virtual ~RadiolibTask(); 14 | 15 | virtual bool setup(System &system) override; 16 | virtual bool loop(System &system) override; 17 | 18 | private: 19 | Module *module; 20 | SX1278 *radio; 21 | 22 | Configuration::LoRa config; 23 | 24 | bool rxEnable, txEnable; 25 | 26 | TaskQueue> &_fromModem; 27 | TaskQueue> &_toModem; 28 | 29 | static volatile bool enableInterrupt; // Need to catch interrupt or not. 30 | static volatile bool operationDone; // Caught IRQ or not. 31 | 32 | static void setFlag(void); 33 | 34 | int16_t startRX(uint8_t mode); 35 | int16_t startTX(String &str); 36 | 37 | uint32_t preambleDurationMilliSec; 38 | Timer txWaitTimer; 39 | }; 40 | 41 | #endif 42 | -------------------------------------------------------------------------------- /platformio.ini: -------------------------------------------------------------------------------- 1 | [platformio] 2 | default_envs = lora_board 3 | 4 | [env] 5 | platform = espressif32 @ 3.1.1 6 | framework = arduino 7 | lib_ldf_mode = deep+ 8 | monitor_speed = 115200 9 | monitor_flags = --raw 10 | lib_deps = 11 | bblanchon/ArduinoJson @ 6.17.0 12 | lewisxhe/AXP202X_Library @ 1.1.2 13 | peterus/APRS-Decoder-Lib @ 0.0.6 14 | peterus/esp-logger @ 1.0.0 15 | peterus/ESP-FTP-Server-Lib @ 0.9.5 16 | knolleary/PubSubClient@^2.8 17 | mikalhart/TinyGPSPlus @ 1.0.2 18 | shaggydog/OneButton @ 1.5.0 19 | jgromes/RadioLib @ 5.1.2 20 | check_tool = cppcheck 21 | check_flags = 22 | cppcheck: --suppress=*:*.pio\* --inline-suppr -DCPPCHECK --force lib -ilib/TimeLib -ilib/LoRa -ilib/NTPClient 23 | check_skip_packages = yes 24 | #monitor_flags = --raw 25 | # activate for OTA Update, use the CALLSIGN from is-cfg.json as upload_port: 26 | #upload_protocol = espota 27 | #upload_port = .local 28 | 29 | [env:lora_board] 30 | board = esp32doit-devkit-v1 31 | build_flags = -Werror -Wall 32 | 33 | [env:lora_board_debug] 34 | board = esp32doit-devkit-v1 35 | build_flags = -Werror -Wall -DCORE_DEBUG_LEVEL=5 36 | build_type = debug 37 | monitor_filters = esp32_exception_decoder 38 | -------------------------------------------------------------------------------- /src/TaskDisplay.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include "TaskDisplay.h" 4 | #include "project_configuration.h" 5 | 6 | DisplayTask::DisplayTask() : Task("DisplayTask", 0) { 7 | } 8 | 9 | DisplayTask::~DisplayTask() { 10 | } 11 | 12 | bool DisplayTask::setup(System &system) { 13 | system.getDisplay().setup(system.getBoardConfig()); 14 | if (system.getUserConfig()->display.turn180) { 15 | system.getDisplay().turn180(); 16 | } 17 | std::shared_ptr statusFrame = std::shared_ptr(new StatusFrame(system.getTaskManager().getTasks())); 18 | system.getDisplay().setStatusFrame(statusFrame); 19 | if (!system.getUserConfig()->display.alwaysOn) { 20 | system.getDisplay().activateDisplaySaveMode(); 21 | system.getDisplay().setDisplaySaveTimeout(system.getUserConfig()->display.timeout); 22 | } 23 | _stateInfo = system.getUserConfig()->callsign; 24 | return true; 25 | } 26 | 27 | bool DisplayTask::loop(System &system) { 28 | if (system.getUserConfig()->display.overwritePin != 0 && !digitalRead(system.getUserConfig()->display.overwritePin)) { 29 | system.getDisplay().activateDistplay(); 30 | } 31 | system.getDisplay().update(); 32 | return true; 33 | } 34 | -------------------------------------------------------------------------------- /lib/System/System.h: -------------------------------------------------------------------------------- 1 | #ifndef SYSTEM_H_ 2 | #define SYSTEM_H_ 3 | 4 | #include 5 | #include 6 | 7 | #include "TaskManager.h" 8 | #include 9 | #include 10 | #include 11 | 12 | class System { 13 | public: 14 | System(); 15 | ~System(); 16 | 17 | void setBoardConfig(BoardConfig const *const boardConfig); 18 | void setUserConfig(Configuration const *const userConfig); 19 | 20 | BoardConfig const *const getBoardConfig() const; 21 | Configuration const *const getUserConfig() const; 22 | TaskManager & getTaskManager(); 23 | Display & getDisplay(); 24 | bool isWifiOrEthConnected() const; 25 | void connectedViaEth(bool status); 26 | void connectedViaWifi(bool status); 27 | logging::Logger & getLogger(); 28 | 29 | private: 30 | BoardConfig const * _boardConfig; 31 | Configuration const *_userConfig; 32 | TaskManager _taskManager; 33 | Display _display; 34 | bool _isEthConnected; 35 | bool _isWifiConnected; 36 | logging::Logger _logger; 37 | }; 38 | 39 | #endif 40 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Create new release 2 | 3 | on: 4 | workflow_dispatch: 5 | 6 | jobs: 7 | version_check: 8 | name: Version Check 9 | runs-on: ubuntu-latest 10 | steps: 11 | - name: Checkout code 12 | uses: actions/checkout@v2 13 | with: 14 | fetch-depth: 0 15 | - name: Set up Python 16 | uses: actions/setup-python@v2 17 | - run: pip install GitPython 18 | - name: check version 19 | run: ./scripts/check_version.py 20 | 21 | create_release: 22 | needs: version_check 23 | name: Create new release 24 | runs-on: ubuntu-latest 25 | steps: 26 | - uses: actions/checkout@v2 27 | - run: sudo apt-get install python3-setuptools python3-wheel 28 | - run: pip3 install platformio 29 | - run: echo "$HOME/.local/bin" >> $GITHUB_PATH 30 | - run: platformio run 31 | - run: echo "VERSION=$(./scripts/create_version_tag.py)" >> $GITHUB_ENV 32 | - uses: ncipollo/release-action@v1 33 | with: 34 | tag: ${{ env.VERSION }} 35 | commit: master 36 | generateReleaseNotes: true 37 | artifacts: ".pio/build/lora_board/firmware.bin,data/is-cfg.json" 38 | owner: ${{ secrets.OWNER }} 39 | token: ${{ secrets.PAT }} 40 | -------------------------------------------------------------------------------- /.devcontainer/devcontainer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Python 3", 3 | "build": { 4 | "dockerfile": "Dockerfile", 5 | "context": "..", 6 | "args": { 7 | "VARIANT": "3.10-bullseye" 8 | } 9 | }, 10 | "settings": { 11 | "python.defaultInterpreterPath": "/usr/local/bin/python", 12 | "python.linting.enabled": true, 13 | "python.linting.pylintEnabled": true, 14 | "python.formatting.autopep8Path": "/usr/local/py-utils/bin/autopep8", 15 | "python.formatting.blackPath": "/usr/local/py-utils/bin/black", 16 | "python.formatting.yapfPath": "/usr/local/py-utils/bin/yapf", 17 | "python.linting.banditPath": "/usr/local/py-utils/bin/bandit", 18 | "python.linting.flake8Path": "/usr/local/py-utils/bin/flake8", 19 | "python.linting.mypyPath": "/usr/local/py-utils/bin/mypy", 20 | "python.linting.pycodestylePath": "/usr/local/py-utils/bin/pycodestyle", 21 | "python.linting.pydocstylePath": "/usr/local/py-utils/bin/pydocstyle", 22 | "python.linting.pylintPath": "/usr/local/py-utils/bin/pylint", 23 | "clang-format.executable": "clang-format-11" 24 | }, 25 | "extensions": [ 26 | "ms-python.python", 27 | "ms-python.vscode-pylance", 28 | "platformio.platformio-ide", 29 | "xaver.clang-format" 30 | ], 31 | "postCreateCommand": "pip3 install --user platformio", 32 | //"remoteUser": "vscode" 33 | } 34 | -------------------------------------------------------------------------------- /src/TaskFTP.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #include "Task.h" 6 | #include "TaskFTP.h" 7 | #include "project_configuration.h" 8 | 9 | FTPTask::FTPTask() : Task(TASK_FTP, TaskFtp), _beginCalled(false) { 10 | } 11 | 12 | FTPTask::~FTPTask() { 13 | } 14 | 15 | bool FTPTask::setup(System &system) { 16 | for (Configuration::Ftp::User user : system.getUserConfig()->ftp.users) { 17 | system.getLogger().log(logging::LoggerLevel::LOGGER_LEVEL_DEBUG, getName(), "Adding user to FTP Server: %s", user.name.c_str()); 18 | _ftpServer.addUser(user.name, user.password); 19 | } 20 | _ftpServer.addFilesystem("SPIFFS", &SPIFFS); 21 | _stateInfo = "waiting"; 22 | return true; 23 | } 24 | 25 | bool FTPTask::loop(System &system) { 26 | if (!_beginCalled) { 27 | _ftpServer.begin(); 28 | _beginCalled = true; 29 | } 30 | _ftpServer.handle(); 31 | static bool configWasOpen = false; 32 | if (configWasOpen && _ftpServer.countConnections() == 0) { 33 | system.getLogger().log(logging::LoggerLevel::LOGGER_LEVEL_WARN, getName(), "Maybe the config has been changed via FTP, lets restart now to get the new config..."); 34 | ESP.restart(); 35 | } 36 | if (_ftpServer.countConnections() > 0) { 37 | configWasOpen = true; 38 | _stateInfo = "has connection"; 39 | } 40 | return true; 41 | } 42 | -------------------------------------------------------------------------------- /lib/TimeLib/TimeLibString.cpp: -------------------------------------------------------------------------------- 1 | /* DateStrings.cpp 2 | * Definitions for date strings for use with the Time library 3 | * 4 | * Updated for Arduino 1.5.7 18 July 2014 5 | * 6 | * No memory is consumed in the sketch if your code does not call any of the string methods 7 | * You can change the text of the strings, make sure the short strings are each exactly 3 characters 8 | * the long strings can be any length up to the constant dt_MAX_STRING_LEN defined in TimeLib.h 9 | * 10 | */ 11 | 12 | #include 13 | #include "TimeLib.h" 14 | 15 | const String monthNames[] = 16 | { 17 | "Error", "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December" 18 | }; 19 | 20 | const String monthStr(uint8_t month) 21 | { 22 | return monthNames[month]; 23 | } 24 | 25 | 26 | const String monthShortNames[] = 27 | { 28 | "Err", "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" 29 | }; 30 | 31 | const String monthShortStr(uint8_t month) 32 | { 33 | return monthShortNames[month]; 34 | } 35 | 36 | 37 | const String dayNames[] = 38 | { 39 | "Err", "Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday" 40 | }; 41 | 42 | const String dayStr(uint8_t day) 43 | { 44 | return dayNames[day]; 45 | } 46 | 47 | 48 | const String dayShortNames[] = 49 | { 50 | "Err", "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat" 51 | }; 52 | 53 | const String dayShortStr(uint8_t day) 54 | { 55 | return dayShortNames[day]; 56 | } 57 | -------------------------------------------------------------------------------- /lib/Display/Bitmap.h: -------------------------------------------------------------------------------- 1 | #ifndef BITMAP_H_ 2 | #define BITMAP_H_ 3 | 4 | #include 5 | #include 6 | 7 | class OLEDDisplay; 8 | 9 | class Bitmap { 10 | public: 11 | explicit Bitmap(uint width, uint height); 12 | explicit Bitmap(OLEDDisplay *display); 13 | virtual ~Bitmap(); 14 | 15 | uint getWidth() const; 16 | uint getHeight() const; 17 | 18 | void setPixel(int x, int y); 19 | void clearPixel(int x, int y); 20 | bool getPixel(int x, int y) const; 21 | void clear(); 22 | 23 | void drawLine(int x0, int y0, int x1, int y1); 24 | void drawHorizontalLine(int x, int y, int length); 25 | void drawVerticalLine(int x, int y, int length); 26 | 27 | void drawRect(int x, int y, int width, int height); 28 | void fillRect(int x, int y, int width, int height); 29 | 30 | void drawCircle(int x0, int y0, int radius); 31 | void fillCircle(int x0, int y0, int radius); 32 | void drawCircleQuads(int x0, int y0, int radius, int quads); 33 | 34 | void drawProgressBar(int x, int y, int width, int height, int progress); 35 | 36 | int drawChar(int x, int y, char c); 37 | int drawString(int x, int y, String text); 38 | void drawStringf(int x, int y, char *buffer, String format, ...); 39 | int drawStringLF(int x, int y, String text); 40 | void drawStringLFf(int x, int y, char *buffer, String format, ...); 41 | 42 | // void drawBitmap(int x, int y, const Bitmap & bitmap); 43 | 44 | private: 45 | const uint _width; 46 | const uint _height; 47 | 48 | uint8_t *_buffer; 49 | 50 | void allocateBuffer(); 51 | 52 | friend class SSD1306; 53 | }; 54 | 55 | #endif 56 | -------------------------------------------------------------------------------- /scripts/check_version.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import git 4 | from datetime import date 5 | 6 | today = date.today() 7 | 8 | current_year = int(str(today.isocalendar()[0])[2:]) 9 | current_week = int(today.isocalendar()[1]) 10 | 11 | version = None 12 | with open("src/LoRa_APRS_iGate.cpp") as f: 13 | for line in f: 14 | if line.startswith("#define VERSION"): 15 | version = line.strip().split(" ")[-1].replace('"', "") 16 | 17 | version_split = version.split(".") 18 | version_year = int(version_split[0]) 19 | version_week = int(version_split[1]) 20 | version_vers = int(version_split[2]) 21 | 22 | print(f"[INFO] firmware version year: {version_year}") 23 | print(f"[INFO] firmware version week: {version_week}") 24 | print(f"[INFO] firmware version version: {version_vers}") 25 | print(f"[INFO] -> {version}") 26 | 27 | print(f"[INFO] current year: {current_year}") 28 | print(f"[INFO] current week: {current_week}") 29 | print(f"[INFO] -> {current_year}.{current_week}.x") 30 | 31 | error = False 32 | if version_year != current_year: 33 | print("[ERROR] firmware version is not current year!") 34 | error = True 35 | 36 | if version_week != current_week: 37 | print("[ERROR] firmware version is not current week!") 38 | error = True 39 | 40 | repo = git.Repo('.') 41 | print(f"[INFO] found {len(repo.tags)} tags in repo") 42 | if f"v{version}" in repo.tags: 43 | print("[ERROR] tag with this version is already existing") 44 | error = True 45 | 46 | if error: 47 | print("[ERROR] check/update VERSION define in src/LoRa_APRS_iGate.cpp to fix this issue") 48 | 49 | exit(error) 50 | -------------------------------------------------------------------------------- /data/is-cfg.json: -------------------------------------------------------------------------------- 1 | { 2 | "callsign": "NOCALL-10", 3 | "network": { 4 | "DHCP": true, 5 | "static": { 6 | "ip": "192.168.0.100", 7 | "subnet": "255.255.255.0", 8 | "gateway": "192.168.0.1", 9 | "dns1": "192.168.0.1", 10 | "dns2": "192.168.0.2" 11 | }, 12 | "hostname": { 13 | "overwrite": false, 14 | "name": "NOCALL-10" 15 | } 16 | }, 17 | "wifi": { 18 | "active": true, 19 | "AP": [ 20 | { 21 | "SSID": "YOURSSID", 22 | "password": "YOURPASSWORD" 23 | } 24 | ] 25 | }, 26 | "beacon": { 27 | "message": "LoRa iGATE & Digi, Info: github.com/lora-aprs/LoRa_APRS_iGate", 28 | "position": { 29 | "latitude": 0.000000, 30 | "longitude": 0.000000 31 | }, 32 | "use_gps": false, 33 | "timeout": 15 34 | }, 35 | "aprs_is": { 36 | "active": true, 37 | "passcode": "", 38 | "server": "euro.aprs2.net", 39 | "port": 14580 40 | }, 41 | "digi": { 42 | "active": false, 43 | "beacon": false 44 | }, 45 | "lora": { 46 | "frequency_rx": 433775000, 47 | "gain_rx": 0, 48 | "frequency_tx": 433775000, 49 | "power": 20, 50 | "spreading_factor": 12, 51 | "signal_bandwidth": 125000, 52 | "coding_rate4": 5, 53 | "tx_enable": false 54 | }, 55 | "display": { 56 | "always_on": true, 57 | "timeout": 10, 58 | "overwrite_pin": 0, 59 | "turn180": true 60 | }, 61 | "ftp": { 62 | "active": false, 63 | "user": [ 64 | { 65 | "name": "ftp", 66 | "password": "ftp" 67 | } 68 | ] 69 | }, 70 | "mqtt": { 71 | "active": false, 72 | "server": "", 73 | "port": 1883, 74 | "name": "", 75 | "password": "", 76 | "topic": "LoraAPRS/Data" 77 | }, 78 | "syslog": { 79 | "active": true, 80 | "server": "syslog.lora-aprs.info", 81 | "port": 514 82 | }, 83 | "ntp_server": "pool.ntp.org" 84 | } 85 | -------------------------------------------------------------------------------- /lib/Display/Display.h: -------------------------------------------------------------------------------- 1 | #ifndef DISPLAY_H_ 2 | #define DISPLAY_H_ 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | class Timer; 14 | class StatusFrame; 15 | 16 | class DisplayFrame { 17 | public: 18 | DisplayFrame() { 19 | } 20 | virtual ~DisplayFrame() { 21 | } 22 | virtual void drawStatusPage(Bitmap &bitmap) = 0; 23 | }; 24 | 25 | class Display { 26 | public: 27 | Display(); 28 | ~Display(); 29 | 30 | void setup(BoardConfig const *const boardConfig); 31 | // setup functions 32 | void showSpashScreen(String firmwareTitle, String version); 33 | void setStatusFrame(std::shared_ptr frame); 34 | void showStatusScreen(String header, String text); 35 | 36 | void turn180(); 37 | void activateDisplaySaveMode(); 38 | void setDisplaySaveTimeout(uint32_t timeout); 39 | 40 | void activateDistplay(); 41 | 42 | // functions for update loop 43 | void update(); 44 | void addFrame(std::shared_ptr frame); 45 | 46 | private: 47 | OLEDDisplay *_disp; 48 | 49 | Timer _displayFrameRate; 50 | std::shared_ptr _statusFrame; 51 | 52 | std::list> _frames; 53 | Timer _frameTimeout; 54 | 55 | bool _displaySaveMode; 56 | Timer _displaySaveModeTimer; 57 | }; 58 | 59 | class TextFrame : public DisplayFrame { 60 | public: 61 | TextFrame(String header, String text) : _header(header), _text(text) { 62 | } 63 | virtual ~TextFrame() { 64 | } 65 | void drawStatusPage(Bitmap &bitmap) override; 66 | 67 | private: 68 | String _header; 69 | String _text; 70 | }; 71 | 72 | #endif 73 | -------------------------------------------------------------------------------- /src/TaskOTA.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include "Task.h" 4 | #include "TaskOTA.h" 5 | #include "project_configuration.h" 6 | 7 | OTATask::OTATask() : Task(TASK_OTA, TaskOta), _beginCalled(false) { 8 | } 9 | 10 | OTATask::~OTATask() { 11 | } 12 | 13 | bool OTATask::setup(System &system) { 14 | _ota.onStart([&]() { 15 | String type; 16 | if (_ota.getCommand() == U_FLASH) { 17 | type = "sketch"; 18 | } else { // U_SPIFFS 19 | type = "filesystem"; 20 | } 21 | system.getLogger().log(logging::LoggerLevel::LOGGER_LEVEL_INFO, getName(), "Start updating %s. please wait, this prozess is taking some time!", type.c_str()); 22 | }) 23 | .onEnd([&]() { 24 | system.getLogger().log(logging::LoggerLevel::LOGGER_LEVEL_INFO, getName(), "OTA End"); 25 | }) 26 | .onError([&](ota_error_t error) { 27 | String error_str; 28 | if (error == OTA_AUTH_ERROR) { 29 | error_str = "Auth Failed"; 30 | } else if (error == OTA_BEGIN_ERROR) { 31 | error_str = "Begin Failed"; 32 | } else if (error == OTA_CONNECT_ERROR) { 33 | error_str = "Connect Failed"; 34 | } else if (error == OTA_RECEIVE_ERROR) { 35 | error_str = "Receive Failed"; 36 | } else if (error == OTA_END_ERROR) { 37 | error_str = "End Failed"; 38 | } 39 | system.getLogger().log(logging::LoggerLevel::LOGGER_LEVEL_ERROR, getName(), "Error[%d]: %s", error, error_str.c_str()); 40 | }); 41 | if (system.getUserConfig()->network.hostname.overwrite) { 42 | _ota.setHostname(system.getUserConfig()->network.hostname.name.c_str()); 43 | } else { 44 | _ota.setHostname(system.getUserConfig()->callsign.c_str()); 45 | } 46 | _stateInfo = ""; 47 | return true; 48 | } 49 | 50 | bool OTATask::loop(System &system) { 51 | if (!_beginCalled) { 52 | _ota.begin(); 53 | _beginCalled = true; 54 | } 55 | _ota.handle(); 56 | return true; 57 | } 58 | -------------------------------------------------------------------------------- /lib/System/TaskManager.h: -------------------------------------------------------------------------------- 1 | #ifndef TASK_MANAGER_H_ 2 | #define TASK_MANAGER_H_ 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | #include 9 | #include 10 | #include 11 | 12 | #include "TaskQueue.h" 13 | 14 | class System; 15 | 16 | enum TaskDisplayState 17 | { 18 | Error, 19 | Warning, 20 | Okay, 21 | }; 22 | 23 | class Task { 24 | public: 25 | Task(String &name, int taskId) : _state(Okay), _stateInfo("Booting"), _name(name), _taskId(taskId) { 26 | } 27 | Task(const char *name, int taskId) : _state(Okay), _stateInfo("Booting"), _name(name), _taskId(taskId) { 28 | } 29 | virtual ~Task() { 30 | } 31 | 32 | String getName() const { 33 | return _name; 34 | } 35 | int getTaskId() const { 36 | return _taskId; 37 | } 38 | 39 | TaskDisplayState getState() const { 40 | return _state; 41 | } 42 | String getStateInfo() const { 43 | return _stateInfo; 44 | } 45 | 46 | virtual bool setup(System &system) = 0; 47 | virtual bool loop(System &system) = 0; 48 | 49 | protected: 50 | TaskDisplayState _state; 51 | String _stateInfo; 52 | 53 | private: 54 | String _name; 55 | int _taskId; 56 | }; 57 | 58 | class TaskManager { 59 | public: 60 | TaskManager(); 61 | ~TaskManager() { 62 | } 63 | 64 | void addTask(Task *task); 65 | void addAlwaysRunTask(Task *task); 66 | std::list getTasks(); 67 | 68 | bool setup(System &system); 69 | bool loop(System &system); 70 | 71 | private: 72 | std::list _tasks; 73 | std::list::iterator _nextTask; 74 | std::list _alwaysRunTasks; 75 | }; 76 | 77 | class StatusFrame : public DisplayFrame { 78 | public: 79 | explicit StatusFrame(const std::list &tasks) : _tasks(tasks) { 80 | } 81 | virtual ~StatusFrame() { 82 | } 83 | void drawStatusPage(Bitmap &bitmap) override; 84 | 85 | private: 86 | std::list _tasks; 87 | }; 88 | 89 | #include "System.h" 90 | 91 | #endif 92 | -------------------------------------------------------------------------------- /lib/Display/SSD1306.h: -------------------------------------------------------------------------------- 1 | /** 2 | * The MIT License (MIT) 3 | * 4 | * Copyright (c) 2018 by ThingPulse, Daniel Eichhorn 5 | * Copyright (c) 2018 by Fabrice Weinberg 6 | * 7 | * Permission is hereby granted, free of charge, to any person obtaining a copy 8 | * of this software and associated documentation files (the "Software"), to deal 9 | * in the Software without restriction, including without limitation the rights 10 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | * copies of the Software, and to permit persons to whom the Software is 12 | * furnished to do so, subject to the following conditions: 13 | * 14 | * The above copyright notice and this permission notice shall be included in all 15 | * copies or substantial portions of the Software. 16 | * 17 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 23 | * SOFTWARE. 24 | * 25 | * ThingPulse invests considerable time and money to develop these open source libraries. 26 | * Please support us by buying our products (and not the clones) from 27 | * https://thingpulse.com 28 | * 29 | */ 30 | 31 | #ifndef SSD1306_h 32 | #define SSD1306_h 33 | 34 | #include "OLEDDisplay.h" 35 | #include 36 | 37 | class SSD1306 : public OLEDDisplay { 38 | public: 39 | SSD1306(TwoWire *wire, uint8_t address, OLEDDISPLAY_GEOMETRY g = GEOMETRY_128_64); 40 | virtual ~SSD1306(); 41 | 42 | virtual void internDisplay(Bitmap *bitmap) override; 43 | 44 | private: 45 | TwoWire *_wire = NULL; 46 | uint8_t _address; 47 | bool _doI2cAutoInit = false; 48 | 49 | virtual void sendCommand(uint8_t command) override; 50 | }; 51 | 52 | #endif 53 | -------------------------------------------------------------------------------- /lib/ConfigurationManagement/configuration.cpp: -------------------------------------------------------------------------------- 1 | #include "configuration.h" 2 | #include 3 | #include 4 | 5 | #define MODULE_NAME "ConfigurationManagement" 6 | 7 | ConfigurationManagement::ConfigurationManagement(logging::Logger &logger, String FilePath) : mFilePath(FilePath) { 8 | if (!SPIFFS.begin(true)) { 9 | logger.log(logging::LoggerLevel::LOGGER_LEVEL_INFO, MODULE_NAME, "Mounting SPIFFS was not possible. Trying to format SPIFFS..."); 10 | SPIFFS.format(); 11 | if (!SPIFFS.begin()) { 12 | logger.log(logging::LoggerLevel::LOGGER_LEVEL_ERROR, MODULE_NAME, "Formating SPIFFS was not okay!"); 13 | } 14 | } 15 | } 16 | 17 | ConfigurationManagement::~ConfigurationManagement() { 18 | } 19 | 20 | void ConfigurationManagement::readConfiguration(logging::Logger &logger, Configuration &conf) { 21 | File file = SPIFFS.open(mFilePath); 22 | if (!file) { 23 | logger.log(logging::LoggerLevel::LOGGER_LEVEL_ERROR, MODULE_NAME, "Failed to open file for reading, using default configuration."); 24 | return; 25 | } 26 | DynamicJsonDocument data(2048); 27 | DeserializationError error = deserializeJson(data, file); 28 | if (error) { 29 | logger.log(logging::LoggerLevel::LOGGER_LEVEL_WARN, MODULE_NAME, "Failed to read file, using default configuration."); 30 | } 31 | // serializeJson(data, Serial); 32 | // Serial.println(); 33 | file.close(); 34 | 35 | readProjectConfiguration(data, conf); 36 | 37 | // update config in memory to get the new fields: 38 | // writeConfiguration(logger, conf); 39 | } 40 | 41 | void ConfigurationManagement::writeConfiguration(logging::Logger &logger, Configuration &conf) { 42 | File file = SPIFFS.open(mFilePath, "w"); 43 | if (!file) { 44 | logger.log(logging::LoggerLevel::LOGGER_LEVEL_ERROR, MODULE_NAME, "Failed to open file for writing..."); 45 | return; 46 | } 47 | DynamicJsonDocument data(2048); 48 | 49 | writeProjectConfiguration(conf, data); 50 | 51 | serializeJson(data, file); 52 | // serializeJson(data, Serial); 53 | // Serial.println(); 54 | file.close(); 55 | } 56 | -------------------------------------------------------------------------------- /lib/Display/FontConfig.cpp: -------------------------------------------------------------------------------- 1 | /*#include "Fonts/Amadeus_12.h" 2 | #include "Fonts/Amadeus_20.h" 3 | #include "Fonts/Amadeus_24.h" 4 | #include "Fonts/Blue_12.h" 5 | #include "Fonts/Blue_20.h" 6 | #include "Fonts/Blue_24.h" 7 | #include "Fonts/Burnstown_12.h" 8 | #include "Fonts/Burnstown_20.h" 9 | #include "Fonts/Burnstown_24.h" 10 | #include "Fonts/Courier.h" 11 | #include "Fonts/Courier_12.h" 12 | #include "Fonts/Courier_20.h" 13 | #include "Fonts/Courier_24.h" 14 | #include "Fonts/Courier_40.h" 15 | #include "Fonts/Cowboys_10.h" 16 | #include "Fonts/Cowboys_12.h" 17 | #include "Fonts/Cowboys_20.h" 18 | #include "Fonts/Cowboys_24.h" 19 | #include "Fonts/FixedSys_11.h" 20 | #include "Fonts/Gabriola_12.h" 21 | #include "Fonts/Gabriola_20.h" 22 | #include "Fonts/Gabriola_24.h"*/ 23 | #include "Fonts/HoloLens_12.h" 24 | #include "Fonts/HoloLens_20.h" 25 | /*#include "Fonts/Mickey_12.h" 26 | #include "Fonts/Mickey_20.h" 27 | #include "Fonts/Neuropol_12.h" 28 | #include "Fonts/Neuropol_20.h" 29 | #include "Fonts/Neuropol_24.h" 30 | #include "Fonts/Open_12.h" 31 | #include "Fonts/Open_20.h"*/ 32 | #include "Fonts/Roboto_12.h" 33 | /*#include "Fonts/Roboto_20.h" 34 | #include "Fonts/Script_10.h" 35 | #include "Fonts/Script_12.h" 36 | #include "Fonts/Script_20.h" 37 | #include "Fonts/Script_24.h" 38 | #include "Fonts/Seaside_12.h" 39 | #include "Fonts/Seaside_20.h" 40 | #include "Fonts/Seaside_24.h" 41 | #include "Fonts/Tahoma_10.h" 42 | #include "Fonts/Tahoma_12.h" 43 | #include "Fonts/Tahoma_20.h" 44 | #include "Fonts/Tahoma_24.h"*/ 45 | #include "Fonts/Terminal_8.h" 46 | #include "Fonts/Terminal_11.h" 47 | /*#include "Fonts/Times_12.h" 48 | #include "Fonts/Times_20.h" 49 | #include "Fonts/Times_24.h" 50 | #include "Fonts/Trebuchet_10.h" 51 | #include "Fonts/Trebuchet_12.h" 52 | #include "Fonts/Trebuchet_20.h" 53 | #include "Fonts/Trebuchet_24.h" 54 | #include "Fonts/Verdana_10.h" 55 | #include "Fonts/Verdana_12.h" 56 | #include "Fonts/Verdana_20.h" 57 | #include "Fonts/Verdana_24.h" 58 | #include "Fonts/Waker_12.h" 59 | #include "Fonts/Waker_20.h" 60 | #include "Fonts/Waker_24.h"*/ 61 | 62 | #include "FontConfig.h" 63 | 64 | fontDesc_t const * getSystemFont() 65 | { 66 | return &FONT; 67 | } 68 | -------------------------------------------------------------------------------- /lib/BoardFinder/BoardFinder.h: -------------------------------------------------------------------------------- 1 | #ifndef BOARD_FINDER_H_ 2 | #define BOARD_FINDER_H_ 3 | 4 | #include 5 | #include 6 | 7 | #include 8 | #include 9 | #include 10 | 11 | #include 12 | 13 | enum BoardType 14 | { 15 | eHELTEC_WIFI_LORA_32_V1, 16 | eHELTEC_WIFI_LORA_32_V2, 17 | eTTGO_LORA32_V1, 18 | eTTGO_LORA32_V2, 19 | eTTGO_T_Beam_V0_7, 20 | eTTGO_T_Beam_V1_0, 21 | eETH_BOARD, 22 | eTRACKERD 23 | }; 24 | 25 | class BoardConfig { 26 | public: 27 | explicit BoardConfig(String name, BoardType type, uint8_t oledsda, uint8_t oledscl, uint8_t oledaddr, uint8_t oledreset, uint8_t lorasck, uint8_t loramiso, uint8_t loramosi, uint8_t loracs, uint8_t lorareset, uint8_t lorairq, uint8_t gpsrx, uint8_t gpstx, uint8_t button, bool needcheckpowerchip = false, bool powercheckstatus = false); 28 | 29 | String Name; 30 | BoardType Type; 31 | 32 | uint8_t OledSda; 33 | uint8_t OledScl; 34 | uint8_t OledAddr; 35 | uint8_t OledReset; 36 | 37 | uint8_t LoraSck; 38 | uint8_t LoraMiso; 39 | uint8_t LoraMosi; 40 | uint8_t LoraCS; 41 | uint8_t LoraReset; 42 | uint8_t LoraIRQ; 43 | uint8_t GpsRx; 44 | uint8_t GpsTx; 45 | uint8_t Button; 46 | 47 | bool needCheckPowerChip; 48 | bool powerCheckStatus; 49 | }; 50 | 51 | class BoardFinder { 52 | public: 53 | explicit BoardFinder(const std::list &boardConfigs); 54 | 55 | BoardConfig const *searchBoardConfig(logging::Logger &logger); 56 | 57 | BoardConfig const *getBoardConfig(String name); 58 | 59 | private: 60 | const std::list &_boardConfigs; 61 | 62 | bool checkOledConfig(BoardConfig const *boardConfig, logging::Logger &logger); 63 | bool checkModemConfig(BoardConfig const *boardConfig); 64 | bool checkPowerConfig(BoardConfig const *boardConfig, logging::Logger &logger); 65 | }; 66 | 67 | extern BoardConfig TTGO_LORA32_V1; 68 | extern BoardConfig TTGO_LORA32_V2; 69 | extern BoardConfig TTGO_T_Beam_V0_7; 70 | extern BoardConfig TTGO_T_Beam_V1_0; 71 | extern BoardConfig ETH_BOARD; 72 | extern BoardConfig TRACKERD; 73 | extern BoardConfig HELTEC_WIFI_LORA_32_V1; 74 | extern BoardConfig HELTEC_WIFI_LORA_32_V2; 75 | 76 | #endif 77 | -------------------------------------------------------------------------------- /src/TaskWifi.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include "Task.h" 5 | #include "TaskEth.h" 6 | #include "TaskWifi.h" 7 | #include "project_configuration.h" 8 | 9 | WifiTask::WifiTask() : Task(TASK_WIFI, TaskWifi), _oldWifiStatus(WL_IDLE_STATUS) { 10 | } 11 | 12 | WifiTask::~WifiTask() { 13 | } 14 | 15 | bool WifiTask::setup(System &system) { 16 | // Don't save WiFi configuration in flash 17 | WiFi.persistent(false); 18 | 19 | // Set WiFi to station mode 20 | WiFi.mode(WIFI_STA); 21 | 22 | WiFi.onEvent(WiFiEvent); 23 | if (system.getUserConfig()->network.hostname.overwrite) { 24 | WiFi.setHostname(system.getUserConfig()->network.hostname.name.c_str()); 25 | } else { 26 | WiFi.setHostname(system.getUserConfig()->callsign.c_str()); 27 | } 28 | 29 | if (!system.getUserConfig()->network.DHCP) { 30 | WiFi.config(system.getUserConfig()->network.static_.ip, system.getUserConfig()->network.static_.gateway, system.getUserConfig()->network.static_.subnet, system.getUserConfig()->network.static_.dns1, system.getUserConfig()->network.static_.dns2); 31 | } 32 | 33 | for (Configuration::Wifi::AP ap : system.getUserConfig()->wifi.APs) { 34 | system.getLogger().log(logging::LoggerLevel::LOGGER_LEVEL_DEBUG, getName(), "Looking for AP: %s", ap.SSID.c_str()); 35 | _wiFiMulti.addAP(ap.SSID.c_str(), ap.password.c_str()); 36 | } 37 | return true; 38 | } 39 | 40 | bool WifiTask::loop(System &system) { 41 | const uint8_t wifi_status = _wiFiMulti.run(); 42 | if (wifi_status != WL_CONNECTED) { 43 | system.connectedViaWifi(false); 44 | system.getLogger().log(logging::LoggerLevel::LOGGER_LEVEL_ERROR, getName(), "WiFi not connected!"); 45 | _oldWifiStatus = wifi_status; 46 | _stateInfo = "WiFi not connected"; 47 | _state = Error; 48 | return false; 49 | } else if (wifi_status != _oldWifiStatus) { 50 | system.getLogger().log(logging::LoggerLevel::LOGGER_LEVEL_DEBUG, getName(), "WiFi IP address: %s", WiFi.localIP().toString().c_str()); 51 | _oldWifiStatus = wifi_status; 52 | return false; 53 | } 54 | system.connectedViaWifi(true); 55 | _stateInfo = String("IP .") + String(WiFi.localIP()[3]) + String(" @ ") + String(WiFi.RSSI()) + String("dBm"); 56 | _state = Okay; 57 | return true; 58 | } 59 | -------------------------------------------------------------------------------- /src/TaskAprsIs.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include "Task.h" 4 | #include "TaskAprsIs.h" 5 | #include "project_configuration.h" 6 | 7 | AprsIsTask::AprsIsTask(TaskQueue> &toAprsIs) : Task(TASK_APRS_IS, TaskAprsIs), _toAprsIs(toAprsIs) { 8 | } 9 | 10 | AprsIsTask::~AprsIsTask() { 11 | } 12 | 13 | bool AprsIsTask::setup(System &system) { 14 | _aprs_is.setup(system.getUserConfig()->callsign, system.getUserConfig()->aprs_is.passcode, "ESP32-APRS-IS", "0.2"); 15 | return true; 16 | } 17 | 18 | bool AprsIsTask::loop(System &system) { 19 | if (!system.isWifiOrEthConnected()) { 20 | return false; 21 | } 22 | if (!_aprs_is.connected()) { 23 | if (!connect(system)) { 24 | _stateInfo = "not connected"; 25 | _state = Error; 26 | return false; 27 | } 28 | _stateInfo = "connected"; 29 | _state = Okay; 30 | return false; 31 | } 32 | 33 | _aprs_is.getAPRSMessage(); 34 | 35 | if (!_toAprsIs.empty()) { 36 | std::shared_ptr msg = _toAprsIs.getElement(); 37 | _aprs_is.sendMessage(msg); 38 | } 39 | 40 | return true; 41 | } 42 | 43 | bool AprsIsTask::connect(System &system) { 44 | system.getLogger().log(logging::LoggerLevel::LOGGER_LEVEL_INFO, getName(), "connecting to APRS-IS server: %s on port: %d", system.getUserConfig()->aprs_is.server.c_str(), system.getUserConfig()->aprs_is.port); 45 | APRS_IS::ConnectionStatus status = _aprs_is.connect(system.getUserConfig()->aprs_is.server, system.getUserConfig()->aprs_is.port); 46 | if (status == APRS_IS::ERROR_CONNECTION) { 47 | system.getLogger().log(logging::LoggerLevel::LOGGER_LEVEL_ERROR, getName(), "Something went wrong on connecting! Is the server reachable?"); 48 | system.getLogger().log(logging::LoggerLevel::LOGGER_LEVEL_ERROR, getName(), "Connection failed."); 49 | return false; 50 | } else if (status == APRS_IS::ERROR_PASSCODE) { 51 | system.getLogger().log(logging::LoggerLevel::LOGGER_LEVEL_ERROR, getName(), "User can not be verified with passcode!"); 52 | system.getLogger().log(logging::LoggerLevel::LOGGER_LEVEL_ERROR, getName(), "Connection failed."); 53 | return false; 54 | } 55 | system.getLogger().log(logging::LoggerLevel::LOGGER_LEVEL_INFO, getName(), "Connected to APRS-IS server!"); 56 | return true; 57 | } 58 | -------------------------------------------------------------------------------- /lib/System/TaskManager.cpp: -------------------------------------------------------------------------------- 1 | #include "TaskManager.h" 2 | #include 3 | #include 4 | 5 | #define MODULE_NAME "TaskManager" 6 | 7 | TaskManager::TaskManager() { 8 | } 9 | 10 | void TaskManager::addTask(Task *task) { 11 | _tasks.push_back(task); 12 | } 13 | 14 | void TaskManager::addAlwaysRunTask(Task *task) { 15 | _alwaysRunTasks.push_back(task); 16 | } 17 | 18 | std::list TaskManager::getTasks() { 19 | std::list tasks = _alwaysRunTasks; 20 | std::copy(_tasks.begin(), _tasks.end(), std::back_inserter(tasks)); 21 | return tasks; 22 | } 23 | 24 | bool TaskManager::setup(System &system) { 25 | system.getLogger().log(logging::LoggerLevel::LOGGER_LEVEL_INFO, MODULE_NAME, "will setup all tasks..."); 26 | for (Task *elem : _alwaysRunTasks) { 27 | system.getLogger().log(logging::LoggerLevel::LOGGER_LEVEL_DEBUG, MODULE_NAME, "call setup for %s", elem->getName().c_str()); 28 | elem->setup(system); 29 | } 30 | for (Task *elem : _tasks) { 31 | system.getLogger().log(logging::LoggerLevel::LOGGER_LEVEL_DEBUG, MODULE_NAME, "call setup for %s", elem->getName().c_str()); 32 | elem->setup(system); 33 | } 34 | _nextTask = _tasks.begin(); 35 | return true; 36 | } 37 | 38 | bool TaskManager::loop(System &system) { 39 | for (Task *elem : _alwaysRunTasks) { 40 | elem->loop(system); 41 | } 42 | 43 | if (_nextTask == _tasks.end()) { 44 | _nextTask = _tasks.begin(); 45 | } 46 | bool ret = (*_nextTask)->loop(system); 47 | ++_nextTask; 48 | return ret; 49 | } 50 | 51 | // cppcheck-suppress unusedFunction 52 | void StatusFrame::drawStatusPage(Bitmap &bitmap) { 53 | int y = 0; 54 | for (Task *task : _tasks) { 55 | int x = bitmap.drawString(0, y, (task->getName()).substring(0, task->getName().indexOf("Task"))); 56 | x = bitmap.drawString(x, y, ": "); 57 | if (task->getStateInfo() == "") { 58 | switch (task->getState()) { 59 | case Error: 60 | bitmap.drawString(x, y, "Error"); 61 | break; 62 | case Warning: 63 | bitmap.drawString(x, y, "Warning"); 64 | default: 65 | break; 66 | } 67 | bitmap.drawString(x, y, "Okay"); 68 | } else { 69 | bitmap.drawString(x, y, task->getStateInfo()); 70 | } 71 | y += getSystemFont()->heightInPixel; 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /src/TaskMQTT.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include "Task.h" 4 | #include "TaskMQTT.h" 5 | #include "project_configuration.h" 6 | 7 | #include 8 | 9 | MQTTTask::MQTTTask(TaskQueue> &toMQTT) : Task(TASK_MQTT, TaskMQTT), _toMQTT(toMQTT), _MQTT(_client) { 10 | } 11 | 12 | MQTTTask::~MQTTTask() { 13 | } 14 | 15 | bool MQTTTask::setup(System &system) { 16 | _MQTT.setServer(system.getUserConfig()->mqtt.server.c_str(), system.getUserConfig()->mqtt.port); 17 | return true; 18 | } 19 | 20 | bool MQTTTask::loop(System &system) { 21 | if (!system.isWifiOrEthConnected()) { 22 | return false; 23 | } 24 | 25 | if (!_MQTT.connected()) { 26 | connect(system); 27 | } 28 | 29 | if (!_toMQTT.empty()) { 30 | std::shared_ptr msg = _toMQTT.getElement(); 31 | 32 | DynamicJsonDocument data(1024); 33 | data["source"] = msg->getSource(); 34 | data["destination"] = msg->getDestination(); 35 | data["path"] = msg->getPath(); 36 | data["type"] = msg->getType().toString(); 37 | String body = msg->getBody()->encode(); 38 | body.replace("\n", ""); 39 | data["data"] = body; 40 | 41 | String r; 42 | serializeJson(data, r); 43 | 44 | String topic = String(system.getUserConfig()->mqtt.topic); 45 | if (!topic.endsWith("/")) { 46 | topic = topic + "/"; 47 | } 48 | topic = topic + system.getUserConfig()->callsign; 49 | system.getLogger().log(logging::LoggerLevel::LOGGER_LEVEL_DEBUG, getName(), "Send MQTT with topic: '%s', data: %s", topic.c_str(), r.c_str()); 50 | _MQTT.publish(topic.c_str(), r.c_str()); 51 | } 52 | _MQTT.loop(); 53 | return true; 54 | } 55 | 56 | bool MQTTTask::connect(System &system) { 57 | system.getLogger().log(logging::LoggerLevel::LOGGER_LEVEL_INFO, getName(), "Connecting to MQTT broker: %s on port %d", system.getUserConfig()->mqtt.server.c_str(), system.getUserConfig()->mqtt.port); 58 | if (_MQTT.connect(system.getUserConfig()->callsign.c_str(), system.getUserConfig()->mqtt.name.c_str(), system.getUserConfig()->mqtt.password.c_str())) { 59 | system.getLogger().log(logging::LoggerLevel::LOGGER_LEVEL_INFO, getName(), "Connected to MQTT broker as: %s", system.getUserConfig()->callsign.c_str()); 60 | return true; 61 | } 62 | system.getLogger().log(logging::LoggerLevel::LOGGER_LEVEL_INFO, getName(), "Connecting to MQTT broker failed. Try again later."); 63 | return false; 64 | } 65 | -------------------------------------------------------------------------------- /lib/APRS-IS/APRS-IS.cpp: -------------------------------------------------------------------------------- 1 | #include "APRS-IS.h" 2 | #include 3 | 4 | void APRS_IS::setup(const String &user, const String &passcode, const String &tool_name, const String &version) { 5 | _user = user; 6 | _passcode = passcode; 7 | _tool_name = tool_name; 8 | _version = version; 9 | } 10 | 11 | APRS_IS::ConnectionStatus APRS_IS::connect(const String &server, const int port) { 12 | const String login = "user " + _user + " pass " + _passcode + " vers " + _tool_name + " " + _version + "\n\r"; 13 | return _connect(server, port, login); 14 | } 15 | 16 | APRS_IS::ConnectionStatus APRS_IS::connect(const String &server, const int port, const String &filter) { 17 | const String login = "user " + _user + " pass " + _passcode + " vers " + _tool_name + " " + _version + " filter " + filter + "\n\r"; 18 | return _connect(server, port, login); 19 | } 20 | 21 | APRS_IS::ConnectionStatus APRS_IS::_connect(const String &server, const int port, const String &login_line) { 22 | if (!_client.connect(server.c_str(), port)) { 23 | return ERROR_CONNECTION; 24 | } 25 | sendMessage(login_line); 26 | while (true) { 27 | String line = _client.readStringUntil('\n'); 28 | if (line.indexOf("logresp") != -1) { 29 | if (line.indexOf("unverified") == -1) { 30 | return SUCCESS; 31 | } else { 32 | return ERROR_PASSCODE; 33 | } 34 | } 35 | } 36 | return SUCCESS; 37 | } 38 | 39 | bool APRS_IS::connected() { 40 | return _client.connected(); 41 | } 42 | 43 | bool APRS_IS::sendMessage(const String &message) { 44 | if (!connected()) { 45 | return false; 46 | } 47 | _client.println(message); 48 | return true; 49 | } 50 | 51 | bool APRS_IS::sendMessage(const std::shared_ptr message) { 52 | if (!connected()) { 53 | return false; 54 | } 55 | _client.println(message->encode() + "\n"); 56 | return true; 57 | } 58 | 59 | int APRS_IS::available() { 60 | return _client.available(); 61 | } 62 | 63 | String APRS_IS::getMessage() { 64 | String line; 65 | if (_client.available() > 0) { 66 | line = _client.readStringUntil('\n'); 67 | } 68 | return line; 69 | } 70 | 71 | std::shared_ptr APRS_IS::getAPRSMessage() { 72 | String line; 73 | if (_client.available() > 0) { 74 | line = _client.readStringUntil('\n'); 75 | } 76 | if (line.startsWith("#")) { 77 | return 0; 78 | } 79 | if (line.length() == 0) { 80 | return 0; 81 | } 82 | std::shared_ptr msg = std::shared_ptr(new APRSMessage()); 83 | msg->decode(line); 84 | return msg; 85 | } 86 | -------------------------------------------------------------------------------- /.github/workflows/build_check.yml: -------------------------------------------------------------------------------- 1 | name: Integreation Tests 2 | 3 | on: 4 | push: 5 | branches: 6 | - '*' 7 | - '!master' 8 | pull_request: 9 | branches: 10 | - master 11 | 12 | jobs: 13 | build: 14 | name: Compile Firmware 15 | runs-on: ubuntu-latest 16 | steps: 17 | - name: Checkout code 18 | uses: actions/checkout@v2 19 | - name: Set up Python 20 | uses: actions/setup-python@v2 21 | - name: Install PlatformIO 22 | run: python -m pip install --upgrade pip platformio 23 | - name: Run PlatformIO CI 24 | run: platformio run 25 | - name: Upload artifacts 26 | uses: actions/upload-artifact@v2 27 | with: 28 | name: firmware 29 | path: .pio/build/lora_board/firmware.bin 30 | 31 | formatting-check: 32 | name: Formatting Check 33 | runs-on: ubuntu-latest 34 | strategy: 35 | matrix: 36 | path: 37 | - 'src' 38 | - 'lib/BoardFinder' 39 | - 'lib/ConfigurationManagement' 40 | #- 'lib/Display' 41 | #- 'lib/NTPClient' 42 | - 'lib/PowerManagement' 43 | - 'lib/System' 44 | #- 'lib/TimeLib' 45 | steps: 46 | - name: Checkout code 47 | uses: actions/checkout@v2 48 | - name: Run clang-format style check for C/C++ programs. 49 | uses: jidicula/clang-format-action@v4.5.0 50 | with: 51 | clang-format-version: '11' 52 | check-path: ${{ matrix.path }} 53 | 54 | cppcheck: 55 | name: Run cppcheck 56 | runs-on: ubuntu-latest 57 | env: 58 | CPPCHECK_ARGS: --enable=all --std=c++14 --inline-suppr -I lib/BoardFinder -I lib/ConfigurationManagement -I lib/Display -I lib/LoRa -I lib/LoRa_APRS -I lib/NTPClient -I lib/PowerManagement -I lib/System -I lib/TimeLib -i lib/Display -i lib/LoRa -i lib/NTPClient -i lib/TimeLib src lib 59 | steps: 60 | - name: checkout code 61 | uses: actions/checkout@v2 62 | - run: docker pull facthunder/cppcheck:latest 63 | - name: Run cppcheck and print result 64 | run: docker run --rm -v ${PWD}:/src facthunder/cppcheck:latest /bin/bash -c "cppcheck $CPPCHECK_ARGS" 65 | - name: Run cppcheck and create html 66 | run: docker run --rm -v ${PWD}:/src facthunder/cppcheck:latest /bin/bash -c "cppcheck --xml $CPPCHECK_ARGS 2> report.xml && cppcheck-htmlreport --file=report.xml --report-dir=output" 67 | - name: Upload report 68 | uses: actions/upload-artifact@v1 69 | with: 70 | name: Cppcheck Report 71 | path: output 72 | -------------------------------------------------------------------------------- /src/TaskRouter.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | 5 | #include "Task.h" 6 | #include "TaskRouter.h" 7 | #include "project_configuration.h" 8 | 9 | RouterTask::RouterTask(TaskQueue> &fromModem, TaskQueue> &toModem, TaskQueue> &toAprsIs, TaskQueue> &toMQTT) : Task(TASK_ROUTER, TaskRouter), _fromModem(fromModem), _toModem(toModem), _toAprsIs(toAprsIs), _toMQTT(toMQTT) { 10 | } 11 | 12 | RouterTask::~RouterTask() { 13 | } 14 | 15 | bool RouterTask::setup(System &system) { 16 | return true; 17 | } 18 | 19 | bool RouterTask::loop(System &system) { 20 | if (!_fromModem.empty()) { 21 | std::shared_ptr modemMsg = _fromModem.getElement(); 22 | 23 | if (system.getUserConfig()->mqtt.active) { 24 | _toMQTT.addElement(modemMsg); 25 | } 26 | 27 | if (system.getUserConfig()->aprs_is.active && modemMsg->getSource() != system.getUserConfig()->callsign) { 28 | std::shared_ptr aprsIsMsg = std::make_shared(*modemMsg); 29 | String path = aprsIsMsg->getPath(); 30 | 31 | if (!(path.indexOf("RFONLY") != -1 || path.indexOf("NOGATE") != -1 || path.indexOf("TCPIP") != -1)) { 32 | if (!path.isEmpty()) { 33 | path += ","; 34 | } 35 | 36 | aprsIsMsg->setPath(path + "qAO," + system.getUserConfig()->callsign); 37 | 38 | system.getLogger().log(logging::LoggerLevel::LOGGER_LEVEL_INFO, getName(), "APRS-IS: %s", aprsIsMsg->toString().c_str()); 39 | _toAprsIs.addElement(aprsIsMsg); 40 | } else { 41 | system.getLogger().log(logging::LoggerLevel::LOGGER_LEVEL_INFO, getName(), "APRS-IS: no forward => RFonly"); 42 | } 43 | } else { 44 | if (!system.getUserConfig()->aprs_is.active) { 45 | system.getLogger().log(logging::LoggerLevel::LOGGER_LEVEL_INFO, getName(), "APRS-IS: disabled"); 46 | } 47 | 48 | if (modemMsg->getSource() == system.getUserConfig()->callsign) { 49 | system.getLogger().log(logging::LoggerLevel::LOGGER_LEVEL_INFO, getName(), "APRS-IS: no forward => own packet received"); 50 | } 51 | } 52 | 53 | if (system.getUserConfig()->digi.active && modemMsg->getSource() != system.getUserConfig()->callsign) { 54 | std::shared_ptr digiMsg = std::make_shared(*modemMsg); 55 | String path = digiMsg->getPath(); 56 | 57 | // simple loop check 58 | if (path.indexOf("WIDE1-1") >= 0 && path.indexOf(system.getUserConfig()->callsign) == -1) { 59 | // fixme 60 | digiMsg->setPath(system.getUserConfig()->callsign + "*"); 61 | 62 | system.getLogger().log(logging::LoggerLevel::LOGGER_LEVEL_INFO, getName(), "DIGI: %s", digiMsg->toString().c_str()); 63 | 64 | _toModem.addElement(digiMsg); 65 | } 66 | } 67 | } 68 | 69 | _stateInfo = "Router done "; 70 | 71 | return true; 72 | } 73 | -------------------------------------------------------------------------------- /lib/Display/FontConfig.h: -------------------------------------------------------------------------------- 1 | #ifndef FONT_CONFIG_H_ 2 | #define FONT_CONFIG_H_ 3 | 4 | // make sure, the font is uncommented in fontConfig.c 5 | // 6 | // you do not need to worry about having too many fonts 7 | // activated in fontConfig.c 8 | // all fonts not referenced will be discarded by the linker 9 | // 10 | 11 | // Note: since all of the fonts have been generated from TTF fonts 12 | // we cannot guarantee a consistent character height. We get what we 13 | // get when requesting a font height of 12 or 20. Some converted 14 | // fonts are visually larger then others. 15 | // You are invited to fix this by try and error in the conversion process. 16 | // The idea is that 12 denotes the intended character height 17 | // including "subscripts" (as in g, y, ...) 18 | // Same for 20. 19 | // Right now, 12 is some sort of small font, 20 is some sort of large font 20 | // if this is not useful to you, then by all means convert your own fonts. 21 | // 22 | // Been there, done that. It is a pain in the ass. Good luck! (Seriously) 23 | 24 | // These fonts are good text fonts 25 | // #define FONT Blue_12_Desc // elegant 26 | // #define FONT Courier_12_Desc // der Klassiker, der immer geht 27 | // #define FONT FixedSys_11_Desc // good allround font 28 | // #define FONT Gabriola_12_Desc // schön, aber nicht besonders nützlich 29 | // #define FONT Gabriola_20_Desc 30 | //#define FONT HoloLens_12_Desc // kleine Schrift, gut zu lesen, elegant 31 | //#define FONT HoloLens_20_Desc 32 | // #define FONT Neuropol_12_Desc // modern, breite Buchstaben 33 | //#define FONT Roboto_12_Desc // almost like Terminal, little bit more elegant 34 | // #define FONT Open_12_Desc // kleine Schrift, gut lesbar, klassisch 35 | // #define FONT Tahoma_10_Desc // gute allround Schrift, was fuers Grobe 36 | #define FONT Terminal_8_Desc // if you need lots of space, still readable 37 | //#define FONT Terminal_11_Desc // good allround font 38 | // #define FONT Times_12_Desc // dazu braucht man nichts sagen, wirkt altbacken 39 | // #define FONT Trebuchet_12_Desc // gute Schrift, wenn es etwas bold sein darf 40 | // #define FONT Verdana_12_Desc // ausgewogen, gut lesbar, bolder als Trebuchet 41 | 42 | // These fonts are fancy fonts 43 | // #define FONT Amadeus_12_Desc 44 | // #define FONT Burnstown_12_Desc // rustikale Bretterschrift 45 | // #define FONT Cowboys_10_Desc // ratatatata Bonanzaaaaa 46 | // #define FONT Mickey_12_Desc // Nomen est omen in den Grossbuchstaben 47 | // #define FONT Script_12_Desc // Schreibschrift 48 | // #define FONT Seaside_12_Desc // Art D'eco 49 | // #define FONT Waker_12_Desc // was fuer zb Halloween 50 | 51 | #define FONT_CHAR_SPACING 2 52 | 53 | #include "Fonts/FontDesc.h" 54 | 55 | fontDesc_t const * getSystemFont(); 56 | 57 | #endif 58 | -------------------------------------------------------------------------------- /lib/NTPClient/NTPClient.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | #define SEVENZYYEARS 2208988800UL 7 | #define NTP_PACKET_SIZE 48 8 | #define NTP_DEFAULT_LOCAL_PORT 1337 9 | 10 | class NTPClient { 11 | private: 12 | WiFiUDP _udp; 13 | bool _udpSetup = false; 14 | 15 | const char* _poolServerName = "pool.ntp.org"; // Default time server 16 | IPAddress _poolServerIP; 17 | unsigned int _port = NTP_DEFAULT_LOCAL_PORT; 18 | long _timeOffset = 0; 19 | 20 | unsigned long _updateInterval = 3600000; // In ms 21 | 22 | unsigned long _currentEpoc = 0; // In s 23 | unsigned long _lastUpdate = 0; // In ms 24 | 25 | byte _packetBuffer[NTP_PACKET_SIZE]; 26 | 27 | void sendNTPPacket(); 28 | 29 | public: 30 | NTPClient(); 31 | explicit NTPClient(long timeOffset); 32 | explicit NTPClient(const char* poolServerName); 33 | NTPClient(const char* poolServerName, long timeOffset); 34 | NTPClient(const char* poolServerName, long timeOffset, unsigned long updateInterval); 35 | explicit NTPClient(IPAddress poolServerIP); 36 | NTPClient(IPAddress poolServerIP, long timeOffset); 37 | NTPClient(IPAddress poolServerIP, long timeOffset, unsigned long updateInterval); 38 | 39 | /** 40 | * Set time server name 41 | * 42 | * @param poolServerName 43 | */ 44 | void setPoolServerName(const char* poolServerName); 45 | 46 | /** 47 | * Set random local port 48 | */ 49 | void setRandomPort(unsigned int minValue = 49152, unsigned int maxValue = 65535); 50 | 51 | /** 52 | * Starts the underlying UDP client with the default local port 53 | */ 54 | void begin(); 55 | 56 | /** 57 | * Starts the underlying UDP client with the specified local port 58 | */ 59 | void begin(unsigned int port); 60 | 61 | /** 62 | * This should be called in the main loop of your application. By default an update from the NTP Server is only 63 | * made every 60 seconds. This can be configured in the NTPClient constructor. 64 | * 65 | * @return true on success, false on failure 66 | */ 67 | bool update(); 68 | 69 | /** 70 | * This will force the update from the NTP Server. 71 | * 72 | * @return true on success, false on failure 73 | */ 74 | bool forceUpdate(); 75 | 76 | int getDay() const; 77 | int getHours() const; 78 | int getMinutes() const; 79 | int getSeconds() const; 80 | 81 | /** 82 | * Changes the time offset. Useful for changing timezones dynamically 83 | */ 84 | void setTimeOffset(int timeOffset); 85 | 86 | /** 87 | * Set the update interval to another frequency. E.g. useful when the 88 | * timeOffset should not be set in the constructor 89 | */ 90 | void setUpdateInterval(unsigned long updateInterval); 91 | 92 | /** 93 | * @return time formatted like `hh:mm:ss` 94 | */ 95 | String getFormattedTime() const; 96 | 97 | /** 98 | * @return time in seconds since Jan. 1, 1970 99 | */ 100 | unsigned long getEpochTime() const; 101 | 102 | /** 103 | * Stops the underlying UDP client 104 | */ 105 | void end(); 106 | }; 107 | -------------------------------------------------------------------------------- /lib/Display/Display.cpp: -------------------------------------------------------------------------------- 1 | #include "Display.h" 2 | #include 3 | #include 4 | 5 | Display::Display() : _disp(0), _statusFrame(0), _displaySaveMode(false) { 6 | } 7 | 8 | Display::~Display() { 9 | } 10 | 11 | void Display::setup(BoardConfig const *const boardConfig) { 12 | if (boardConfig->OledReset != 0) { 13 | pinMode(boardConfig->OledReset, OUTPUT); 14 | digitalWrite(boardConfig->OledReset, HIGH); 15 | delay(1); 16 | digitalWrite(boardConfig->OledReset, LOW); 17 | delay(10); 18 | digitalWrite(boardConfig->OledReset, HIGH); 19 | } 20 | Wire.begin(boardConfig->OledSda, boardConfig->OledScl); 21 | _disp = new SSD1306(&Wire, boardConfig->OledAddr); 22 | 23 | Bitmap bitmap(_disp->getWidth(), _disp->getHeight()); 24 | _disp->display(&bitmap); 25 | 26 | _displayFrameRate.setTimeout(500); 27 | _displayFrameRate.start(); 28 | 29 | _frameTimeout.setTimeout(15 * 1000); 30 | _displaySaveModeTimer.setTimeout(10 * 1000); 31 | } 32 | 33 | void Display::turn180() { 34 | _disp->flipScreenVertically(); 35 | } 36 | 37 | void Display::activateDisplaySaveMode() { 38 | _displaySaveMode = true; 39 | } 40 | 41 | void Display::setDisplaySaveTimeout(uint32_t timeout) { 42 | _displaySaveModeTimer.setTimeout(timeout * 1000); 43 | } 44 | 45 | void Display::activateDistplay() 46 | { 47 | _disp->displayOn(); 48 | } 49 | 50 | void Display::update() { 51 | if (_displayFrameRate.check()) { 52 | 53 | if (_frames.size() > 0) { 54 | std::shared_ptr frame = *_frames.begin(); 55 | Bitmap bitmap(_disp); 56 | frame->drawStatusPage(bitmap); 57 | _disp->display(&bitmap); 58 | 59 | if (!_frameTimeout.isActive()) { 60 | _frameTimeout.start(); 61 | _displaySaveModeTimer.reset(); 62 | } else if (_frameTimeout.check()) { 63 | _frames.pop_front(); 64 | _frameTimeout.reset(); 65 | } 66 | } else { 67 | if (_disp->isDisplayOn()) { 68 | Bitmap bitmap(_disp); 69 | _statusFrame->drawStatusPage(bitmap); 70 | _disp->display(&bitmap); 71 | 72 | if (_displaySaveMode) { 73 | if (_displaySaveModeTimer.isActive() && _displaySaveModeTimer.check()) { 74 | _disp->displayOff(); 75 | _displaySaveModeTimer.reset(); 76 | } else if (!_displaySaveModeTimer.isActive()) { 77 | _displaySaveModeTimer.start(); 78 | } 79 | } 80 | } 81 | } 82 | 83 | _displayFrameRate.start(); 84 | } 85 | } 86 | 87 | void Display::addFrame(std::shared_ptr frame) { 88 | _frames.push_back(frame); 89 | } 90 | 91 | void Display::setStatusFrame(std::shared_ptr frame) { 92 | _statusFrame = frame; 93 | } 94 | 95 | void Display::showSpashScreen(String firmwareTitle, String version) { 96 | Bitmap bitmap(_disp); 97 | bitmap.drawString(0, 10, firmwareTitle); 98 | bitmap.drawString(0, 20, version); 99 | bitmap.drawString(0, 35, "by Peter Buchegger"); 100 | bitmap.drawString(30, 45, "OE5BPA"); 101 | _disp->display(&bitmap); 102 | } 103 | 104 | void Display::showStatusScreen(String header, String text) { 105 | Bitmap bitmap(_disp); 106 | bitmap.drawString(0, 0, header); 107 | bitmap.drawStringLF(0, 10, text); 108 | _disp->display(&bitmap); 109 | } 110 | 111 | void TextFrame::drawStatusPage(Bitmap &bitmap) { 112 | bitmap.drawString(0, 0, _header); 113 | bitmap.drawStringLF(0, 10, _text); 114 | } 115 | -------------------------------------------------------------------------------- /src/TaskBeacon.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | #include 5 | 6 | #include "Task.h" 7 | #include "TaskBeacon.h" 8 | #include "project_configuration.h" 9 | 10 | BeaconTask::BeaconTask(TaskQueue> &toModem, TaskQueue> &toAprsIs) : Task(TASK_BEACON, TaskBeacon), _toModem(toModem), _toAprsIs(toAprsIs), _ss(1), _useGps(false) { 11 | } 12 | 13 | BeaconTask::~BeaconTask() { 14 | } 15 | 16 | OneButton BeaconTask::_userButton; 17 | bool BeaconTask::_send_update; 18 | uint BeaconTask::_instances; 19 | 20 | void BeaconTask::pushButton() { 21 | _send_update = true; 22 | } 23 | 24 | bool BeaconTask::setup(System &system) { 25 | if (_instances++ == 0 && system.getBoardConfig()->Button > 0) { 26 | _userButton = OneButton(system.getBoardConfig()->Button, true, true); 27 | _userButton.attachClick(pushButton); 28 | _send_update = false; 29 | } 30 | 31 | _useGps = system.getUserConfig()->beacon.use_gps; 32 | 33 | if (_useGps) { 34 | if (system.getBoardConfig()->GpsRx != 0) { 35 | _ss.begin(9600, SERIAL_8N1, system.getBoardConfig()->GpsTx, system.getBoardConfig()->GpsRx); 36 | } else { 37 | system.getLogger().log(logging::LoggerLevel::LOGGER_LEVEL_INFO, getName(), "NO GPS found."); 38 | _useGps = false; 39 | } 40 | } 41 | // setup beacon 42 | _beacon_timer.setTimeout(system.getUserConfig()->beacon.timeout * 60 * 1000); 43 | 44 | _beaconMsg = std::shared_ptr(new APRSMessage()); 45 | _beaconMsg->setSource(system.getUserConfig()->callsign); 46 | _beaconMsg->setDestination("APLG01"); 47 | 48 | return true; 49 | } 50 | 51 | bool BeaconTask::loop(System &system) { 52 | if (_useGps) { 53 | while (_ss.available() > 0) { 54 | char c = _ss.read(); 55 | _gps.encode(c); 56 | } 57 | } 58 | 59 | _userButton.tick(); 60 | 61 | // check for beacon 62 | if (_beacon_timer.check() || _send_update) { 63 | if (sendBeacon(system)) { 64 | _send_update = false; 65 | _beacon_timer.start(); 66 | } 67 | } 68 | 69 | uint32_t diff = _beacon_timer.getTriggerTimeInSec(); 70 | _stateInfo = "beacon " + String(uint32_t(diff / 600)) + String(uint32_t(diff / 60) % 10) + ":" + String(uint32_t(diff / 10) % 6) + String(uint32_t(diff % 10)); 71 | 72 | return true; 73 | } 74 | 75 | String create_lat_aprs(double lat) { 76 | char str[20]; 77 | char n_s = 'N'; 78 | if (lat < 0) { 79 | n_s = 'S'; 80 | } 81 | lat = std::abs(lat); 82 | sprintf(str, "%02d%05.2f%c", (int)lat, (lat - (double)((int)lat)) * 60.0, n_s); 83 | String lat_str(str); 84 | return lat_str; 85 | } 86 | 87 | String create_long_aprs(double lng) { 88 | char str[20]; 89 | char e_w = 'E'; 90 | if (lng < 0) { 91 | e_w = 'W'; 92 | } 93 | lng = std::abs(lng); 94 | sprintf(str, "%03d%05.2f%c", (int)lng, (lng - (double)((int)lng)) * 60.0, e_w); 95 | String lng_str(str); 96 | return lng_str; 97 | } 98 | 99 | bool BeaconTask::sendBeacon(System &system) { 100 | double lat = system.getUserConfig()->beacon.positionLatitude; 101 | double lng = system.getUserConfig()->beacon.positionLongitude; 102 | 103 | if (_useGps) { 104 | if (_gps.location.isUpdated()) { 105 | lat = _gps.location.lat(); 106 | lng = _gps.location.lng(); 107 | } else { 108 | return false; 109 | } 110 | } 111 | _beaconMsg->getBody()->setData(String("=") + create_lat_aprs(lat) + "L" + create_long_aprs(lng) + "&" + system.getUserConfig()->beacon.message); 112 | 113 | system.getLogger().log(logging::LoggerLevel::LOGGER_LEVEL_INFO, getName(), "[%s] %s", timeString().c_str(), _beaconMsg->encode().c_str()); 114 | 115 | if (system.getUserConfig()->aprs_is.active) { 116 | _toAprsIs.addElement(_beaconMsg); 117 | } 118 | 119 | if (system.getUserConfig()->digi.beacon) { 120 | _toModem.addElement(_beaconMsg); 121 | } 122 | 123 | system.getDisplay().addFrame(std::shared_ptr(new TextFrame("BEACON", _beaconMsg->toString()))); 124 | 125 | return true; 126 | } 127 | -------------------------------------------------------------------------------- /src/project_configuration.h: -------------------------------------------------------------------------------- 1 | #ifndef PROJECT_CONFIGURATION_H_ 2 | #define PROJECT_CONFIGURATION_H_ 3 | 4 | #include 5 | #include 6 | 7 | class Configuration { 8 | public: 9 | class Static { 10 | public: 11 | IPAddress ip; 12 | IPAddress subnet; 13 | IPAddress gateway; 14 | IPAddress dns1; 15 | IPAddress dns2; 16 | }; 17 | 18 | class Hostname { 19 | public: 20 | bool overwrite; 21 | String name; 22 | }; 23 | 24 | class Network { 25 | public: 26 | Network() : DHCP(true) { 27 | } 28 | 29 | bool DHCP; 30 | Static static_; 31 | Hostname hostname; 32 | }; 33 | 34 | class Wifi { 35 | public: 36 | Wifi() : active(true) { 37 | } 38 | 39 | bool active; 40 | class AP { 41 | public: 42 | String SSID; 43 | String password; 44 | }; 45 | 46 | std::list APs; 47 | }; 48 | 49 | class Beacon { 50 | public: 51 | Beacon() : message("LoRa iGATE & Digi, Info: github.com/peterus/LoRa_APRS_iGate"), positionLatitude(0.0), positionLongitude(0.0), use_gps(false), timeout(15) { 52 | } 53 | 54 | String message; 55 | double positionLatitude; 56 | double positionLongitude; 57 | bool use_gps; 58 | int timeout; 59 | }; 60 | 61 | class APRS_IS { 62 | public: 63 | APRS_IS() : active(true), server("euro.aprs2.net"), port(14580) { 64 | } 65 | 66 | bool active; 67 | String passcode; 68 | String server; 69 | int port; 70 | }; 71 | 72 | class Digi { 73 | public: 74 | Digi() : active(false), beacon(true) { 75 | } 76 | 77 | bool active; 78 | bool beacon; 79 | }; 80 | 81 | class LoRa { 82 | public: 83 | LoRa() : frequencyRx(433775000), frequencyTx(433775000), power(20), spreadingFactor(12), signalBandwidth(125000), codingRate4(5), tx_enable(true) { 84 | } 85 | 86 | long frequencyRx; 87 | uint8_t gainRx; 88 | long frequencyTx; 89 | int power; 90 | int spreadingFactor; 91 | long signalBandwidth; 92 | int codingRate4; 93 | bool tx_enable; 94 | }; 95 | 96 | class Display { 97 | public: 98 | Display() : alwaysOn(true), timeout(10), overwritePin(0), turn180(true) { 99 | } 100 | 101 | bool alwaysOn; 102 | int timeout; 103 | int overwritePin; 104 | bool turn180; 105 | }; 106 | 107 | class Ftp { 108 | public: 109 | class User { 110 | public: 111 | String name; 112 | String password; 113 | }; 114 | 115 | Ftp() : active(false) { 116 | } 117 | 118 | bool active; 119 | std::list users; 120 | }; 121 | 122 | class MQTT { 123 | public: 124 | MQTT() : active(false), server(""), port(1883), name(""), password(""), topic("LoraAPRS/Data") { 125 | } 126 | 127 | bool active; 128 | String server; 129 | int port; 130 | String name; 131 | String password; 132 | String topic; 133 | }; 134 | 135 | class Syslog { 136 | public: 137 | Syslog() : active(true), server("syslog.lora-aprs.info"), port(514) { 138 | } 139 | 140 | bool active; 141 | String server; 142 | int port; 143 | }; 144 | 145 | Configuration() : callsign("NOCALL-10"), ntpServer("pool.ntp.org"), board("") { 146 | } 147 | 148 | String callsign; 149 | Network network; 150 | Wifi wifi; 151 | Beacon beacon; 152 | APRS_IS aprs_is; 153 | Digi digi; 154 | LoRa lora; 155 | Display display; 156 | Ftp ftp; 157 | MQTT mqtt; 158 | Syslog syslog; 159 | String ntpServer; 160 | String board; 161 | }; 162 | 163 | class ProjectConfigurationManagement : public ConfigurationManagement { 164 | public: 165 | explicit ProjectConfigurationManagement(logging::Logger &logger) : ConfigurationManagement(logger, "/is-cfg.json") { 166 | } 167 | virtual ~ProjectConfigurationManagement() { 168 | } 169 | 170 | private: 171 | virtual void readProjectConfiguration(DynamicJsonDocument &data, Configuration &conf) override; 172 | virtual void writeProjectConfiguration(Configuration &conf, DynamicJsonDocument &data) override; 173 | }; 174 | 175 | #endif 176 | -------------------------------------------------------------------------------- /lib/Display/OLEDDisplay.h: -------------------------------------------------------------------------------- 1 | /** 2 | * The MIT License (MIT) 3 | * 4 | * Copyright (c) 2018 by ThingPulse, Daniel Eichhorn 5 | * Copyright (c) 2018 by Fabrice Weinberg 6 | * Copyright (c) 2019 by Helmut Tschemernjak - www.radioshuttle.de 7 | * 8 | * Permission is hereby granted, free of charge, to any person obtaining a copy 9 | * of this software and associated documentation files (the "Software"), to deal 10 | * in the Software without restriction, including without limitation the rights 11 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 12 | * copies of the Software, and to permit persons to whom the Software is 13 | * furnished to do so, subject to the following conditions: 14 | * 15 | * The above copyright notice and this permission notice shall be included in all 16 | * copies or substantial portions of the Software. 17 | * 18 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 23 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 24 | * SOFTWARE. 25 | * 26 | * ThingPulse invests considerable time and money to develop these open source libraries. 27 | * Please support us by buying our products (and not the clones) from 28 | * https://thingpulse.com 29 | * 30 | */ 31 | 32 | #ifndef OLEDDISPLAY_h 33 | #define OLEDDISPLAY_h 34 | 35 | #include "Bitmap.h" 36 | #include 37 | //#include "OLEDDisplayFonts.h" 38 | 39 | // Display commands 40 | #define CHARGEPUMP 0x8D 41 | #define COLUMNADDR 0x21 42 | #define COMSCANDEC 0xC8 43 | #define COMSCANINC 0xC0 44 | #define DISPLAYALLON 0xA5 45 | #define DISPLAYALLON_RESUME 0xA4 46 | #define DISPLAYOFF 0xAE 47 | #define DISPLAYON 0xAF 48 | #define EXTERNALVCC 0x1 49 | #define INVERTDISPLAY 0xA7 50 | #define MEMORYMODE 0x20 51 | #define NORMALDISPLAY 0xA6 52 | #define PAGEADDR 0x22 53 | #define SEGREMAP 0xA0 54 | #define SETCOMPINS 0xDA 55 | #define SETCONTRAST 0x81 56 | #define SETDISPLAYCLOCKDIV 0xD5 57 | #define SETDISPLAYOFFSET 0xD3 58 | #define SETHIGHCOLUMN 0x10 59 | #define SETLOWCOLUMN 0x00 60 | #define SETMULTIPLEX 0xA8 61 | #define SETPRECHARGE 0xD9 62 | #define SETSEGMENTREMAP 0xA1 63 | #define SETSTARTLINE 0x40 64 | #define SETVCOMDETECT 0xDB 65 | #define SWITCHCAPVCC 0x2 66 | 67 | enum OLEDDISPLAY_GEOMETRY 68 | { 69 | GEOMETRY_128_64 = 0, 70 | GEOMETRY_128_32 = 1, 71 | GEOMETRY_64_48 = 2, 72 | GEOMETRY_64_32 = 3 73 | }; 74 | 75 | class OLEDDisplay { 76 | public: 77 | OLEDDisplay(OLEDDISPLAY_GEOMETRY g = GEOMETRY_128_64); 78 | virtual ~OLEDDisplay(); 79 | 80 | // Turn the display on 81 | void displayOn(); 82 | 83 | // Is the Display on? 84 | bool isDisplayOn() const; 85 | 86 | // Turn the display offs 87 | void displayOff(); 88 | 89 | // Is the Display off? 90 | bool isDisplayOff() const; 91 | 92 | // Inverted display mode 93 | void invertDisplay(); 94 | 95 | // Normal display mode 96 | void normalDisplay(); 97 | 98 | // Set display contrast 99 | // really low brightness & contrast: contrast = 10, precharge = 5, comdetect = 0 100 | // normal brightness & contrast: contrast = 100 101 | void setContrast(uint8_t contrast, uint8_t precharge = 241, uint8_t comdetect = 64); 102 | 103 | // Convenience method to access 104 | void setBrightness(uint8_t brightness); 105 | 106 | // Reset display rotation or mirroring 107 | void resetOrientation(); 108 | 109 | // Turn the display upside down 110 | void flipScreenVertically(); 111 | 112 | // Mirror the display (to be used in a mirror or as a projector) 113 | void mirrorScreen(); 114 | 115 | // Write the buffer to the display memory 116 | void display(Bitmap *bitmap); 117 | 118 | // Clear the local pixel buffer 119 | void clear(); 120 | 121 | // Get screen geometry 122 | uint getWidth(); 123 | uint getHeight(); 124 | 125 | protected: 126 | // Send all the init commands 127 | void sendInitCommands(); 128 | 129 | private: 130 | OLEDDISPLAY_GEOMETRY _geometry; 131 | 132 | // Send a command to the display (low level function) 133 | virtual void sendCommand(uint8_t com) = 0; 134 | 135 | virtual void internDisplay(Bitmap *bitmap) = 0; 136 | 137 | bool _displayIsOn; 138 | }; 139 | 140 | #endif 141 | -------------------------------------------------------------------------------- /.clang-format: -------------------------------------------------------------------------------- 1 | --- 2 | Language: Cpp 3 | # BasedOnStyle: LLVM 4 | AccessModifierOffset: -2 5 | AlignAfterOpenBracket: Align 6 | AlignConsecutiveMacros: true 7 | AlignConsecutiveAssignments: true 8 | AlignConsecutiveBitFields: true 9 | AlignConsecutiveDeclarations: true 10 | AlignEscapedNewlines: Left 11 | AlignOperands: Align 12 | AlignTrailingComments: true 13 | AllowAllArgumentsOnNextLine: true 14 | AllowAllConstructorInitializersOnNextLine: true 15 | AllowAllParametersOfDeclarationOnNextLine: true 16 | AllowShortEnumsOnASingleLine: false 17 | AllowShortBlocksOnASingleLine: Never 18 | AllowShortCaseLabelsOnASingleLine: false 19 | AllowShortFunctionsOnASingleLine: None 20 | AllowShortLambdasOnASingleLine: None 21 | AllowShortIfStatementsOnASingleLine: Never 22 | AllowShortLoopsOnASingleLine: false 23 | AlwaysBreakAfterDefinitionReturnType: None 24 | AlwaysBreakAfterReturnType: None 25 | AlwaysBreakBeforeMultilineStrings: false 26 | AlwaysBreakTemplateDeclarations: MultiLine 27 | BinPackArguments: true 28 | BinPackParameters: true 29 | BraceWrapping: 30 | AfterCaseLabel: false 31 | AfterClass: false 32 | AfterControlStatement: Never 33 | AfterEnum: false 34 | AfterFunction: false 35 | AfterNamespace: false 36 | AfterObjCDeclaration: false 37 | AfterStruct: false 38 | AfterUnion: false 39 | AfterExternBlock: false 40 | BeforeCatch: false 41 | BeforeElse: false 42 | BeforeLambdaBody: false 43 | BeforeWhile: false 44 | IndentBraces: false 45 | SplitEmptyFunction: true 46 | SplitEmptyRecord: true 47 | SplitEmptyNamespace: true 48 | BreakBeforeBinaryOperators: None 49 | BreakBeforeBraces: Attach 50 | BreakBeforeInheritanceComma: false 51 | BreakInheritanceList: BeforeColon 52 | BreakBeforeTernaryOperators: true 53 | BreakConstructorInitializersBeforeComma: false 54 | BreakConstructorInitializers: BeforeColon 55 | BreakAfterJavaFieldAnnotations: false 56 | BreakStringLiterals: true 57 | ColumnLimit: 500 58 | CommentPragmas: '^ IWYU pragma:' 59 | CompactNamespaces: false 60 | ConstructorInitializerAllOnOneLineOrOnePerLine: false 61 | ConstructorInitializerIndentWidth: 4 62 | ContinuationIndentWidth: 4 63 | Cpp11BracedListStyle: true 64 | DeriveLineEnding: true 65 | DerivePointerAlignment: false 66 | DisableFormat: false 67 | ExperimentalAutoDetectBinPacking: false 68 | FixNamespaceComments: true 69 | ForEachMacros: 70 | - foreach 71 | - Q_FOREACH 72 | - BOOST_FOREACH 73 | IncludeBlocks: Preserve 74 | IncludeCategories: 75 | - Regex: '^"(llvm|llvm-c|clang|clang-c)/' 76 | Priority: 2 77 | SortPriority: 0 78 | - Regex: '^(<|"(gtest|gmock|isl|json)/)' 79 | Priority: 3 80 | SortPriority: 0 81 | - Regex: '.*' 82 | Priority: 1 83 | SortPriority: 0 84 | IncludeIsMainRegex: '(Test)?$' 85 | IncludeIsMainSourceRegex: '' 86 | IndentCaseLabels: false 87 | IndentCaseBlocks: false 88 | IndentGotoLabels: true 89 | IndentPPDirectives: None 90 | IndentExternBlock: AfterExternBlock 91 | IndentWidth: 2 92 | IndentWrappedFunctionNames: false 93 | InsertTrailingCommas: None 94 | JavaScriptQuotes: Leave 95 | JavaScriptWrapImports: true 96 | KeepEmptyLinesAtTheStartOfBlocks: true 97 | MacroBlockBegin: '' 98 | MacroBlockEnd: '' 99 | MaxEmptyLinesToKeep: 1 100 | NamespaceIndentation: None 101 | ObjCBinPackProtocolList: Auto 102 | ObjCBlockIndentWidth: 2 103 | ObjCBreakBeforeNestedBlockParam: true 104 | ObjCSpaceAfterProperty: false 105 | ObjCSpaceBeforeProtocolList: true 106 | PenaltyBreakAssignment: 2 107 | PenaltyBreakBeforeFirstCallParameter: 19 108 | PenaltyBreakComment: 300 109 | PenaltyBreakFirstLessLess: 120 110 | PenaltyBreakString: 1000 111 | PenaltyBreakTemplateDeclaration: 10 112 | PenaltyExcessCharacter: 1000000 113 | PenaltyReturnTypeOnItsOwnLine: 60 114 | PointerAlignment: Right 115 | ReflowComments: true 116 | SortIncludes: true 117 | SortUsingDeclarations: true 118 | SpaceAfterCStyleCast: false 119 | SpaceAfterLogicalNot: false 120 | SpaceAfterTemplateKeyword: true 121 | SpaceBeforeAssignmentOperators: true 122 | SpaceBeforeCpp11BracedList: false 123 | SpaceBeforeCtorInitializerColon: true 124 | SpaceBeforeInheritanceColon: true 125 | SpaceBeforeParens: ControlStatements 126 | SpaceBeforeRangeBasedForLoopColon: true 127 | SpaceInEmptyBlock: false 128 | SpaceInEmptyParentheses: false 129 | SpacesBeforeTrailingComments: 1 130 | SpacesInAngles: false 131 | SpacesInConditionalStatement: false 132 | SpacesInContainerLiterals: true 133 | SpacesInCStyleCastParentheses: false 134 | SpacesInParentheses: false 135 | SpacesInSquareBrackets: false 136 | SpaceBeforeSquareBrackets: false 137 | Standard: Latest 138 | StatementMacros: 139 | - Q_UNUSED 140 | - QT_REQUIRE_VERSION 141 | TabWidth: 8 142 | UseCRLF: false 143 | UseTab: Never 144 | WhitespaceSensitiveMacros: 145 | - STRINGIZE 146 | - PP_STRINGIZE 147 | - BOOST_PP_STRINGIZE 148 | ... 149 | 150 | -------------------------------------------------------------------------------- /src/TaskEth.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #include "Task.h" 6 | #include "TaskEth.h" 7 | #include "project_configuration.h" 8 | 9 | #define WIFI_EVENT "WiFiEvent" 10 | 11 | volatile bool eth_connected = false; 12 | logging::Logger *_logger; 13 | 14 | void setWiFiLogger(logging::Logger *logger) { 15 | _logger = logger; 16 | } 17 | 18 | void WiFiEvent(WiFiEvent_t event) { 19 | switch (event) { 20 | case SYSTEM_EVENT_STA_START: 21 | _logger->log(logging::LoggerLevel::LOGGER_LEVEL_INFO, WIFI_EVENT, "WiFi Started"); 22 | break; 23 | case SYSTEM_EVENT_STA_CONNECTED: 24 | _logger->log(logging::LoggerLevel::LOGGER_LEVEL_INFO, WIFI_EVENT, "WiFi Connected"); 25 | break; 26 | case SYSTEM_EVENT_STA_GOT_IP: 27 | _logger->log(logging::LoggerLevel::LOGGER_LEVEL_INFO, WIFI_EVENT, "WiFi MAC: %s", WiFi.macAddress().c_str()); 28 | _logger->log(logging::LoggerLevel::LOGGER_LEVEL_INFO, WIFI_EVENT, "WiFi IPv4: %s", WiFi.localIP().toString().c_str()); 29 | _logger->log(logging::LoggerLevel::LOGGER_LEVEL_INFO, WIFI_EVENT, "WiFi Gateway: %s", WiFi.gatewayIP().toString().c_str()); 30 | _logger->log(logging::LoggerLevel::LOGGER_LEVEL_INFO, WIFI_EVENT, "WiFi DNS1: %s", WiFi.dnsIP().toString().c_str()); 31 | _logger->log(logging::LoggerLevel::LOGGER_LEVEL_INFO, WIFI_EVENT, "WiFi DNS2: %s", WiFi.dnsIP(1).toString().c_str()); 32 | _logger->log(logging::LoggerLevel::LOGGER_LEVEL_INFO, WIFI_EVENT, "WiFi Hostname: %s", WiFi.getHostname()); 33 | break; 34 | case SYSTEM_EVENT_STA_DISCONNECTED: 35 | _logger->log(logging::LoggerLevel::LOGGER_LEVEL_INFO, WIFI_EVENT, "WiFi Disconnected"); 36 | break; 37 | case SYSTEM_EVENT_STA_STOP: 38 | _logger->log(logging::LoggerLevel::LOGGER_LEVEL_INFO, WIFI_EVENT, "WiFi Stopped"); 39 | break; 40 | case SYSTEM_EVENT_ETH_START: 41 | _logger->log(logging::LoggerLevel::LOGGER_LEVEL_INFO, WIFI_EVENT, "ETH Started"); 42 | break; 43 | case SYSTEM_EVENT_ETH_CONNECTED: 44 | _logger->log(logging::LoggerLevel::LOGGER_LEVEL_INFO, WIFI_EVENT, "ETH Connected"); 45 | break; 46 | case SYSTEM_EVENT_ETH_GOT_IP: 47 | _logger->log(logging::LoggerLevel::LOGGER_LEVEL_INFO, WIFI_EVENT, "ETH MAC: %s", ETH.macAddress().c_str()); 48 | _logger->log(logging::LoggerLevel::LOGGER_LEVEL_INFO, WIFI_EVENT, "ETH IPv4: %s", ETH.localIP().toString().c_str()); 49 | _logger->log(logging::LoggerLevel::LOGGER_LEVEL_INFO, WIFI_EVENT, "ETH Gateway: %s", ETH.gatewayIP().toString().c_str()); 50 | _logger->log(logging::LoggerLevel::LOGGER_LEVEL_INFO, WIFI_EVENT, "ETH DNS1: %s", ETH.dnsIP().toString().c_str()); 51 | _logger->log(logging::LoggerLevel::LOGGER_LEVEL_INFO, WIFI_EVENT, "ETH DNS2: %s", ETH.dnsIP(1).toString().c_str()); 52 | _logger->log(logging::LoggerLevel::LOGGER_LEVEL_INFO, WIFI_EVENT, "ETH Hostname: %s", ETH.getHostname()); 53 | if (ETH.fullDuplex()) { 54 | _logger->log(logging::LoggerLevel::LOGGER_LEVEL_INFO, WIFI_EVENT, "ETH FULL_DUPLEX"); 55 | } 56 | _logger->log(logging::LoggerLevel::LOGGER_LEVEL_INFO, WIFI_EVENT, "ETH Speed: %dMbps", ETH.linkSpeed()); 57 | eth_connected = true; 58 | break; 59 | case SYSTEM_EVENT_ETH_DISCONNECTED: 60 | _logger->log(logging::LoggerLevel::LOGGER_LEVEL_WARN, WIFI_EVENT, "ETH Disconnected"); 61 | eth_connected = false; 62 | break; 63 | case SYSTEM_EVENT_ETH_STOP: 64 | _logger->log(logging::LoggerLevel::LOGGER_LEVEL_WARN, WIFI_EVENT, "ETH Stopped"); 65 | eth_connected = false; 66 | break; 67 | default: 68 | break; 69 | } 70 | } 71 | 72 | EthTask::EthTask() : Task(TASK_ETH, TaskEth) { 73 | } 74 | 75 | EthTask::~EthTask() { 76 | } 77 | 78 | bool EthTask::setup(System &system) { 79 | WiFi.onEvent(WiFiEvent); 80 | 81 | constexpr uint8_t ETH_NRST = 5; 82 | constexpr uint8_t ETH_ADDR = 0; 83 | constexpr int ETH_POWER_PIN = -1; 84 | constexpr int ETH_MDC_PIN = 23; 85 | constexpr int ETH_MDIO_PIN = 18; 86 | constexpr eth_phy_type_t ETH_TYPE = ETH_PHY_LAN8720; 87 | constexpr eth_clock_mode_t ETH_CLK = ETH_CLOCK_GPIO17_OUT; // TTGO PoE V1.0 88 | // constexpr eth_clock_mode_t ETH_CLK = ETH_CLOCK_GPIO0_OUT; // TTGO PoE V1.2 89 | 90 | pinMode(ETH_NRST, OUTPUT); 91 | digitalWrite(ETH_NRST, 0); 92 | delay(200); 93 | digitalWrite(ETH_NRST, 1); 94 | delay(200); 95 | digitalWrite(ETH_NRST, 0); 96 | delay(200); 97 | digitalWrite(ETH_NRST, 1); 98 | 99 | ETH.begin(ETH_ADDR, ETH_POWER_PIN, ETH_MDC_PIN, ETH_MDIO_PIN, ETH_TYPE, ETH_CLK); 100 | 101 | if (!system.getUserConfig()->network.DHCP) { 102 | ETH.config(system.getUserConfig()->network.static_.ip, system.getUserConfig()->network.static_.gateway, system.getUserConfig()->network.static_.subnet, system.getUserConfig()->network.static_.dns1, system.getUserConfig()->network.static_.dns2); 103 | } 104 | if (system.getUserConfig()->network.hostname.overwrite) { 105 | ETH.setHostname(system.getUserConfig()->network.hostname.name.c_str()); 106 | } else { 107 | ETH.setHostname(system.getUserConfig()->callsign.c_str()); 108 | } 109 | 110 | return true; 111 | } 112 | 113 | bool EthTask::loop(System &system) { 114 | if (!eth_connected) { 115 | system.connectedViaEth(false); 116 | _stateInfo = "Ethernet not connected"; 117 | _state = Error; 118 | return false; 119 | } 120 | system.connectedViaEth(true); 121 | _stateInfo = ETH.localIP().toString(); 122 | _state = Okay; 123 | return true; 124 | } 125 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # LoRa APRS iGate 2 | 3 | ![Build check and build](https://github.com/lora-aprs/LoRa_APRS_iGate/workflows/Build%20check%20and%20build/badge.svg) 4 | [![Codacy Badge](https://api.codacy.com/project/badge/Grade/0b7452d5b3b747b88c736e253dda51e6)](https://app.codacy.com/gh/lora-aprs/LoRa_APRS_iGate?utm_source=github.com&utm_medium=referral&utm_content=lora-aprs/LoRa_APRS_iGate&utm_campaign=Badge_Grade_Dashboard) 5 | 6 | The LoRa APRS iGate will work with very cheep hardware which you can buy from amazon, ebay or aliexpress. 7 | Try it out and be part of the APRS network. 8 | 9 | ![TTGO LoRa32](pics/iGate.png) 10 | 11 | ## Blog posts and Youtube videos from other Hams 12 | 13 | * [Manuel Lausmann - iGate & Tracker](https://www.youtube.com/watch?v=-KdFQEaNC1k) (youtube - german) 04.06.2021 14 | * [Manuel Lausmann - Installationsanleitung als PDF](http://mala-one.de/Lora-APRS/) (PDF - german) 04.06.2021 15 | * [OE1ROT](https://www.aronaut.at/2020/11/lora-aprs-gateway-mit-esp32-boards/) (blog post - german) 14.11.2020 16 | * [DL7AG](http://online.dl7ag.de/lora-aprs-dl7ag-10/) (blog post - german) 08.11.2020 17 | * [Manuel Lausmann - iGate](https://www.youtube.com/watch?v=C7hfVe32pXs) (youtube - german - OLD) 06.11.2020 18 | * [Manuel Lausmann - Tracker](https://www.youtube.com/watch?v=clIlTEFbWLk) (youtube - german - OLD) 02.11.2020 19 | * [OE1ROT](https://www.aronaut.at/2019/12/lora-aprs-tracker-mit-ttgo-t-beam-433mhz/) (blog post - german) 09.12.2019 20 | 21 | Feel free to add yours or create a ticket if you want to be added. 22 | 23 | ## Supported boards 24 | 25 | You can use one of the Lora32 boards without changes: 26 | 27 | * Heltec WiFi LoRa 32 V1 (433MHz SX1278) 28 | * Heltec WiFi LoRa 32 V2 (433MHz SX1278) 29 | * TTGO LoRa32 V1 (433MHz SX1278) 30 | * TTGO LoRa32 V2 (433MHz SX1278) 31 | * TTGO LoRa32 V2.1 (433MHz SX1278) 32 | * TTGO T-Beam V0.7 (433MHz SX1278) 33 | * TTGO T-Beam V1 (433MHz SX1278) 34 | * Tracker D from [OE1ACM and OE1CGC](https://www.lora-aprs.at/) 35 | * and sure many more... 36 | 37 | Here are some amazon-de links for some example boards: 38 | * [LoRa32 V1](https://www.amazon.de/dp/B07VPHYYJD) 39 | * [LoRa32 V1](https://www.amazon.de/dp/B07QRG89ZV) 40 | * [LoRa32 V2](https://www.amazon.de/dp/B07VL97VNH) 41 | * [LoRa32 V2.1](https://www.amazon.de/dp/B07RXSKPBX) 42 | * [T-Beam V1.0](https://www.amazon.de/dp/B07RT9FKPL) 43 | 44 | This boards cost around 20 Euros, they are very cheap and perfect for an LoRa iGate. 45 | Keep in mind: you need a 433MHz version! 46 | 47 | ## Compiling and configuration 48 | 49 | **There is a german [quick start](https://www.lora-aprs.info/docs/LoRa_APRS_iGate/quick-start-guide/) page! Take a look ;)** 50 | 51 | **There is a french [quick start](http://www.f5kmy.fr/spip.php?article509) page! Take a look ;)** 52 | 53 | ### How to compile 54 | 55 | The best success is to use PlatformIO (and it is the only platform where I can support you). 56 | 57 | * Go to [PlatformIO](https://platformio.org/) download and install the IDE. 58 | * If installed open the IDE, go to the left side and klick on 'extensions' then search for 'PatformIO' and install. 59 | * When installed click 'the ant head' on the left and choose import the project on the right. 60 | * Just open the folder and you can compile the Firmware. 61 | 62 | ### Configuration 63 | 64 | * You can find all nessesary settings to change for your configuration in **data/is-cfg.json**. 65 | * To upload it to your board you have to do this via **Upload File System image** in PlatformIO! 66 | * To find the 'Upload File System image' click the PlatformIO symbol (the little alien) on the left side, choose your configuration, click on 'Platform' and search for 'Upload File System image'. 67 | 68 | ## Branches in this repository and version system 69 | 70 | This firmware is developed in a rolling release system: everyday a new release could be created. But there are still rules where new pull requests has to go and and how the version system looks like. 71 | 72 | ### Version system 73 | 74 | If the *develop* branch is stable enough for a new release it will be merged with a pull request to the *master* branch and a new version will be generated. 75 | 76 | The versions are based on this settings: 77 | * major: the current year (2 digits) 78 | * minor: the current week of the year 79 | * patch: if there is a important fix for an release, just increment the number, otherwise 0 80 | 81 | *example*: a new release will be created on the 11/14/2020, this version numbers will be used: 82 | * major: 20 83 | * minor: 46 84 | * patch: 0 85 | 86 | so the version will be: 20.46.0 87 | 88 | ## Future plans 89 | 90 | * [x] show time until next beaconing 91 | * [ ] show login issues from IS server 92 | * [ ] add better OLED library to support multiple different OLEDs 93 | * [x] add support to turn OLED on, off and dimming 94 | * [ ] add support for temperature chips (BMExxx) 95 | * [x] add FTP server support to upload configuration 96 | * [ ] add web server for configuration and other things 97 | * [ ] add statistics for received packages 98 | * [ ] show received packages on a map 99 | * [ ] etc. 100 | 101 | ## LoRa Tracker 102 | 103 | Look at my other project: a [LoRa Tracker](https://github.com/peterus/LoRa_APRS_Tracker) 104 | 105 | ## Hints 106 | 107 | ### Here are some peculiarities of the different boards 108 | 109 | * TTGO T-Beam V1.0 and V1.1 and SSD1306 OLED display 110 | 111 | When adding an SSD1306 0,96" OLED display direct to the board you have to be careful, there are two different pinout 112 | versions on the market. 113 | For direct mount you need a display with this Pinout -> [VCC - GND - SCL - SDA](pics/display-right.jpg). 114 | A direct mount of the [other display](pics/display-wrong.jpg) is not possible without damage the display! 115 | The 'wrong' display works too but you have to change VCC and GND by wire ! 116 | 117 | The [LoRa APRS WiKi Displays](https://github.com/lora-aprs/LoRa_APRS_Tracker/wiki/Displays) page has more details. 118 | 119 | Feel free to add hints! 120 | 121 | -------------------------------------------------------------------------------- /lib/TimeLib/TimeLib.h: -------------------------------------------------------------------------------- 1 | /* 2 | time.h - low level time and date functions 3 | */ 4 | 5 | /* 6 | July 3 2011 - fixed elapsedSecsThisWeek macro (thanks Vincent Valdy for this) 7 | - fixed daysToTime_t macro (thanks maniacbug) 8 | */ 9 | 10 | #ifndef _Time_h 11 | #define _Time_h 12 | 13 | #include 14 | #include 15 | 16 | typedef enum {timeNotSet, timeNeedsSync, timeSet 17 | } timeStatus_t ; 18 | 19 | typedef enum { 20 | dowInvalid, dowSunday, dowMonday, dowTuesday, dowWednesday, dowThursday, dowFriday, dowSaturday 21 | } timeDayOfWeek_t; 22 | 23 | typedef enum { 24 | tmSecond, tmMinute, tmHour, tmWday, tmDay,tmMonth, tmYear, tmNbrFields 25 | } tmByteFields; 26 | 27 | typedef struct { 28 | uint8_t Second; 29 | uint8_t Minute; 30 | uint8_t Hour; 31 | uint8_t Wday; // day of week, sunday is day 1 32 | uint8_t Day; 33 | uint8_t Month; 34 | uint8_t Year; // offset from 1970; 35 | } tmElements_t, TimeElements, *tmElementsPtr_t; 36 | 37 | //convenience macros to convert to and from tm years 38 | #define tmYearToCalendar(Y) ((Y) + 1970) // full four digit year 39 | #define CalendarYrToTm(Y) ((Y) - 1970) 40 | #define tmYearToY2k(Y) ((Y) - 30) // offset is from 2000 41 | #define y2kYearToTm(Y) ((Y) + 30) 42 | 43 | typedef time_t(*getExternalTime)(); 44 | //typedef void (*setExternalTime)(const time_t); // not used in this version 45 | 46 | 47 | /*==============================================================================*/ 48 | /* Useful Constants */ 49 | #define SECS_PER_MIN ((time_t)(60UL)) 50 | #define SECS_PER_HOUR ((time_t)(3600UL)) 51 | #define SECS_PER_DAY ((time_t)(SECS_PER_HOUR * 24UL)) 52 | #define DAYS_PER_WEEK ((time_t)(7UL)) 53 | #define SECS_PER_WEEK ((time_t)(SECS_PER_DAY * DAYS_PER_WEEK)) 54 | #define SECS_PER_YEAR ((time_t)(SECS_PER_DAY * 365UL)) // TODO: ought to handle leap years 55 | #define SECS_YR_2000 ((time_t)(946684800UL)) // the time at the start of y2k 56 | 57 | /* Useful Macros for getting elapsed time */ 58 | #define numberOfSeconds(_time_) ((_time_) % SECS_PER_MIN) 59 | #define numberOfMinutes(_time_) (((_time_) / SECS_PER_MIN) % SECS_PER_MIN) 60 | #define numberOfHours(_time_) (((_time_) % SECS_PER_DAY) / SECS_PER_HOUR) 61 | #define dayOfWeek(_time_) ((((_time_) / SECS_PER_DAY + 4) % DAYS_PER_WEEK)+1) // 1 = Sunday 62 | #define elapsedDays(_time_) ((_time_) / SECS_PER_DAY) // this is number of days since Jan 1 1970 63 | #define elapsedSecsToday(_time_) ((_time_) % SECS_PER_DAY) // the number of seconds since last midnight 64 | // The following macros are used in calculating alarms and assume the clock is set to a date later than Jan 1 1971 65 | // Always set the correct time before setting alarms 66 | #define previousMidnight(_time_) (((_time_) / SECS_PER_DAY) * SECS_PER_DAY) // time at the start of the given day 67 | #define nextMidnight(_time_) (previousMidnight(_time_) + SECS_PER_DAY) // time at the end of the given day 68 | #define elapsedSecsThisWeek(_time_) (elapsedSecsToday(_time_) + ((dayOfWeek(_time_)-1) * SECS_PER_DAY)) // note that week starts on day 1 69 | #define previousSunday(_time_) ((_time_) - elapsedSecsThisWeek(_time_)) // time at the start of the week for the given time 70 | #define nextSunday(_time_) (previousSunday(_time_)+SECS_PER_WEEK) // time at the end of the week for the given time 71 | 72 | 73 | /* Useful Macros for converting elapsed time to a time_t */ 74 | #define minutesToTime_t(M) ((M) * SECS_PER_MIN) 75 | #define hoursToTime_t(H) ((H) * SECS_PER_HOUR) 76 | #define daysToTime_t(D) ((D) * SECS_PER_DAY) // fixed on Jul 22 2011 77 | #define weeksToTime_t(W) ((W) * SECS_PER_WEEK) 78 | 79 | /*============================================================================*/ 80 | /* time and date functions */ 81 | int hour(); // the hour now 82 | int hour(time_t t); // the hour for the given time 83 | int hourFormat12(); // the hour now in 12 hour format 84 | int hourFormat12(time_t t); // the hour for the given time in 12 hour format 85 | uint8_t isAM(); // returns true if time now is AM 86 | uint8_t isAM(time_t t); // returns true the given time is AM 87 | uint8_t isPM(); // returns true if time now is PM 88 | uint8_t isPM(time_t t); // returns true the given time is PM 89 | int minute(); // the minute now 90 | int minute(time_t t); // the minute for the given time 91 | int second(); // the second now 92 | int second(time_t t); // the second for the given time 93 | int day(); // the day now 94 | int day(time_t t); // the day for the given time 95 | int weekday(); // the weekday now (Sunday is day 1) 96 | int weekday(time_t t); // the weekday for the given time 97 | int month(); // the month now (Jan is month 1) 98 | int month(time_t t); // the month for the given time 99 | int year(); // the full four digit year: (2009, 2010 etc) 100 | int year(time_t t); // the year for the given time 101 | 102 | const String timeString(); 103 | const String timeString(time_t t); 104 | 105 | time_t now(); // return the current time as seconds since Jan 1 1970 106 | void setTime(time_t t); 107 | void setTime(int hr,int min,int sec,int day, int month, int yr); 108 | void adjustTime(long adjustment); 109 | 110 | /* date strings */ 111 | #define dt_MAX_STRING_LEN 9 // length of longest date string (excluding terminating null) 112 | const String monthStr(uint8_t month); 113 | const String dayStr(uint8_t day); 114 | const String monthShortStr(uint8_t month); 115 | const String dayShortStr(uint8_t day); 116 | 117 | /* time sync functions */ 118 | timeStatus_t timeStatus(); // indicates if time has been set and recently synchronized 119 | void setSyncProvider( getExternalTime getTimeFunction); // identify the external time provider 120 | void setSyncInterval(time_t interval); // set the number of seconds between re-sync 121 | 122 | /* low level functions to convert to and from system time */ 123 | void breakTime(time_t time, tmElements_t &tm); // break time_t into elements 124 | time_t makeTime(const tmElements_t &tm); // convert time elements into time_t 125 | 126 | #endif /* _Time_h */ 127 | 128 | -------------------------------------------------------------------------------- /lib/Display/OLEDDisplay.cpp: -------------------------------------------------------------------------------- 1 | /** 2 | * The MIT License (MIT) 3 | * 4 | * Copyright (c) 2018 by ThingPulse, Daniel Eichhorn 5 | * Copyright (c) 2018 by Fabrice Weinberg 6 | * Copyright (c) 2019 by Helmut Tschemernjak - www.radioshuttle.de 7 | * 8 | * Permission is hereby granted, free of charge, to any person obtaining a copy 9 | * of this software and associated documentation files (the "Software"), to deal 10 | * in the Software without restriction, including without limitation the rights 11 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 12 | * copies of the Software, and to permit persons to whom the Software is 13 | * furnished to do so, subject to the following conditions: 14 | * 15 | * The above copyright notice and this permission notice shall be included in all 16 | * copies or substantial portions of the Software. 17 | * 18 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 23 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 24 | * SOFTWARE. 25 | * 26 | * ThingPulse invests considerable time and money to develop these open source libraries. 27 | * Please support us by buying our products (and not the clones) from 28 | * https://thingpulse.com 29 | * 30 | */ 31 | 32 | #include "OLEDDisplay.h" 33 | 34 | OLEDDisplay::OLEDDisplay(OLEDDISPLAY_GEOMETRY g) : _geometry(g), _displayIsOn(false) { 35 | } 36 | 37 | OLEDDisplay::~OLEDDisplay() { 38 | } 39 | 40 | // cppcheck-suppress unusedFunction 41 | void OLEDDisplay::displayOn() { 42 | sendCommand(DISPLAYON); 43 | _displayIsOn = true; 44 | } 45 | 46 | // cppcheck-suppress unusedFunction 47 | bool OLEDDisplay::isDisplayOn() const { 48 | return _displayIsOn; 49 | } 50 | 51 | // cppcheck-suppress unusedFunction 52 | void OLEDDisplay::displayOff() { 53 | sendCommand(DISPLAYOFF); 54 | _displayIsOn = false; 55 | } 56 | 57 | // cppcheck-suppress unusedFunction 58 | bool OLEDDisplay::isDisplayOff() const { 59 | return !_displayIsOn; 60 | } 61 | 62 | // cppcheck-suppress unusedFunction 63 | void OLEDDisplay::invertDisplay() { 64 | sendCommand(INVERTDISPLAY); 65 | } 66 | 67 | // cppcheck-suppress unusedFunction 68 | void OLEDDisplay::normalDisplay() { 69 | sendCommand(NORMALDISPLAY); 70 | } 71 | 72 | // cppcheck-suppress unusedFunction 73 | void OLEDDisplay::setContrast(uint8_t contrast, uint8_t precharge, uint8_t comdetect) { 74 | sendCommand(SETPRECHARGE); // 0xD9 75 | sendCommand(precharge); // 0xF1 default, to lower the contrast, put 1-1F 76 | sendCommand(SETCONTRAST); 77 | sendCommand(contrast); // 0-255 78 | sendCommand(SETVCOMDETECT); // 0xDB, (additionally needed to lower the contrast) 79 | sendCommand(comdetect); // 0x40 default, to lower the contrast, put 0 80 | sendCommand(DISPLAYALLON_RESUME); 81 | sendCommand(NORMALDISPLAY); 82 | sendCommand(DISPLAYON); 83 | } 84 | 85 | // cppcheck-suppress unusedFunction 86 | void OLEDDisplay::setBrightness(uint8_t brightness) { 87 | uint8_t contrast = brightness * 1.171 - 43; 88 | if (brightness < 128) { 89 | // Magic values to get a smooth/ step-free transition 90 | contrast = brightness * 1.171; 91 | } 92 | 93 | uint8_t precharge = 241; 94 | if (brightness == 0) { 95 | precharge = 0; 96 | } 97 | uint8_t comdetect = brightness / 8; 98 | setContrast(contrast, precharge, comdetect); 99 | } 100 | 101 | // cppcheck-suppress unusedFunction 102 | void OLEDDisplay::resetOrientation() { 103 | sendCommand(SEGREMAP); 104 | sendCommand(COMSCANINC); 105 | } 106 | 107 | // cppcheck-suppress unusedFunction 108 | void OLEDDisplay::flipScreenVertically() { 109 | sendCommand(SEGREMAP | 0x01); 110 | sendCommand(COMSCANDEC); 111 | } 112 | 113 | // cppcheck-suppress unusedFunction 114 | void OLEDDisplay::mirrorScreen() { 115 | sendCommand(SEGREMAP); 116 | sendCommand(COMSCANDEC); 117 | } 118 | 119 | void OLEDDisplay::display(Bitmap *bitmap) { 120 | if (isDisplayOff()) { 121 | displayOn(); 122 | } 123 | internDisplay(bitmap); 124 | } 125 | 126 | // cppcheck-suppress unusedFunction 127 | void OLEDDisplay::clear() { 128 | } 129 | 130 | // cppcheck-suppress unusedFunction 131 | uint OLEDDisplay::getWidth() { 132 | switch (_geometry) { 133 | case GEOMETRY_128_64: 134 | case GEOMETRY_128_32: 135 | return 128; 136 | case GEOMETRY_64_48: 137 | case GEOMETRY_64_32: 138 | return 64; 139 | } 140 | return 0; 141 | } 142 | 143 | // cppcheck-suppress unusedFunction 144 | uint OLEDDisplay::getHeight() { 145 | switch (_geometry) { 146 | case GEOMETRY_128_64: 147 | return 64; 148 | case GEOMETRY_64_48: 149 | return 48; 150 | case GEOMETRY_128_32: 151 | case GEOMETRY_64_32: 152 | return 32; 153 | } 154 | return 0; 155 | } 156 | 157 | // cppcheck-suppress unusedFunction 158 | void OLEDDisplay::sendInitCommands() { 159 | sendCommand(DISPLAYOFF); 160 | sendCommand(SETDISPLAYCLOCKDIV); 161 | sendCommand(0xF0); // Increase speed of the display max ~96Hz 162 | sendCommand(SETMULTIPLEX); 163 | sendCommand(this->getHeight() - 1); 164 | sendCommand(SETDISPLAYOFFSET); 165 | sendCommand(0x00); 166 | if (_geometry == GEOMETRY_64_32) { 167 | sendCommand(0x00); 168 | } else { 169 | sendCommand(SETSTARTLINE); 170 | } 171 | sendCommand(CHARGEPUMP); 172 | sendCommand(0x14); 173 | sendCommand(MEMORYMODE); 174 | sendCommand(0x00); 175 | sendCommand(SEGREMAP); 176 | sendCommand(COMSCANINC); 177 | sendCommand(SETCOMPINS); 178 | 179 | if (_geometry == GEOMETRY_128_64 || _geometry == GEOMETRY_64_48 || _geometry == GEOMETRY_64_32) { 180 | sendCommand(0x12); 181 | } else if (_geometry == GEOMETRY_128_32) { 182 | sendCommand(0x02); 183 | } 184 | 185 | sendCommand(SETCONTRAST); 186 | 187 | if (_geometry == GEOMETRY_128_64 || _geometry == GEOMETRY_64_48 || _geometry == GEOMETRY_64_32) { 188 | sendCommand(0xCF); 189 | } else if (_geometry == GEOMETRY_128_32) { 190 | sendCommand(0x8F); 191 | } 192 | 193 | sendCommand(SETPRECHARGE); 194 | sendCommand(0xF1); 195 | sendCommand(SETVCOMDETECT); // 0xDB, (additionally needed to lower the contrast) 196 | sendCommand(0x40); // 0x40 default, to lower the contrast, put 0 197 | sendCommand(DISPLAYALLON_RESUME); 198 | sendCommand(NORMALDISPLAY); 199 | sendCommand(0x2e); // stop scroll 200 | sendCommand(DISPLAYON); 201 | } 202 | -------------------------------------------------------------------------------- /lib/BoardFinder/BoardFinder.cpp: -------------------------------------------------------------------------------- 1 | #include "BoardFinder.h" 2 | #include 3 | #include 4 | 5 | #define MODULE_NAME "BoardFinder" 6 | 7 | BoardConfig::BoardConfig(String name, BoardType type, uint8_t oledsda, uint8_t oledscl, uint8_t oledaddr, uint8_t oledreset, uint8_t lorasck, uint8_t loramiso, uint8_t loramosi, uint8_t loracs, uint8_t lorareset, uint8_t lorairq, uint8_t gpsrx, uint8_t gpstx, uint8_t button, bool needcheckpowerchip, bool powercheckstatus) 8 | : Name(name), Type(type), OledSda(oledsda), OledScl(oledscl), OledAddr(oledaddr), OledReset(oledreset), LoraSck(lorasck), LoraMiso(loramiso), LoraMosi(loramosi), LoraCS(loracs), LoraReset(lorareset), LoraIRQ(lorairq), GpsRx(gpsrx), GpsTx(gpstx), Button(button), needCheckPowerChip(needcheckpowerchip), powerCheckStatus(powercheckstatus) { 9 | } 10 | 11 | BoardFinder::BoardFinder(const std::list &boardConfigs) : _boardConfigs(boardConfigs) { 12 | } 13 | 14 | BoardConfig const *BoardFinder::searchBoardConfig(logging::Logger &logger) { 15 | logger.log(logging::LoggerLevel::LOGGER_LEVEL_INFO, MODULE_NAME, "looking for a board config."); 16 | logger.log(logging::LoggerLevel::LOGGER_LEVEL_INFO, MODULE_NAME, "searching for OLED..."); 17 | 18 | for (BoardConfig const *boardconf : _boardConfigs) { 19 | if (boardconf->needCheckPowerChip && checkPowerConfig(boardconf, logger) == boardconf->powerCheckStatus) { 20 | PowerManagement powerManagement; 21 | Wire.begin(boardconf->OledSda, boardconf->OledScl); 22 | powerManagement.begin(Wire); 23 | powerManagement.activateOLED(); 24 | } else if (boardconf->needCheckPowerChip) { 25 | continue; 26 | } 27 | if (checkOledConfig(boardconf, logger)) { 28 | logger.log(logging::LoggerLevel::LOGGER_LEVEL_INFO, MODULE_NAME, "found a board config: %s", boardconf->Name.c_str()); 29 | return boardconf; 30 | } 31 | } 32 | 33 | logger.log(logging::LoggerLevel::LOGGER_LEVEL_INFO, MODULE_NAME, "could not find OLED, will search for the modem now..."); 34 | 35 | for (BoardConfig const *boardconf : _boardConfigs) { 36 | if (boardconf->needCheckPowerChip && checkPowerConfig(boardconf, logger) == boardconf->powerCheckStatus) { 37 | PowerManagement powerManagement; 38 | Wire.begin(boardconf->OledSda, boardconf->OledScl); 39 | powerManagement.begin(Wire); 40 | powerManagement.activateLoRa(); 41 | } 42 | if (checkModemConfig(boardconf)) { 43 | logger.log(logging::LoggerLevel::LOGGER_LEVEL_INFO, MODULE_NAME, "found a board config: %s", boardconf->Name.c_str()); 44 | return boardconf; 45 | } 46 | } 47 | 48 | logger.log(logging::LoggerLevel::LOGGER_LEVEL_ERROR, MODULE_NAME, "could not find a board config!"); 49 | 50 | return 0; 51 | } 52 | 53 | BoardConfig const *BoardFinder::getBoardConfig(String name) { 54 | std::_List_const_iterator elem = std::find_if(_boardConfigs.begin(), _boardConfigs.end(), [&](BoardConfig const *conf) { 55 | return conf->Name == name; 56 | }); 57 | if (elem == _boardConfigs.end()) { 58 | return 0; 59 | } 60 | return *elem; 61 | } 62 | 63 | bool BoardFinder::checkOledConfig(BoardConfig const *boardConfig, logging::Logger &logger) { 64 | if (boardConfig->OledReset > 0) { 65 | pinMode(boardConfig->OledReset, OUTPUT); 66 | digitalWrite(boardConfig->OledReset, HIGH); 67 | delay(1); 68 | digitalWrite(boardConfig->OledReset, LOW); 69 | delay(10); 70 | digitalWrite(boardConfig->OledReset, HIGH); 71 | } 72 | if (!Wire.begin(boardConfig->OledSda, boardConfig->OledScl)) { 73 | logger.log(logging::LoggerLevel::LOGGER_LEVEL_WARN, MODULE_NAME, "issue with wire"); 74 | return false; 75 | } 76 | Wire.beginTransmission(boardConfig->OledAddr); 77 | if (!Wire.endTransmission()) { 78 | return true; 79 | } 80 | return false; 81 | } 82 | 83 | bool BoardFinder::checkModemConfig(BoardConfig const *boardConfig) { 84 | pinMode(boardConfig->LoraReset, OUTPUT); 85 | digitalWrite(boardConfig->LoraReset, LOW); 86 | delay(10); 87 | digitalWrite(boardConfig->LoraReset, HIGH); 88 | delay(10); 89 | 90 | pinMode(boardConfig->LoraCS, OUTPUT); 91 | digitalWrite(boardConfig->LoraCS, HIGH); 92 | 93 | SPI.begin(boardConfig->LoraSck, boardConfig->LoraMiso, boardConfig->LoraMosi, boardConfig->LoraCS); 94 | 95 | digitalWrite(boardConfig->LoraCS, LOW); 96 | 97 | SPI.beginTransaction(SPISettings(8E6, MSBFIRST, SPI_MODE0)); 98 | SPI.transfer(0x42); 99 | uint8_t response = SPI.transfer(0x00); 100 | SPI.endTransaction(); 101 | 102 | digitalWrite(boardConfig->LoraCS, HIGH); 103 | 104 | if (response == 0x12) { 105 | return true; 106 | } 107 | return false; 108 | } 109 | 110 | bool BoardFinder::checkPowerConfig(BoardConfig const *boardConfig, logging::Logger &logger) { 111 | if (!Wire.begin(boardConfig->OledSda, boardConfig->OledScl)) { 112 | logger.log(logging::LoggerLevel::LOGGER_LEVEL_WARN, MODULE_NAME, "issue with wire"); 113 | return false; 114 | } 115 | Wire.beginTransmission(0x34); 116 | Wire.write(0x03); 117 | Wire.endTransmission(); 118 | 119 | Wire.requestFrom(0x34, 1); 120 | int response = Wire.read(); 121 | Wire.endTransmission(); 122 | 123 | logger.log(logging::LoggerLevel::LOGGER_LEVEL_DEBUG, MODULE_NAME, "wire response: %d", response); 124 | if (response == 0x03) { 125 | logger.log(logging::LoggerLevel::LOGGER_LEVEL_DEBUG, MODULE_NAME, "power chip found!"); 126 | return true; 127 | } 128 | logger.log(logging::LoggerLevel::LOGGER_LEVEL_DEBUG, MODULE_NAME, "power chip NOT found"); 129 | return false; 130 | } 131 | 132 | // clang-format off 133 | BoardConfig TTGO_LORA32_V1 ("TTGO_LORA32_V1", eTTGO_LORA32_V1, 4, 15, 0x3C, 0, 5, 19, 27, 18, 14, 26, 0, 0, 0); 134 | BoardConfig TTGO_LORA32_V2 ("TTGO_LORA32_V2", eTTGO_LORA32_V2, 21, 22, 0x3C, 0, 5, 19, 27, 18, 14, 26, 0, 0, 0, true); 135 | BoardConfig TTGO_T_Beam_V0_7 ("TTGO_T_Beam_V0_7", eTTGO_T_Beam_V0_7, 21, 22, 0x3C, 0, 5, 19, 27, 18, 14, 26, 15, 12, 38, true); 136 | BoardConfig TTGO_T_Beam_V1_0 ("TTGO_T_Beam_V1_0", eTTGO_T_Beam_V1_0, 21, 22, 0x3C, 0, 5, 19, 27, 18, 14, 26, 12, 34, 38, true, true); 137 | BoardConfig ETH_BOARD ("ETH_BOARD", eETH_BOARD, 33, 32, 0x3C, 0, 14, 2, 15, 12, 4, 36, 0, 0, 0); 138 | BoardConfig TRACKERD ("TRACKERD", eTRACKERD, 5, 4, 0x3C, 0, 18, 19, 23, 16, 14, 26, 0, 0, 0); 139 | BoardConfig HELTEC_WIFI_LORA_32_V1("HELTEC_WIFI_LORA_32_V1", eHELTEC_WIFI_LORA_32_V1, 4, 15, 0x3C, 16, 5, 19, 27, 18, 14, 26, 0, 0, 0); 140 | BoardConfig HELTEC_WIFI_LORA_32_V2("HELTEC_WIFI_LORA_32_V2", eHELTEC_WIFI_LORA_32_V2, 4, 15, 0x3C, 16, 5, 19, 27, 18, 14, 26, 0, 0, 0); 141 | // clang-format on 142 | -------------------------------------------------------------------------------- /src/LoRa_APRS_iGate.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | #include "TaskAprsIs.h" 11 | #include "TaskBeacon.h" 12 | #include "TaskDisplay.h" 13 | #include "TaskEth.h" 14 | #include "TaskFTP.h" 15 | #include "TaskMQTT.h" 16 | #include "TaskNTP.h" 17 | #include "TaskOTA.h" 18 | #include "TaskRadiolib.h" 19 | #include "TaskRouter.h" 20 | #include "TaskWifi.h" 21 | #include "project_configuration.h" 22 | 23 | #define VERSION "22.20.0" 24 | #define MODULE_NAME "Main" 25 | 26 | String create_lat_aprs(double lat); 27 | String create_long_aprs(double lng); 28 | 29 | TaskQueue> toAprsIs; 30 | TaskQueue> fromModem; 31 | TaskQueue> toModem; 32 | TaskQueue> toMQTT; 33 | 34 | System LoRaSystem; 35 | Configuration userConfig; 36 | 37 | DisplayTask displayTask; 38 | // ModemTask modemTask(fromModem, toModem); 39 | RadiolibTask modemTask(fromModem, toModem); 40 | EthTask ethTask; 41 | WifiTask wifiTask; 42 | OTATask otaTask; 43 | NTPTask ntpTask; 44 | FTPTask ftpTask; 45 | MQTTTask mqttTask(toMQTT); 46 | AprsIsTask aprsIsTask(toAprsIs); 47 | RouterTask routerTask(fromModem, toModem, toAprsIs, toMQTT); 48 | BeaconTask beaconTask(toModem, toAprsIs); 49 | 50 | void setup() { 51 | Serial.begin(115200); 52 | LoRaSystem.getLogger().setSerial(&Serial); 53 | setWiFiLogger(&LoRaSystem.getLogger()); 54 | delay(500); 55 | LoRaSystem.getLogger().log(logging::LoggerLevel::LOGGER_LEVEL_INFO, MODULE_NAME, "LoRa APRS iGate by OE5BPA (Peter Buchegger)"); 56 | LoRaSystem.getLogger().log(logging::LoggerLevel::LOGGER_LEVEL_INFO, MODULE_NAME, "Version: %s", VERSION); 57 | 58 | std::list boardConfigs; 59 | boardConfigs.push_back(&TTGO_LORA32_V1); 60 | boardConfigs.push_back(&TTGO_LORA32_V2); 61 | boardConfigs.push_back(&TTGO_T_Beam_V0_7); 62 | boardConfigs.push_back(&TTGO_T_Beam_V1_0); 63 | boardConfigs.push_back(Ð_BOARD); 64 | boardConfigs.push_back(&TRACKERD); 65 | boardConfigs.push_back(&HELTEC_WIFI_LORA_32_V1); 66 | boardConfigs.push_back(&HELTEC_WIFI_LORA_32_V2); 67 | 68 | ProjectConfigurationManagement confmg(LoRaSystem.getLogger()); 69 | confmg.readConfiguration(LoRaSystem.getLogger(), userConfig); 70 | 71 | BoardFinder finder(boardConfigs); 72 | BoardConfig const *boardConfig = finder.getBoardConfig(userConfig.board); 73 | if (!boardConfig) { 74 | boardConfig = finder.searchBoardConfig(LoRaSystem.getLogger()); 75 | if (!boardConfig) { 76 | LoRaSystem.getLogger().log(logging::LoggerLevel::LOGGER_LEVEL_ERROR, MODULE_NAME, "Board config not set and search failed!"); 77 | while (true) 78 | ; 79 | } else { 80 | userConfig.board = boardConfig->Name; 81 | confmg.writeConfiguration(LoRaSystem.getLogger(), userConfig); 82 | LoRaSystem.getLogger().log(logging::LoggerLevel::LOGGER_LEVEL_INFO, MODULE_NAME, "will restart board now!"); 83 | ESP.restart(); 84 | } 85 | } 86 | 87 | LoRaSystem.getLogger().log(logging::LoggerLevel::LOGGER_LEVEL_INFO, MODULE_NAME, "Board %s loaded.", boardConfig->Name.c_str()); 88 | 89 | if (boardConfig->Type == eTTGO_T_Beam_V1_0) { 90 | Wire.begin(boardConfig->OledSda, boardConfig->OledScl); 91 | PowerManagement powerManagement; 92 | if (!powerManagement.begin(Wire)) { 93 | LoRaSystem.getLogger().log(logging::LoggerLevel::LOGGER_LEVEL_INFO, MODULE_NAME, "AXP192 init done!"); 94 | } else { 95 | LoRaSystem.getLogger().log(logging::LoggerLevel::LOGGER_LEVEL_ERROR, MODULE_NAME, "AXP192 init failed!"); 96 | } 97 | powerManagement.activateLoRa(); 98 | powerManagement.activateOLED(); 99 | if (userConfig.beacon.use_gps) { 100 | powerManagement.activateGPS(); 101 | } else { 102 | powerManagement.deactivateGPS(); 103 | } 104 | } 105 | 106 | LoRaSystem.setBoardConfig(boardConfig); 107 | LoRaSystem.setUserConfig(&userConfig); 108 | LoRaSystem.getTaskManager().addTask(&displayTask); 109 | LoRaSystem.getTaskManager().addTask(&modemTask); 110 | LoRaSystem.getTaskManager().addTask(&routerTask); 111 | LoRaSystem.getTaskManager().addTask(&beaconTask); 112 | 113 | bool tcpip = false; 114 | 115 | if (userConfig.wifi.active) { 116 | LoRaSystem.getTaskManager().addAlwaysRunTask(&wifiTask); 117 | tcpip = true; 118 | } 119 | if (boardConfig->Type == eETH_BOARD) { 120 | LoRaSystem.getTaskManager().addAlwaysRunTask(ðTask); 121 | tcpip = true; 122 | } 123 | 124 | if (tcpip) { 125 | LoRaSystem.getTaskManager().addTask(&otaTask); 126 | LoRaSystem.getTaskManager().addTask(&ntpTask); 127 | if (userConfig.ftp.active) { 128 | LoRaSystem.getTaskManager().addTask(&ftpTask); 129 | } 130 | 131 | if (userConfig.aprs_is.active) { 132 | LoRaSystem.getTaskManager().addTask(&aprsIsTask); 133 | } 134 | 135 | if (userConfig.mqtt.active) { 136 | LoRaSystem.getTaskManager().addTask(&mqttTask); 137 | } 138 | } 139 | 140 | LoRaSystem.getTaskManager().setup(LoRaSystem); 141 | 142 | LoRaSystem.getDisplay().showSpashScreen("LoRa APRS iGate", VERSION); 143 | 144 | if (userConfig.callsign == "NOCALL-10") { 145 | LoRaSystem.getLogger().log(logging::LoggerLevel::LOGGER_LEVEL_ERROR, MODULE_NAME, "You have to change your settings in 'data/is-cfg.json' and upload it via 'Upload File System image'!"); 146 | LoRaSystem.getDisplay().showStatusScreen("ERROR", "You have to change your settings in 'data/is-cfg.json' and upload it via \"Upload File System image\"!"); 147 | while (true) 148 | ; 149 | } 150 | if ((!userConfig.aprs_is.active) && !(userConfig.digi.active)) { 151 | LoRaSystem.getLogger().log(logging::LoggerLevel::LOGGER_LEVEL_ERROR, MODULE_NAME, "No mode selected (iGate or Digi)! You have to activate one of iGate or Digi."); 152 | LoRaSystem.getDisplay().showStatusScreen("ERROR", "No mode selected (iGate or Digi)! You have to activate one of iGate or Digi."); 153 | while (true) 154 | ; 155 | } 156 | 157 | if (userConfig.display.overwritePin != 0) { 158 | pinMode(userConfig.display.overwritePin, INPUT); 159 | pinMode(userConfig.display.overwritePin, INPUT_PULLUP); 160 | } 161 | 162 | delay(5000); 163 | LoRaSystem.getLogger().log(logging::LoggerLevel::LOGGER_LEVEL_INFO, MODULE_NAME, "setup done..."); 164 | } 165 | 166 | volatile bool syslogSet = false; 167 | 168 | void loop() { 169 | LoRaSystem.getTaskManager().loop(LoRaSystem); 170 | if (LoRaSystem.isWifiOrEthConnected() && LoRaSystem.getUserConfig()->syslog.active && !syslogSet) { 171 | LoRaSystem.getLogger().setSyslogServer(LoRaSystem.getUserConfig()->syslog.server, LoRaSystem.getUserConfig()->syslog.port, LoRaSystem.getUserConfig()->callsign); 172 | LoRaSystem.getLogger().log(logging::LoggerLevel::LOGGER_LEVEL_INFO, MODULE_NAME, "System connected after a restart to the network, syslog server set"); 173 | syslogSet = true; 174 | } 175 | } 176 | -------------------------------------------------------------------------------- /lib/NTPClient/NTPClient.cpp: -------------------------------------------------------------------------------- 1 | /** 2 | * The MIT License (MIT) 3 | * Copyright (c) 2015 by Fabrice Weinberg 4 | * 5 | * Permission is hereby granted, free of charge, to any person obtaining a copy 6 | * of this software and associated documentation files (the "Software"), to deal 7 | * in the Software without restriction, including without limitation the rights 8 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | * copies of the Software, and to permit persons to whom the Software is 10 | * furnished to do so, subject to the following conditions: 11 | * The above copyright notice and this permission notice shall be included in all 12 | * copies or substantial portions of the Software. 13 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | * SOFTWARE. 20 | */ 21 | 22 | #include "NTPClient.h" 23 | 24 | NTPClient::NTPClient() { 25 | } 26 | 27 | NTPClient::NTPClient(long timeOffset) { 28 | this->_timeOffset = timeOffset; 29 | } 30 | 31 | NTPClient::NTPClient(const char* poolServerName) { 32 | this->_poolServerName = poolServerName; 33 | } 34 | 35 | NTPClient::NTPClient(IPAddress poolServerIP) { 36 | this->_poolServerIP = poolServerIP; 37 | this->_poolServerName = NULL; 38 | } 39 | 40 | NTPClient::NTPClient(const char* poolServerName, long timeOffset) { 41 | this->_timeOffset = timeOffset; 42 | this->_poolServerName = poolServerName; 43 | } 44 | 45 | NTPClient::NTPClient(IPAddress poolServerIP, long timeOffset){ 46 | this->_timeOffset = timeOffset; 47 | this->_poolServerIP = poolServerIP; 48 | this->_poolServerName = NULL; 49 | } 50 | 51 | NTPClient::NTPClient(const char* poolServerName, long timeOffset, unsigned long updateInterval) { 52 | this->_timeOffset = timeOffset; 53 | this->_poolServerName = poolServerName; 54 | this->_updateInterval = updateInterval; 55 | } 56 | 57 | NTPClient::NTPClient(IPAddress poolServerIP, long timeOffset, unsigned long updateInterval) { 58 | this->_timeOffset = timeOffset; 59 | this->_poolServerIP = poolServerIP; 60 | this->_poolServerName = NULL; 61 | this->_updateInterval = updateInterval; 62 | } 63 | 64 | void NTPClient::begin() { 65 | this->begin(NTP_DEFAULT_LOCAL_PORT); 66 | } 67 | 68 | void NTPClient::begin(unsigned int port) { 69 | this->_port = port; 70 | 71 | this->_udp.begin(this->_port); 72 | 73 | this->_udpSetup = true; 74 | } 75 | 76 | bool NTPClient::forceUpdate() { 77 | #ifdef DEBUG_NTPClient 78 | Serial.println("Update from NTP Server"); 79 | #endif 80 | 81 | // flush any existing packets 82 | while(this->_udp.parsePacket() != 0) 83 | this->_udp.flush(); 84 | 85 | this->sendNTPPacket(); 86 | 87 | // Wait till data is there or timeout... 88 | byte timeout = 0; 89 | int cb = 0; 90 | do { 91 | delay ( 10 ); 92 | cb = this->_udp.parsePacket(); 93 | if (timeout > 100) return false; // timeout after 1000 ms 94 | timeout++; 95 | } while (cb == 0); 96 | 97 | this->_lastUpdate = millis() - (10 * (timeout + 1)); // Account for delay in reading the time 98 | 99 | this->_udp.read(this->_packetBuffer, NTP_PACKET_SIZE); 100 | 101 | unsigned long highWord = word(this->_packetBuffer[40], this->_packetBuffer[41]); 102 | unsigned long lowWord = word(this->_packetBuffer[42], this->_packetBuffer[43]); 103 | // combine the four bytes (two words) into a long integer 104 | // this is NTP time (seconds since Jan 1 1900): 105 | unsigned long secsSince1900 = highWord << 16 | lowWord; 106 | 107 | this->_currentEpoc = secsSince1900 - SEVENZYYEARS; 108 | 109 | return true; // return true after successful update 110 | } 111 | 112 | bool NTPClient::update() { 113 | if ((millis() - this->_lastUpdate >= this->_updateInterval) // Update after _updateInterval 114 | || this->_lastUpdate == 0) { // Update if there was no update yet. 115 | if (!this->_udpSetup || this->_port != NTP_DEFAULT_LOCAL_PORT) this->begin(this->_port); // setup the UDP client if needed 116 | return this->forceUpdate(); 117 | } 118 | return false; // return false if update does not occur 119 | } 120 | 121 | unsigned long NTPClient::getEpochTime() const { 122 | return this->_timeOffset + // User offset 123 | this->_currentEpoc + // Epoc returned by the NTP server 124 | ((millis() - this->_lastUpdate) / 1000); // Time since last update 125 | } 126 | 127 | int NTPClient::getDay() const { 128 | return (((this->getEpochTime() / 86400L) + 4 ) % 7); //0 is Sunday 129 | } 130 | int NTPClient::getHours() const { 131 | return ((this->getEpochTime() % 86400L) / 3600); 132 | } 133 | int NTPClient::getMinutes() const { 134 | return ((this->getEpochTime() % 3600) / 60); 135 | } 136 | int NTPClient::getSeconds() const { 137 | return (this->getEpochTime() % 60); 138 | } 139 | 140 | String NTPClient::getFormattedTime() const { 141 | unsigned long rawTime = this->getEpochTime(); 142 | unsigned long hours = (rawTime % 86400L) / 3600; 143 | String hoursStr = hours < 10 ? "0" + String(hours) : String(hours); 144 | 145 | unsigned long minutes = (rawTime % 3600) / 60; 146 | String minuteStr = minutes < 10 ? "0" + String(minutes) : String(minutes); 147 | 148 | unsigned long seconds = rawTime % 60; 149 | String secondStr = seconds < 10 ? "0" + String(seconds) : String(seconds); 150 | 151 | return hoursStr + ":" + minuteStr + ":" + secondStr; 152 | } 153 | 154 | void NTPClient::end() { 155 | this->_udp.stop(); 156 | 157 | this->_udpSetup = false; 158 | } 159 | 160 | void NTPClient::setTimeOffset(int timeOffset) { 161 | this->_timeOffset = timeOffset; 162 | } 163 | 164 | void NTPClient::setUpdateInterval(unsigned long updateInterval) { 165 | this->_updateInterval = updateInterval; 166 | } 167 | 168 | void NTPClient::setPoolServerName(const char* poolServerName) { 169 | this->_poolServerName = poolServerName; 170 | } 171 | 172 | void NTPClient::sendNTPPacket() { 173 | // set all bytes in the buffer to 0 174 | memset(this->_packetBuffer, 0, NTP_PACKET_SIZE); 175 | // Initialize values needed to form NTP request 176 | // (see URL above for details on the packets) 177 | 178 | this->_packetBuffer[0] = 0b11100011; // LI, Version, Mode 179 | this->_packetBuffer[1] = 0; // Stratum, or type of clock 180 | this->_packetBuffer[2] = 6; // Polling Interval 181 | this->_packetBuffer[3] = 0xEC; // Peer Clock Precision 182 | // 8 bytes of zero for Root Delay & Root Dispersion 183 | this->_packetBuffer[12] = 49; 184 | this->_packetBuffer[13] = 0x4E; 185 | this->_packetBuffer[14] = 49; 186 | this->_packetBuffer[15] = 52; 187 | 188 | // all NTP fields have been given values, now 189 | // you can send a packet requesting a timestamp: 190 | if (this->_poolServerName) { 191 | this->_udp.beginPacket(this->_poolServerName, 123); 192 | } else { 193 | this->_udp.beginPacket(this->_poolServerIP, 123); 194 | } 195 | this->_udp.write(this->_packetBuffer, NTP_PACKET_SIZE); 196 | this->_udp.endPacket(); 197 | } 198 | 199 | void NTPClient::setRandomPort(unsigned int minValue, unsigned int maxValue) { 200 | randomSeed(analogRead(0)); 201 | this->_port = random(minValue, maxValue); 202 | } -------------------------------------------------------------------------------- /src/project_configuration.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | 5 | #include "project_configuration.h" 6 | 7 | void ProjectConfigurationManagement::readProjectConfiguration(DynamicJsonDocument &data, Configuration &conf) { 8 | if (data.containsKey("callsign")) 9 | conf.callsign = data["callsign"].as(); 10 | 11 | if (data.containsKey("network")) { 12 | conf.network.DHCP = data["network"]["DHCP"] | false; 13 | if (data["network"].containsKey("static")) { 14 | if (data["network"]["static"].containsKey("ip")) 15 | conf.network.static_.ip.fromString(data["network"]["static"]["ip"].as()); 16 | if (data["network"]["static"].containsKey("subnet")) 17 | conf.network.static_.subnet.fromString(data["network"]["static"]["subnet"].as()); 18 | if (data["network"]["static"].containsKey("gateway")) 19 | conf.network.static_.gateway.fromString(data["network"]["static"]["gateway"].as()); 20 | if (data["network"]["static"].containsKey("dns1")) 21 | conf.network.static_.dns1.fromString(data["network"]["static"]["dns1"].as()); 22 | if (data["network"]["static"].containsKey("dns2")) 23 | conf.network.static_.dns2.fromString(data["network"]["static"]["dns2"].as()); 24 | } 25 | if (data["network"].containsKey("hostname")) { 26 | conf.network.hostname.overwrite = data["network"]["hostname"]["overwrite"] | false; 27 | if (data["network"]["hostname"].containsKey("name")) 28 | conf.network.hostname.name = data["network"]["hostname"]["name"].as(); 29 | } 30 | } 31 | 32 | conf.wifi.active = data["wifi"]["active"]; 33 | JsonArray aps = data["wifi"]["AP"].as(); 34 | for (JsonVariant v : aps) { 35 | Configuration::Wifi::AP ap; 36 | if (v.containsKey("SSID")) 37 | ap.SSID = v["SSID"].as(); 38 | if (v.containsKey("password")) 39 | ap.password = v["password"].as(); 40 | conf.wifi.APs.push_back(ap); 41 | } 42 | if (data.containsKey("beacon") && data["beacon"].containsKey("message")) 43 | conf.beacon.message = data["beacon"]["message"].as(); 44 | conf.beacon.positionLatitude = data["beacon"]["position"]["latitude"] | 0.0; 45 | conf.beacon.positionLongitude = data["beacon"]["position"]["longitude"] | 0.0; 46 | conf.beacon.use_gps = data["beacon"]["use_gps"] | false; 47 | conf.beacon.timeout = data["beacon"]["timeout"] | 15; 48 | conf.aprs_is.active = data["aprs_is"]["active"] | true; 49 | 50 | if (data.containsKey("aprs_is") && data["aprs_is"].containsKey("passcode")) 51 | conf.aprs_is.passcode = data["aprs_is"]["passcode"].as(); 52 | if (data.containsKey("aprs_is") && data["aprs_is"].containsKey("server")) 53 | conf.aprs_is.server = data["aprs_is"]["server"].as(); 54 | conf.aprs_is.port = data["aprs_is"]["port"] | 14580; 55 | 56 | conf.digi.active = data["digi"]["active"] | false; 57 | conf.digi.beacon = data["digi"]["beacon"] | false; 58 | 59 | conf.lora.frequencyRx = data["lora"]["frequency_rx"] | 433775000; 60 | conf.lora.gainRx = data["lora"]["gain_rx"] | 0; 61 | conf.lora.frequencyTx = data["lora"]["frequency_tx"] | 433775000; 62 | conf.lora.power = data["lora"]["power"] | 20; 63 | conf.lora.spreadingFactor = data["lora"]["spreading_factor"] | 12; 64 | conf.lora.signalBandwidth = data["lora"]["signal_bandwidth"] | 125000; 65 | conf.lora.codingRate4 = data["lora"]["coding_rate4"] | 5; 66 | conf.lora.tx_enable = data["lora"]["tx_enable"] | true; 67 | 68 | conf.display.alwaysOn = data["display"]["always_on"] | true; 69 | conf.display.timeout = data["display"]["timeout"] | 10; 70 | conf.display.overwritePin = data["display"]["overwrite_pin"] | 0; 71 | conf.display.turn180 = data["display"]["turn180"] | true; 72 | 73 | conf.ftp.active = data["ftp"]["active"] | false; 74 | JsonArray users = data["ftp"]["user"].as(); 75 | for (JsonVariant u : users) { 76 | Configuration::Ftp::User us; 77 | if (u.containsKey("name")) 78 | us.name = u["name"].as(); 79 | if (u.containsKey("password")) 80 | us.password = u["password"].as(); 81 | conf.ftp.users.push_back(us); 82 | } 83 | if (conf.ftp.users.empty()) { 84 | Configuration::Ftp::User us; 85 | us.name = "ftp"; 86 | us.password = "ftp"; 87 | conf.ftp.users.push_back(us); 88 | } 89 | 90 | conf.mqtt.active = data["mqtt"]["active"] | false; 91 | if (data["mqtt"].containsKey("server")) 92 | conf.mqtt.server = data["mqtt"]["server"].as(); 93 | conf.mqtt.port = data["mqtt"]["port"] | 1883; 94 | if (data["mqtt"].containsKey("name")) 95 | conf.mqtt.name = data["mqtt"]["name"].as(); 96 | if (data["mqtt"].containsKey("password")) 97 | conf.mqtt.password = data["mqtt"]["password"].as(); 98 | if (data["mqtt"].containsKey("topic")) 99 | conf.mqtt.topic = data["mqtt"]["topic"].as(); 100 | 101 | conf.syslog.active = data["syslog"]["active"] | true; 102 | if (data["syslog"].containsKey("server")) 103 | conf.syslog.server = data["syslog"]["server"].as(); 104 | conf.syslog.port = data["syslog"]["port"] | 514; 105 | 106 | if (data.containsKey("ntp_server")) 107 | conf.ntpServer = data["ntp_server"].as(); 108 | 109 | if (data.containsKey("board")) 110 | conf.board = data["board"].as(); 111 | } 112 | 113 | void ProjectConfigurationManagement::writeProjectConfiguration(Configuration &conf, DynamicJsonDocument &data) { 114 | data["callsign"] = conf.callsign; 115 | 116 | if (!conf.network.DHCP) { 117 | data["network"]["DHCP"] = conf.network.DHCP; 118 | data["network"]["static"]["ip"] = conf.network.static_.ip.toString(); 119 | data["network"]["static"]["subnet"] = conf.network.static_.subnet.toString(); 120 | data["network"]["static"]["gateway"] = conf.network.static_.gateway.toString(); 121 | data["network"]["static"]["dns1"] = conf.network.static_.dns1.toString(); 122 | data["network"]["static"]["dns2"] = conf.network.static_.dns2.toString(); 123 | data["network"]["hostname"]["overwrite"] = conf.network.hostname.overwrite; 124 | data["network"]["hostname"]["name"] = conf.network.hostname.name; 125 | } 126 | 127 | data["wifi"]["active"] = conf.wifi.active; 128 | JsonArray aps = data["wifi"].createNestedArray("AP"); 129 | for (Configuration::Wifi::AP ap : conf.wifi.APs) { 130 | JsonObject v = aps.createNestedObject(); 131 | v["SSID"] = ap.SSID; 132 | v["password"] = ap.password; 133 | } 134 | data["beacon"]["message"] = conf.beacon.message; 135 | data["beacon"]["position"]["latitude"] = conf.beacon.positionLatitude; 136 | data["beacon"]["position"]["longitude"] = conf.beacon.positionLongitude; 137 | data["beacon"]["use_gps"] = conf.beacon.use_gps; 138 | data["beacon"]["timeout"] = conf.beacon.timeout; 139 | data["aprs_is"]["active"] = conf.aprs_is.active; 140 | data["aprs_is"]["passcode"] = conf.aprs_is.passcode; 141 | data["aprs_is"]["server"] = conf.aprs_is.server; 142 | data["aprs_is"]["port"] = conf.aprs_is.port; 143 | data["digi"]["active"] = conf.digi.active; 144 | data["digi"]["beacon"] = conf.digi.beacon; 145 | data["lora"]["frequency_rx"] = conf.lora.frequencyRx; 146 | data["lora"]["gain_rx"] = conf.lora.gainRx; 147 | data["lora"]["frequency_tx"] = conf.lora.frequencyTx; 148 | data["lora"]["power"] = conf.lora.power; 149 | data["lora"]["spreading_factor"] = conf.lora.spreadingFactor; 150 | data["lora"]["signal_bandwidth"] = conf.lora.signalBandwidth; 151 | data["lora"]["coding_rate4"] = conf.lora.codingRate4; 152 | data["lora"]["tx_enable"] = conf.lora.tx_enable; 153 | data["display"]["always_on"] = conf.display.alwaysOn; 154 | data["display"]["timeout"] = conf.display.timeout; 155 | data["display"]["overwrite_pin"] = conf.display.overwritePin; 156 | data["display"]["turn180"] = conf.display.turn180; 157 | data["ftp"]["active"] = conf.ftp.active; 158 | JsonArray users = data["ftp"].createNestedArray("user"); 159 | for (Configuration::Ftp::User u : conf.ftp.users) { 160 | JsonObject v = users.createNestedObject(); 161 | v["name"] = u.name; 162 | v["password"] = u.password; 163 | } 164 | data["mqtt"]["active"] = conf.mqtt.active; 165 | data["mqtt"]["server"] = conf.mqtt.server; 166 | data["mqtt"]["port"] = conf.mqtt.port; 167 | data["mqtt"]["name"] = conf.mqtt.name; 168 | data["mqtt"]["password"] = conf.mqtt.password; 169 | data["mqtt"]["topic"] = conf.mqtt.topic; 170 | data["syslog"]["active"] = conf.syslog.active; 171 | data["syslog"]["server"] = conf.syslog.server; 172 | data["syslog"]["port"] = conf.syslog.port; 173 | data["ntp_server"] = conf.ntpServer; 174 | 175 | data["board"] = conf.board; 176 | } 177 | -------------------------------------------------------------------------------- /src/TaskRadiolib.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #include "TaskRadiolib.h" 6 | 7 | RadiolibTask::RadiolibTask(TaskQueue> &fromModem, TaskQueue> &toModem) : Task(TASK_RADIOLIB, TaskRadiolib), _fromModem(fromModem), _toModem(toModem) { 8 | } 9 | 10 | RadiolibTask::~RadiolibTask() { 11 | radio->clearDio0Action(); 12 | } 13 | 14 | volatile bool RadiolibTask::enableInterrupt = true; // Need to catch interrupt or not. 15 | volatile bool RadiolibTask::operationDone = false; // Caught IRQ or not. 16 | 17 | void RadiolibTask::setFlag(void) { 18 | if (!enableInterrupt) { 19 | return; 20 | } 21 | 22 | operationDone = true; 23 | } 24 | 25 | bool RadiolibTask::setup(System &system) { 26 | SPI.begin(system.getBoardConfig()->LoraSck, system.getBoardConfig()->LoraMiso, system.getBoardConfig()->LoraMosi, system.getBoardConfig()->LoraCS); 27 | module = new Module(system.getBoardConfig()->LoraCS, system.getBoardConfig()->LoraIRQ, system.getBoardConfig()->LoraReset); 28 | radio = new SX1278(module); 29 | 30 | config = system.getUserConfig()->lora; 31 | 32 | rxEnable = true; 33 | txEnable = config.tx_enable; 34 | 35 | float freqMHz = (float)config.frequencyRx / 1000000; 36 | float BWkHz = (float)config.signalBandwidth / 1000; 37 | 38 | const uint16_t preambleLength = 8; 39 | 40 | int16_t state = radio->begin(freqMHz, BWkHz, config.spreadingFactor, config.codingRate4, RADIOLIB_SX127X_SYNC_WORD, config.power, preambleLength, config.gainRx); 41 | if (state != RADIOLIB_ERR_NONE) { 42 | switch (state) { 43 | case RADIOLIB_ERR_INVALID_FREQUENCY: 44 | system.getLogger().log(logging::LoggerLevel::LOGGER_LEVEL_ERROR, getName(), "[%s] SX1278 init failed, The supplied frequency value (%fMHz) is invalid for this module.", timeString().c_str(), freqMHz); 45 | rxEnable = false; 46 | txEnable = false; 47 | break; 48 | case RADIOLIB_ERR_INVALID_BANDWIDTH: 49 | system.getLogger().log(logging::LoggerLevel::LOGGER_LEVEL_ERROR, getName(), "[%s] SX1278 init failed, The supplied bandwidth value (%fkHz) is invalid for this module. Should be 7800, 10400, 15600, 20800, 31250, 41700 ,62500, 125000, 250000, 500000.", timeString().c_str(), BWkHz); 50 | rxEnable = false; 51 | txEnable = false; 52 | break; 53 | case RADIOLIB_ERR_INVALID_SPREADING_FACTOR: 54 | system.getLogger().log(logging::LoggerLevel::LOGGER_LEVEL_ERROR, getName(), "[%s] SX1278 init failed, The supplied spreading factor value (%d) is invalid for this module.", timeString().c_str(), config.spreadingFactor); 55 | rxEnable = false; 56 | txEnable = false; 57 | break; 58 | case RADIOLIB_ERR_INVALID_CODING_RATE: 59 | system.getLogger().log(logging::LoggerLevel::LOGGER_LEVEL_ERROR, getName(), "[%s] SX1278 init failed, The supplied coding rate value (%d) is invalid for this module.", timeString().c_str(), config.codingRate4); 60 | rxEnable = false; 61 | txEnable = false; 62 | break; 63 | case RADIOLIB_ERR_INVALID_OUTPUT_POWER: 64 | system.getLogger().log(logging::LoggerLevel::LOGGER_LEVEL_ERROR, getName(), "[%s] SX1278 init failed, The supplied output power value (%d) is invalid for this module.", timeString().c_str(), config.power); 65 | txEnable = false; 66 | break; 67 | case RADIOLIB_ERR_INVALID_PREAMBLE_LENGTH: 68 | system.getLogger().log(logging::LoggerLevel::LOGGER_LEVEL_ERROR, getName(), "[%s] SX1278 init failed, The supplied preamble length is invalid.", timeString().c_str()); 69 | txEnable = false; 70 | break; 71 | case RADIOLIB_ERR_INVALID_GAIN: 72 | system.getLogger().log(logging::LoggerLevel::LOGGER_LEVEL_ERROR, getName(), "[%s] SX1278 init failed, The supplied gain value (%d) is invalid.", timeString().c_str(), config.gainRx); 73 | rxEnable = false; 74 | break; 75 | default: 76 | system.getLogger().log(logging::LoggerLevel::LOGGER_LEVEL_ERROR, getName(), "[%s] SX1278 init failed, code %d", timeString().c_str(), state); 77 | rxEnable = false; 78 | txEnable = false; 79 | } 80 | _stateInfo = "LoRa-Modem failed"; 81 | _state = Error; 82 | } 83 | 84 | state = radio->setCRC(true); 85 | if (state != RADIOLIB_ERR_NONE) { 86 | system.getLogger().log(logging::LoggerLevel::LOGGER_LEVEL_ERROR, getName(), "[%s] setCRC failed, code %d", timeString().c_str(), state); 87 | _stateInfo = "LoRa-Modem failed"; 88 | _state = Error; 89 | } 90 | 91 | radio->setDio0Action(setFlag); 92 | 93 | if (rxEnable) { 94 | int state = startRX(RADIOLIB_SX127X_RXCONTINUOUS); 95 | if (state != RADIOLIB_ERR_NONE) { 96 | system.getLogger().log(logging::LoggerLevel::LOGGER_LEVEL_ERROR, getName(), "[%s] startRX failed, code %d", timeString().c_str(), state); 97 | rxEnable = false; 98 | _stateInfo = "LoRa-Modem failed"; 99 | _state = Error; 100 | } 101 | } 102 | 103 | preambleDurationMilliSec = ((uint64_t)(preambleLength + 4) << (config.spreadingFactor + 10 /* to milli-sec */)) / config.signalBandwidth; 104 | 105 | _stateInfo = ""; 106 | return true; 107 | } 108 | 109 | int transmissionState = RADIOLIB_ERR_NONE; 110 | bool transmitFlag = false; // Transmitting or not. 111 | 112 | bool RadiolibTask::loop(System &system) { 113 | if (operationDone) { // occurs interrupt. 114 | enableInterrupt = false; 115 | 116 | if (transmitFlag) { // transmitted. 117 | if (transmissionState == RADIOLIB_ERR_NONE) { 118 | system.getLogger().log(logging::LoggerLevel::LOGGER_LEVEL_DEBUG, getName(), "[%s] TX done", timeString().c_str()); 119 | 120 | } else { 121 | system.getLogger().log(logging::LoggerLevel::LOGGER_LEVEL_ERROR, getName(), "[%s] transmitFlag failed, code %d", timeString().c_str(), transmissionState); 122 | } 123 | operationDone = false; 124 | transmitFlag = false; 125 | 126 | txWaitTimer.setTimeout(preambleDurationMilliSec * 2); 127 | txWaitTimer.start(); 128 | 129 | } else { // received. 130 | String str; 131 | int state = radio->readData(str); 132 | 133 | if (state != RADIOLIB_ERR_NONE) { 134 | system.getLogger().log(logging::LoggerLevel::LOGGER_LEVEL_ERROR, getName(), "[%s] readData failed, code %d", timeString().c_str(), state); 135 | } else { 136 | if (str.substring(0, 3) != "<\xff\x01") { 137 | system.getLogger().log(logging::LoggerLevel::LOGGER_LEVEL_DEBUG, getName(), "[%s] Unknown packet '%s' with RSSI %.0fdBm, SNR %.2fdB and FreqErr %fHz%s", timeString().c_str(), str.c_str(), radio->getRSSI(), radio->getSNR(), -radio->getFrequencyError()); 138 | } else { 139 | std::shared_ptr msg = std::shared_ptr(new APRSMessage()); 140 | msg->decode(str.substring(3)); 141 | _fromModem.addElement(msg); 142 | system.getLogger().log(logging::LoggerLevel::LOGGER_LEVEL_DEBUG, getName(), "[%s] Received packet '%s' with RSSI %.0fdBm, SNR %.2fdB and FreqErr %fHz", timeString().c_str(), msg->toString().c_str(), radio->getRSSI(), radio->getSNR(), -radio->getFrequencyError()); 143 | system.getDisplay().addFrame(std::shared_ptr(new TextFrame("LoRa", msg->toString().c_str()))); 144 | } 145 | } 146 | operationDone = false; 147 | } 148 | 149 | if (rxEnable) { 150 | int state = startRX(RADIOLIB_SX127X_RXCONTINUOUS); 151 | if (state != RADIOLIB_ERR_NONE) { 152 | system.getLogger().log(logging::LoggerLevel::LOGGER_LEVEL_ERROR, getName(), "[%s] startRX failed, code %d", timeString().c_str(), state); 153 | rxEnable = false; 154 | } 155 | } 156 | 157 | enableInterrupt = true; 158 | } else { // not interrupt. 159 | if (!txWaitTimer.check()) { 160 | } else { 161 | if (!txEnable) { 162 | // system.getLogger().log(logging::LoggerLevel::LOGGER_LEVEL_DEBUG, getName(), "[%s] TX is not enabled", timeString().c_str()); 163 | } else { 164 | if (transmitFlag) { 165 | // system.getLogger().log(logging::LoggerLevel::LOGGER_LEVEL_DEBUG, getName(), "[%s] TX signal detected. Waiting TX", timeString().c_str()); 166 | } else { 167 | if (!_toModem.empty()) { 168 | if (config.frequencyRx == config.frequencyTx && (radio->getModemStatus() & 0x01) == 0x01) { 169 | // system.getLogger().log(logging::LoggerLevel::LOGGER_LEVEL_DEBUG, getName(), "[%s] RX signal detected. Waiting TX", timeString().c_str()); 170 | } else { 171 | std::shared_ptr msg = _toModem.getElement(); 172 | system.getLogger().log(logging::LoggerLevel::LOGGER_LEVEL_DEBUG, getName(), "[%s] Transmitting packet '%s'", timeString().c_str(), msg->toString().c_str()); 173 | 174 | int16_t state = startTX("<\xff\x01" + msg->encode()); 175 | if (state != RADIOLIB_ERR_NONE) { 176 | system.getLogger().log(logging::LoggerLevel::LOGGER_LEVEL_ERROR, getName(), "[%s] startTX failed, code %d", timeString().c_str(), state); 177 | txEnable = false; 178 | return true; 179 | } 180 | } 181 | } 182 | } 183 | } 184 | } 185 | } 186 | 187 | return true; 188 | } 189 | 190 | int16_t RadiolibTask::startRX(uint8_t mode) { 191 | if (config.frequencyTx != config.frequencyRx) { 192 | int16_t state = radio->setFrequency((float)config.frequencyRx / 1000000); 193 | if (state != RADIOLIB_ERR_NONE) { 194 | return state; 195 | } 196 | } 197 | 198 | return radio->startReceive(0, mode); 199 | } 200 | 201 | int16_t RadiolibTask::startTX(String &str) { 202 | if (config.frequencyTx != config.frequencyRx) { 203 | int16_t state = radio->setFrequency((float)config.frequencyTx / 1000000); 204 | if (state != RADIOLIB_ERR_NONE) { 205 | return state; 206 | } 207 | } 208 | 209 | transmissionState = radio->startTransmit(str); 210 | transmitFlag = true; 211 | return RADIOLIB_ERR_NONE; 212 | } 213 | -------------------------------------------------------------------------------- /lib/TimeLib/TimeLib.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | time.c - low level time and date functions 3 | Copyright (c) Michael Margolis 2009-2014 4 | 5 | This library is free software; you can redistribute it and/or 6 | modify it under the terms of the GNU Lesser General Public 7 | License as published by the Free Software Foundation; either 8 | version 2.1 of the License, or (at your option) any later version. 9 | 10 | This library is distributed in the hope that it will be useful, 11 | but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 13 | Lesser General Public License for more details. 14 | 15 | You should have received a copy of the GNU Lesser General Public 16 | License along with this library; if not, write to the Free Software 17 | Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA 18 | 19 | 1.0 6 Jan 2010 - initial release 20 | 1.1 12 Feb 2010 - fixed leap year calculation error 21 | 1.2 1 Nov 2010 - fixed setTime bug (thanks to Korman for this) 22 | 1.3 24 Mar 2012 - many edits by Paul Stoffregen: fixed timeStatus() to update 23 | status, updated examples for Arduino 1.0, fixed ARM 24 | compatibility issues, added TimeArduinoDue and TimeTeensy3 25 | examples, add error checking and messages to RTC examples, 26 | add examples to DS1307RTC library. 27 | 1.4 5 Sep 2014 - compatibility with Arduino 1.5.7 28 | */ 29 | 30 | #include 31 | 32 | #include "TimeLib.h" 33 | 34 | static tmElements_t tm; // a cache of time elements 35 | static time_t cacheTime; // the time the cache was updated 36 | static uint32_t syncInterval = 300; // time sync will be attempted after this many seconds 37 | 38 | void refreshCache(time_t t) { 39 | if (t != cacheTime) { 40 | breakTime(t, tm); 41 | cacheTime = t; 42 | } 43 | } 44 | 45 | int hour() { // the hour now 46 | return hour(now()); 47 | } 48 | 49 | int hour(time_t t) { // the hour for the given time 50 | refreshCache(t); 51 | return tm.Hour; 52 | } 53 | 54 | int hourFormat12() { // the hour now in 12 hour format 55 | return hourFormat12(now()); 56 | } 57 | 58 | int hourFormat12(time_t t) { // the hour for the given time in 12 hour format 59 | refreshCache(t); 60 | if( tm.Hour == 0 ) 61 | return 12; // 12 midnight 62 | else if( tm.Hour > 12) 63 | return tm.Hour - 12 ; 64 | else 65 | return tm.Hour ; 66 | } 67 | 68 | uint8_t isAM() { // returns true if time now is AM 69 | return !isPM(now()); 70 | } 71 | 72 | uint8_t isAM(time_t t) { // returns true if given time is AM 73 | return !isPM(t); 74 | } 75 | 76 | uint8_t isPM() { // returns true if PM 77 | return isPM(now()); 78 | } 79 | 80 | uint8_t isPM(time_t t) { // returns true if PM 81 | return (hour(t) >= 12); 82 | } 83 | 84 | int minute() { 85 | return minute(now()); 86 | } 87 | 88 | int minute(time_t t) { // the minute for the given time 89 | refreshCache(t); 90 | return tm.Minute; 91 | } 92 | 93 | int second() { 94 | return second(now()); 95 | } 96 | 97 | int second(time_t t) { // the second for the given time 98 | refreshCache(t); 99 | return tm.Second; 100 | } 101 | 102 | int day(){ 103 | return(day(now())); 104 | } 105 | 106 | int day(time_t t) { // the day for the given time (0-6) 107 | refreshCache(t); 108 | return tm.Day; 109 | } 110 | 111 | int weekday() { // Sunday is day 1 112 | return weekday(now()); 113 | } 114 | 115 | int weekday(time_t t) { 116 | refreshCache(t); 117 | return tm.Wday; 118 | } 119 | 120 | int month(){ 121 | return month(now()); 122 | } 123 | 124 | int month(time_t t) { // the month for the given time 125 | refreshCache(t); 126 | return tm.Month; 127 | } 128 | 129 | int year() { // as in Processing, the full four digit year: (2009, 2010 etc) 130 | return year(now()); 131 | } 132 | 133 | int year(time_t t) { // the year for the given time 134 | refreshCache(t); 135 | return tmYearToCalendar(tm.Year); 136 | } 137 | 138 | const String timeString() 139 | { 140 | return timeString(now()); 141 | } 142 | 143 | const String timeString(time_t t) 144 | { 145 | char line[30]; 146 | sprintf(line, "%02d:%02d:%02d", hour(t), minute(t), second(t)); 147 | return String(line); 148 | } 149 | 150 | /*============================================================================*/ 151 | /* functions to convert to and from system time */ 152 | /* These are for interfacing with time services and are not normally needed in a sketch */ 153 | 154 | // leap year calculator expects year argument as years offset from 1970 155 | #define LEAP_YEAR(Y) ( ((1970+(Y))>0) && !((1970+(Y))%4) && ( ((1970+(Y))%100) || !((1970+(Y))%400) ) ) 156 | 157 | static const uint8_t monthDays[]={31,28,31,30,31,30,31,31,30,31,30,31}; // API starts months from 1, this array starts from 0 158 | 159 | void breakTime(time_t timeInput, tmElements_t &tm){ 160 | // break the given time_t into time components 161 | // this is a more compact version of the C library localtime function 162 | // note that year is offset from 1970 !!! 163 | 164 | uint8_t year; 165 | uint8_t month, monthLength; 166 | uint32_t time; 167 | unsigned long days; 168 | 169 | time = (uint32_t)timeInput; 170 | tm.Second = time % 60; 171 | time /= 60; // now it is minutes 172 | tm.Minute = time % 60; 173 | time /= 60; // now it is hours 174 | tm.Hour = time % 24; 175 | time /= 24; // now it is days 176 | tm.Wday = ((time + 4) % 7) + 1; // Sunday is day 1 177 | 178 | year = 0; 179 | days = 0; 180 | while((unsigned)(days += (LEAP_YEAR(year) ? 366 : 365)) <= time) { 181 | year++; 182 | } 183 | tm.Year = year; // year is offset from 1970 184 | 185 | days -= LEAP_YEAR(year) ? 366 : 365; 186 | time -= days; // now it is days in this year, starting at 0 187 | 188 | days=0; 189 | month=0; 190 | monthLength=0; 191 | for (month=0; month<12; month++) { 192 | if (month==1) { // february 193 | if (LEAP_YEAR(year)) { 194 | monthLength=29; 195 | } else { 196 | monthLength=28; 197 | } 198 | } else { 199 | monthLength = monthDays[month]; 200 | } 201 | 202 | if (time >= monthLength) { 203 | time -= monthLength; 204 | } else { 205 | break; 206 | } 207 | } 208 | tm.Month = month + 1; // jan is month 1 209 | tm.Day = time + 1; // day of month 210 | } 211 | 212 | time_t makeTime(const tmElements_t &tm){ 213 | // assemble time elements into time_t 214 | // note year argument is offset from 1970 (see macros in time.h to convert to other formats) 215 | // previous version used full four digit year (or digits since 2000),i.e. 2009 was 2009 or 9 216 | 217 | int i; 218 | uint32_t seconds; 219 | 220 | // seconds from 1970 till 1 jan 00:00:00 of the given year 221 | seconds= tm.Year*(SECS_PER_DAY * 365); 222 | for (i = 0; i < tm.Year; i++) { 223 | if (LEAP_YEAR(i)) { 224 | seconds += SECS_PER_DAY; // add extra days for leap years 225 | } 226 | } 227 | 228 | // add days for this year, months start from 1 229 | for (i = 1; i < tm.Month; i++) { 230 | if ( (i == 2) && LEAP_YEAR(tm.Year)) { 231 | seconds += SECS_PER_DAY * 29; 232 | } else { 233 | seconds += SECS_PER_DAY * monthDays[i-1]; //monthDay array starts from 0 234 | } 235 | } 236 | seconds+= (tm.Day-1) * SECS_PER_DAY; 237 | seconds+= tm.Hour * SECS_PER_HOUR; 238 | seconds+= tm.Minute * SECS_PER_MIN; 239 | seconds+= tm.Second; 240 | return (time_t)seconds; 241 | } 242 | /*=====================================================*/ 243 | /* Low level system time functions */ 244 | 245 | static uint32_t sysTime = 0; 246 | static uint32_t prevMillis = 0; 247 | static uint32_t nextSyncTime = 0; 248 | static timeStatus_t Status = timeNotSet; 249 | 250 | getExternalTime getTimePtr; // pointer to external sync function 251 | //setExternalTime setTimePtr; // not used in this version 252 | 253 | #ifdef TIME_DRIFT_INFO // define this to get drift data 254 | time_t sysUnsyncedTime = 0; // the time sysTime unadjusted by sync 255 | #endif 256 | 257 | 258 | time_t now() { 259 | // calculate number of seconds passed since last call to now() 260 | while (millis() - prevMillis >= 1000) { 261 | // millis() and prevMillis are both unsigned ints thus the subtraction will always be the absolute value of the difference 262 | sysTime++; 263 | prevMillis += 1000; 264 | #ifdef TIME_DRIFT_INFO 265 | sysUnsyncedTime++; // this can be compared to the synced time to measure long term drift 266 | #endif 267 | } 268 | if (nextSyncTime <= sysTime) { 269 | if (getTimePtr != 0) { 270 | time_t t = getTimePtr(); 271 | if (t != 0) { 272 | setTime(t); 273 | } else { 274 | nextSyncTime = sysTime + syncInterval; 275 | Status = (Status == timeNotSet) ? timeNotSet : timeNeedsSync; 276 | } 277 | } 278 | } 279 | return (time_t)sysTime; 280 | } 281 | 282 | void setTime(time_t t) { 283 | #ifdef TIME_DRIFT_INFO 284 | if(sysUnsyncedTime == 0) 285 | sysUnsyncedTime = t; // store the time of the first call to set a valid Time 286 | #endif 287 | 288 | sysTime = (uint32_t)t; 289 | nextSyncTime = (uint32_t)t + syncInterval; 290 | Status = timeSet; 291 | prevMillis = millis(); // restart counting from now (thanks to Korman for this fix) 292 | } 293 | 294 | void setTime(int hr,int min,int sec,int dy, int mnth, int yr){ 295 | // year can be given as full four digit year or two digts (2010 or 10 for 2010); 296 | //it is converted to years since 1970 297 | if( yr > 99) 298 | yr = yr - 1970; 299 | else 300 | yr += 30; 301 | tm.Year = yr; 302 | tm.Month = mnth; 303 | tm.Day = dy; 304 | tm.Hour = hr; 305 | tm.Minute = min; 306 | tm.Second = sec; 307 | setTime(makeTime(tm)); 308 | } 309 | 310 | void adjustTime(long adjustment) { 311 | sysTime += adjustment; 312 | } 313 | 314 | // indicates if time has been set and recently synchronized 315 | timeStatus_t timeStatus() { 316 | now(); // required to actually update the status 317 | return Status; 318 | } 319 | 320 | void setSyncProvider( getExternalTime getTimeFunction){ 321 | getTimePtr = getTimeFunction; 322 | nextSyncTime = sysTime; 323 | now(); // this will sync the clock 324 | } 325 | 326 | void setSyncInterval(time_t interval){ // set the number of seconds between re-sync 327 | syncInterval = (uint32_t)interval; 328 | nextSyncTime = sysTime + syncInterval; 329 | } 330 | -------------------------------------------------------------------------------- /lib/Display/Bitmap.cpp: -------------------------------------------------------------------------------- 1 | #include "Bitmap.h" 2 | #include "FontConfig.h" 3 | #include "OLEDDisplay.h" 4 | //#include "OLEDDisplayFonts.h" 5 | 6 | // cppcheck-suppress unusedFunction 7 | Bitmap::Bitmap(uint width, uint height) : _width(width), _height(height), _buffer(0) { 8 | allocateBuffer(); 9 | } 10 | 11 | // cppcheck-suppress unusedFunction 12 | Bitmap::Bitmap(OLEDDisplay *display) : _width(display->getWidth()), _height(display->getHeight()), _buffer(0) { 13 | allocateBuffer(); 14 | } 15 | 16 | // cppcheck-suppress unusedFunction 17 | Bitmap::~Bitmap() { 18 | if (_buffer != 0) { 19 | delete _buffer; 20 | } 21 | } 22 | 23 | // cppcheck-suppress unusedFunction 24 | uint Bitmap::getWidth() const { 25 | return _width; 26 | } 27 | 28 | // cppcheck-suppress unusedFunction 29 | uint Bitmap::getHeight() const { 30 | return _height; 31 | } 32 | 33 | // cppcheck-suppress unusedFunction 34 | void Bitmap::setPixel(int x, int y) { 35 | if (x >= 0 && x < _width && y >= 0 && y < _height) { 36 | _buffer[x + (y / 8) * _width] |= (1 << (y % 8)); 37 | } 38 | } 39 | 40 | // cppcheck-suppress unusedFunction 41 | void Bitmap::clearPixel(int x, int y) { 42 | if (x >= 0 && x < _width && y >= 0 && y < _height) { 43 | _buffer[x + (y / 8) * _width] &= ~(1 << (y % 8)); 44 | } 45 | } 46 | 47 | // cppcheck-suppress unusedFunction 48 | bool Bitmap::getPixel(int x, int y) const { 49 | if (x >= 0 && x < _width && y >= 0 && y < _height) { 50 | return _buffer[x + (y / 8) * _width] & (1 << (y % 8)); 51 | } 52 | return false; 53 | } 54 | 55 | // cppcheck-suppress unusedFunction 56 | void Bitmap::clear() { 57 | memset(_buffer, 0, _width * _height / 8); 58 | } 59 | 60 | // cppcheck-suppress unusedFunction 61 | void Bitmap::drawLine(int x0, int y0, int x1, int y1) { 62 | int dx = abs(x1 - x0); 63 | int dy = abs(y1 - y0); 64 | int sx = x0 < x1 ? 1 : -1; 65 | int sy = y0 < y1 ? 1 : -1; 66 | int err = (dx > dy ? dx : -dy) / 2; 67 | 68 | while (true) { 69 | setPixel(x0, y0); 70 | if (x0 == x1 && y0 == y1) 71 | break; 72 | 73 | int e2 = err; 74 | if (e2 > -dx) { 75 | err -= dy; 76 | x0 += sx; 77 | } 78 | if (e2 < dy) { 79 | err += dx; 80 | y0 += sy; 81 | } 82 | } 83 | } 84 | 85 | // cppcheck-suppress unusedFunction 86 | void Bitmap::drawHorizontalLine(int x, int y, int length) { 87 | if (y < 0 || y >= _height) { 88 | return; 89 | } 90 | 91 | for (int i = 0; i < length; i++) { 92 | setPixel(x + i, y); 93 | } 94 | } 95 | 96 | // cppcheck-suppress unusedFunction 97 | void Bitmap::drawVerticalLine(int x, int y, int length) { 98 | if (x < 0 || x >= _width) { 99 | return; 100 | } 101 | 102 | for (int i = 0; i < length; i++) { 103 | setPixel(x, y + i); 104 | } 105 | } 106 | 107 | // cppcheck-suppress unusedFunction 108 | void Bitmap::drawRect(int x, int y, int width, int height) { 109 | drawHorizontalLine(x, y, width); 110 | drawVerticalLine(x, y, height); 111 | drawVerticalLine(x + width - 1, y, height); 112 | drawHorizontalLine(x, y + height - 1, width); 113 | } 114 | 115 | // cppcheck-suppress unusedFunction 116 | void Bitmap::fillRect(int x, int y, int width, int height) { 117 | for (int i = 0; i < width; i++) { 118 | drawVerticalLine(x + i, y, height); 119 | } 120 | } 121 | 122 | // cppcheck-suppress unusedFunction 123 | void Bitmap::drawCircle(int x0, int y0, int radius) { 124 | int x = 0; 125 | int y = radius; 126 | int dp = 1 - radius; 127 | 128 | do { 129 | if (dp < 0) { 130 | dp = dp + (x++) * 2 + 3; 131 | } else { 132 | dp = dp + (x++) * 2 - (y--) * 2 + 5; 133 | } 134 | 135 | setPixel(x0 + x, y0 + y); // For the 8 octants 136 | setPixel(x0 - x, y0 + y); 137 | setPixel(x0 + x, y0 - y); 138 | setPixel(x0 - x, y0 - y); 139 | setPixel(x0 + y, y0 + x); 140 | setPixel(x0 - y, y0 + x); 141 | setPixel(x0 + y, y0 - x); 142 | setPixel(x0 - y, y0 - x); 143 | } while (x < y); 144 | 145 | setPixel(x0 + radius, y0); 146 | setPixel(x0, y0 + radius); 147 | setPixel(x0 - radius, y0); 148 | setPixel(x0, y0 - radius); 149 | } 150 | 151 | // cppcheck-suppress unusedFunction 152 | void Bitmap::fillCircle(int x0, int y0, int radius) { 153 | int x = 0; 154 | int y = radius; 155 | int dp = 1 - radius; 156 | 157 | do { 158 | if (dp < 0) { 159 | dp = dp + (x++) * 2 + 3; 160 | } else { 161 | dp = dp + (x++) * 2 - (y--) * 2 + 5; 162 | } 163 | 164 | drawHorizontalLine(x0 - x, y0 - y, 2 * x); 165 | drawHorizontalLine(x0 - x, y0 + y, 2 * x); 166 | drawHorizontalLine(x0 - y, y0 - x, 2 * y); 167 | drawHorizontalLine(x0 - y, y0 + x, 2 * y); 168 | } while (x < y); 169 | 170 | drawHorizontalLine(x0 - radius, y0, 2 * radius); 171 | } 172 | 173 | // cppcheck-suppress unusedFunction 174 | void Bitmap::drawCircleQuads(int x0, int y0, int radius, int quads) { 175 | int x = 0; 176 | int y = radius; 177 | int dp = 1 - radius; 178 | 179 | while (x < y) { 180 | if (dp < 0) { 181 | dp = dp + (x++) * 2 + 3; 182 | } else { 183 | dp = dp + (x++) * 2 - (y--) * 2 + 5; 184 | } 185 | 186 | if (quads & 0x1) { 187 | setPixel(x0 + x, y0 - y); 188 | setPixel(x0 + y, y0 - x); 189 | } 190 | if (quads & 0x2) { 191 | setPixel(x0 - y, y0 - x); 192 | setPixel(x0 - x, y0 - y); 193 | } 194 | if (quads & 0x4) { 195 | setPixel(x0 - y, y0 + x); 196 | setPixel(x0 - x, y0 + y); 197 | } 198 | if (quads & 0x8) { 199 | setPixel(x0 + x, y0 + y); 200 | setPixel(x0 + y, y0 + x); 201 | } 202 | } 203 | if (quads & 0x1 && quads & 0x8) { 204 | setPixel(x0 + radius, y0); 205 | } 206 | if (quads & 0x4 && quads & 0x8) { 207 | setPixel(x0, y0 + radius); 208 | } 209 | if (quads & 0x2 && quads & 0x4) { 210 | setPixel(x0 - radius, y0); 211 | } 212 | if (quads & 0x1 && quads & 0x2) { 213 | setPixel(x0, y0 - radius); 214 | } 215 | } 216 | 217 | // cppcheck-suppress unusedFunction 218 | void Bitmap::drawProgressBar(int x, int y, int width, int height, int progress) { 219 | int radius = height / 2; 220 | int xRadius = x + radius; 221 | int yRadius = y + radius; 222 | int doubleRadius = 2 * radius; 223 | int innerRadius = radius - 2; 224 | 225 | drawCircleQuads(xRadius, yRadius, radius, 0b00000110); 226 | drawHorizontalLine(xRadius, y, width - doubleRadius + 1); 227 | drawHorizontalLine(xRadius, y + height, width - doubleRadius + 1); 228 | drawCircleQuads(x + width - radius, yRadius, radius, 0b00001001); 229 | 230 | uint16_t maxProgressWidth = (width - doubleRadius + 1) * progress / 100; 231 | 232 | fillCircle(xRadius, yRadius, innerRadius); 233 | fillRect(xRadius + 1, y + 2, maxProgressWidth, height - 3); 234 | fillCircle(xRadius + maxProgressWidth, yRadius, innerRadius); 235 | } 236 | 237 | // cppcheck-suppress unusedFunction 238 | int Bitmap::drawChar(int x, int y, char c) { 239 | fontDesc_t const *font = getSystemFont(); 240 | 241 | if (c == ' ') { 242 | return x + font->widthInPixel * 4 / 10; 243 | } 244 | 245 | unsigned char cu = (unsigned char)c; 246 | if (cu < font->firstChar || cu > font->lastChar) { 247 | cu = '?'; 248 | } 249 | 250 | int firstPixelBitPos = 0; 251 | for (int i = 0; i < (cu - font->firstChar); i++) { 252 | firstPixelBitPos = firstPixelBitPos + font->pData[i]; 253 | } 254 | firstPixelBitPos = firstPixelBitPos * font->heightInPixel; 255 | 256 | unsigned char const *pDataStart = &(font->pData[font->lastChar - font->firstChar + 1]); 257 | const int top = y; 258 | const int widthInPixel = font->pData[cu - font->firstChar]; 259 | for (int i = 0; i < widthInPixel * font->heightInPixel; i++) { 260 | int bytePos = firstPixelBitPos / 8; 261 | int bitPos = firstPixelBitPos % 8; 262 | 263 | if (pDataStart[bytePos] & (1 << bitPos)) { 264 | setPixel(x, y); 265 | } else { 266 | clearPixel(x, y); 267 | } 268 | 269 | firstPixelBitPos++; 270 | y++; 271 | if (y == top + font->heightInPixel) { 272 | y = top; 273 | x++; 274 | } 275 | } 276 | 277 | return x + FONT_CHAR_SPACING; 278 | } 279 | 280 | // cppcheck-suppress unusedFunction 281 | int Bitmap::drawString(int x, int y, String text) { 282 | int next_x = x; 283 | for (int i = 0; i < text.length(); i++) { 284 | next_x = drawChar(next_x, y, text[i]); 285 | } 286 | return next_x; 287 | } 288 | 289 | // cppcheck-suppress unusedFunction 290 | void Bitmap::drawStringf(int x, int y, char *buffer, String format, ...) { 291 | va_list myargs; 292 | va_start(myargs, format); 293 | vsprintf(buffer, format.c_str(), myargs); 294 | va_end(myargs); 295 | drawString(x, y, buffer); 296 | } 297 | 298 | // cppcheck-suppress unusedFunction 299 | int Bitmap::drawStringLF(int x, int y, String text) { 300 | fontDesc_t const *font = getSystemFont(); 301 | int next_x = x; 302 | for (int i = 0; i < text.length(); i++) { 303 | if (next_x + font->widthInPixel > _width) { 304 | next_x = 0; 305 | y += font->heightInPixel; 306 | } 307 | next_x = drawChar(next_x, y, text[i]); 308 | } 309 | return next_x; 310 | } 311 | 312 | // cppcheck-suppress unusedFunction 313 | void Bitmap::drawStringLFf(int x, int y, char *buffer, String format, ...) { 314 | va_list myargs; 315 | va_start(myargs, format); 316 | vsprintf(buffer, format.c_str(), myargs); 317 | va_end(myargs); 318 | drawStringLF(x, y, buffer); 319 | } 320 | 321 | /*void Bitmap::drawBitmap(int x, int y, const Bitmap & bitmap) 322 | { 323 | if(_width < x + bitmap.getWidth() || _height < y + bitmap.getHeight()) 324 | { 325 | return; 326 | } 327 | 328 | for(int _x = 0; _x < bitmap.getWidth(); _x++) 329 | { 330 | for(int _y = 0; _y < bitmap.getHeight(); _y++) 331 | { 332 | if(bitmap.getPixel(_x, _y)) 333 | { 334 | // _buffer[x + (y / 8) * _width] |= (1 << (y % 8)); 335 | // return _buffer[x + (y / 8) * _width] & (1 << (y % 8)); 336 | Serial.print(_x); 337 | Serial.print(" "); 338 | Serial.println(_y); 339 | setPixel(x + _x, y + _y); 340 | } 341 | else 342 | { 343 | clearPixel(x + _x, y + _y); 344 | } 345 | } 346 | } 347 | }*/ 348 | 349 | // cppcheck-suppress unusedFunction 350 | void Bitmap::allocateBuffer() { 351 | _buffer = new uint8_t[_width * _height / 8]; 352 | clear(); 353 | } 354 | -------------------------------------------------------------------------------- /lib/Display/Fonts/Terminal_8.h: -------------------------------------------------------------------------------- 1 | /* 2 | created with FontEditor written by H. Reddmann 3 | HaReddmann at t-online dot de 4 | 5 | File Name : Terminal_8.h 6 | Date : 08.10.2019 7 | Font size in bytes : 0x05E0, 1504 8 | Font width : 7 9 | Font height : 8 10 | Font first char : 0x01 11 | Font last char : 0xFE 12 | Font bits per pixel : 1 13 | Font is compressed : false 14 | 15 | The font data are defined as 16 | 17 | struct _FONT_ { 18 | // common shared fields 19 | uint16_t font_Size_in_Bytes_over_all_included_Size_it_self; 20 | uint8_t font_Width_in_Pixel_for_fixed_drawing; 21 | uint8_t font_Height_in_Pixel_for_all_Characters; 22 | uint8_t font_Bits_per_Pixels; 23 | // if MSB are set then font is a compressed font 24 | uint8_t font_First_Char; 25 | uint8_t font_Last_Char; 26 | uint8_t font_Char_Widths[font_Last_Char - font_First_Char +1]; 27 | // for each character the separate width in pixels, 28 | // characters < 128 have an implicit virtual right empty row 29 | // characters with font_Char_Widths[] == 0 are undefined 30 | 31 | // if compressed font then additional fields 32 | uint8_t font_Byte_Padding; 33 | // each Char in the table are aligned in size to this value 34 | uint8_t font_RLE_Table[3]; 35 | // Run Length Encoding Table for compression 36 | uint8_t font_Char_Size_in_Bytes[font_Last_Char - font_First_Char +1]; 37 | // for each char the size in (bytes / font_Byte_Padding) are stored, 38 | // this get us the table to seek to the right beginning of each char 39 | // in the font_data[]. 40 | 41 | // for compressed and uncompressed fonts 42 | uint8_t font_data[]; 43 | // bit field of all characters 44 | } 45 | */ 46 | 47 | #include "FontDesc.h" 48 | 49 | #ifndef Terminal_8_FONT_H 50 | #define Terminal_8_FONT_H 51 | 52 | #define Terminal_8_WIDTH 7 53 | #define Terminal_8_HEIGHT 8 54 | 55 | static unsigned char const Terminal_8_Bytes[] = { 56 | 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x02, 0x06, 0x04, 0x06, 0x05, 0x05, 0x05, 0x05, 0x05, 0x04, 57 | 0x04, 0x05, 0x03, 0x05, 0x05, 0x04, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x00, 0x00, 58 | 0x03, 0x05, 0x05, 0x04, 0x05, 0x05, 0x02, 0x02, 0x02, 0x05, 0x05, 0x02, 0x05, 0x02, 0x05, 0x05, 59 | 0x03, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x02, 0x02, 0x04, 0x05, 0x04, 0x05, 0x05, 60 | 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x03, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 61 | 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x04, 0x03, 0x05, 0x03, 0x05, 0x06, 0x02, 62 | 0x05, 0x05, 0x05, 0x05, 0x05, 0x04, 0x05, 0x04, 0x02, 0x04, 0x04, 0x02, 0x05, 0x04, 0x05, 0x05, 63 | 0x05, 0x05, 0x05, 0x04, 0x04, 0x05, 0x05, 0x04, 0x04, 0x04, 0x04, 0x01, 0x04, 0x04, 0x05, 0x06, 64 | 0x05, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x05, 0x05, 0x05, 0x06, 0x06, 0x06, 65 | 0x06, 0x06, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 66 | 0x05, 0x05, 0x05, 0x05, 0x05, 0x06, 0x05, 0x06, 0x06, 0x06, 0x06, 0x06, 0x05, 0x06, 0x06, 0x06, 67 | 0x06, 0x06, 0x04, 0x04, 0x06, 0x06, 0x06, 0x06, 0x04, 0x04, 0x04, 0x04, 0x05, 0x06, 0x04, 0x06, 68 | 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x05, 69 | 0x06, 0x06, 0x06, 0x06, 0x04, 0x05, 0x05, 0x05, 0x04, 0x06, 0x06, 0x06, 0x04, 0x05, 0x06, 0x05, 70 | 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x06, 0x05, 0x04, 0x05, 71 | 0x05, 0x06, 0x06, 0x06, 0x06, 0x06, 0x05, 0x05, 0x05, 0x03, 0x03, 0x04, 0x04, 0x05, 72 | 0x3E, 0x45, 0x51, 0x45, 0x3E, 0x3E, 0x6B, 0x6F, 0x6B, 0x3E, 0x1C, 0x3E, 0x7C, 0x3E, 0x1C, 0x18, 73 | 0x3C, 0x7E, 0x3C, 0x18, 0x30, 0x36, 0x7F, 0x36, 0x30, 0x18, 0x5C, 0x7E, 0x5C, 0x18, 0x18, 0x18, 74 | 0xFF, 0xFF, 0xE7, 0xE7, 0xFF, 0xFF, 0x3C, 0x24, 0x24, 0x3C, 0xFF, 0xC3, 0xDB, 0xDB, 0xC3, 0xFF, 75 | 0x30, 0x48, 0x4A, 0x36, 0x0E, 0x06, 0x29, 0x79, 0x29, 0x06, 0x60, 0x70, 0x3F, 0x02, 0x04, 0x60, 76 | 0x7E, 0x0A, 0x35, 0x3F, 0x2A, 0x1C, 0x36, 0x1C, 0x2A, 0x7F, 0x3E, 0x1C, 0x08, 0x08, 0x1C, 0x3E, 77 | 0x7F, 0x14, 0x36, 0x7F, 0x36, 0x14, 0x5F, 0x00, 0x5F, 0x06, 0x09, 0x7F, 0x01, 0x7F, 0x22, 0x4D, 78 | 0x55, 0x59, 0x22, 0x60, 0x60, 0x60, 0x60, 0x14, 0xB6, 0xFF, 0xB6, 0x14, 0x04, 0x06, 0x7F, 0x06, 79 | 0x04, 0x10, 0x30, 0x7F, 0x30, 0x10, 0x08, 0x08, 0x3E, 0x1C, 0x08, 0x08, 0x1C, 0x3E, 0x08, 0x08, 80 | 0x78, 0x40, 0x40, 0x40, 0x40, 0x08, 0x3E, 0x08, 0x3E, 0x08, 0x30, 0x3C, 0x3F, 0x3C, 0x30, 0x06, 81 | 0x5F, 0x06, 0x07, 0x03, 0x00, 0x07, 0x03, 0x24, 0x7E, 0x24, 0x7E, 0x24, 0x24, 0x2B, 0x6A, 0x12, 82 | 0x63, 0x13, 0x08, 0x64, 0x63, 0x36, 0x49, 0x56, 0x20, 0x50, 0x07, 0x03, 0x3E, 0x41, 0x41, 0x3E, 83 | 0x08, 0x3E, 0x1C, 0x3E, 0x08, 0x08, 0x08, 0x3E, 0x08, 0x08, 0xE0, 0x60, 0x08, 0x08, 0x08, 0x08, 84 | 0x08, 0x60, 0x60, 0x20, 0x10, 0x08, 0x04, 0x02, 0x3E, 0x51, 0x49, 0x45, 0x3E, 0x42, 0x7F, 0x40, 85 | 0x62, 0x51, 0x49, 0x49, 0x46, 0x22, 0x49, 0x49, 0x49, 0x36, 0x18, 0x14, 0x12, 0x7F, 0x10, 0x2F, 86 | 0x49, 0x49, 0x49, 0x31, 0x3C, 0x4A, 0x49, 0x49, 0x30, 0x01, 0x71, 0x09, 0x05, 0x03, 0x36, 0x49, 87 | 0x49, 0x49, 0x36, 0x06, 0x49, 0x49, 0x29, 0x1E, 0x6C, 0x6C, 0xEC, 0x6C, 0x08, 0x14, 0x22, 0x41, 88 | 0x24, 0x24, 0x24, 0x24, 0x24, 0x41, 0x22, 0x14, 0x08, 0x02, 0x01, 0x59, 0x09, 0x06, 0x3E, 0x41, 89 | 0x5D, 0x55, 0x1E, 0x7E, 0x11, 0x11, 0x11, 0x7E, 0x7F, 0x49, 0x49, 0x49, 0x36, 0x3E, 0x41, 0x41, 90 | 0x41, 0x22, 0x7F, 0x41, 0x41, 0x41, 0x3E, 0x7F, 0x49, 0x49, 0x49, 0x41, 0x7F, 0x09, 0x09, 0x09, 91 | 0x01, 0x3E, 0x41, 0x49, 0x49, 0x7A, 0x7F, 0x08, 0x08, 0x08, 0x7F, 0x41, 0x7F, 0x41, 0x30, 0x40, 92 | 0x40, 0x40, 0x3F, 0x7F, 0x08, 0x14, 0x22, 0x41, 0x7F, 0x40, 0x40, 0x40, 0x40, 0x7F, 0x02, 0x04, 93 | 0x02, 0x7F, 0x7F, 0x02, 0x04, 0x08, 0x7F, 0x3E, 0x41, 0x41, 0x41, 0x3E, 0x7F, 0x09, 0x09, 0x09, 94 | 0x06, 0x3E, 0x41, 0x51, 0x21, 0x5E, 0x7F, 0x09, 0x09, 0x19, 0x66, 0x26, 0x49, 0x49, 0x49, 0x32, 95 | 0x01, 0x01, 0x7F, 0x01, 0x01, 0x3F, 0x40, 0x40, 0x40, 0x3F, 0x1F, 0x20, 0x40, 0x20, 0x1F, 0x3F, 96 | 0x40, 0x3C, 0x40, 0x3F, 0x63, 0x14, 0x08, 0x14, 0x63, 0x07, 0x08, 0x70, 0x08, 0x07, 0x71, 0x49, 97 | 0x45, 0x43, 0x7F, 0x41, 0x41, 0x02, 0x04, 0x08, 0x10, 0x20, 0x41, 0x41, 0x7F, 0x04, 0x02, 0x01, 98 | 0x02, 0x04, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x03, 0x07, 0x20, 0x54, 0x54, 0x54, 0x78, 0x7F, 99 | 0x44, 0x44, 0x44, 0x38, 0x38, 0x44, 0x44, 0x44, 0x28, 0x38, 0x44, 0x44, 0x44, 0x7F, 0x38, 0x54, 100 | 0x54, 0x54, 0x08, 0x08, 0x7E, 0x09, 0x09, 0x18, 0xA4, 0xA4, 0xA4, 0x7C, 0x7F, 0x04, 0x04, 0x78, 101 | 0x7D, 0x40, 0x40, 0x80, 0x84, 0x7D, 0x7F, 0x10, 0x28, 0x44, 0x7F, 0x40, 0x7C, 0x04, 0x18, 0x04, 102 | 0x78, 0x7C, 0x04, 0x04, 0x78, 0x38, 0x44, 0x44, 0x44, 0x38, 0xFC, 0x44, 0x44, 0x44, 0x38, 0x38, 103 | 0x44, 0x44, 0x44, 0xFC, 0x44, 0x78, 0x44, 0x04, 0x08, 0x08, 0x54, 0x54, 0x54, 0x20, 0x04, 0x3E, 104 | 0x44, 0x24, 0x3C, 0x40, 0x20, 0x7C, 0x1C, 0x20, 0x40, 0x20, 0x1C, 0x3C, 0x60, 0x30, 0x60, 0x3C, 105 | 0x6C, 0x10, 0x10, 0x6C, 0x9C, 0xA0, 0x60, 0x3C, 0x64, 0x54, 0x54, 0x4C, 0x08, 0x3E, 0x41, 0x41, 106 | 0x77, 0x41, 0x41, 0x3E, 0x08, 0x02, 0x01, 0x02, 0x01, 0x3C, 0x26, 0x23, 0x26, 0x3C, 0x00, 0x1E, 107 | 0xA1, 0xE1, 0x21, 0x12, 0x00, 0x3D, 0x40, 0x20, 0x7D, 0x00, 0x38, 0x54, 0x54, 0x55, 0x09, 0x00, 108 | 0x20, 0x55, 0x55, 0x55, 0x78, 0x00, 0x20, 0x55, 0x54, 0x55, 0x78, 0x00, 0x20, 0x55, 0x55, 0x54, 109 | 0x78, 0x00, 0x20, 0x57, 0x55, 0x57, 0x78, 0x00, 0x1C, 0xA2, 0xE2, 0x22, 0x14, 0x00, 0x38, 0x55, 110 | 0x55, 0x55, 0x08, 0x00, 0x38, 0x55, 0x54, 0x55, 0x08, 0x00, 0x38, 0x55, 0x55, 0x54, 0x08, 0x00, 111 | 0x00, 0x01, 0x7C, 0x41, 0x00, 0x00, 0x01, 0x7D, 0x41, 0x00, 0x00, 0x01, 0x7C, 0x40, 0x00, 0x70, 112 | 0x29, 0x24, 0x29, 0x70, 0x00, 0x78, 0x2F, 0x25, 0x2F, 0x78, 0x00, 0x7C, 0x54, 0x54, 0x55, 0x45, 113 | 0x00, 0x34, 0x54, 0x7C, 0x54, 0x58, 0x00, 0x7E, 0x09, 0x7F, 0x49, 0x49, 0x00, 0x38, 0x45, 0x45, 114 | 0x39, 0x00, 0x38, 0x45, 0x44, 0x39, 0x00, 0x39, 0x45, 0x44, 0x38, 0x00, 0x3C, 0x41, 0x21, 0x7D, 115 | 0x00, 0x3D, 0x41, 0x20, 0x7C, 0x00, 0x9C, 0xA1, 0x60, 0x3D, 0x00, 0x3D, 0x42, 0x42, 0x3D, 0x00, 116 | 0x3C, 0x41, 0x40, 0x3D, 0x80, 0x70, 0x68, 0x58, 0x38, 0x04, 0x00, 0x48, 0x3E, 0x49, 0x49, 0x62, 117 | 0x00, 0x7E, 0x61, 0x5D, 0x43, 0x3F, 0x00, 0x22, 0x14, 0x08, 0x14, 0x22, 0x00, 0x40, 0x88, 0x7E, 118 | 0x09, 0x02, 0x00, 0x20, 0x54, 0x55, 0x55, 0x78, 0x00, 0x00, 0x00, 0x7D, 0x41, 0x00, 0x38, 0x44, 119 | 0x45, 0x39, 0x00, 0x3C, 0x40, 0x21, 0x7D, 0x00, 0x7A, 0x09, 0x0A, 0x71, 0x00, 0x7A, 0x11, 0x22, 120 | 0x79, 0x00, 0x08, 0x55, 0x55, 0x55, 0x5E, 0x00, 0x4E, 0x51, 0x51, 0x4E, 0x00, 0x30, 0x48, 0x4D, 121 | 0x40, 0x20, 0x3E, 0x41, 0x5D, 0x4B, 0x55, 0x3E, 0x04, 0x04, 0x04, 0x04, 0x04, 0x1C, 0x00, 0x17, 122 | 0x08, 0x4C, 0x6A, 0x50, 0x00, 0x17, 0x08, 0x34, 0x2A, 0x78, 0x00, 0x00, 0x30, 0x7D, 0x30, 0x00, 123 | 0x08, 0x14, 0x00, 0x08, 0x14, 0x00, 0x14, 0x08, 0x00, 0x14, 0x08, 0x44, 0x11, 0x44, 0x11, 0x44, 124 | 0x11, 0xAA, 0x55, 0xAA, 0x55, 0xAA, 0x55, 0xBB, 0xEE, 0xBB, 0xEE, 0xBB, 0xEE, 0x00, 0x00, 0x00, 125 | 0xFF, 0x08, 0x08, 0x08, 0xFF, 0x00, 0x70, 0x28, 0x25, 0x29, 0x70, 0x00, 0x70, 0x29, 0x25, 0x29, 126 | 0x70, 0x00, 0x70, 0x29, 0x25, 0x28, 0x70, 0x3E, 0x41, 0x5D, 0x55, 0x41, 0x3E, 0x0A, 0xFB, 0x00, 127 | 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x0A, 0xFA, 0x02, 0xFE, 0x0A, 0x0B, 0x08, 0x0F, 0x00, 0x18, 0x24, 128 | 0x66, 0x24, 0x00, 0x29, 0x2A, 0x7C, 0x2A, 0x29, 0x08, 0x08, 0x08, 0xF8, 0x00, 0x00, 0x00, 0x0F, 129 | 0x08, 0x08, 0x08, 0x08, 0x08, 0x0F, 0x08, 0x08, 0x08, 0x08, 0x08, 0xF8, 0x08, 0x08, 0x00, 0x00, 130 | 0x00, 0xFF, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0xFF, 0x08, 0x08, 131 | 0x00, 0x20, 0x56, 0x55, 0x56, 0x79, 0x00, 0x70, 0x2A, 0x25, 0x2A, 0x71, 0x00, 0x0F, 0x08, 0x0B, 132 | 0x0A, 0x0A, 0x00, 0xFE, 0x02, 0xFA, 0x0A, 0x0A, 0x0A, 0x0B, 0x08, 0x0B, 0x0A, 0x0A, 0x0A, 0xFA, 133 | 0x02, 0xFA, 0x0A, 0x0A, 0x00, 0xFF, 0x00, 0xFB, 0x0A, 0x0A, 0x0A, 0x0A, 0x0A, 0x0A, 0x0A, 0x0A, 134 | 0x0A, 0xFB, 0x00, 0xFB, 0x0A, 0x0A, 0x00, 0x5D, 0x22, 0x22, 0x22, 0x5D, 0x00, 0x22, 0x55, 0x59, 135 | 0x30, 0x00, 0x08, 0x7F, 0x49, 0x41, 0x3E, 0x00, 0x7C, 0x55, 0x55, 0x55, 0x44, 0x00, 0x7C, 0x55, 136 | 0x54, 0x55, 0x44, 0x00, 0x7C, 0x55, 0x55, 0x54, 0x44, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x44, 137 | 0x7D, 0x45, 0x00, 0x00, 0x45, 0x7D, 0x45, 0x00, 0x00, 0x45, 0x7C, 0x45, 0x08, 0x08, 0x08, 0x0F, 138 | 0x00, 0x00, 0x00, 0xF8, 0x08, 0x08, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0xF0, 0xF0, 0xF0, 139 | 0xF0, 0xF0, 0x00, 0x00, 0x00, 0x77, 0x00, 0x00, 0x45, 0x7D, 0x44, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 140 | 0x0F, 0x00, 0x3C, 0x42, 0x43, 0x3D, 0x00, 0xFE, 0x4A, 0x4A, 0x34, 0x00, 0x3C, 0x43, 0x43, 0x3D, 141 | 0x00, 0x3D, 0x43, 0x42, 0x3C, 0x00, 0x32, 0x49, 0x4A, 0x31, 0x00, 0x3A, 0x45, 0x46, 0x39, 0x00, 142 | 0xFC, 0x20, 0x20, 0x1C, 0x00, 0xFE, 0xAA, 0x28, 0x10, 0x00, 0xFF, 0xA5, 0x24, 0x18, 0x00, 0x3C, 143 | 0x40, 0x41, 0x3D, 0x00, 0x3C, 0x41, 0x41, 0x3D, 0x00, 0x3D, 0x41, 0x40, 0x3C, 0x00, 0x9C, 0xA0, 144 | 0x61, 0x3D, 0x00, 0x04, 0x08, 0x71, 0x09, 0x04, 0x00, 0x00, 0x02, 0x02, 0x02, 0x00, 0x00, 0x07, 145 | 0x03, 0x00, 0x00, 0x08, 0x08, 0x08, 0x00, 0x00, 0x24, 0x2E, 0x24, 0x00, 0x24, 0x24, 0x24, 0x24, 146 | 0x24, 0x05, 0x17, 0x0A, 0x34, 0x2A, 0x78, 0x00, 0x06, 0x09, 0x7F, 0x01, 0x7F, 0x00, 0x22, 0x4D, 147 | 0x55, 0x59, 0x22, 0x00, 0x08, 0x08, 0x2A, 0x08, 0x08, 0x00, 0x00, 0x08, 0x18, 0x18, 0x00, 0x06, 148 | 0x09, 0x09, 0x06, 0x00, 0x00, 0x08, 0x00, 0x08, 0x00, 0x00, 0x08, 0x00, 0x02, 0x0F, 0x00, 0x09, 149 | 0x0F, 0x05, 0x00, 0x09, 0x0D, 0x0A, 0x00, 0x3C, 0x3C, 0x3C, 0x3C 150 | }; 151 | 152 | static struct fontDesc_t const Terminal_8_Desc = { 153 | sizeof(Terminal_8_Bytes), // total Size 154 | 7, // width in pixel 155 | 8, // height in pixel 156 | 1, // bits per pixel 157 | 0x01, // Code of first char 158 | 0xFE, // Code of last char 159 | Terminal_8_Bytes // Data 160 | }; 161 | 162 | #endif 163 | 164 | -------------------------------------------------------------------------------- /lib/Display/Fonts/HoloLens_12.h: -------------------------------------------------------------------------------- 1 | /* 2 | created with FontEditor written by H. Reddmann 3 | HaReddmann at t-online dot de 4 | 5 | File Name : HoloLens_12.h 6 | Date : 10.03.2019 7 | Font size in bytes : 0x0D64, 3428 8 | Font width : 13 9 | Font height : 17 10 | Font first char : 0x0B 11 | Font last char : 0xFF 12 | Font bits per pixel : 1 13 | Font is compressed : false 14 | 15 | The font data are defined as 16 | 17 | struct _FONT_ { 18 | // common shared fields 19 | uint16_t font_Size_in_Bytes_over_all_included_Size_it_self; 20 | uint8_t font_Width_in_Pixel_for_fixed_drawing; 21 | uint8_t font_Height_in_Pixel_for_all_Characters; 22 | uint8_t font_Bits_per_Pixels; 23 | // if MSB are set then font is a compressed font 24 | uint8_t font_First_Char; 25 | uint8_t font_Last_Char; 26 | uint8_t font_Char_Widths[font_Last_Char - font_First_Char +1]; 27 | // for each character the separate width in pixels, 28 | // characters < 128 have an implicit virtual right empty row 29 | // characters with font_Char_Widths[] == 0 are undefined 30 | 31 | // if compressed font then additional fields 32 | uint8_t font_Byte_Padding; 33 | // each Char in the table are aligned in size to this value 34 | uint8_t font_RLE_Table[3]; 35 | // Run Length Encoding Table for compression 36 | uint8_t font_Char_Size_in_Bytes[font_Last_Char - font_First_Char +1]; 37 | // for each char the size in (bytes / font_Byte_Padding) are stored, 38 | // this get us the table to seek to the right beginning of each char 39 | // in the font_data[]. 40 | 41 | // for compressed and uncompressed fonts 42 | uint8_t font_data[]; 43 | // bit field of all characters 44 | } 45 | */ 46 | 47 | #include "FontDesc.h" 48 | 49 | #ifndef HoloLens_12_FONT_H 50 | #define HoloLens_12_FONT_H 51 | 52 | #define HoloLens_12_WIDTH 13 53 | #define HoloLens_12_HEIGHT 17 54 | 55 | static unsigned char const HoloLens_12_Bytes[] = { 56 | 0x04, 0x0A, 0x00, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 57 | 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x04, 0x08, 0x06, 0x0A, 0x07, 0x02, 0x04, 0x04, 0x06, 58 | 0x06, 0x03, 0x05, 0x02, 0x04, 0x06, 0x04, 0x06, 0x06, 0x07, 0x06, 0x06, 0x06, 0x06, 0x06, 0x02, 59 | 0x03, 0x06, 0x06, 0x06, 0x06, 0x0B, 0x08, 0x07, 0x08, 0x08, 0x07, 0x07, 0x08, 0x08, 0x02, 0x06, 60 | 0x08, 0x07, 0x0A, 0x08, 0x08, 0x08, 0x08, 0x08, 0x07, 0x08, 0x08, 0x08, 0x0C, 0x09, 0x08, 0x06, 61 | 0x03, 0x04, 0x03, 0x06, 0x08, 0x03, 0x06, 0x06, 0x06, 0x06, 0x06, 0x04, 0x06, 0x06, 0x02, 0x03, 62 | 0x06, 0x02, 0x0A, 0x06, 0x06, 0x06, 0x06, 0x04, 0x05, 0x04, 0x06, 0x06, 0x0A, 0x07, 0x06, 0x05, 63 | 0x04, 0x02, 0x04, 0x07, 0x04, 0x07, 0x00, 0x04, 0x07, 0x06, 0x09, 0x06, 0x06, 0x04, 0x10, 0x08, 64 | 0x04, 0x0C, 0x00, 0x07, 0x00, 0x00, 0x04, 0x04, 0x06, 0x06, 0x05, 0x07, 0x0D, 0x06, 0x0A, 0x06, 65 | 0x04, 0x0B, 0x00, 0x06, 0x08, 0x00, 0x03, 0x07, 0x07, 0x07, 0x07, 0x03, 0x07, 0x04, 0x0A, 0x05, 66 | 0x07, 0x07, 0x05, 0x0A, 0x07, 0x05, 0x07, 0x05, 0x05, 0x04, 0x08, 0x07, 0x03, 0x04, 0x04, 0x05, 67 | 0x07, 0x0A, 0x0B, 0x0A, 0x07, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x0C, 0x09, 0x08, 0x08, 0x08, 68 | 0x08, 0x03, 0x04, 0x04, 0x04, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x07, 0x09, 0x09, 0x09, 69 | 0x09, 0x09, 0x08, 0x09, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x0B, 0x07, 0x07, 0x07, 0x07, 70 | 0x07, 0x03, 0x04, 0x04, 0x04, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 71 | 0x07, 0x07, 0x06, 0x07, 0x06, 72 | 0xE0, 0x7F, 0xC0, 0xFF, 0x80, 0x00, 0x01, 0x01, 0x02, 0xC0, 0x01, 0xC0, 0x03, 0xC0, 0x03, 0xC0, 73 | 0x3F, 0x80, 0x7F, 0x00, 0xFF, 0x00, 0x3C, 0x00, 0xF0, 0x00, 0xC0, 0x03, 0x00, 0x07, 0xF8, 0x1F, 74 | 0xF0, 0x3F, 0x20, 0x40, 0x40, 0x80, 0x80, 0xFF, 0x01, 0xFF, 0x03, 0x02, 0x04, 0x04, 0x08, 0xF8, 75 | 0x1F, 0xF0, 0x3F, 0x20, 0x40, 0x40, 0x80, 0x80, 0xFF, 0x01, 0xFF, 0x03, 0x02, 0x04, 0x04, 0x08, 76 | 0xF8, 0x1F, 0xF0, 0x3F, 0x20, 0x40, 0x40, 0x80, 0x80, 0xFF, 0x01, 0xFF, 0x03, 0x02, 0x04, 0x04, 77 | 0x08, 0xF8, 0x1F, 0xF0, 0x3F, 0x20, 0x40, 0x40, 0x80, 0x80, 0xFF, 0x01, 0xFF, 0x03, 0x02, 0x04, 78 | 0x04, 0x08, 0xF8, 0x1F, 0xF0, 0x3F, 0x20, 0x40, 0x40, 0x80, 0x80, 0xFF, 0x01, 0xFF, 0x03, 0x02, 79 | 0x04, 0x04, 0x08, 0xF8, 0x1F, 0xF0, 0x3F, 0x20, 0x40, 0x40, 0x80, 0x80, 0xFF, 0x01, 0xFF, 0x03, 80 | 0x02, 0x04, 0x04, 0x08, 0xF8, 0x1F, 0xF0, 0x3F, 0x20, 0x40, 0x40, 0x80, 0x80, 0xFF, 0x01, 0xFF, 81 | 0x03, 0x02, 0x04, 0x04, 0x08, 0xF0, 0x17, 0xE0, 0x2F, 0xC0, 0x01, 0x80, 0x03, 0x00, 0x07, 0x00, 82 | 0x0E, 0x00, 0x00, 0x01, 0x20, 0x0E, 0xC0, 0x1F, 0xE0, 0x3F, 0xC0, 0x7F, 0x80, 0x3F, 0x00, 0x47, 83 | 0x00, 0x08, 0x00, 0x38, 0x02, 0xF8, 0x0C, 0xF8, 0x3F, 0xF0, 0x7F, 0xC0, 0x7C, 0x00, 0x71, 0x00, 84 | 0x06, 0x00, 0x1E, 0x01, 0x24, 0x03, 0x78, 0x03, 0x60, 0x03, 0x00, 0x1B, 0x00, 0x7B, 0x00, 0x93, 85 | 0x00, 0xE2, 0x01, 0x80, 0x01, 0x80, 0x03, 0xB0, 0x0F, 0xF0, 0x11, 0x20, 0x27, 0xC0, 0x7B, 0x00, 86 | 0xF3, 0x00, 0x60, 0x01, 0x0E, 0x00, 0x1C, 0x00, 0xC0, 0x07, 0xE0, 0x3F, 0xE0, 0xE0, 0x40, 0x00, 87 | 0x81, 0x00, 0x02, 0x07, 0x07, 0xFC, 0x07, 0xE0, 0x03, 0x10, 0x00, 0xA0, 0x00, 0xE0, 0x01, 0xC0, 88 | 0x03, 0x00, 0x05, 0x00, 0x02, 0x00, 0x20, 0x00, 0x40, 0x00, 0xE0, 0x03, 0xC0, 0x07, 0x00, 0x02, 89 | 0x00, 0x04, 0x00, 0x00, 0x01, 0x00, 0x03, 0x00, 0x02, 0x80, 0x00, 0x00, 0x01, 0x00, 0x02, 0x00, 90 | 0x04, 0x00, 0x08, 0x00, 0x80, 0x00, 0x00, 0x01, 0x00, 0x03, 0xF0, 0x07, 0xF8, 0x03, 0x30, 0x00, 91 | 0xC0, 0x1F, 0xC0, 0x7F, 0x80, 0x80, 0x00, 0x01, 0x01, 0xFE, 0x03, 0xF8, 0x03, 0x10, 0x00, 0x20, 92 | 0x00, 0xE0, 0x3F, 0xC0, 0x7F, 0x00, 0xC1, 0x00, 0xC3, 0x01, 0xC2, 0x02, 0xC4, 0x04, 0xF8, 0x08, 93 | 0xE0, 0x10, 0x40, 0x10, 0xC0, 0x60, 0x80, 0x88, 0x00, 0x11, 0x01, 0xFE, 0x03, 0xB8, 0x03, 0x00, 94 | 0x03, 0x00, 0x07, 0x80, 0x0B, 0x80, 0x13, 0x80, 0xFF, 0x00, 0xFF, 0x01, 0x80, 0x00, 0x7C, 0x02, 95 | 0xF8, 0x0C, 0x90, 0x10, 0x20, 0x21, 0x40, 0x7E, 0x80, 0x78, 0x00, 0xFE, 0x00, 0xFE, 0x03, 0x64, 96 | 0x04, 0x48, 0x08, 0xB0, 0x1F, 0x40, 0x1E, 0x40, 0x00, 0x80, 0xE0, 0x00, 0xF1, 0x01, 0x7A, 0x00, 97 | 0x3C, 0x00, 0x18, 0x00, 0xE0, 0x0E, 0xE0, 0x3F, 0x40, 0x44, 0x80, 0x88, 0x00, 0xFF, 0x01, 0xDC, 98 | 0x01, 0x78, 0x02, 0xF8, 0x0D, 0x10, 0x12, 0x20, 0x26, 0xC0, 0x7F, 0x00, 0x7F, 0x00, 0x08, 0x01, 99 | 0x10, 0x02, 0x00, 0x08, 0x40, 0x18, 0x80, 0x10, 0x00, 0x02, 0x00, 0x0E, 0x00, 0x36, 0x00, 0x44, 100 | 0x00, 0x8C, 0x01, 0x08, 0x02, 0x40, 0x01, 0x80, 0x02, 0x00, 0x05, 0x00, 0x0A, 0x00, 0x14, 0x00, 101 | 0x28, 0x00, 0x04, 0x01, 0x18, 0x03, 0x20, 0x02, 0xC0, 0x06, 0x00, 0x07, 0x00, 0x04, 0x00, 0x01, 102 | 0x00, 0x03, 0x00, 0xC2, 0x02, 0xC4, 0x05, 0xF8, 0x00, 0xE0, 0x00, 0x00, 0x0F, 0x80, 0x7F, 0x00, 103 | 0xC3, 0x00, 0x33, 0x03, 0xF2, 0x04, 0x24, 0x09, 0xC8, 0x13, 0xB0, 0x27, 0xC0, 0x48, 0x80, 0x1F, 104 | 0x00, 0x3C, 0x00, 0x80, 0x01, 0xE0, 0x03, 0xF0, 0x01, 0x78, 0x02, 0xF0, 0x04, 0x80, 0x0F, 0x00, 105 | 0x7C, 0x00, 0xC0, 0x00, 0xFF, 0x01, 0xFE, 0x03, 0x44, 0x04, 0x88, 0x08, 0x10, 0x11, 0xE0, 0x3F, 106 | 0x80, 0x3B, 0x00, 0x3E, 0x00, 0xFE, 0x00, 0x06, 0x03, 0x04, 0x04, 0x08, 0x08, 0x30, 0x18, 0xC0, 107 | 0x18, 0x00, 0x11, 0x80, 0xFF, 0x00, 0xFF, 0x01, 0x02, 0x02, 0x04, 0x04, 0x08, 0x08, 0x30, 0x18, 108 | 0xC0, 0x1F, 0x00, 0x1F, 0x80, 0xFF, 0x00, 0xFF, 0x01, 0x22, 0x02, 0x44, 0x04, 0x88, 0x08, 0x10, 109 | 0x11, 0x20, 0x20, 0xC0, 0x7F, 0x80, 0xFF, 0x00, 0x11, 0x00, 0x22, 0x00, 0x44, 0x00, 0x88, 0x00, 110 | 0x10, 0x00, 0x80, 0x0F, 0x80, 0x3F, 0x80, 0xC1, 0x00, 0x01, 0x01, 0x22, 0x02, 0x4C, 0x06, 0xB0, 111 | 0x0F, 0x40, 0x1F, 0xE0, 0x3F, 0xC0, 0x7F, 0x00, 0x08, 0x00, 0x10, 0x00, 0x20, 0x00, 0x40, 0x00, 112 | 0xF8, 0x0F, 0xF0, 0x1F, 0xE0, 0x3F, 0xC0, 0x7F, 0x00, 0x60, 0x00, 0xC0, 0x01, 0x00, 0x02, 0x00, 113 | 0x04, 0xF8, 0x0F, 0xF0, 0x0F, 0xE0, 0x3F, 0xC0, 0x7F, 0x00, 0x0C, 0x00, 0x3C, 0x00, 0xCC, 0x00, 114 | 0x0C, 0x03, 0x08, 0x0C, 0x00, 0x10, 0xE0, 0x3F, 0xC0, 0x7F, 0x00, 0x80, 0x00, 0x00, 0x01, 0x00, 115 | 0x02, 0x00, 0x04, 0x00, 0x08, 0xF0, 0x1F, 0xE0, 0x3F, 0x00, 0x07, 0x00, 0x3C, 0x00, 0xE0, 0x00, 116 | 0xC0, 0x01, 0xE0, 0x01, 0xE0, 0x00, 0xF0, 0x1F, 0xE0, 0x3F, 0xC0, 0x7F, 0x80, 0xFF, 0x00, 0x0E, 117 | 0x00, 0x38, 0x00, 0xC0, 0x01, 0x00, 0x07, 0xF0, 0x1F, 0xE0, 0x3F, 0x00, 0x1F, 0x00, 0x7F, 0x00, 118 | 0x83, 0x01, 0x02, 0x02, 0x04, 0x04, 0x18, 0x0C, 0xE0, 0x0F, 0x80, 0x0F, 0xC0, 0x7F, 0x80, 0xFF, 119 | 0x00, 0x11, 0x00, 0x22, 0x00, 0x44, 0x00, 0x88, 0x00, 0xF0, 0x01, 0xC0, 0x01, 0x00, 0x1F, 0x00, 120 | 0x7F, 0x00, 0x83, 0x01, 0x02, 0x02, 0x04, 0x05, 0x18, 0x0E, 0xE0, 0x1F, 0x80, 0x2F, 0xC0, 0x7F, 121 | 0x80, 0xFF, 0x00, 0x11, 0x00, 0x22, 0x00, 0x44, 0x00, 0x88, 0x01, 0xF0, 0x1F, 0xC0, 0x39, 0x80, 122 | 0x33, 0x80, 0xEF, 0x00, 0x11, 0x01, 0x22, 0x02, 0x44, 0x04, 0xB8, 0x0F, 0x60, 0x0E, 0x20, 0x00, 123 | 0x40, 0x00, 0x80, 0x00, 0x00, 0xFF, 0x01, 0xFE, 0x03, 0x04, 0x00, 0x08, 0x00, 0x10, 0x00, 0xE0, 124 | 0x0F, 0xC0, 0x3F, 0x00, 0xC0, 0x00, 0x00, 0x01, 0x00, 0x02, 0x00, 0x06, 0xF8, 0x07, 0xF0, 0x07, 125 | 0x60, 0x00, 0xC0, 0x07, 0x00, 0x3E, 0x00, 0xE0, 0x01, 0xC0, 0x03, 0xF0, 0x01, 0xF8, 0x00, 0x30, 126 | 0x00, 0x60, 0x00, 0xC0, 0x07, 0x00, 0x3E, 0x00, 0xE0, 0x01, 0xC0, 0x03, 0xF0, 0x01, 0xE0, 0x03, 127 | 0x00, 0x1E, 0x00, 0x3C, 0x00, 0x1F, 0x80, 0x0F, 0x00, 0x03, 0x00, 0x02, 0x02, 0x0C, 0x06, 0x30, 128 | 0x06, 0xC0, 0x07, 0x00, 0x07, 0x00, 0x1F, 0x00, 0x63, 0x00, 0x83, 0x01, 0x02, 0x02, 0x0C, 0x00, 129 | 0x38, 0x00, 0xC0, 0x00, 0x00, 0x3F, 0x00, 0x7E, 0x00, 0x06, 0x00, 0x07, 0x00, 0x06, 0x00, 0x04, 130 | 0x06, 0x08, 0x0F, 0x10, 0x17, 0xA0, 0x23, 0xC0, 0x43, 0x80, 0x81, 0x00, 0xFF, 0x07, 0xFE, 0x0F, 131 | 0x04, 0x10, 0x18, 0x00, 0xF0, 0x07, 0x80, 0x3F, 0x00, 0x60, 0x80, 0x00, 0x02, 0xFF, 0x07, 0xFE, 132 | 0x0F, 0x10, 0x00, 0x30, 0x00, 0x30, 0x00, 0x60, 0x00, 0x80, 0x01, 0x00, 0x02, 0x00, 0x00, 0x04, 133 | 0x00, 0x08, 0x00, 0x10, 0x00, 0x20, 0x00, 0x40, 0x00, 0x80, 0x00, 0x00, 0x01, 0x00, 0x02, 0x01, 134 | 0x00, 0x06, 0x00, 0x08, 0x00, 0x00, 0x06, 0x80, 0x1E, 0x00, 0x25, 0x00, 0x4A, 0x00, 0xFC, 0x00, 135 | 0xF0, 0x01, 0xFE, 0x03, 0xFC, 0x07, 0x40, 0x08, 0x80, 0x10, 0x00, 0x3F, 0x00, 0x3C, 0x00, 0x78, 136 | 0x00, 0xF8, 0x01, 0x10, 0x02, 0x20, 0x04, 0xC0, 0x0C, 0x00, 0x09, 0x00, 0x1E, 0x00, 0x7E, 0x00, 137 | 0x84, 0x00, 0x08, 0x01, 0xFE, 0x03, 0xFC, 0x07, 0x80, 0x07, 0x80, 0x1F, 0x00, 0x25, 0x00, 0x4A, 138 | 0x00, 0xDC, 0x00, 0xB0, 0x00, 0x10, 0x00, 0xF8, 0x07, 0xF8, 0x0F, 0x90, 0x00, 0x00, 0x9E, 0x00, 139 | 0x7E, 0x01, 0x84, 0x02, 0x08, 0x05, 0xF0, 0x0F, 0xE0, 0x0F, 0xF8, 0x0F, 0xF0, 0x1F, 0x00, 0x03, 140 | 0x00, 0x02, 0x00, 0xFC, 0x00, 0xF0, 0x01, 0xF2, 0x03, 0xE4, 0x07, 0x00, 0x20, 0x90, 0x7F, 0x20, 141 | 0x7F, 0xC0, 0x7F, 0x80, 0xFF, 0x00, 0x70, 0x00, 0xB0, 0x01, 0x20, 0x06, 0x00, 0x08, 0xF0, 0x1F, 142 | 0xE0, 0x3F, 0x00, 0x7E, 0x00, 0xFC, 0x00, 0x18, 0x00, 0x10, 0x00, 0xE0, 0x07, 0x80, 0x0F, 0x80, 143 | 0x01, 0x00, 0x01, 0x00, 0x7E, 0x00, 0xF8, 0x00, 0xF8, 0x01, 0xF0, 0x03, 0x60, 0x00, 0x40, 0x00, 144 | 0x80, 0x1F, 0x00, 0x3E, 0x00, 0x3C, 0x00, 0xFC, 0x00, 0x08, 0x01, 0x10, 0x02, 0xE0, 0x07, 0x80, 145 | 0x07, 0x80, 0x7F, 0x00, 0xFF, 0x00, 0x42, 0x00, 0x84, 0x00, 0xF8, 0x01, 0xE0, 0x01, 0xC0, 0x03, 146 | 0xC0, 0x0F, 0x80, 0x10, 0x00, 0x21, 0x00, 0xFE, 0x01, 0xFC, 0x03, 0xF8, 0x01, 0xF0, 0x03, 0x60, 147 | 0x00, 0x40, 0x00, 0x00, 0x09, 0x00, 0x37, 0x00, 0x5A, 0x00, 0xEC, 0x00, 0x90, 0x00, 0x10, 0x00, 148 | 0xF8, 0x03, 0xF0, 0x0F, 0x80, 0x10, 0x00, 0x1F, 0x00, 0x7E, 0x00, 0x80, 0x00, 0x80, 0x01, 0xF0, 149 | 0x03, 0xE0, 0x07, 0xC0, 0x00, 0x80, 0x07, 0x00, 0x3C, 0x00, 0x78, 0x00, 0x3C, 0x00, 0x18, 0x00, 150 | 0x30, 0x00, 0xE0, 0x03, 0x00, 0x0F, 0x00, 0x1E, 0x00, 0x1F, 0x00, 0x3E, 0x00, 0xF0, 0x00, 0xE0, 151 | 0x01, 0xF0, 0x01, 0x60, 0x00, 0x40, 0x08, 0x80, 0x19, 0x00, 0x1E, 0x00, 0x18, 0x00, 0x78, 0x00, 152 | 0x98, 0x01, 0x10, 0x02, 0x60, 0x10, 0xC0, 0x33, 0x00, 0x3E, 0x00, 0x3C, 0x00, 0x1E, 0x00, 0x0C, 153 | 0x00, 0x88, 0x01, 0x90, 0x03, 0xA0, 0x05, 0xC0, 0x09, 0x80, 0x11, 0x00, 0x04, 0x80, 0xFF, 0x80, 154 | 0xEF, 0x03, 0x01, 0x04, 0xFE, 0x0F, 0xFC, 0x1F, 0x08, 0x20, 0xF0, 0x7D, 0xC0, 0x7F, 0x00, 0x08, 155 | 0x00, 0x01, 0x00, 0x03, 0x00, 0x02, 0x00, 0x0C, 0x00, 0x10, 0x00, 0x30, 0x00, 0x20, 0x00, 0xC0, 156 | 0x7F, 0x80, 0xFF, 0x00, 0xFF, 0x01, 0xFE, 0x03, 0xA0, 0x00, 0xF0, 0x07, 0xF0, 0x1F, 0x20, 0x25, 157 | 0x40, 0x42, 0x80, 0xE3, 0x00, 0xC6, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x38, 0x00, 0x30, 0x00, 158 | 0x80, 0x00, 0x00, 0x01, 0xF4, 0x03, 0xFE, 0x03, 0x3E, 0x00, 0x24, 0x00, 0x08, 0x00, 0x00, 0x00, 159 | 0x00, 0x80, 0x00, 0xC0, 0x01, 0x80, 0x03, 0x00, 0x07, 0x00, 0x06, 0x00, 0x00, 0x00, 0x0C, 0x00, 160 | 0x18, 0x00, 0x00, 0x00, 0x60, 0x00, 0xC0, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x06, 0x40, 0x00, 161 | 0x80, 0x00, 0xE0, 0x1F, 0xC0, 0x3F, 0x00, 0x04, 0x00, 0x08, 0x00, 0x48, 0x00, 0x90, 0x00, 0xF8, 162 | 0x07, 0xF0, 0x0F, 0x80, 0x04, 0x00, 0x09, 0x00, 0x01, 0x00, 0x03, 0x00, 0x06, 0x00, 0x08, 0x00, 163 | 0x30, 0x00, 0xF0, 0x10, 0x20, 0x31, 0xC0, 0x3B, 0x00, 0x3B, 0x00, 0x1C, 0x00, 0x9C, 0x01, 0x8C, 164 | 0x07, 0x08, 0x09, 0x00, 0x1E, 0x00, 0x18, 0x00, 0x30, 0x00, 0xF0, 0x00, 0x20, 0x01, 0xC0, 0x03, 165 | 0x00, 0x03, 0x00, 0x00, 0xE0, 0x0C, 0xE0, 0x3B, 0x50, 0x44, 0xE0, 0x88, 0xC0, 0x11, 0x81, 0xEE, 166 | 0x03, 0x98, 0x03, 0x00, 0x00, 0x00, 0x06, 0x00, 0x1E, 0x00, 0x24, 0x00, 0x00, 0x00, 0x7C, 0x00, 167 | 0xFC, 0x01, 0x0C, 0x06, 0x08, 0x08, 0x10, 0x10, 0x60, 0x30, 0xC0, 0x7F, 0x80, 0xFF, 0x00, 0x11, 168 | 0x01, 0x22, 0x02, 0x04, 0x04, 0x00, 0x00, 0x10, 0x18, 0x28, 0x3C, 0x70, 0x5C, 0xE0, 0x8E, 0x40, 169 | 0x0F, 0x01, 0x06, 0x02, 0x00, 0x00, 0x20, 0x00, 0x70, 0x00, 0x60, 0x00, 0x00, 0x00, 0x00, 0x03, 170 | 0x00, 0x07, 0x00, 0x02, 0x00, 0x00, 0x00, 0x20, 0x00, 0x70, 0x00, 0xE0, 0x00, 0xC0, 0x01, 0x80, 171 | 0x01, 0x00, 0x00, 0x00, 0x0C, 0x00, 0x1C, 0x00, 0x38, 0x00, 0x70, 0x00, 0x20, 0x00, 0x00, 0x00, 172 | 0x00, 0x38, 0x00, 0x70, 0x00, 0xE0, 0x00, 0xC0, 0x01, 0x00, 0x01, 0x00, 0x02, 0x00, 0x04, 0x00, 173 | 0x08, 0x00, 0x10, 0x00, 0x20, 0x00, 0x40, 0x00, 0x80, 0x00, 0x00, 0x01, 0x00, 0x02, 0x00, 0x04, 174 | 0x00, 0x08, 0x00, 0x10, 0x00, 0x20, 0x00, 0x40, 0x00, 0x80, 0x00, 0x00, 0x01, 0x00, 0x02, 0x00, 175 | 0x04, 0x00, 0x08, 0x40, 0x00, 0xC0, 0x00, 0x80, 0x01, 0x00, 0x03, 0x00, 0x06, 0x00, 0x04, 0x00, 176 | 0x00, 0x00, 0x40, 0x00, 0x80, 0x07, 0x00, 0x0F, 0x00, 0x1E, 0x00, 0x3C, 0x00, 0x70, 0x00, 0xE0, 177 | 0x00, 0xE0, 0x01, 0xC0, 0x03, 0x00, 0x00, 0x00, 0x90, 0x00, 0x72, 0x03, 0xAC, 0x05, 0xD8, 0x0E, 178 | 0x10, 0x09, 0x00, 0x00, 0x00, 0x24, 0x00, 0x78, 0x00, 0x60, 0x00, 0x00, 0x00, 0xC0, 0x03, 0xC0, 179 | 0x0F, 0x80, 0x10, 0x00, 0x21, 0x00, 0x7E, 0x00, 0xFC, 0x00, 0x28, 0x01, 0x50, 0x02, 0xE0, 0x06, 180 | 0x80, 0x05, 0x00, 0x00, 0x00, 0x31, 0x40, 0x72, 0x80, 0xB5, 0x00, 0x3B, 0x01, 0x32, 0x02, 0x0C, 181 | 0x00, 0x38, 0x00, 0xC8, 0x00, 0x10, 0x3F, 0x20, 0x7E, 0x40, 0x06, 0x00, 0x07, 0x00, 0x06, 0x00, 182 | 0x00, 0x00, 0x40, 0x3F, 0x80, 0x7E, 0x00, 0x00, 0x00, 0x3C, 0x00, 0xFC, 0x01, 0xF8, 0x03, 0xF8, 183 | 0x03, 0x70, 0x06, 0x80, 0x04, 0x00, 0x00, 0xC0, 0x32, 0xC0, 0x7F, 0x80, 0xBC, 0x00, 0x13, 0x01, 184 | 0x24, 0x02, 0x00, 0x04, 0x00, 0x00, 0xA0, 0x0B, 0xC0, 0x1F, 0x00, 0x11, 0x00, 0x22, 0x00, 0xFE, 185 | 0x00, 0x74, 0x01, 0x00, 0x00, 0xB8, 0x02, 0xF0, 0x05, 0x00, 0x3F, 0x00, 0x7E, 0x00, 0x2F, 0x00, 186 | 0x56, 0x00, 0x00, 0x00, 0x7C, 0x1F, 0xF8, 0x3E, 0x00, 0x00, 0xC0, 0x46, 0xC0, 0x9F, 0x81, 0x64, 187 | 0x02, 0x99, 0x04, 0xE6, 0x0F, 0x88, 0x0D, 0x10, 0x00, 0x20, 0x00, 0x40, 0x00, 0x80, 0x00, 0x00, 188 | 0x3E, 0x00, 0xFE, 0x00, 0x76, 0x03, 0xF4, 0x05, 0x28, 0x0A, 0x50, 0x14, 0xA0, 0x28, 0xC0, 0x60, 189 | 0x00, 0x7F, 0x00, 0x7C, 0x00, 0x00, 0x00, 0x20, 0x01, 0xE8, 0x02, 0xF0, 0x05, 0xC0, 0x0B, 0x00, 190 | 0x00, 0x00, 0x20, 0x00, 0xE0, 0x00, 0xE0, 0x03, 0xC0, 0x07, 0x80, 0x0D, 0x00, 0x11, 0x00, 0x00, 191 | 0x00, 0x04, 0x00, 0x08, 0x00, 0x10, 0x00, 0x20, 0x00, 0xC0, 0x00, 0x80, 0x01, 0x00, 0x02, 0x00, 192 | 0x04, 0x00, 0x08, 0x00, 0x10, 0x00, 0x20, 0x00, 0xF8, 0x00, 0xF8, 0x03, 0x18, 0x0C, 0xD0, 0x17, 193 | 0xA0, 0x2F, 0x40, 0x4D, 0x80, 0xBE, 0x00, 0xCB, 0x01, 0xFC, 0x01, 0xF0, 0x01, 0x02, 0x00, 0x04, 194 | 0x00, 0x08, 0x00, 0x10, 0x00, 0x20, 0x00, 0x40, 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 195 | 0x70, 0x00, 0xE0, 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x10, 0x01, 0x20, 0x02, 0xF0, 0x05, 0xE0, 196 | 0x0B, 0x00, 0x11, 0x00, 0x22, 0x80, 0x04, 0x80, 0x0D, 0x00, 0x1D, 0x00, 0x2E, 0x00, 0x48, 0x00, 197 | 0x50, 0x00, 0xB0, 0x01, 0xA0, 0x02, 0xC0, 0x07, 0x00, 0x05, 0x00, 0x00, 0x00, 0x04, 0x00, 0x0C, 198 | 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0xFF, 0x00, 0xFE, 0x01, 0x80, 0x00, 0x00, 0x01, 0xF0, 0x03, 199 | 0xE0, 0x07, 0x00, 0x08, 0xE0, 0x00, 0xE0, 0x03, 0xC0, 0xFF, 0x81, 0xFF, 0x03, 0xFF, 0x07, 0xFE, 200 | 0x0F, 0x04, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x06, 201 | 0x00, 0x0C, 0x00, 0x00, 0x10, 0x00, 0xF0, 0x01, 0xE0, 0x03, 0x00, 0x00, 0x00, 0x27, 0x00, 0x5F, 202 | 0x00, 0xBE, 0x00, 0x38, 0x01, 0x00, 0x00, 0x00, 0x11, 0x00, 0x36, 0x00, 0x7C, 0x00, 0xF8, 0x00, 203 | 0xE0, 0x00, 0x80, 0x00, 0x08, 0x00, 0xF8, 0x00, 0xF0, 0x11, 0x00, 0x38, 0x00, 0x3C, 0x00, 0x1C, 204 | 0x00, 0xCE, 0x00, 0xCE, 0x01, 0xC4, 0x07, 0x80, 0x0F, 0x20, 0x00, 0xE0, 0x03, 0xC0, 0x47, 0x00, 205 | 0xE0, 0x00, 0xF0, 0x00, 0x70, 0x00, 0xB8, 0x04, 0xB8, 0x0D, 0x10, 0x1D, 0x00, 0x2E, 0x00, 0x48, 206 | 0x00, 0x05, 0x00, 0x1B, 0x00, 0x2A, 0x02, 0x7C, 0x07, 0xD0, 0x07, 0x80, 0x03, 0xC0, 0x19, 0xC0, 207 | 0x39, 0x80, 0xF8, 0x00, 0xF0, 0x01, 0x00, 0x00, 0x00, 0x0C, 0x00, 0x3C, 0x80, 0x4E, 0x00, 0x8D, 208 | 0x00, 0x80, 0x01, 0x00, 0x01, 0x80, 0x01, 0xE0, 0x03, 0xF1, 0x01, 0x7E, 0x02, 0xF8, 0x04, 0x80, 209 | 0x0F, 0x00, 0x7C, 0x00, 0xC0, 0x00, 0x80, 0x01, 0xE0, 0x03, 0xF0, 0x01, 0x7C, 0x02, 0xFC, 0x04, 210 | 0x88, 0x0F, 0x00, 0x7C, 0x00, 0xC0, 0x00, 0x80, 0x01, 0xE0, 0x03, 0xF2, 0x01, 0x7E, 0x02, 0xFC, 211 | 0x04, 0x90, 0x0F, 0x00, 0x7C, 0x00, 0xC0, 0x00, 0x80, 0x01, 0xE1, 0x03, 0xF3, 0x01, 0x7E, 0x02, 212 | 0xFC, 0x04, 0x98, 0x0F, 0x10, 0x7C, 0x00, 0xC0, 0x00, 0x80, 0x01, 0xE1, 0x03, 0xF2, 0x01, 0x78, 213 | 0x02, 0xF0, 0x04, 0x90, 0x0F, 0x20, 0x7C, 0x00, 0xC0, 0x00, 0x80, 0x01, 0xE0, 0x03, 0xF1, 0x01, 214 | 0x7F, 0x02, 0xFE, 0x04, 0x88, 0x0F, 0x00, 0x7C, 0x00, 0xC0, 0x00, 0x00, 0x01, 0x80, 0x03, 0x80, 215 | 0x03, 0xC0, 0x01, 0xE0, 0x03, 0xE0, 0x04, 0xC0, 0x7F, 0x80, 0xFF, 0x00, 0x11, 0x01, 0x22, 0x02, 216 | 0x44, 0x04, 0x08, 0x08, 0x00, 0x00, 0x80, 0x0F, 0x80, 0x3F, 0x80, 0xC1, 0x00, 0x01, 0x05, 0x02, 217 | 0x0E, 0x0C, 0x1E, 0x30, 0x06, 0x40, 0x04, 0x00, 0x00, 0xC0, 0x7F, 0x80, 0xFF, 0x40, 0x11, 0x81, 218 | 0x23, 0x02, 0x46, 0x04, 0x88, 0x08, 0x10, 0x10, 0x00, 0x00, 0xC0, 0x7F, 0x80, 0xFF, 0x00, 0x11, 219 | 0x01, 0x23, 0x02, 0x47, 0x04, 0x8A, 0x08, 0x10, 0x10, 0x00, 0x00, 0xC0, 0x7F, 0x80, 0xFF, 0x80, 220 | 0x11, 0x81, 0x23, 0x02, 0x47, 0x04, 0x8C, 0x08, 0x10, 0x10, 0x00, 0x00, 0xC0, 0x7F, 0xA0, 0xFF, 221 | 0x40, 0x11, 0x01, 0x22, 0x02, 0x45, 0x04, 0x8A, 0x08, 0x10, 0x10, 0x08, 0x00, 0xF0, 0x7F, 0xC0, 222 | 0xFF, 0x00, 0x00, 0x00, 0xFF, 0x03, 0xFF, 0x07, 0x02, 0x00, 0x08, 0x00, 0xF8, 0x3F, 0xF0, 0x7F, 223 | 0x40, 0x00, 0x40, 0x00, 0x80, 0xFE, 0x03, 0xFD, 0x07, 0x02, 0x00, 0x00, 0x01, 0xE0, 0x3F, 0xC0, 224 | 0x7F, 0x80, 0x88, 0x00, 0x11, 0x01, 0x02, 0x02, 0x0C, 0x06, 0xF0, 0x07, 0xC0, 0x07, 0x00, 0x00, 225 | 0xC0, 0x7F, 0xC0, 0xFF, 0xC0, 0x0E, 0x80, 0x39, 0x00, 0xC3, 0x01, 0x06, 0x07, 0xF4, 0x1F, 0xE0, 226 | 0x3F, 0x00, 0x00, 0x00, 0x3E, 0x00, 0xFE, 0x80, 0x06, 0x03, 0x07, 0x04, 0x0C, 0x08, 0x30, 0x18, 227 | 0xC0, 0x1F, 0x00, 0x1F, 0x00, 0x00, 0x00, 0x7C, 0x00, 0xFC, 0x01, 0x0C, 0x06, 0x0C, 0x08, 0x1C, 228 | 0x10, 0x68, 0x30, 0x80, 0x3F, 0x00, 0x3E, 0x00, 0x00, 0x00, 0xF8, 0x00, 0xF8, 0x03, 0x1C, 0x0C, 229 | 0x1C, 0x10, 0x38, 0x20, 0xE0, 0x60, 0x00, 0x7F, 0x00, 0x7C, 0x00, 0x00, 0x00, 0xF0, 0x01, 0xF4, 230 | 0x07, 0x3C, 0x18, 0x38, 0x20, 0x70, 0x40, 0xE0, 0xC1, 0x40, 0xFE, 0x00, 0xF8, 0x00, 0x00, 0x00, 231 | 0xE0, 0x03, 0xE0, 0x0F, 0x68, 0x30, 0x50, 0x40, 0xA0, 0x80, 0x40, 0x83, 0x01, 0xFC, 0x01, 0xF0, 232 | 0x01, 0x00, 0x00, 0x40, 0x04, 0x80, 0x0D, 0x00, 0x0E, 0x00, 0x1C, 0x00, 0x6C, 0x00, 0x88, 0x00, 233 | 0x00, 0x00, 0xE0, 0x0B, 0xE0, 0x1F, 0x60, 0x3C, 0x40, 0x5C, 0x80, 0x8E, 0x00, 0x8F, 0x01, 0xFE, 234 | 0x01, 0xF4, 0x01, 0x00, 0x00, 0xF0, 0x07, 0xE0, 0x1F, 0x10, 0x60, 0x60, 0x80, 0x80, 0x00, 0x01, 235 | 0x00, 0x03, 0xFC, 0x03, 0xF8, 0x03, 0x00, 0x00, 0xE0, 0x0F, 0xC0, 0x3F, 0x00, 0xC0, 0x80, 0x00, 236 | 0x81, 0x01, 0x02, 0x01, 0x06, 0xF8, 0x07, 0xF0, 0x07, 0x00, 0x00, 0xC0, 0x1F, 0x80, 0x7F, 0x80, 237 | 0x80, 0x81, 0x01, 0x02, 0x03, 0x04, 0x04, 0x0C, 0xF0, 0x0F, 0xE0, 0x0F, 0x00, 0x00, 0x80, 0x3F, 238 | 0x00, 0xFF, 0x80, 0x00, 0x03, 0x01, 0x04, 0x02, 0x08, 0x04, 0x18, 0xE0, 0x1F, 0xC0, 0x1F, 0x80, 239 | 0x01, 0x00, 0x07, 0x00, 0x18, 0x00, 0xE2, 0x07, 0xC6, 0x0F, 0xC4, 0x00, 0xE0, 0x00, 0xC0, 0x00, 240 | 0x00, 0x00, 0x00, 0xFF, 0x01, 0xFE, 0x03, 0x10, 0x01, 0x20, 0x02, 0x40, 0x04, 0x80, 0x08, 0x00, 241 | 0x1F, 0x00, 0x1C, 0x00, 0x00, 0x00, 0xFC, 0x03, 0xFC, 0x07, 0xC8, 0x08, 0xF0, 0x13, 0xC0, 0x3C, 242 | 0x00, 0x30, 0x00, 0x00, 0x00, 0xC0, 0x00, 0xD2, 0x03, 0xAC, 0x04, 0x50, 0x09, 0x80, 0x1F, 0x00, 243 | 0x3E, 0x00, 0x00, 0x00, 0x60, 0x00, 0xE8, 0x01, 0x54, 0x02, 0xAC, 0x04, 0xC8, 0x0F, 0x00, 0x1F, 244 | 0x00, 0x00, 0x00, 0x30, 0x00, 0xF5, 0x00, 0x2B, 0x01, 0x56, 0x02, 0xE8, 0x07, 0x80, 0x0F, 0x00, 245 | 0x00, 0x40, 0x18, 0xC0, 0x7A, 0x80, 0x95, 0x00, 0x2B, 0x01, 0xF6, 0x03, 0xC4, 0x07, 0x00, 0x00, 246 | 0x00, 0x0C, 0x40, 0x3D, 0x80, 0x4A, 0x00, 0x95, 0x00, 0xFA, 0x01, 0xE0, 0x03, 0x00, 0x00, 0x00, 247 | 0x06, 0x90, 0x1E, 0x70, 0x25, 0xE0, 0x4A, 0x80, 0xFC, 0x00, 0xF0, 0x01, 0x00, 0x00, 0x00, 0x03, 248 | 0x40, 0x0F, 0x80, 0x12, 0x00, 0x25, 0x00, 0x7E, 0x00, 0xFC, 0x00, 0x28, 0x01, 0x50, 0x02, 0xE0, 249 | 0x06, 0x80, 0x05, 0x00, 0x00, 0x00, 0x1E, 0x00, 0x7E, 0x01, 0x84, 0x03, 0x08, 0x07, 0x30, 0x03, 250 | 0x40, 0x02, 0x00, 0x00, 0x00, 0x0F, 0x20, 0x3F, 0xC0, 0x4A, 0x00, 0x95, 0x00, 0xB8, 0x01, 0x60, 251 | 0x01, 0x00, 0x00, 0x80, 0x07, 0x80, 0x1F, 0x40, 0x25, 0xC0, 0x4A, 0x80, 0xDC, 0x00, 0xB0, 0x00, 252 | 0x00, 0x00, 0xC0, 0x03, 0xD0, 0x0F, 0xB0, 0x12, 0x60, 0x25, 0x80, 0x6E, 0x00, 0x58, 0x00, 0x00, 253 | 0x00, 0xE0, 0x01, 0xE8, 0x07, 0x50, 0x09, 0xA0, 0x12, 0x40, 0x37, 0x00, 0x2C, 0x80, 0x00, 0x00, 254 | 0xFB, 0x01, 0xF4, 0x03, 0x00, 0x00, 0xD0, 0x0F, 0xB0, 0x1F, 0x20, 0x00, 0x80, 0x00, 0x80, 0xFD, 255 | 0x00, 0xFB, 0x01, 0x04, 0x00, 0x08, 0x00, 0xD0, 0x0F, 0xA0, 0x1F, 0x40, 0x00, 0x00, 0x00, 0x00, 256 | 0x70, 0x00, 0xF5, 0x01, 0x2E, 0x02, 0x7C, 0x04, 0xE8, 0x0F, 0x00, 0x0F, 0x00, 0x00, 0x80, 0x7E, 257 | 0x80, 0xFD, 0x00, 0x1B, 0x00, 0x16, 0x00, 0xEC, 0x07, 0x88, 0x0F, 0x00, 0x00, 0x00, 0x1E, 0x40, 258 | 0x7E, 0x80, 0x85, 0x00, 0x0A, 0x01, 0xF0, 0x03, 0xC0, 0x03, 0x00, 0x00, 0x00, 0x0F, 0x00, 0x3F, 259 | 0x80, 0x42, 0x80, 0x85, 0x00, 0xF9, 0x01, 0xE0, 0x01, 0x00, 0x00, 0x80, 0x07, 0xA0, 0x1F, 0x60, 260 | 0x21, 0xC0, 0x42, 0x00, 0xFD, 0x00, 0xF0, 0x00, 0x00, 0x00, 0xC8, 0x03, 0xD8, 0x0F, 0xB0, 0x10, 261 | 0x60, 0x21, 0xC0, 0x7E, 0x80, 0x78, 0x00, 0x00, 0x00, 0xE0, 0x01, 0xE8, 0x07, 0x50, 0x08, 0xA0, 262 | 0x10, 0x40, 0x3F, 0x00, 0x3C, 0x00, 0x00, 0x00, 0x10, 0x00, 0x20, 0x00, 0x50, 0x01, 0xA0, 0x02, 263 | 0x00, 0x01, 0x00, 0x02, 0x00, 0x00, 0x00, 0xF8, 0x00, 0xF8, 0x01, 0xD0, 0x03, 0xE0, 0x05, 0xC0, 264 | 0x0F, 0x80, 0x0F, 0x00, 0x00, 0x00, 0x3E, 0x80, 0xFC, 0x00, 0x03, 0x01, 0x04, 0x03, 0xE0, 0x07, 265 | 0xC0, 0x0F, 0x00, 0x00, 0x00, 0x1F, 0x00, 0x7E, 0x00, 0x81, 0x00, 0x83, 0x01, 0xF2, 0x03, 0xE0, 266 | 0x07, 0x00, 0x00, 0x80, 0x0F, 0x40, 0x3F, 0xC0, 0x40, 0x80, 0xC1, 0x00, 0xFA, 0x01, 0xF0, 0x03, 267 | 0x00, 0x00, 0xC0, 0x07, 0xA0, 0x1F, 0x40, 0x20, 0x80, 0x60, 0x00, 0xFD, 0x00, 0xF8, 0x01, 0x30, 268 | 0x08, 0xE0, 0x19, 0x10, 0x1F, 0x30, 0x1E, 0x20, 0x0F, 0x00, 0x06, 0x00, 0x00, 0x00, 0xFF, 0x07, 269 | 0xFE, 0x0F, 0x20, 0x04, 0x40, 0x08, 0x80, 0x1F, 0x00, 0x1E, 0x00, 0x06, 0x01, 0x3D, 0x03, 0xE2, 270 | 0x03, 0xC4, 0x03, 0xE8, 0x01, 0xC0, 0x00, 0x00 271 | }; 272 | 273 | static struct fontDesc_t const HoloLens_12_Desc = { 274 | sizeof(HoloLens_12_Bytes), // total Size 275 | 13, // width in pixel 276 | 17, // height in pixel 277 | 1, // bits per pixel 278 | 0x0B, // Code of first char 279 | 0xFF, // Code of last char 280 | HoloLens_12_Bytes // Data 281 | }; 282 | 283 | #endif 284 | 285 | --------------------------------------------------------------------------------