├── .gitattributes ├── .github └── workflows │ └── main.yaml ├── .gitignore ├── .tool-versions ├── .vscode └── extensions.json ├── LICENSE ├── README.md ├── configuration.json ├── dashboard.json ├── pics ├── dashboard.png ├── esp8266.jpg ├── irsensor.jpg ├── irsensor2.jpg └── logo.jpg ├── platformio.ini └── src ├── ferraris.cpp ├── ferraris.h ├── main.cpp ├── mqtt_publish.cpp ├── mqtt_publish.h ├── mqtt_subscribe.cpp └── mqtt_subscribe.h /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | -------------------------------------------------------------------------------- /.github/workflows/main.yaml: -------------------------------------------------------------------------------- 1 | on: push 2 | jobs: 3 | build-nodemcuv2: 4 | name: Build binary for nodemcuv2 5 | runs-on: ubuntu-22.04 6 | steps: 7 | - uses: actions/checkout@v3 8 | - name: Setup PlatformIO 9 | uses: n-vr/setup-platformio-action@v1 10 | with: 11 | platformio-version: "6.1.4" 12 | - uses: actions/setup-node@v3 13 | with: 14 | node-version: 14 # npm dependency resolution for esp8266 iot framework breaks with 16 15 | - name: Build PlatformIO project 16 | run: pio run -d ./ -e nodemcuv2 17 | - name: Adjust firmware filename 18 | run: mv .pio/build/nodemcuv2/firmware.bin firmware-nodemcuv2.bin 19 | - name: Release 20 | uses: fnkr/github-action-ghr@v1 21 | if: startsWith(github.ref, 'refs/tags/') 22 | env: 23 | GHR_PATH: firmware-nodemcuv2.bin 24 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 25 | build-d1: 26 | name: Build binary for d1 27 | runs-on: ubuntu-22.04 28 | steps: 29 | - uses: actions/checkout@v3 30 | - name: Setup PlatformIO 31 | uses: n-vr/setup-platformio-action@v1 32 | with: 33 | platformio-version: "6.1.4" 34 | - uses: actions/setup-node@v3 35 | with: 36 | node-version: 14 # npm dependency resolution for esp8266 iot framework breaks with 16 37 | - name: Build PlatformIO project 38 | run: pio run -d ./ -e d1 39 | - name: Adjust firmware filename 40 | run: mv .pio/build/d1/firmware.bin firmware-d1.bin 41 | - name: Release 42 | uses: fnkr/github-action-ghr@v1 43 | if: startsWith(github.ref, 'refs/tags/') 44 | env: 45 | GHR_PATH: firmware-d1.bin 46 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 47 | build-d1_mini: 48 | name: Build binary for d1_mini 49 | runs-on: ubuntu-22.04 50 | steps: 51 | - uses: actions/checkout@v3 52 | - name: Setup PlatformIO 53 | uses: n-vr/setup-platformio-action@v1 54 | with: 55 | platformio-version: "6.1.4" 56 | - uses: actions/setup-node@v3 57 | with: 58 | node-version: 14 # npm dependency resolution for esp8266 iot framework breaks with 16 59 | - name: Build PlatformIO project 60 | run: pio run -d ./ -e d1_mini 61 | - name: Adjust firmware filename 62 | run: mv .pio/build/d1_mini/firmware.bin firmware-d1_mini.bin 63 | - name: Release 64 | uses: fnkr/github-action-ghr@v1 65 | if: startsWith(github.ref, 'refs/tags/') 66 | env: 67 | GHR_PATH: firmware-d1_mini.bin 68 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 69 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .pio 2 | .vscode/.browse.c_cpp.db* 3 | .vscode/c_cpp_properties.json 4 | .vscode/launch.json 5 | .vscode/ipch 6 | -------------------------------------------------------------------------------- /.tool-versions: -------------------------------------------------------------------------------- 1 | nodejs 14.20.1 2 | -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Lars Weimar 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![build](https://github.com/Eisbaeeer/Ferraris_MQTT_Energy_Counter_Meter_TCRT5000/actions/workflows/main.yaml/badge.svg) [![No Maintenance Intended](http://unmaintained.tech/badge.svg)](http://unmaintained.tech/) 2 | 3 | ![Logo](pics/logo.jpg) 4 | # Ferraris TCRT5000 ESP8266 Energy Meter Counter MQTT 5 | 6 | ## Description 7 | This project allows you to count the rounds per KWh of meter disk with the red mark of ferraris energy counter . 8 | The code is tested on ESP8266 platform. 9 | You are able to connect maximum four energy meter. 10 | You can use the cheap TCRT5000 as infrared sensors. 11 | Printable enclosures are available on thingiverse. 12 | 13 | ## DEPRECATED - This repo will not be updated! See forks for updates! 14 | 15 | ## Features 16 | - the code creates a filesystem on flash storage of the esp8266 17 | - all settings are stored on the filesystem in a JSON format 18 | - Wifi-Manager for easy connection to available AccessPoints 19 | - MQTT client to transmit the values to a central server like home-automation-systems 20 | - Webpage to configure all settings or read the values 21 | - OTA Over-The-Air update of firmware 22 | 23 | ![Logo](pics/irsensor.jpg) 24 | ![Logo](pics/irsensor2.jpg) 25 | ![Logo](pics/esp8266.jpg) 26 | 27 | ## Steps to get running 28 | 1. Install VSCode + PlatformIO 29 | 2. Follow the installation guide to compile the framework 30 | https://github.com/maakbaas/esp8266-iot-framework/blob/master/docs/installation-guide.md 31 | 32 | ## Calibrate sensor 33 | 1. Connect the sensor Pin A0 to Pin A0 on the NodeMCU board 34 | 2. Open the dashboard and calibrate the sensor by moving up/down - left/right until the best hysteresis 35 | 3. Turn the poti left until the light is off, now turn right and stop when the light turn on. 36 | 37 | ![Logo](pics/dashboard.png) 38 | 39 | 40 | ## Using upload ready binary 41 | You are able to upload the compiled binary without Arduino IDE installed. You will find the compiled bin file in binary folder. 42 | The binary supports ESP8266 with 4MB flash e.g. NodeMCU version 2+3. 43 | 44 | ## Weblinks to get running 45 | - iobroker forum: https://forum.iobroker.net/topic/35404/ferraris-z%C3%A4hler-mit-tcrt5000-und-esp8266 46 | - thingiverse: https://www.thingiverse.com/thing:4560681 47 | 48 | ## ToDo 49 | - adding floats 50 | - adding gas sensor 51 | - adding impuls settings for S0 52 | 53 | ## Changelog 54 | 55 | ### Ver. 0.94 56 | (Eisbaeeer 20221225) 57 | - Last Version - Deprecated. See Forks for updates!!! 58 | - Changed structure for auto-compiling different boards 59 | - Fixed: missing react-website compile by auto-compile 60 | - removed obsolete binary folder 61 | 62 | ### Ver. 0.92 63 | (Eisbaeeer 20211014) 64 | - Bugfix: Interrupt Routinen bei MQTT Übertragung unterbrochen 65 | - Bugfix: Interrupt Routinen beim Speichern mit littleFS unterbrochen 66 | - Dashboard mit zusätzlichen Infos erweitert 67 | 68 | ### Ver. 0.91 69 | (Eisbaeeer 20211011) 70 | - Graphen zum Dashboard hinzugefügt 71 | - ISR mit no-delay Entprellung angepasst 72 | - Nachkommastellen durch fehlerhafte addition von floats entfernt 73 | - Entprellung ist jetzt konfigurierbar 74 | 75 | ### Version 0.9 76 | (Eisbaeeer 20210917) 77 | - Graphen zum Dashboard hinzugefügt 78 | - Analogwert vom Sensor wird jetzt auf dem Dashboard angezeigt 79 | 80 | ### Version 0.8 81 | (Eisbaeeer 20210914) 82 | - Bugfix Zählerroutine - jetzt per Interrupt auf alle Eingänge 83 | 84 | ### Version 0.7 85 | (Eisbaeeer 20210822) 86 | - Bugfix Zählerstand 87 | - Zählerstand auf Nachkommastellen erweitert 88 | 89 | ### Version 0.6 90 | (Eisbaeeer 20210819) 91 | - Moved from ArduinoIDE to PlatformIO 92 | - Merged project to framework 93 | 94 | ### Version 0.5 95 | (Eisbaeeer 20210813) 96 | - Bugfix boolean 97 | - Added 3 digits after dot 98 | 99 | ### Version 0.4 100 | (Eisbaeeer 20200905) 101 | - Bugfix Zähler 3 und 4 (Zählerstand) 102 | - Neu: MQTT Server Port konfigurierbar 103 | - Neu: MQTT publish Zeit einstellbar (1-9999 Sekunden) 104 | - Blinken der internen LED aus kompatibilitätsgründen von anderen Boards entfernt (manche Boards nutzen D4 für die interne LED) 105 | (ACHTUNG: mit dieser Version gehen die Zählerdaten verloren! bitte über Browser neu eintragen!) 106 | - Neu: Port D4 auf D5 umgezogen! (D4 ist bei manchen Boards die interne LED 107 | - Neu: Alle Zählerdaten werden im EEPROM abgespeichert. 108 | 109 | ### Version 0.3 110 | (Eisbaeeer) 111 | - adding upload of firmware bin files via webpage (http://.../update) 112 | 113 | ### Version 0.2 114 | (Eisbaeeer 20200911) 115 | - fixing debounce to 20ms 116 | 117 | ### Version 0.1 118 | (Eisbaeeer 20200803) 119 | - Initial Version 120 | - Filesystem to store and read values from 121 | - Wifi-Manager to connect to Wifi easy 122 | - Stored values are in JSON format 123 | - MQTT client to publish values 124 | - HTTP page for configuration 125 | - Over the air update of firmware 126 | - four meter counter (IR-Input pins) 127 | 128 | ## License 129 | The MIT License (MIT) 130 | Copyright (c) 2020 Eisbaeeer 131 | -------------------------------------------------------------------------------- /configuration.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "name": "projectName", 4 | "label": "Project Name", 5 | "type": "char", 6 | "disabled": true, 7 | "hidden": true, 8 | "length": 32, 9 | "value": "Ferraris MQTT" 10 | }, 11 | { 12 | "name": "language", 13 | "type": "char", 14 | "length": 3, 15 | "value": "en", 16 | "hidden": true 17 | }, 18 | { 19 | "name": "wifi_hostname", 20 | "label": "WiFi Hostname", 21 | "type": "char", 22 | "length": 20, 23 | "value": "Ferraris" 24 | }, 25 | { 26 | "type": "header", 27 | "text": "MQTT" 28 | }, 29 | { 30 | "name": "messure_place", 31 | "label": "Messplatz", 32 | "type": "char", 33 | "length": 20, 34 | "value": "Messplatz" 35 | }, 36 | { 37 | "name": "mqtt_server", 38 | "label": "MQTT Server IP/FQDN", 39 | "type": "char", 40 | "length": 20, 41 | "value": "mqttserver.local" 42 | }, 43 | { 44 | "name": "mqtt_port", 45 | "label": "MQTT Port", 46 | "type": "int16_t", 47 | "value": 1883 48 | }, 49 | { 50 | "name": "mqtt_interval", 51 | "label": "MQTT Publish Interval (seconds)", 52 | "type": "int8_t", 53 | "value": "30" 54 | }, 55 | { 56 | "name": "mqtt_user", 57 | "label": "MQTT User Name", 58 | "type": "char", 59 | "length": 50, 60 | "value": "username" 61 | }, 62 | { 63 | "name": "mqtt_password", 64 | "label": "MQTT Password", 65 | "type": "char", 66 | "length": 50, 67 | "value": "password", 68 | "control": "password" 69 | }, 70 | { 71 | "name": "home_assistant_auto_discovery", 72 | "label": "Home-Assistant Auto Discovery", 73 | "type": "bool", 74 | "value": false 75 | }, 76 | { 77 | "type": "header", 78 | "text": "Zähler 1" 79 | }, 80 | { 81 | "name": "meter_counter_reading_1", 82 | "label": "Zählerstand [kWh]", 83 | "type": "float", 84 | "digits": 1, 85 | "value": "0" 86 | }, 87 | { 88 | "name": "meter_loops_count_1", 89 | "label": "Umdrehungen / kWh", 90 | "type": "uint16_t", 91 | "value": "75" 92 | }, 93 | { 94 | "name": "meter_debounce_1", 95 | "label": "Entprellzeit [ms]", 96 | "type": "uint16_t", 97 | "value": "80" 98 | }, 99 | { 100 | "name": "meter_twoway_1", 101 | "label": "Dual direction count mode", 102 | "type": "bool", 103 | "value": false 104 | }, 105 | { 106 | "type": "header", 107 | "text": "Zähler 2" 108 | }, 109 | { 110 | "name": "meter_counter_reading_2", 111 | "label": "Zählerstand [kWh]", 112 | "type": "float", 113 | "digits": 1, 114 | "value": "0" 115 | }, 116 | { 117 | "name": "meter_loops_count_2", 118 | "label": "Umdrehungen / kWh", 119 | "type": "uint16_t", 120 | "value": "75" 121 | }, 122 | { 123 | "name": "meter_debounce_2", 124 | "label": "Entprellzeit [ms]", 125 | "type": "uint16_t", 126 | "value": "80" 127 | }, 128 | { 129 | "name": "meter_twoway_2", 130 | "label": "Dual direction count mode", 131 | "type": "bool", 132 | "value": false 133 | }, 134 | { 135 | "type": "header", 136 | "text": "Zähler 3" 137 | }, 138 | { 139 | "name": "meter_counter_reading_3", 140 | "label": "Zählerstand [kWh]", 141 | "type": "float", 142 | "digits": 1, 143 | "value": "0" 144 | }, 145 | { 146 | "name": "meter_loops_count_3", 147 | "label": "Umdrehungen / kWh", 148 | "type": "uint16_t", 149 | "value": "75" 150 | }, 151 | { 152 | "name": "meter_debounce_3", 153 | "label": "Entprellzeit [ms]", 154 | "type": "uint16_t", 155 | "value": "80" 156 | }, 157 | { 158 | "name": "meter_twoway_3", 159 | "label": "Dual direction count mode", 160 | "type": "bool", 161 | "value": false 162 | }, 163 | { 164 | "type": "header", 165 | "text": "Zähler 4" 166 | }, 167 | { 168 | "name": "meter_counter_reading_4", 169 | "label": "Zählerstand [kWh]", 170 | "type": "float", 171 | "digits": 1, 172 | "value": "0" 173 | }, 174 | { 175 | "name": "meter_loops_count_4", 176 | "label": "Umdrehungen / kWh", 177 | "type": "uint16_t", 178 | "value": "75" 179 | }, 180 | { 181 | "name": "meter_debounce_4", 182 | "label": "Entprellzeit [ms]", 183 | "type": "uint16_t", 184 | "value": "80" 185 | }, 186 | { 187 | "name": "meter_twoway_4", 188 | "label": "Dual direction count mode", 189 | "type": "bool", 190 | "value": false 191 | } 192 | ] 193 | -------------------------------------------------------------------------------- /dashboard.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "name": "Version", 4 | "type": "char", 5 | "length": 6, 6 | "direction": "display" 7 | }, 8 | { 9 | "name": "Wifi_RSSI", 10 | "type": "char", 11 | "length": 6, 12 | "direction": "display" 13 | }, 14 | { 15 | "name": "MQTT_Connected", 16 | "type": "bool", 17 | "direction": "display" 18 | }, 19 | { 20 | "name": "Impuls_Z1", 21 | "type": "bool", 22 | "direction": "display", 23 | "display": "graph", 24 | "xaxis": 50 25 | }, 26 | { 27 | "name": "Impuls_Z2", 28 | "type": "bool", 29 | "direction": "display", 30 | "display": "graph", 31 | "xaxis": 50 32 | }, 33 | { 34 | "name": "Impuls_Z3", 35 | "type": "bool", 36 | "direction": "display", 37 | "display": "graph", 38 | "xaxis": 50 39 | }, 40 | { 41 | "name": "Impuls_Z4", 42 | "type": "bool", 43 | "direction": "display", 44 | "display": "graph", 45 | "xaxis": 50 46 | }, 47 | { 48 | "type": "header", 49 | "text": "Zähler 1" 50 | }, 51 | { 52 | "name": "revolutions_1", 53 | "label": "Umdrehungen [U]", 54 | "type": "uint32_t", 55 | "length": 5, 56 | "direction": "display" 57 | }, 58 | { 59 | "name": "W_1", 60 | "label": "Leistung [W]", 61 | "type": "int16_t", 62 | "digits": 3, 63 | "direction": "display" 64 | }, 65 | { 66 | "name": "kWh_1", 67 | "label": "Zählerstand [kWh]", 68 | "type": "float", 69 | "digits": 1, 70 | "direction": "display" 71 | }, 72 | { 73 | "type": "header", 74 | "text": "Zähler 2" 75 | }, 76 | { 77 | "name": "revolutions_2", 78 | "label": "Umdrehungen [U]", 79 | "type": "uint32_t", 80 | "length": 5, 81 | "direction": "display" 82 | }, 83 | { 84 | "name": "W_2", 85 | "label": "Leistung [W]", 86 | "type": "int16_t", 87 | "digits": 3, 88 | "direction": "display" 89 | }, 90 | { 91 | "name": "kWh_2", 92 | "label": "Zählerstand [kWh]", 93 | "type": "float", 94 | "digits": 1, 95 | "direction": "display" 96 | }, 97 | { 98 | "type": "header", 99 | "text": "Zähler 3" 100 | }, 101 | { 102 | "name": "revolutions_3", 103 | "label": "Umdrehungen [U]", 104 | "type": "uint32_t", 105 | "length": 5, 106 | "direction": "display" 107 | }, 108 | { 109 | "name": "W_3", 110 | "label": "Leistung [W]", 111 | "type": "int16_t", 112 | "digits": 3, 113 | "direction": "display" 114 | }, 115 | { 116 | "name": "kWh_3", 117 | "label": "Zählerstand [kWh]", 118 | "type": "float", 119 | "digits": 1, 120 | "direction": "display" 121 | }, 122 | { 123 | "type": "header", 124 | "text": "Zähler 4" 125 | }, 126 | { 127 | "name": "revolutions_4", 128 | "label": "Umdrehungen [U]", 129 | "type": "uint32_t", 130 | "length": 5, 131 | "direction": "display" 132 | }, 133 | { 134 | "name": "W_4", 135 | "label": "Leistung [W]", 136 | "type": "int16_t", 137 | "digits": 3, 138 | "direction": "display" 139 | }, 140 | { 141 | "name": "kWh_4", 142 | "label": "Zählerstand [kWh]", 143 | "type": "float", 144 | "digits": 1, 145 | "direction": "display" 146 | }, 147 | { 148 | "name": "Sensor", 149 | "type": "int16_t", 150 | "direction": "display", 151 | "display": "graph", 152 | "xaxis": 50 153 | }, 154 | { 155 | "name": "WLAN_RSSI", 156 | "type": "int8_t", 157 | "direction": "display", 158 | "display": "graph", 159 | "xaxis": 50 160 | } 161 | ] -------------------------------------------------------------------------------- /pics/dashboard.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Eisbaeeer/Ferraris_MQTT_Energy_Counter_Meter_TCRT5000/0bc48ad2752ec67440caf74324296622e6fc2d74/pics/dashboard.png -------------------------------------------------------------------------------- /pics/esp8266.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Eisbaeeer/Ferraris_MQTT_Energy_Counter_Meter_TCRT5000/0bc48ad2752ec67440caf74324296622e6fc2d74/pics/esp8266.jpg -------------------------------------------------------------------------------- /pics/irsensor.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Eisbaeeer/Ferraris_MQTT_Energy_Counter_Meter_TCRT5000/0bc48ad2752ec67440caf74324296622e6fc2d74/pics/irsensor.jpg -------------------------------------------------------------------------------- /pics/irsensor2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Eisbaeeer/Ferraris_MQTT_Energy_Counter_Meter_TCRT5000/0bc48ad2752ec67440caf74324296622e6fc2d74/pics/irsensor2.jpg -------------------------------------------------------------------------------- /pics/logo.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Eisbaeeer/Ferraris_MQTT_Energy_Counter_Meter_TCRT5000/0bc48ad2752ec67440caf74324296622e6fc2d74/pics/logo.jpg -------------------------------------------------------------------------------- /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:nodemcuv2] 12 | platform = espressif8266 13 | board = nodemcuv2 14 | framework = arduino 15 | board_build.f_cpu = 160000000L 16 | upload_speed = 921600 17 | monitor_speed = 115200 18 | build_type = debug 19 | monitor_filters = esp8266_exception_decoder 20 | lib_deps = 21 | ArduinoJson 22 | https://github.com/maakbaas/esp8266-iot-framework 23 | me-no-dev/ESP Async WebServer @ ^1.2.3 24 | knolleary/PubSubClient @ ^2.8 25 | build_flags = -DCONFIG_PATH=configuration.json -DDASHBOARD_PATH=dashboard.json -DREBUILD_HTML -DREBUILD_DASHBOARD -DREBUILD_CONFIG 26 | extra_scripts = scripts/preBuild.py 27 | 28 | [env:d1_mini] 29 | platform = espressif8266 30 | board = d1_mini 31 | framework = arduino 32 | monitor_speed = 115200 33 | monitor_filters = esp8266_exception_decoder 34 | lib_deps = 35 | ArduinoJson 36 | https://github.com/maakbaas/esp8266-iot-framework 37 | me-no-dev/ESP Async WebServer @ ^1.2.3 38 | knolleary/PubSubClient @ ^2.8 39 | build_flags = -DCONFIG_PATH=configuration.json -DDASHBOARD_PATH=dashboard.json -DREBUILD_HTML -DREBUILD_DASHBOARD -DREBUILD_CONFIG 40 | extra_scripts = scripts/preBuild.py 41 | 42 | [env:d1] 43 | platform = espressif8266 44 | board = d1 45 | framework = arduino 46 | monitor_speed = 115200 47 | monitor_filters = esp8266_exception_decoder 48 | lib_deps = 49 | ArduinoJson 50 | https://github.com/maakbaas/esp8266-iot-framework 51 | me-no-dev/ESP Async WebServer @ ^1.2.3 52 | knolleary/PubSubClient @ ^2.8 53 | build_flags = -DCONFIG_PATH=configuration.json -DDASHBOARD_PATH=dashboard.json -DREBUILD_HTML -DREBUILD_DASHBOARD -DREBUILD_CONFIG 54 | extra_scripts = scripts/preBuild.py 55 | -------------------------------------------------------------------------------- /src/ferraris.cpp: -------------------------------------------------------------------------------- 1 | #include "ferraris.h" 2 | #include 3 | 4 | 5 | // ugly stuff to get interrupt callable 6 | static void IRAM_ATTR staticInterruptHandler0() { Ferraris::getInstance(0).IRQhandler(); } 7 | static void IRAM_ATTR staticInterruptHandler1() { Ferraris::getInstance(1).IRQhandler(); } 8 | static void IRAM_ATTR staticInterruptHandler2() { Ferraris::getInstance(2).IRQhandler(); } 9 | static void IRAM_ATTR staticInterruptHandler3() { Ferraris::getInstance(3).IRQhandler(); } 10 | static void IRAM_ATTR staticInterruptHandler4() { Ferraris::getInstance(4).IRQhandler(); } 11 | static void IRAM_ATTR staticInterruptHandler5() { Ferraris::getInstance(5).IRQhandler(); } 12 | static void IRAM_ATTR staticInterruptHandler6() { Ferraris::getInstance(6).IRQhandler(); } 13 | 14 | void (*staticInterruptHandlers[FERRARIS_NUM])() = 15 | { 16 | &staticInterruptHandler0, 17 | #if FERRARIS_NUM > 1 18 | &staticInterruptHandler1, 19 | #endif 20 | #if FERRARIS_NUM > 2 21 | &staticInterruptHandler2, 22 | #endif 23 | #if FERRARIS_NUM > 3 24 | &staticInterruptHandler3, 25 | #endif 26 | #if FERRARIS_NUM > 4 27 | &staticInterruptHandler4, 28 | #endif 29 | #if FERRARIS_NUM > 5 30 | &staticInterruptHandler5, 31 | #endif 32 | #if FERRARIS_NUM > 6 33 | &staticInterruptHandler6 34 | #endif 35 | }; 36 | 37 | 38 | // initialize private instance counter 39 | uint8_t Ferraris::FINSTANCE = 0; 40 | 41 | // private constructor 42 | Ferraris::Ferraris() 43 | : m_state(startup) 44 | , m_config_rev_kWh(75) 45 | , m_config_debounce(80) 46 | , m_config_twoway(false) 47 | , m_timestamp(millis()) 48 | , m_timestampLast1(0) 49 | , m_timestampLast2(0) 50 | , m_revolutions(0) 51 | , m_changed(false) 52 | , m_direction(+1) 53 | , m_average_timestamp(0) 54 | , m_average_revolutions(0) 55 | { 56 | uint8_t F = Ferraris::FINSTANCE++; 57 | m_PIN = Ferraris::PINS[F]; 58 | m_DPIN = Ferraris::DPINS[F]; 59 | pinMode(m_PIN, INPUT_PULLUP); 60 | switch (F) { 61 | case 0: m_handler = staticInterruptHandler0; break; 62 | case 1: m_handler = staticInterruptHandler1; break; 63 | case 2: m_handler = staticInterruptHandler2; break; 64 | case 3: m_handler = staticInterruptHandler3; break; 65 | case 4: m_handler = staticInterruptHandler4; break; 66 | case 5: m_handler = staticInterruptHandler5; break; 67 | case 6: m_handler = staticInterruptHandler6; break; 68 | default: abort(); 69 | } 70 | } 71 | 72 | Ferraris& Ferraris::getInstance(unsigned char F) 73 | { 74 | static Ferraris singleton[FERRARIS_NUM]; 75 | assert(F < FERRARIS_NUM); 76 | return singleton[F]; 77 | } 78 | 79 | 80 | // ---------------------------------------------------------------------------- 81 | // main interface 82 | // ---------------------------------------------------------------------------- 83 | 84 | void Ferraris::begin() 85 | { 86 | u_int8_t value = digitalRead(m_PIN); 87 | 88 | if (value == FERRARIS_SILVER) { 89 | m_state = Ferraris::states::silver_debounce; 90 | } 91 | 92 | if (value == FERRARIS_RED) { 93 | m_state = Ferraris::states::red_debounce; 94 | m_timestampLast1 = m_timestamp; 95 | m_timestampLast2 = m_timestamp; 96 | } 97 | m_changed = true; 98 | } 99 | 100 | // define IRQ mode to get to PIN state 101 | #if (FERRARIS_RED == HIGH) 102 | #define FERRARIS_IRQMODE_RED RISING 103 | #define FERRARIS_IRQMODE_SILVER FALLING 104 | #else 105 | #define FERRARIS_IRQMODE_RED FALLING 106 | #define FERRARIS_IRQMODE_SILVER RISING 107 | #endif 108 | 109 | bool Ferraris::loop() 110 | { 111 | // wait for debounce time 112 | int timestamp = millis(); 113 | if (m_state == Ferraris::states::silver_debounce) 114 | if ((timestamp - m_timestamp) >= 4*m_config_debounce) { 115 | m_state = Ferraris::states::silver; 116 | detachInterrupt(digitalPinToInterrupt(m_PIN)); 117 | attachInterrupt(digitalPinToInterrupt(m_PIN), m_handler, FERRARIS_IRQMODE_RED); 118 | } 119 | 120 | if (m_state == Ferraris::states::red_debounce) 121 | if ((timestamp - m_timestamp) >= m_config_debounce) { 122 | if (digitalRead(m_PIN) == FERRARIS_RED) { 123 | m_state = Ferraris::states::red; 124 | detachInterrupt(digitalPinToInterrupt(m_PIN)); 125 | attachInterrupt(digitalPinToInterrupt(m_PIN), m_handler, FERRARIS_IRQMODE_SILVER); 126 | } else { 127 | // we missed RED -> SILVER ! 128 | m_state = Ferraris::states::silver_debounce; 129 | m_timestamp = timestamp; 130 | Serial.println("Missed RED->SILVER !"); 131 | } 132 | } 133 | 134 | // return "something has changed" state 135 | bool retval = m_changed; 136 | m_changed = false; 137 | return retval; 138 | } 139 | 140 | void Ferraris::IRQhandler() 141 | { 142 | // disable interrupt during debounce time 143 | //detachInterrupt(digitalPinToInterrupt(m_PIN)); 144 | m_timestamp = millis(); 145 | 146 | // silver -> red 147 | if (m_state == Ferraris::states::silver) { 148 | m_state = Ferraris::states::red_debounce; 149 | if ((!m_config_twoway) || 150 | (m_config_twoway && (digitalRead(m_DPIN) == FERRARIS_SILVER))) { 151 | m_revolutions++; 152 | m_direction = std::min(1, m_direction+1); 153 | m_timestampLast2 = m_timestampLast1; 154 | m_timestampLast1 = m_timestamp; 155 | m_changed = true; 156 | } 157 | } 158 | 159 | // red -> silver 160 | if (m_state == Ferraris::states::red) { 161 | m_state = Ferraris::states::silver_debounce; 162 | if (m_config_twoway && (digitalRead(m_DPIN) == FERRARIS_SILVER)) { 163 | m_revolutions--; 164 | m_direction = std::max(-1, m_direction-1); 165 | m_timestampLast2 = m_timestampLast1; 166 | m_timestampLast1 = m_timestamp; 167 | m_changed = true; 168 | } 169 | } 170 | } 171 | 172 | 173 | // ---------------------------------------------------------------------------- 174 | // calculation functions 175 | // ---------------------------------------------------------------------------- 176 | 177 | bool Ferraris::get_state() const 178 | { 179 | switch (m_state) { 180 | case Ferraris::states::silver: 181 | case Ferraris::states::silver_debounce: 182 | return FERRARIS_SILVER; 183 | break; 184 | default: 185 | return FERRARIS_RED; 186 | } 187 | } 188 | 189 | // get current consumption based on duration of last revolution 190 | int Ferraris::get_W() const 191 | { 192 | unsigned long elapsedtime = m_timestampLast1 - m_timestampLast2; // last full cycle 193 | if (elapsedtime == 0) return 0; 194 | unsigned long runningtime = millis() - m_timestampLast1; // current open cycle 195 | // [60 min * 60 sec * 1000 ms] * [1 kW -> 1000 W] / [time for 1kWh] 196 | return m_direction * 3600000000 / (std::max(elapsedtime, runningtime) * m_config_rev_kWh); 197 | } 198 | 199 | // get current consumption average since last call 200 | int Ferraris::get_W_average() 201 | { 202 | // check for very low consumption (less than 2 revolutions per call) 203 | if (m_average_timestamp == m_timestampLast1) { 204 | return get_W(); 205 | } 206 | if (m_average_timestamp == m_timestampLast2) { 207 | m_average_timestamp = m_timestampLast1; 208 | m_average_revolutions = m_revolutions; 209 | return get_W(); 210 | } 211 | 212 | // changes during last average cycle 213 | unsigned long elapsedT = m_timestampLast1 - m_average_timestamp; 214 | signed long elapsedR = m_revolutions - m_average_revolutions; 215 | if ((elapsedT == 0) || (elapsedR == 0)) return 0; 216 | 217 | // update average state with last capture 218 | m_average_timestamp = m_timestampLast1; 219 | m_average_revolutions = m_revolutions; 220 | 221 | // [60 min * 60 sec * 1000 ms] * [1 kW -> 1000 W] / [time for 1kWh] 222 | return 3600000000 * elapsedR / (elapsedT * m_config_rev_kWh); 223 | } 224 | 225 | float Ferraris::get_kW() const 226 | { 227 | unsigned long elapsedtime = m_timestampLast1 - m_timestampLast2; // last full cycle 228 | if (elapsedtime == 0) return 0.0f; 229 | unsigned long runningtime = millis() - m_timestampLast1; // current open cycle 230 | // [60 min * 60 sec * 1000 ms] * [1 kW] / [time for 1kWh] 231 | return m_direction * 3600000.0f / (std::max(elapsedtime, runningtime) * m_config_rev_kWh); 232 | } 233 | 234 | float Ferraris::get_kWh() const 235 | { 236 | return float(m_revolutions) / float(m_config_rev_kWh); 237 | } 238 | 239 | 240 | // ---------------------------------------------------------------------------- 241 | // simple getter and setter 242 | // ---------------------------------------------------------------------------- 243 | 244 | unsigned long Ferraris::get_revolutions() const 245 | { 246 | return m_revolutions; 247 | } 248 | 249 | void Ferraris::set_revolutions(unsigned long value) 250 | { 251 | m_revolutions = value; 252 | m_changed = true; 253 | } 254 | 255 | unsigned int Ferraris::get_U_kWh() const 256 | { 257 | return m_config_rev_kWh; 258 | } 259 | 260 | void Ferraris::set_U_kWh(unsigned int value) 261 | { 262 | m_config_rev_kWh = value; 263 | m_changed = true; 264 | } 265 | 266 | unsigned int Ferraris::get_debounce() const 267 | { 268 | return m_config_debounce; 269 | } 270 | 271 | void Ferraris::set_debounce(unsigned int value) 272 | { 273 | m_config_debounce = value; 274 | } 275 | 276 | bool Ferraris::get_twoway() const 277 | { 278 | return m_config_twoway; 279 | } 280 | 281 | void Ferraris::set_twoway(bool flag) 282 | { 283 | m_config_twoway = flag; 284 | } 285 | -------------------------------------------------------------------------------- /src/ferraris.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | 6 | // how many Ferraris meters do we want 7 | #define FERRARIS_NUM 4 8 | 9 | // define logic level of pin input 10 | #define FERRARIS_RED HIGH 11 | #define FERRARIS_SILVER LOW 12 | 13 | 14 | class Ferraris { 15 | public: 16 | /* 17 | * Used pins NodeMCU V2+V3 Wemos D1 Wemos D1 mini 18 | * Internal LED GPIO 16 (IO16) (D0) 19 | * IR Pin Messure 1 (D1) GPIO 05 (IO5) (D1) 20 | * IR Pin Messure 2 (D2) GPIO 04 (IO4) (D2) 21 | * IR Pin Messure 3 (D3) GPIO 00 (IO0) (D3) 22 | * ESP8266 LED GPIO 02 23 | * IR Pin Messure 4 (D5) GPIO 14 (IO14) (D5) 24 | * IR Pin Messure 5 (D6) GPIO 12 (IO12) (D6) 25 | * IR Pin Messure 6 (D7) GPIO 13 (IO13) (D7) 26 | * IR Pin Messure 7 (D8) GPIO 15 (D8) 27 | */ 28 | const u_int8_t PINS[7] = {D1, D2, D3, D5, D6, D7, D8}; 29 | const u_int8_t DPINS[7] = {D2, D1, D5, D3, D7, D8, D7}; 30 | 31 | private: // singleton pattern: prevent access to constructors 32 | Ferraris(); 33 | Ferraris(const Ferraris &); 34 | Ferraris &operator=(const Ferraris &); 35 | 36 | public: 37 | static Ferraris& getInstance(unsigned char F); 38 | 39 | void begin(); 40 | bool loop(); 41 | 42 | void IRQhandler(); 43 | 44 | bool get_state() const; // actual PIN state 45 | int get_W() const; // actual consumption in [W] 46 | float get_kW() const; // actual consumption in [kW] 47 | float get_kWh() const; // total reading of meter 48 | int get_W_average(); // average consumption since last call in [W] 49 | 50 | // total revolution count 51 | unsigned long get_revolutions() const; 52 | void set_revolutions(unsigned long value); 53 | 54 | // config: revolutions per kWh 55 | unsigned int get_U_kWh() const; 56 | void set_U_kWh(unsigned int value); 57 | 58 | // config: debounce time [ms] 59 | unsigned int get_debounce() const; 60 | void set_debounce(unsigned int value); 61 | 62 | // config: count mode, single / two way 63 | bool get_twoway() const; 64 | void set_twoway(bool flag); 65 | 66 | enum states {startup, silver_debounce, silver, red_debounce, red}; 67 | 68 | private: 69 | uint8_t m_PIN; 70 | uint8_t m_DPIN; 71 | void (*m_handler)(); 72 | Ferraris::states m_state; 73 | unsigned int m_config_rev_kWh; // revolutions per kWh 74 | unsigned int m_config_debounce; // debounce time [ms] 75 | bool m_config_twoway; // single or dual way counting 76 | 77 | unsigned long m_timestamp; 78 | unsigned long m_timestampLast1; 79 | unsigned long m_timestampLast2; 80 | unsigned long m_revolutions; // total amount of revolutions 81 | bool m_changed; // something has changed -> give info in loop() 82 | short int m_direction; // direction of last count 83 | 84 | unsigned long m_average_timestamp; // last average call: timestampLast2 85 | unsigned long m_average_revolutions; // last average call: amount of revolutions 86 | 87 | static uint8_t FINSTANCE; // used to identify instance 88 | }; 89 | 90 | #if (FERRARIS_NUM < 1) || (FERRARIS_NUM > 7) 91 | #error "Maximum of 7 Farraris meters allowed" 92 | #endif 93 | -------------------------------------------------------------------------------- /src/main.cpp: -------------------------------------------------------------------------------- 1 | /*************************************************************************** 2 | Part one: 3 | IR Ferraris Reader 4 | 2019 Adlerweb//Bitbastelei 5 | Florian Knodt - www.adlerweb.info 6 | Part two: 7 | Rui Santos 8 | Complete project details at https://randomnerdtutorials.com 9 | https://randomnerdtutorials.com/wifimanager-with-esp8266-autoconnect-custom-parameter-and-manage-your-ssid-and-password/ 10 | Part three: 11 | MQTT and combine other parts 12 | 2020 Eisbaeeer 13 | This sketch allows you to connect an infrared sensor who detect the red mark on a ferraris energy counter. 14 | The data will be sent via MQTT to a server. The counter will be stored on the file-system as json. 15 | The ESP firmware update can be done via "Over-The-Air". 16 | 17 | History 18 | Ver. 0.98 (20230617) 19 | - combine each two meters to allow backwards counting with two IR detectors on same meter 20 | 21 | Ver. 0.97 (202302xx) 22 | - use "Ferraris" objects to capsule code and state for each meter 23 | - allow 1..7 meters just by one #define 24 | - switch from [kW] -> [W] for live consumption, so we do not need float there 25 | - use the count of revolutions as source of total consumption [kWh] to allow fractional part there and no float in IRQ handler 26 | 27 | Ver. 0.96 (20230130) 28 | - first part of code refactoring: split into multiple source files 29 | - use header lines in Dashboard and Configuration for more structured view 30 | - minor changes to improve stability 31 | - use blue LED to show lost WiFi 32 | - fix: value of analog sensor in Dashboard 33 | 34 | Ver. 0.95 (20230121) 35 | - Fixed: WiFi automatic reconnect after WiFi loss 36 | - Fixed: prevent watchdog reboot during long running MQTT update 37 | - Fixed: compiler warnings 38 | 39 | Ver. 0.94 (20221225) 40 | - Changed structure for auto-compiling different boards 41 | - Fixed: missing react-website compile by auto-compile 42 | - removed obsolete binary folder 43 | 44 | Ver. 0.92 (20211014) 45 | - Bugfix: Interrupt Routinen bei MQTT Übertragung unterbrochen 46 | - Bugfix: Interrupt Routinen beim Speichern mit littleFS unterbrochen 47 | - Dashboard mit zusätzlichen Infos erweitert 48 | 49 | Ver. 0.91 (20211011) 50 | - Graphen zum Dashboard hinzugefügt 51 | - ISR mit no-delay Entprellung angepasst 52 | - Nachkommastellen durch fehlerhafte addition von floats entfernt 53 | 54 | Ver. 0.9 (20210917) 55 | - Graphen zum Dashboard hinzugefügt 56 | - Analogwert vom Sensor wird jetzt auf dem Dashboard angezeigt 57 | 58 | Ver. 0.8 (20210914) 59 | - Bugfix Zählerroutine - jetzt per Interrupt auf alle Eingänge 60 | 61 | Ver. 0.7 (20210822) 62 | - Bugfix Zählerstand 63 | - Zählerstand auf Nachkommastellen erweitert 64 | 65 | Ver. 0.6 (20210818) 66 | - Change project to iot-framework 67 | 68 | Ver. 0.5 (20210813) 69 | (Eisbaeeer) 70 | - Bugfix boolean 71 | - Added 3 digits after dot 72 | 73 | Ver. 0.4 (20200905) 74 | (Eisbaeeer) 75 | - Bugfix Zähler 3 und 4 (Zählerstand) 76 | - Neu: MQTT Server Port konfigurierbar 77 | - Neu: MQTT publish Zeit einstellbar (1-9999 Sekunden) 78 | - Blinken der internen LED aus kompatibilitätsgründen von anderen Boards entfernt (manche Boards nutzen D4 für die interne LED) 79 | (ACHTUNG: mit dieser Version gehen die Zählerdaten verloren! bitte über Browser neu eintragen!) 80 | - Neu: Port D4 auf D5 umgezogen! (D4 ist bei manchen Boards die interne LED 81 | - Neu: Alle Zählerdaten werden im EEPROM abgespeichert. 82 | Ver. 0.3 (20200824) 83 | (Eisbaeeer) 84 | - OTA per Webbrowser (http://.../update) 85 | Ver. 0.2 (20200911) 86 | (Eisbaeeer) 87 | - Anpassung Entprellzeit (20ms) 88 | Ver. 0.1 (20200803) 89 | (Eisbaeeer) 90 | * initial version 91 | - Filesystem to store and read values from 92 | - Wifi-Manager to connect to Wifi easy 93 | - Stored values are in JSON format 94 | - MQTT client to publish values 95 | - HTTP page for configuration 96 | - Over the air update of firmware 97 | - 4 meter counter (IR-Input pins) 98 | 99 | *********/ 100 | #include 101 | #include 102 | #include "LittleFS.h" 103 | #include "WiFiManager.h" 104 | #include "webServer.h" 105 | #include "updater.h" 106 | #include "configManager.h" 107 | #include "dashboard.h" 108 | #include "timeSync.h" 109 | #include 110 | #include // time() ctime() 111 | #include 112 | 113 | #include "ferraris.h" 114 | #include "mqtt_subscribe.h" 115 | #include "mqtt_publish.h" 116 | 117 | const int analogInPin = A0; // ESP8266 Analog Pin ADC0 = A0 118 | 119 | int mqttPublishTime; // last publish time in seconds 120 | 121 | // MQTT 122 | WiFiClient espClient; 123 | PubSubClient MQTTclient(espClient); 124 | 125 | // Tasks 126 | struct task 127 | { 128 | unsigned long rate; 129 | unsigned long previous; 130 | }; 131 | 132 | task taskA = { .rate = 1000, .previous = 0 }; 133 | task taskB = { .rate = 200, .previous = 0 }; 134 | 135 | 136 | // ---------------------------------------------------------------------------- 137 | // helper functions 138 | // ---------------------------------------------------------------------------- 139 | 140 | void copyConfig2Ferraris() 141 | { 142 | Ferraris::getInstance(0).set_U_kWh (configManager.data.meter_loops_count_1); 143 | Ferraris::getInstance(0).set_revolutions(configManager.data.meter_counter_reading_1 * configManager.data.meter_loops_count_1); 144 | Ferraris::getInstance(0).set_debounce (configManager.data.meter_debounce_1); 145 | Ferraris::getInstance(0).set_twoway (configManager.data.meter_twoway_1); 146 | #if FERRARIS_NUM > 1 147 | Ferraris::getInstance(1).set_U_kWh (configManager.data.meter_loops_count_2); 148 | Ferraris::getInstance(1).set_revolutions(configManager.data.meter_counter_reading_2 * configManager.data.meter_loops_count_2); 149 | Ferraris::getInstance(1).set_debounce (configManager.data.meter_debounce_2); 150 | Ferraris::getInstance(1).set_twoway (configManager.data.meter_twoway_2); 151 | #endif 152 | #if FERRARIS_NUM > 2 153 | Ferraris::getInstance(2).set_U_kWh (configManager.data.meter_loops_count_3); 154 | Ferraris::getInstance(2).set_revolutions(configManager.data.meter_counter_reading_3 * configManager.data.meter_loops_count_3); 155 | Ferraris::getInstance(2).set_debounce (configManager.data.meter_debounce_3); 156 | Ferraris::getInstance(2).set_twoway (configManager.data.meter_twoway_3); 157 | #endif 158 | #if FERRARIS_NUM > 3 159 | Ferraris::getInstance(3).set_U_kWh (configManager.data.meter_loops_count_4); 160 | Ferraris::getInstance(3).set_revolutions(configManager.data.meter_counter_reading_4 * configManager.data.meter_loops_count_4); 161 | Ferraris::getInstance(3).set_debounce (configManager.data.meter_debounce_4); 162 | Ferraris::getInstance(3).set_twoway (configManager.data.meter_twoway_4); 163 | #endif 164 | #if FERRARIS_NUM > 4 165 | Ferraris::getInstance(4).set_U_kWh (configManager.data.meter_loops_count_5); 166 | Ferraris::getInstance(4).set_revolutions(configManager.data.meter_counter_reading_5 * configManager.data.meter_loops_count_5); 167 | Ferraris::getInstance(4).set_debounce (configManager.data.meter_debounce_5; 168 | Ferraris::getInstance(4).set_twoway (configManager.data.meter_twoway_5); 169 | #endif 170 | #if FERRARIS_NUM > 5 171 | Ferraris::getInstance(5).set_U_kWh (configManager.data.meter_loops_count_6); 172 | Ferraris::getInstance(5).set_revolutions(configManager.data.meter_counter_reading_6 * configManager.data.meter_loops_count_6); 173 | Ferraris::getInstance(5).set_debounce (configManager.data.meter_debounce_6); 174 | Ferraris::getInstance(5).set_twoway (configManager.data.meter_twoway_6); 175 | #endif 176 | #if FERRARIS_NUM > 6 177 | Ferraris::getInstance(6).set_U_kWh (configManager.data.meter_loops_count_7); 178 | Ferraris::getInstance(6).set_revolutions(configManager.data.meter_counter_reading_7 * configManager.data.meter_loops_count_7); 179 | Ferraris::getInstance(6).set_debounce (configManager.data.meter_debounce_7); 180 | Ferraris::getInstance(6).set_twoway (configManager.data.meter_twoway_7); 181 | #endif 182 | } 183 | 184 | // update Dashboard with current measurement values 185 | void updateDashboard(uint8_t F) 186 | { 187 | assert(F < FERRARIS_NUM); 188 | 189 | switch (F) { 190 | case 0: 191 | dash.data.revolutions_1 = Ferraris::getInstance(0).get_revolutions(); 192 | dash.data.kWh_1 = Ferraris::getInstance(0).get_kWh(); 193 | dash.data.W_1 = Ferraris::getInstance(0).get_W(); 194 | configManager.data.meter_counter_reading_1 = Ferraris::getInstance(0).get_kWh(); 195 | break; 196 | #if FERRARIS_NUM > 1 197 | case 1: 198 | dash.data.revolutions_2 = Ferraris::getInstance(1).get_revolutions(); 199 | dash.data.kWh_2 = Ferraris::getInstance(1).get_kWh(); 200 | dash.data.W_2 = Ferraris::getInstance(1).get_W(); 201 | configManager.data.meter_counter_reading_2 = Ferraris::getInstance(1).get_kWh(); 202 | break; 203 | #endif 204 | #if FERRARIS_NUM > 2 205 | case 2: 206 | dash.data.revolutions_3 = Ferraris::getInstance(2).get_revolutions(); 207 | dash.data.kWh_3 = Ferraris::getInstance(2).get_kWh(); 208 | dash.data.W_3 = Ferraris::getInstance(2).get_W(); 209 | configManager.data.meter_counter_reading_3 = Ferraris::getInstance(2).get_kWh(); 210 | break; 211 | #endif 212 | #if FERRARIS_NUM > 3 213 | case 3: 214 | dash.data.revolutions_4 = Ferraris::getInstance(3).get_revolutions(); 215 | dash.data.kWh_4 = Ferraris::getInstance(3).get_kWh(); 216 | dash.data.W_4 = Ferraris::getInstance(3).get_W(); 217 | configManager.data.meter_counter_reading_4 = Ferraris::getInstance(3).get_kWh(); 218 | break; 219 | #endif 220 | #if FERRARIS_NUM > 4 221 | case 4: 222 | dash.data.revolutions_5 = Ferraris::getInstance(1).get_revolutions(); 223 | dash.data.kWh_5 = Ferraris::getInstance(1).get_kWh(); 224 | dash.data.W_5 = Ferraris::getInstance(1).get_W(); 225 | configManager.data.meter_counter_reading_5 = Ferraris::getInstance(4).get_kWh(); 226 | break; 227 | #endif 228 | #if FERRARIS_NUM > 5 229 | case 5: 230 | dash.data.revolutions_6 = Ferraris::getInstance(2).get_revolutions(); 231 | dash.data.kWh_6 = Ferraris::getInstance(2).get_kWh(); 232 | dash.data.W_6 = Ferraris::getInstance(2).get_W(); 233 | configManager.data.meter_counter_reading_6 = Ferraris::getInstance(5).get_kWh(); 234 | break; 235 | #endif 236 | #if FERRARIS_NUM > 6 237 | case 6: 238 | dash.data.revolutions_7 = Ferraris::getInstance(3).get_revolutions(); 239 | dash.data.kWh_7 = Ferraris::getInstance(3).get_kWh(); 240 | dash.data.W_7 = Ferraris::getInstance(3).get_W(); 241 | configManager.data.meter_counter_reading_7 = Ferraris::getInstance(6).get_kWh(); 242 | break; 243 | #endif 244 | } 245 | } 246 | 247 | // update Dashboard with graph plot data, only necessary when client is connected 248 | void updateDashboardGraph() 249 | { 250 | int rssi = WiFi.RSSI(); 251 | sprintf(dash.data.Wifi_RSSI, "%d", rssi) ; 252 | dash.data.WLAN_RSSI = WiFi.RSSI(); 253 | 254 | dash.data.Sensor = analogRead(analogInPin); 255 | 256 | dash.data.Impuls_Z1 = !Ferraris::getInstance(0).get_state(); 257 | #if FERRARIS_NUM > 1 258 | dash.data.Impuls_Z2 = !Ferraris::getInstance(1).get_state(); 259 | #endif 260 | #if FERRARIS_NUM > 2 261 | dash.data.Impuls_Z3 = !Ferraris::getInstance(2).get_state(); 262 | #endif 263 | #if FERRARIS_NUM > 3 264 | dash.data.Impuls_Z4 = !Ferraris::getInstance(3).get_state(); 265 | #endif 266 | #if FERRARIS_NUM > 4 267 | dash.data.Impuls_Z4 = !Ferraris::getInstance(4).get_state(); 268 | #endif 269 | #if FERRARIS_NUM > 5 270 | dash.data.Impuls_Z4 = !Ferraris::getInstance(5).get_state(); 271 | #endif 272 | #if FERRARIS_NUM > 6 273 | dash.data.Impuls_Z4 = !Ferraris::getInstance(6).get_state(); 274 | #endif 275 | 276 | for (uint8_t F=0; F taskA.rate)) { 331 | taskA.previous = millis(); 332 | if (WiFi.status() == WL_CONNECTED) { 333 | checkMQTTconnection(); 334 | 335 | if (mqttPublishTime <= configManager.data.mqtt_interval) { 336 | mqttPublishTime++; 337 | } else { 338 | publishMQTT(); 339 | mqttPublishTime = 0; 340 | } 341 | } 342 | time(&now); // read the current time 343 | tm *tm = localtime(&now); // update the structure tm with the current time 344 | if (tm->tm_sec == 0) { 345 | // every minute -> check WiFi status 346 | if ((last_wifi_status == WL_IDLE_STATUS) && (WiFi.status() == WL_IDLE_STATUS)) { 347 | // try to fix stuck WiFi 348 | Serial.println("!!! WL_IDLE_STATUS == 0 for a minute !!!"); 349 | Serial.println("Restart stuck WiFi"); 350 | ETS_UART_INTR_DISABLE(); 351 | wifi_station_disconnect(); 352 | ETS_UART_INTR_ENABLE(); 353 | WiFi.begin(); 354 | } 355 | last_wifi_status = WiFi.status(); 356 | if (tm->tm_min % 5 == 0) { 357 | // every 5 minutes -> print current time 358 | char buffer[20]; 359 | sprintf(buffer, "%4hu-%02hhu-%02hhu %2hhu:%02hhu", 360 | tm->tm_year + 1900, 361 | tm->tm_mon + 1, // January = 0 (!) 362 | tm->tm_mday, // day of month 363 | tm->tm_hour, // hours since midnight 0-23 364 | tm->tm_min); // minutes after the hour 0-59 365 | Serial.println(buffer); 366 | } 367 | } 368 | } 369 | 370 | if (taskB.previous == 0 || (millis() - taskB.previous > taskB.rate)) { 371 | taskB.previous = millis(); 372 | if (GUI.ws.count() > 0) // only when clients are connected to web-server 373 | updateDashboardGraph(); 374 | 375 | digitalWrite(LED_BUILTIN, (WiFi.status() == WL_CONNECTED)); 376 | } 377 | 378 | if (saveConfig) { 379 | Serial.println("[save config]..."); 380 | saveConfig = false; 381 | configManager.save(); // may take ~50ms 382 | Serial.println("...[save config]"); 383 | } 384 | } 385 | -------------------------------------------------------------------------------- /src/mqtt_publish.cpp: -------------------------------------------------------------------------------- 1 | #include "mqtt_publish.h" 2 | // libraries 3 | #include 4 | #include 5 | #include 6 | // access to data on web server 7 | #include "configManager.h" 8 | #include "dashboard.h" 9 | #include "ferraris.h" 10 | 11 | 12 | String getTopicName(int meter, String measurement) { 13 | String topic = "Ferraris/"; 14 | topic = topic + configManager.data.messure_place; 15 | topic = topic +"/Zähler"; 16 | topic = topic + String(meter); 17 | topic = topic +"/"; 18 | topic = topic + measurement; 19 | 20 | return topic; 21 | } 22 | 23 | String getHATopicName(String mqtt_type, char uniqueId[30]) { 24 | String topic = "homeassistant/"; 25 | topic = topic + mqtt_type; 26 | topic = topic +"/"; 27 | topic = topic + String(uniqueId); 28 | topic = topic +"/config"; 29 | 30 | return topic; 31 | } 32 | 33 | String getSetTopicName(int meter, String measurement) { 34 | String topic = getTopicName(meter,measurement); 35 | topic = topic + "/set"; 36 | 37 | return topic; 38 | } 39 | 40 | 41 | // declare some external stuff -> todo: better refactoring 42 | extern PubSubClient MQTTclient; 43 | 44 | static int mqttReconnect; // timeout for reconnecting MQTT Server 45 | 46 | void checkMQTTconnection(void) 47 | { 48 | if (mqttReconnect++ > 60) { 49 | mqttReconnect = 0; // reset reconnect timeout 50 | // reconnect to MQTT Server 51 | if (!MQTTclient.connected()) { 52 | dash.data.MQTT_Connected = false; 53 | Serial.println("Attempting MQTT connection..."); 54 | // Create a random client ID 55 | String clientId = "FerrarisClient-"; 56 | //clientId += String(random(0xffff), HEX); 57 | clientId += String(configManager.data.messure_place); 58 | // Attempt to connect 59 | if (MQTTclient.connect(clientId.c_str(),configManager.data.mqtt_user,configManager.data.mqtt_password)) { 60 | Serial.println("connected"); 61 | dash.data.MQTT_Connected = true; 62 | // Once connected, publish an announcement... 63 | publishMQTT(); 64 | // ... and resubscribe 65 | // MQTTclient.subscribe("inTopic"); 66 | String topic[3]={"UKWh","Stand","Entprellzeit"}; 67 | for (int tId = 0; tId < 3; tId++) { 68 | for (int i = 0; i < 4; i++) { 69 | ESP.wdtFeed(); // keep WatchDog alive 70 | String t = getSetTopicName(i+1, topic[tId]); 71 | if (MQTTclient.subscribe(t.c_str())) { 72 | Serial.print("subscribed to "); 73 | } else { 74 | Serial.print("failed to subscribe to "); 75 | } 76 | Serial.print(t); 77 | Serial.println(); 78 | } 79 | } 80 | } else { 81 | Serial.print("failed, rc="); 82 | Serial.print(MQTTclient.state()); 83 | Serial.println(" try again in one minute"); 84 | } 85 | } 86 | } 87 | } 88 | 89 | 90 | #define MSG_BUFFER_SIZE (20) 91 | char message_buffer[MSG_BUFFER_SIZE]; 92 | 93 | void publishMQTT_HA(void) 94 | { 95 | String topic; 96 | String cmdTopic; 97 | String haTopic; 98 | StaticJsonDocument<240> discoverDocument; 99 | char discoverJson[240]; 100 | char uniqueId[30]; 101 | String meterName; 102 | ESP.wdtFeed(); // keep WatchDog alive 103 | for (int i = 0; i < 4; i++) { 104 | // kW 105 | discoverDocument.clear(); 106 | memset(discoverJson, 0, sizeof(discoverJson)); 107 | memset(uniqueId, 0, sizeof(uniqueId)); 108 | 109 | snprintf_P(uniqueId, sizeof(uniqueId), PSTR("%06X_%s_%d"), ESP.getChipId(), "kw", i+1); 110 | topic = getTopicName(i+1, "KW"); 111 | meterName = "Zähler "+String(i+1)+" kW"; 112 | 113 | discoverDocument["dev_cla"] = "power"; 114 | discoverDocument["uniq_id"] = uniqueId; 115 | discoverDocument["name"] = meterName; 116 | discoverDocument["stat_t"] = topic; 117 | discoverDocument["unit_of_meas"] = "kW"; 118 | discoverDocument["val_tpl"] = "{{value}}"; 119 | 120 | serializeJson(discoverDocument, discoverJson); 121 | 122 | haTopic = getHATopicName("sensor", uniqueId); 123 | if (!MQTTclient.publish(haTopic.c_str(), discoverJson, true)) { 124 | Serial.print("failed to publish kw "+String(i+1)+" discover json:"); 125 | Serial.println(); 126 | Serial.print(discoverJson); 127 | Serial.println(); 128 | } 129 | 130 | // kWh / Stand 131 | discoverDocument.clear(); 132 | memset(discoverJson, 0, sizeof(discoverJson)); 133 | memset(uniqueId, 0, sizeof(uniqueId)); 134 | 135 | snprintf_P(uniqueId, sizeof(uniqueId), PSTR("%06X_%s_%d"), ESP.getChipId(), "kwh", i+1); 136 | topic = getTopicName(i+1, "Stand"); 137 | meterName = "Zähler "+String(i+1)+" kW/h"; 138 | cmdTopic = getSetTopicName(i+1, "Stand"); 139 | 140 | discoverDocument["dev_cla"] = "energy"; 141 | discoverDocument["cmd_t"] = cmdTopic; 142 | discoverDocument["uniq_id"] = uniqueId; 143 | discoverDocument["name"] = meterName; 144 | discoverDocument["stat_t"] = topic; 145 | discoverDocument["unit_of_meas"] = "kWh"; 146 | discoverDocument["val_tpl"] = "{{value}}"; 147 | 148 | serializeJson(discoverDocument, discoverJson); 149 | 150 | haTopic = getHATopicName("sensor", uniqueId); 151 | if (!MQTTclient.publish(haTopic.c_str(), discoverJson, true)) { 152 | Serial.print("failed to publish kwh "+String(i+1)+" discover json:"); 153 | Serial.println(); 154 | Serial.print(discoverJson); 155 | Serial.println(); 156 | } 157 | 158 | // Umdrehungen/kWh 159 | discoverDocument.clear(); 160 | memset(discoverJson, 0, sizeof(discoverJson)); 161 | memset(uniqueId, 0, sizeof(uniqueId)); 162 | snprintf_P(uniqueId, sizeof(uniqueId), PSTR("%06X_%s_%d"), ESP.getChipId(), "ukwh", i+1); 163 | topic = getTopicName(i+1, "UKWh"); 164 | meterName = "Zähler "+String(i+1)+" Umdrehungen/kWh"; 165 | cmdTopic = getSetTopicName(i+1, "UKWh"); 166 | 167 | discoverDocument["cmd_t"] = cmdTopic; 168 | discoverDocument["uniq_id"] = uniqueId; 169 | discoverDocument["name"] = meterName; 170 | discoverDocument["stat_t"] = topic; 171 | discoverDocument["unit_of_meas"] = "Umdrehungen/kWh"; 172 | discoverDocument["val_tpl"] = "{{value}}"; 173 | discoverDocument["max"] = 512; 174 | 175 | serializeJson(discoverDocument, discoverJson); 176 | 177 | haTopic = getHATopicName("number", uniqueId); 178 | if (!MQTTclient.publish(haTopic.c_str(), discoverJson, true)) { 179 | Serial.print("failed to publish ukwh "+String(i+1)+" discover json:"); 180 | Serial.println(); 181 | Serial.print(discoverJson); 182 | Serial.println(); 183 | } 184 | 185 | // Entprellzeit 186 | discoverDocument.clear(); 187 | memset(discoverJson, 0, sizeof(discoverJson)); 188 | memset(uniqueId, 0, sizeof(uniqueId)); 189 | snprintf_P(uniqueId, sizeof(uniqueId), PSTR("%06X_%s_%d"), ESP.getChipId(), "entprellzeit", i+1); 190 | topic = getTopicName(i+1, "Entprellzeit"); 191 | meterName = "Zähler "+String(i+1)+" Entprellzeit"; 192 | cmdTopic = getSetTopicName(i+1, "Entprellzeit"); 193 | 194 | discoverDocument["cmd_t"] = cmdTopic; 195 | discoverDocument["uniq_id"] = uniqueId; 196 | discoverDocument["name"] = meterName; 197 | discoverDocument["stat_t"] = topic; 198 | discoverDocument["unit_of_meas"] = "ms"; 199 | discoverDocument["val_tpl"] = "{{value}}"; 200 | discoverDocument["max"] = 200; // TODO: Is this a reasonable maximum value? 201 | 202 | serializeJson(discoverDocument, discoverJson); 203 | 204 | haTopic = getHATopicName("number", uniqueId); 205 | if (!MQTTclient.publish(haTopic.c_str(), discoverJson, true)) { 206 | Serial.print("failed to publish debounce time "+String(i+1)+" discover json:"); 207 | Serial.println(); 208 | Serial.print(discoverJson); 209 | Serial.println(); 210 | } 211 | ESP.wdtFeed(); // keep WatchDog alive 212 | } 213 | } 214 | 215 | 216 | void publishMQTT_simple() 217 | { 218 | String topic; 219 | 220 | // Meter #1 221 | topic = getTopicName(1, "Stand"); 222 | dtostrf(Ferraris::getInstance(0).get_kWh(), 1, 1, message_buffer); 223 | MQTTclient.publish(topic.c_str(), message_buffer, true); 224 | 225 | topic = getTopicName(1, "W"); 226 | dtostrf(Ferraris::getInstance(0).get_W_average(), 1, 1, message_buffer); 227 | MQTTclient.publish(topic.c_str(), message_buffer, false); 228 | 229 | topic = getTopicName(1, "UKWh"); 230 | itoa(configManager.data.meter_loops_count_1, message_buffer, 10); 231 | MQTTclient.publish(topic.c_str(), message_buffer, true); 232 | 233 | topic = getTopicName(1, "Entprellzeit"); 234 | itoa(configManager.data.meter_debounce_1, message_buffer, 10); 235 | MQTTclient.publish(topic.c_str(), message_buffer, true); 236 | 237 | // Meter #2 238 | topic = getTopicName(2, "Stand"); 239 | dtostrf(Ferraris::getInstance(1).get_kWh(), 1, 1, message_buffer); 240 | MQTTclient.publish(topic.c_str(), message_buffer, true); 241 | 242 | topic = getTopicName(2, "W"); 243 | dtostrf(Ferraris::getInstance(1).get_W_average(), 1, 1, message_buffer); 244 | MQTTclient.publish(topic.c_str(), message_buffer, false); 245 | 246 | topic = getTopicName(2, "UKWh"); 247 | itoa(configManager.data.meter_loops_count_2, message_buffer, 10); 248 | MQTTclient.publish(topic.c_str(), message_buffer, true); 249 | 250 | topic = getTopicName(2,"Entprellzeit"); 251 | itoa(configManager.data.meter_debounce_2, message_buffer, 10); 252 | MQTTclient.publish(topic.c_str(), message_buffer, true); 253 | 254 | // Meter #3 255 | topic = getTopicName(3, "Stand"); 256 | dtostrf(Ferraris::getInstance(2).get_kWh(), 1, 1, message_buffer); 257 | MQTTclient.publish(topic.c_str(), message_buffer, true); 258 | 259 | topic = getTopicName(3, "W"); 260 | dtostrf(Ferraris::getInstance(2).get_W_average(), 1, 1, message_buffer); 261 | MQTTclient.publish(topic.c_str(), message_buffer, false); 262 | 263 | topic = getTopicName(3, "UKWh"); 264 | itoa(configManager.data.meter_loops_count_3, message_buffer, 10); 265 | MQTTclient.publish(topic.c_str(), message_buffer, true); 266 | 267 | topic = getTopicName(3, "Entprellzeit"); 268 | itoa(configManager.data.meter_debounce_3, message_buffer, 10); 269 | MQTTclient.publish(topic.c_str(), message_buffer, true); 270 | 271 | // Meter #4 272 | topic = getTopicName(4, "Stand"); 273 | dtostrf(Ferraris::getInstance(3).get_kWh(), 1, 1, message_buffer); 274 | MQTTclient.publish(topic.c_str(), message_buffer, true); 275 | 276 | topic = getTopicName(4, "W"); 277 | dtostrf(Ferraris::getInstance(3).get_W_average(), 1, 1, message_buffer); 278 | MQTTclient.publish(topic.c_str(), message_buffer, false); 279 | 280 | topic = getTopicName(4, "UKWh"); 281 | itoa(configManager.data.meter_loops_count_4, message_buffer, 10); 282 | MQTTclient.publish(topic.c_str(), message_buffer, true); 283 | 284 | topic = getTopicName(4, "Entprellzeit"); 285 | itoa(configManager.data.meter_debounce_4, message_buffer, 10); 286 | MQTTclient.publish(topic.c_str(), message_buffer, true); 287 | } 288 | 289 | 290 | void publishMQTT(void) 291 | { 292 | if (configManager.data.home_assistant_auto_discovery) { 293 | publishMQTT_HA(); 294 | } 295 | 296 | publishMQTT_simple(); 297 | } 298 | -------------------------------------------------------------------------------- /src/mqtt_publish.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | 4 | void checkMQTTconnection(void); 5 | void publishMQTT(void); 6 | -------------------------------------------------------------------------------- /src/mqtt_subscribe.cpp: -------------------------------------------------------------------------------- 1 | #include "ferraris.h" 2 | #include "mqtt_subscribe.h" 3 | 4 | #include "configManager.h" 5 | 6 | 7 | bool saveConfig = false; 8 | 9 | 10 | // declare some external stuff -> todo: better refactoring 11 | String getSetTopicName(int meter, String measurement); 12 | 13 | // MQTT subscribe callback 14 | void parseMQTTmessage(char* topic, byte* payload, unsigned int length) 15 | { 16 | Serial.print("Message arrived ["); 17 | Serial.print(topic); 18 | Serial.print("] "); 19 | 20 | // copy payload into string compatible format 21 | char pl[length+1]; 22 | for (unsigned int i = 0; i < length; i++) { 23 | Serial.print((char)payload[i]); 24 | pl[i] = (char)payload[i]; 25 | } 26 | pl[length] = '\0'; 27 | Serial.println(); 28 | 29 | String p = String(pl); 30 | String t = String(topic); 31 | 32 | String ukwhCmdTopic; 33 | String kwhCmdTopic; 34 | String debounceTimeCmdTopic; 35 | bool processed=false; 36 | for (int i=0; i<4; i++) { 37 | ukwhCmdTopic = getSetTopicName(i+1, "UKWh"); 38 | kwhCmdTopic = getSetTopicName(i+1, "Stand"); 39 | debounceTimeCmdTopic = getSetTopicName(i+1, "Entprellzeit"); 40 | 41 | if (t == ukwhCmdTopic){ 42 | uint16_t meters_per_loop = p.toInt(); 43 | switch (i+1) { 44 | case 1: 45 | Serial.print("Setting configManager.data.meter_loops_count_1 to "); 46 | Serial.print(meters_per_loop); 47 | Serial.println(); 48 | configManager.data.meter_loops_count_1 = meters_per_loop; 49 | saveConfig = true; 50 | processed = true; 51 | break; 52 | case 2: 53 | Serial.print("Setting configManager.data.meter_loops_count_2 to "); 54 | Serial.print(meters_per_loop); 55 | Serial.println(); 56 | configManager.data.meter_loops_count_2 = meters_per_loop; 57 | saveConfig = true; 58 | processed = true; 59 | break; 60 | case 3: 61 | Serial.print("Setting configManager.data.meter_loops_count_3 to "); 62 | Serial.print(meters_per_loop); 63 | Serial.println(); 64 | configManager.data.meter_loops_count_3 = meters_per_loop; 65 | saveConfig = true; 66 | processed = true; 67 | break; 68 | case 4: 69 | Serial.print("Setting configManager.data.meter_loops_count_4 to "); 70 | Serial.print(meters_per_loop); 71 | Serial.println(); 72 | configManager.data.meter_loops_count_4 = meters_per_loop; 73 | saveConfig = true; 74 | processed = true; 75 | break; 76 | 77 | default: 78 | break; 79 | } 80 | 81 | if (processed) { 82 | break; 83 | } 84 | } 85 | 86 | if (t == kwhCmdTopic) { 87 | float meter_value = p.toFloat(); 88 | switch (i+1) { 89 | case 1: 90 | Serial.print("Setting configManager.data.meter_counter_reading_1 to "); 91 | Serial.print(meter_value); 92 | Serial.println(); 93 | configManager.data.meter_counter_reading_1 = meter_value; 94 | saveConfig = true; 95 | processed = true; 96 | break; 97 | case 2: 98 | Serial.print("Setting configManager.data.meter_counter_reading_2 to "); 99 | Serial.print(meter_value); 100 | Serial.println(); 101 | configManager.data.meter_counter_reading_2 = meter_value; 102 | saveConfig = true; 103 | processed = true; 104 | break; 105 | case 3: 106 | Serial.print("Setting configManager.data.meter_counter_reading_3 to "); 107 | Serial.print(meter_value); 108 | Serial.println(); 109 | configManager.data.meter_counter_reading_3 = meter_value; 110 | saveConfig = true; 111 | processed = true; 112 | break; 113 | case 4: 114 | Serial.print("Setting configManager.data.meter_counter_reading_4 to "); 115 | Serial.print(meter_value); 116 | Serial.println(); 117 | configManager.data.meter_counter_reading_4 = meter_value; 118 | saveConfig = true; 119 | processed = true; 120 | break; 121 | 122 | default: 123 | break; 124 | } 125 | 126 | if (processed) { 127 | break; 128 | } 129 | } 130 | 131 | if (t == debounceTimeCmdTopic) { 132 | uint16_t debounce_value = p.toInt(); 133 | switch (i+1) { 134 | case 1: 135 | Serial.print("Setting configManager.data.debounce_1 to "); 136 | Serial.print(debounce_value); 137 | Serial.println(); 138 | configManager.data.meter_debounce_1 = debounce_value; 139 | saveConfig = true; 140 | processed = true; 141 | break; 142 | case 2: 143 | Serial.print("Setting configManager.data.debounce_2 to "); 144 | Serial.print(debounce_value); 145 | Serial.println(); 146 | configManager.data.meter_debounce_2 = debounce_value; 147 | saveConfig = true; 148 | processed = true; 149 | break; 150 | case 3: 151 | Serial.print("Setting configManager.data.debounce_3 to "); 152 | Serial.print(debounce_value); 153 | Serial.println(); 154 | configManager.data.meter_debounce_3 = debounce_value; 155 | saveConfig = true; 156 | processed = true; 157 | break; 158 | case 4: 159 | Serial.print("Setting configManager.data.debounce_4 to "); 160 | Serial.print(debounce_value); 161 | Serial.println(); 162 | configManager.data.meter_debounce_4 = debounce_value; 163 | saveConfig = true; 164 | processed = true; 165 | break; 166 | 167 | default: 168 | break; 169 | } 170 | 171 | if (processed) { 172 | break; 173 | } 174 | } 175 | } 176 | 177 | if (!processed) { 178 | Serial.print("Could not process request!"); 179 | Serial.println(); 180 | } 181 | } 182 | -------------------------------------------------------------------------------- /src/mqtt_subscribe.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | 6 | extern bool saveConfig; 7 | 8 | 9 | // MQTT subscribe callback 10 | void parseMQTTmessage(char* topic, byte* payload, unsigned int length); 11 | --------------------------------------------------------------------------------