├── .github ├── FUNDING.yml └── stale.yml ├── .gitignore ├── .travis.yml ├── .vscode ├── extensions.json ├── settings.json └── templates │ ├── cpp.lict │ └── h.lict ├── CMakeLists.txt ├── LICENSE.md ├── README.md ├── append_version_to_progname.py ├── boards └── hb-rf-eth.json ├── build └── CMakeFiles │ └── cmake.check_cache ├── build_webui.py ├── include ├── README ├── dcf.h ├── ethernet.h ├── gps.h ├── hmframe.h ├── led.h ├── linereader.h ├── mdnsserver.h ├── ntpclient.h ├── ntpserver.h ├── pins.h ├── pushbuttonhandler.h ├── radiomoduleconnector.h ├── radiomoduledetector.h ├── radiomoduledetector_utils.h ├── rawuartudplistener.h ├── rtc.h ├── settings.h ├── streamparser.h ├── sysinfo.h ├── systemclock.h ├── udphelper.h ├── updatecheck.h └── webui.h ├── lib └── README ├── partitions.csv ├── platformio.ini ├── sdkconfig.hb-rf-eth ├── src ├── CMakeLists.txt ├── dcf.cpp ├── ethernet.cpp ├── gps.cpp ├── hmframe.cpp ├── led.cpp ├── linereader.cpp ├── main.cpp ├── mdnsserver.cpp ├── ntpclient.cpp ├── ntpserver.cpp ├── pushbuttonhandler.cpp ├── radiomoduleconnector.cpp ├── radiomoduledetector.cpp ├── rawuartudplistener.cpp ├── rtc.cpp ├── settings.cpp ├── streamparser.cpp ├── sysinfo.cpp ├── systemclock.cpp ├── updatecheck.cpp └── webui.cpp ├── test └── README ├── version.txt └── webui ├── .babelrc ├── .compressrc ├── favicon.ico ├── index.htm ├── package-lock.json ├── package.json └── src ├── about.vue ├── app.vue ├── firmwareupdate.vue ├── header.vue ├── home.vue ├── login.vue ├── main.js ├── settings.vue └── sysinfo.vue /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: [alexreinert] 2 | custom: ['https://www.amazon.de/gp/registry/wishlist/3NNUQIQO20AAP/ref=nav_wishlist_lists_1', 'https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=WUC7QU84EU7DA'] 3 | -------------------------------------------------------------------------------- /.github/stale.yml: -------------------------------------------------------------------------------- 1 | # Number of days of inactivity before an issue becomes stale 2 | daysUntilStale: 60 3 | # Number of days of inactivity before a stale issue is closed 4 | daysUntilClose: 7 5 | # Issues with these labels will never be considered stale 6 | exemptLabels: 7 | - pinned 8 | - security 9 | # Label to use when marking an issue as stale 10 | staleLabel: stale 11 | # Comment to post when marking an issue as stale. Set to `false` to disable 12 | markComment: > 13 | This issue has been automatically marked as stale because it has not had recent activity. 14 | It will be closed if no further activity occurs. Thank you for your contributions. 15 | # Comment to post when closing a stale issue. Set to `false` to disable 16 | closeComment: false 17 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .pio 2 | .vscode/.browse.c_cpp.db* 3 | .vscode/c_cpp_properties.json 4 | .vscode/launch.json 5 | .vscode/ipch 6 | src/webui/*.gzip 7 | webui/.cache 8 | webui/dist 9 | webui/node_modules 10 | 11 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | # Continuous Integration (CI) is the practice, in software 2 | # engineering, of merging all developer working copies with a shared mainline 3 | # several times a day < https://docs.platformio.org/page/ci/index.html > 4 | # 5 | # Documentation: 6 | # 7 | # * Travis CI Embedded Builds with PlatformIO 8 | # < https://docs.travis-ci.com/user/integration/platformio/ > 9 | # 10 | # * PlatformIO integration with Travis CI 11 | # < https://docs.platformio.org/page/ci/travis.html > 12 | # 13 | # * User Guide for `platformio ci` command 14 | # < https://docs.platformio.org/page/userguide/cmd_ci.html > 15 | # 16 | # 17 | # Please choose one of the following templates (proposed below) and uncomment 18 | # it (remove "# " before each line) or use own configuration according to the 19 | # Travis CI documentation (see above). 20 | # 21 | 22 | 23 | # 24 | # Template #1: General project. Test it using existing `platformio.ini`. 25 | # 26 | 27 | # language: python 28 | # python: 29 | # - "2.7" 30 | # 31 | # sudo: false 32 | # cache: 33 | # directories: 34 | # - "~/.platformio" 35 | # 36 | # install: 37 | # - pip install -U platformio 38 | # - platformio update 39 | # 40 | # script: 41 | # - platformio run 42 | 43 | 44 | # 45 | # Template #2: The project is intended to be used as a library with examples. 46 | # 47 | 48 | # language: python 49 | # python: 50 | # - "2.7" 51 | # 52 | # sudo: false 53 | # cache: 54 | # directories: 55 | # - "~/.platformio" 56 | # 57 | # env: 58 | # - PLATFORMIO_CI_SRC=path/to/test/file.c 59 | # - PLATFORMIO_CI_SRC=examples/file.ino 60 | # - PLATFORMIO_CI_SRC=path/to/test/directory 61 | # 62 | # install: 63 | # - pip install -U platformio 64 | # - platformio update 65 | # 66 | # script: 67 | # - platformio ci --lib="." --board=ID_1 --board=ID_2 --board=ID_N 68 | -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | // See http://go.microsoft.com/fwlink/?LinkId=827846 3 | // for the documentation about the extensions.json format 4 | "recommendations": [ 5 | "platformio.platformio-ide" 6 | ], 7 | "unwantedRecommendations": [ 8 | "ms-vscode.cpptools-extension-pack" 9 | ] 10 | } 11 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "files.associations": { 3 | "typeinfo": "cpp", 4 | "unordered_map": "cpp", 5 | "initializer_list": "cpp", 6 | "atomic": "cpp", 7 | "array": "cpp", 8 | "*.tcc": "cpp", 9 | "cctype": "cpp", 10 | "chrono": "cpp", 11 | "clocale": "cpp", 12 | "cmath": "cpp", 13 | "codecvt": "cpp", 14 | "condition_variable": "cpp", 15 | "csignal": "cpp", 16 | "cstdarg": "cpp", 17 | "cstddef": "cpp", 18 | "cstdint": "cpp", 19 | "cstdio": "cpp", 20 | "cstdlib": "cpp", 21 | "cstring": "cpp", 22 | "ctime": "cpp", 23 | "cwchar": "cpp", 24 | "cwctype": "cpp", 25 | "deque": "cpp", 26 | "list": "cpp", 27 | "vector": "cpp", 28 | "exception": "cpp", 29 | "algorithm": "cpp", 30 | "functional": "cpp", 31 | "iterator": "cpp", 32 | "map": "cpp", 33 | "memory": "cpp", 34 | "memory_resource": "cpp", 35 | "numeric": "cpp", 36 | "optional": "cpp", 37 | "random": "cpp", 38 | "ratio": "cpp", 39 | "set": "cpp", 40 | "string": "cpp", 41 | "string_view": "cpp", 42 | "system_error": "cpp", 43 | "tuple": "cpp", 44 | "type_traits": "cpp", 45 | "utility": "cpp", 46 | "fstream": "cpp", 47 | "future": "cpp", 48 | "iosfwd": "cpp", 49 | "istream": "cpp", 50 | "limits": "cpp", 51 | "mutex": "cpp", 52 | "new": "cpp", 53 | "ostream": "cpp", 54 | "sstream": "cpp", 55 | "stdexcept": "cpp", 56 | "streambuf": "cpp", 57 | "thread": "cpp", 58 | "cinttypes": "cpp", 59 | "*.ipp": "cpp", 60 | "esp_transport.h": "c" 61 | }, 62 | "license-header-manager.excludeExtensions": [ 63 | ".js", 64 | ".css", 65 | ".py", 66 | ".vue" 67 | ] 68 | } -------------------------------------------------------------------------------- /.vscode/templates/cpp.lict: -------------------------------------------------------------------------------- 1 | %(File) is part of the HB-RF-ETH firmware - https://github.com/alexreinert/HB-RF-ETH 2 | 3 | Copyright %(CurrentYear) Alexander Reinert 4 | 5 | The HB-RF-ETH firmware is licensed under a 6 | Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International License. 7 | 8 | You should have received a copy of the license along with this 9 | work. If not, see . 10 | 11 | Unless required by applicable law or agreed to in writing, software 12 | distributed under the License is distributed on an "AS IS" BASIS, 13 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | See the License for the specific language governing permissions and 15 | limitations under the License. 16 | -------------------------------------------------------------------------------- /.vscode/templates/h.lict: -------------------------------------------------------------------------------- 1 | %(File) is part of the HB-RF-ETH firmware - https://github.com/alexreinert/HB-RF-ETH 2 | 3 | Copyright %(CurrentYear) Alexander Reinert 4 | 5 | The HB-RF-ETH firmware is licensed under a 6 | Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International License. 7 | 8 | You should have received a copy of the license along with this 9 | work. If not, see . 10 | 11 | Unless required by applicable law or agreed to in writing, software 12 | distributed under the License is distributed on an "AS IS" BASIS, 13 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | See the License for the specific language governing permissions and 15 | limitations under the License. 16 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.16.0) 2 | include($ENV{IDF_PATH}/tools/cmake/project.cmake) 3 | project(HB-RF-ETH) 4 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # HB-RF-ETH Firmware 2 | 3 | ### Unterstützung [Support me on Ko-fi](https://ko-fi.com/alexreinert) [Donate via Paypal](https://www.paypal.com/donate/?cmd=_s-xclick&hosted_button_id=4PW43VJ2DZ7R2) 4 | Meine Entwicklungen im Homematic Umfeld sind sehr kostenintensiv, z.B. werden viele verschiedene Testgeräte oder auch diverse Prototypen von Platinen benötigt. Allerdings erhält meine Projekt keine Unterstützung durch kommerzielle Anbieter. Ich freue mich daher durch eine Unterstützung mit einer Spende via [Ko-fi](https://ko-fi.com/alexreinert), [PayPal](https://www.paypal.com/donate/?cmd=_s-xclick&hosted_button_id=4PW43VJ2DZ7R2) oder durch eine Aufmerksamkeit auf meinem [Amazon Wunschzettel](https://www.amazon.de/gp/registry/wishlist/3NNUQIQO20AAP/ref=nav_wishlist_lists_1). 5 | 6 | ### Worum es geht 7 | Dieses Repository enhält die Firmware für die HB-RF-ETH Platine, welches es ermöglicht, ein Homematic Funkmodul HM-MOD-RPI-PCB oder RPI-RF-MOD per Netzwerk an eine debmatic oder piVCCU3 Installation anzubinden. 8 | 9 | Hierbei gilt, dass bei einer debmatic oder piVCCU3 Installation immer nur ein Funkmodul angebunden werden kann, egal ob die Anbindung direkt per GPIO Leiste, USB mittels HB-RF-USB(-2) Platine oder per HB-RF-ETH Platine erfolgt. 10 | 11 | ### Was kann die Firmware 12 | * Bereitstellung des Funkmoduls RPI-RF-MOD oder HM-MOD-RPI-PCB per UDP als raw-uart Gerät inkl. Ansteuerung der LEDs des RPI-RF-MODs 13 | * (S)NTP Server für die Verteilung der Zeit im lokalen Netzwerk 14 | * Unterstützung der RTC des RPI-RF-MODs oder eines [DS3231 Aufsteckmoduls](https://www.amazon.de/ANGEEK-DS3231-Precision-Arduino-Raspberry/dp/B07WJSQ6M2) 15 | * Verschiedene mögliche Zeitquellen 16 | * (S)NTP Client 17 | * DCF77 Empfänger (aka Funkuhr) mittels [optionalem Moduls](https://de.elv.com/elv-gehaeuse-fuer-externe-dcf-antenne-dcf-et1-komplettbausatz-ohne-dcf-modul-142883): 18 | * Konnektor J5 19 | * Pin 1: VCC 20 | * Pin 2: DCF Signal 21 | * Pin 3: Gnd 22 | * GPS Empfänger mittels [optionalem Moduls](https://www.amazon.de/AZDelivery-NEO-6M-GPS-baugleich-u-blox/dp/B01N38EMBF): 23 | * Konnektor J5 24 | * Pin 1: VCC 25 | * Pin 2: TX 26 | * Pin 3: Gnd 27 | * MDNS Server um Platine im Netzwerk bekannt zu machen 28 | * Netzwerkeinsellungen per DHCP oder statisch konfigurierbar 29 | * WebUI zur Konfiguration 30 | * Intialpasswort: admin 31 | * Firmware Update per Webinterface 32 | * Erkennung des Funkmoduls und Ausgabe von Typ, Seriennummer, Funkadresse und SGTIN in der WebUI 33 | * Regelmäßige Prüfung auf Firmwareupdates 34 | * Werksreset per Taster 35 | 36 | ### Bekannte Einschränkungen 37 | * Nach einem Neustart der Platine (z.B. bei Stromausfall) findet kein automatischer Reconnect statt, in diesem Fall muss die CCU Software daher neu gestartet werden. 38 | * Die Stromversorgung mittels des Funkmoduls RPI-RF-MOD darf nur erfolgen, wenn keine andere Stromversorgung (USB oder PoE) angeschlossen ist. 39 | 40 | ### Werksreset 41 | Die Firmware kann per Taster auf Werkseinstellungen zurückgesetzt werden: 42 | 1. Platine vom Strom trennen 43 | 2. Taster drücken und gedrückt halten 44 | 3. Stromversorgung wiederherstellen 45 | 4. Nach ca. 4 Sekunden fängt die rote Status LED schnell zu blinken an und die grüne Power LED hört auf zu leuchten 46 | 5. Taster kurz loslassen und wieder drücken und gedrückt halten 47 | 6. Nach ca. 4 Sekunden leuchten die grüne Power LED und die rote Status LED für eine Sekunde 48 | 7. Danach ist der Werkreset abgeschlossen und es folgt der normale Bootvorgang 49 | 50 | ### Blinkcodes der LEDs 51 | #### RPI-RF-MOD 52 | Siehe Hilfe zum RPI-RF-MOD 53 | 54 | #### Grüne Power LED und rote Status LED 55 | * Blinken abwechselnd mit grüner Power LED: System bootet 56 | * Schnelles Blinken der roten Status LED, grüne Power LED leuchtet nicht: Siehe Werksreset 57 | * Schnelles Blinken der roten Status LED, grüne Power LED leuchtet dauerhaft: Firmware Update wird eingespielt 58 | * Langsames Blinken der roten Status LED, grüne Power LED leuchtet dauerhaft: Es ist ein Firmware Update verfügbar 59 | * Dauerhaftes Leuchten der grünen Power LED: Sytem ist gestartet 60 | 61 | ### Firmware Updates 62 | Firmware Updates sind fertig kompiliert und Releases zu finden und können per Webinterface eingespielt werden. Zum Übernehmen der Firmware muss die Platine neu gestartet werden (mittel Power-On Reset). 63 | 64 | ### Einbindung in piVCCU3 und debmatic 65 | Die Unterstützung für die Platine HB-RF-ETH ist in piVCCU3 ab Version 3.51.6-41 und in debmatic ab Version 3.51.6-46 eingebaut. Die Installation der Platine erfolgt über das Paket "hb-rf-eth". Weiteres Details findet man in der Installationsanleitung von piVCCU3 bzw. debmatic. 66 | 67 | ### Roadmap 68 | Folgende Punkte sind angedacht für zukünftige Releases. Die Sortierung ist als zufällig anzusehen und es ist nicht garantiert, dass alle Punkte auch umgesetzt werden. 69 | 70 | * Transportverschlüsselung raw-uart 71 | * LED Fading 72 | * SNMP 73 | * CheckMK Agent 74 | * LAN GW Modus 75 | * AskSin Analyzer Light 76 | 77 | ### Lizenz 78 | Die Firmware steht unter Creative Commons Attribution-NonCommercial-ShareAlike 4.0 Lizenz. 79 | -------------------------------------------------------------------------------- /append_version_to_progname.py: -------------------------------------------------------------------------------- 1 | Import("env") 2 | 3 | with open("version.txt") as fp: 4 | version = fp.readline() 5 | env.Replace(PROGNAME="firmware_%s" % version.replace(".", "_")) 6 | -------------------------------------------------------------------------------- /boards/hb-rf-eth.json: -------------------------------------------------------------------------------- 1 | { 2 | "build": { 3 | "core": "esp32", 4 | "f_cpu": "240000000L", 5 | "f_flash": "40000000L", 6 | "flash_mode": "qio", 7 | "mcu": "esp32", 8 | "variant": "hb-rf-eth", 9 | "hwids": [ 10 | [ "0x1A86", "0x7523" ] ] 11 | }, 12 | "connectivity": [ 13 | "wifi", 14 | "bluetooth", 15 | "ethernet" 16 | ], 17 | "debug": { 18 | "openocd_board": "esp-wroom-32.cfg" 19 | }, 20 | "frameworks": [ 21 | "espidf" 22 | ], 23 | "name": "HB-RF-ETH", 24 | "upload": { 25 | "flash_size": "4MB", 26 | "maximum_ram_size": 327680, 27 | "maximum_size": 4194304, 28 | "require_upload_port": true, 29 | "speed": 921600 30 | }, 31 | "url": "https://github.com/alexreinert/PCB", 32 | "vendor": "Alexander Reinert" 33 | } 34 | -------------------------------------------------------------------------------- /build/CMakeFiles/cmake.check_cache: -------------------------------------------------------------------------------- 1 | # This file is generated by cmake for dependency checking of the CMakeCache.txt file 2 | -------------------------------------------------------------------------------- /build_webui.py: -------------------------------------------------------------------------------- 1 | from shutil import copyfile 2 | from subprocess import check_output, CalledProcessError 3 | import sys 4 | import os 5 | import platform 6 | import subprocess 7 | 8 | Import("env") 9 | 10 | def is_tool(name): 11 | cmd = "where" if platform.system() == "Windows" else "which" 12 | try: 13 | check_output([cmd, name]) 14 | return True 15 | except: 16 | return False 17 | 18 | def build_web(): 19 | if is_tool("npm"): 20 | os.chdir("webui") 21 | print("Attempting to build webpage...") 22 | try: 23 | if platform.system() == "Windows": 24 | print(check_output(["npm.cmd", "run", "build"])) 25 | else: 26 | print(check_output(["npm", "run", "build"])) 27 | finally: 28 | os.chdir(".."); 29 | 30 | build_web() 31 | -------------------------------------------------------------------------------- /include/README: -------------------------------------------------------------------------------- 1 | 2 | This directory is intended for project header files. 3 | 4 | A header file is a file containing C declarations and macro definitions 5 | to be shared between several project source files. You request the use of a 6 | header file in your project source file (C, C++, etc) located in `src` folder 7 | by including it, with the C preprocessing directive `#include'. 8 | 9 | ```src/main.c 10 | 11 | #include "header.h" 12 | 13 | int main (void) 14 | { 15 | ... 16 | } 17 | ``` 18 | 19 | Including a header file produces the same results as copying the header file 20 | into each source file that needs it. Such copying would be time-consuming 21 | and error-prone. With a header file, the related declarations appear 22 | in only one place. If they need to be changed, they can be changed in one 23 | place, and programs that include the header file will automatically use the 24 | new version when next recompiled. The header file eliminates the labor of 25 | finding and changing all the copies as well as the risk that a failure to 26 | find one copy will result in inconsistencies within a program. 27 | 28 | In C, the usual convention is to give header files names that end with `.h'. 29 | It is most portable to use only letters, digits, dashes, and underscores in 30 | header file names, and at most one dot. 31 | 32 | Read more about using header files in official GCC documentation: 33 | 34 | * Include Syntax 35 | * Include Operation 36 | * Once-Only Headers 37 | * Computed Includes 38 | 39 | https://gcc.gnu.org/onlinedocs/cpp/Header-Files.html 40 | -------------------------------------------------------------------------------- /include/dcf.h: -------------------------------------------------------------------------------- 1 | /* 2 | * dcf.h is part of the HB-RF-ETH firmware - https://github.com/alexreinert/HB-RF-ETH 3 | * 4 | * Copyright 2022 Alexander Reinert 5 | * 6 | * The HB-RF-ETH firmware is licensed under a 7 | * Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International License. 8 | * 9 | * You should have received a copy of the license along with this 10 | * work. If not, see . 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | * 18 | */ 19 | 20 | #include "systemclock.h" 21 | #include "settings.h" 22 | 23 | class DCF 24 | { 25 | public: 26 | DCF(Settings *settings, SystemClock *clk); 27 | 28 | void start(void); 29 | void stop(void); 30 | }; 31 | -------------------------------------------------------------------------------- /include/ethernet.h: -------------------------------------------------------------------------------- 1 | /* 2 | * ethernet.h is part of the HB-RF-ETH firmware - https://github.com/alexreinert/HB-RF-ETH 3 | * 4 | * Copyright 2022 Alexander Reinert 5 | * 6 | * The HB-RF-ETH firmware is licensed under a 7 | * Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International License. 8 | * 9 | * You should have received a copy of the license along with this 10 | * work. If not, see . 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | * 18 | */ 19 | 20 | #pragma once 21 | 22 | #include 23 | #include 24 | #include "freertos/FreeRTOS.h" 25 | #include "freertos/task.h" 26 | #include "esp_netif.h" 27 | #include "esp_eth.h" 28 | #include "esp_event.h" 29 | #include "esp_log.h" 30 | #include "driver/gpio.h" 31 | #include "sdkconfig.h" 32 | #include "settings.h" 33 | 34 | class Ethernet 35 | { 36 | private: 37 | esp_netif_config_t _netif_cfg; 38 | esp_netif_t *_eth_netif; 39 | esp_eth_handle_t _eth_handle; 40 | eth_mac_config_t _mac_config; 41 | eth_phy_config_t _phy_config; 42 | esp_eth_mac_t *_mac; 43 | esp_eth_phy_t *_phy; 44 | esp_eth_config_t _eth_config; 45 | 46 | Settings *_settings; 47 | bool _isConnected; 48 | 49 | public: 50 | Ethernet(Settings *settings); 51 | 52 | void start(); 53 | void stop(); 54 | 55 | void getNetworkSettings(ip4_addr_t *ip, ip4_addr_t *netmask, ip4_addr_t *gateway, ip4_addr_t *dns1, ip4_addr_t *dns2); 56 | 57 | void _handleETHEvent(esp_event_base_t event_base, int32_t event_id, void *event_data); 58 | void _handleIPEvent(esp_event_base_t event_base, int32_t event_id, void *event_data); 59 | }; -------------------------------------------------------------------------------- /include/gps.h: -------------------------------------------------------------------------------- 1 | /* 2 | * gps.h is part of the HB-RF-ETH firmware - https://github.com/alexreinert/HB-RF-ETH 3 | * 4 | * Copyright 2022 Alexander Reinert 5 | * 6 | * The HB-RF-ETH firmware is licensed under a 7 | * Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International License. 8 | * 9 | * You should have received a copy of the license along with this 10 | * work. If not, see . 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | * 18 | */ 19 | 20 | #include "freertos/FreeRTOS.h" 21 | #include "freertos/task.h" 22 | #include "freertos/queue.h" 23 | #include "driver/uart.h" 24 | #include "systemclock.h" 25 | #include "settings.h" 26 | #include "linereader.h" 27 | 28 | class GPS 29 | { 30 | private: 31 | Settings *_settings; 32 | SystemClock *_clk; 33 | TaskHandle_t _tHandle = NULL; 34 | QueueHandle_t _uart_queue; 35 | LineReader *_lineReader; 36 | uint64_t _nextSync = 0; 37 | 38 | public: 39 | GPS(Settings *settings, SystemClock *clk); 40 | 41 | void start(void); 42 | void stop(void); 43 | 44 | void _gpsSerialQueueHandler(); 45 | void _handleLine(unsigned char *buffer, uint16_t len); 46 | }; 47 | -------------------------------------------------------------------------------- /include/hmframe.h: -------------------------------------------------------------------------------- 1 | /* 2 | * hmframe.h is part of the HB-RF-ETH firmware - https://github.com/alexreinert/HB-RF-ETH 3 | * 4 | * Copyright 2022 Alexander Reinert 5 | * 6 | * The HB-RF-ETH firmware is licensed under a 7 | * Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International License. 8 | * 9 | * You should have received a copy of the license along with this 10 | * work. If not, see . 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | * 18 | */ 19 | 20 | #pragma once 21 | 22 | #include 23 | 24 | class HMFrame 25 | { 26 | public: 27 | static bool TryParse(unsigned char *buffer, uint16_t len, HMFrame *frame); 28 | static uint16_t crc(unsigned char *buffer, uint16_t len); 29 | 30 | HMFrame(); 31 | uint8_t counter; 32 | uint8_t destination; 33 | uint8_t command; 34 | unsigned char *data; 35 | uint16_t data_len; 36 | 37 | uint16_t encode(unsigned char *buffer, uint16_t len, bool escaped); 38 | }; 39 | 40 | typedef enum 41 | { 42 | HM_DST_HMSYSTEM = 0x00, 43 | HM_DST_TRX = 0x01, 44 | HM_DST_HMIP = 0x02, 45 | HM_DST_LLMAC = 0x03, 46 | HM_DST_COMMON = 0xfe, 47 | } hm_dst_t; 48 | 49 | typedef enum 50 | { 51 | HM_CMD_HMSYSTEM_IDENTIFY = 0x00, 52 | HM_CMD_HMSYSTEM_GET_VERSION = 0x02, 53 | HM_CMD_HMSYSTEM_CHANGE_APP = 0x03, 54 | HM_CMD_HMSYSTEM_ACK = 0x04, 55 | HM_CMD_HMSYSTEM_GET_SERIAL = 0x0b, 56 | } hm_cmd_hmsystem_t; 57 | 58 | typedef enum 59 | { 60 | HM_CMD_TRX_GET_VERSION = 0x02, 61 | HM_CMD_TRX_ACK = 0x04, 62 | HM_CMD_TRX_GET_MCU_TYPE = 0x09, 63 | HM_CMD_TRX_GET_DEFAULT_RF_ADDR = 0x10, 64 | } hm_cmd_trx_t; 65 | 66 | typedef enum 67 | { 68 | HM_CMD_HMIP_GET_DEFAULT_RF_ADDR = 0x01, 69 | HM_CMD_HMIP_ACK = 0x06, 70 | } hm_cmd_hmip_t; 71 | 72 | typedef enum 73 | { 74 | HM_CMD_LLMAC_ACK = 0x01, 75 | HM_CMD_LLMAC_GET_SERIAL = 0x07, 76 | HM_CMD_LLMAC_GET_DEFAULT_RF_ADDR = 0x08, 77 | } hm_cmd_llmac_t; 78 | 79 | typedef enum 80 | { 81 | HM_CMD_COMMON_IDENTIFY = 0x01, 82 | HM_CMD_COMMON_START_BL = 0x02, 83 | HM_CMD_COMMON_START_APP = 0x03, 84 | HM_CMD_COMMON_GET_SGTIN = 0x04, 85 | HM_CMD_COMMON_ACK = 0x05, 86 | } hm_cmd_common_t; 87 | -------------------------------------------------------------------------------- /include/led.h: -------------------------------------------------------------------------------- 1 | /* 2 | * led.h is part of the HB-RF-ETH firmware - https://github.com/alexreinert/HB-RF-ETH 3 | * 4 | * Copyright 2022 Alexander Reinert 5 | * 6 | * The HB-RF-ETH firmware is licensed under a 7 | * Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International License. 8 | * 9 | * You should have received a copy of the license along with this 10 | * work. If not, see . 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | * 18 | */ 19 | 20 | #pragma once 21 | 22 | #include 23 | #include "driver/gpio.h" 24 | #include "driver/ledc.h" 25 | #include "settings.h" 26 | 27 | #define MAX_LED_COUNT 5 28 | 29 | typedef enum 30 | { 31 | LED_STATE_OFF = 0, 32 | LED_STATE_ON = 1, 33 | LED_STATE_BLINK = 2, 34 | LED_STATE_BLINK_INV = 3, 35 | LED_STATE_BLINK_FAST = 4, 36 | LED_STATE_BLINK_SLOW = 5, 37 | } led_state_t; 38 | 39 | class LED 40 | { 41 | private: 42 | uint8_t _state; 43 | ledc_channel_config_t _channel_conf; 44 | void _setPinState(bool enabled); 45 | 46 | public: 47 | static void start(Settings *settings); 48 | static void stop(); 49 | 50 | LED(gpio_num_t pin); 51 | void setState(led_state_t state); 52 | void updatePinState(); 53 | }; -------------------------------------------------------------------------------- /include/linereader.h: -------------------------------------------------------------------------------- 1 | /* 2 | * linereader.h is part of the HB-RF-ETH firmware - https://github.com/alexreinert/HB-RF-ETH 3 | * 4 | * Copyright 2022 Alexander Reinert 5 | * 6 | * The HB-RF-ETH firmware is licensed under a 7 | * Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International License. 8 | * 9 | * You should have received a copy of the license along with this 10 | * work. If not, see . 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | * 18 | */ 19 | 20 | #pragma once 21 | 22 | #include 23 | #include 24 | 25 | class LineReader 26 | { 27 | private: 28 | unsigned char _buffer[1024]; 29 | std::function _processor; 30 | uint16_t _buffer_pos; 31 | 32 | public: 33 | LineReader(std::function processor); 34 | 35 | void Append(unsigned char chr); 36 | void Append(unsigned char *buffer, uint16_t len); 37 | void Flush(); 38 | }; 39 | -------------------------------------------------------------------------------- /include/mdnsserver.h: -------------------------------------------------------------------------------- 1 | /* 2 | * mdnsserver.h is part of the HB-RF-ETH firmware - https://github.com/alexreinert/HB-RF-ETH 3 | * 4 | * Copyright 2022 Alexander Reinert 5 | * 6 | * The HB-RF-ETH firmware is licensed under a 7 | * Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International License. 8 | * 9 | * You should have received a copy of the license along with this 10 | * work. If not, see . 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | * 18 | */ 19 | 20 | #pragma once 21 | 22 | #include "settings.h" 23 | 24 | class MDns 25 | { 26 | public: 27 | void start(Settings* settings); 28 | void stop(); 29 | }; -------------------------------------------------------------------------------- /include/ntpclient.h: -------------------------------------------------------------------------------- 1 | /* 2 | * ntpclient.h is part of the HB-RF-ETH firmware - https://github.com/alexreinert/HB-RF-ETH 3 | * 4 | * Copyright 2022 Alexander Reinert 5 | * 6 | * The HB-RF-ETH firmware is licensed under a 7 | * Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International License. 8 | * 9 | * You should have received a copy of the license along with this 10 | * work. If not, see . 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | * 18 | */ 19 | 20 | #pragma once 21 | 22 | #include "settings.h" 23 | #include "systemclock.h" 24 | 25 | class NtpClient 26 | { 27 | public: 28 | NtpClient(Settings *settings, SystemClock *clk); 29 | void start(); 30 | void stop(); 31 | }; 32 | -------------------------------------------------------------------------------- /include/ntpserver.h: -------------------------------------------------------------------------------- 1 | /* 2 | * ntpserver.h is part of the HB-RF-ETH firmware - https://github.com/alexreinert/HB-RF-ETH 3 | * 4 | * Copyright 2022 Alexander Reinert 5 | * 6 | * The HB-RF-ETH firmware is licensed under a 7 | * Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International License. 8 | * 9 | * You should have received a copy of the license along with this 10 | * work. If not, see . 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | * 18 | */ 19 | 20 | #pragma once 21 | 22 | #include "lwip/opt.h" 23 | #include "lwip/inet.h" 24 | #include "lwip/udp.h" 25 | #include "lwip/priv/tcpip_priv.h" 26 | #include "systemclock.h" 27 | 28 | typedef unsigned long long tstamp; 29 | 30 | typedef struct ntp_packet 31 | { 32 | uint8_t flags; 33 | uint8_t stratum; 34 | uint8_t poll; 35 | int8_t precision; 36 | uint32_t delay; 37 | uint32_t dispersion; 38 | char ref_id[4]; 39 | tstamp ref_time; 40 | tstamp orig_time; 41 | tstamp recv_time; 42 | tstamp trns_time; 43 | } ntp_packet_t; 44 | 45 | class NtpServer { 46 | private: 47 | SystemClock* _clk; 48 | udp_pcb* _pcb; 49 | QueueHandle_t _udp_queue; 50 | TaskHandle_t _tHandle = NULL; 51 | 52 | void handlePacket(pbuf *pb, ip4_addr_t addr, uint16_t port); 53 | 54 | public: 55 | NtpServer(SystemClock* clk); 56 | 57 | void start(); 58 | void stop(); 59 | 60 | void _udpQueueHandler(); 61 | bool _udpReceivePacket(pbuf *pb, const ip_addr_t *addr, uint16_t port); 62 | }; 63 | -------------------------------------------------------------------------------- /include/pins.h: -------------------------------------------------------------------------------- 1 | /* 2 | * pins.h is part of the HB-RF-ETH firmware - https://github.com/alexreinert/HB-RF-ETH 3 | * 4 | * Copyright 2022 Alexander Reinert 5 | * 6 | * The HB-RF-ETH firmware is licensed under a 7 | * Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International License. 8 | * 9 | * You should have received a copy of the license along with this 10 | * work. If not, see . 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | * 18 | */ 19 | 20 | #pragma once 21 | 22 | #include "driver/gpio.h" 23 | #include "soc/adc_channel.h" 24 | 25 | #define HM_RX_PIN GPIO_NUM_35 26 | #define HM_TX_PIN GPIO_NUM_2 27 | 28 | #define HM_SDA_PIN GPIO_NUM_18 29 | #define HM_SCL_PIN GPIO_NUM_5 30 | 31 | #define HM_RST_PIN GPIO_NUM_23 32 | #define HM_BTN_PIN GPIO_NUM_34 33 | #define HM_RED_PIN GPIO_NUM_15 34 | #define HM_GREEN_PIN GPIO_NUM_14 35 | #define HM_BLUE_PIN GPIO_NUM_12 36 | 37 | #define LED_STATUS_PIN GPIO_NUM_4 38 | #define LED_PWR_PIN GPIO_NUM_16 39 | 40 | #define DCF_PIN GPIO_NUM_39 41 | 42 | #define BOARD_REV_SENSE_CHANNEL ((adc_channel_t)ADC1_GPIO36_CHANNEL) 43 | #define BOARD_REV_SENSE_UNIT ADC_UNIT_1 44 | 45 | #define ETH_PHY_ADDR 0 46 | #define ETH_POWER_PIN GPIO_NUM_13 47 | #define ETH_MDC_PIN GPIO_NUM_32 48 | #define ETH_MDIO_PIN GPIO_NUM_33 49 | -------------------------------------------------------------------------------- /include/pushbuttonhandler.h: -------------------------------------------------------------------------------- 1 | /* 2 | * pushbuttonhandler.h is part of the HB-RF-ETH firmware - https://github.com/alexreinert/HB-RF-ETH 3 | * 4 | * Copyright 2022 Alexander Reinert 5 | * 6 | * The HB-RF-ETH firmware is licensed under a 7 | * Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International License. 8 | * 9 | * You should have received a copy of the license along with this 10 | * work. If not, see . 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | * 18 | */ 19 | 20 | #pragma once 21 | 22 | #include "esp_log.h" 23 | #include "led.h" 24 | #include "settings.h" 25 | #include "pins.h" 26 | 27 | class PushButtonHandler { 28 | public: 29 | PushButtonHandler(); 30 | void handleStartupFactoryReset(LED *powerLED, LED *statusLED, Settings *settings); 31 | }; 32 | -------------------------------------------------------------------------------- /include/radiomoduleconnector.h: -------------------------------------------------------------------------------- 1 | /* 2 | * radiomoduleconnector.h is part of the HB-RF-ETH firmware - https://github.com/alexreinert/HB-RF-ETH 3 | * 4 | * Copyright 2022 Alexander Reinert 5 | * 6 | * The HB-RF-ETH firmware is licensed under a 7 | * Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International License. 8 | * 9 | * You should have received a copy of the license along with this 10 | * work. If not, see . 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | * 18 | */ 19 | 20 | #pragma once 21 | 22 | #include "freertos/FreeRTOS.h" 23 | #include "freertos/task.h" 24 | #include "freertos/queue.h" 25 | #include "driver/uart.h" 26 | #include "led.h" 27 | #include "streamparser.h" 28 | #include 29 | #define _Atomic(X) std::atomic 30 | 31 | class FrameHandler 32 | { 33 | public: 34 | virtual void handleFrame(unsigned char *buffer, uint16_t len) = 0; 35 | }; 36 | 37 | class RadioModuleConnector 38 | { 39 | private: 40 | LED *_redLED; 41 | LED *_greenLED; 42 | LED *_blueLED; 43 | StreamParser *_streamParser; 44 | std::atomic _frameHandler = ATOMIC_VAR_INIT(0); 45 | QueueHandle_t _uart_queue; 46 | TaskHandle_t _tHandle = NULL; 47 | 48 | void _handleFrame(unsigned char *buffer, uint16_t len); 49 | 50 | public: 51 | RadioModuleConnector(LED *redLED, LED *greenLed, LED *blueLed); 52 | 53 | void start(); 54 | void stop(); 55 | 56 | void setLED(bool red, bool green, bool blue); 57 | 58 | void setFrameHandler(FrameHandler *handler, bool decodeEscaped); 59 | 60 | void resetModule(); 61 | 62 | void sendFrame(unsigned char *buffer, uint16_t len); 63 | 64 | void _serialQueueHandler(); 65 | }; 66 | -------------------------------------------------------------------------------- /include/radiomoduledetector.h: -------------------------------------------------------------------------------- 1 | /* 2 | * radiomoduledetector.h is part of the HB-RF-ETH firmware - https://github.com/alexreinert/HB-RF-ETH 3 | * 4 | * Copyright 2022 Alexander Reinert 5 | * 6 | * The HB-RF-ETH firmware is licensed under a 7 | * Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International License. 8 | * 9 | * You should have received a copy of the license along with this 10 | * work. If not, see . 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | * 18 | */ 19 | 20 | #pragma once 21 | 22 | #include "radiomoduleconnector.h" 23 | #include "radiomoduledetector_utils.h" 24 | 25 | typedef enum 26 | { 27 | RADIO_MODULE_NONE = 0, 28 | RADIO_MODULE_HMIP_RFUSB = 1, 29 | RADIO_MODULE_HM_MOD_RPI_PCB = 3, 30 | RADIO_MODULE_RPI_RF_MOD = 4, 31 | } radio_module_type_t; 32 | 33 | typedef enum 34 | { 35 | DETECT_STATE_START_BL = 0, 36 | DETECT_STATE_START_APP = 10, 37 | 38 | DETECT_STATE_GET_MCU_TYPE = 20, 39 | DETECT_STATE_GET_VERSION = 30, 40 | DETECT_STATE_GET_HMIP_RF_ADDRESS = 40, 41 | DETECT_STATE_GET_SGTIN = 50, 42 | DETECT_STATE_GET_BIDCOS_RF_ADDRESS = 60, 43 | DETECT_STATE_GET_SERIAL = 70, 44 | 45 | DETECT_STATE_LEGACY_GET_VERSION = 31, 46 | DETECT_STATE_LEGACY_GET_BIDCOS_RF_ADDRESS = 61, 47 | DETECT_STATE_LEGACY_GET_SERIAL = 71, 48 | 49 | DETECT_STATE_FINISHED = 255, 50 | } detect_radio_module_state_t; 51 | 52 | class RadioModuleDetector : private FrameHandler 53 | { 54 | private: 55 | void handleFrame(unsigned char *buffer, uint16_t len); 56 | void sendFrame(uint8_t counter, uint8_t destination, uint8_t command, unsigned char *data, uint data_len); 57 | 58 | char _serial[11] = {0}; 59 | uint32_t _bidCosRadioMAC = 0; 60 | uint32_t _hmIPRadioMAC = 0; 61 | char _sgtin[25] = {0}; 62 | uint8_t _firmwareVersion[3] = {0}; 63 | radio_module_type_t _radioModuleType = RADIO_MODULE_NONE; 64 | 65 | int _detectState; 66 | int _detectRetryCount; 67 | int _detectMsgCounter; 68 | SemaphoreHandle_t _detectWaitFrameDataSemaphore; 69 | RadioModuleConnector *_radioModuleConnector; 70 | 71 | public: 72 | void detectRadioModule(RadioModuleConnector *radioModuleConnector); 73 | const char *getSerial(); 74 | uint32_t getBidCosRadioMAC(); 75 | uint32_t getHmIPRadioMAC(); 76 | const char *getSGTIN(); 77 | const uint8_t *getFirmwareVersion(); 78 | radio_module_type_t getRadioModuleType(); 79 | }; -------------------------------------------------------------------------------- /include/radiomoduledetector_utils.h: -------------------------------------------------------------------------------- 1 | /* 2 | * radiomoduledetector_utils.h is part of the HB-RF-ETH firmware - https://github.com/alexreinert/HB-RF-ETH 3 | * 4 | * Copyright 2022 Alexander Reinert 5 | * 6 | * The HB-RF-ETH firmware is licensed under a 7 | * Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International License. 8 | * 9 | * You should have received a copy of the license along with this 10 | * work. If not, see . 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | * 18 | */ 19 | 20 | #include "esp_log.h" 21 | 22 | #define sem_take(__sem, __timeout) (xSemaphoreTake(__sem, __timeout * 1000 / portTICK_PERIOD_MS) == pdTRUE) 23 | #define sem_give(__sem) xSemaphoreGive(__sem) 24 | #define sem_init(__sem) __sem = xSemaphoreCreateBinary(); 25 | 26 | #define log_frame(__text, __buffer, __len) \ 27 | ESP_LOGD(TAG, __text); \ 28 | ESP_LOG_BUFFER_HEX_LEVEL(TAG, __buffer, __len, ESP_LOG_DEBUG); 29 | 30 | -------------------------------------------------------------------------------- /include/rawuartudplistener.h: -------------------------------------------------------------------------------- 1 | /* 2 | * rawuartudplistener.h is part of the HB-RF-ETH firmware - https://github.com/alexreinert/HB-RF-ETH 3 | * 4 | * Copyright 2022 Alexander Reinert 5 | * 6 | * The HB-RF-ETH firmware is licensed under a 7 | * Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International License. 8 | * 9 | * You should have received a copy of the license along with this 10 | * work. If not, see . 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | * 18 | */ 19 | 20 | #pragma once 21 | 22 | #include "lwip/opt.h" 23 | #include "lwip/inet.h" 24 | #include "lwip/udp.h" 25 | #include "lwip/priv/tcpip_priv.h" 26 | #include 27 | #define _Atomic(X) std::atomic 28 | #include "radiomoduleconnector.h" 29 | 30 | class RawUartUdpListener : FrameHandler 31 | { 32 | private: 33 | RadioModuleConnector *_radioModuleConnector; 34 | std::atomic _remoteAddress; 35 | std::atomic _remotePort; 36 | std::atomic _connectionStarted; 37 | std::atomic _counter; 38 | std::atomic _endpointConnectionIdentifier; 39 | uint64_t _lastReceivedKeepAlive; 40 | udp_pcb *_pcb; 41 | QueueHandle_t _udp_queue; 42 | TaskHandle_t _tHandle = NULL; 43 | 44 | void handlePacket(pbuf *pb, ip4_addr_t addr, uint16_t port); 45 | void sendMessage(unsigned char command, unsigned char *buffer, size_t len); 46 | 47 | public: 48 | RawUartUdpListener(RadioModuleConnector *radioModuleConnector); 49 | 50 | void handleFrame(unsigned char *buffer, uint16_t len); 51 | void handleEvent(); 52 | 53 | ip4_addr_t getConnectedRemoteAddress(); 54 | 55 | void start(); 56 | void stop(); 57 | 58 | void _udpQueueHandler(); 59 | bool _udpReceivePacket(pbuf *pb, const ip_addr_t *addr, uint16_t port); 60 | }; 61 | -------------------------------------------------------------------------------- /include/rtc.h: -------------------------------------------------------------------------------- 1 | /* 2 | * rtc.h is part of the HB-RF-ETH firmware - https://github.com/alexreinert/HB-RF-ETH 3 | * 4 | * Copyright 2022 Alexander Reinert 5 | * 6 | * The HB-RF-ETH firmware is licensed under a 7 | * Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International License. 8 | * 9 | * You should have received a copy of the license along with this 10 | * work. If not, see . 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | * 18 | */ 19 | 20 | #pragma once 21 | 22 | #include 23 | #include 24 | #include "driver/i2c.h" 25 | #include "pins.h" 26 | 27 | class Rtc 28 | { 29 | protected: 30 | Rtc(uint8_t address, uint8_t reg_start); 31 | i2c_port_t _i2c_port; 32 | const uint8_t _address; 33 | const uint8_t _reg_start; 34 | 35 | public: 36 | static Rtc* detect(); 37 | 38 | virtual bool begin(); 39 | virtual ~Rtc(); 40 | struct timeval GetTime(); 41 | void SetTime(struct timeval now); 42 | }; 43 | 44 | class RtcDS3231 : public Rtc 45 | { 46 | public: 47 | RtcDS3231(); 48 | }; 49 | 50 | class RtcRX8130 : public Rtc 51 | { 52 | public: 53 | RtcRX8130(); 54 | bool begin(); 55 | }; 56 | -------------------------------------------------------------------------------- /include/settings.h: -------------------------------------------------------------------------------- 1 | /* 2 | * settings.h is part of the HB-RF-ETH firmware - https://github.com/alexreinert/HB-RF-ETH 3 | * 4 | * Copyright 2022 Alexander Reinert 5 | * 6 | * The HB-RF-ETH firmware is licensed under a 7 | * Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International License. 8 | * 9 | * You should have received a copy of the license along with this 10 | * work. If not, see . 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | * 18 | */ 19 | 20 | #pragma once 21 | 22 | #include 23 | #include 24 | 25 | typedef enum 26 | { 27 | TIMESOURCE_NTP = 0, 28 | TIMESOURCE_DCF = 1, 29 | TIMESOURCE_GPS = 2 30 | } timesource_t; 31 | 32 | class Settings 33 | { 34 | private: 35 | char _adminPassword[33] = {0}; 36 | 37 | char _hostname[33] = {0}; 38 | bool _useDHCP; 39 | ip4_addr_t _localIP; 40 | ip4_addr_t _netmask; 41 | ip4_addr_t _gateway; 42 | ip4_addr_t _dns1; 43 | ip4_addr_t _dns2; 44 | 45 | int _timesource; 46 | 47 | int _dcfOffset; 48 | 49 | int _gpsBaudrate; 50 | 51 | char _ntpServer[65] = {0}; 52 | 53 | int _ledBrightness; 54 | 55 | public: 56 | Settings(); 57 | void load(); 58 | void save(); 59 | void clear(); 60 | 61 | char *getAdminPassword(); 62 | void setAdminPassword(char* password); 63 | 64 | char *getHostname(); 65 | bool getUseDHCP(); 66 | ip4_addr_t getLocalIP(); 67 | ip4_addr_t getNetmask(); 68 | ip4_addr_t getGateway(); 69 | ip4_addr_t getDns1(); 70 | ip4_addr_t getDns2(); 71 | 72 | void setNetworkSettings(char *hostname, bool useDHCP, ip4_addr_t localIP, ip4_addr_t netmask, ip4_addr_t gateway, ip4_addr_t dns1, ip4_addr_t dns2); 73 | 74 | timesource_t getTimesource(); 75 | void setTimesource(timesource_t timesource); 76 | 77 | int getDcfOffset(); 78 | void setDcfOffset(int offset); 79 | 80 | int getGpsBaudrate(); 81 | void setGpsBaudrate(int baudrate); 82 | 83 | char *getNtpServer(); 84 | void setNtpServer(char *ntpServer); 85 | 86 | int getLEDBrightness(); 87 | void setLEDBrightness(int brightness); 88 | }; -------------------------------------------------------------------------------- /include/streamparser.h: -------------------------------------------------------------------------------- 1 | /* 2 | * streamparser.h is part of the HB-RF-ETH firmware - https://github.com/alexreinert/HB-RF-ETH 3 | * 4 | * Copyright 2022 Alexander Reinert 5 | * 6 | * The HB-RF-ETH firmware is licensed under a 7 | * Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International License. 8 | * 9 | * You should have received a copy of the license along with this 10 | * work. If not, see . 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | * 18 | */ 19 | 20 | #pragma once 21 | 22 | #include 23 | #include 24 | 25 | typedef enum 26 | { 27 | NO_DATA, 28 | RECEIVE_LENGTH_HIGH_BYTE, 29 | RECEIVE_LENGTH_LOW_BYTE, 30 | RECEIVE_FRAME_DATA, 31 | FRAME_COMPLETE 32 | } state_t; 33 | 34 | class StreamParser 35 | { 36 | private: 37 | unsigned char _buffer[2048]; 38 | uint16_t _bufferPos; 39 | uint16_t _framePos; 40 | uint16_t _frameLength; 41 | state_t _state; 42 | bool _isEscaped; 43 | bool _decodeEscaped; 44 | std::function _processor; 45 | 46 | public: 47 | StreamParser(bool decodeEscaped, std::function processor); 48 | 49 | void append(unsigned char chr); 50 | void append(unsigned char *buffer, uint16_t len); 51 | void flush(); 52 | 53 | bool getDecodeEscaped(); 54 | void setDecodeEscaped(bool decodeEscaped); 55 | }; 56 | -------------------------------------------------------------------------------- /include/sysinfo.h: -------------------------------------------------------------------------------- 1 | /* 2 | * sysinfo.h is part of the HB-RF-ETH firmware - https://github.com/alexreinert/HB-RF-ETH 3 | * 4 | * Copyright 2022 Alexander Reinert 5 | * 6 | * The HB-RF-ETH firmware is licensed under a 7 | * Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International License. 8 | * 9 | * You should have received a copy of the license along with this 10 | * work. If not, see . 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | * 18 | */ 19 | 20 | #pragma once 21 | 22 | typedef enum 23 | { 24 | BOARD_TYPE_REV_1_8_PUB = 0, 25 | BOARD_TYPE_REV_1_8_SK = 1, 26 | BOARD_TYPE_REV_1_10_PUB = 2, 27 | BOARD_TYPE_REV_1_10_SK = 3, 28 | BOARD_TYPE_UNKNOWN = 255 29 | } board_type_t; 30 | 31 | class SysInfo 32 | { 33 | public: 34 | SysInfo(); 35 | double getCpuUsage(); 36 | double getMemoryUsage(); 37 | const char* getCurrentVersion(); 38 | const char *getSerialNumber(); 39 | board_type_t getBoardType(); 40 | }; 41 | -------------------------------------------------------------------------------- /include/systemclock.h: -------------------------------------------------------------------------------- 1 | /* 2 | * systemclock.h is part of the HB-RF-ETH firmware - https://github.com/alexreinert/HB-RF-ETH 3 | * 4 | * Copyright 2022 Alexander Reinert 5 | * 6 | * The HB-RF-ETH firmware is licensed under a 7 | * Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International License. 8 | * 9 | * You should have received a copy of the license along with this 10 | * work. If not, see . 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | * 18 | */ 19 | 20 | #pragma once 21 | 22 | #include "freertos/FreeRTOS.h" 23 | #include "freertos/task.h" 24 | #include "rtc.h" 25 | 26 | class SystemClock 27 | { 28 | private: 29 | Rtc *_rtc; 30 | struct timeval _lastSyncTime = { .tv_sec = 0, .tv_usec = 0 }; 31 | TaskHandle_t _tHandle = NULL; 32 | 33 | public: 34 | SystemClock(Rtc *rtc); 35 | 36 | void start(); 37 | void stop(); 38 | 39 | void setTime(struct timeval *tv); 40 | struct timeval getTime(); 41 | struct timeval getLastSyncTime(); 42 | struct tm getLocalTime(); 43 | }; 44 | -------------------------------------------------------------------------------- /include/udphelper.h: -------------------------------------------------------------------------------- 1 | /* 2 | * udphelper.h is part of the HB-RF-ETH firmware - https://github.com/alexreinert/HB-RF-ETH 3 | * 4 | * Copyright 2022 Alexander Reinert 5 | * 6 | * The HB-RF-ETH firmware is licensed under a 7 | * Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International License. 8 | * 9 | * You should have received a copy of the license along with this 10 | * work. If not, see . 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | * 18 | */ 19 | 20 | #pragma once 21 | 22 | #include "lwip/opt.h" 23 | #include "lwip/inet.h" 24 | #include "lwip/udp.h" 25 | #include "lwip/priv/tcpip_priv.h" 26 | 27 | typedef struct 28 | { 29 | struct tcpip_api_call_data call; 30 | udp_pcb *pcb; 31 | const ip_addr_t *addr; 32 | uint16_t port; 33 | struct pbuf *pb; 34 | err_t err; 35 | } udp_api_call_t; 36 | 37 | typedef struct 38 | { 39 | pbuf *pb; 40 | ip4_addr_t addr; 41 | uint16_t port; 42 | } udp_event_t; 43 | 44 | static err_t _udp_remove_api(struct tcpip_api_call_data *api_call_msg) 45 | { 46 | udp_api_call_t *msg = (udp_api_call_t *)api_call_msg; 47 | msg->err = 0; 48 | udp_remove(msg->pcb); 49 | return msg->err; 50 | } 51 | 52 | static void _udp_remove(struct udp_pcb *pcb) 53 | { 54 | udp_api_call_t msg; 55 | msg.pcb = pcb; 56 | tcpip_api_call(_udp_remove_api, (struct tcpip_api_call_data *)&msg); 57 | } 58 | 59 | static err_t _udp_bind_api(struct tcpip_api_call_data *api_call_msg) 60 | { 61 | udp_api_call_t *msg = (udp_api_call_t *)api_call_msg; 62 | msg->err = udp_bind(msg->pcb, msg->addr, msg->port); 63 | return msg->err; 64 | } 65 | 66 | static err_t _udp_bind(struct udp_pcb *pcb, const ip_addr_t *addr, u16_t port) 67 | { 68 | udp_api_call_t msg; 69 | msg.pcb = pcb; 70 | msg.addr = addr; 71 | msg.port = port; 72 | tcpip_api_call(_udp_bind_api, (struct tcpip_api_call_data *)&msg); 73 | return msg.err; 74 | } 75 | 76 | static err_t _udp_disconnect_api(struct tcpip_api_call_data *api_call_msg) 77 | { 78 | udp_api_call_t *msg = (udp_api_call_t *)api_call_msg; 79 | msg->err = 0; 80 | udp_disconnect(msg->pcb); 81 | return msg->err; 82 | } 83 | 84 | static void _udp_disconnect(struct udp_pcb *pcb) 85 | { 86 | udp_api_call_t msg; 87 | msg.pcb = pcb; 88 | tcpip_api_call(_udp_disconnect_api, (struct tcpip_api_call_data *)&msg); 89 | } 90 | 91 | static err_t _udp_sendto_api(struct tcpip_api_call_data *api_call_msg) 92 | { 93 | udp_api_call_t *msg = (udp_api_call_t *)api_call_msg; 94 | msg->err = udp_sendto(msg->pcb, msg->pb, msg->addr, msg->port); 95 | return msg->err; 96 | } 97 | 98 | static err_t _udp_sendto(struct udp_pcb *pcb, struct pbuf *pb, const ip_addr_t *addr, u16_t port) 99 | { 100 | udp_api_call_t msg; 101 | msg.pcb = pcb; 102 | msg.addr = addr; 103 | msg.port = port; 104 | msg.pb = pb; 105 | tcpip_api_call(_udp_sendto_api, (struct tcpip_api_call_data *)&msg); 106 | return msg.err; 107 | } 108 | -------------------------------------------------------------------------------- /include/updatecheck.h: -------------------------------------------------------------------------------- 1 | /* 2 | * updatecheck.h is part of the HB-RF-ETH firmware - https://github.com/alexreinert/HB-RF-ETH 3 | * 4 | * Copyright 2022 Alexander Reinert 5 | * 6 | * The HB-RF-ETH firmware is licensed under a 7 | * Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International License. 8 | * 9 | * You should have received a copy of the license along with this 10 | * work. If not, see . 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | * 18 | */ 19 | 20 | #pragma once 21 | 22 | #include "freertos/FreeRTOS.h" 23 | #include "freertos/task.h" 24 | #include "sysinfo.h" 25 | #include "led.h" 26 | 27 | class UpdateCheck 28 | { 29 | private: 30 | SysInfo* _sysInfo; 31 | LED *_statusLED; 32 | TaskHandle_t _tHandle = NULL; 33 | void _updateLatestVersion(); 34 | char _latestVersion[33] = "n/a"; 35 | 36 | public: 37 | UpdateCheck(SysInfo* sysInfo, LED *statusLED); 38 | void start(); 39 | void stop(); 40 | 41 | const char* getLatestVersion(); 42 | 43 | void _taskFunc(); 44 | }; -------------------------------------------------------------------------------- /include/webui.h: -------------------------------------------------------------------------------- 1 | /* 2 | * webui.h is part of the HB-RF-ETH firmware - https://github.com/alexreinert/HB-RF-ETH 3 | * 4 | * Copyright 2022 Alexander Reinert 5 | * 6 | * The HB-RF-ETH firmware is licensed under a 7 | * Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International License. 8 | * 9 | * You should have received a copy of the license along with this 10 | * work. If not, see . 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | * 18 | */ 19 | 20 | #pragma once 21 | #include 22 | #include "settings.h" 23 | #include "led.h" 24 | #include "updatecheck.h" 25 | #include "radiomoduleconnector.h" 26 | #include "radiomoduledetector.h" 27 | #include "rawuartudplistener.h" 28 | #include "ethernet.h" 29 | #include "esp_http_server.h" 30 | 31 | class WebUI 32 | { 33 | private: 34 | httpd_handle_t _httpd_handle; 35 | 36 | public: 37 | WebUI(Settings *settings, LED *statusLED, SysInfo *sysInfo, UpdateCheck *updateCheck, Ethernet *ethernet, RawUartUdpListener *rawUartUdpListener, RadioModuleConnector *radioModuleConnector, RadioModuleDetector *radioModuleDetector); 38 | void start(); 39 | void stop(); 40 | }; -------------------------------------------------------------------------------- /lib/README: -------------------------------------------------------------------------------- 1 | 2 | This directory is intended for project specific (private) libraries. 3 | PlatformIO will compile them to static libraries and link into executable file. 4 | 5 | The source code of each library should be placed in a an own separate directory 6 | ("lib/your_library_name/[here are source files]"). 7 | 8 | For example, see a structure of the following two libraries `Foo` and `Bar`: 9 | 10 | |--lib 11 | | | 12 | | |--Bar 13 | | | |--docs 14 | | | |--examples 15 | | | |--src 16 | | | |- Bar.c 17 | | | |- Bar.h 18 | | | |- library.json (optional, custom build options, etc) https://docs.platformio.org/page/librarymanager/config.html 19 | | | 20 | | |--Foo 21 | | | |- Foo.c 22 | | | |- Foo.h 23 | | | 24 | | |- README --> THIS FILE 25 | | 26 | |- platformio.ini 27 | |--src 28 | |- main.c 29 | 30 | and a contents of `src/main.c`: 31 | ``` 32 | #include 33 | #include 34 | 35 | int main (void) 36 | { 37 | ... 38 | } 39 | 40 | ``` 41 | 42 | PlatformIO Library Dependency Finder will find automatically dependent 43 | libraries scanning project source files. 44 | 45 | More information about PlatformIO Library Dependency Finder 46 | - https://docs.platformio.org/page/librarymanager/ldf.html 47 | -------------------------------------------------------------------------------- /partitions.csv: -------------------------------------------------------------------------------- 1 | # ESP-IDF Partition Table 2 | # Name, Type, SubType, Offset, Size, Flags 3 | nvs, data, nvs, , 0x4000, 4 | otadata, data, ota, , 0x2000, 5 | phy_init, data, phy, , 0x1000, 6 | ota_0, app, ota_0, , 0x1d0000, 7 | ota_1, app, ota_1, , 0x1d0000, 8 | spiffs, data, spiffs, , 0x50000 9 | -------------------------------------------------------------------------------- /platformio.ini: -------------------------------------------------------------------------------- 1 | ; PlatformIO Project Configuration File 2 | ; 3 | ; Build options: build flags, source filter 4 | ; Upload options: custom upload port, speed and extra flags 5 | ; Library options: dependencies, extra library storages 6 | ; Advanced options: extra scripting 7 | ; 8 | ; Please visit documentation for the other options and examples 9 | ; https://docs.platformio.org/page/projectconf.html 10 | 11 | [env] 12 | 13 | [env:hb-rf-eth] 14 | platform = espressif32 15 | board = hb-rf-eth 16 | framework = espidf 17 | monitor_speed = 115200 18 | monitor_filters = esp32_exception_decoder 19 | board_build.embed_files = 20 | webui/dist/main.1e43358e.css.gz 21 | webui/dist/main.1e43358e.js.gz 22 | webui/dist/index.html.gz 23 | webui/dist/favicon.26242483.ico.gz 24 | board_build.partitions = partitions.csv 25 | extra_scripts = 26 | pre:append_version_to_progname.py 27 | pre:build_webui.py -------------------------------------------------------------------------------- /src/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # This file was automatically generated for projects 2 | # without default 'CMakeLists.txt' file. 3 | 4 | FILE(GLOB_RECURSE app_sources ${CMAKE_SOURCE_DIR}/src/*.*) 5 | 6 | idf_component_register(SRCS ${app_sources}) 7 | 8 | target_add_binary_data(${COMPONENT_TARGET} "../webui/dist/main.1e43358e.css.gz" BINARY) 9 | target_add_binary_data(${COMPONENT_TARGET} "../webui/dist/main.1e43358e.js.gz" BINARY) 10 | target_add_binary_data(${COMPONENT_TARGET} "../webui/dist/index.html.gz" BINARY) 11 | target_add_binary_data(${COMPONENT_TARGET} "../webui/dist/favicon.26242483.ico.gz" BINARY) 12 | -------------------------------------------------------------------------------- /src/dcf.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * dcf.cpp is part of the HB-RF-ETH firmware - https://github.com/alexreinert/HB-RF-ETH 3 | * 4 | * Copyright 2022 Alexander Reinert 5 | * 6 | * The HB-RF-ETH firmware is licensed under a 7 | * Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International License. 8 | * 9 | * You should have received a copy of the license along with this 10 | * work. If not, see . 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | * 18 | */ 19 | 20 | #include "dcf.h" 21 | #include 22 | #include "esp_log.h" 23 | #include "freertos/FreeRTOS.h" 24 | #include "freertos/task.h" 25 | #include "freertos/queue.h" 26 | #include "driver/gpio.h" 27 | #include "pins.h" 28 | 29 | static SystemClock *_clk; 30 | static Settings *_settings; 31 | 32 | static uint64_t _buffer = 0; 33 | static uint8_t _bufferPos = 0; 34 | 35 | static int64_t _previousSecondMark = 0; 36 | static int64_t _secondMark = 0; 37 | 38 | static QueueHandle_t _flank_queue; 39 | static TaskHandle_t _queueHandlerTask; 40 | 41 | static const char *TAG = "DCF"; 42 | 43 | typedef struct 44 | { 45 | int64_t flankTime; 46 | int state; 47 | } flank_event_t; 48 | 49 | inline uint8_t bcd2bin(uint8_t val) 50 | { 51 | return val - 6 * (val >> 4); 52 | } 53 | 54 | DCF::DCF(Settings *settings, SystemClock *clk) 55 | { 56 | _settings = settings; 57 | _clk = clk; 58 | } 59 | 60 | bool checkParity(uint8_t start, uint8_t end) 61 | { 62 | int parity = 0; 63 | 64 | for (int pos = start; pos <= end; pos++) 65 | { 66 | parity ^= (int)((_buffer >> pos) & 1); 67 | } 68 | 69 | return parity == 0; 70 | } 71 | 72 | int is_leap(unsigned int y) 73 | { 74 | return (y % 4) == 0 && ((y % 100) != 0 || ((y + 1900) % 400) == 0); 75 | } 76 | 77 | time_t dcf2epoch(struct tm *dcf_tm, uint8_t tz) 78 | { 79 | static const unsigned ndays[12] = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}; 80 | 81 | time_t res = 0; 82 | int i; 83 | 84 | for (i = 70; i < dcf_tm->tm_year; ++i) 85 | { 86 | res += is_leap(i) ? 366 : 365; 87 | } 88 | 89 | for (i = 0; i < dcf_tm->tm_mon; ++i) 90 | { 91 | res += ndays[i]; 92 | if (i == 1 && is_leap(dcf_tm->tm_year)) 93 | { 94 | res++; 95 | } 96 | } 97 | 98 | res += dcf_tm->tm_mday - 1; 99 | res *= 24; 100 | res += dcf_tm->tm_hour; 101 | res -= tz ^ 3; 102 | res *= 60; 103 | res += dcf_tm->tm_min; 104 | res *= 60; 105 | 106 | return res; 107 | } 108 | 109 | static void IRAM_ATTR onPinChange(void *arg) 110 | { 111 | int64_t flankTime = esp_timer_get_time(); 112 | int state = gpio_get_level(DCF_PIN); 113 | 114 | flank_event_t event; 115 | event.flankTime = flankTime; 116 | event.state = state; 117 | 118 | BaseType_t xHigherPriorityTaskWokenByPost = pdFALSE; 119 | xQueueSendFromISR(_flank_queue, &event, &xHigherPriorityTaskWokenByPost); 120 | 121 | if (xHigherPriorityTaskWokenByPost) 122 | { 123 | portYIELD_FROM_ISR(); 124 | } 125 | } 126 | 127 | static void handlePinChange(int64_t flankTime, int state) 128 | { 129 | if (state) 130 | { 131 | // end of pulse 132 | int64_t secondLength = _secondMark - _previousSecondMark; 133 | int64_t pulseLength = flankTime - _secondMark; 134 | 135 | uint64_t pulseValue; 136 | if (pulseLength > 80000 && pulseLength < 135000) 137 | { 138 | pulseValue = 0; 139 | } 140 | else if (pulseLength > 180000 && pulseLength < 235000) 141 | { 142 | pulseValue = 1; 143 | } 144 | else 145 | { 146 | ESP_LOGE(TAG, "Invalid pulse: %llu %llu", secondLength / 1000ULL, pulseLength / 1000ULL); 147 | return; // invalid pulse length 148 | } 149 | 150 | if (secondLength > 970000 && secondLength < 1035000) 151 | { 152 | if (_bufferPos < 59) 153 | { 154 | _bufferPos++; 155 | _buffer |= (pulseValue << _bufferPos); 156 | } 157 | } 158 | else if (secondLength > 1970000 && secondLength < 2035000) 159 | { 160 | uint8_t timezone = (uint8_t)((_buffer >> 17) & 3); 161 | 162 | if (_bufferPos < 58 || _bufferPos > 59 || (int)(_buffer & 1) != 0 || (int)((_buffer >> 20) & 1) != 1 || timezone == 0 || timezone == 3 || !checkParity(21, 28) || !checkParity(29, 35) || !checkParity(36, 58)) 163 | { 164 | ESP_LOGE(TAG, "Received invalid frame"); 165 | } 166 | else 167 | { 168 | struct tm dcf_tm; 169 | dcf_tm.tm_year = 100 + bcd2bin((int)((_buffer >> 50) & 0xff)); 170 | dcf_tm.tm_mon = bcd2bin((int)((_buffer >> 45) & 0x1f)) - 1; 171 | dcf_tm.tm_mday = bcd2bin((int)((_buffer >> 36) & 0x3f)); 172 | dcf_tm.tm_hour = bcd2bin((int)((_buffer >> 29) & 0x3f)); 173 | dcf_tm.tm_min = bcd2bin((int)((_buffer >> 21) & 0x7f)); 174 | dcf_tm.tm_sec = 0; 175 | 176 | struct timeval tv; 177 | tv.tv_sec = dcf2epoch(&dcf_tm, timezone); 178 | tv.tv_usec = esp_timer_get_time() - _secondMark + _settings->getDcfOffset(); 179 | 180 | ESP_LOGI(TAG, "Updated time to %02d-%02d-%02d %02d:%02d:%02d.%06ld %s", dcf_tm.tm_year + 1900, dcf_tm.tm_mon + 1, dcf_tm.tm_mday, dcf_tm.tm_hour, dcf_tm.tm_min, dcf_tm.tm_sec, tv.tv_usec, timezone == 2 ? "CET" : "CEST"); 181 | _clk->setTime(&tv); 182 | } 183 | 184 | _buffer = pulseValue; 185 | _bufferPos = 0; 186 | } 187 | else 188 | { 189 | ESP_LOGE(TAG, "Invalid second: %llu %llu", secondLength / 1000ULL, pulseLength / 1000ULL); 190 | return; // invalid second length 191 | } 192 | } 193 | else 194 | { 195 | // start of pulse 196 | _previousSecondMark = _secondMark; 197 | _secondMark = flankTime; 198 | } 199 | } 200 | 201 | static void flankEventQueueHandler(void *arg) 202 | { 203 | flank_event_t event; 204 | 205 | for (;;) 206 | { 207 | if (xQueueReceive(_flank_queue, &event, portMAX_DELAY) == pdTRUE) 208 | { 209 | handlePinChange(event.flankTime, event.state); 210 | } 211 | } 212 | 213 | vTaskDelete(NULL); 214 | } 215 | 216 | void DCF::start() 217 | { 218 | _flank_queue = xQueueCreate(8, sizeof(flank_event_t)); 219 | xTaskCreate(flankEventQueueHandler, "DFC_FlankEvent_QueueHandler", 4096, NULL, 17, &_queueHandlerTask); 220 | 221 | gpio_config_t io_conf; 222 | io_conf.intr_type = GPIO_INTR_ANYEDGE; 223 | io_conf.mode = GPIO_MODE_INPUT; 224 | io_conf.pin_bit_mask = 1ULL << DCF_PIN; 225 | io_conf.pull_down_en = GPIO_PULLDOWN_DISABLE; 226 | io_conf.pull_up_en = GPIO_PULLUP_DISABLE; 227 | gpio_config(&io_conf); 228 | 229 | gpio_install_isr_service(0); 230 | gpio_isr_handler_add(DCF_PIN, onPinChange, NULL); 231 | } 232 | 233 | void DCF::stop() 234 | { 235 | gpio_isr_handler_remove(DCF_PIN); 236 | xQueueReset(_flank_queue); 237 | vTaskDelete(_queueHandlerTask); 238 | } 239 | -------------------------------------------------------------------------------- /src/ethernet.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * ethernet.cpp is part of the HB-RF-ETH firmware - https://github.com/alexreinert/HB-RF-ETH 3 | * 4 | * Copyright 2022 Alexander Reinert 5 | * 6 | * The HB-RF-ETH firmware is licensed under a 7 | * Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International License. 8 | * 9 | * You should have received a copy of the license along with this 10 | * work. If not, see . 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | * 18 | */ 19 | 20 | #include "ethernet.h" 21 | #include "pins.h" 22 | 23 | static const char *TAG = "Ethernet"; 24 | 25 | void _handleETHEvent(void *arg, esp_event_base_t event_base, int32_t event_id, void *event_data) 26 | { 27 | reinterpret_cast(arg)->_handleETHEvent(event_base, event_id, event_data); 28 | } 29 | 30 | void _handleIPEvent(void *arg, esp_event_base_t event_base, int32_t event_id, void *event_data) 31 | { 32 | reinterpret_cast(arg)->_handleIPEvent(event_base, event_id, event_data); 33 | } 34 | 35 | Ethernet::Ethernet(Settings *settings) : _settings(settings), _isConnected(false) 36 | { 37 | ESP_ERROR_CHECK_WITHOUT_ABORT(esp_netif_init()); 38 | 39 | ESP_ERROR_CHECK_WITHOUT_ABORT(esp_event_loop_create_default()); 40 | 41 | _netif_cfg = ESP_NETIF_DEFAULT_ETH(); 42 | _eth_netif = esp_netif_new(&_netif_cfg); 43 | 44 | if (settings->getUseDHCP()) 45 | { 46 | ESP_ERROR_CHECK_WITHOUT_ABORT(esp_netif_dhcpc_start(_eth_netif)); 47 | } 48 | else 49 | { 50 | ESP_ERROR_CHECK_WITHOUT_ABORT(esp_netif_dhcpc_stop(_eth_netif)); 51 | 52 | esp_netif_ip_info_t ipInfo; 53 | ipInfo.ip.addr = settings->getLocalIP().addr; 54 | ipInfo.netmask.addr = settings->getNetmask().addr; 55 | ipInfo.gw.addr = settings->getGateway().addr; 56 | ESP_ERROR_CHECK_WITHOUT_ABORT(esp_netif_set_ip_info(_eth_netif, &ipInfo)); 57 | 58 | esp_netif_dns_info_t dnsInfo; 59 | dnsInfo.ip.type = ESP_IPADDR_TYPE_V4; 60 | dnsInfo.ip.u_addr.ip4.addr = settings->getDns1().addr; 61 | if (dnsInfo.ip.u_addr.ip4.addr != IPADDR_ANY && dnsInfo.ip.u_addr.ip4.addr != IPADDR_NONE) 62 | { 63 | ESP_ERROR_CHECK_WITHOUT_ABORT(esp_netif_set_dns_info(_eth_netif, ESP_NETIF_DNS_MAIN, &dnsInfo)); 64 | } 65 | 66 | dnsInfo.ip.u_addr.ip4.addr = settings->getDns2().addr; 67 | if (dnsInfo.ip.u_addr.ip4.addr != IPADDR_ANY && dnsInfo.ip.u_addr.ip4.addr != IPADDR_NONE) 68 | { 69 | ESP_ERROR_CHECK_WITHOUT_ABORT(esp_netif_set_dns_info(_eth_netif, ESP_NETIF_DNS_BACKUP, &dnsInfo)); 70 | } 71 | } 72 | 73 | ESP_ERROR_CHECK_WITHOUT_ABORT(esp_eth_set_default_handlers(_eth_netif)); 74 | ESP_ERROR_CHECK_WITHOUT_ABORT(esp_event_handler_register(ETH_EVENT, ESP_EVENT_ANY_ID, &::_handleETHEvent, (void *)this)); 75 | ESP_ERROR_CHECK_WITHOUT_ABORT(esp_event_handler_register(IP_EVENT, IP_EVENT_ETH_GOT_IP, &::_handleIPEvent, (void *)this)); 76 | 77 | _phy_config = ETH_PHY_DEFAULT_CONFIG(); 78 | _phy_config.phy_addr = ETH_PHY_ADDR; 79 | _phy_config.reset_gpio_num = ETH_POWER_PIN; 80 | _phy = esp_eth_phy_new_lan8720(&_phy_config); 81 | 82 | _mac_config = ETH_MAC_DEFAULT_CONFIG(); 83 | _mac_config.smi_mdc_gpio_num = ETH_MDC_PIN; 84 | _mac_config.smi_mdio_gpio_num = ETH_MDIO_PIN; 85 | _mac = esp_eth_mac_new_esp32(&_mac_config); 86 | 87 | _eth_config = ETH_DEFAULT_CONFIG(_mac, _phy); 88 | _eth_handle = NULL; 89 | ESP_ERROR_CHECK_WITHOUT_ABORT(esp_eth_driver_install(&_eth_config, &_eth_handle)); 90 | ESP_ERROR_CHECK_WITHOUT_ABORT(esp_netif_attach(_eth_netif, esp_eth_new_netif_glue(_eth_handle))); 91 | } 92 | 93 | void Ethernet::start() 94 | { 95 | ESP_ERROR_CHECK_WITHOUT_ABORT(esp_eth_start(_eth_handle)); 96 | } 97 | 98 | void Ethernet::stop() 99 | { 100 | ESP_ERROR_CHECK_WITHOUT_ABORT(esp_eth_stop(_eth_handle)); 101 | } 102 | 103 | void Ethernet::getNetworkSettings(ip4_addr_t *ip, ip4_addr_t *netmask, ip4_addr_t *gateway, ip4_addr_t *dns1, ip4_addr_t *dns2) 104 | { 105 | if (_isConnected) 106 | { 107 | esp_netif_ip_info_t ipInfo; 108 | esp_netif_get_ip_info(_eth_netif, &ipInfo); 109 | ip->addr = ipInfo.ip.addr; 110 | netmask->addr = ipInfo.netmask.addr; 111 | gateway->addr = ipInfo.gw.addr; 112 | 113 | esp_netif_dns_info_t dnsInfo; 114 | esp_netif_get_dns_info(_eth_netif, ESP_NETIF_DNS_MAIN, &dnsInfo); 115 | dns1->addr = dnsInfo.ip.u_addr.ip4.addr; 116 | esp_netif_get_dns_info(_eth_netif, ESP_NETIF_DNS_BACKUP, &dnsInfo); 117 | dns2->addr = dnsInfo.ip.u_addr.ip4.addr; 118 | } 119 | else 120 | { 121 | ip->addr = 0; 122 | netmask->addr = 0; 123 | gateway->addr = 0; 124 | dns1->addr = 0; 125 | dns2->addr = 0; 126 | } 127 | } 128 | 129 | void Ethernet::_handleETHEvent(esp_event_base_t event_base, int32_t event_id, void *event_data) 130 | { 131 | esp_eth_handle_t eth_handle = *(esp_eth_handle_t *)event_data; 132 | uint8_t mac_addr[6] = {0}; 133 | 134 | switch (event_id) 135 | { 136 | case ETHERNET_EVENT_CONNECTED: 137 | ESP_ERROR_CHECK_WITHOUT_ABORT(esp_eth_ioctl(eth_handle, ETH_CMD_G_MAC_ADDR, mac_addr)); 138 | ESP_LOGI(TAG, "Link Up"); 139 | ESP_LOGI(TAG, "HW Addr %02x:%02x:%02x:%02x:%02x:%02x", mac_addr[0], mac_addr[1], mac_addr[2], mac_addr[3], mac_addr[4], mac_addr[5]); 140 | break; 141 | case ETHERNET_EVENT_DISCONNECTED: 142 | _isConnected = false; 143 | ESP_LOGI(TAG, "Link Down"); 144 | break; 145 | case ETHERNET_EVENT_START: 146 | ESP_LOGI(TAG, "Started"); 147 | ESP_ERROR_CHECK_WITHOUT_ABORT(esp_netif_set_hostname(_eth_netif, _settings->getHostname())); 148 | break; 149 | case ETHERNET_EVENT_STOP: 150 | _isConnected = false; 151 | ESP_LOGI(TAG, "Stopped"); 152 | break; 153 | default: 154 | break; 155 | } 156 | } 157 | 158 | void Ethernet::_handleIPEvent(esp_event_base_t event_base, int32_t event_id, void *event_data) 159 | { 160 | ip_event_got_ip_t *event = (ip_event_got_ip_t *)event_data; 161 | const esp_netif_ip_info_t *ip_info = &event->ip_info; 162 | 163 | _isConnected = true; 164 | ESP_LOGI(TAG, "IPv4: " IPSTR, IP2STR(&ip_info->ip)); 165 | } 166 | -------------------------------------------------------------------------------- /src/gps.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * gps.cpp is part of the HB-RF-ETH firmware - https://github.com/alexreinert/HB-RF-ETH 3 | * 4 | * Copyright 2022 Alexander Reinert 5 | * 6 | * The HB-RF-ETH firmware is licensed under a 7 | * Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International License. 8 | * 9 | * You should have received a copy of the license along with this 10 | * work. If not, see . 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | * 18 | */ 19 | 20 | #include "GPS.h" 21 | #include "pins.h" 22 | #include "esp_log.h" 23 | #include "string.h" 24 | 25 | void gpsSerialQueueHandlerTask(void *parameter) 26 | { 27 | ((GPS *)parameter)->_gpsSerialQueueHandler(); 28 | } 29 | 30 | GPS::GPS(Settings *settings, SystemClock *clk) : _settings(settings), _clk(clk) 31 | { 32 | uart_config_t uart_config = { 33 | .baud_rate = _settings->getGpsBaudrate(), 34 | .data_bits = UART_DATA_8_BITS, 35 | .parity = UART_PARITY_DISABLE, 36 | .stop_bits = UART_STOP_BITS_1, 37 | .flow_ctrl = UART_HW_FLOWCTRL_DISABLE, 38 | .rx_flow_ctrl_thresh = 0, 39 | .use_ref_tick = false}; 40 | uart_param_config(UART_NUM_2, &uart_config); 41 | uart_set_pin(UART_NUM_2, GPIO_NUM_0, DCF_PIN, UART_PIN_NO_CHANGE, UART_PIN_NO_CHANGE); 42 | 43 | using namespace std::placeholders; 44 | _lineReader = new LineReader(std::bind(&GPS::_handleLine, this, _1, _2)); 45 | } 46 | 47 | void GPS::start() 48 | { 49 | uart_driver_install(UART_NUM_2, UART_FIFO_LEN * 2, 0, 20, &_uart_queue, 0); 50 | xTaskCreate(gpsSerialQueueHandlerTask, "GPS_UART_QueueHandler", 4096, this, 15, &_tHandle); 51 | } 52 | 53 | void GPS::stop() 54 | { 55 | uart_driver_delete(UART_NUM_2); 56 | vTaskDelete(_tHandle); 57 | } 58 | 59 | void GPS::_gpsSerialQueueHandler() 60 | { 61 | uart_event_t event; 62 | uint8_t *buffer = (uint8_t *)malloc(UART_FIFO_LEN); 63 | 64 | uart_flush_input(UART_NUM_2); 65 | 66 | for (;;) 67 | { 68 | if (xQueueReceive(_uart_queue, (void *)&event, (portTickType)portMAX_DELAY)) 69 | { 70 | switch (event.type) 71 | { 72 | case UART_DATA: 73 | uart_read_bytes(UART_NUM_2, buffer, event.size, portMAX_DELAY); 74 | _lineReader->Append(buffer, event.size); 75 | break; 76 | case UART_FIFO_OVF: 77 | case UART_BUFFER_FULL: 78 | uart_flush_input(UART_NUM_2); 79 | xQueueReset(_uart_queue); 80 | _lineReader->Flush(); 81 | break; 82 | case UART_BREAK: 83 | case UART_PARITY_ERR: 84 | case UART_FRAME_ERR: 85 | _lineReader->Flush(); 86 | break; 87 | default: 88 | break; 89 | } 90 | } 91 | } 92 | 93 | free(buffer); 94 | buffer = NULL; 95 | vTaskDelete(NULL); 96 | } 97 | 98 | bool parseRMCTime(unsigned char *buffer, uint16_t len, timeval *tv) 99 | { 100 | int fieldIndex = 0; 101 | int fieldStart = 0; 102 | 103 | int fieldLength; 104 | 105 | struct tm time = {}; 106 | time.tm_isdst = 0; 107 | 108 | for (int i = 0; i < len; i++) 109 | { 110 | if (buffer[i] == ',') 111 | { 112 | fieldLength = i - fieldStart; 113 | 114 | if (fieldIndex == 1) 115 | { 116 | if (fieldLength != 9) 117 | return false; 118 | 119 | time.tm_sec = ((buffer[fieldStart + 4] - '0') * 10) + (buffer[fieldStart + 5] - '0'); 120 | time.tm_min = ((buffer[fieldStart + 2] - '0') * 10) + (buffer[fieldStart + 3] - '0'); 121 | time.tm_hour = ((buffer[fieldStart + 0] - '0') * 10) + (buffer[fieldStart + 1] - '0'); 122 | 123 | tv->tv_usec = ((buffer[fieldStart + 4] - '7') * 100000) + ((buffer[fieldStart + 8] - '0') * 10000); 124 | } 125 | else if (fieldIndex == 9) 126 | { 127 | if (fieldLength != 6) 128 | return false; 129 | 130 | time.tm_year = ((buffer[fieldStart + 4] - '0') * 10) + (buffer[fieldStart + 5] - '0') + 100; 131 | time.tm_mon = ((buffer[fieldStart + 2] - '0') * 10) + (buffer[fieldStart + 3] - '0') - 1; 132 | time.tm_mday = ((buffer[fieldStart + 0] - '0') * 10) + (buffer[fieldStart + 1] - '0'); 133 | } 134 | 135 | fieldIndex++; 136 | fieldStart = i + 1; 137 | } 138 | } 139 | 140 | if (fieldIndex != 12) 141 | return false; 142 | 143 | tv->tv_sec = mktime(&time); 144 | 145 | return true; 146 | } 147 | 148 | void GPS::_handleLine(unsigned char *buffer, uint16_t len) 149 | { 150 | uint64_t startTime = esp_timer_get_time(); 151 | if ((len > 6) && (strncmp((char *)buffer, "$GPRMC", 6) == 0)) 152 | { 153 | timeval tv; 154 | if (parseRMCTime(buffer, len, &tv)) 155 | { 156 | if (_nextSync < startTime) 157 | { 158 | _nextSync = startTime + 300 * 1000 * 1000; // every 5 minutes 159 | 160 | tv.tv_usec += esp_timer_get_time() - startTime; 161 | _clk->setTime(&tv); 162 | } 163 | } 164 | } 165 | } 166 | -------------------------------------------------------------------------------- /src/hmframe.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * hmframe.cpp is part of the HB-RF-ETH firmware - https://github.com/alexreinert/HB-RF-ETH 3 | * 4 | * Copyright 2022 Alexander Reinert 5 | * 6 | * The HB-RF-ETH firmware is licensed under a 7 | * Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International License. 8 | * 9 | * You should have received a copy of the license along with this 10 | * work. If not, see . 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | * 18 | */ 19 | 20 | #include "hmframe.h" 21 | #include 22 | 23 | uint16_t HMFrame::crc(unsigned char *buffer, uint16_t len) 24 | { 25 | uint16_t crc = 0xd77f; 26 | int i; 27 | 28 | while (len--) 29 | { 30 | crc ^= *buffer++ << 8; 31 | for (i = 0; i < 8; i++) 32 | { 33 | if (crc & 0x8000) 34 | { 35 | crc <<= 1; 36 | crc ^= 0x8005; 37 | } 38 | else 39 | { 40 | crc <<= 1; 41 | } 42 | } 43 | } 44 | 45 | return crc; 46 | } 47 | 48 | bool HMFrame::TryParse(unsigned char *buffer, uint16_t len, HMFrame *frame) 49 | { 50 | uint16_t crc; 51 | 52 | if (len < 8) 53 | return false; 54 | 55 | if (buffer[0] != 0xfd) 56 | return false; 57 | 58 | frame->data_len = ((buffer[1] << 8) | buffer[2]) - 3; 59 | if (frame->data_len + 8 != len) 60 | return false; 61 | 62 | crc = (buffer[len - 2] << 8) | buffer[len - 1]; 63 | if (crc != HMFrame::crc(buffer, len - 2)) 64 | return false; 65 | 66 | frame->destination = buffer[3]; 67 | frame->counter = buffer[4]; 68 | frame->command = buffer[5]; 69 | frame->data = &buffer[6]; 70 | 71 | return true; 72 | } 73 | 74 | HMFrame::HMFrame() : data_len(0) 75 | { 76 | } 77 | 78 | uint16_t HMFrame::encode(unsigned char *buffer, uint16_t len, bool escaped) 79 | { 80 | uint16_t crc; 81 | 82 | if (data_len + 8 > len) 83 | return 0; 84 | 85 | buffer[0] = 0xfd; 86 | buffer[1] = ((data_len + 3) >> 8) & 0xff; 87 | buffer[2] = (data_len + 3) & 0xff; 88 | buffer[3] = destination; 89 | buffer[4] = counter; 90 | buffer[5] = command; 91 | if (data_len > 0) 92 | memcpy(&(buffer[6]), data, data_len); 93 | 94 | crc = HMFrame::crc(buffer, data_len + 6); 95 | buffer[data_len + 6] = (crc >> 8) & 0xff; 96 | buffer[data_len + 7] = crc & 0xff; 97 | 98 | uint16_t res = data_len + 8; 99 | 100 | if (escaped) 101 | { 102 | for (uint16_t i = 1; i < res; i++) 103 | { 104 | if (buffer[i] == 0xfc || buffer[i] == 0xfd) 105 | { 106 | memmove(buffer + i + 1, buffer + i, res - i); 107 | buffer[i++] = 0xfc; 108 | buffer[i] &= 0x7f; 109 | res++; 110 | } 111 | } 112 | } 113 | 114 | return res; 115 | } 116 | -------------------------------------------------------------------------------- /src/led.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * led.cpp is part of the HB-RF-ETH firmware - https://github.com/alexreinert/HB-RF-ETH 3 | * 4 | * Copyright 2022 Alexander Reinert 5 | * 6 | * The HB-RF-ETH firmware is licensed under a 7 | * Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International License. 8 | * 9 | * You should have received a copy of the license along with this 10 | * work. If not, see . 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | * 18 | */ 19 | 20 | #include "led.h" 21 | #include "freertos/FreeRTOS.h" 22 | #include "freertos/task.h" 23 | #include "driver/gpio.h" 24 | 25 | static uint8_t _blinkState = 0; 26 | static LED *_leds[MAX_LED_COUNT] = {0}; 27 | static TaskHandle_t _switchTaskHandle = NULL; 28 | static int _highDuty; 29 | 30 | void ledSwitcherTask(void *parameter) 31 | { 32 | for (;;) 33 | { 34 | _blinkState = (_blinkState + 1) % 24; 35 | 36 | for (uint8_t i = 0; i < MAX_LED_COUNT; i++) 37 | { 38 | if (_leds[i] == 0) 39 | { 40 | break; 41 | } 42 | _leds[i]->updatePinState(); 43 | } 44 | vTaskDelay(125 / portTICK_PERIOD_MS); 45 | } 46 | 47 | vTaskDelete(NULL); 48 | } 49 | 50 | void LED::start(Settings *settings) 51 | { 52 | ledc_timer_config_t ledc_timer = { 53 | .speed_mode = LEDC_HIGH_SPEED_MODE, 54 | .duty_resolution = LEDC_TIMER_11_BIT, 55 | .timer_num = LEDC_TIMER_0, 56 | .freq_hz = 5000, 57 | .clk_cfg = LEDC_AUTO_CLK, 58 | }; 59 | 60 | _highDuty = settings->getLEDBrightness() * (1 << ledc_timer.duty_resolution) / 100; 61 | 62 | ledc_timer_config(&ledc_timer); 63 | 64 | if (!_switchTaskHandle) 65 | { 66 | xTaskCreate(ledSwitcherTask, "LED_Switcher", 4096, NULL, 10, &_switchTaskHandle); 67 | } 68 | } 69 | 70 | void LED::stop() 71 | { 72 | if (_switchTaskHandle) 73 | { 74 | vTaskDelete(_switchTaskHandle); 75 | _switchTaskHandle = NULL; 76 | } 77 | } 78 | 79 | LED::LED(gpio_num_t pin) 80 | { 81 | _channel_conf = { 82 | .gpio_num = pin, 83 | .speed_mode = LEDC_HIGH_SPEED_MODE, 84 | .channel = LEDC_CHANNEL_MAX, 85 | .intr_type = LEDC_INTR_DISABLE, 86 | .timer_sel = LEDC_TIMER_0, 87 | .duty = 0, 88 | .hpoint = 0, 89 | }; 90 | 91 | for (uint8_t i = 0; i < MAX_LED_COUNT; i++) 92 | { 93 | if (_leds[i] == 0) 94 | { 95 | _channel_conf.channel = (ledc_channel_t)i; 96 | break; 97 | } 98 | } 99 | 100 | ledc_channel_config(&_channel_conf); 101 | 102 | _leds[_channel_conf.channel] = this; 103 | } 104 | 105 | void LED::setState(led_state_t state) 106 | { 107 | _state = state; 108 | updatePinState(); 109 | } 110 | 111 | void LED::_setPinState(bool enabled) { 112 | ledc_set_duty(_channel_conf.speed_mode, _channel_conf.channel, enabled ? _highDuty : 0); 113 | ledc_update_duty(_channel_conf.speed_mode, _channel_conf.channel); 114 | } 115 | 116 | void LED::updatePinState() 117 | { 118 | switch (_state) 119 | { 120 | case LED_STATE_OFF: 121 | _setPinState(false); 122 | break; 123 | case LED_STATE_ON: 124 | _setPinState(true); 125 | break; 126 | case LED_STATE_BLINK: 127 | _setPinState((_blinkState % 8) < 4); 128 | break; 129 | case LED_STATE_BLINK_INV: 130 | _setPinState((_blinkState % 8) >= 4); 131 | break; 132 | case LED_STATE_BLINK_FAST: 133 | _setPinState((_blinkState % 2) == 0); 134 | break; 135 | case LED_STATE_BLINK_SLOW: 136 | _setPinState(_blinkState < 12); 137 | break; 138 | } 139 | } -------------------------------------------------------------------------------- /src/linereader.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * linereader.cpp is part of the HB-RF-ETH firmware - https://github.com/alexreinert/HB-RF-ETH 3 | * 4 | * Copyright 2022 Alexander Reinert 5 | * 6 | * The HB-RF-ETH firmware is licensed under a 7 | * Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International License. 8 | * 9 | * You should have received a copy of the license along with this 10 | * work. If not, see . 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | * 18 | */ 19 | 20 | #include "linereader.h" 21 | #include 22 | 23 | LineReader::LineReader(std::function processor) : _processor(processor), _buffer_pos(0) 24 | { 25 | } 26 | 27 | void LineReader::Append(unsigned char chr) 28 | { 29 | switch (chr) 30 | { 31 | case '\r': 32 | return; 33 | 34 | case '\n': 35 | _buffer[_buffer_pos++] = 0; 36 | _processor(_buffer, _buffer_pos); 37 | _buffer_pos = 0; 38 | break; 39 | 40 | default: 41 | _buffer[_buffer_pos++] = chr; 42 | break; 43 | } 44 | } 45 | 46 | void LineReader::Append(unsigned char *buffer, uint16_t len) 47 | { 48 | int i; 49 | for (i = 0; i < len; i++) 50 | { 51 | Append(buffer[i]); 52 | } 53 | } 54 | 55 | void LineReader::Flush() 56 | { 57 | _buffer_pos = 0; 58 | } 59 | -------------------------------------------------------------------------------- /src/main.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * main.cpp is part of the HB-RF-ETH firmware - https://github.com/alexreinert/HB-RF-ETH 3 | * 4 | * Copyright 2022 Alexander Reinert 5 | * 6 | * The HB-RF-ETH firmware is licensed under a 7 | * Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International License. 8 | * 9 | * You should have received a copy of the license along with this 10 | * work. If not, see . 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | * 18 | */ 19 | 20 | #include 21 | #include "freertos/FreeRTOS.h" 22 | #include "freertos/task.h" 23 | #include "esp_system.h" 24 | #include "esp_spi_flash.h" 25 | 26 | #include "pins.h" 27 | #include "led.h" 28 | #include "settings.h" 29 | #include "rtc.h" 30 | #include "systemclock.h" 31 | #include "dcf.h" 32 | #include "ntpclient.h" 33 | #include "gps.h" 34 | #include "ethernet.h" 35 | #include "pushbuttonhandler.h" 36 | #include "radiomoduleconnector.h" 37 | #include "radiomoduledetector.h" 38 | #include "rawuartudplistener.h" 39 | #include "webui.h" 40 | #include "mdnsserver.h" 41 | #include "ntpserver.h" 42 | #include "esp_ota_ops.h" 43 | #include "updatecheck.h" 44 | 45 | static const char *TAG = "HB-RF-ETH"; 46 | 47 | extern "C" 48 | { 49 | void app_main(void); 50 | } 51 | 52 | void app_main() 53 | { 54 | uart_config_t uart_config = { 55 | .baud_rate = 115200, 56 | .data_bits = UART_DATA_8_BITS, 57 | .parity = UART_PARITY_DISABLE, 58 | .stop_bits = UART_STOP_BITS_1, 59 | .flow_ctrl = UART_HW_FLOWCTRL_DISABLE, 60 | .rx_flow_ctrl_thresh = 0, 61 | .source_clk = UART_SCLK_APB}; 62 | uart_param_config(UART_NUM_0, &uart_config); 63 | uart_set_pin(UART_NUM_0, GPIO_NUM_1, GPIO_NUM_3, UART_PIN_NO_CHANGE, UART_PIN_NO_CHANGE); 64 | 65 | Settings settings; 66 | 67 | LED powerLED(LED_PWR_PIN); 68 | LED statusLED(LED_STATUS_PIN); 69 | 70 | LED redLED(HM_RED_PIN); 71 | LED greenLED(HM_GREEN_PIN); 72 | LED blueLED(HM_BLUE_PIN); 73 | 74 | LED::start(&settings); 75 | 76 | powerLED.setState(LED_STATE_BLINK); 77 | statusLED.setState(LED_STATE_BLINK_INV); 78 | 79 | redLED.setState(LED_STATE_OFF); 80 | greenLED.setState(LED_STATE_OFF); 81 | blueLED.setState(LED_STATE_OFF); 82 | 83 | SysInfo sysInfo; 84 | 85 | PushButtonHandler pushButton; 86 | pushButton.handleStartupFactoryReset(&powerLED, &statusLED, &settings); 87 | 88 | RadioModuleConnector radioModuleConnector(&redLED, &greenLED, &blueLED); 89 | radioModuleConnector.start(); 90 | 91 | Ethernet ethernet(&settings); 92 | ethernet.start(); 93 | 94 | setenv("TZ", "UTC0", 1); 95 | tzset(); 96 | 97 | Rtc *rtc = NULL; 98 | rtc = Rtc::detect(); 99 | 100 | SystemClock clk(rtc); 101 | clk.start(); 102 | 103 | DCF dcf(&settings, &clk); 104 | NtpClient ntpClient(&settings, &clk); 105 | GPS gps(&settings, &clk); 106 | 107 | switch (settings.getTimesource()) 108 | { 109 | case TIMESOURCE_NTP: 110 | ntpClient.start(); 111 | break; 112 | case TIMESOURCE_GPS: 113 | gps.start(); 114 | break; 115 | case TIMESOURCE_DCF: 116 | dcf.start(); 117 | break; 118 | } 119 | 120 | MDns mdns; 121 | mdns.start(&settings); 122 | 123 | NtpServer ntpServer(&clk); 124 | ntpServer.start(); 125 | 126 | RadioModuleDetector radioModuleDetector; 127 | radioModuleDetector.detectRadioModule(&radioModuleConnector); 128 | 129 | radio_module_type_t radioModuleType = radioModuleDetector.getRadioModuleType(); 130 | if (radioModuleType != RADIO_MODULE_NONE) 131 | { 132 | switch (radioModuleType) 133 | { 134 | case RADIO_MODULE_HM_MOD_RPI_PCB: 135 | ESP_LOGI(TAG, "Detected HM-MOD-RPI-PCB:"); 136 | break; 137 | case RADIO_MODULE_RPI_RF_MOD: 138 | ESP_LOGI(TAG, "Detected RPI-RF-MOD:"); 139 | break; 140 | default: 141 | ESP_LOGI(TAG, "Detected unknown radio module:"); 142 | break; 143 | } 144 | 145 | ESP_LOGI(TAG, " Serial: %s", radioModuleDetector.getSerial()); 146 | ESP_LOGI(TAG, " SGTIN: %s", radioModuleDetector.getSGTIN()); 147 | ESP_LOGI(TAG, " BidCos Radio MAC: 0x%06X", radioModuleDetector.getBidCosRadioMAC()); 148 | ESP_LOGI(TAG, " HmIP Radio MAC: 0x%06X", radioModuleDetector.getHmIPRadioMAC()); 149 | 150 | const uint8_t *firmwareVersion = radioModuleDetector.getFirmwareVersion(); 151 | ESP_LOGI(TAG, " Firmware Version: %d.%d.%d", *firmwareVersion, *(firmwareVersion + 1), *(firmwareVersion + 2)); 152 | } 153 | else 154 | { 155 | ESP_LOGW(TAG, "Radio module could not be detected."); 156 | } 157 | 158 | radioModuleConnector.resetModule(); 159 | 160 | RawUartUdpListener rawUartUdpLister(&radioModuleConnector); 161 | rawUartUdpLister.start(); 162 | 163 | UpdateCheck updateCheck(&sysInfo, &statusLED); 164 | updateCheck.start(); 165 | 166 | WebUI webUI(&settings, &statusLED, &sysInfo, &updateCheck, ðernet, &rawUartUdpLister, &radioModuleConnector, &radioModuleDetector); 167 | webUI.start(); 168 | 169 | powerLED.setState(LED_STATE_ON); 170 | statusLED.setState(LED_STATE_OFF); 171 | 172 | esp_ota_mark_app_valid_cancel_rollback(); 173 | 174 | vTaskSuspend(NULL); 175 | } 176 | -------------------------------------------------------------------------------- /src/mdnsserver.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * mdnsserver.cpp is part of the HB-RF-ETH firmware - https://github.com/alexreinert/HB-RF-ETH 3 | * 4 | * Copyright 2022 Alexander Reinert 5 | * 6 | * The HB-RF-ETH firmware is licensed under a 7 | * Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International License. 8 | * 9 | * You should have received a copy of the license along with this 10 | * work. If not, see . 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | * 18 | */ 19 | 20 | #include "mdnsserver.h" 21 | #include "mdns.h" 22 | #include "esp_system.h" 23 | 24 | void MDns::start(Settings* settings) 25 | { 26 | ESP_ERROR_CHECK_WITHOUT_ABORT(mdns_init()); 27 | ESP_ERROR_CHECK_WITHOUT_ABORT(mdns_hostname_set(settings->getHostname())); 28 | 29 | ESP_ERROR_CHECK_WITHOUT_ABORT(mdns_service_add(NULL, "_http", "_tcp", 80, NULL, 0)); 30 | ESP_ERROR_CHECK_WITHOUT_ABORT(mdns_service_add(NULL, "_raw-uart", "_udp", 3008, NULL, 0)); 31 | ESP_ERROR_CHECK_WITHOUT_ABORT(mdns_service_add(NULL, "_ntp", "_udp", 123, NULL, 0)); 32 | } 33 | 34 | void MDns::stop() 35 | { 36 | mdns_free(); 37 | } 38 | -------------------------------------------------------------------------------- /src/ntpclient.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * ntpclient.cpp is part of the HB-RF-ETH firmware - https://github.com/alexreinert/HB-RF-ETH 3 | * 4 | * Copyright 2022 Alexander Reinert 5 | * 6 | * The HB-RF-ETH firmware is licensed under a 7 | * Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International License. 8 | * 9 | * You should have received a copy of the license along with this 10 | * work. If not, see . 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | * 18 | */ 19 | 20 | #include "ntpclient.h" 21 | #include "esp_sntp.h" 22 | 23 | static Settings *_settings; 24 | static SystemClock *_clk; 25 | 26 | static void _time_sync_notification_cb(struct timeval *tv) 27 | { 28 | _clk->setTime(tv); 29 | } 30 | 31 | NtpClient::NtpClient(Settings *settings, SystemClock *clk) 32 | { 33 | _settings = settings; 34 | _clk = clk; 35 | } 36 | 37 | void NtpClient::start() 38 | { 39 | sntp_setoperatingmode(SNTP_OPMODE_POLL); 40 | sntp_setservername(0, _settings->getNtpServer()); 41 | sntp_set_time_sync_notification_cb(_time_sync_notification_cb); 42 | sntp_init(); 43 | } 44 | 45 | void NtpClient::stop() 46 | { 47 | sntp_stop(); 48 | } -------------------------------------------------------------------------------- /src/ntpserver.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * ntpserver.cpp is part of the HB-RF-ETH firmware - https://github.com/alexreinert/HB-RF-ETH 3 | * 4 | * Copyright 2022 Alexander Reinert 5 | * 6 | * The HB-RF-ETH firmware is licensed under a 7 | * Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International License. 8 | * 9 | * You should have received a copy of the license along with this 10 | * work. If not, see . 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | * 18 | */ 19 | 20 | #include "ntpserver.h" 21 | #include 22 | #include "esp_log.h" 23 | #include "udphelper.h" 24 | 25 | static const char *TAG = "NtpServer"; 26 | 27 | void _ntp_udpQueueHandlerTask(void *parameter) 28 | { 29 | ((NtpServer *)parameter)->_udpQueueHandler(); 30 | } 31 | 32 | void _ntp_udpReceivePaket(void *arg, udp_pcb *pcb, pbuf *pb, const ip_addr_t *addr, uint16_t port) 33 | { 34 | while (pb != NULL) 35 | { 36 | pbuf *this_pb = pb; 37 | pb = pb->next; 38 | this_pb->next = NULL; 39 | if (!((NtpServer *)arg)->_udpReceivePacket(this_pb, addr, port)) 40 | { 41 | pbuf_free(this_pb); 42 | } 43 | } 44 | } 45 | 46 | NtpServer::NtpServer(SystemClock *clk) : _clk(clk) 47 | { 48 | } 49 | 50 | inline tstamp convertToNtp(struct timeval *tv) 51 | { 52 | tv->tv_sec = htonl(tv->tv_sec + 2208988800L); 53 | tv->tv_usec = htonl(tv->tv_usec / 1e6 * 4294967296.); 54 | 55 | return (unsigned long long)tv->tv_sec + (((unsigned long long)tv->tv_usec) << 32); 56 | } 57 | 58 | void NtpServer::handlePacket(pbuf *pb, ip4_addr_t addr, uint16_t port) 59 | { 60 | struct timeval tv = _clk->getTime(); 61 | tstamp recv = convertToNtp(&tv); 62 | 63 | struct timeval lastSync = _clk->getLastSyncTime(); 64 | if (lastSync.tv_sec < 1577836800l) // 2020-01-01 00:00:00 GMT 65 | { 66 | ESP_LOGE(TAG, "Ignoring ntp request because local time is not set"); 67 | return; 68 | } 69 | 70 | if (pb->len != sizeof(ntp_packet_t)) 71 | { 72 | ESP_LOGE(TAG, "Ignoring packet with bad length %d (should be %d)", pb->len, sizeof(ntp_packet_t)); 73 | return; 74 | } 75 | 76 | ntp_packet_t ntp; 77 | memcpy(&ntp, pb->payload, sizeof(ntp)); 78 | 79 | pbuf *resp_pb = pbuf_alloc_reference(&ntp, sizeof(ntp_packet_t), PBUF_REF); 80 | 81 | ip_addr_t resp_addr; 82 | resp_addr.type = IPADDR_TYPE_V4; 83 | resp_addr.u_addr.ip4 = addr; 84 | 85 | ntp.flags = 4 << 3 | 4; // version 4, mode server 86 | ntp.stratum = 15; // Prefer other ntp server over us 87 | ntp.poll = 8; 88 | ntp.precision = 0; // precision 2^0=1sec because of RTC values; 89 | ntp.delay = htonl(1 << 16); // 1sec 90 | ntp.dispersion = htonl(1 << 16); // 1sec 91 | memset(ntp.ref_id, 0, sizeof(ntp.ref_id)); 92 | ntp.orig_time = ntp.trns_time; 93 | ntp.recv_time = recv; 94 | ntp.ref_time = convertToNtp(&lastSync); 95 | tv = _clk->getTime(); 96 | ntp.trns_time = convertToNtp(&tv); 97 | 98 | _udp_sendto(_pcb, resp_pb, &resp_addr, port); 99 | pbuf_free(resp_pb); 100 | } 101 | 102 | void NtpServer::start() 103 | { 104 | _udp_queue = xQueueCreate(32, sizeof(udp_event_t *)); 105 | xTaskCreate(_ntp_udpQueueHandlerTask, "NTPServer_UDP_QueueHandler", 4096, this, 10, &_tHandle); 106 | 107 | _pcb = udp_new(); 108 | udp_recv(_pcb, &_ntp_udpReceivePaket, (void *)this); 109 | 110 | _udp_bind(_pcb, IP4_ADDR_ANY, 123); 111 | } 112 | 113 | void NtpServer::stop() 114 | { 115 | _udp_disconnect(_pcb); 116 | udp_recv(_pcb, NULL, NULL); 117 | _udp_remove(_pcb); 118 | _pcb = NULL; 119 | 120 | vTaskDelete(_tHandle); 121 | } 122 | 123 | void NtpServer::_udpQueueHandler() 124 | { 125 | udp_event_t *event = NULL; 126 | 127 | for (;;) 128 | { 129 | if (xQueueReceive(_udp_queue, &event, portMAX_DELAY) == pdTRUE) 130 | { 131 | handlePacket(event->pb, event->addr, event->port); 132 | pbuf_free(event->pb); 133 | free(event); 134 | } 135 | } 136 | 137 | vTaskDelete(NULL); 138 | } 139 | 140 | bool NtpServer::_udpReceivePacket(pbuf *pb, const ip_addr_t *addr, uint16_t port) 141 | { 142 | udp_event_t *e = (udp_event_t *)malloc(sizeof(udp_event_t)); 143 | if (!e) 144 | { 145 | return false; 146 | } 147 | 148 | e->pb = pb; 149 | 150 | #pragma GCC diagnostic push 151 | #pragma GCC diagnostic ignored "-Wpointer-arith" 152 | 153 | ip_hdr *iphdr = reinterpret_cast(pb->payload - UDP_HLEN - IP_HLEN); 154 | e->addr.addr = iphdr->src.addr; 155 | 156 | udp_hdr *udphdr = reinterpret_cast(pb->payload - UDP_HLEN); 157 | e->port = ntohs(udphdr->src); 158 | 159 | #pragma GCC diagnostic pop 160 | 161 | if (xQueueSend(_udp_queue, &e, portMAX_DELAY) != pdPASS) 162 | { 163 | free((void *)(e)); 164 | return false; 165 | } 166 | return true; 167 | } -------------------------------------------------------------------------------- /src/pushbuttonhandler.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * pushbuttonhandler.cpp is part of the HB-RF-ETH firmware - https://github.com/alexreinert/HB-RF-ETH 3 | * 4 | * Copyright 2022 Alexander Reinert 5 | * 6 | * The HB-RF-ETH firmware is licensed under a 7 | * Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International License. 8 | * 9 | * You should have received a copy of the license along with this 10 | * work. If not, see . 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | * 18 | */ 19 | 20 | #include "pushbuttonhandler.h" 21 | 22 | static const char *TAG = "PushButtonHandler"; 23 | 24 | PushButtonHandler::PushButtonHandler() 25 | { 26 | gpio_config_t io_conf; 27 | io_conf.intr_type = GPIO_INTR_DISABLE; 28 | io_conf.mode = GPIO_MODE_INPUT; 29 | io_conf.pin_bit_mask = 1ULL << HM_BTN_PIN; 30 | io_conf.pull_down_en = GPIO_PULLDOWN_DISABLE; 31 | io_conf.pull_up_en = GPIO_PULLUP_DISABLE; 32 | gpio_config(&io_conf); 33 | } 34 | 35 | void PushButtonHandler::handleStartupFactoryReset(LED *powerLED, LED *statusLED, Settings *settings) 36 | { 37 | if (gpio_get_level(HM_BTN_PIN) == 0) 38 | { 39 | // reset request start if button is pressed at least for 4sec 40 | for (int i = 0; i < 40; i++) 41 | { 42 | if (gpio_get_level(HM_BTN_PIN) == 1) 43 | { 44 | return; 45 | } 46 | vTaskDelay(100 / portTICK_PERIOD_MS); 47 | } 48 | 49 | powerLED->setState(LED_STATE_OFF); 50 | statusLED->setState(LED_STATE_BLINK_FAST); 51 | 52 | ESP_LOGI(TAG, "Factory Reset mode started."); 53 | 54 | // wait for release of button 55 | while (gpio_get_level(HM_BTN_PIN) == 0) 56 | { 57 | vTaskDelay(100 / portTICK_PERIOD_MS); 58 | } 59 | 60 | // wait to be pressed again or timeout 61 | for (int i = 0; i < 40; i++) 62 | { 63 | if (gpio_get_level(HM_BTN_PIN) == 0) 64 | { 65 | break; 66 | } 67 | vTaskDelay(100 / portTICK_PERIOD_MS); 68 | } 69 | 70 | // reset request start if button is pressed at least for 4sec 71 | for (int i = 0; i < 40; i++) 72 | { 73 | if (gpio_get_level(HM_BTN_PIN) == 1) 74 | { 75 | statusLED->setState(LED_STATE_ON); 76 | vTaskDelay(1000 / portTICK_PERIOD_MS); 77 | powerLED->setState(LED_STATE_ON); 78 | statusLED->setState(LED_STATE_OFF); 79 | ESP_LOGI(TAG, "Factory Reset mode timeout."); 80 | return; 81 | } 82 | vTaskDelay(100 / portTICK_PERIOD_MS); 83 | } 84 | 85 | statusLED->setState(LED_STATE_OFF); 86 | vTaskDelay(100 / portTICK_PERIOD_MS); 87 | 88 | settings->clear(); 89 | ESP_LOGI(TAG, "Factory Reset done."); 90 | 91 | powerLED->setState(LED_STATE_ON); 92 | statusLED->setState(LED_STATE_ON); 93 | vTaskDelay(1000 / portTICK_PERIOD_MS); 94 | powerLED->setState(LED_STATE_BLINK); 95 | statusLED->setState(LED_STATE_BLINK_INV); 96 | } 97 | } -------------------------------------------------------------------------------- /src/radiomoduleconnector.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * radiomoduleconnector.cpp is part of the HB-RF-ETH firmware - https://github.com/alexreinert/HB-RF-ETH 3 | * 4 | * Copyright 2022 Alexander Reinert 5 | * 6 | * The HB-RF-ETH firmware is licensed under a 7 | * Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International License. 8 | * 9 | * You should have received a copy of the license along with this 10 | * work. If not, see . 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | * 18 | */ 19 | 20 | #include 21 | #include 22 | #include 23 | #include "radiomoduleconnector.h" 24 | #include "hmframe.h" 25 | #include "driver/gpio.h" 26 | #include "pins.h" 27 | #include "esp_log.h" 28 | 29 | static const char *TAG = "RadioModuleConnector"; 30 | 31 | void serialQueueHandlerTask(void *parameter) 32 | { 33 | ((RadioModuleConnector *)parameter)->_serialQueueHandler(); 34 | } 35 | 36 | RadioModuleConnector::RadioModuleConnector(LED *redLED, LED *greenLed, LED *blueLed) : _redLED(redLED), _greenLED(greenLed), _blueLED(blueLed) 37 | { 38 | gpio_config_t io_conf; 39 | io_conf.intr_type = GPIO_INTR_DISABLE; 40 | io_conf.mode = GPIO_MODE_OUTPUT; 41 | io_conf.pin_bit_mask = 1ULL << HM_RST_PIN; 42 | io_conf.pull_down_en = GPIO_PULLDOWN_DISABLE; 43 | io_conf.pull_up_en = GPIO_PULLUP_DISABLE; 44 | gpio_config(&io_conf); 45 | 46 | uart_config_t uart_config = { 47 | .baud_rate = 115200, 48 | .data_bits = UART_DATA_8_BITS, 49 | .parity = UART_PARITY_DISABLE, 50 | .stop_bits = UART_STOP_BITS_1, 51 | .flow_ctrl = UART_HW_FLOWCTRL_DISABLE, 52 | .rx_flow_ctrl_thresh = 0, 53 | .source_clk = UART_SCLK_APB}; 54 | uart_param_config(UART_NUM_1, &uart_config); 55 | uart_set_pin(UART_NUM_1, HM_TX_PIN, HM_RX_PIN, UART_PIN_NO_CHANGE, UART_PIN_NO_CHANGE); 56 | 57 | using namespace std::placeholders; 58 | _streamParser = new StreamParser(false, std::bind(&RadioModuleConnector::_handleFrame, this, _1, _2)); 59 | } 60 | 61 | void RadioModuleConnector::start() 62 | { 63 | setLED(false, false, false); 64 | 65 | uart_driver_install(UART_NUM_1, UART_FIFO_LEN * 2, 0, 20, &_uart_queue, 0); 66 | 67 | xTaskCreate(serialQueueHandlerTask, "RadioModuleConnector_UART_QueueHandler", 4096, this, 15, &_tHandle); 68 | resetModule(); 69 | } 70 | 71 | void RadioModuleConnector::stop() 72 | { 73 | resetModule(); 74 | uart_driver_delete(UART_NUM_1); 75 | vTaskDelete(_tHandle); 76 | } 77 | 78 | void RadioModuleConnector::setFrameHandler(FrameHandler *frameHandler, bool decodeEscaped) 79 | { 80 | atomic_store(&_frameHandler, frameHandler); 81 | _streamParser->setDecodeEscaped(decodeEscaped); 82 | } 83 | 84 | void RadioModuleConnector::setLED(bool red, bool green, bool blue) 85 | { 86 | _redLED->setState(red ? LED_STATE_ON : LED_STATE_OFF); 87 | _greenLED->setState(green ? LED_STATE_ON : LED_STATE_OFF); 88 | _blueLED->setState(blue ? LED_STATE_ON : LED_STATE_OFF); 89 | } 90 | 91 | void RadioModuleConnector::resetModule() 92 | { 93 | gpio_set_level(HM_RST_PIN, 1); 94 | vTaskDelay(50 / portTICK_PERIOD_MS); 95 | gpio_set_level(HM_RST_PIN, 0); 96 | vTaskDelay(50 / portTICK_PERIOD_MS); 97 | } 98 | 99 | void RadioModuleConnector::sendFrame(unsigned char *buffer, uint16_t len) 100 | { 101 | uart_write_bytes(UART_NUM_1, (const char *)buffer, len); 102 | } 103 | 104 | void RadioModuleConnector::_serialQueueHandler() 105 | { 106 | uart_event_t event; 107 | uint8_t *buffer = (uint8_t *)malloc(UART_FIFO_LEN); 108 | 109 | uart_flush_input(UART_NUM_1); 110 | 111 | for (;;) 112 | { 113 | if (xQueueReceive(_uart_queue, (void *)&event, (portTickType)portMAX_DELAY)) 114 | { 115 | switch (event.type) 116 | { 117 | case UART_DATA: 118 | uart_read_bytes(UART_NUM_1, buffer, event.size, portMAX_DELAY); 119 | _streamParser->append(buffer, event.size); 120 | break; 121 | case UART_FIFO_OVF: 122 | case UART_BUFFER_FULL: 123 | uart_flush_input(UART_NUM_1); 124 | xQueueReset(_uart_queue); 125 | _streamParser->flush(); 126 | break; 127 | case UART_BREAK: 128 | case UART_PARITY_ERR: 129 | case UART_FRAME_ERR: 130 | _streamParser->flush(); 131 | break; 132 | default: 133 | break; 134 | } 135 | } 136 | } 137 | 138 | free(buffer); 139 | buffer = NULL; 140 | vTaskDelete(NULL); 141 | } 142 | 143 | void RadioModuleConnector::_handleFrame(unsigned char *buffer, uint16_t len) 144 | { 145 | FrameHandler *frameHandler = (FrameHandler *)atomic_load(&_frameHandler); 146 | 147 | if (frameHandler) 148 | { 149 | frameHandler->handleFrame(buffer, len); 150 | } 151 | } 152 | -------------------------------------------------------------------------------- /src/radiomoduledetector.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * radiomoduledetector.cpp is part of the HB-RF-ETH firmware - https://github.com/alexreinert/HB-RF-ETH 3 | * 4 | * Copyright 2022 Alexander Reinert 5 | * 6 | * The HB-RF-ETH firmware is licensed under a 7 | * Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International License. 8 | * 9 | * You should have received a copy of the license along with this 10 | * work. If not, see . 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | * 18 | */ 19 | 20 | #include 21 | #include 22 | #include 23 | #include "radiomoduledetector.h" 24 | #include "hmframe.h" 25 | 26 | static const char *TAG = "RadioModuleConnector"; 27 | 28 | void RadioModuleDetector::detectRadioModule(RadioModuleConnector *radioModuleConnector) 29 | { 30 | _radioModuleConnector = radioModuleConnector; 31 | 32 | _detectState = DETECT_STATE_START_BL; 33 | _detectRetryCount = 0; 34 | _detectMsgCounter = 0; 35 | 36 | _radioModuleType = RADIO_MODULE_NONE; 37 | 38 | sem_init(_detectWaitFrameDataSemaphore); 39 | 40 | _radioModuleConnector->setFrameHandler(this, true); 41 | 42 | while (_detectState == DETECT_STATE_START_BL && _detectRetryCount < 3) 43 | { 44 | sendFrame(_detectMsgCounter++, HM_DST_COMMON, HM_CMD_COMMON_IDENTIFY, NULL, 0); 45 | if (!sem_take(_detectWaitFrameDataSemaphore, 3)) 46 | { 47 | sendFrame(_detectMsgCounter++, HM_DST_HMSYSTEM, HM_CMD_HMSYSTEM_IDENTIFY, NULL, 0); 48 | if (!sem_take(_detectWaitFrameDataSemaphore, 3)) 49 | { 50 | _detectRetryCount++; 51 | } 52 | } 53 | } 54 | 55 | _detectRetryCount = 0; 56 | while (_detectState == DETECT_STATE_START_APP && _detectRetryCount < 3) 57 | { 58 | sendFrame(_detectMsgCounter++, HM_DST_COMMON, HM_CMD_COMMON_IDENTIFY, NULL, 0); 59 | if (!sem_take(_detectWaitFrameDataSemaphore, 3)) 60 | { 61 | sendFrame(_detectMsgCounter++, HM_DST_HMSYSTEM, HM_CMD_HMSYSTEM_IDENTIFY, NULL, 0); 62 | if (!sem_take(_detectWaitFrameDataSemaphore, 3)) 63 | { 64 | _detectRetryCount++; 65 | } 66 | } 67 | } 68 | 69 | while (true) 70 | { 71 | switch (_detectState) 72 | { 73 | case DETECT_STATE_START_BL: 74 | case DETECT_STATE_START_APP: 75 | _detectState = DETECT_STATE_FINISHED; 76 | break; 77 | 78 | case DETECT_STATE_GET_MCU_TYPE: 79 | sendFrame(_detectMsgCounter++, HM_DST_TRX, HM_CMD_TRX_GET_MCU_TYPE, NULL, 0); 80 | break; 81 | 82 | case DETECT_STATE_GET_VERSION: 83 | sendFrame(_detectMsgCounter++, HM_DST_TRX, HM_CMD_TRX_GET_VERSION, NULL, 0); 84 | break; 85 | 86 | case DETECT_STATE_GET_HMIP_RF_ADDRESS: 87 | sendFrame(_detectMsgCounter++, HM_DST_HMIP, HM_CMD_HMIP_GET_DEFAULT_RF_ADDR, NULL, 0); 88 | break; 89 | 90 | case DETECT_STATE_GET_SGTIN: 91 | sendFrame(_detectMsgCounter++, HM_DST_COMMON, HM_CMD_COMMON_GET_SGTIN, NULL, 0); 92 | break; 93 | 94 | case DETECT_STATE_GET_BIDCOS_RF_ADDRESS: 95 | sendFrame(_detectMsgCounter++, HM_DST_LLMAC, HM_CMD_LLMAC_GET_DEFAULT_RF_ADDR, NULL, 0); 96 | break; 97 | 98 | case DETECT_STATE_GET_SERIAL: 99 | sendFrame(_detectMsgCounter++, HM_DST_LLMAC, HM_CMD_LLMAC_GET_SERIAL, NULL, 0); 100 | break; 101 | 102 | case DETECT_STATE_LEGACY_GET_VERSION: 103 | sendFrame(_detectMsgCounter++, HM_DST_HMSYSTEM, HM_CMD_HMSYSTEM_GET_VERSION, NULL, 0); 104 | break; 105 | 106 | case DETECT_STATE_LEGACY_GET_BIDCOS_RF_ADDRESS: 107 | sendFrame(_detectMsgCounter++, HM_DST_TRX, HM_CMD_TRX_GET_DEFAULT_RF_ADDR, NULL, 0); 108 | break; 109 | 110 | case DETECT_STATE_LEGACY_GET_SERIAL: 111 | sendFrame(_detectMsgCounter++, HM_DST_HMSYSTEM, HM_CMD_HMSYSTEM_GET_SERIAL, NULL, 0); 112 | break; 113 | 114 | case DETECT_STATE_FINISHED: 115 | break; 116 | } 117 | 118 | if (_detectState == DETECT_STATE_FINISHED || !sem_take(_detectWaitFrameDataSemaphore, 3)) 119 | { 120 | break; 121 | } 122 | } 123 | 124 | _radioModuleConnector->setFrameHandler(NULL, false); 125 | } 126 | 127 | void RadioModuleDetector::handleFrame(unsigned char *buffer, uint16_t len) 128 | { 129 | log_frame("Received HM frame:", buffer, len); 130 | 131 | HMFrame frame; 132 | if (!HMFrame::TryParse(buffer, len, &frame)) 133 | { 134 | return; 135 | } 136 | 137 | switch (_detectState) 138 | { 139 | case DETECT_STATE_START_BL: 140 | if ((frame.destination == HM_DST_COMMON && frame.command == HM_CMD_COMMON_ACK && frame.data_len == 12 && frame.data[0] == 1 && strncmp((char *)(frame.data + 1), "HMIP_TRX_Bl", 11) == 0) || (frame.destination == HM_DST_COMMON && frame.command == 0 && frame.data_len == 11 && strncmp((char *)(frame.data), "HMIP_TRX_Bl", 11) == 0)) 141 | { 142 | // TRX CoPro in bootloader 143 | _detectState = DETECT_STATE_START_APP; 144 | sem_give(_detectWaitFrameDataSemaphore); 145 | } 146 | else if ((frame.destination == HM_DST_HMSYSTEM && frame.command == HM_CMD_HMSYSTEM_ACK && frame.data_len == 10 && frame.data[0] == 2 && strncmp((char *)(frame.data + 1), "Co_CPU_BL", 9) == 0) || (frame.destination == HM_DST_HMSYSTEM && frame.command == 0 && frame.data_len == 9 && strncmp((char *)frame.data, "Co_CPU_BL", 9) == 0)) 147 | { 148 | // Legacy CoPro in bootloader 149 | _detectState = DETECT_STATE_START_APP; 150 | sem_give(_detectWaitFrameDataSemaphore); 151 | } 152 | else if ((frame.destination == HM_DST_COMMON && frame.command == HM_CMD_COMMON_ACK && frame.data_len == 14 && frame.data[0] == 1 && strncmp((char *)(frame.data + 1), "DualCoPro_App", 13) == 0) || (frame.destination == HM_DST_COMMON && frame.command == 0 && frame.data_len == 13 && strncmp((char *)frame.data, "DualCoPro_App", 13) == 0)) 153 | { 154 | // Dual CoPro in app --> start bootloader 155 | sendFrame(_detectMsgCounter++, HM_DST_COMMON, HM_CMD_COMMON_START_BL, NULL, 0); 156 | } 157 | else if ((frame.destination == HM_DST_COMMON && frame.command == HM_CMD_COMMON_ACK && frame.data_len == 13 && frame.data[0] == 1 && strncmp((char *)(frame.data + 1), "HMIP_TRX_App", 12) == 0) || (frame.destination == HM_DST_COMMON && frame.command == 0 && frame.data_len == 12 && strncmp((char *)frame.data, "HMIP_TRX_App", 12) == 0)) 158 | { 159 | // HmIP only in app --> start bootloader 160 | sendFrame(_detectMsgCounter++, HM_DST_COMMON, HM_CMD_COMMON_START_BL, NULL, 0); 161 | } 162 | else if ((frame.destination == HM_DST_HMSYSTEM && frame.command == HM_CMD_HMSYSTEM_ACK && frame.data_len == 11 && frame.data[0] == 2 && strncmp((char *)(frame.data + 1), "Co_CPU_App", 10) == 0) || (frame.destination == HM_DST_HMSYSTEM && frame.command == 0 && frame.data_len == 10 && strncmp((char *)frame.data, "Co_CPU_App", 10) == 0)) 163 | { 164 | // Legacy CoPro in app --> start bootloader 165 | sendFrame(_detectMsgCounter++, HM_DST_HMSYSTEM, HM_CMD_HMSYSTEM_CHANGE_APP, NULL, 0); 166 | } 167 | break; 168 | 169 | case DETECT_STATE_START_APP: 170 | if ((frame.destination == HM_DST_COMMON && frame.command == HM_CMD_COMMON_ACK && frame.data_len == 12 && frame.data[0] == 1 && strncmp((char *)(frame.data + 1), "HMIP_TRX_Bl", 11) == 0) || (frame.destination == HM_DST_COMMON && frame.command == 0 && frame.data_len == 11 && strncmp((char *)(frame.data), "HMIP_TRX_Bl", 11) == 0)) 171 | { 172 | // TRX CoPro in bootloader --> start app 173 | sendFrame(_detectMsgCounter++, HM_DST_COMMON, HM_CMD_COMMON_START_APP, NULL, 0); 174 | } 175 | else if ((frame.destination == HM_DST_HMSYSTEM && frame.command == HM_CMD_HMSYSTEM_ACK && frame.data_len == 10 && frame.data[0] == 2 && strncmp((char *)(frame.data + 1), "Co_CPU_BL", 9) == 0) || (frame.destination == HM_DST_HMSYSTEM && frame.command == 0 && frame.data_len == 9 && strncmp((char *)frame.data, "Co_CPU_BL", 9) == 0)) 176 | { 177 | // Legacy CoPro in bootloader --> start app 178 | sendFrame(_detectMsgCounter++, HM_DST_HMSYSTEM, HM_CMD_HMSYSTEM_CHANGE_APP, NULL, 0); 179 | } 180 | else if ((frame.destination == HM_DST_COMMON && frame.command == HM_CMD_COMMON_ACK && frame.data_len == 14 && frame.data[0] == 1 && strncmp((char *)(frame.data + 1), "DualCoPro_App", 13) == 0) || (frame.destination == HM_DST_COMMON && frame.command == 0 && frame.data_len == 13 && strncmp((char *)frame.data, "DualCoPro_App", 13) == 0)) 181 | { 182 | // Dual CoPro in app 183 | _detectState = DETECT_STATE_GET_MCU_TYPE; 184 | sem_give(_detectWaitFrameDataSemaphore); 185 | } 186 | else if ((frame.destination == HM_DST_COMMON && frame.command == HM_CMD_COMMON_ACK && frame.data_len == 13 && frame.data[0] == 1 && strncmp((char *)(frame.data + 1), "HMIP_TRX_App", 12) == 0) || (frame.destination == HM_DST_COMMON && frame.command == 0 && frame.data_len == 12 && strncmp((char *)frame.data, "HMIP_TRX_App", 12) == 0)) 187 | { 188 | // HmIP only in app 189 | _detectState = DETECT_STATE_GET_MCU_TYPE; 190 | sem_give(_detectWaitFrameDataSemaphore); 191 | } 192 | else if ((frame.destination == HM_DST_HMSYSTEM && frame.command == HM_CMD_HMSYSTEM_ACK && frame.data_len == 11 && frame.data[0] == 2 && strncmp((char *)(frame.data + 1), "Co_CPU_App", 10) == 0) || (frame.destination == HM_DST_HMSYSTEM && frame.command == 0 && frame.data_len == 10 && strncmp((char *)frame.data, "Co_CPU_App", 10) == 0)) 193 | { 194 | // Legacy CoPro in app 195 | _detectState = DETECT_STATE_LEGACY_GET_VERSION; 196 | sprintf(_sgtin, "n/a"); 197 | _hmIPRadioMAC = 0; 198 | _radioModuleType = RADIO_MODULE_HM_MOD_RPI_PCB; 199 | sem_give(_detectWaitFrameDataSemaphore); 200 | } 201 | break; 202 | 203 | case DETECT_STATE_GET_MCU_TYPE: 204 | if (frame.destination == HM_DST_TRX && frame.command == HM_CMD_TRX_ACK && frame.data_len == 2 && frame.data[0] == 1) 205 | { 206 | _radioModuleType = (radio_module_type_t)frame.data[1]; 207 | _detectState = DETECT_STATE_GET_VERSION; 208 | sem_give(_detectWaitFrameDataSemaphore); 209 | } 210 | break; 211 | 212 | case DETECT_STATE_GET_VERSION: 213 | if (frame.destination == HM_DST_TRX && frame.command == HM_CMD_TRX_ACK && frame.data_len == 10 && frame.data[0] == 1) 214 | { 215 | memcpy(_firmwareVersion, frame.data + 1, 3); 216 | _detectState = DETECT_STATE_GET_HMIP_RF_ADDRESS; 217 | sem_give(_detectWaitFrameDataSemaphore); 218 | } 219 | break; 220 | 221 | case DETECT_STATE_GET_HMIP_RF_ADDRESS: 222 | if (frame.destination == HM_DST_HMIP && frame.command == HM_CMD_HMIP_ACK && frame.data_len == 4 && frame.data[0] == 1) 223 | { 224 | _hmIPRadioMAC = (frame.data[1] << 16) | (frame.data[2] << 8) | frame.data[3]; 225 | _detectState = DETECT_STATE_GET_SGTIN; 226 | sem_give(_detectWaitFrameDataSemaphore); 227 | } 228 | break; 229 | 230 | case DETECT_STATE_GET_SGTIN: 231 | if (frame.destination == HM_DST_COMMON && frame.command == HM_CMD_COMMON_ACK && frame.data_len == 13 && frame.data[0] == 1) 232 | { 233 | sprintf(_sgtin, "%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X", frame.data[1], frame.data[2], frame.data[3], frame.data[4], frame.data[5], frame.data[6], frame.data[7], frame.data[8], frame.data[9], frame.data[10], frame.data[11], frame.data[12]); 234 | 235 | switch (_radioModuleType) 236 | { 237 | case RADIO_MODULE_RPI_RF_MOD: 238 | _bidCosRadioMAC = 0xff0000 | (frame.data[11] << 8) | frame.data[12]; 239 | if (_bidCosRadioMAC == 0xffffff) 240 | _bidCosRadioMAC = 0xfffffe; 241 | sprintf(_serial, "%02X%02X%02X%02X%02X", frame.data[8], frame.data[9], frame.data[10], frame.data[11], frame.data[12]); 242 | _detectState = DETECT_STATE_GET_BIDCOS_RF_ADDRESS; 243 | break; 244 | 245 | case RADIO_MODULE_HMIP_RFUSB: 246 | _bidCosRadioMAC = 0; 247 | sprintf(_serial, "%02X%02X%02X%02X%02X", frame.data[8], frame.data[9], frame.data[10], frame.data[11], frame.data[12]); 248 | _detectState = DETECT_STATE_FINISHED; 249 | break; 250 | 251 | default: 252 | _detectState = DETECT_STATE_GET_BIDCOS_RF_ADDRESS; 253 | break; 254 | } 255 | sem_give(_detectWaitFrameDataSemaphore); 256 | } 257 | break; 258 | 259 | case DETECT_STATE_GET_BIDCOS_RF_ADDRESS: 260 | if (frame.destination == HM_DST_LLMAC && frame.command == HM_CMD_LLMAC_ACK && frame.data_len == 4 && frame.data[0] == 1) 261 | { 262 | uint32_t radioMac = (frame.data[1] << 16) | (frame.data[2] << 8) | frame.data[3]; 263 | if (radioMac != 0 && (radioMac & 0xffff) != 0xffff) 264 | _bidCosRadioMAC = radioMac; 265 | _detectState = _radioModuleType == RADIO_MODULE_RPI_RF_MOD ? DETECT_STATE_FINISHED : DETECT_STATE_GET_SERIAL; 266 | sem_give(_detectWaitFrameDataSemaphore); 267 | } 268 | else if (frame.destination == HM_DST_LLMAC && frame.command == HM_CMD_LLMAC_ACK && frame.data_len == 1 && frame.data[0] == 0) 269 | { 270 | if (_radioModuleType == RADIO_MODULE_RPI_RF_MOD) 271 | _detectState = DETECT_STATE_FINISHED; 272 | sem_give(_detectWaitFrameDataSemaphore); 273 | } 274 | break; 275 | 276 | case DETECT_STATE_GET_SERIAL: 277 | if (frame.destination == HM_DST_LLMAC && frame.command == HM_CMD_LLMAC_ACK && frame.data_len == 11 && frame.data[0] == 1) 278 | { 279 | memcpy(_serial, frame.data + 1, 10); 280 | _detectState = DETECT_STATE_FINISHED; 281 | sem_give(_detectWaitFrameDataSemaphore); 282 | } 283 | break; 284 | 285 | case DETECT_STATE_LEGACY_GET_VERSION: 286 | if (frame.destination == HM_DST_HMSYSTEM && frame.command == HM_CMD_HMSYSTEM_ACK && frame.data_len == 7 && frame.data[0] == 2) 287 | { 288 | memcpy(_firmwareVersion, frame.data + 4, 3); 289 | _detectState = DETECT_STATE_LEGACY_GET_BIDCOS_RF_ADDRESS; 290 | sem_give(_detectWaitFrameDataSemaphore); 291 | } 292 | break; 293 | 294 | case DETECT_STATE_LEGACY_GET_BIDCOS_RF_ADDRESS: 295 | if (frame.destination == HM_DST_TRX && frame.command == HM_CMD_TRX_ACK && frame.data_len == 6) 296 | { 297 | _bidCosRadioMAC = (frame.data[3] << 16) | (frame.data[4] << 8) | frame.data[5]; 298 | _detectState = DETECT_STATE_LEGACY_GET_SERIAL; 299 | sem_give(_detectWaitFrameDataSemaphore); 300 | } 301 | break; 302 | 303 | case DETECT_STATE_LEGACY_GET_SERIAL: 304 | if (frame.destination == HM_DST_HMSYSTEM && frame.command == HM_CMD_HMSYSTEM_ACK && frame.data_len == 11 && frame.data[0] == 2) 305 | { 306 | memcpy(_serial, frame.data + 1, 10); 307 | _detectState = DETECT_STATE_FINISHED; 308 | sem_give(_detectWaitFrameDataSemaphore); 309 | } 310 | break; 311 | } 312 | } 313 | 314 | const char *RadioModuleDetector::getSerial() 315 | { 316 | return _serial; 317 | } 318 | 319 | uint32_t RadioModuleDetector::getBidCosRadioMAC() 320 | { 321 | return _bidCosRadioMAC; 322 | } 323 | 324 | uint32_t RadioModuleDetector::getHmIPRadioMAC() 325 | { 326 | return _hmIPRadioMAC; 327 | } 328 | 329 | const char *RadioModuleDetector::getSGTIN() 330 | { 331 | return _sgtin; 332 | } 333 | 334 | const uint8_t *RadioModuleDetector::getFirmwareVersion() 335 | { 336 | return _firmwareVersion; 337 | } 338 | 339 | radio_module_type_t RadioModuleDetector::getRadioModuleType() 340 | { 341 | return _radioModuleType; 342 | } 343 | 344 | void RadioModuleDetector::sendFrame(uint8_t counter, uint8_t destination, uint8_t command, unsigned char *data, uint data_len) 345 | { 346 | HMFrame frame; 347 | unsigned char sendBuffer[8 + data_len + 10]; 348 | 349 | frame.counter = counter; 350 | frame.destination = destination; 351 | frame.command = command; 352 | frame.data = data; 353 | frame.data_len = data_len; 354 | uint16_t len = frame.encode(sendBuffer, sizeof(sendBuffer), true); 355 | 356 | log_frame("Sending HM frame:", sendBuffer, len); 357 | 358 | _radioModuleConnector->sendFrame(sendBuffer, len); 359 | } 360 | -------------------------------------------------------------------------------- /src/rawuartudplistener.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * rawuartudplistener.cpp is part of the HB-RF-ETH firmware - https://github.com/alexreinert/HB-RF-ETH 3 | * 4 | * Copyright 2022 Alexander Reinert 5 | * 6 | * The HB-RF-ETH firmware is licensed under a 7 | * Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International License. 8 | * 9 | * You should have received a copy of the license along with this 10 | * work. If not, see . 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | * 18 | */ 19 | 20 | #include "rawuartudplistener.h" 21 | #include "hmframe.h" 22 | #include "esp_log.h" 23 | #include 24 | #include "udphelper.h" 25 | 26 | static const char *TAG = "RawUartUdpListener"; 27 | 28 | void _raw_uart_udpQueueHandlerTask(void *parameter) 29 | { 30 | ((RawUartUdpListener *)parameter)->_udpQueueHandler(); 31 | } 32 | 33 | void _raw_uart_udpReceivePaket(void *arg, udp_pcb *pcb, pbuf *pb, const ip_addr_t *addr, uint16_t port) 34 | { 35 | while (pb != NULL) 36 | { 37 | pbuf *this_pb = pb; 38 | pb = pb->next; 39 | this_pb->next = NULL; 40 | if (!((RawUartUdpListener *)arg)->_udpReceivePacket(this_pb, addr, port)) 41 | { 42 | pbuf_free(this_pb); 43 | } 44 | } 45 | } 46 | 47 | RawUartUdpListener::RawUartUdpListener(RadioModuleConnector *radioModuleConnector) : _radioModuleConnector(radioModuleConnector) 48 | { 49 | atomic_init(&_connectionStarted, false); 50 | atomic_init(&_remotePort, (ushort)0); 51 | atomic_init(&_remoteAddress, 0u); 52 | atomic_init(&_counter, 0); 53 | atomic_init(&_endpointConnectionIdentifier, 1); 54 | } 55 | 56 | void RawUartUdpListener::handlePacket(pbuf *pb, ip4_addr_t addr, uint16_t port) 57 | { 58 | size_t length = pb->len; 59 | unsigned char *data = (unsigned char *)(pb->payload); 60 | unsigned char response_buffer[3]; 61 | 62 | if (length < 4) 63 | { 64 | ESP_LOGE(TAG, "Received invalid raw-uart packet, length %d", length); 65 | return; 66 | } 67 | 68 | if (data[0] != 0 && (addr.addr != atomic_load(&_remoteAddress) || port != atomic_load(&_remotePort))) 69 | { 70 | ESP_LOGE(TAG, "Received raw-uart packet from invalid address."); 71 | return; 72 | } 73 | 74 | if (*((uint16_t *)(data + length - 2)) != htons(HMFrame::crc(data, length - 2))) 75 | { 76 | ESP_LOGE(TAG, "Received raw-uart packet with invalid crc."); 77 | return; 78 | } 79 | 80 | _lastReceivedKeepAlive = esp_timer_get_time(); 81 | 82 | switch (data[0]) 83 | { 84 | case 0: // connect 85 | if (length == 5 && data[2] == 1) 86 | { // protocol version 1 87 | atomic_fetch_add(&_endpointConnectionIdentifier, 2); 88 | atomic_store(&_remotePort, (ushort)0); 89 | atomic_store(&_connectionStarted, false); 90 | atomic_store(&_remoteAddress, addr.addr); 91 | atomic_store(&_remotePort, port); 92 | _radioModuleConnector->setLED(true, true, false); 93 | response_buffer[0] = 1; 94 | response_buffer[1] = data[1]; 95 | sendMessage(0, response_buffer, 2); 96 | } 97 | else if (length == 6 && data[2] == 2) { 98 | int endpointConnectionIdentifier = atomic_load(&_endpointConnectionIdentifier); 99 | 100 | if (data[3] == 0) 101 | { 102 | endpointConnectionIdentifier += 2; 103 | atomic_store(&_endpointConnectionIdentifier, endpointConnectionIdentifier); 104 | atomic_store(&_connectionStarted, false); 105 | } 106 | else if (data[3] != (endpointConnectionIdentifier & 0xff)) 107 | { 108 | ESP_LOGE(TAG, "Received raw-uart reconnect packet with invalid endpoint identifier %d, should be %d", data[3], endpointConnectionIdentifier); 109 | return; 110 | } 111 | 112 | atomic_store(&_remotePort, (ushort)0); 113 | atomic_store(&_remoteAddress, addr.addr); 114 | atomic_store(&_remotePort, port); 115 | _radioModuleConnector->setLED(true, true, false); 116 | response_buffer[0] = 2; 117 | response_buffer[1] = data[1]; 118 | response_buffer[2] = endpointConnectionIdentifier; 119 | sendMessage(0, response_buffer, 3); 120 | } 121 | else { 122 | ESP_LOGE(TAG, "Received invalid raw-uart connect packet, length %d", length); 123 | return; 124 | } 125 | break; 126 | 127 | case 1: // disconnect 128 | atomic_store(&_remotePort, (ushort)0); 129 | atomic_store(&_connectionStarted, false); 130 | atomic_store(&_remoteAddress, 0u); 131 | _radioModuleConnector->setLED(false, false, false); 132 | break; 133 | 134 | case 2: // keep alive 135 | break; 136 | 137 | case 3: // LED 138 | if (length != 5) 139 | { 140 | ESP_LOGE(TAG, "Received invalid raw-uart LED packet, length %d", length); 141 | return; 142 | } 143 | 144 | _radioModuleConnector->setLED(data[2] & 1, data[2] & 2, data[2] & 4); 145 | break; 146 | 147 | case 4: // Reset 148 | if (length != 4) 149 | { 150 | ESP_LOGE(TAG, "Received invalid raw-uart reset packet, length %d", length); 151 | return; 152 | } 153 | 154 | _radioModuleConnector->resetModule(); 155 | break; 156 | 157 | case 5: // Start connection 158 | if (length != 4) 159 | { 160 | ESP_LOGE(TAG, "Received invalid raw-uart startconn packet, length %d", length); 161 | return; 162 | } 163 | 164 | atomic_store(&_connectionStarted, true); 165 | break; 166 | 167 | case 6: // End connection 168 | if (length != 4) 169 | { 170 | ESP_LOGE(TAG, "Received invalid raw-uart endconn packet, length %d", length); 171 | return; 172 | } 173 | 174 | atomic_store(&_connectionStarted, false); 175 | break; 176 | 177 | case 7: // Frame 178 | if (length < 5) 179 | { 180 | ESP_LOGE(TAG, "Received invalid raw-uart frame packet, length %d", length); 181 | return; 182 | } 183 | 184 | _radioModuleConnector->sendFrame(&data[2], length - 4); 185 | break; 186 | 187 | default: 188 | ESP_LOGE(TAG, "Received invalid raw-uart packet with unknown type %d", data[0]); 189 | break; 190 | } 191 | } 192 | 193 | ip4_addr_t RawUartUdpListener::getConnectedRemoteAddress() 194 | { 195 | uint16_t port = atomic_load(&_remotePort); 196 | uint32_t address = atomic_load(&_remoteAddress); 197 | 198 | if (port) 199 | { 200 | ip4_addr_t res{ .addr = address }; 201 | return res; 202 | } 203 | else 204 | { 205 | return IP4_ADDR_ANY->u_addr.ip4; 206 | } 207 | } 208 | 209 | void RawUartUdpListener::sendMessage(unsigned char command, unsigned char *buffer, size_t len) 210 | { 211 | uint16_t port = atomic_load(&_remotePort); 212 | uint32_t address = atomic_load(&_remoteAddress); 213 | 214 | pbuf *pb = pbuf_alloc(PBUF_TRANSPORT, len + 4, PBUF_RAM); 215 | unsigned char *sendBuffer = (unsigned char *)pb->payload; 216 | 217 | ip_addr_t addr; 218 | addr.type = IPADDR_TYPE_V4; 219 | addr.u_addr.ip4.addr = address; 220 | 221 | if (!port) 222 | return; 223 | 224 | sendBuffer[0] = command; 225 | sendBuffer[1] = (unsigned char)atomic_fetch_add(&_counter, 1); 226 | 227 | if (len) 228 | memcpy(sendBuffer + 2, buffer, len); 229 | 230 | *((uint16_t *)(sendBuffer + len + 2)) = htons(HMFrame::crc(sendBuffer, len + 2)); 231 | 232 | _udp_sendto(_pcb, pb, &addr, port); 233 | pbuf_free(pb); 234 | } 235 | 236 | void RawUartUdpListener::handleFrame(unsigned char *buffer, uint16_t len) 237 | { 238 | if (!atomic_load(&_connectionStarted)) 239 | return; 240 | 241 | if (len > (1500 - 28 - 4)) 242 | { 243 | ESP_LOGE(TAG, "Received oversized frame from radio module, length %d", len); 244 | return; 245 | } 246 | 247 | sendMessage(7, buffer, len); 248 | } 249 | 250 | void RawUartUdpListener::start() 251 | { 252 | _udp_queue = xQueueCreate(32, sizeof(udp_event_t *)); 253 | xTaskCreate(_raw_uart_udpQueueHandlerTask, "RawUartUdpListener_UDP_QueueHandler", 4096, this, 15, &_tHandle); 254 | 255 | _pcb = udp_new(); 256 | udp_recv(_pcb, &_raw_uart_udpReceivePaket, (void *)this); 257 | 258 | _udp_bind(_pcb, IP4_ADDR_ANY, 3008); 259 | 260 | _radioModuleConnector->setFrameHandler(this, false); 261 | } 262 | 263 | void RawUartUdpListener::stop() 264 | { 265 | _udp_disconnect(_pcb); 266 | udp_recv(_pcb, NULL, NULL); 267 | _udp_remove(_pcb); 268 | _pcb = NULL; 269 | 270 | _radioModuleConnector->setFrameHandler(NULL, false); 271 | vTaskDelete(_tHandle); 272 | } 273 | 274 | void RawUartUdpListener::_udpQueueHandler() 275 | { 276 | udp_event_t *event = NULL; 277 | int64_t nextKeepAliveSentOut = esp_timer_get_time(); 278 | 279 | for (;;) 280 | { 281 | if (xQueueReceive(_udp_queue, &event, (portTickType)(100 / portTICK_PERIOD_MS)) == pdTRUE) 282 | { 283 | handlePacket(event->pb, event->addr, event->port); 284 | pbuf_free(event->pb); 285 | free(event); 286 | } 287 | 288 | if (atomic_load(&_remotePort) != 0) 289 | { 290 | int64_t now = esp_timer_get_time(); 291 | 292 | if (now > _lastReceivedKeepAlive + 5000000) 293 | { // 5 sec 294 | atomic_store(&_remotePort, (ushort)0); 295 | atomic_store(&_remoteAddress, 0u); 296 | _radioModuleConnector->setLED(true, false, false); 297 | ESP_LOGE(TAG, "Connection timed out"); 298 | } 299 | 300 | if (now > nextKeepAliveSentOut) 301 | { 302 | nextKeepAliveSentOut = now + 1000000; // 1sec 303 | sendMessage(2, NULL, 0); 304 | } 305 | } 306 | } 307 | 308 | vTaskDelete(NULL); 309 | } 310 | 311 | bool RawUartUdpListener::_udpReceivePacket(pbuf *pb, const ip_addr_t *addr, uint16_t port) 312 | { 313 | udp_event_t *e = (udp_event_t *)malloc(sizeof(udp_event_t)); 314 | if (!e) 315 | { 316 | return false; 317 | } 318 | 319 | e->pb = pb; 320 | 321 | #pragma GCC diagnostic push 322 | #pragma GCC diagnostic ignored "-Wpointer-arith" 323 | 324 | ip_hdr *iphdr = reinterpret_cast(pb->payload - UDP_HLEN - IP_HLEN); 325 | e->addr.addr = iphdr->src.addr; 326 | 327 | udp_hdr *udphdr = reinterpret_cast(pb->payload - UDP_HLEN); 328 | e->port = ntohs(udphdr->src); 329 | 330 | #pragma GCC diagnostic pop 331 | 332 | if (xQueueSend(_udp_queue, &e, portMAX_DELAY) != pdPASS) 333 | { 334 | free((void *)(e)); 335 | return false; 336 | } 337 | return true; 338 | } 339 | 340 | /* 341 | Index 0 - Type: 0-Connect, 1-Disconnect, 2-KeepAlive, 3-LED, 4-StartConn, 5-StopConn, 6-Reset, 7-Frame 342 | Index 1 - Counter 343 | Index 2..n-2 - Payload 344 | Index n-2,n-1 - CRC16 345 | 346 | Payload: 347 | Keepalive: Empty 348 | Connect: 1 Byte: Protocol version 349 | LED: 1 Byte: Bit 0 R, Bit 1 G, Bit 2 B 350 | Reset: Empty 351 | Frame: Frame-Data 352 | */ 353 | -------------------------------------------------------------------------------- /src/rtc.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * rtc.cpp is part of the HB-RF-ETH firmware - https://github.com/alexreinert/HB-RF-ETH 3 | * 4 | * Copyright 2022 Alexander Reinert 5 | * 6 | * The HB-RF-ETH firmware is licensed under a 7 | * Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International License. 8 | * 9 | * You should have received a copy of the license along with this 10 | * work. If not, see . 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | * 18 | */ 19 | 20 | #include 21 | #include "rtc.h" 22 | #include "esp_log.h" 23 | 24 | static const char *TAG = "RTC"; 25 | 26 | static uint8_t bcd2bin(uint8_t val) 27 | { 28 | return val - 6 * (val >> 4); 29 | } 30 | 31 | static uint8_t bin2bcd(uint8_t val) 32 | { 33 | return val + 6 * (val / 10); 34 | } 35 | 36 | const uint8_t daysInMonth[] = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}; 37 | 38 | static bool _isDriverInstalled = false; 39 | 40 | static void i2c_master_init() 41 | { 42 | if (_isDriverInstalled) 43 | return; 44 | 45 | i2c_config_t i2c_config = { 46 | .mode = I2C_MODE_MASTER, 47 | .sda_io_num = HM_SDA_PIN, 48 | .scl_io_num = HM_SCL_PIN, 49 | .sda_pullup_en = GPIO_PULLUP_ENABLE, 50 | .scl_pullup_en = GPIO_PULLUP_ENABLE, 51 | .master{.clk_speed = 100000}}; 52 | i2c_param_config(I2C_NUM_0, &i2c_config); 53 | i2c_driver_install(I2C_NUM_0, I2C_MODE_MASTER, 0, 0, 0); 54 | _isDriverInstalled = true; 55 | } 56 | 57 | Rtc *Rtc::detect() 58 | { 59 | RtcDS3231 *ds3231 = new RtcDS3231(); 60 | if (ds3231->begin()) 61 | { 62 | ESP_LOGI(TAG, "DS3231 RTC found and initialized."); 63 | return ds3231; 64 | } 65 | else 66 | { 67 | delete ds3231; 68 | } 69 | 70 | RtcRX8130 *rx9130 = new RtcRX8130(); 71 | if (rx9130->begin()) 72 | { 73 | ESP_LOGI(TAG, "RX9130 RTC found and initialized."); 74 | return rx9130; 75 | } 76 | else 77 | { 78 | delete rx9130; 79 | } 80 | 81 | ESP_LOGE(TAG, "No RTC found."); 82 | return NULL; 83 | } 84 | 85 | Rtc::Rtc(uint8_t address, uint8_t reg_start) : _address(address), _reg_start(reg_start) 86 | { 87 | i2c_master_init(); 88 | } 89 | 90 | Rtc::~Rtc() 91 | { 92 | } 93 | 94 | bool Rtc::begin() 95 | { 96 | i2c_cmd_handle_t cmd = i2c_cmd_link_create(); 97 | i2c_master_start(cmd); 98 | i2c_master_write_byte(cmd, (_address << 1) | I2C_MASTER_WRITE, true); 99 | i2c_master_stop(cmd); 100 | esp_err_t ret = i2c_master_cmd_begin(I2C_NUM_0, cmd, 50 / portTICK_RATE_MS); 101 | i2c_cmd_link_delete(cmd); 102 | 103 | return ret == ESP_OK; 104 | } 105 | 106 | struct timeval Rtc::GetTime() 107 | { 108 | struct timeval res = {}; 109 | 110 | uint8_t rawData[7] = {0}; 111 | 112 | i2c_cmd_handle_t cmd = i2c_cmd_link_create(); 113 | i2c_master_start(cmd); 114 | i2c_master_write_byte(cmd, _address << 1 | I2C_MASTER_WRITE, true); 115 | i2c_master_write_byte(cmd, _reg_start, true); 116 | i2c_master_start(cmd); 117 | i2c_master_write_byte(cmd, _address << 1 | I2C_MASTER_READ, true); 118 | i2c_master_read(cmd, rawData, sizeof(rawData) - 1, I2C_MASTER_ACK); 119 | i2c_master_read_byte(cmd, rawData + sizeof(rawData) - 1, I2C_MASTER_NACK); 120 | i2c_master_stop(cmd); 121 | esp_err_t ret = i2c_master_cmd_begin(I2C_NUM_0, cmd, 50 / portTICK_RATE_MS); 122 | i2c_cmd_link_delete(cmd); 123 | 124 | if (ret != ESP_OK) 125 | { 126 | ESP_LOGE(TAG, "Could not read time from RTC"); 127 | res.tv_sec = 0; 128 | res.tv_usec = 0; 129 | return res; 130 | } 131 | 132 | res.tv_sec = bcd2bin(rawData[0]); // seconds 133 | res.tv_sec += bcd2bin(rawData[1]) * 60; // minutes 134 | res.tv_sec += bcd2bin(rawData[2]) * 3600; // hours 135 | 136 | uint16_t days = bcd2bin(rawData[4]); 137 | uint8_t month = bcd2bin(rawData[5]); 138 | uint8_t year = bcd2bin(rawData[6]); 139 | 140 | for (uint8_t i = 1; i < month; ++i) 141 | { 142 | days += daysInMonth[i - 1]; 143 | } 144 | 145 | if (month > 2 && year % 4 == 0) 146 | days++; 147 | 148 | days += 365 * year + (year + 3) / 4 - 1; 149 | 150 | res.tv_sec += days * 86400; 151 | 152 | res.tv_sec += 10957 * 86400; // epoch diff 1970 vs. 2000 153 | 154 | return res; 155 | } 156 | 157 | void Rtc::SetTime(struct timeval now) 158 | { 159 | now.tv_sec -= 10957 * 86400; // epoch diff 1970 vs. 2000 160 | 161 | uint8_t seconds = now.tv_sec % 60; 162 | now.tv_sec /= 60; 163 | uint8_t minutes = now.tv_sec % 60; 164 | now.tv_sec /= 60; 165 | uint8_t hours = now.tv_sec % 24; 166 | 167 | uint16_t days = now.tv_sec / 24; 168 | 169 | uint8_t leap; 170 | uint8_t year; 171 | 172 | for (year = 0;; year++) 173 | { 174 | leap = year % 4 == 0; 175 | if (days < 365 + leap) 176 | break; 177 | days -= 365 + leap; 178 | } 179 | 180 | uint8_t month; 181 | for (month = 1;; month++) 182 | { 183 | uint8_t daysPerMonth = daysInMonth[month - 1]; 184 | if (leap && month == 2) 185 | ++daysPerMonth; 186 | if (days < daysPerMonth) 187 | break; 188 | days -= daysPerMonth; 189 | } 190 | days++; 191 | 192 | i2c_cmd_handle_t cmd = i2c_cmd_link_create(); 193 | i2c_master_start(cmd); 194 | i2c_master_write_byte(cmd, _address << 1 | I2C_MASTER_WRITE, true); 195 | i2c_master_write_byte(cmd, _reg_start, true); 196 | i2c_master_write_byte(cmd, bin2bcd(seconds), true); 197 | i2c_master_write_byte(cmd, bin2bcd(minutes), true); 198 | i2c_master_write_byte(cmd, bin2bcd(hours), true); 199 | i2c_master_write_byte(cmd, 0, true); 200 | i2c_master_write_byte(cmd, bin2bcd(days), true); 201 | i2c_master_write_byte(cmd, bin2bcd(month), true); 202 | i2c_master_write_byte(cmd, bin2bcd(year), true); 203 | i2c_master_stop(cmd); 204 | esp_err_t ret = i2c_master_cmd_begin(I2C_NUM_0, cmd, 50 / portTICK_RATE_MS); 205 | i2c_cmd_link_delete(cmd); 206 | 207 | if (ret != ESP_OK) 208 | { 209 | ESP_LOGE(TAG, "Could not write time to RTC"); 210 | } 211 | } 212 | 213 | RtcDS3231::RtcDS3231() : Rtc::Rtc(0x68, 0) 214 | { 215 | } 216 | 217 | RtcRX8130::RtcRX8130() : Rtc::Rtc(0x32, 0x10) 218 | { 219 | } 220 | 221 | bool RtcRX8130::begin() 222 | { 223 | if (Rtc::begin()) 224 | { 225 | i2c_cmd_handle_t cmd = i2c_cmd_link_create(); 226 | i2c_master_start(cmd); 227 | i2c_master_write_byte(cmd, _address << 1 | I2C_MASTER_WRITE, true); 228 | i2c_master_write_byte(cmd, 0x1f, true); 229 | i2c_master_write_byte(cmd, 0x31, true); 230 | i2c_master_stop(cmd); 231 | esp_err_t ret = i2c_master_cmd_begin(I2C_NUM_0, cmd, 50 / portTICK_RATE_MS); 232 | i2c_cmd_link_delete(cmd); 233 | 234 | return ret == ESP_OK; 235 | } 236 | else 237 | { 238 | return false; 239 | } 240 | } 241 | -------------------------------------------------------------------------------- /src/settings.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * settings.cpp is part of the HB-RF-ETH firmware - https://github.com/alexreinert/HB-RF-ETH 3 | * 4 | * Copyright 2022 Alexander Reinert 5 | * 6 | * The HB-RF-ETH firmware is licensed under a 7 | * Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International License. 8 | * 9 | * You should have received a copy of the license along with this 10 | * work. If not, see . 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | * 18 | */ 19 | 20 | #include "settings.h" 21 | #include "nvs.h" 22 | #include "nvs_flash.h" 23 | #include 24 | 25 | Settings::Settings() 26 | { 27 | load(); 28 | } 29 | 30 | static const char *TAG = "Settings"; 31 | static const char *NVS_NAMESPACE = "HB-RF-ETH"; 32 | 33 | #define GET_IP_ADDR(handle, name, var, defaultValue) \ 34 | if (nvs_get_u32(handle, name, &var.addr) != ESP_OK) \ 35 | { \ 36 | var.addr = defaultValue; \ 37 | } 38 | 39 | #define GET_INT(handle, name, var, defaultValue) \ 40 | if (nvs_get_i32(handle, name, &var) != ESP_OK) \ 41 | { \ 42 | var = defaultValue; \ 43 | } 44 | 45 | #define GET_BOOL(handle, name, var, defaultValue) \ 46 | int8_t __##var##_temp; \ 47 | if (nvs_get_i8(handle, name, &__##var##_temp) != ESP_OK) \ 48 | { \ 49 | var = defaultValue; \ 50 | } \ 51 | else \ 52 | { \ 53 | var = (__##var##_temp != 0); \ 54 | } 55 | 56 | #define SET_IP_ADDR(handle, name, var) ESP_ERROR_CHECK_WITHOUT_ABORT(nvs_set_u32(handle, name, var.addr)); 57 | #define SET_INT(handle, name, var) ESP_ERROR_CHECK_WITHOUT_ABORT(nvs_set_i32(handle, name, var)); 58 | #define SET_STR(handle, name, var) ESP_ERROR_CHECK_WITHOUT_ABORT(nvs_set_str(handle, name, var)); 59 | #define SET_BOOL(handle, name, var) ESP_ERROR_CHECK_WITHOUT_ABORT(nvs_set_i8(handle, name, var ? 1 : 0)); 60 | 61 | void Settings::load() 62 | { 63 | uint32_t handle; 64 | 65 | esp_err_t err = nvs_flash_init(); 66 | if (err == ESP_ERR_NVS_NO_FREE_PAGES || err == ESP_ERR_NVS_NEW_VERSION_FOUND) 67 | { 68 | ESP_ERROR_CHECK(nvs_flash_erase()); 69 | err = nvs_flash_init(); 70 | } 71 | ESP_ERROR_CHECK(err); 72 | 73 | ESP_ERROR_CHECK_WITHOUT_ABORT(nvs_open(NVS_NAMESPACE, NVS_READWRITE, &handle)); 74 | 75 | size_t adminPasswordLength = sizeof(_adminPassword); 76 | if (nvs_get_str(handle, "adminPassword", _adminPassword, &adminPasswordLength) != ESP_OK) 77 | { 78 | strncpy(_adminPassword, "admin", sizeof(_adminPassword) - 1); 79 | } 80 | 81 | size_t hostnameLength = sizeof(_hostname); 82 | if (nvs_get_str(handle, "hostname", _hostname, &hostnameLength) != ESP_OK) 83 | { 84 | uint8_t baseMac[6]; 85 | esp_read_mac(baseMac, ESP_MAC_ETH); 86 | snprintf(_hostname, sizeof(_hostname) - 1, "HB-RF-ETH-%02X%02X%02X", baseMac[3], baseMac[4], baseMac[5]); 87 | } 88 | 89 | GET_BOOL(handle, "useDHCP", _useDHCP, true); 90 | GET_IP_ADDR(handle, "localIP", _localIP, IPADDR_ANY); 91 | GET_IP_ADDR(handle, "netmask", _netmask, IPADDR_ANY); 92 | GET_IP_ADDR(handle, "gateway", _gateway, IPADDR_ANY); 93 | GET_IP_ADDR(handle, "dns1", _dns1, IPADDR_ANY); 94 | GET_IP_ADDR(handle, "dns2", _dns2, IPADDR_ANY); 95 | 96 | GET_INT(handle, "timesource", _timesource, TIMESOURCE_NTP); 97 | 98 | GET_INT(handle, "dcfOffset", _dcfOffset, 40000); 99 | 100 | GET_INT(handle, "gpsBaudrate", _gpsBaudrate, 9600); 101 | 102 | size_t ntpServerLength = sizeof(_ntpServer); 103 | if (nvs_get_str(handle, "ntpServer", _ntpServer, &ntpServerLength) != ESP_OK) 104 | { 105 | strncpy(_ntpServer, "pool.ntp.org", sizeof(_ntpServer) - 1); 106 | } 107 | 108 | GET_INT(handle, "ledBrightness", _ledBrightness, 100); 109 | 110 | nvs_close(handle); 111 | } 112 | 113 | void Settings::save() 114 | { 115 | uint32_t handle; 116 | 117 | ESP_ERROR_CHECK_WITHOUT_ABORT(nvs_open(NVS_NAMESPACE, NVS_READWRITE, &handle)); 118 | 119 | SET_STR(handle, "adminPassword", _adminPassword); 120 | 121 | SET_STR(handle, "hostname", _hostname); 122 | SET_BOOL(handle, "useDHCP", _useDHCP); 123 | SET_IP_ADDR(handle, "localIP", _localIP); 124 | SET_IP_ADDR(handle, "netmask", _netmask); 125 | SET_IP_ADDR(handle, "gateway", _gateway); 126 | SET_IP_ADDR(handle, "dns1", _dns1); 127 | SET_IP_ADDR(handle, "dns2", _dns2); 128 | 129 | SET_INT(handle, "timesource", _timesource); 130 | 131 | SET_INT(handle, "dcfOffset", _dcfOffset); 132 | 133 | SET_INT(handle, "gpsBaudrate", _gpsBaudrate); 134 | 135 | SET_STR(handle, "ntpServer", _ntpServer); 136 | 137 | SET_INT(handle, "ledBrightness", _ledBrightness); 138 | 139 | nvs_close(handle); 140 | } 141 | 142 | void Settings::clear() 143 | { 144 | uint32_t handle; 145 | 146 | ESP_ERROR_CHECK_WITHOUT_ABORT(nvs_open(NVS_NAMESPACE, NVS_READWRITE, &handle)); 147 | ESP_ERROR_CHECK_WITHOUT_ABORT(nvs_erase_all(handle)); 148 | ESP_ERROR_CHECK_WITHOUT_ABORT(nvs_commit(handle)); 149 | nvs_close(handle); 150 | 151 | load(); 152 | } 153 | 154 | char *Settings::getAdminPassword() 155 | { 156 | return _adminPassword; 157 | } 158 | 159 | void Settings::setAdminPassword(char *adminPassword) 160 | { 161 | strncpy(_adminPassword, adminPassword, sizeof(_adminPassword) - 1); 162 | } 163 | 164 | char *Settings::getHostname() 165 | { 166 | return _hostname; 167 | } 168 | 169 | bool Settings::getUseDHCP() 170 | { 171 | return _useDHCP; 172 | } 173 | 174 | ip4_addr_t Settings::getLocalIP() 175 | { 176 | return _localIP; 177 | } 178 | 179 | ip4_addr_t Settings::getNetmask() 180 | { 181 | return _netmask; 182 | } 183 | 184 | ip4_addr_t Settings::getGateway() 185 | { 186 | return _gateway; 187 | } 188 | 189 | ip4_addr_t Settings::getDns1() 190 | { 191 | return _dns1; 192 | } 193 | 194 | ip4_addr_t Settings::getDns2() 195 | { 196 | return _dns2; 197 | } 198 | 199 | void Settings::setNetworkSettings(char *hostname, bool useDHCP, ip4_addr_t localIP, ip4_addr_t netmask, ip4_addr_t gateway, ip4_addr_t dns1, ip4_addr_t dns2) 200 | { 201 | strncpy(_hostname, hostname, sizeof(_hostname) - 1); 202 | _useDHCP = useDHCP; 203 | _localIP = localIP; 204 | _netmask = netmask; 205 | _gateway = gateway; 206 | _dns1 = dns1; 207 | _dns2 = dns2; 208 | } 209 | 210 | int Settings::getDcfOffset() 211 | { 212 | return _dcfOffset; 213 | } 214 | 215 | void Settings::setDcfOffset(int dcfOffset) 216 | { 217 | _dcfOffset = dcfOffset; 218 | } 219 | 220 | int Settings::getGpsBaudrate() 221 | { 222 | return _gpsBaudrate; 223 | } 224 | 225 | void Settings::setGpsBaudrate(int gpsBaudrate) 226 | { 227 | _gpsBaudrate = gpsBaudrate; 228 | } 229 | 230 | timesource_t Settings::getTimesource() 231 | { 232 | return (timesource_t)_timesource; 233 | } 234 | 235 | void Settings::setTimesource(timesource_t timesource) 236 | { 237 | _timesource = timesource; 238 | } 239 | 240 | char *Settings::getNtpServer() 241 | { 242 | return _ntpServer; 243 | } 244 | 245 | void Settings::setNtpServer(char *ntpServer) 246 | { 247 | strncpy(_ntpServer, ntpServer, sizeof(_ntpServer) - 1); 248 | } 249 | 250 | int Settings::getLEDBrightness() 251 | { 252 | return _ledBrightness; 253 | } 254 | 255 | void Settings::setLEDBrightness(int ledBrightness) 256 | { 257 | _ledBrightness = ledBrightness; 258 | } 259 | -------------------------------------------------------------------------------- /src/streamparser.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * streamparser.cpp is part of the HB-RF-ETH firmware - https://github.com/alexreinert/HB-RF-ETH 3 | * 4 | * Copyright 2022 Alexander Reinert 5 | * 6 | * The HB-RF-ETH firmware is licensed under a 7 | * Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International License. 8 | * 9 | * You should have received a copy of the license along with this 10 | * work. If not, see . 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | * 18 | */ 19 | 20 | #include "streamparser.h" 21 | #include 22 | 23 | StreamParser::StreamParser(bool decodeEscaped, std::function processor) : _bufferPos(0), _state(NO_DATA), _isEscaped(false), _decodeEscaped(decodeEscaped), _processor(processor) 24 | { 25 | } 26 | 27 | void StreamParser::append(unsigned char chr) 28 | { 29 | switch (chr) 30 | { 31 | case 0xfd: 32 | _bufferPos = 0; 33 | _isEscaped = false; 34 | _state = RECEIVE_LENGTH_HIGH_BYTE; 35 | break; 36 | 37 | case 0xfc: 38 | _isEscaped = true; 39 | if (_decodeEscaped) 40 | return; 41 | break; 42 | 43 | default: 44 | if (_isEscaped && _decodeEscaped) 45 | chr |= 0x80; 46 | 47 | switch (_state) 48 | { 49 | case NO_DATA: 50 | case FRAME_COMPLETE: 51 | return; // Do nothing until the first frame prefix occurs 52 | 53 | case RECEIVE_LENGTH_HIGH_BYTE: 54 | _frameLength = (_isEscaped ? chr | 0x80 : chr) << 8; 55 | _state = RECEIVE_LENGTH_LOW_BYTE; 56 | break; 57 | 58 | case RECEIVE_LENGTH_LOW_BYTE: 59 | _frameLength |= (_isEscaped ? chr | 0x80 : chr); 60 | _frameLength += 2; // handle crc as frame data 61 | _framePos = 0; 62 | _state = RECEIVE_FRAME_DATA; 63 | break; 64 | 65 | case RECEIVE_FRAME_DATA: 66 | _framePos++; 67 | _state = (_framePos == _frameLength) ? FRAME_COMPLETE : RECEIVE_FRAME_DATA; 68 | break; 69 | } 70 | _isEscaped = false; 71 | } 72 | 73 | _buffer[_bufferPos++] = chr; 74 | 75 | if (_bufferPos == sizeof(_buffer)) 76 | _state = FRAME_COMPLETE; 77 | 78 | if (_state == FRAME_COMPLETE) 79 | { 80 | _processor(_buffer, _bufferPos); 81 | _state = NO_DATA; 82 | } 83 | } 84 | 85 | void StreamParser::append(unsigned char *buffer, uint16_t len) 86 | { 87 | int i; 88 | for (i = 0; i < len; i++) 89 | { 90 | append(buffer[i]); 91 | } 92 | } 93 | 94 | void StreamParser::flush() 95 | { 96 | _state = NO_DATA; 97 | _bufferPos = 0; 98 | _isEscaped = false; 99 | } 100 | 101 | bool StreamParser::getDecodeEscaped() 102 | { 103 | return _decodeEscaped; 104 | } 105 | void StreamParser::setDecodeEscaped(bool decodeEscaped) 106 | { 107 | _decodeEscaped = decodeEscaped; 108 | } 109 | -------------------------------------------------------------------------------- /src/sysinfo.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * sysinfo.cpp is part of the HB-RF-ETH firmware - https://github.com/alexreinert/HB-RF-ETH 3 | * 4 | * Copyright 2022 Alexander Reinert 5 | * 6 | * The HB-RF-ETH firmware is licensed under a 7 | * Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International License. 8 | * 9 | * You should have received a copy of the license along with this 10 | * work. If not, see . 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | * 18 | */ 19 | 20 | #include "sysinfo.h" 21 | #include "freertos/FreeRTOS.h" 22 | #include "freertos/task.h" 23 | #include "esp_ota_ops.h" 24 | #include "esp_log.h" 25 | #include "esp_adc_cal.h" 26 | #include "pins.h" 27 | 28 | #define DEFAULT_VREF 1100 29 | 30 | static const char *TAG = "SysInfo"; 31 | 32 | static double _cpuUsage; 33 | static char _serial[13]; 34 | static const char *_currentVersion; 35 | static board_type_t _board; 36 | 37 | void updateCPUUsageTask(void *arg) 38 | { 39 | TaskStatus_t *taskStatus = (TaskStatus_t *)malloc(25 * sizeof(TaskStatus_t)); 40 | 41 | TaskHandle_t idle0Task = xTaskGetIdleTaskHandleForCPU(0); 42 | TaskHandle_t idle1Task = xTaskGetIdleTaskHandleForCPU(1); 43 | 44 | uint32_t totalRunTime = 0, idleRunTime = 0, lastTotalRunTime = 0, lastIdleRunTime = 0; 45 | 46 | for (;;) 47 | { 48 | vTaskDelay(1000 / portTICK_PERIOD_MS); 49 | 50 | UBaseType_t taskCount = uxTaskGetSystemState(taskStatus, 25, &totalRunTime); 51 | 52 | idleRunTime = 0; 53 | 54 | if (totalRunTime > 0) 55 | { 56 | for (int i = 0; i < taskCount; i++) 57 | { 58 | TaskStatus_t ts = taskStatus[i]; 59 | 60 | if (ts.xHandle == idle0Task || ts.xHandle == idle1Task) 61 | { 62 | idleRunTime += ts.ulRunTimeCounter; 63 | } 64 | } 65 | } 66 | 67 | _cpuUsage = 100.0 - ((idleRunTime - lastIdleRunTime) * 100.0 / ((totalRunTime - lastTotalRunTime) * 2)); 68 | 69 | lastIdleRunTime = idleRunTime; 70 | lastTotalRunTime = totalRunTime; 71 | } 72 | 73 | free(taskStatus); 74 | vTaskDelete(NULL); 75 | } 76 | 77 | uint32_t get_voltage(adc_unit_t adc_unit, adc_channel_t adc_channel, adc_bits_width_t adc_width, adc_atten_t adc_atten) 78 | { 79 | esp_adc_cal_characteristics_t *adc_chars = reinterpret_cast(calloc(1, sizeof(esp_adc_cal_characteristics_t))); 80 | esp_adc_cal_characterize(adc_unit, adc_atten, adc_width, DEFAULT_VREF, adc_chars); 81 | 82 | adc_gpio_init(adc_unit, adc_channel); 83 | 84 | if (adc_unit == ADC_UNIT_1) 85 | { 86 | adc1_config_width(adc_width); 87 | adc1_config_channel_atten((adc1_channel_t)adc_channel, adc_atten); 88 | } 89 | else 90 | { 91 | adc2_config_channel_atten((adc2_channel_t)adc_channel, adc_atten); 92 | } 93 | 94 | uint32_t voltage; 95 | esp_adc_cal_get_voltage(adc_channel, adc_chars, &voltage); 96 | 97 | free(adc_chars); 98 | 99 | return voltage; 100 | } 101 | 102 | board_type_t detectBoard() 103 | { 104 | uint32_t voltage = get_voltage(BOARD_REV_SENSE_UNIT, BOARD_REV_SENSE_CHANNEL, ADC_WIDTH_BIT_10, ADC_ATTEN_DB_11); 105 | 106 | switch (voltage) // R31/R32 107 | { 108 | case 400 ... 700: // 10K/2K 109 | return BOARD_TYPE_REV_1_10_PUB; 110 | 111 | case 1500 ... 1800: // 10K/10K 112 | return BOARD_TYPE_REV_1_8_SK; 113 | 114 | case 2600 ... 2900: // 2K/10K 115 | return BOARD_TYPE_REV_1_8_PUB; 116 | 117 | case 2901 ... 3200: // 1K/12K 118 | return BOARD_TYPE_REV_1_10_SK; 119 | 120 | default: 121 | ESP_LOGW(TAG, "Could not determine board, voltage: %u", voltage); 122 | return BOARD_TYPE_UNKNOWN; 123 | } 124 | } 125 | 126 | SysInfo::SysInfo() 127 | { 128 | xTaskCreate(updateCPUUsageTask, "UpdateCPUUsage", 4096, NULL, 3, NULL); 129 | 130 | uint8_t baseMac[6]; 131 | esp_read_mac(baseMac, ESP_MAC_ETH); 132 | snprintf(_serial, sizeof(_serial), "%02X%02X%02X%02X%02X%02X", baseMac[0], baseMac[1], baseMac[2], baseMac[3], baseMac[4], baseMac[5]); 133 | 134 | _currentVersion = esp_ota_get_app_description()->version; 135 | 136 | _board = detectBoard(); 137 | } 138 | 139 | double SysInfo::getCpuUsage() 140 | { 141 | return _cpuUsage; 142 | } 143 | 144 | double SysInfo::getMemoryUsage() 145 | { 146 | multi_heap_info_t info; 147 | heap_caps_get_info(&info, MALLOC_CAP_INTERNAL); 148 | 149 | return 100.0 - (info.total_free_bytes * 100.0 / (info.total_free_bytes + info.total_allocated_bytes)); 150 | } 151 | 152 | const char *SysInfo::getSerialNumber() 153 | { 154 | return _serial; 155 | } 156 | 157 | board_type_t SysInfo::getBoardType() 158 | { 159 | return _board; 160 | } 161 | 162 | const char *SysInfo::getCurrentVersion() 163 | { 164 | return _currentVersion; 165 | } 166 | -------------------------------------------------------------------------------- /src/systemclock.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * systemclock.cpp is part of the HB-RF-ETH firmware - https://github.com/alexreinert/HB-RF-ETH 3 | * 4 | * Copyright 2022 Alexander Reinert 5 | * 6 | * The HB-RF-ETH firmware is licensed under a 7 | * Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International License. 8 | * 9 | * You should have received a copy of the license along with this 10 | * work. If not, see . 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | * 18 | */ 19 | 20 | #include "systemclock.h" 21 | #include 22 | #include "esp_log.h" 23 | 24 | static const char *TAG = "SystemClock"; 25 | 26 | #define get_tzname(isdst) isdst > 0 ? *(tzname + 1) : *tzname 27 | 28 | void updateRtcTask(void *parameter) 29 | { 30 | Rtc *_rtc = (Rtc *)parameter; 31 | 32 | for (;;) 33 | { 34 | if (ulTaskNotifyTake(pdTRUE, portMAX_DELAY) != 1) 35 | continue; 36 | 37 | struct timeval tv; 38 | gettimeofday(&tv, NULL); 39 | _rtc->SetTime(tv); 40 | 41 | struct tm now; 42 | localtime_r(&tv.tv_sec, &now); 43 | 44 | ESP_LOGI(TAG, "Updated RTC to %02d-%02d-%02d %02d:%02d:%02d %s", now.tm_year + 1900, now.tm_mon + 1, now.tm_mday, now.tm_hour, now.tm_min, now.tm_sec, get_tzname(now.tm_isdst)); 45 | } 46 | 47 | vTaskDelete(NULL); 48 | } 49 | 50 | SystemClock::SystemClock(Rtc *rtc) : _rtc(rtc) 51 | { 52 | } 53 | 54 | void SystemClock::start(void) 55 | { 56 | if (_rtc) 57 | { 58 | struct timeval tv = _rtc->GetTime(); 59 | settimeofday(&tv, NULL); 60 | _lastSyncTime = tv; 61 | 62 | time_t nowtime = tv.tv_sec; 63 | struct tm *now = localtime(&nowtime); 64 | 65 | ESP_LOGI(TAG, "Updated time from RTC to %02d-%02d-%02d %02d:%02d:%02d %s", now->tm_year + 1900, now->tm_mon + 1, now->tm_mday, now->tm_hour, now->tm_min, now->tm_sec, get_tzname(now->tm_isdst)); 66 | 67 | xTaskCreate(updateRtcTask, "SystemClock_RtcUpdateTask", 4096, _rtc, 10, &_tHandle); 68 | } 69 | } 70 | 71 | void SystemClock::stop(void) 72 | { 73 | if (_tHandle != NULL) 74 | { 75 | vTaskDelete(_tHandle); 76 | _tHandle = NULL; 77 | } 78 | } 79 | 80 | void SystemClock::setTime(struct timeval *tv) 81 | { 82 | settimeofday(tv, NULL); 83 | _lastSyncTime = *tv; 84 | 85 | if (_tHandle != NULL) 86 | { 87 | xTaskNotifyGive(_tHandle); 88 | } 89 | } 90 | 91 | struct timeval SystemClock::getTime() 92 | { 93 | struct timeval tv; 94 | gettimeofday(&tv, NULL); 95 | return tv; 96 | } 97 | 98 | struct timeval SystemClock::getLastSyncTime() 99 | { 100 | return _lastSyncTime; 101 | } 102 | 103 | struct tm SystemClock::getLocalTime(void) 104 | { 105 | time_t now; 106 | time(&now); 107 | 108 | struct tm info; 109 | localtime_r(&now, &info); 110 | 111 | return info; 112 | } 113 | -------------------------------------------------------------------------------- /src/updatecheck.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * updatecheck.cpp is part of the HB-RF-ETH firmware - https://github.com/alexreinert/HB-RF-ETH 3 | * 4 | * Copyright 2022 Alexander Reinert 5 | * 6 | * The HB-RF-ETH firmware is licensed under a 7 | * Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International License. 8 | * 9 | * You should have received a copy of the license along with this 10 | * work. If not, see . 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | * 18 | */ 19 | 20 | #include "updatecheck.h" 21 | #include "esp_http_client.h" 22 | #include "esp_log.h" 23 | #include "string.h" 24 | #include "cJSON.h" 25 | 26 | static const char *TAG = "UpdateCheck"; 27 | 28 | void _update_check_task_func(void *parameter) 29 | { 30 | { 31 | ((UpdateCheck *)parameter)->_taskFunc(); 32 | } 33 | } 34 | 35 | UpdateCheck::UpdateCheck(SysInfo* sysInfo, LED *statusLED) : _sysInfo(sysInfo), _statusLED(statusLED) 36 | { 37 | } 38 | 39 | void UpdateCheck::start() 40 | { 41 | xTaskCreate(_update_check_task_func, "UpdateCheck", 4096, this, 3, &_tHandle); 42 | } 43 | 44 | void UpdateCheck::stop() 45 | { 46 | vTaskDelete(_tHandle); 47 | } 48 | 49 | const char *UpdateCheck::getLatestVersion() 50 | { 51 | return _latestVersion; 52 | } 53 | 54 | void UpdateCheck::_updateLatestVersion() 55 | { 56 | char url[128]; 57 | snprintf(url, sizeof(url), "https://www.debmatic.de/hb-rf-eth/latestVersion.json?board=%d&serial=%s&version=%s", _sysInfo->getBoardType(), _sysInfo->getSerialNumber(), _sysInfo->getCurrentVersion()); 58 | 59 | esp_http_client_config_t config = {}; 60 | config.url = url; 61 | 62 | esp_http_client_handle_t client = esp_http_client_init(&config); 63 | 64 | if (esp_http_client_perform(client) == ESP_OK) 65 | { 66 | char buffer[128]; 67 | int len = esp_http_client_read(client, buffer, sizeof(buffer) - 1); 68 | 69 | if (len > 0) 70 | { 71 | buffer[len] = 0; 72 | cJSON *json = cJSON_Parse(buffer); 73 | 74 | char *latestVersion = cJSON_GetStringValue(cJSON_GetObjectItem(json, "version")); 75 | if (latestVersion != NULL) 76 | { 77 | strcpy(_latestVersion, latestVersion); 78 | } 79 | cJSON_Delete(json); 80 | } 81 | } 82 | 83 | esp_http_client_cleanup(client); 84 | } 85 | 86 | void UpdateCheck::_taskFunc() 87 | { 88 | // some time for initial network connection 89 | vTaskDelay(30000 / portTICK_PERIOD_MS); 90 | 91 | for (;;) 92 | { 93 | ESP_LOGI(TAG, "Start checking for the latest available firmware."); 94 | _updateLatestVersion(); 95 | 96 | if (strcmp(_latestVersion, "n/a") != 0 && strcmp(_sysInfo->getCurrentVersion(), _latestVersion) < 0) 97 | { 98 | ESP_LOGW(TAG, "An updated firmware with version %s is available.", _latestVersion); 99 | _statusLED->setState(LED_STATE_BLINK_SLOW); 100 | } 101 | else 102 | { 103 | ESP_LOGI(TAG, "There is no newer firmware available."); 104 | } 105 | 106 | vTaskDelay((8 * 60 * 60000) / portTICK_PERIOD_MS); // 8h 107 | } 108 | 109 | vTaskDelete(NULL); 110 | } -------------------------------------------------------------------------------- /test/README: -------------------------------------------------------------------------------- 1 | 2 | This directory is intended for PIO Unit Testing and project tests. 3 | 4 | Unit Testing is a software testing method by which individual units of 5 | source code, sets of one or more MCU program modules together with associated 6 | control data, usage procedures, and operating procedures, are tested to 7 | determine whether they are fit for use. Unit testing finds problems early 8 | in the development cycle. 9 | 10 | More information about PIO Unit Testing: 11 | - https://docs.platformio.org/page/plus/unit-testing.html 12 | -------------------------------------------------------------------------------- /version.txt: -------------------------------------------------------------------------------- 1 | 1.3.0 -------------------------------------------------------------------------------- /webui/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | "env" 4 | ] 5 | } -------------------------------------------------------------------------------- /webui/.compressrc: -------------------------------------------------------------------------------- 1 | { 2 | "test": ".", 3 | "threshold": undefined, 4 | concurrency: 2, 5 | "gzip": { 6 | "enabled": true, 7 | "numiterations": 15, 8 | "blocksplitting": true, 9 | "blocksplittinglast": false, 10 | "blocksplittingmax": 15, 11 | "zlib": true, 12 | "zlibLevel": 9, 13 | "zlibMemLevel": 9 14 | }, 15 | "brotli": { 16 | "enabled": false 17 | }, 18 | compressOutput: false 19 | } -------------------------------------------------------------------------------- /webui/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexreinert/HB-RF-ETH/5af00aa4438fe4aed33a2963763b5fa6c1f679f8/webui/favicon.ico -------------------------------------------------------------------------------- /webui/index.htm: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | HB-RF-ETH 6 | 7 | 8 | 9 | 10 | 11 | 12 |
13 | 14 | 15 | -------------------------------------------------------------------------------- /webui/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "webui", 3 | "version": "1.3.0", 4 | "description": "WebUI of HB-RF-ETH firmware", 5 | "main": "main.js", 6 | "scripts": { 7 | "dev": "parcel index.htm", 8 | "build": "parcel build index.htm --no-source-maps --no-content-hash" 9 | }, 10 | "keywords": [], 11 | "author": "Alexander Reinert", 12 | "license": "CC-BY-NC-SA-4.0", 13 | "dependencies": { 14 | "axios": "^0.21.1", 15 | "bootstrap": "^4.6.0", 16 | "bootstrap-vue": "^2.21.2", 17 | "vue": "^2.6.14", 18 | "vue-hot-reload-api": "^2.3.4", 19 | "vue-i18n": "^8.24.5", 20 | "vue-router": "^3.5.2", 21 | "vuelidate": "^0.7.6", 22 | "vuex": "^3.6.2" 23 | }, 24 | "devDependencies": { 25 | "@vue/component-compiler-utils": "^3.2.2", 26 | "babel-core": "^6.26.3", 27 | "babel-preset-env": "^1.7.0", 28 | "parcel-bundler": "^1.3.1", 29 | "parcel-plugin-compress": "^2.0.2", 30 | "vue-template-compiler": "^2.6.14" 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /webui/src/app.vue: -------------------------------------------------------------------------------- 1 | 7 | 8 | -------------------------------------------------------------------------------- /webui/src/firmwareupdate.vue: -------------------------------------------------------------------------------- 1 | 70 | 71 | 165 | 166 | -------------------------------------------------------------------------------- /webui/src/header.vue: -------------------------------------------------------------------------------- 1 | 20 | 21 | -------------------------------------------------------------------------------- /webui/src/home.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 24 | 25 | -------------------------------------------------------------------------------- /webui/src/login.vue: -------------------------------------------------------------------------------- 1 | 35 | 36 | 118 | 119 | -------------------------------------------------------------------------------- /webui/src/main.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | 3 | import Vuex from "vuex"; 4 | Vue.use(Vuex); 5 | 6 | import VueRouter from 'vue-router' 7 | Vue.use(VueRouter) 8 | 9 | import Axios from 'axios' 10 | 11 | import 'bootstrap/dist/css/bootstrap.css' 12 | import 'bootstrap-vue/dist/bootstrap-vue.css' 13 | 14 | import App from './app.vue' 15 | import Home from './home.vue' 16 | import Settings from "./settings.vue"; 17 | import FirmwareUpdate from "./firmwareupdate.vue"; 18 | import Login from './login.vue' 19 | import About from './about.vue' 20 | 21 | const moduleLogin = { 22 | namespaced: true, 23 | state: () => ({ 24 | isLoggedIn: localStorage.getItem("hb-rf-eth-pw") != null, 25 | token: localStorage.getItem("hb-rf-eth-pw") 26 | }), 27 | mutations: { 28 | login(state, token) { 29 | localStorage.setItem("hb-rf-eth-pw", token); 30 | state.token = token; 31 | state.isLoggedIn = true; 32 | }, 33 | logout(state) { 34 | state.isLoggedIn = false; 35 | localStorage.removeItem("hb-rf-eth-pw"); 36 | state.token = ""; 37 | }, 38 | }, 39 | actions: { 40 | tryLogin(context, password) { 41 | return new Promise((resolve, reject) => { 42 | Axios 43 | .post("/login.json", { password: password }) 44 | .then( 45 | response => { 46 | if (response.data.isAuthenticated) { 47 | context.commit("login", response.data.token); 48 | 49 | resolve(); 50 | } else { 51 | reject(); 52 | } 53 | }, 54 | () => { 55 | reject(); 56 | }) 57 | }); 58 | }, 59 | logout(context) { 60 | context.commit("logout"); 61 | } 62 | } 63 | }; 64 | 65 | const moduleSysInfo = { 66 | namespaced: true, 67 | state: () => ({ 68 | serial: "", 69 | currentVersion: "", 70 | latestVersion: "", 71 | rawUartRemoteAddress: "", 72 | memoryUsage: 0.0, 73 | cpuUsage: 0.0, 74 | radioModuleType: "", 75 | radioModuleSerial: "", 76 | radioModuleBidCosRadioMAC: "", 77 | radioModuleHmIPRadioMAC: "", 78 | radioModuleSGTIN: "" 79 | }), 80 | mutations: { 81 | sysInfo(state, newState) { 82 | state.serial = newState.serial; 83 | state.currentVersion = newState.currentVersion; 84 | state.latestVersion = newState.latestVersion; 85 | state.rawUartRemoteAddress = newState.rawUartRemoteAddress; 86 | state.memoryUsage = newState.memoryUsage; 87 | state.cpuUsage = newState.cpuUsage; 88 | state.radioModuleType = newState.radioModuleType; 89 | state.radioModuleSerial = newState.radioModuleSerial; 90 | state.radioModuleBidCosRadioMAC = newState.radioModuleBidCosRadioMAC; 91 | state.radioModuleHmIPRadioMAC = newState.radioModuleHmIPRadioMAC; 92 | state.radioModuleSGTIN = newState.radioModuleSGTIN; 93 | }, 94 | }, 95 | actions: { 96 | update(context) { 97 | return new Promise((resolve, reject) => { 98 | Axios 99 | .get("/sysinfo.json") 100 | .then( 101 | response => { 102 | context.commit("sysInfo", response.data.sysInfo); 103 | resolve(); 104 | } 105 | , 106 | () => { 107 | reject(); 108 | }); 109 | }); 110 | } 111 | } 112 | }; 113 | 114 | const moduleSettings = { 115 | namespaced: true, 116 | state: () => ({ 117 | hostname: "", 118 | useDHCP: true, 119 | localIP: "", 120 | netmask: "", 121 | gateway: "", 122 | dns1: "", 123 | dns2: "", 124 | timesource: 0, 125 | dcfOffset: 0, 126 | gpsBaudrate: 9600, 127 | ntpServer: "", 128 | ledBrightness: 100, 129 | }), 130 | mutations: { 131 | settings(state, value) { 132 | state.hostname = value.hostname; 133 | state.useDHCP = value.useDHCP; 134 | state.localIP = value.localIP; 135 | state.netmask = value.netmask; 136 | state.gateway = value.gateway; 137 | state.dns1 = value.dns1; 138 | state.dns2 = value.dns2; 139 | state.timesource = value.timesource; 140 | state.dcfOffset = value.dcfOffset; 141 | state.gpsBaudrate = value.gpsBaudrate; 142 | state.ntpServer = value.ntpServer; 143 | state.ledBrightness = value.ledBrightness; 144 | }, 145 | }, 146 | actions: { 147 | load(context) { 148 | return new Promise((resolve, reject) => { 149 | Axios 150 | .get("/settings.json") 151 | .then( 152 | response => { 153 | context.commit("settings", response.data.settings); 154 | resolve(); 155 | }, 156 | () => { reject(); }); 157 | }); 158 | }, 159 | save(context, settings) { 160 | return new Promise((resolve, reject) => { 161 | Axios 162 | .post("/settings.json", settings) 163 | .then( 164 | response => { 165 | context.commit("settings", response.data.settings); 166 | resolve(); 167 | }, 168 | () => { reject(); }); 169 | }); 170 | } 171 | } 172 | }; 173 | 174 | const moduleFirmwareUpdate = { 175 | namespaced: true, 176 | state: () => ({ 177 | progress: 0, 178 | }), 179 | mutations: { 180 | progress(state, value) { 181 | state.progress = value; 182 | }, 183 | }, 184 | actions: { 185 | update(context, file) { 186 | return new Promise((resolve, reject) => { 187 | context.commit("progress", 0); 188 | 189 | var form = new FormData(); 190 | form.append("file", file, file.name); 191 | form.append("upload_file", true); 192 | 193 | Axios 194 | .post("/ota_update", form, { 195 | headers: { 196 | 'Content-Type': 'multipart/form-data' 197 | }, 198 | onUploadProgress: event => { 199 | if (event.lengthComputable) { 200 | context.commit("progress", Math.ceil((event.loaded || event.position) / event.total * 100)); 201 | } 202 | } 203 | }) 204 | .then( 205 | response => { 206 | context.commit("progress", 0); 207 | resolve(); 208 | }, 209 | (err) => { 210 | reject(); 211 | }); 212 | }); 213 | } 214 | } 215 | }; 216 | 217 | const store = new Vuex.Store({ 218 | modules: { 219 | login: moduleLogin, 220 | sysInfo: moduleSysInfo, 221 | settings: moduleSettings, 222 | firmwareUpdate: moduleFirmwareUpdate, 223 | } 224 | }); 225 | 226 | const router = new VueRouter({ 227 | mode: "history", 228 | routes: [ 229 | { path: '/', component: Home }, 230 | { path: '/settings', component: Settings, meta: { requiresAuth: true } }, 231 | { path: '/firmware', component: FirmwareUpdate, meta: { requiresAuth: true } }, 232 | { path: '/about', component: About }, 233 | { path: '/login', component: Login }, 234 | ] 235 | }); 236 | 237 | router.beforeEach((to, from, next) => { 238 | if (to.matched.some(r => r.meta.requiresAuth)) { 239 | if (!store.state.login.isLoggedIn) { 240 | next({ 241 | path: '/login', 242 | query: { redirect: to.fullPath } 243 | }) 244 | return; 245 | } 246 | } 247 | 248 | next() 249 | }); 250 | 251 | Axios.interceptors.request.use( 252 | request => { 253 | if (store.state.login.isLoggedIn) { 254 | request.headers['Authorization'] = 'Token ' + store.state.login.token; 255 | } 256 | return request; 257 | }, 258 | error => { Promise.reject(error) } 259 | ); 260 | 261 | Axios.interceptors.response.use( 262 | response => { return response; }, 263 | error => { 264 | if (error.response.status == 401) { 265 | store.dispatch("login/logout"); 266 | router.go(); 267 | } 268 | 269 | return Promise.reject(error); 270 | } 271 | ); 272 | 273 | new Vue({ 274 | el: '#app', 275 | store, 276 | router, 277 | render: h => h(App) 278 | }); 279 | -------------------------------------------------------------------------------- /webui/src/settings.vue: -------------------------------------------------------------------------------- 1 | 146 | 147 | 416 | 417 | -------------------------------------------------------------------------------- /webui/src/sysinfo.vue: -------------------------------------------------------------------------------- 1 | 50 | 51 | 121 | 122 | --------------------------------------------------------------------------------