├── esp32 └── beegl_broker │ ├── .gitignore │ ├── include │ ├── Storage.h │ ├── LogManagement.h │ ├── HttpPublisher.h │ ├── Service.h │ ├── Indicator.h │ ├── MqttPublisher.h │ ├── README │ ├── Connection.h │ ├── Measurer.h │ ├── Runtime.h │ ├── Updater.h │ ├── Publisher.h │ ├── SettingsManagement.h │ ├── Broker.h │ ├── Log.h │ └── Settings.h │ ├── customparts_no_ota.csv │ ├── customparts.csv │ ├── customparts_tight.csv │ ├── post_build.py │ ├── pre_build.py │ ├── src │ ├── Storage.cpp │ ├── Service.cpp │ ├── Indicator.cpp │ ├── LogManagement.cpp │ ├── HttpPublisher.cpp │ ├── MqttPublisher.cpp │ ├── Broker.cpp │ ├── Log.cpp │ ├── Settings.cpp │ ├── main.cpp │ ├── Runtime.cpp │ ├── Measurer.cpp │ ├── Publisher.cpp │ ├── Connection.cpp │ ├── Updater.cpp │ └── SettingsManagement.cpp │ ├── test │ └── README │ ├── lib │ └── README │ ├── platformio.ini │ └── README.md ├── hw ├── BeeGl_Broker_Variant_A_BOM.xlsx ├── BeeGl_Broker_Variant_B_BOM.xlsx ├── 3D_models │ ├── beegl_broker_case_cover.stl │ ├── beegl_broker_mounting_panel.stl │ ├── beegl_broker_case_cover_no_holes.stl │ ├── beegl_broker_variant_A_hx711_dht22_case.stl │ ├── beegl_broker_variant_A_load_cell_holder.stl │ ├── beegl_broker_variant_C_hx711_dht22_case.stl │ ├── beegl_broker_variant_A_hx711_dht22_case_cover.stl │ ├── beegl_broker_variant_C_hx711_dht22_case_cover.stl │ ├── beegl_broker_solar_panel_holder_beegl_broker_mount.stl │ ├── beegl_broker_solar_panel_holder_solar_panel_mount.stl │ ├── beegl_broker_variant_B_lolind32pro_dual_18650_case.stl │ ├── beegl_broker_variant_B_lolind32pro_sim800l_dual_18650_case.stl │ ├── beegl_broker_variant_AC_lolind32pro_sim800l_dual_18650_case.stl │ ├── beegl_broker_variant_B_almgsi_20x20x2_straight_bar_load_cell_base.stl │ ├── beegl_broker_variant_B_almgsi_20x20x2_straight_bar_load_cell_top_edge.stl │ ├── beegl_broker_variant_BC_almgsi_20x20x2_straight_bar_load_cell_dist_20mm.stl │ ├── beegl_broker_variant_B_almgsi_20x20x2_straight_bar_load_cell_bottom_edge.stl │ ├── beegl_broker_variant_B_almgsi_20x20x2_straight_bar_load_cell_cable_align.stl │ ├── beegl_broker_variant_C_almgsi_20x20x2_straight_bar_load_cell_top_dist_w_holes.stl │ └── beegl_broker_variant_BC_almgsi_20x20x2_straight_bar_load_cell_top_dist_wo_holes.stl ├── drawings │ └── BeeGl_Broker_Variant_B_Drawings.pdf └── schematic │ ├── BeeGl_Broker_LolinD32Pro_SIM800L_rev_1.0.png │ └── BeeGL_Broker_LolinD32Pro_SIM800L_BOM_rev_1.0.xlsx ├── .github └── ISSUE_TEMPLATE │ ├── feature_request.md │ └── bug_report.md ├── .vscode └── launch.json ├── README.md └── LICENSE /esp32/beegl_broker/.gitignore: -------------------------------------------------------------------------------- 1 | .pio 2 | .pioenvs 3 | .piolibdeps 4 | .vscode/* 5 | .releases 6 | 7 | -------------------------------------------------------------------------------- /hw/BeeGl_Broker_Variant_A_BOM.xlsx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bsorgo/beegl/HEAD/hw/BeeGl_Broker_Variant_A_BOM.xlsx -------------------------------------------------------------------------------- /hw/BeeGl_Broker_Variant_B_BOM.xlsx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bsorgo/beegl/HEAD/hw/BeeGl_Broker_Variant_B_BOM.xlsx -------------------------------------------------------------------------------- /hw/3D_models/beegl_broker_case_cover.stl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bsorgo/beegl/HEAD/hw/3D_models/beegl_broker_case_cover.stl -------------------------------------------------------------------------------- /hw/3D_models/beegl_broker_mounting_panel.stl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bsorgo/beegl/HEAD/hw/3D_models/beegl_broker_mounting_panel.stl -------------------------------------------------------------------------------- /hw/drawings/BeeGl_Broker_Variant_B_Drawings.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bsorgo/beegl/HEAD/hw/drawings/BeeGl_Broker_Variant_B_Drawings.pdf -------------------------------------------------------------------------------- /hw/3D_models/beegl_broker_case_cover_no_holes.stl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bsorgo/beegl/HEAD/hw/3D_models/beegl_broker_case_cover_no_holes.stl -------------------------------------------------------------------------------- /hw/3D_models/beegl_broker_variant_A_hx711_dht22_case.stl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bsorgo/beegl/HEAD/hw/3D_models/beegl_broker_variant_A_hx711_dht22_case.stl -------------------------------------------------------------------------------- /hw/3D_models/beegl_broker_variant_A_load_cell_holder.stl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bsorgo/beegl/HEAD/hw/3D_models/beegl_broker_variant_A_load_cell_holder.stl -------------------------------------------------------------------------------- /hw/3D_models/beegl_broker_variant_C_hx711_dht22_case.stl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bsorgo/beegl/HEAD/hw/3D_models/beegl_broker_variant_C_hx711_dht22_case.stl -------------------------------------------------------------------------------- /hw/schematic/BeeGl_Broker_LolinD32Pro_SIM800L_rev_1.0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bsorgo/beegl/HEAD/hw/schematic/BeeGl_Broker_LolinD32Pro_SIM800L_rev_1.0.png -------------------------------------------------------------------------------- /hw/3D_models/beegl_broker_variant_A_hx711_dht22_case_cover.stl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bsorgo/beegl/HEAD/hw/3D_models/beegl_broker_variant_A_hx711_dht22_case_cover.stl -------------------------------------------------------------------------------- /hw/3D_models/beegl_broker_variant_C_hx711_dht22_case_cover.stl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bsorgo/beegl/HEAD/hw/3D_models/beegl_broker_variant_C_hx711_dht22_case_cover.stl -------------------------------------------------------------------------------- /hw/schematic/BeeGL_Broker_LolinD32Pro_SIM800L_BOM_rev_1.0.xlsx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bsorgo/beegl/HEAD/hw/schematic/BeeGL_Broker_LolinD32Pro_SIM800L_BOM_rev_1.0.xlsx -------------------------------------------------------------------------------- /hw/3D_models/beegl_broker_solar_panel_holder_beegl_broker_mount.stl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bsorgo/beegl/HEAD/hw/3D_models/beegl_broker_solar_panel_holder_beegl_broker_mount.stl -------------------------------------------------------------------------------- /hw/3D_models/beegl_broker_solar_panel_holder_solar_panel_mount.stl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bsorgo/beegl/HEAD/hw/3D_models/beegl_broker_solar_panel_holder_solar_panel_mount.stl -------------------------------------------------------------------------------- /hw/3D_models/beegl_broker_variant_B_lolind32pro_dual_18650_case.stl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bsorgo/beegl/HEAD/hw/3D_models/beegl_broker_variant_B_lolind32pro_dual_18650_case.stl -------------------------------------------------------------------------------- /hw/3D_models/beegl_broker_variant_B_lolind32pro_sim800l_dual_18650_case.stl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bsorgo/beegl/HEAD/hw/3D_models/beegl_broker_variant_B_lolind32pro_sim800l_dual_18650_case.stl -------------------------------------------------------------------------------- /hw/3D_models/beegl_broker_variant_AC_lolind32pro_sim800l_dual_18650_case.stl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bsorgo/beegl/HEAD/hw/3D_models/beegl_broker_variant_AC_lolind32pro_sim800l_dual_18650_case.stl -------------------------------------------------------------------------------- /hw/3D_models/beegl_broker_variant_B_almgsi_20x20x2_straight_bar_load_cell_base.stl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bsorgo/beegl/HEAD/hw/3D_models/beegl_broker_variant_B_almgsi_20x20x2_straight_bar_load_cell_base.stl -------------------------------------------------------------------------------- /hw/3D_models/beegl_broker_variant_B_almgsi_20x20x2_straight_bar_load_cell_top_edge.stl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bsorgo/beegl/HEAD/hw/3D_models/beegl_broker_variant_B_almgsi_20x20x2_straight_bar_load_cell_top_edge.stl -------------------------------------------------------------------------------- /hw/3D_models/beegl_broker_variant_BC_almgsi_20x20x2_straight_bar_load_cell_dist_20mm.stl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bsorgo/beegl/HEAD/hw/3D_models/beegl_broker_variant_BC_almgsi_20x20x2_straight_bar_load_cell_dist_20mm.stl -------------------------------------------------------------------------------- /hw/3D_models/beegl_broker_variant_B_almgsi_20x20x2_straight_bar_load_cell_bottom_edge.stl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bsorgo/beegl/HEAD/hw/3D_models/beegl_broker_variant_B_almgsi_20x20x2_straight_bar_load_cell_bottom_edge.stl -------------------------------------------------------------------------------- /hw/3D_models/beegl_broker_variant_B_almgsi_20x20x2_straight_bar_load_cell_cable_align.stl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bsorgo/beegl/HEAD/hw/3D_models/beegl_broker_variant_B_almgsi_20x20x2_straight_bar_load_cell_cable_align.stl -------------------------------------------------------------------------------- /hw/3D_models/beegl_broker_variant_C_almgsi_20x20x2_straight_bar_load_cell_top_dist_w_holes.stl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bsorgo/beegl/HEAD/hw/3D_models/beegl_broker_variant_C_almgsi_20x20x2_straight_bar_load_cell_top_dist_w_holes.stl -------------------------------------------------------------------------------- /hw/3D_models/beegl_broker_variant_BC_almgsi_20x20x2_straight_bar_load_cell_top_dist_wo_holes.stl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bsorgo/beegl/HEAD/hw/3D_models/beegl_broker_variant_BC_almgsi_20x20x2_straight_bar_load_cell_top_dist_wo_holes.stl -------------------------------------------------------------------------------- /esp32/beegl_broker/include/Storage.h: -------------------------------------------------------------------------------- 1 | #ifndef Storage_h 2 | #define Storage_h 3 | #include 4 | #ifdef FILESYSTEM_SD 5 | #define FILESYSTEM SD 6 | #include 7 | #else 8 | #define FILESYSTEM SPIFFS 9 | #endif 10 | 11 | bool storage_setup(); 12 | 13 | #endif -------------------------------------------------------------------------------- /esp32/beegl_broker/customparts_no_ota.csv: -------------------------------------------------------------------------------- 1 | # Name, Type, SubType, Offset, Size, Flags 2 | nvs, data, nvs, 0x9000, 0x5000, 3 | otadata, data, ota, 0xe000, 0x2000, 4 | app0, app, ota_0, 0x10000, 0x200000, 5 | eeprom, data, 0x99, 0x210000,0x1000, 6 | spiffs, data, spiffs, 0x211000,0xEF000, -------------------------------------------------------------------------------- /esp32/beegl_broker/customparts.csv: -------------------------------------------------------------------------------- 1 | # Name, Type, SubType, Offset, Size, Flags 2 | nvs, data, nvs, 0x9000, 0x5000, 3 | otadata, data, ota, 0xe000, 0x2000, 4 | app0, app, ota_0, 0x10000, 0x300000, 5 | app1, app, ota_1, 0x310000, 0x300000, 6 | eeprom, data, 0x99, 0x610000,0x1000, 7 | spiffs, data, spiffs, 0x611000,0xEF000, 8 | -------------------------------------------------------------------------------- /esp32/beegl_broker/customparts_tight.csv: -------------------------------------------------------------------------------- 1 | # Name, Type, SubType, Offset, Size, Flags 2 | nvs, data, nvs, 0x9000, 0x5000, 3 | otadata, data, ota, 0xe000, 0x2000, 4 | app0, app, ota_0, 0x10000, 0x1B0000, 5 | app1, app, ota_1, 0x1C0000, 0x1B0000, 6 | eeprom, data, 0x99, 0x370000, 0x1000, 7 | spiffs, data, spiffs, 0x371000, 0x8F000, 8 | -------------------------------------------------------------------------------- /esp32/beegl_broker/post_build.py: -------------------------------------------------------------------------------- 1 | ry: 2 | import configparser 3 | except ImportError: 4 | import ConfigParser as configparser 5 | 6 | Import("env") 7 | print(env.Dump()) 8 | 9 | config = configparser.ConfigParser() 10 | config.read("platformio.ini") 11 | 12 | progname_prefix = config.get("common","progname_prefix") 13 | variant = env['PIOENV'] 14 | ver = config.get("common","ver") 15 | 16 | 17 | env.Replace(PROGNAME="%s%s_%s" % (progname_prefix, variant, ver)) -------------------------------------------------------------------------------- /esp32/beegl_broker/pre_build.py: -------------------------------------------------------------------------------- 1 | 2 | try: 3 | import configparser 4 | except ImportError: 5 | import ConfigParser as configparser 6 | 7 | Import("env") 8 | print(env.Dump()) 9 | 10 | config = configparser.ConfigParser() 11 | config.read("platformio.ini") 12 | 13 | progname_prefix = config.get("common","progname_prefix") 14 | variant = env['PIOENV'] 15 | ver = config.get("common","ver") 16 | 17 | 18 | env.Replace(PROGNAME="%s%s_%s" % (progname_prefix, variant, ver)) -------------------------------------------------------------------------------- /esp32/beegl_broker/src/Storage.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | bool storage_setup() 4 | { 5 | Serial.println("[SPIFFS] Begin SPIFFS"); 6 | if (!SPIFFS.begin(true)) 7 | { 8 | return false; 9 | Serial.println("[SPIFFS] An Error has occurred while mounting SPIFFS"); 10 | } 11 | #ifdef FILESYSTEM_SD 12 | Serial.println("[SD] Begin SD"); 13 | if (!SD.begin(4)) 14 | { 15 | Serial.println("[SD] An Error has occurred while mounting SD"); 16 | return false; 17 | } 18 | #endif 19 | return true; 20 | } -------------------------------------------------------------------------------- /esp32/beegl_broker/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 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 12 | 13 | **Describe the solution you'd like** 14 | A clear and concise description of what you want to happen. 15 | 16 | **Describe alternatives you've considered** 17 | A clear and concise description of any alternative solutions or features you've considered. 18 | 19 | **Additional context** 20 | Add any other context or screenshots about the feature request here. 21 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // Use IntelliSense to learn about possible attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | "version": "0.2.0", 6 | "configurations": [ 7 | 8 | { 9 | "type": "node", 10 | "request": "launch", 11 | "name": "Launch Program", 12 | "program": "${workspaceFolder}/node-red/beegl_server/beegl_migrate_to_mongo.js", 13 | "args": ["beegl_migrate_to_mongo.js", "flows_ip-172-31-46-91.json", "localhost:27017", "beegl", "beegl", "abc123"], 14 | "localRoot" : "${workspaceFolder}/node-red/beegl_server", 15 | "remoteRoot" : "${workspaceFolder}/node-red/beegl_server", 16 | "autoAttachChildProcesses": true 17 | } 18 | ] 19 | } -------------------------------------------------------------------------------- /esp32/beegl_broker/src/Service.cpp: -------------------------------------------------------------------------------- 1 | 2 | 3 | #include "Service.h" 4 | #include "Settings.h" 5 | class Settings; 6 | 7 | void notFound(AsyncWebServerRequest *request) 8 | { 9 | request->send(404, "text/plain", "Not found"); 10 | } 11 | 12 | Service::Service(Settings* settings) 13 | { 14 | m_settings = settings; 15 | m_webserver = new AsyncWebServer(80); 16 | m_webserver->onNotFound(notFound); 17 | 18 | } 19 | void Service::setup() { 20 | webServerSetup(); 21 | } 22 | 23 | 24 | 25 | 26 | AsyncWebServer* Service::getWebServer() { 27 | return m_webserver; 28 | } 29 | 30 | void Service::webServerSetup() 31 | { 32 | 33 | if(m_settings->inboundMode & 0x1 || m_settings->outboundMode & 0x1) { 34 | blog_i("[WEB Server] Starting web server"); 35 | m_webserver->begin(); 36 | blog_i("[WEB Server] Web server started"); 37 | } 38 | 39 | 40 | } 41 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of what the bug is. 12 | 13 | **To Reproduce** 14 | Steps to reproduce the behavior: 15 | 1. Go to '...' 16 | 2. Click on '....' 17 | 3. Scroll down to '....' 18 | 4. See error 19 | 20 | **Expected behavior** 21 | A clear and concise description of what you expected to happen. 22 | 23 | **Screenshots** 24 | If applicable, add screenshots to help explain your problem. 25 | 26 | **Desktop (please complete the following information):** 27 | - OS: [e.g. iOS] 28 | - Browser [e.g. chrome, safari] 29 | - Version [e.g. 22] 30 | 31 | **Smartphone (please complete the following information):** 32 | - Device: [e.g. iPhone6] 33 | - OS: [e.g. iOS8.1] 34 | - Browser [e.g. stock browser, safari] 35 | - Version [e.g. 22] 36 | 37 | **Additional context** 38 | Add any other context about the problem here. 39 | -------------------------------------------------------------------------------- /esp32/beegl_broker/include/LogManagement.h: -------------------------------------------------------------------------------- 1 | /* 2 | LogManagement.h - Log management interface 3 | 4 | This file is part of the BeeGl distribution (https://github.com/bsorgo/beegl). 5 | Copyright (c) 2019 Bostjan Sorgo 6 | 7 | This program is free software: you can redistribute it and/or modify 8 | it under the terms of the GNU General Public License as published by 9 | the Free Software Foundation, version 3. 10 | 11 | This program is distributed in the hope that it will be useful, but 12 | WITHOUT ANY WARRANTY; without even the implied warranty of 13 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 14 | General Public License for more details. 15 | 16 | You should have received a copy of the GNU General Public License 17 | along with this program. If not, see . 18 | 19 | */ 20 | 21 | #ifndef LogManagement_h 22 | #define LogManagement_h 23 | 24 | #include "Log.h" 25 | #include "Settings.h" 26 | #include "Service.h" 27 | 28 | class LogManagement 29 | { 30 | public: 31 | LogManagement(Settings* settings, Service* service); 32 | 33 | private: 34 | Settings *m_settings; 35 | Service* m_server; 36 | void webServerBind(); 37 | 38 | }; 39 | 40 | #endif -------------------------------------------------------------------------------- /esp32/beegl_broker/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 | -------------------------------------------------------------------------------- /esp32/beegl_broker/include/HttpPublisher.h: -------------------------------------------------------------------------------- 1 | /* 2 | Publisher.h - Http Publisher header file 3 | 4 | This file is part of the BeeGl distribution (https://github.com/bsorgo/beegl). 5 | Copyright (c) 2019 Bostjan Sorgo 6 | 7 | This program is free software: you can redistribute it and/or modify 8 | it under the terms of the GNU General Public License as published by 9 | the Free Software Foundation, version 3. 10 | 11 | This program is distributed in the hope that it will be useful, but 12 | WITHOUT ANY WARRANTY; without even the implied warranty of 13 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 14 | General Public License for more details. 15 | 16 | You should have received a copy of the GNU General Public License 17 | along with this program. If not, see . 18 | */ 19 | 20 | #ifndef HttpPublisher_h 21 | #define HttpPublisher_h 22 | 23 | 24 | #include "Publisher.h" 25 | 26 | class HttpPublisher : public Publisher 27 | { 28 | public: 29 | HttpPublisher(Runtime *runtime, Settings *settings, Connection *outboundConnection, Service *service); 30 | void setup(); 31 | 32 | protected: 33 | bool reconnect(); 34 | bool publishMessage(const char *message); 35 | }; 36 | 37 | #endif -------------------------------------------------------------------------------- /esp32/beegl_broker/include/Service.h: -------------------------------------------------------------------------------- 1 | /* 2 | Service.h - Service header file 3 | 4 | This file is part of the BeeGl distribution (https://github.com/bsorgo/beegl). 5 | Copyright (c) 2019 Bostjan Sorgo 6 | 7 | This program is free software: you can redistribute it and/or modify 8 | it under the terms of the GNU General Public License as published by 9 | the Free Software Foundation, version 3. 10 | 11 | This program is distributed in the hope that it will be useful, but 12 | WITHOUT ANY WARRANTY; without even the implied warranty of 13 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 14 | General Public License for more details. 15 | 16 | You should have received a copy of the GNU General Public License 17 | along with this program. If not, see . 18 | 19 | */ 20 | 21 | #ifndef Service_h 22 | #define Service_h 23 | 24 | #include "Log.h" 25 | #include 26 | #include 27 | 28 | class Service 29 | { 30 | 31 | public: 32 | Service(Settings* settings); 33 | AsyncWebServer *getWebServer(); 34 | void setup(); 35 | 36 | private: 37 | AsyncWebServer* m_webserver; 38 | void webServerSetup(); 39 | Settings* m_settings; 40 | }; 41 | 42 | #endif -------------------------------------------------------------------------------- /esp32/beegl_broker/include/Indicator.h: -------------------------------------------------------------------------------- 1 | /* 2 | Indicator.h - Indicator header file 3 | 4 | This file is part of the BeeGl distribution (https://github.com/bsorgo/beegl). 5 | Copyright (c) 2019 Bostjan Sorgo 6 | 7 | This program is free software: you can redistribute it and/or modify 8 | it under the terms of the GNU General Public License as published by 9 | the Free Software Foundation, version 3. 10 | 11 | This program is distributed in the hope that it will be useful, but 12 | WITHOUT ANY WARRANTY; without even the implied warranty of 13 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 14 | General Public License for more details. 15 | 16 | You should have received a copy of the GNU General Public License 17 | along with this program. If not, see . 18 | 19 | */ 20 | 21 | 22 | #ifndef Indicator_h 23 | #define Indicator_h 24 | 25 | #include "Log.h" 26 | #include 27 | 28 | class Indicator 29 | { 30 | 31 | public: 32 | Indicator(); 33 | void reportSuccess(int blinks); 34 | void reportFail(int blinks); 35 | void reportFail(); 36 | void reportSuccess(); 37 | private: 38 | const int RED_LED_PIN = 25; 39 | const int GREEN_LED_PIN = 26; 40 | }; 41 | 42 | #endif -------------------------------------------------------------------------------- /esp32/beegl_broker/include/MqttPublisher.h: -------------------------------------------------------------------------------- 1 | /* 2 | Publisher.h - Mqtt Publisher header file 3 | 4 | This file is part of the BeeGl distribution (https://github.com/bsorgo/beegl). 5 | Copyright (c) 2019 Bostjan Sorgo 6 | 7 | This program is free software: you can redistribute it and/or modify 8 | it under the terms of the GNU General Public License as published by 9 | the Free Software Foundation, version 3. 10 | 11 | This program is distributed in the hope that it will be useful, but 12 | WITHOUT ANY WARRANTY; without even the implied warranty of 13 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 14 | General Public License for more details. 15 | 16 | You should have received a copy of the GNU General Public License 17 | along with this program. If not, see . 18 | 19 | */ 20 | 21 | #ifndef MqttPublisher_h 22 | #define MqttPublisher_h 23 | 24 | 25 | #include "Publisher.h" 26 | #include 27 | 28 | 29 | class MqttPublisher : public Publisher 30 | { 31 | public: 32 | MqttPublisher(Runtime *runtime, Settings *settings, Connection *outboundConnection, Service *service); 33 | void setup(); 34 | 35 | private: 36 | PubSubClient *mqttClient; 37 | 38 | protected: 39 | bool reconnect(); 40 | bool publishMessage(const char *message); 41 | }; 42 | 43 | #endif -------------------------------------------------------------------------------- /esp32/beegl_broker/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 | -------------------------------------------------------------------------------- /esp32/beegl_broker/include/Connection.h: -------------------------------------------------------------------------------- 1 | #ifndef Connection_h 2 | #define Connection_h 3 | 4 | 5 | /* 6 | Connection.h - Connection header file 7 | 8 | This file is part of the BeeGl distribution (https://github.com/bsorgo/beegl). 9 | Copyright (c) 2019 Bostjan Sorgo 10 | 11 | This program is free software: you can redistribute it and/or modify 12 | it under the terms of the GNU General Public License as published by 13 | the Free Software Foundation, version 3. 14 | 15 | This program is distributed in the hope that it will be useful, but 16 | WITHOUT ANY WARRANTY; without even the implied warranty of 17 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 18 | General Public License for more details. 19 | 20 | You should have received a copy of the GNU General Public License 21 | along with this program. If not, see . 22 | 23 | */ 24 | 25 | #include "Log.h" 26 | #include "Settings.h" 27 | #include 28 | #include 29 | #include 30 | 31 | 32 | class Connection 33 | { 34 | 35 | public: 36 | Connection(Settings *settings); 37 | Client *getClient(); 38 | void checkConnect(); 39 | bool setup(); 40 | void shutdown(); 41 | TinyGsm * getModem(); 42 | void modemOff(); 43 | void suspend(); 44 | void resume(); 45 | 46 | private: 47 | const int MODEM_RX_PIN = 15; 48 | const int MODEM_TX_PIN = 14; 49 | 50 | Settings *m_settings; 51 | 52 | HardwareSerial *serialAT; 53 | TinyGsm *modem; 54 | TinyGsmClient *gsmClient; 55 | 56 | WiFiClient *wifiClient; 57 | 58 | void btOff(); 59 | 60 | void wifiOff(); 61 | 62 | bool gsmSetup(); 63 | bool gprsSetup(); 64 | bool wifiSetup(); 65 | }; 66 | #endif -------------------------------------------------------------------------------- /esp32/beegl_broker/include/Measurer.h: -------------------------------------------------------------------------------- 1 | /* 2 | Measurer.h - Measurer header file 3 | 4 | This file is part of the BeeGl distribution (https://github.com/bsorgo/beegl). 5 | Copyright (c) 2019 Bostjan Sorgo 6 | 7 | This program is free software: you can redistribute it and/or modify 8 | it under the terms of the GNU General Public License as published by 9 | the Free Software Foundation, version 3. 10 | 11 | This program is distributed in the hope that it will be useful, but 12 | WITHOUT ANY WARRANTY; without even the implied warranty of 13 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 14 | General Public License for more details. 15 | 16 | You should have received a copy of the GNU General Public License 17 | along with this program. If not, see . 18 | 19 | */ 20 | 21 | #ifndef Measurer_h 22 | #define Measurer_h 23 | 24 | #include "Log.h" 25 | #include "DHTesp.h" 26 | #include 27 | #include "Service.h" 28 | #include "Settings.h" 29 | #include "Publisher.h" 30 | 31 | struct MeasureData 32 | { 33 | float weight; 34 | float temp; 35 | float humidity; 36 | }; 37 | 38 | class Measurer 39 | { 40 | 41 | 42 | public: 43 | Measurer(Runtime* runtime, Service* server, Settings *settings, Publisher *publisher); 44 | bool setup(); 45 | long zero(); 46 | char* measure(); 47 | 48 | private: 49 | HX711 *m_scale; 50 | Settings *m_settings; 51 | Publisher *m_publisher; 52 | Runtime *m_runtime; 53 | DHTesp *m_dht; 54 | const char SCALE_DOUT_PIN = 32; 55 | const char SCALE_SCK_PIN = 33; 56 | const int DHT_PIN = 13; 57 | bool scaleSetup(); 58 | bool dhtSetup(); 59 | char *storeMessage(MeasureData measureData); 60 | void webServerBind(); 61 | Service* m_server; 62 | }; 63 | 64 | #endif -------------------------------------------------------------------------------- /esp32/beegl_broker/include/Runtime.h: -------------------------------------------------------------------------------- 1 | /* 2 | Runtime.h - Runtime header file 3 | 4 | This file is part of the BeeGl distribution (https://github.com/bsorgo/beegl). 5 | Copyright (c) 2019 Bostjan Sorgo 6 | 7 | This program is free software: you can redistribute it and/or modify 8 | it under the terms of the GNU General Public License as published by 9 | the Free Software Foundation, version 3. 10 | 11 | This program is distributed in the hope that it will be useful, but 12 | WITHOUT ANY WARRANTY; without even the implied warranty of 13 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 14 | General Public License for more details. 15 | 16 | You should have received a copy of the GNU General Public License 17 | along with this program. If not, see . 18 | 19 | */ 20 | 21 | #ifndef Runtime_h 22 | #define Runtime_h 23 | 24 | #include "Log.h" 25 | #include "Settings.h" 26 | #include "Connection.h" 27 | #include "Service.h" 28 | 29 | #include 30 | 31 | 32 | class Runtime 33 | { 34 | 35 | public: 36 | Runtime(Service* server, Settings* settings, Connection* connection); 37 | void checkOperationalTime(); 38 | void deepSleep(uint32_t timeToSleep); 39 | void deepSleep(); 40 | void printWakeupReason(); 41 | int8_t getSafeModeOnRestart(); 42 | void setSafeModeOnRestart(int8_t safeModeOnRestart); 43 | int8_t getSafeMode(); 44 | void setSafeMode(int8_t value); 45 | void initialize(); 46 | #ifdef VER 47 | const char* FIRMWAREVERSION = VER; 48 | #else 49 | const char* FIRMWAREVERSION = "1.5.0"; 50 | #endif 51 | private: 52 | Settings *m_settings; 53 | Connection *m_connection; 54 | Service* m_server; 55 | int8_t m_safeMode; 56 | void webServerBind(); 57 | 58 | }; 59 | 60 | #endif -------------------------------------------------------------------------------- /esp32/beegl_broker/include/Updater.h: -------------------------------------------------------------------------------- 1 | /* 2 | Updater.h - Updater header file 3 | 4 | This file is part of the BeeGl distribution (https://github.com/bsorgo/beegl). 5 | Copyright (c) 2019 Bostjan Sorgo 6 | 7 | This program is free software: you can redistribute it and/or modify 8 | it under the terms of the GNU General Public License as published by 9 | the Free Software Foundation, version 3. 10 | 11 | This program is distributed in the hope that it will be useful, but 12 | WITHOUT ANY WARRANTY; without even the implied warranty of 13 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 14 | General Public License for more details. 15 | 16 | You should have received a copy of the GNU General Public License 17 | along with this program. If not, see . 18 | 19 | */ 20 | 21 | 22 | #ifndef Updater_h 23 | #define Updater_h 24 | 25 | #include "Log.h" 26 | #include "Settings.h" 27 | #include "Connection.h" 28 | #include "Service.h" 29 | #include "Runtime.h" 30 | 31 | #include 32 | #include 33 | 34 | #define STORAGE_SIZE 30 35 | #define INDEXHTML "index.html" 36 | #ifndef SYSTEM_VARIANT 37 | #define SYSTEM_VARIANT "" 38 | #endif; 39 | class Updater 40 | { 41 | 42 | public: 43 | Updater(Runtime *runtime, Service *service, Settings *settings, Connection *connection); 44 | void checkFirmware(); 45 | bool checkDownloadFile(const char* filename); 46 | private: 47 | Connection *m_connection; 48 | Settings *m_settings; 49 | Service *m_server; 50 | Runtime *m_runtime; 51 | const char* m_currentVersion; 52 | void webServerBind(); 53 | void downloadFirmware(HttpClient *httpClient, char *filePath); 54 | String getLocalFileMd5(const char* filename); 55 | String getServerFileMd5(const char* filename); 56 | bool downloadFile(const char* hostname, const char *path, const char* filename); 57 | bool downloadFile(const char *filename); 58 | }; 59 | 60 | #endif -------------------------------------------------------------------------------- /esp32/beegl_broker/src/Indicator.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | Indicator.cpp - Provides (so far) LED indicator for success/failed flows. 3 | 4 | This file is part of the BeeGl distribution (https://github.com/bsorgo/beegl). 5 | Copyright (c) 2019 Bostjan Sorgo 6 | 7 | This program is free software: you can redistribute it and/or modify 8 | it under the terms of the GNU General Public License as published by 9 | the Free Software Foundation, version 3. 10 | 11 | This program is distributed in the hope that it will be useful, but 12 | WITHOUT ANY WARRANTY; without even the implied warranty of 13 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 14 | General Public License for more details. 15 | 16 | You should have received a copy of the GNU General Public License 17 | along with this program. If not, see . 18 | */ 19 | 20 | 21 | #include "Indicator.h" 22 | 23 | 24 | #define BLING_PAUSE 80 25 | Indicator::Indicator() 26 | { 27 | pinMode(GREEN_LED_PIN, OUTPUT); 28 | pinMode(RED_LED_PIN, OUTPUT); 29 | digitalWrite(RED_LED_PIN, LOW); 30 | digitalWrite(GREEN_LED_PIN, HIGH); 31 | } 32 | 33 | void Indicator::reportSuccess(int blinks) 34 | { 35 | digitalWrite(RED_LED_PIN, LOW); 36 | for (int i = 0; i < blinks; i++) 37 | { 38 | digitalWrite(GREEN_LED_PIN, HIGH); 39 | delay(BLING_PAUSE); 40 | digitalWrite(GREEN_LED_PIN, LOW); 41 | delay(BLING_PAUSE); 42 | } 43 | } 44 | 45 | void Indicator::reportFail(int blinks) 46 | { 47 | digitalWrite(RED_LED_PIN, LOW); 48 | for (int i = 0; i < blinks; i++) 49 | { 50 | digitalWrite(RED_LED_PIN, HIGH); 51 | delay(BLING_PAUSE); 52 | digitalWrite(RED_LED_PIN, LOW); 53 | delay(BLING_PAUSE); 54 | } 55 | } 56 | 57 | void Indicator::reportFail() 58 | { 59 | digitalWrite(RED_LED_PIN, HIGH); 60 | digitalWrite(GREEN_LED_PIN, LOW); 61 | } 62 | void Indicator::reportSuccess() 63 | { 64 | digitalWrite(RED_LED_PIN, LOW); 65 | digitalWrite(GREEN_LED_PIN, HIGH); 66 | } -------------------------------------------------------------------------------- /esp32/beegl_broker/include/Publisher.h: -------------------------------------------------------------------------------- 1 | /* 2 | Publisher.h - Publisher header file 3 | 4 | This file is part of the BeeGl distribution (https://github.com/bsorgo/beegl). 5 | Copyright (c) 2019 Bostjan Sorgo 6 | 7 | This program is free software: you can redistribute it and/or modify 8 | it under the terms of the GNU General Public License as published by 9 | the Free Software Foundation, version 3. 10 | 11 | This program is distributed in the hope that it will be useful, but 12 | WITHOUT ANY WARRANTY; without even the implied warranty of 13 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 14 | General Public License for more details. 15 | 16 | You should have received a copy of the GNU General Public License 17 | along with this program. If not, see . 18 | 19 | */ 20 | 21 | #ifndef Publisher_h 22 | #define Publisher_h 23 | 24 | 25 | #include "Settings.h" 26 | #include "Connection.h" 27 | #include "Runtime.h" 28 | #include "Service.h" 29 | 30 | #include 31 | 32 | #define STORAGE_SIZE 30 33 | #define BACKLOG_NVS "backlog" 34 | #define BACKLOG_DIR "/backlog" 35 | #define BACKLOG_DIR_PREFIX BACKLOG_DIR "/" 36 | #define BACKLOG_EXTENSION ".json" 37 | #ifndef MAX_BACKLOG 38 | #define MAX_BACKLOG 200 39 | #endif 40 | 41 | class Publisher 42 | { 43 | 44 | public: 45 | Publisher(Runtime *runtime, Settings *settings, Connection *outboundConnection, Service *service); 46 | void setSettings(Settings *settings); 47 | virtual void setup(); 48 | bool publish(); 49 | char *storeMessage(JsonObject &jsonObj); 50 | 51 | private: 52 | int32_t backlogCount; 53 | void webServerBind(); 54 | 55 | protected: 56 | Connection *m_connection; 57 | Settings *m_settings; 58 | Runtime *m_runtime; 59 | Service *m_service; 60 | char messageStorage[STORAGE_SIZE][350]; 61 | int storageIndex = -1; 62 | int publishIndex = -1; 63 | 64 | int getIndex(); 65 | virtual bool reconnect(); 66 | virtual bool publishMessage(const char *message); 67 | 68 | }; 69 | #endif -------------------------------------------------------------------------------- /esp32/beegl_broker/src/LogManagement.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | LogManagmeent.cpp - log file management: 3 | 4 | - Provide log content over web server 5 | - Provide log files list 6 | 7 | This file is part of the BeeGl distribution (https://github.com/bsorgo/beegl). 8 | Copyright (c) 2019 Bostjan Sorgo 9 | 10 | This program is free software: you can redistribute it and/or modify 11 | it under the terms of the GNU General Public License as published by 12 | the Free Software Foundation, version 3. 13 | 14 | This program is distributed in the hope that it will be useful, but 15 | WITHOUT ANY WARRANTY; without even the implied warranty of 16 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 17 | General Public License for more details. 18 | 19 | You should have received a copy of the GNU General Public License 20 | along with this program. If not, see . 21 | 22 | */ 23 | 24 | 25 | #include "LogManagement.h" 26 | 27 | LogManagement::LogManagement(Settings *settings, Service *service) 28 | { 29 | m_settings = settings; 30 | m_server = service; 31 | webServerBind(); 32 | } 33 | 34 | void LogManagement::webServerBind() 35 | { 36 | m_server->getWebServer()->serveStatic("/log/", FILESYSTEM, LOG_DIR_PREFIX); 37 | 38 | m_server->getWebServer()->on("/rest/logs", HTTP_GET, [](AsyncWebServerRequest *request) { 39 | AsyncResponseStream *response = request->beginResponseStream("application/json"); 40 | StaticJsonBuffer<1024> jsonBuffer; 41 | JsonObject &root = jsonBuffer.createObject(); 42 | JsonArray &logs = root.createNestedArray("logs"); 43 | 44 | long logNumber = log_number(); 45 | for(int i=logNumber;i>logNumber-MAX_LOG_FILES && i>0;i--) 46 | { 47 | 48 | String filename = String(LOG_DIR_PREFIX); 49 | filename+=i; 50 | filename+=LOG_EXTENSION; 51 | log_d("[LOG MANAGEMENT] Filename: %s", filename.c_str()); 52 | if(FILESYSTEM.exists(filename)) 53 | { 54 | JsonObject &log = logs.createNestedObject(); 55 | log["filename"] = filename; 56 | } 57 | } 58 | root.printTo(*response); 59 | jsonBuffer.clear(); 60 | request->send(response); 61 | }); 62 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # BeeGl 2 | 3 | Beehive monitoring system based on _ESP32 based boards_ and _Node Red_. Main system application is beehive monitoring but can be used also as weather station. 4 | 5 | # Main features 6 | 7 | - Measures weight, temperature and humidity 8 | - Outbound communication through WiFi and GPRS (2G) 9 | - Inbound communication using WiFi and Bluetooth (BLE) 10 | - Web based management API - Web GUI and rest 11 | - Pull/push OTA and setting updates 12 | - Node operational time frame rules and power management 13 | - Forms a distributed system where source and broker nodes can be linked together 14 | - Basic server capable of displaying realtime data and statistics 15 | 16 | 17 | 18 | If you like **BeeGl**, give it a star, or fork it and contribute! If you find this system useful please [![Donate](https://img.shields.io/badge/Donate-PayPal-green.svg)](https://www.paypal.com/cgi-bin/webscr?cmd=_donations&business=bostjan.sorgo%40gmail.com¤cy_code=EUR&source=url) 19 | 20 | ## Quick Install 21 | 22 | - Broker Device - [README.md](esp32/beegl_broker/README.md) 23 | - Server [README.md](https://github.com/bsorgo/beegl_server) 24 | 25 | ## Libraries Used 26 | 27 | ### Broker component 28 | 29 | - [ArduinoHTTPClient](https://github.com/arduino-libraries/ArduinoHttpClient) - forked (https://github.com/bsorgo/ArduinoHttpClient) - pending fixes for ESP32 environment 30 | - [ArduinoJson](https://github.com/bblanchon/ArduinoJson) 31 | - [AsyncTcp](https://github.com/me-no-dev/AsyncTCP) 32 | - [DHT sensor library for ESPx](https://github.com/beegee-tokyo/DHTesp) 33 | - [ESP Async WebServer](https://github.com/me-no-dev/ESPAsyncWebServer) 34 | - [HX711](https://github.com/bogde/HX711) 35 | - [PubSubClient](https://github.com/knolleary/pubsubclient) 36 | - [Time](https://github.com/PaulStoffregen/Time) - forked (https://github.com/bsorgo/Time) 37 | - [Timezone](https://github.com/JChristensen/Timezone) 38 | - [TinyGSM](https://github.com/vshymanskyy) 39 | 40 | ### Server components 41 | 42 | 43 | - [Mosquitto](https://mosquitto.org/) 44 | - [Node Red](https://nodered.org/) 45 | - [Node Red Dashboard](https://flows.nodered.org/node/node-red-dashboard) 46 | 47 | ## License 48 | 49 | This program is licensed under GPL-3.0 50 | -------------------------------------------------------------------------------- /esp32/beegl_broker/include/SettingsManagement.h: -------------------------------------------------------------------------------- 1 | /* 2 | Service.h - Service header file 3 | 4 | This file is part of the BeeGl distribution (https://github.com/bsorgo/beegl). 5 | Copyright (c) 2019 Bostjan Sorgo 6 | 7 | This program is free software: you can redistribute it and/or modify 8 | it under the terms of the GNU General Public License as published by 9 | the Free Software Foundation, version 3. 10 | 11 | This program is distributed in the hope that it will be useful, but 12 | WITHOUT ANY WARRANTY; without even the implied warranty of 13 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 14 | General Public License for more details. 15 | 16 | You should have received a copy of the GNU General Public License 17 | along with this program. If not, see . 18 | 19 | */ 20 | 21 | #ifndef SettingsManagement_h 22 | #define SettingsManagement_h 23 | 24 | #define CONTENTTYPEJSON "application/json" 25 | #define CONTENTTYPE "Content-Type" 26 | #define CONTENTLENGTH "Content-Length" 27 | 28 | #include "Log.h" 29 | #include 30 | #include 31 | #include "Settings.h" 32 | #include "Connection.h" 33 | #include "Service.h" 34 | #include "Runtime.h" 35 | #include 36 | 37 | class SettingsManagement 38 | { 39 | 40 | public: 41 | SettingsManagement(Settings *settings, Connection *connection, Service *service, Runtime *runtime); 42 | void setup(); 43 | void readFromFilesystem(char *path); 44 | bool writeConfig(JsonObject &input); 45 | bool writeConfig(); 46 | bool readConfig(); 47 | bool writeSettingsToServer(); 48 | void syncTimeAndSettings(); 49 | void storeLastGood(); 50 | 51 | private: 52 | Settings *m_settings; 53 | Service *m_server; 54 | Connection *m_connection; 55 | Runtime *m_runtime; 56 | bool copyFile(const char *source, const char *destination); 57 | void webServerBind(); 58 | bool readTimeAndSettings(HttpClient *httpClient, char *path); 59 | void merge(JsonObject &dest, JsonObject &src); 60 | bool writeSettings(HttpClient *httpClient, char *path, char *username, char *password); 61 | bool writeConfigToFS(const char *filename, JsonObject &root); 62 | int getMonthFromString(char *s); 63 | String getLocalFileMd5(const char *filename); 64 | bool readAndParseJson(const char *filename, JsonObject **root, StaticJsonBuffer *jsonBuffer); 65 | }; 66 | 67 | #endif -------------------------------------------------------------------------------- /esp32/beegl_broker/include/Broker.h: -------------------------------------------------------------------------------- 1 | /* 2 | Broker.h - Broker header file 3 | 4 | This file is part of the BeeGl distribution (https://github.com/bsorgo/beegl). 5 | Copyright (c) 2019 Bostjan Sorgo 6 | 7 | This program is free software: you can redistribute it and/or modify 8 | it under the terms of the GNU General Public License as published by 9 | the Free Software Foundation, version 3. 10 | 11 | This program is distributed in the hope that it will be useful, but 12 | WITHOUT ANY WARRANTY; without even the implied warranty of 13 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 14 | General Public License for more details. 15 | 16 | You should have received a copy of the GNU General Public License 17 | along with this program. If not, see . 18 | */ 19 | 20 | 21 | #ifndef Broker_h 22 | #define Broker_h 23 | 24 | #define SERVICE_UUID "ebd31aa0-b7c7-497a-a92c-e18f78f64efa" 25 | #define CHARACTERISTIC_UUID "df878320-0c82-45c9-a5e2-47ff1ee43883" 26 | 27 | #include "Log.h" 28 | #include "Service.h" 29 | #include "Publisher.h" 30 | #include 31 | #include 32 | #include 33 | 34 | #include 35 | 36 | typedef char* (*jsonFunctionPtr)(JsonObject &jsonObj); 37 | 38 | class Broker { 39 | 40 | class BLESensorCallback : public BLECharacteristicCallbacks 41 | { 42 | 43 | private: 44 | Broker* m_broker; 45 | public: 46 | BLESensorCallback(Broker* broker) 47 | { 48 | m_broker = broker; 49 | } 50 | void onWrite(BLECharacteristic *pCharacteristic) 51 | { 52 | std::string value = pCharacteristic->getValue(); 53 | if (value.length() > 0) 54 | { 55 | StaticJsonBuffer<512> jsonBuffer; 56 | JsonObject &jsonObj = jsonBuffer.parseObject(value.c_str()); 57 | if (!jsonObj.success()) 58 | { 59 | log_e( "[BLE] parseObject() failed"); 60 | } 61 | else 62 | { 63 | if (m_broker) 64 | { 65 | m_broker->storeMessage(jsonObj); 66 | } 67 | } 68 | } 69 | } 70 | }; 71 | 72 | public: 73 | Broker(Service* server,Settings *settings, Publisher* publisher); 74 | void setSettings(Settings *settings); 75 | void setPublisher(Publisher* publisher); 76 | 77 | void setup(); 78 | char* storeMessage(JsonObject &jsonObj); 79 | private: 80 | Service* m_server; 81 | Settings* m_settings; 82 | Publisher* m_publisher; 83 | BLESensorCallback* m_bleCallback; 84 | 85 | BLEServer *pServer; 86 | BLECharacteristic *pCharacteristic; 87 | 88 | AsyncCallbackJsonWebHandler *sensorsHandler; 89 | 90 | bool bleBind(); 91 | void webServerBind(); 92 | 93 | 94 | }; 95 | 96 | #endif 97 | -------------------------------------------------------------------------------- /esp32/beegl_broker/src/HttpPublisher.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | HttpPublisher.cpp - Provides Temporary storage and publishes messages to central server using Http client 3 | 4 | This file is part of the BeeGl distribution (https://github.com/bsorgo/beegl). 5 | Copyright (c) 2019 Bostjan Sorgo 6 | 7 | This program is free software: you can redistribute it and/or modify 8 | it under the terms of the GNU General Public License as published by 9 | the Free Software Foundation, version 3. 10 | 11 | This program is distributed in the hope that it will be useful, but 12 | WITHOUT ANY WARRANTY; without even the implied warranty of 13 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 14 | General Public License for more details. 15 | 16 | You should have received a copy of the GNU General Public License 17 | along with this program. If not, see . 18 | 19 | */ 20 | 21 | #include "HttpPublisher.h" 22 | 23 | #define MESSAGE "{test:\"test\"}" 24 | #define CONTENTTYPEJSON "application/json" 25 | #define CONTENTTYPE "Content-Type" 26 | #define CONTENTLENGTH "Content-Length" 27 | 28 | void HttpPublisher::setup() 29 | { 30 | } 31 | 32 | HttpPublisher::HttpPublisher(Runtime *runtime, Settings *settings, Connection *connection, Service *service) : Publisher(runtime, settings, connection, service) 33 | { 34 | } 35 | 36 | bool HttpPublisher::publishMessage(const char *message) 37 | { 38 | 39 | char *hostname = m_settings->getSettingsHostname(); 40 | char *path = m_settings->getSensorPublishPath(); 41 | 42 | blog_d("[HTTPPUBLISHER] Hostname: %s", hostname); 43 | blog_d("[HTTPPUBLISHER] Path: %s", path); 44 | blog_d("[HTTPPUBLISHER] Username: %s, password: %s", m_settings->httpTimeAndSettingUsername, m_settings->httpTimeAndSettingPassword); 45 | HttpClient httpClient = HttpClient(*m_connection->getClient(), hostname, 80); 46 | httpClient.connectionKeepAlive(); 47 | httpClient.setHttpResponseTimeout(8000); 48 | httpClient.beginRequest(); 49 | httpClient.post(path); 50 | httpClient.sendBasicAuth(m_settings->httpTimeAndSettingUsername, m_settings->httpTimeAndSettingPassword); 51 | httpClient.sendHeader(CONTENTLENGTH, strlen(message)); 52 | httpClient.sendHeader(CONTENTTYPE, CONTENTTYPEJSON); 53 | httpClient.beginBody(); 54 | httpClient.print(message); 55 | httpClient.endRequest(); 56 | int responseCode = httpClient.responseStatusCode(); 57 | httpClient.responseBody(); 58 | httpClient.stop(); 59 | free(hostname); 60 | free(path); 61 | if (responseCode == 200) 62 | { 63 | return true; 64 | } 65 | else 66 | { 67 | blog_e("[HTTPPUBLISHER] Error. Response code from server: %u", responseCode); 68 | return false; 69 | } 70 | } 71 | 72 | bool HttpPublisher::reconnect() 73 | { 74 | delay(5000); 75 | return true; 76 | } -------------------------------------------------------------------------------- /esp32/beegl_broker/src/MqttPublisher.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | Publisher.cpp - Provides Temporary storage and publishes messages to central server over Mqtt 3 | 4 | This file is part of the BeeGl distribution (https://github.com/bsorgo/beegl). 5 | Copyright (c) 2019 Bostjan Sorgo 6 | 7 | This program is free software: you can redistribute it and/or modify 8 | it under the terms of the GNU General Public License as published by 9 | the Free Software Foundation, version 3. 10 | 11 | This program is distributed in the hope that it will be useful, but 12 | WITHOUT ANY WARRANTY; without even the implied warranty of 13 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 14 | General Public License for more details. 15 | 16 | You should have received a copy of the GNU General Public License 17 | along with this program. If not, see . 18 | 19 | */ 20 | 21 | #include "MqttPublisher.h" 22 | 23 | void MqttPublisher::setup() 24 | { 25 | blog_i("[MQTT] Setup"); 26 | mqttClient->setClient(*m_connection->getClient()); 27 | blog_i("[MQTT] Server: mqttServer "); 28 | blog_i("[MQTT] Server: %s, port %u",m_settings->mqttServer, m_settings->mqttPort); 29 | mqttClient->setServer(m_settings->mqttServer, m_settings->mqttPort); 30 | } 31 | 32 | MqttPublisher::MqttPublisher(Runtime * runtime, Settings *settings, Connection *connection, Service *service) : Publisher(runtime, settings, connection, service) 33 | { 34 | mqttClient = new PubSubClient(*m_connection->getClient()); 35 | } 36 | 37 | bool MqttPublisher::publishMessage(const char *message) 38 | { 39 | 40 | blog_d( "[MQTTPUBLISHER] %s", m_settings->sensorTopic); 41 | 42 | if (mqttClient->publish(m_settings->sensorTopic, message)) 43 | { 44 | blog_d( "[MQTTPUBLISHER] Publish OK"); 45 | return true; 46 | } 47 | else 48 | { 49 | blog_e( "[MQTTPUBLISHER] Publish NOK"); 50 | return false; 51 | } 52 | } 53 | 54 | 55 | bool MqttPublisher::reconnect() 56 | { 57 | if (!mqttClient->connected()) 58 | { 59 | String clientName = ""; 60 | clientName += m_settings->deviceName; 61 | clientName += "-"; 62 | clientName += String(micros() & 0xff, 16); 63 | // Attempt to connect 64 | blog_d( "[MQTTPUBLISHER] Client: %s, username: %s ", clientName.c_str(), m_settings->mqttUsername); 65 | if (mqttClient->connect((char *)clientName.c_str(), m_settings->mqttUsername, m_settings->mqttPassword)) 66 | { 67 | blog_d( "[MQTTPUBLISHER] Connected. "); 68 | return true; 69 | } 70 | else 71 | { 72 | blog_e( "[MQTTPUBLISHER] failed, rc=%u, try again in 3 seconds", mqttClient->state()); 73 | // Wait 5 seconds before retrying 74 | delay(3000); 75 | return false; 76 | } 77 | } 78 | 79 | return true; 80 | } -------------------------------------------------------------------------------- /esp32/beegl_broker/include/Log.h: -------------------------------------------------------------------------------- 1 | /* 2 | Log.h - Log interface 3 | 4 | This file is part of the BeeGl distribution (https://github.com/bsorgo/beegl). 5 | Copyright (c) 2019 Bostjan Sorgo 6 | 7 | This program is free software: you can redistribute it and/or modify 8 | it under the terms of the GNU General Public License as published by 9 | the Free Software Foundation, version 3. 10 | 11 | This program is distributed in the hope that it will be useful, but 12 | WITHOUT ANY WARRANTY; without even the implied warranty of 13 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 14 | General Public License for more details. 15 | 16 | You should have received a copy of the GNU General Public License 17 | along with this program. If not, see . 18 | 19 | */ 20 | 21 | #ifndef Log_h 22 | #define Log_h 23 | 24 | 25 | #include 26 | 27 | #include 28 | 29 | #include 30 | 31 | #include 32 | #include 33 | 34 | #include "Storage.h" 35 | 36 | 37 | #ifndef MAX_LOG_FILES 38 | #define MAX_LOG_FILES 10 39 | #endif 40 | #ifndef MAX_LOG_FILE_SIZE 41 | #define MAX_LOG_FILE_SIZE 4096 42 | #endif 43 | 44 | #define LOG_DIR "/log" 45 | #define LOG_DIR_PREFIX LOG_DIR "/" 46 | #define LOG_EXTENSION ".log" 47 | #define LAST_LOG_NVS "last_log" 48 | 49 | int fs_log_printf(const char *fmt, ...); 50 | long log_number() ; 51 | #define BEEGL_LONG_LOG_FORMAT(letter, format) "[" #letter "][%s:%u] %s(): " format "\r\n", pathToFileName(__FILE__), __LINE__, __FUNCTION__ 52 | #define BEEGL_SHORT_LOG_FORMAT(letter, format) "[" #letter "]" format "\r\n" 53 | 54 | #if ARDUHAL_LOG_LEVEL >= ARDUHAL_LOG_LEVEL_VERBOSE 55 | #define blog_v(format, ...) do {\ 56 | log_printf(ARDUHAL_LOG_FORMAT(V, format), ##__VA_ARGS__);\ 57 | fs_log_printf(BEEGL_SHORT_LOG_FORMAT(V,format), ##__VA_ARGS__);\ 58 | } while (0) 59 | 60 | #else 61 | #define blog_v(format, ...) 62 | #endif 63 | 64 | #if ARDUHAL_LOG_LEVEL >= ARDUHAL_LOG_LEVEL_DEBUG 65 | #define blog_d(format, ...) do {\ 66 | log_printf(ARDUHAL_LOG_FORMAT(D, format), ##__VA_ARGS__);\ 67 | fs_log_printf(BEEGL_SHORT_LOG_FORMAT(D,format), ##__VA_ARGS__);\ 68 | } while (0) 69 | #else 70 | #define blog_d(format, ...) 71 | #endif 72 | 73 | #if ARDUHAL_LOG_LEVEL >= ARDUHAL_LOG_LEVEL_INFO 74 | #define blog_i(format, ...) do {\ 75 | log_printf(ARDUHAL_LOG_FORMAT(I, format), ##__VA_ARGS__);\ 76 | fs_log_printf(BEEGL_SHORT_LOG_FORMAT(I,format), ##__VA_ARGS__);\ 77 | } while (0) 78 | #else 79 | #define blog_i(format, ...) 80 | #endif 81 | 82 | #if ARDUHAL_LOG_LEVEL >= ARDUHAL_LOG_LEVEL_WARN 83 | #define blog_w(format, ...) do {\ 84 | log_printf(ARDUHAL_LOG_FORMAT(W, format), ##__VA_ARGS__);\ 85 | fs_log_printf(BEEGL_SHORT_LOG_FORMAT(W,format), ##__VA_ARGS__);\ 86 | } while (0) 87 | #else 88 | #define blog_w(format, ...) 89 | #endif 90 | 91 | #if ARDUHAL_LOG_LEVEL >= ARDUHAL_LOG_LEVEL_ERROR 92 | #define blog_e(format, ...) do {\ 93 | log_printf(ARDUHAL_LOG_FORMAT(E, format), ##__VA_ARGS__);\ 94 | fs_log_printf(BEEGL_SHORT_LOG_FORMAT(E,format), ##__VA_ARGS__);\ 95 | } while (0) 96 | #else 97 | #define blog_e(format, ...) 98 | #endif 99 | 100 | #if ARDUHAL_LOG_LEVEL >= ARDUHAL_LOG_LEVEL_NONE 101 | #define blog_n(format, ...) log_printf(ARDUHAL_LOG_FORMAT(E, format), ##__VA_ARGS__) 102 | #else 103 | #define blog_n(format, ...) 104 | #endif 105 | 106 | #endif -------------------------------------------------------------------------------- /esp32/beegl_broker/src/Broker.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | Broker.cpp - 3 | Broker functionality using inbound wifi and/or BLE bluetooth 4 | 5 | This file is part of the BeeGl distribution (https://github.com/bsorgo/beegl). 6 | Copyright (c) 2019 Bostjan Sorgo 7 | 8 | This program is free software: you can redistribute it and/or modify 9 | it under the terms of the GNU General Public License as published by 10 | the Free Software Foundation, version 3. 11 | 12 | This program is distributed in the hope that it will be useful, but 13 | WITHOUT ANY WARRANTY; without even the implied warranty of 14 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 15 | General Public License for more details. 16 | 17 | You should have received a copy of the GNU General Public License 18 | along with this program. If not, see . 19 | 20 | */ 21 | 22 | #include "Broker.h" 23 | 24 | Broker::Broker(Service *server, Settings *settings, Publisher *publisher) 25 | { 26 | m_server = server; 27 | m_settings = settings; 28 | m_publisher = publisher; 29 | m_bleCallback = new BLESensorCallback(this); 30 | } 31 | 32 | void Broker::webServerBind() 33 | { 34 | if (m_settings->inboundMode & 0x1) 35 | { 36 | sensorsHandler = new AsyncCallbackJsonWebHandler("/beegl/v1/measurements", [&](AsyncWebServerRequest *request, JsonVariant &json) { 37 | JsonObject &jsonObj = json.as(); 38 | jsonObj[STR_TIME] = m_settings->getDateTimeString(now()); 39 | m_publisher->storeMessage(jsonObj); 40 | request->send(200, "text/plain", ""); 41 | }); 42 | m_server->getWebServer()->addHandler(sensorsHandler); 43 | } 44 | } 45 | 46 | void Broker::setSettings(Settings *settings) 47 | { 48 | m_settings = settings; 49 | } 50 | void Broker::setPublisher(Publisher *publisher) 51 | { 52 | m_publisher = publisher; 53 | } 54 | 55 | void Broker::setup() 56 | { 57 | webServerBind(); 58 | bleBind(); 59 | } 60 | 61 | bool Broker::bleBind() 62 | { 63 | if (m_settings->inboundMode & 0x2) 64 | { 65 | blog_i( "[BLE] Configuring BLE Server %s ", m_settings->deviceName); 66 | blog_i( "[BLE] Service %s ", SERVICE_UUID); 67 | blog_i( "[BLE] Characteristic %s ", CHARACTERISTIC_UUID); 68 | 69 | if (esp_ble_tx_power_set(ESP_BLE_PWR_TYPE_ADV, ESP_PWR_LVL_P4) == OK) 70 | { 71 | blog_i( "[BLE] Transmission power changed\n"); 72 | } 73 | BLEDevice::init(m_settings->deviceName); 74 | BLEServer *pServer = BLEDevice::createServer(); 75 | 76 | BLEService *pService = pServer->createService(SERVICE_UUID); 77 | 78 | BLECharacteristic *pCharacteristic = pService->createCharacteristic( 79 | CHARACTERISTIC_UUID, 80 | BLECharacteristic::PROPERTY_READ | 81 | BLECharacteristic::PROPERTY_WRITE | 82 | BLECharacteristic::PROPERTY_INDICATE); 83 | 84 | ; 85 | 86 | pCharacteristic->setCallbacks(m_bleCallback); 87 | pService->start(); 88 | BLEAdvertising *pAdvertising = pServer->getAdvertising(); 89 | pAdvertising->addServiceUUID(SERVICE_UUID); 90 | pAdvertising->setScanResponse(true); 91 | pAdvertising->setMinPreferred(0x06); // functions that help with iPhone connections issue 92 | pAdvertising->setMinPreferred(0x12); 93 | pAdvertising->start(); 94 | } 95 | return true; 96 | } 97 | 98 | char *Broker::storeMessage(JsonObject &jsonObj) 99 | { 100 | return m_publisher->storeMessage(jsonObj); 101 | } 102 | -------------------------------------------------------------------------------- /esp32/beegl_broker/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 | [common] 11 | lib_deps = 12 | https://github.com/bsorgo/ArduinoHttpClient 13 | https://github.com/bsorgo/Time 14 | https://github.com/bogde/HX711 15 | ArduinoJson@<6.0.0 16 | DHT sensor library for ESPx@1.0.11 17 | ESP Async WebServer@1.2.2 18 | PubSubClient@2.7 19 | Timezone@1.2.2 20 | TinyGSM@0.7.9 21 | ArduinoNvs@2.5 22 | progname_prefix = beegl_broker_ 23 | ver = 1.1.1 24 | 25 | [env:lolin_d32_pro_16mb_sim800_spiffs] 26 | platform = espressif32 27 | lib_deps = ${common.lib_deps} 28 | board = lolin_d32_pro 29 | framework = arduino 30 | board_build.partitions = customparts.csv 31 | upload_port = COM4 32 | monitor_port = COM4 33 | monitor_speed = 115200 34 | extra_scripts = pre:pre_build.py 35 | 36 | build_flags = 37 | -DCOMPONENT_EMBED_TXTFILES=src/index.html 38 | -DCORE_DEBUG_LEVEL=3 39 | -DBOARD_HAS_PSRAM -mfix-esp32-psram-cache-issue 40 | -DTINY_GSM_MODEM_SIM800 41 | -DMAX_LOG_FILES=5 42 | -DMAX_LOG_FILE_SIZE=65536 43 | -DMQTT_MAX_PACKET_SIZE=512 44 | -DMAX_BACKLOG=200 45 | -DSYSTEM_VARIANT=\"${common.progname_prefix}$PIOENV\" 46 | -DVER=\"${common.ver}\" 47 | 48 | 49 | [env:lolin_d32_pro_16mb_sim800_sd] 50 | platform = espressif32 51 | lib_deps = ${common.lib_deps} 52 | board = lolin_d32_pro 53 | framework = arduino 54 | board_build.partitions = customparts.csv 55 | upload_port = COM4 56 | monitor_port = COM4 57 | monitor_speed = 115200 58 | extra_scripts = pre:pre_build.py 59 | build_flags = 60 | -DCOMPONENT_EMBED_TXTFILES=src/index.html 61 | -DCORE_DEBUG_LEVEL=3 62 | -DBOARD_HAS_PSRAM 63 | -mfix-esp32-psram-cache-issue 64 | -DTINY_GSM_MODEM_SIM800 65 | -DFILESYSTEM_SD 66 | -DMAX_LOG_FILES=10 67 | -DMAX_LOG_FILE_SIZE=1048576 68 | -DMQTT_MAX_PACKET_SIZE=512 69 | -DMAX_BACKLOG=10000 70 | -DSYSTEM_VARIANT=\"${common.progname_prefix}$PIOENV\" 71 | -DVER=\"${common.ver}\" 72 | 73 | [env:lolin_d32_pro_16mb_sim800_spiffs_heaptrace] 74 | platform = espressif32 75 | lib_deps = ${common.lib_deps} 76 | board = lolin_d32_pro 77 | framework = arduino 78 | board_build.partitions = customparts.csv 79 | upload_port = COM4 80 | monitor_port = COM4 81 | monitor_speed = 115200 82 | extra_scripts = pre:pre_build.py 83 | build_flags = 84 | -DCOMPONENT_EMBED_TXTFILES=src/index.html 85 | -DCORE_DEBUG_LEVEL=4 86 | -DHEAPTRACE=1 87 | -DBOARD_HAS_PSRAM 88 | -mfix-esp32-psram-cache-issue 89 | -DTINY_GSM_MODEM_SIM800 90 | -DMAX_LOG_FILES=5 91 | -DMAX_LOG_FILE_SIZE=65536 92 | -DMQTT_MAX_PACKET_SIZE=512 93 | -DMAX_BACKLOG=200 94 | -DSYSTEM_VARIANT=\"${common.progname_prefix}$PIOENV\" 95 | -DVER=\"${common.ver}\" 96 | [env:esp32dev_sim800_spiffs] 97 | platform = espressif32 98 | lib_deps = ${common.lib_deps} 99 | board = esp32dev 100 | framework = arduino 101 | board_build.partitions = customparts_tight.csv 102 | upload_port = COM10 103 | monitor_port = COM10 104 | monitor_speed = 115200 105 | extra_scripts = pre:pre_build.py 106 | build_flags = 107 | -DCOMPONENT_EMBED_TXTFILES=src/index.html 108 | -DCORE_DEBUG_LEVEL=5 109 | -DTINY_GSM_MODEM_SIM800 110 | -DMAX_LOG_FILES=5 111 | -DMAX_LOG_FILE_SIZE=65536 112 | -DMQTT_MAX_PACKET_SIZE=512 113 | -DMAX_BACKLOG=200 114 | -DSYSTEM_VARIANT=\"${common.progname_prefix}$PIOENV\" 115 | -DVER=\"${common.ver}\" 116 | 117 | [env:esp32dev_a6gsm_spiffs] 118 | platform = espressif32 119 | lib_deps = ${common.lib_deps} 120 | board = esp32dev 121 | framework = arduino 122 | board_build.partitions = customparts_tight.csv 123 | upload_port = COM10 124 | monitor_port = COM10 125 | monitor_speed = 115200 126 | extra_scripts = pre:pre_build.py 127 | build_flags = 128 | -DCOMPONENT_EMBED_TXTFILES=src/index.html 129 | -DCORE_DEBUG_LEVEL=4 130 | -DTINY_GSM_MODEM_A6 131 | -TINY_GSM_RX_BUFFER=650 132 | -DMAX_LOG_FILES=5 133 | -DMAX_LOG_FILE_SIZE=65536 134 | -DMQTT_MAX_PACKET_SIZE=512 135 | -DMAX_BACKLOG=200 136 | -DSYSTEM_VARIANT=\"${common.progname_prefix}$PIOENV\" 137 | -DVER=\"${common.ver}\" 138 | 139 | -------------------------------------------------------------------------------- /esp32/beegl_broker/src/Log.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | Log.cpp - runtime log 3 | 4 | This file is part of the BeeGl distribution (https://github.com/bsorgo/beegl). 5 | Copyright (c) 2019 Bostjan Sorgo 6 | 7 | This program is free software: you can redistribute it and/or modify 8 | it under the terms of the GNU General Public License as published by 9 | the Free Software Foundation, version 3. 10 | 11 | This program is distributed in the hope that it will be useful, but 12 | WITHOUT ANY WARRANTY; without even the implied warranty of 13 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 14 | General Public License for more details. 15 | 16 | You should have received a copy of the GNU General Public License 17 | along with this program. If not, see . 18 | 19 | */ 20 | 21 | #include "Log.h" 22 | 23 | int fs_log_printf(const char *fmt, ...); 24 | int lastLog = 0; 25 | long lastLogFileSize = 0; 26 | const long maxLogFileSize = MAX_LOG_FILE_SIZE; 27 | const int maxLogFiles = MAX_LOG_FILES; 28 | 29 | File logFile; 30 | 31 | int fs_log_printf(const char *format, ...) 32 | { 33 | 34 | static char loc_buf[64]; 35 | char *temp = loc_buf; 36 | int len; 37 | va_list arg; 38 | va_list copy; 39 | va_start(arg, format); 40 | va_copy(copy, arg); 41 | len = vsnprintf(NULL, 0, format, arg); 42 | va_end(copy); 43 | if (len >= sizeof(loc_buf)) 44 | { 45 | temp = (char *)malloc(len + 1); 46 | if (temp == NULL) 47 | { 48 | return 0; 49 | } 50 | } 51 | vsnprintf(temp, len + 1, format, arg); 52 | String filename; 53 | 54 | if (!logFile) 55 | { 56 | #ifdef FILESYSTEM_SD 57 | uint8_t cardType = SD.cardType(); 58 | if (cardType == CARD_NONE) 59 | { 60 | log_e("[LOG] No SD card attached."); 61 | return 0; 62 | } 63 | #endif 64 | if (!FILESYSTEM.exists(LOG_DIR)) 65 | { 66 | 67 | if (!FILESYSTEM.mkdir(LOG_DIR)) 68 | { 69 | log_e("[LOG] Error creating dir: %s", LOG_DIR); 70 | } 71 | } 72 | lastLog = NVS.getInt(LAST_LOG_NVS); 73 | if (lastLog == 0) 74 | { 75 | do 76 | { 77 | lastLog++; 78 | filename = String(LOG_DIR_PREFIX); 79 | filename += String(lastLog); 80 | filename += LOG_EXTENSION; 81 | } while (FILESYSTEM.exists(filename)); 82 | NVS.setInt(LAST_LOG_NVS, lastLog); 83 | } 84 | else 85 | { 86 | filename = String(LOG_DIR_PREFIX); 87 | filename += String(lastLog); 88 | filename += LOG_EXTENSION; 89 | } 90 | 91 | log_d("[LOG] Log filename: %s", filename.c_str()); 92 | logFile = FILESYSTEM.open(filename, FILE_APPEND); 93 | if (!logFile) 94 | { 95 | log_e("[LOG] Failed to open log file to append: %s", filename.c_str()); 96 | return 0; 97 | } 98 | lastLogFileSize = logFile.size(); 99 | } 100 | 101 | if (lastLogFileSize + strlen(temp) > maxLogFileSize) 102 | { 103 | logFile.close(); 104 | lastLog++; 105 | filename = String(LOG_DIR_PREFIX); 106 | filename += String(lastLog); 107 | filename += LOG_EXTENSION; 108 | logFile = FILESYSTEM.open(filename, FILE_APPEND); 109 | lastLogFileSize = 0; 110 | NVS.setInt(LAST_LOG_NVS, lastLog); 111 | log_d("[LOG] New log filename: %s", filename.c_str()); 112 | if (!logFile) 113 | { 114 | log_e("[LOG] Failed to open new log file to append: %s", filename.c_str()); 115 | return 0; 116 | } 117 | int removeLog = lastLog - maxLogFiles; 118 | if (removeLog > 0) 119 | { 120 | String removeFilename = String(LOG_DIR_PREFIX); 121 | removeFilename += String(removeLog); 122 | removeFilename += LOG_EXTENSION; 123 | if (!FILESYSTEM.remove(removeFilename)) 124 | { 125 | log_e("[LOG] Failed to remove log: %s", removeFilename.c_str()); 126 | } 127 | } 128 | } 129 | 130 | logFile.write((uint8_t *)temp, (size_t)len); 131 | lastLogFileSize += len; 132 | logFile.flush(); 133 | va_end(arg); 134 | if (len >= sizeof(loc_buf)) 135 | { 136 | free(temp); 137 | } 138 | return len; 139 | } 140 | 141 | long log_number() 142 | { 143 | return lastLog; 144 | } 145 | -------------------------------------------------------------------------------- /esp32/beegl_broker/src/Settings.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | Settings.cpp - settings & time management: 3 | 4 | - Settings SPIFFS read/write 5 | - Central server read/write 6 | - Time functions 7 | - Other setting | time related functions 8 | 9 | This file is part of the BeeGl distribution (https://github.com/bsorgo/beegl). 10 | Copyright (c) 2019 Bostjan Sorgo 11 | 12 | This program is free software: you can redistribute it and/or modify 13 | it under the terms of the GNU General Public License as published by 14 | the Free Software Foundation, version 3. 15 | 16 | This program is distributed in the hope that it will be useful, but 17 | WITHOUT ANY WARRANTY; without even the implied warranty of 18 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 19 | General Public License for more details. 20 | 21 | You should have received a copy of the GNU General Public License 22 | along with this program. If not, see . 23 | 24 | */ 25 | 26 | #include "Settings.h" 27 | 28 | Settings::Settings() 29 | { 30 | apIp = IPAddress(192, 168, 4, 1); 31 | apGateway = IPAddress(0, 0, 0, 0); 32 | apSubnet = IPAddress(255, 255, 255, 0); 33 | 34 | wifiIp = IPAddress(192, 168, 0, 155); 35 | wifiGateway = IPAddress(192, 168, 0, 1); 36 | wifiSubnet = IPAddress(255, 255, 255, 0); 37 | 38 | setTimezone(); 39 | } 40 | 41 | char *Settings::getSettingsHostname() 42 | { 43 | char *hostname = getHostname(httpTimeAndSettingsPrefix); 44 | return hostname; 45 | } 46 | char *Settings::getSettingsPath() 47 | { 48 | 49 | char *path = getPath(httpTimeAndSettingsPrefix); 50 | strcat(path, "v1/devices/"); 51 | strcat(path, deviceName); 52 | 53 | return path; 54 | } 55 | 56 | char *Settings::getSensorPublishPath() 57 | { 58 | 59 | char *path = getPath(httpTimeAndSettingsPrefix); 60 | strcat(path, "v1/measurements"); 61 | return path; 62 | } 63 | 64 | char *Settings::getFirmwarePath() 65 | { 66 | 67 | char *path = getPath(httpTimeAndSettingsPrefix); 68 | strcat(path, "fws/"); 69 | return path; 70 | } 71 | 72 | String Settings::getDateTimeString(time_t utc) 73 | { 74 | char *buf = (char *)malloc(32); 75 | TimeChangeRule *tcr; 76 | time_t t = m_timezone->toLocal(utc, &tcr); 77 | char *abbrev = getStrTimezoneOffset(tcr); 78 | sprintf(buf, STR_TIMEFORMAT, year(t), month(t), day(t), hour(t), minute(t), second(t), abbrev); 79 | free(abbrev); 80 | String ret = String(buf); 81 | free(buf); 82 | return ret; 83 | } 84 | 85 | char *Settings::getStrTimezoneOffset(TimeChangeRule *tcr) 86 | { 87 | int hours = abs(tcr->offset) / 60; 88 | int minutes = abs(tcr->offset) % 60; 89 | char *abbrev = (char *)malloc(7); 90 | sprintf(abbrev, "%s%02d:%02d", (tcr->offset > 0 ? "+" : "-"), hours, minutes); 91 | return abbrev; 92 | } 93 | 94 | Timezone *Settings::getTimezone() 95 | { 96 | return m_timezone; 97 | } 98 | 99 | void Settings::setTimezone() 100 | { 101 | 102 | free(CEST); 103 | free(CET); 104 | CET = (TimeChangeRule *)malloc(sizeof(struct TimeChangeRule)); 105 | CEST = (TimeChangeRule *)malloc(sizeof(struct TimeChangeRule)); 106 | *CET = (TimeChangeRule){"", Last, Sun, Oct, 3, 480}; 107 | *CEST = (TimeChangeRule){"", Last, Sun, Mar, 2, 540}; 108 | CET->offset = standardTimeZone; 109 | CEST->offset = summerTimeZone; 110 | 111 | m_timezone = new Timezone(*CEST, *CET); 112 | } 113 | 114 | SchEntryType Settings::getCurrentSchedulerEntry() 115 | { 116 | time_t t = getTimezone()->toLocal(now()); 117 | int time = hour(t) * 60 + minute(t) + 1; 118 | for (int i = 0; i < schEntriesLength; i++) 119 | { 120 | int iTimeFrom = schEntries[i].schedulerHourFrom * 60 + schEntries[i].schedulerMinFrom; 121 | int iTimeTo = schEntries[i].schedulerHourTo * 60 + schEntries[i].schedulerMinTo; 122 | if (iTimeFrom <= time && iTimeTo > time) 123 | { 124 | blog_d("[SCHEDULER] Current entry: %u;%u:%u,%u:%u;%d", 125 | i, 126 | schEntries[i].schedulerHourFrom, 127 | schEntries[i].schedulerMinFrom, 128 | schEntries[i].schedulerHourTo, 129 | schEntries[i].schedulerMinTo, 130 | schEntries[i].updateFromServer); 131 | return schEntries[i]; 132 | } 133 | } 134 | SchEntryType entry = {0, 0, 0, 0, false}; 135 | blog_d("[SCHEDULER] Returning default entry: -1;0:0;0:0;false"); 136 | return entry; 137 | } 138 | 139 | /** 140 | * Provides hostname fraction of url 141 | */ 142 | char *Settings::getHostname(char *url) 143 | { 144 | char *hostname = (char *)malloc(sizeof(char) * 32); 145 | int firstDelimiter = strcspn(url, "/"); 146 | memcpy(hostname, url, firstDelimiter); 147 | hostname[firstDelimiter] = '\0'; 148 | return hostname; 149 | } 150 | /** 151 | * Provides path fraction of url 152 | */ 153 | char *Settings::getPath(char *url) 154 | { 155 | char *path = (char *)malloc(sizeof(char) * 128); 156 | int firstDelimiter = strcspn(url, "/"); 157 | strncpy(path, url + firstDelimiter, strlen(url) - firstDelimiter + 1); 158 | return path; 159 | } 160 | -------------------------------------------------------------------------------- /esp32/beegl_broker/src/main.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | main.cpp - BeeGl Broker Main Program 3 | 4 | This file is part of the BeeGl distribution (https://github.com/bsorgo/beegl). 5 | Copyright (c) 2019 Bostjan Sorgo 6 | 7 | This program is free software: you can redistribute it and/or modify 8 | it under the terms of the GNU General Public License as published by 9 | the Free Software Foundation, version 3. 10 | 11 | This program is distributed in the hope that it will be useful, but 12 | WITHOUT ANY WARRANTY; without even the implied warranty of 13 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 14 | General Public License for more details. 15 | 16 | You should have received a copy of the GNU General Public License 17 | along with this program. If not, see . 18 | 19 | */ 20 | #undef CONFIG_ARDUHAL_ESP_LOG 21 | #define DEBUG 1 22 | 23 | #ifndef ESP32 24 | #error "Only ESP32 supported" 25 | #endif 26 | #include 27 | #include 28 | #include 29 | 30 | #include 31 | #include 32 | #include 33 | #include 34 | #include 35 | #include "esp32-hal-log.h" 36 | #include "Indicator.h" 37 | #include "Service.h" 38 | #include "Settings.h" 39 | #include "Connection.h" 40 | 41 | #include "Runtime.h" 42 | #include "Updater.h" 43 | #include "Publisher.h" 44 | #include "HttpPublisher.h" 45 | #include "MqttPublisher.h" 46 | #include "Measurer.h" 47 | #include "SettingsManagement.h" 48 | #include "LogManagement.h" 49 | 50 | #include "Broker.h" 51 | #ifdef HEAPTRACE 52 | #include "esp_heap_trace.h" 53 | #define HEAP_TRACE_NUM_RECORDS 100 54 | static heap_trace_record_t trace_record[HEAP_TRACE_NUM_RECORDS]; // This buffer must be in internal RAM 55 | #endif 56 | 57 | WiFiClient wifiClient; 58 | Indicator indicator = Indicator(); 59 | Settings settings = Settings(); 60 | Service service = Service(&settings); 61 | Connection connection = Connection(&settings); 62 | Runtime runtime = Runtime(&service, &settings, &connection); 63 | Updater updater = Updater(&runtime, &service, &settings, &connection); 64 | SettingsManagement settingsManagement = SettingsManagement(&settings, &connection, &service, &runtime); 65 | LogManagement logManagement = LogManagement(&settings, &service); 66 | 67 | Publisher *publisher; 68 | Measurer *measurer; 69 | Broker *broker; 70 | 71 | void reportStatus() 72 | { 73 | if (runtime.getSafeMode()) 74 | { 75 | indicator.reportFail(); 76 | } 77 | else 78 | { 79 | indicator.reportSuccess(); 80 | } 81 | } 82 | 83 | 84 | 85 | bool nvsSetup() 86 | { 87 | Serial.println("[NVS] Begin NVS"); 88 | if (!NVS.begin()) 89 | { 90 | Serial.println("[NVS] An Error has occurred while initilizing NVS"); 91 | return false; 92 | } 93 | return true; 94 | } 95 | 96 | void setup() 97 | { 98 | 99 | Serial.begin(115200); 100 | while (!Serial) 101 | { 102 | ; 103 | } 104 | if (!nvsSetup() || !storage_setup()) 105 | { 106 | indicator.reportFail(1); 107 | return; 108 | } 109 | else 110 | { 111 | indicator.reportSuccess(1); 112 | } 113 | runtime.initialize(); 114 | runtime.setSafeModeOnRestart(1); 115 | 116 | delay(500); 117 | Serial.flush(); 118 | 119 | runtime.printWakeupReason(); 120 | 121 | if (runtime.getSafeMode()) 122 | { 123 | // start AP and web server only 124 | settings.outboundMode = 0x0; 125 | settings.inboundMode = 0x1; 126 | connection.setup(); 127 | service.setup(); 128 | runtime.setSafeModeOnRestart(0); 129 | log_i("***************************************"); 130 | log_i("******** SAFE/MAINTENANCE MODE ********"); 131 | log_i("***************************************"); 132 | log_i("Use Wifi device, connect to SSID:%s. By using web browser navigate to http://%s", settings.deviceName, settings.apIp.toString().c_str()); 133 | return; 134 | } 135 | 136 | if (!settingsManagement.readConfig()) 137 | { 138 | indicator.reportFail(2); 139 | runtime.setSafeMode(1); 140 | } 141 | else 142 | { 143 | indicator.reportSuccess(2); 144 | } 145 | 146 | log_i("[ESP] Device name: %s ", settings.deviceName); 147 | log_i("[ESP] Inbound mode: %u ", settings.inboundMode); 148 | log_i("[ESP] Outbound mode: %u ", settings.outboundMode); 149 | 150 | if (!connection.setup()) 151 | { 152 | indicator.reportFail(3); 153 | runtime.deepSleep(); 154 | } 155 | else 156 | { 157 | indicator.reportSuccess(3); 158 | } 159 | 160 | service.setup(); 161 | settingsManagement.syncTimeAndSettings(); 162 | updater.checkFirmware(); 163 | 164 | if (settings.protocol & 0x2) 165 | { 166 | publisher = new HttpPublisher(&runtime, &settings, &connection, &service); 167 | } 168 | else if (settings.protocol & 0x1) 169 | { 170 | publisher = new MqttPublisher(&runtime, &settings, &connection, &service); 171 | } 172 | publisher->setup(); 173 | measurer = new Measurer(&runtime, &service, &settings, publisher); 174 | measurer->setup(); 175 | broker = new Broker(&service, &settings, publisher); 176 | broker->setup(); 177 | 178 | runtime.setSafeModeOnRestart(0); 179 | settingsManagement.storeLastGood(); 180 | indicator.reportSuccess(4); 181 | connection.suspend(); 182 | delay(2000); 183 | } 184 | 185 | void loop() 186 | { 187 | reportStatus(); 188 | if (runtime.getSafeMode() && millis() > 600000) 189 | { 190 | ESP.restart(); 191 | } 192 | 193 | if (!runtime.getSafeMode()) 194 | { 195 | runtime.checkOperationalTime(); 196 | #ifdef HEAPTRACE 197 | Serial.printf("B measure: %u/n", ESP.getFreeHeap()); 198 | #endif 199 | measurer->measure(); 200 | #ifdef HEAPTRACE 201 | Serial.printf("A measure, B publish: %u/n", ESP.getFreeHeap()); 202 | #endif 203 | if (!publisher->publish()) 204 | { 205 | indicator.reportFail(2); 206 | } 207 | #ifdef HEAPTRACE 208 | Serial.printf("A publish: %u/n", ESP.getFreeHeap()); 209 | #endif 210 | } 211 | delay(settings.refreshInterval); 212 | } 213 | -------------------------------------------------------------------------------- /esp32/beegl_broker/src/Runtime.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | Runtime.cpp - Controls ESP32 runtime states: 3 | 4 | - Safe mode 5 | - Scheduler - operational times 6 | - Deep sleep 7 | 8 | This file is part of the BeeGl distribution (https://github.com/bsorgo/beegl). 9 | Copyright (c) 2019 Bostjan Sorgo 10 | 11 | This program is free software: you can redistribute it and/or modify 12 | it under the terms of the GNU General Public License as published by 13 | the Free Software Foundation, version 3. 14 | 15 | This program is distributed in the hope that it will be useful, but 16 | WITHOUT ANY WARRANTY; without even the implied warranty of 17 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 18 | General Public License for more details. 19 | 20 | You should have received a copy of the GNU General Public License 21 | along with this program. If not, see . 22 | 23 | */ 24 | 25 | #define uS_TO_S_FACTOR 1000000ULL /* Conversion factor for micro seconds to seconds */ 26 | #define TIME_TO_SLEEP 600 /* wake up interval */ 27 | 28 | #include "Runtime.h" 29 | 30 | Runtime::Runtime(Service *server, Settings *settings, Connection *connection) 31 | { 32 | m_settings = settings; 33 | m_connection = connection; 34 | m_server = server; 35 | 36 | webServerBind(); 37 | } 38 | 39 | void Runtime::initialize() 40 | { 41 | blog_i("[ESP] Firmware version %s", FIRMWAREVERSION); 42 | m_safeMode = getSafeModeOnRestart(); 43 | } 44 | 45 | void Runtime::webServerBind() 46 | { 47 | m_server->getWebServer()->on("/info", HTTP_GET, [&](AsyncWebServerRequest *request) { 48 | request->send(200, "text/plain", this->FIRMWAREVERSION); 49 | }); 50 | m_server->getWebServer()->on("/rest/reboot", HTTP_POST, 51 | [](AsyncWebServerRequest *request) { 52 | ESP.restart(); 53 | request->send(200); 54 | }); 55 | } 56 | 57 | void Runtime::checkOperationalTime() 58 | { 59 | uint32_t sleepTime = 0; 60 | if (millis() > 600000) 61 | { 62 | time_t t = m_settings->getTimezone()->toLocal(now()); 63 | // in seconds 64 | 65 | int time = hour(t) * 60 + minute(t); 66 | for (int i = 0; i < m_settings->schEntriesLength; i++) 67 | { 68 | 69 | int nextEntry = (i == m_settings->schEntriesLength - 1 ? 0 : i + 1); 70 | int iTimeFrom = m_settings->schEntries[i].schedulerHourFrom * 60 + m_settings->schEntries[i].schedulerMinFrom; 71 | int iTimeTo = m_settings->schEntries[i].schedulerHourTo * 60 + m_settings->schEntries[i].schedulerMinTo; 72 | int nextTimeFrom = m_settings->schEntries[nextEntry].schedulerHourFrom * 60 + m_settings->schEntries[nextEntry].schedulerMinFrom; 73 | 74 | blog_d("[SCHEDULER] Entries:%u, Entry: %u;%u:%u,%u:%u Next entry: %u;%u:%u,%u:%u", m_settings->schEntriesLength, i, 75 | m_settings->schEntries[i].schedulerHourFrom, 76 | m_settings->schEntries[i].schedulerMinFrom, 77 | m_settings->schEntries[i].schedulerHourTo, 78 | m_settings->schEntries[i].schedulerMinTo, 79 | 80 | nextEntry, 81 | m_settings->schEntries[nextEntry].schedulerHourFrom, 82 | m_settings->schEntries[nextEntry].schedulerMinFrom, 83 | m_settings->schEntries[nextEntry].schedulerHourTo, 84 | m_settings->schEntries[nextEntry].schedulerMinTo); 85 | 86 | blog_d("[SCHEDULER] Calculated time: %u, iFrom:%u, iTo:%u, nextFrom:%u", time, iTimeFrom, iTimeTo, nextTimeFrom); 87 | if ((iTimeFrom > 0 || iTimeTo > 0) && ((i == 0 && time < iTimeFrom) || (time > iTimeTo)) && 88 | ((time < nextTimeFrom) || 89 | nextEntry == 0)) 90 | { 91 | 92 | if (time > nextTimeFrom) 93 | { 94 | sleepTime = (24 - hour(t)) * 3600 + (0 - minute(t)) * 60; 95 | sleepTime += nextTimeFrom * 60; 96 | } 97 | else 98 | { 99 | sleepTime = (nextTimeFrom - time) * 60; 100 | } 101 | blog_d("[SCHEDULER] Sleep time: %u seconds\n\r", sleepTime); 102 | if (sleepTime) 103 | { 104 | break; 105 | } 106 | } 107 | } 108 | } 109 | 110 | if (sleepTime) 111 | { 112 | deepSleep(sleepTime); 113 | } 114 | } 115 | 116 | void Runtime::deepSleep(uint32_t timeToSleep) 117 | { 118 | uint64_t timeToSleepuSeconds = timeToSleep * uS_TO_S_FACTOR; 119 | blog_i("[ESP32] Deep sleep for %u s", timeToSleep); 120 | m_connection->shutdown(); 121 | Serial.flush(); 122 | delay(1000); 123 | esp_sleep_disable_wakeup_source(ESP_SLEEP_WAKEUP_ALL); 124 | esp_sleep_enable_timer_wakeup(timeToSleepuSeconds); 125 | esp_deep_sleep_start(); 126 | } 127 | 128 | void Runtime::deepSleep() 129 | { 130 | deepSleep(TIME_TO_SLEEP); 131 | } 132 | 133 | void Runtime::printWakeupReason() 134 | { 135 | esp_sleep_wakeup_cause_t wakeup_reason; 136 | wakeup_reason = esp_sleep_get_wakeup_cause(); 137 | switch (wakeup_reason) 138 | { 139 | case ESP_SLEEP_WAKEUP_EXT0: 140 | blog_i("[ESP32] Wakeup caused by external signal using RTC_IO"); 141 | break; 142 | case ESP_SLEEP_WAKEUP_EXT1: 143 | blog_i("[ESP32] Wakeup caused by external signal using RTC_CNTL"); 144 | break; 145 | case ESP_SLEEP_WAKEUP_TIMER: 146 | blog_i("[ESP32] Wakeup caused by timer"); 147 | break; 148 | case ESP_SLEEP_WAKEUP_TOUCHPAD: 149 | blog_i("[ESP32] Wakeup caused by touchpad"); 150 | break; 151 | case ESP_SLEEP_WAKEUP_ULP: 152 | blog_i("[ESP32] Wakeup caused by ULP program"); 153 | break; 154 | default: 155 | blog_i("[ESP32] Wakeup was not caused by deep sleep. Reason: %u", wakeup_reason); 156 | break; 157 | } 158 | } 159 | 160 | int8_t Runtime::getSafeModeOnRestart() 161 | { 162 | return NVS.getInt("sfres"); 163 | } 164 | 165 | void Runtime::setSafeModeOnRestart(int8_t safeModeOnRestart) 166 | { 167 | NVS.setInt("sfres", safeModeOnRestart); 168 | } 169 | 170 | int8_t Runtime::getSafeMode() 171 | { 172 | return m_safeMode; 173 | } 174 | 175 | void Runtime::setSafeMode(int8_t safeMode) 176 | { 177 | m_safeMode = safeMode; 178 | } -------------------------------------------------------------------------------- /esp32/beegl_broker/src/Measurer.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | Measurer.cpp - Performs scale measures like weight, temp and humidity 3 | 4 | This file is part of the BeeGl distribution (https://github.com/bsorgo/beegl). 5 | Copyright (c) 2019 Bostjan Sorgo 6 | 7 | This program is free software: you can redistribute it and/or modify 8 | it under the terms of the GNU General Public License as published by 9 | the Free Software Foundation, version 3. 10 | 11 | This program is distributed in the hope that it will be useful, but 12 | WITHOUT ANY WARRANTY; without even the implied warranty of 13 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 14 | General Public License for more details. 15 | 16 | You should have received a copy of the GNU General Public License 17 | along with this program. If not, see . 18 | 19 | */ 20 | 21 | #include "Measurer.h" 22 | 23 | Measurer::Measurer(Runtime *runtime, Service *server, Settings *settings, Publisher *publisher) 24 | { 25 | 26 | m_server = server; 27 | m_settings = settings; 28 | m_publisher = publisher; 29 | m_runtime = runtime; 30 | 31 | m_scale = new HX711(); 32 | m_scale->begin(SCALE_DOUT_PIN, SCALE_SCK_PIN); 33 | m_dht = new DHTesp(); 34 | 35 | webServerBind(); 36 | } 37 | 38 | void Measurer::webServerBind() 39 | { 40 | m_server->getWebServer()->on("/rest/measure", HTTP_GET, [&](AsyncWebServerRequest *request) { 41 | if(request->hasParam("scaleFactor")) m_settings->scaleFactor = atof(request->getParam("scaleFactor", false,false)->value().c_str()); 42 | if(request->hasParam("scaleOffset")) m_settings->scaleOffset = atoi(request->getParam("scaleOffset", false,false)->value().c_str()); 43 | blog_d("Scale factor: %f, offset: %u", m_settings->scaleFactor, m_settings->scaleOffset); 44 | char *message = measure(); 45 | if (message) 46 | { 47 | blog_d("Payload: %s", message); 48 | request->send(200, "text/plain", message); 49 | } 50 | else 51 | { 52 | request->send(500); 53 | } 54 | }); 55 | 56 | m_server->getWebServer()->on("/rest/scale/tare", HTTP_POST, 57 | [&](AsyncWebServerRequest *request) { 58 | long tareValue = zero(); 59 | 60 | String strValue = String(tareValue); 61 | request->send(200, "text/plain", strValue); 62 | }); 63 | } 64 | 65 | bool Measurer::scaleSetup() 66 | { 67 | if (m_settings->measureWeight) 68 | { 69 | blog_i("[HX711] Setup"); 70 | blog_i("[HX711] Scale factor: %f", m_settings->scaleFactor); 71 | blog_i("[HX711] Scale offset: %u", m_settings->scaleOffset); 72 | blog_i("[HX711] Scale unit: %s", m_settings->scaleUnit); 73 | } 74 | return true; 75 | } 76 | 77 | bool Measurer::dhtSetup() 78 | { 79 | if (m_settings->measureTempAndHumidity) 80 | { 81 | blog_i("[DHT] Setup"); 82 | m_dht->setup(DHT_PIN, DHTesp::DHT22); 83 | } 84 | return true; 85 | } 86 | 87 | bool Measurer::setup() 88 | { 89 | if (!scaleSetup() || !dhtSetup()) 90 | { 91 | return false; 92 | } 93 | return true; 94 | } 95 | 96 | long Measurer::zero() 97 | { 98 | blog_d("[HX711] Powerup"); 99 | m_scale->power_up(); 100 | m_scale->set_scale(m_settings->scaleFactor); 101 | m_scale->set_offset(m_settings->scaleOffset); 102 | blog_i("[HX711] Tare"); 103 | m_scale->tare(10); 104 | long tareValue = m_scale->get_offset(); 105 | blog_d("[HX711] Tare value: %u", tareValue); 106 | m_settings->scaleOffset = m_scale->get_offset(); 107 | blog_d("[HX711] Shutdown"); 108 | m_scale->power_down(); 109 | return tareValue; 110 | } 111 | 112 | char *Measurer::measure() 113 | { 114 | 115 | MeasureData data; 116 | 117 | if (m_settings->measureWeight) 118 | { 119 | blog_d("[HX711] Powerup"); 120 | m_scale->power_up(); 121 | delay(200); 122 | m_scale->set_scale(m_settings->scaleFactor); 123 | m_scale->set_offset(m_settings->scaleOffset); 124 | delay(200); 125 | data.weight = -1; 126 | data.weight = m_scale->get_units(10); 127 | blog_d("[MEASURER] Read weight %f ", data.weight); 128 | blog_d("[HX711] Shutdown"); 129 | m_scale->power_down(); 130 | } 131 | if (m_settings->measureTempAndHumidity) 132 | { 133 | TempAndHumidity sensorData = m_dht->getTempAndHumidity(); 134 | data.temp = sensorData.temperature; 135 | data.humidity = sensorData.humidity; 136 | blog_d("[MEASURER] Read temperature and humidity: %.2f C %.2f %% ", data.temp, data.humidity); 137 | } 138 | char *message = storeMessage(data); 139 | 140 | return message; 141 | } 142 | 143 | char *Measurer::storeMessage(MeasureData measureData) 144 | { 145 | StaticJsonBuffer<512> jsonBuffer; 146 | JsonObject &root = jsonBuffer.createObject(); 147 | root[STR_DEVICEID] = m_settings->deviceName; 148 | root[STR_TIME] = m_settings->getDateTimeString(now()); 149 | root[STR_VER] = m_runtime->FIRMWAREVERSION; 150 | if (m_settings->measureWeight && measureData.weight == measureData.weight) 151 | { 152 | 153 | JsonObject &weightSensor = root.createNestedObject(STR_WEIGHTSENSOR); 154 | weightSensor[STR_WEIGHT] = measureData.weight; 155 | weightSensor[STR_WEIGHTUNIT] = STR_WEIGHTUNITG; 156 | } 157 | 158 | if (m_settings->measureTempAndHumidity && measureData.temp == measureData.temp) 159 | { 160 | JsonObject &tempSensor = root.createNestedObject(STR_TEMPSENSOR); 161 | tempSensor[STR_TEMP] = measureData.temp; 162 | tempSensor[STR_TEMPUNIT] = STR_TEMPUNITC; 163 | 164 | JsonObject &humiditySensor = root.createNestedObject(STR_HUMIDITYSENSOR); 165 | humiditySensor[STR_HUMIDITY] = measureData.humidity; 166 | humiditySensor[STR_HUMIDITYUNIT] = STR_HUMIDITYUNITPERCENT; 167 | } 168 | return m_publisher->storeMessage(root); 169 | } 170 | -------------------------------------------------------------------------------- /esp32/beegl_broker/src/Publisher.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | Publisher.cpp - Provides Temporary storage and publishes messages to central server 3 | 4 | This file is part of the BeeGl distribution (https://github.com/bsorgo/beegl). 5 | Copyright (c) 2019 Bostjan Sorgo 6 | 7 | This program is free software: you can redistribute it and/or modify 8 | it under the terms of the GNU General Public License as published by 9 | the Free Software Foundation, version 3. 10 | 11 | This program is distributed in the hope that it will be useful, but 12 | WITHOUT ANY WARRANTY; without even the implied warranty of 13 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 14 | General Public License for more details. 15 | 16 | You should have received a copy of the GNU General Public License 17 | along with this program. If not, see . 18 | 19 | 20 | */ 21 | 22 | #include "Publisher.h" 23 | 24 | void Publisher::setup() 25 | { 26 | } 27 | 28 | Publisher::Publisher(Runtime *runtime, Settings *settings, Connection *connection, Service *service) 29 | { 30 | m_connection = connection; 31 | m_settings = settings; 32 | m_runtime = runtime; 33 | m_service = service; 34 | if (!FILESYSTEM.exists(BACKLOG_DIR)) 35 | { 36 | FILESYSTEM.mkdir(BACKLOG_DIR); 37 | } 38 | webServerBind(); 39 | } 40 | 41 | void Publisher::webServerBind() 42 | { 43 | m_service->getWebServer()->serveStatic("/backlog/", FILESYSTEM, BACKLOG_DIR_PREFIX); 44 | 45 | m_service->getWebServer()->on("/rest/backlogs", HTTP_GET, [this](AsyncWebServerRequest *request) { 46 | AsyncResponseStream *response = request->beginResponseStream("application/json"); 47 | StaticJsonBuffer<128> jsonBuffer; 48 | JsonObject &root = jsonBuffer.createObject(); 49 | JsonObject &backlog = root.createNestedObject("backlog"); 50 | backlog["count"] = backlogCount; 51 | 52 | root.printTo(*response); 53 | jsonBuffer.clear(); 54 | request->send(response); 55 | }); 56 | } 57 | 58 | int Publisher::getIndex() 59 | { 60 | storageIndex++; 61 | if (storageIndex > STORAGE_SIZE - 1) 62 | { 63 | storageIndex = 0; 64 | } 65 | 66 | if (storageIndex == publishIndex) 67 | { 68 | publishIndex = -1; 69 | } 70 | 71 | return storageIndex; 72 | } 73 | 74 | bool Publisher::reconnect() 75 | { 76 | return true; 77 | } 78 | 79 | char *Publisher::storeMessage(JsonObject &jsonObj) 80 | { 81 | int i = getIndex(); 82 | jsonObj.printTo(messageStorage[i], jsonObj.measureLength() + 1); 83 | blog_d("[STORE] Message"); 84 | return messageStorage[i]; 85 | } 86 | 87 | bool Publisher::publishMessage(const char *message) 88 | { 89 | return false; 90 | } 91 | 92 | bool Publisher::publish() 93 | { 94 | if (publishIndex != storageIndex || backlogCount>0) 95 | { 96 | m_connection->resume(); 97 | m_connection->checkConnect(); 98 | bool connected = false; 99 | int retries = 0; 100 | while (!connected) 101 | { 102 | connected = reconnect(); 103 | retries++; 104 | if (!connected) 105 | { 106 | if (retries % 2 == 0) 107 | { 108 | m_connection->checkConnect(); 109 | } 110 | if (retries % 5 == 0) 111 | { 112 | break; 113 | } 114 | } 115 | } 116 | backlogCount = NVS.getInt(BACKLOG_NVS); 117 | blog_d("[PUBLISHER] Backlog count: %u", backlogCount); 118 | long fileNumber = 0; 119 | while (connected && backlogCount>0) 120 | { 121 | fileNumber++; 122 | String backlogFilename = String(BACKLOG_DIR_PREFIX); 123 | backlogFilename += fileNumber; 124 | backlogFilename += BACKLOG_EXTENSION; 125 | File backlogFile = FILESYSTEM.open(backlogFilename, FILE_READ); 126 | if (backlogFile) 127 | { 128 | String backlogMessage = backlogFile.readString(); 129 | blog_d("[PUBLISHER] Backlog: %s", backlogMessage.c_str()); 130 | backlogFile.close(); 131 | if(publishMessage(backlogMessage.c_str())) 132 | { 133 | if(!FILESYSTEM.remove(backlogFilename)) 134 | { 135 | blog_e("[PUBLISHER] Failed to remove measurement backlog file: %s", backlogFilename.c_str()); 136 | } 137 | backlogCount--; 138 | NVS.setInt(BACKLOG_NVS, backlogCount); 139 | } 140 | else 141 | { 142 | break; 143 | } 144 | 145 | } 146 | else 147 | { 148 | blog_e("[PUBLISHER] Failed to open measurement backlog file: %s", backlogFilename.c_str()); 149 | } 150 | } 151 | 152 | while (publishIndex != storageIndex) 153 | { 154 | blog_d("[PUBLISHER] Message: %s", messageStorage[publishIndex + 1]); 155 | if ((!connected || backlogCount>0 || !publishMessage(messageStorage[publishIndex + 1])) && backlogCount STORAGE_SIZE - 1) 180 | { 181 | publishIndex = -1; 182 | } 183 | } 184 | } 185 | m_connection->suspend(); 186 | } 187 | 188 | return true; 189 | } -------------------------------------------------------------------------------- /esp32/beegl_broker/src/Connection.cpp: -------------------------------------------------------------------------------- 1 | 2 | /* 3 | Connection.cpp - Connection class. Manages inbound & outbound connections 4 | 5 | This file is part of the BeeGl distribution (https://github.com/bsorgo/beegl). 6 | Copyright (c) 2019 Bostjan Sorgo 7 | 8 | This program is free software: you can redistribute it and/or modify 9 | it under the terms of the GNU General Public License as published by 10 | the Free Software Foundation, version 3. 11 | 12 | This program is distributed in the hope that it will be useful, but 13 | WITHOUT ANY WARRANTY; without even the implied warranty of 14 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 15 | General Public License for more details. 16 | 17 | You should have received a copy of the GNU General Public License 18 | along with this program. If not, see . 19 | 20 | */ 21 | 22 | 23 | #include "Connection.h" 24 | 25 | 26 | Connection::Connection(Settings *settings) 27 | { 28 | serialAT = new HardwareSerial(1); 29 | modem = new TinyGsm(*serialAT); 30 | gsmClient = new TinyGsmClient(); 31 | gsmClient->init(modem); 32 | wifiClient = new WiFiClient(); 33 | m_settings = settings; 34 | } 35 | 36 | void Connection::btOff() 37 | { 38 | blog_i( "[BLE] OFF"); 39 | esp_bt_controller_disable(); 40 | } 41 | 42 | void Connection::modemOff() 43 | { 44 | blog_i( "[GSM] OFF"); 45 | #ifdef TINY_GSM_MODEM_SIM800 46 | modem->radioOff(); 47 | #endif 48 | delay(500); 49 | modem->sendAT("+CSCLK=2"); 50 | } 51 | 52 | void Connection::suspend() 53 | { 54 | if (m_settings->outboundMode & 0x2) 55 | { 56 | #ifdef TINY_GSM_MODEM_SIM800 57 | modem->sendAT("+CFUN=4"); 58 | #endif 59 | delay(500); 60 | log_d( "[GSM] SUSPENDED"); 61 | } 62 | } 63 | 64 | void Connection::resume() 65 | { 66 | if (m_settings->outboundMode & 0x2) 67 | { 68 | #ifdef TINY_GSM_MODEM_SIM800 69 | modem->sendAT("+CFUN=1"); 70 | #endif 71 | blog_d( "[GSM] RESUMED"); 72 | } 73 | 74 | 75 | } 76 | 77 | void Connection::shutdown() 78 | { 79 | if (m_settings->outboundMode & 0x2) 80 | { 81 | modemOff(); 82 | } 83 | if (m_settings->inboundMode & 0x2) 84 | { 85 | btOff(); 86 | } 87 | } 88 | 89 | void Connection::wifiOff() 90 | { 91 | blog_i( "[WIFI] OFF"); 92 | WiFi.mode(WIFI_OFF); 93 | } 94 | 95 | bool Connection::gprsSetup() 96 | { 97 | if (m_settings->outboundMode & 0x2) 98 | { 99 | 100 | blog_i( "[GSM] Connecting to APN %s with username %s and password %s", m_settings->apn, m_settings->apnUser, m_settings->apnPass); 101 | if (!modem->gprsConnect(m_settings->apn, m_settings->apnUser, m_settings->apnPass)) 102 | { 103 | blog_i( "[GSM] NOK"); 104 | return false; 105 | } 106 | blog_i( "[GSM] OK"); 107 | } 108 | return true; 109 | } 110 | 111 | // Wifi setup 112 | bool Connection::wifiSetup() 113 | { 114 | 115 | int retries = 0; 116 | blog_i( "[WIFI] Settings: Use custom ip: %u, IP: %s GW: %s NETMASK: %s", m_settings->wifiCustomIp, m_settings->wifiIp.toString().c_str(), m_settings->wifiGateway.toString().c_str(), m_settings->wifiSubnet.toString().c_str()); 117 | blog_i( "[WIFI] APSettings: IP: %s GW: %s NETMASK: %s", m_settings->apIp.toString().c_str(), m_settings->apGateway.toString().c_str(), m_settings->apSubnet.toString().c_str()); 118 | if ((m_settings->inboundMode & 0x1) && (m_settings->outboundMode & 0x1)) 119 | { 120 | 121 | // STA + AP 122 | 123 | blog_i( "[WIFI] Starting AP STA"); 124 | blog_i( "[WIFI] Outbound connecting to %s", m_settings->wifiSSID); 125 | WiFi.disconnect(); 126 | WiFi.mode(WIFI_AP_STA); 127 | if (m_settings->wifiCustomIp) 128 | { 129 | if (!WiFi.config(m_settings->wifiIp, m_settings->wifiGateway, m_settings->wifiSubnet)) 130 | { 131 | Serial.println("[WIFI] STA Config Failed"); 132 | } 133 | } 134 | 135 | WiFi.begin(m_settings->wifiSSID, m_settings->wifiPassword); 136 | 137 | blog_i( "[WIFI] AP started"); 138 | blog_i( "[WIFI] AP IP: %s ", WiFi.softAPIP().toString().c_str()); 139 | 140 | while (WiFi.status() != WL_CONNECTED) 141 | { 142 | delay(500); 143 | Serial.print("."); 144 | if (retries > 20) 145 | { 146 | return false; 147 | } 148 | retries++; 149 | } 150 | blog_i( "[WIFI] connected"); 151 | blog_i( "[WIFI] address: %s ", WiFi.localIP().toString().c_str()); 152 | } 153 | else if ((m_settings->inboundMode & 0x1) && !(m_settings->outboundMode & 0x1)) 154 | { 155 | blog_i( "[WIFI] Starting AP"); 156 | WiFi.disconnect(); 157 | WiFi.mode(WIFI_AP); 158 | WiFi.softAP(m_settings->deviceName, m_settings->apPassword); 159 | blog_i( "[WIFI] AP started"); 160 | blog_i( "[WIFI] AP IP: %s", WiFi.softAPIP().toString().c_str()); 161 | blog_i( "[WIFI] AP password: %s", m_settings->apPassword); 162 | } 163 | else if (!(m_settings->inboundMode & 0x1) && (m_settings->outboundMode & 0x1)) 164 | { 165 | blog_i( "[WIFI] Starting STA"); 166 | blog_i( "[WIFI] Outbound connecting to %s", m_settings->wifiSSID); 167 | WiFi.disconnect(); 168 | WiFi.mode(WIFI_STA); 169 | if (m_settings->wifiCustomIp) 170 | { 171 | if (!WiFi.config(m_settings->wifiIp, m_settings->wifiGateway, m_settings->wifiSubnet)) 172 | { 173 | blog_e( "[WIFI] STA Config Failed"); 174 | } 175 | } 176 | WiFi.begin(m_settings->wifiSSID, m_settings->wifiPassword); 177 | while (WiFi.status() != WL_CONNECTED) 178 | { 179 | delay(500); 180 | if (retries > 20) 181 | { 182 | return false; 183 | } 184 | retries++; 185 | } 186 | blog_i( "[WIFI] connected"); 187 | blog_i( "[WIFI] IP: %s", WiFi.localIP().toString().c_str()); 188 | return true; 189 | } 190 | else 191 | { 192 | // NOWIFI 193 | Serial.println("[WIFI] No WIFI"); 194 | wifiOff(); 195 | } 196 | return true; 197 | } 198 | 199 | // GPRS setup 200 | bool Connection::gsmSetup() 201 | { 202 | modem->sendAT(""); 203 | modem->waitResponse(GSM_OK); 204 | if (m_settings->outboundMode & 0x2) 205 | { 206 | 207 | 208 | // Restart takes quite some time 209 | // To skip it, call init() instead of restart() 210 | blog_i( "[GSM] Initializing GPRS modem"); 211 | modem->sendAT("+CSCLK=0"); 212 | modem->sendAT("+CFUN=1"); 213 | modem->restart(); 214 | delay(2000); 215 | blog_i( "[GSM] Modem type: %s , IMEI: %s, ICCID: %s", modem->getModemInfo().c_str(), modem->getIMEI().c_str(), modem->getSimCCID().c_str()); 216 | delay(1000); 217 | blog_i( "[GSM] Waiting for network..."); 218 | if (!modem->waitForNetwork()) 219 | { 220 | modemOff(); 221 | return false; 222 | } 223 | else 224 | { 225 | 226 | blog_i("[GSM] OK. Network strenght: %u ", modem->getSignalQuality()); 227 | } 228 | } 229 | else 230 | { 231 | modemOff(); 232 | } 233 | return true; 234 | } 235 | 236 | bool Connection::setup() 237 | { 238 | serialAT->begin(115200, SERIAL_8N1, MODEM_RX_PIN, MODEM_TX_PIN, false); 239 | while (!serialAT) 240 | { 241 | ; 242 | } 243 | if (!wifiSetup() || !gsmSetup() || !gprsSetup()) 244 | { 245 | return false; 246 | } 247 | return true; 248 | } 249 | 250 | void Connection::checkConnect() 251 | { 252 | if (m_settings->outboundMode & 0x2) 253 | { 254 | if (!modem->isGprsConnected()) 255 | { 256 | gprsSetup(); 257 | } 258 | } 259 | } 260 | 261 | Client *Connection::getClient() 262 | { 263 | if (m_settings->outboundMode & 0x2) 264 | { 265 | return gsmClient; 266 | } 267 | else 268 | { 269 | return wifiClient; 270 | } 271 | } 272 | TinyGsm *Connection::getModem() { 273 | return modem; 274 | } -------------------------------------------------------------------------------- /esp32/beegl_broker/src/Updater.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | Updater.cpp - provides firmware & resources update functions 3 | 4 | - Explicit firmware update through web interface 5 | - Implicit firmware update from server 6 | - Implicit resource update (e.g. index.html) from server based on MD5 7 | 8 | This file is part of the BeeGl distribution (https://github.com/bsorgo/beegl). 9 | Copyright (c) 2019 Bostjan Sorgo 10 | 11 | This program is free software: you can redistribute it and/or modify 12 | it under the terms of the GNU General Public License as published by 13 | the Free Software Foundation, version 3. 14 | 15 | This program is distributed in the hope that it will be useful, but 16 | WITHOUT ANY WARRANTY; without even the implied warranty of 17 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 18 | General Public License for more details. 19 | 20 | You should have received a copy of the GNU General Public License 21 | along with this program. If not, see . 22 | 23 | */ 24 | 25 | #include "Updater.h" 26 | 27 | 28 | Updater::Updater(Runtime *runtime, Service *service, Settings *settings, Connection *connection) 29 | { 30 | m_server = service; 31 | m_settings = settings; 32 | m_connection = connection; 33 | m_runtime = runtime; 34 | webServerBind(); 35 | } 36 | 37 | void Updater::webServerBind() 38 | { 39 | m_server->getWebServer()->on("/update", HTTP_POST, [](AsyncWebServerRequest *request) { 40 | request->send(200); 41 | ESP.restart(); }, [](AsyncWebServerRequest *request, String filename, size_t index, uint8_t *data, size_t len, bool final) { 42 | 43 | if(!index){ 44 | Serial.printf("[UPDATER] UploadStart: %s\n", filename.c_str()); 45 | if (!Update.begin(UPDATE_SIZE_UNKNOWN)) { 46 | Update.printError(Serial); 47 | } 48 | } 49 | if(Update.write(data, len)!=len) 50 | { 51 | Update.printError(Serial); 52 | } 53 | if(final) { 54 | Serial.printf("[UPDATER] BodyEnd: %u B\n", index+len); 55 | if(Update.end(true)) 56 | { 57 | Serial.printf("[UPDATER] Update Success: %u\nRebooting...\n", index+len); 58 | ESP.restart(); 59 | } 60 | } }); 61 | } 62 | 63 | void Updater::checkFirmware() 64 | { 65 | 66 | SchEntryType schEntry = m_settings->getCurrentSchedulerEntry(); 67 | 68 | // is this operational time entry for updates? 69 | if(schEntry.updateFromServer) { 70 | // firmware 71 | if (strcmp(m_runtime->FIRMWAREVERSION, m_settings->firmwareVersion) < 0) 72 | { 73 | char *hostname = m_settings->getSettingsHostname(); 74 | char *path = m_settings->getFirmwarePath(); 75 | strcat(path, SYSTEM_VARIANT); 76 | strcat(path, "_"); 77 | strcat(path, m_settings->firmwareVersion); 78 | strcat(path, ".bin"); 79 | blog_i("[UPDATER] Firmware hostname: %s", hostname); 80 | blog_i("[UPDATER] Firmware file path: %s", path); 81 | m_connection->checkConnect(); 82 | HttpClient httpClient = HttpClient(*m_connection->getClient(), hostname, 80); 83 | downloadFirmware(&httpClient, path); 84 | httpClient.stop(); 85 | free(hostname); 86 | free(path); 87 | } 88 | } 89 | } 90 | 91 | String Updater::getLocalFileMd5(const char *filename) 92 | { 93 | char *localFilename = (char *)malloc(sizeof(char) * 32); 94 | strcpy(localFilename, "/"); 95 | strcat(localFilename, filename); 96 | File file = FILESYSTEM.open(localFilename, FILE_READ); 97 | 98 | if (!file) 99 | { 100 | blog_e("[UPDATER] Error. File %s not found.", localFilename);; 101 | return "0"; 102 | } 103 | MD5Builder md5; 104 | md5.begin(); 105 | md5.addStream(file, 50000); 106 | md5.calculate(); 107 | String md5str = md5.toString(); 108 | blog_i("[UPDATER] Local file:%s MD5:%s", filename, md5str.c_str()); 109 | file.close(); 110 | free(localFilename); 111 | return md5str; 112 | } 113 | 114 | String Updater::getServerFileMd5(const char *filename) 115 | { 116 | 117 | char *hostname = m_settings->getSettingsHostname(); 118 | char *path = m_settings->getFirmwarePath(); 119 | strcat(path, filename); 120 | strcat(path, ".md5"); 121 | m_connection->checkConnect(); 122 | HttpClient httpClient = HttpClient(*m_connection->getClient(), hostname, 80); 123 | int res = httpClient.get(path); 124 | int responseCode = httpClient.responseStatusCode(); 125 | if (res == 0 && responseCode == 200) 126 | { 127 | httpClient.skipResponseHeaders(); 128 | String md5str = httpClient.readString(); 129 | blog_i("[UPDATER] Server file:%s MD5:%s", path, md5str.c_str()); 130 | return md5str; 131 | } 132 | else if (res == 0 && responseCode == 404) 133 | { 134 | blog_i("[UPDATER] No server file on path:%s", path); 135 | return ""; 136 | } else { 137 | blog_e("[UPDATER] Error obtaining md5 file :%s. Response code:%u", path, responseCode); 138 | return ""; 139 | } 140 | free(hostname); 141 | free(path); 142 | } 143 | 144 | void Updater::downloadFirmware(HttpClient *httpClient, char *filePath) 145 | { 146 | int err = 0; 147 | httpClient->connectionKeepAlive(); 148 | err = httpClient->get(filePath); 149 | if (err == 0 && httpClient->responseStatusCode() == 200) 150 | { 151 | if (!Update.begin(UPDATE_SIZE_UNKNOWN)) 152 | { 153 | Update.printError(Serial); 154 | return; 155 | } 156 | blog_i("[UPDATER] Download start: %s\n", filePath); 157 | int len = httpClient->contentLength(); 158 | blog_d("[UPDATER] Found file size: %u ", len); 159 | uint8_t buff[1024] = {0}; 160 | while (httpClient->connected() && (len > 0 || len == -1)) 161 | { 162 | 163 | size_t size = httpClient->available(); 164 | 165 | size_t c = httpClient->readBytes(buff, ((size > sizeof(buff)) ? sizeof(buff) : size)); 166 | if (Update.write(buff, c) != c) 167 | { 168 | Update.printError(Serial); 169 | return; 170 | } 171 | if (len > 0) 172 | { 173 | len -= c; 174 | } 175 | blog_d("[UPDATER] Downloading .. left %u ", len); 176 | delay(2); 177 | } 178 | delay(100); 179 | if (Update.end(true)) 180 | { 181 | blog_i("[UPDATER] Update success: %u. \nRebooting...\n", len); 182 | m_runtime->setSafeModeOnRestart(0); 183 | ESP.restart(); 184 | } 185 | else 186 | { 187 | Update.printError(Serial); 188 | return; 189 | } 190 | } 191 | } 192 | 193 | bool Updater::checkDownloadFile(const char *filename) 194 | { 195 | String serverMd5 = getServerFileMd5(filename); 196 | String localMd5 = getLocalFileMd5(filename); 197 | serverMd5.trim(); 198 | localMd5.trim(); 199 | if (serverMd5.compareTo(localMd5)!=0) 200 | { 201 | return downloadFile(filename); 202 | } 203 | return true; 204 | } 205 | bool Updater::downloadFile(const char *filename) 206 | { 207 | char *hostname = m_settings->getSettingsHostname(); 208 | char *path = m_settings->getFirmwarePath(); 209 | strcat(path, filename); 210 | int result = downloadFile(hostname, path, filename); 211 | free(hostname); 212 | free(path); 213 | return result; 214 | } 215 | bool Updater::downloadFile(const char *hostname, const char *path, const char *filename) 216 | { 217 | int err = 0; 218 | char *localFilename = (char *)malloc(sizeof(char) * 32); 219 | strcpy(localFilename, "/"); 220 | strcat(localFilename, filename); 221 | m_connection->checkConnect(); 222 | 223 | HttpClient httpClient = HttpClient(*m_connection->getClient(), hostname, 80); 224 | httpClient.connectionKeepAlive(); 225 | err = httpClient.get(path); 226 | if (err == 0 && httpClient.responseStatusCode() == 200) 227 | { 228 | File f = FILESYSTEM.open(localFilename, FILE_WRITE); 229 | if (!f) 230 | { 231 | free(localFilename); 232 | return false; 233 | } 234 | blog_i("[UPDATER] Download start: %s\n", path); 235 | int len = httpClient.contentLength(); 236 | blog_d("[UPDATER] File size: %u ", len); 237 | uint8_t buff[512] = {0}; 238 | while (httpClient.connected() && (len > 0 || len == -1)) 239 | { 240 | 241 | size_t size = httpClient.available(); 242 | 243 | size_t c = httpClient.readBytes(buff, ((size > sizeof(buff)) ? sizeof(buff) : size)); 244 | f.write(buff, c); 245 | if (len > 0) 246 | { 247 | len -= c; 248 | } 249 | blog_d("[UPDATER] Downloading .. left %u ", len); 250 | delay(2); 251 | } 252 | f.close(); 253 | } 254 | else 255 | { 256 | free(localFilename); 257 | return false; 258 | } 259 | free(localFilename); 260 | return true; 261 | } -------------------------------------------------------------------------------- /esp32/beegl_broker/include/Settings.h: -------------------------------------------------------------------------------- 1 | /* 2 | Settings.h- settings header file 3 | 4 | This file is part of the BeeGl distribution (https://github.com/bsorgo/beegl). 5 | Copyright (c) 2019 Bostjan Sorgo 6 | 7 | This program is free software: you can redistribute it and/or modify 8 | it under the terms of the GNU General Public License as published by 9 | the Free Software Foundation, version 3. 10 | 11 | This program is distributed in the hope that it will be useful, but 12 | WITHOUT ANY WARRANTY; without even the implied warranty of 13 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 14 | General Public License for more details. 15 | 16 | You should have received a copy of the GNU General Public License 17 | along with this program. If not, see . 18 | */ 19 | 20 | #ifndef BeescaleSettings_h 21 | #define BeescaleSettings_h 22 | 23 | #define CONFIG_BUFFER 2600 24 | 25 | #define STR_WEIGHTSENSOR "whtS" 26 | #define STR_WEIGHT "w" 27 | #define STR_WEIGHTUNIT "u" 28 | #define STR_WEIGHTUNITG "g" 29 | #define STR_TEMPSENSOR "tmpS" 30 | #define STR_TEMP "t" 31 | #define STR_TEMPUNIT "u" 32 | #define STR_TEMPUNITC "C" 33 | #define STR_HUMIDITYSENSOR "humS" 34 | #define STR_HUMIDITY "h" 35 | #define STR_HUMIDITYUNIT "u" 36 | #define STR_HUMIDITYUNITPERCENT "pct" 37 | #define STR_DEVICEID "id" 38 | #define STR_REFRESH "refresh" 39 | #define STR_DEVICENAME "id" 40 | #define STR_INBOUNDMODE "inM" 41 | #define STR_OUTBOUNDMODE "outM" 42 | #define STR_DEVICETYPE "type" 43 | #define STR_WIFISETTINGS "wifiS" 44 | #define STR_WIFISSID "ssid" 45 | #define STR_WIFIPASSWORD "pwd" 46 | #define STR_WIFICUSTOMIP "cip" 47 | #define STR_WIFIIP "ip" 48 | #define STR_WIFIGATEWAY "gw" 49 | #define STR_WIFINETMASK "nmsk" 50 | #define STR_SCALESETTINGS "scaleS" 51 | #define STR_SCALEUNIT "u" 52 | #define STR_SCALEOFFSET "off" 53 | #define STR_SCALEFACTOR "f" 54 | #define STR_APSSID "ssid" 55 | #define STR_APPASSWORD "pwd" 56 | #define STR_APSETTINGS "apS" 57 | #define STR_APIP "ip" 58 | #define STR_APGATEWAY "gw" 59 | #define STR_APNETMASK "nmsk" 60 | #define STR_MQTTSETTINGS "mqttS" 61 | #define STR_MQTTSERVER "server" 62 | #define STR_MQTTPORT "port" 63 | #define STR_MQTTUSERNAME "user" 64 | #define STR_MQTTPASSWORD "pwd" 65 | #define STR_MQTTTOPIC "topic" 66 | #define STR_GPRSSETTINGS "gprsS" 67 | #define STR_GPRSAPN "apn" 68 | #define STR_GPRSUSERNAME "user" 69 | #define STR_GPRSPASSWORD "pwd" 70 | #define STR_RESTARTINTERVAL "restInt" 71 | #define STR_SCHSETTINGS "schS" 72 | #define STR_SCHENTRY "schE" 73 | #define STR_SCHHOURFROM "hf" 74 | #define STR_SCHMINFROM "mf" 75 | #define STR_SCHHOURTO "ht" 76 | #define STR_SCHMINTO "mt" 77 | #define STR_SCHUPDATE "upd" 78 | #define STR_TIMEFORMAT "%04d-%02d-%02dT%02d:%02d:%02d%s" 79 | #define STR_TIME "time" 80 | #define STR_TIMESETTINGS "timeS" 81 | #define STR_TIMESZONE "sZone" 82 | #define STR_TIMEZONE "zone" 83 | #define STR_SETTINGSURL "setUrl" 84 | #define STR_SETTINGSUSERNAME "setUser" 85 | #define STR_SETTINGSPASSWORD "setPwd" 86 | #define STR_MEASURERSETTINGS "mesS" 87 | #define STR_MEASURERWEIGHT "mesW" 88 | #define STR_MEASURERTEMPHUM "mesTh" 89 | #define STR_PUBLISHERSETTINGS "pubS" 90 | #define STR_PUBLISHERPROTOCOL "proto" 91 | #define STR_VER "ver" 92 | #define STR_FW_PREFIX "fwUrl" 93 | #define CONFIG_JSON "/config.json" 94 | #define CONFIG_JSON_DEFAULT "/config_default.json" 95 | #define CONFIG_JSON_LAST_GOOD "/config_last_good.json" 96 | #define CONFIG_JSON_BACKUP "/config_backup.json" 97 | #define CONFIG_JSON_TEMP "/config_temp.json" 98 | 99 | #include "Log.h" 100 | #include 101 | #include 102 | 103 | #include 104 | #include 105 | 106 | struct SchEntryType 107 | { 108 | int schedulerHourFrom; 109 | int schedulerMinFrom; 110 | int schedulerHourTo; 111 | int schedulerMinTo; 112 | bool updateFromServer; 113 | }; 114 | 115 | static const char CONFIGJSON[] = CONFIG_JSON; 116 | static const char CONFIGJSONLASTGOOD[] = CONFIG_JSON_LAST_GOOD; 117 | static const char CONFIGJSONTEMP[] = CONFIG_JSON_TEMP; 118 | static const char CONFIGJSONBACKUP[] = CONFIG_JSON_BACKUP; 119 | static const char CONFIGJSONDEFAULT[] = CONFIG_JSON_DEFAULT; 120 | 121 | extern const uint8_t index_html_start[] asm("_binary_src_index_html_start"); 122 | extern const uint8_t index_html_end[] asm("_binary_src_index_html_end"); 123 | class Settings 124 | { 125 | 126 | public: 127 | /* General parameters 128 | deviceName: 129 | Device name. Also AP SSID. 130 | restartInterval: 131 | outboundMode: 132 | 0x1 - WiFi - connect to AP 133 | 0x2 - GPRS - use GSM 2G network 134 | inboundMode: 135 | 0x1 - Wifi - establish AP 136 | 0x2 - Bluetooth - register BLE service & characteristic 137 | deviceType: 138 | 0x1 - Broker 139 | 0x2 - Measure unit 140 | httpTimeAndSettingsPrefix: 141 | Server resource path prefix 142 | httpTimeAndSettingUsername: 143 | Username 144 | httpTimeAndSettingUPassword: 145 | Password 146 | 147 | 148 | */ 149 | char deviceName[16] = "BEE000"; 150 | int restartInterval = 3600; 151 | char outboundMode = 0x1; 152 | char inboundMode = 0x0; 153 | char deviceType = 0x1; 154 | char httpTimeAndSettingsPrefix[64] = "www.example.com/beegl/"; 155 | char httpTimeAndSettingUsername[16] = "user"; 156 | char httpTimeAndSettingPassword[16] = "password"; 157 | uint32_t refreshInterval = 60000; 158 | char firmwareVersion[8]; 159 | 160 | /* Timezone 161 | summerTimeZone: 162 | Timezone for summer time. 163 | standardTimeZone 164 | Std timezone - winter time 165 | */ 166 | int summerTimeZone=120; 167 | int standardTimeZone=60; 168 | /* Publisher 169 | protocol: 170 | 0x1 - Publish over MQTT - Use MQTT settings 171 | 0x2 - Publish over HTTP - use Setting resource path & credentials 172 | */ 173 | char protocol = 0x1; 174 | 175 | /* Measurer 176 | measureWeight: 177 | To measure weight 178 | measureTempAndHumidity: 179 | To measure weight and humidity 180 | */ 181 | bool measureWeight = true; 182 | bool measureTempAndHumidity = true; 183 | 184 | /* SCALE parameters 185 | scaleFactor: 186 | Scale factor 187 | scaleOffset: 188 | Offset - absolute 189 | scaleUnit: 190 | Scale unit 191 | */ 192 | float scaleFactor = 1; 193 | long scaleOffset = 0; 194 | char scaleUnit[5] = "g"; 195 | /* WIFI STA parameters 196 | wifiPassword: 197 | Wifi password 198 | wifiSSID: 199 | SSID 200 | wifiCustomIp: 201 | 0 - No custom IP (DHCP) 202 | 1 - Custom IP 203 | wifiGateway: 204 | Gateway 205 | wifiSubnet: 206 | Subnet - netmask 207 | */ 208 | char wifiPassword[32] = "123456789"; 209 | char wifiSSID[32] = "WifiSSID"; 210 | int wifiCustomIp = 0; 211 | IPAddress wifiIp; 212 | IPAddress wifiGateway; 213 | IPAddress wifiSubnet; 214 | /* WIFI AP parameters 215 | apPassword: 216 | Access point password 217 | apIP: 218 | Access point IP 219 | apGatway: 220 | Access point gateway 221 | apSubnet: 222 | Subnet - netmask 223 | */ 224 | char apPassword[16] = "123456789"; 225 | IPAddress apIp; 226 | IPAddress apGateway; 227 | IPAddress apSubnet; 228 | /* MQTT parameters 229 | externalMqttServer: 230 | Mqtt hostname when GPRS is used. 231 | internalMqttServer: 232 | Mqtt hostname when Wifi is used. 233 | mqttPort: 234 | Mqtt port 235 | mqttUsername: 236 | Mqtt username 237 | mqttPassword: 238 | Mqtt password 239 | sensorTopic 240 | Measurement publish topic 241 | */ 242 | char mqttServer[32] = "mqtt.example.com"; 243 | int mqttPort = 1883; 244 | char mqttUsername[16] = "iot_username"; 245 | char mqttPassword[16] = "iot_password"; 246 | char sensorTopic[64] = "measurements"; 247 | /* GPRS parameters 248 | apn: 249 | APN 250 | apnUser: 251 | APN username: 252 | apnPassword: 253 | APN password 254 | */ 255 | char apn[32] = "internet"; 256 | char apnUser[16] = "mobitel"; 257 | char apnPass[16] = "internet"; 258 | /* Scheduler parameters 259 | 260 | Scheduler entries: 261 | schedulerHourFrom: 262 | Operational hour from 263 | schedulerMinFrom: 264 | Operational minute from 265 | schedulerHourTo: 266 | Operational hour to 267 | schedulerMinTo: 268 | Operational minute to 269 | updateFromServer: 270 | Perform upate from server (firmware, settings, resources) in this time interval 271 | */ 272 | struct SchEntryType schEntries[10] = {{0, 0, 23, 59, true}}; 273 | int schEntriesLength = 1; 274 | Settings(); 275 | 276 | 277 | char *getSettingsHostname(); 278 | char *getSettingsPath(); 279 | char *getFirmwarePath(); 280 | char *getSensorPublishPath(); 281 | String getDateTimeString(time_t utc); 282 | Timezone *getTimezone(); 283 | 284 | void setTimezone(); 285 | SchEntryType getCurrentSchedulerEntry(); 286 | char* getPath(char *url); 287 | char* getHostname(char *url); 288 | private: 289 | 290 | 291 | TimeChangeRule* CEST; 292 | TimeChangeRule* CET; 293 | Timezone *m_timezone; 294 | 295 | 296 | 297 | char* getStrTimezoneOffset(TimeChangeRule* tcr); 298 | }; 299 | 300 | #endif 301 | -------------------------------------------------------------------------------- /esp32/beegl_broker/README.md: -------------------------------------------------------------------------------- 1 | # BeeGl ESP32 Source/Broker 2 | 3 | ## Installing / Getting started 4 | 5 | ### Hardware 6 | 1. Assemble 7 | - build broker /source node device using instructions [BeeGl Beehive Monitoring](https://www.thingiverse.com/thing:3718552) or 8 | - assemble components on test board using ESP32 board of your choice following [BeeGl Broker/Source Node Schematic](https://thingiverse-production-new.s3.amazonaws.com/assets/a2/c6/a6/41/1a/BeeGl_Broker_rev_1.0.png) or 9 | - just use any ESP32 board with at least 4MB of flash without components just to play with app 10 | 2. Connect device to serial port using appropriate wiring 11 | ### Software 12 | #### From source 13 | 1. Clone repository: git clone https://github.com/bsorgo/beegl.git 14 | 2. Install Platform.IO IDE [PlatformIO IDE](https://platformio.org/platformio-ide?utm_source=github&utm_medium=arduino-esp32) 15 | 3. Run Platform.IO and install Espressif 32 Platform 16 | 4. Open Folder which represents local path of esp32/platformio/beegl_broker 17 | 5. Modify platform.ini [configuration](https://docs.platformio.org/en/latest/platforms/espressif32.html#configuration) `[env:esp32dev]`, `[env:env:lolin_d32_pro_16mb]` 18 | or create a new one. In particular `monitor_port` and `upload_port`should be changed to match assigned local COM port 19 | ``` 20 | [env:esp32dev_sim800_spiffs] 21 | platform = espressif32 22 | lib_deps = ${common.lib_deps} 23 | board = esp32dev 24 | framework = arduino 25 | board_build.partitions = customparts_tight.csv 26 | upload_port = COM10 27 | monitor_port = COM10 28 | monitor_speed = 115200 29 | build_flags = 30 | -DCOMPONENT_EMBED_TXTFILES=src/index.html 31 | -DCORE_DEBUG_LEVEL=5 32 | -DTINY_GSM_MODEM_SIM800 33 | -DMAX_LOG_FILES=5 34 | -DMAX_LOG_FILE_SIZE=65536 35 | -DMQTT_MAX_PACKET_SIZE=512 36 | -DSYSTEM_VARIANT=\"esp32dev_sim800_spiffs\" 37 | -DMAX_BACKLOG=200 38 | -DVER=\"1.1.0\" 39 | ``` 40 | 6. Upload 41 | 7. Examine serial output using Monitor 42 | 43 | #### Development/Stable releases 44 | 45 | Check [Releases](http://www.github.com/bsorgo/beegl/releases/) 46 | 47 | ## Runtime 48 | 49 | ### First run/safe/maintenance mode 50 | 51 | At first run or when startup sequence is interrupted by hardware reset/error, device will terminate in safe/maintenance mode. Also indicated by continious red LED. In this case all outbound connections are disabled (WiFi, GPRS) and WiFi access point with SSID: BEE000 and password: 123456789 is brought up. 52 | 53 | Using WiFi capable mobile device establish a connection with SSID named BEE000. 54 | 55 | With web browser on device navigate to http://192.168.4.1 - BeeGl Broker settings page. 56 | 57 | The serial output should look something like this: 58 | 59 | ``` 60 | [I][Connection.cpp:133] wifiSetup(): [WIFI] AP started 61 | [I][Connection.cpp:134] wifiSetup(): [WIFI] AP IP: 192.168.4.1 62 | [D][WiFiGeneric.cpp:336] _eventCallback(): Event: 13 - AP_START 63 | [I][Connection.cpp:135] wifiSetup(): [WIFI] AP password: 123456789 64 | [I][Connection.cpp:44] modemOff(): [GSM] OFF 65 | [I][Service.cpp:27] webServerSetup(): [WEB Server] Starting web server 66 | [I][Service.cpp:33] webServerSetup(): [WEB Server] Web server started 67 | [D][Runtime.cpp:223] setSafeModeOnRestart(): [ESP32] Open done 68 | [D][Runtime.cpp:233] setSafeModeOnRestart(): [ESP32] Write fdone. 69 | [D][Runtime.cpp:243] setSafeModeOnRestart(): [ESP32] Commit done. 70 | [I][main.cpp:128] setup(): *************************************** 71 | [I][main.cpp:129] setup(): ******** SAFE/MAINTENANCE MODE ******** 72 | [I][main.cpp:130] setup(): *************************************** 73 | [I][main.cpp:131] setup(): Use Wifi device, connect to SSID:BEE000. By using web browser navigate to http://192.168.4.1 74 | ``` 75 | 76 | Note: 77 | 78 | On some Android phones mobile data should be disabled when connected to non-internet AP. 79 | 80 | ### Operational mode 81 | 82 | It's normal broker/source node mode. At startup setup sequence is executed: 83 | 84 | 1. Mount FS filesystem - 1 green LED blink 85 | 2. Read config file - 2 green LED blinks 86 | 3. Establish inbound/outbound connection and services (web server) - 3 blinks 87 | 4. Initiate publisher, sensors and broker - 4 blinks 88 | 89 | After setup sequence green LED lights continiously. If any of above steps fails the device goes to deep sleep or safe/maintanance mode. 90 | In addition updates are checked between step 3 and 4 if set accordingly in time interval/frame definition (`"upd" : "true"`) 91 | 92 | During main program loop: 93 | 94 | - Measurements are performed (weight & temp/humidity) 95 | - Publish measurement to central server 96 | - Check operational time frame and put device in deep sleep 97 | 98 | ``` 99 | [I][Runtime.cpp:41] initialize(): [ESP] Firmware version 1.4 100 | [D][Runtime.cpp:181] getSafeModeOnRestart(): [ESP32] Open done 101 | [D][Runtime.cpp:188] getSafeModeOnRestart(): [ESP32] Read OK, value: 0 102 | [SPIFFS] Begin FS 103 | [D][Runtime.cpp:223] setSafeModeOnRestart(): [ESP32] Open done 104 | [D][Runtime.cpp:233] setSafeModeOnRestart(): [ESP32] Write fdone. 105 | [D][Runtime.cpp:243] setSafeModeOnRestart(): [ESP32] Commit done. 106 | [I][Runtime.cpp:155] printWakeupReason(): [ESP32] Wakeup was not caused by deep sleep. Reason: 0 107 | [I][main.cpp:136] setup(): [ESP] Device name: BEE007 108 | [I][main.cpp:137] setup(): [ESP] Inbound mode: 0 109 | [I][main.cpp:138] setup(): [ESP] Outbound mode: 1 110 | [I][Connection.cpp:90] wifiSetup(): [WIFI] Settings: Use custom ip: 0, IP: 192.168.4.1 GW: 192.168.4.1 NETMASK: 255.255.255.0 111 | [I][Connection.cpp:91] wifiSetup(): [WIFI] APSettings: IP: 192.168.4.1 GW: 0.0.0.0 NETMASK: 255.255.255.0 112 | [I][Connection.cpp:139] wifiSetup(): [WIFI] Starting STA 113 | [I][Connection.cpp:140] wifiSetup(): [WIFI] Outbound connecting to MyWifi 114 | [D][WiFiGeneric.cpp:336] _eventCallback(): Event: 0 - WIFI_READY 115 | [D][WiFiGeneric.cpp:336] _eventCallback(): Event: 2 - STA_START 116 | [D][WiFiGeneric.cpp:336] _eventCallback(): Event: 4 - STA_CONNECTED 117 | [D][WiFiGeneric.cpp:336] _eventCallback(): Event: 7 - STA_GOT_IP 118 | [D][WiFiGeneric.cpp:379] _eventCallback(): STA IP: 192.168.0.163, MASK: 255.255.255.0, GW: 192.168.0.1 119 | [I][Connection.cpp:160] wifiSetup(): [WIFI] connected 120 | [I][Connection.cpp:161] wifiSetup(): [WIFI] IP: 192.168.0.163 121 | [I][Connection.cpp:44] modemOff(): [GSM] OFF 122 | [I][Service.cpp:27] webServerSetup(): [WEB Server] Starting web server 123 | [I][Service.cpp:33] webServerSetup(): [WEB Server] Web server started 124 | [D][Settings.cpp:429] syncTimeAndSettings(): [SETTINGS] Time and setting prefix: www.allium4.com/beegl/ 125 | [D][Settings.cpp:434] syncTimeAndSettings(): [SETTINGS] Hostname: www.allium4.com 126 | [D][Settings.cpp:435] syncTimeAndSettings(): [SETTINGS] Path: /beegl/v1/devices/BEE007 127 | [D][Settings.cpp:436] syncTimeAndSettings(): [SETTINGS] Username: bee 128 | [I][Settings.cpp:454] readTimeAndSettings(): [SETTINGS] Response code from server: 200 129 | [I][Settings.cpp:464] readTimeAndSettings(): [SETTINGS] Header date string: Mon, 01 Jul 2019 18:05:21 GMT 130 | [D][Settings.cpp:498] readTimeAndSettings(): [SETTINGS] Time fractions: 2019-07-01T18:05:21.000Z 131 | [D][Settings.cpp:751] getCurrentSchedulerEntry(): [SCHEDULER] Current entry: 7;20:0,20:10;0 132 | [D][Settings.cpp:751] getCurrentSchedulerEntry(): [SCHEDULER] Current entry: 7;20:0,20:10;0 133 | [I][MqttPublisher.cpp:25] setup(): [MQTT] Setup 134 | [I][MqttPublisher.cpp:36] setup(): [MQTT] Server: internalMqttServer 135 | [I][MqttPublisher.cpp:37] setup(): [MQTT] Server: mqtt.allium4.com, port 1883 136 | [I][Measurer.cpp:64] scaleSetup(): [HX711] Setup 137 | [I][Measurer.cpp:65] scaleSetup(): [HX711] Scale factor: -23.209999 138 | [I][Measurer.cpp:66] scaleSetup(): [HX711] Scale offset: 1828191 139 | [I][Measurer.cpp:67] scaleSetup(): [HX711] Scale unit: g 140 | [I][Measurer.cpp:79] dhtSetup(): [DHT] Setup 141 | [D][Runtime.cpp:223] setSafeModeOnRestart(): [ESP32] Open done 142 | [D][Runtime.cpp:233] setSafeModeOnRestart(): [ESP32] Write fdone. 143 | [D][Runtime.cpp:243] setSafeModeOnRestart(): [ESP32] Commit done. 144 | [D][Measurer.cpp:116] measure(): [HX711] Powerup 145 | [D][Measurer.cpp:122] measure(): [MEASURER] Read weight 23451.12 146 | [D][Measurer.cpp:123] measure(): [HX711] Shutdown 147 | [D][Measurer.cpp:131] measure(): [MEASURER] Read temperature and humidity: 23.5 C 65 % 148 | [D][Publisher.cpp:60] storeMessage(): [STORE] Message 149 | [D][MqttPublisher.cpp:74] reconnect(): [MQTTPUBLISHER] Client: BEE007-20, username: bee 150 | [D][MqttPublisher.cpp:77] reconnect(): [MQTTPUBLISHER] Connected. 151 | {"id":"BEE007","time":"2019-07-01T20:05:21+02:00","ver":"1.4","whtS":{"w":23451.12,"u":"g"},"tmpS":{"t":23.5,"u":"C"},"humS":{"h":65,"u":"pct"}} 152 | [D][MqttPublisher.cpp:50] publishMessage(): [MQTTPUBLISHER] scale_sensors 153 | [D][MqttPublisher.cpp:51] publishMessage(): 154 | [E][MqttPublisher.cpp:59] publishMessage(): [MQTTPUBLISHER] Publish OK 155 | ``` 156 | 157 | ### Settings 158 | 159 | Settings can be managed: 160 | 161 | - Using web interface on http: `'http://` 162 | - Using rest api GET/POST `'http:///rest/settings'` 163 | 164 | 165 | 166 | #### Basic settings 167 | 168 | 1. Device name - Unique device name 169 | 2. Outbound mode. Defines upstream communication preference. For broker node this sets communication with internet/intranet towards central server. For source node this defines communication channel with broker node. 170 | - Select WiFi if outbound communication is WiFi. Specify Outbound WiFi Settings (SSID, Password and IP related settings) 171 | - Select GPRS for GPRS communication. Specify Outbound GSM/GPRS Settings (APN, Username and Password). Needles to say SIM card should be inserted in SIM module. These settings are GSM G2 network operator specific. 172 | 3. Setting URL prefix defines central server url in format `//`. Example `192.168.0.250/beegl/`. Currently only http on port 80 is supported. Provide also username and password for basic authentication if required 173 | 4. Publisher settings 174 | - Select MQTT if publisher uses MQTT protocol. Measurements are published on `measurements` topic. 175 | - Select HTTP to publish over HTTP. Path is concatenated using Setting URL Prefix and `v1/measurements` path fraction. For example if setting URL prefix is `192.168.0.250/beegl/` measurements resource measurement url is will be published on `192.168.0.250/beegl/v1/measurements`. 176 | 5. MQTT Settings set upstream MQTT server connection parameters 177 | - Internal server address - MQTT server IP or hostname when outbound connection is WiFi. 178 | - External server address - MQTT server IP or hostname when outbound connection is GPRS. 179 | - Port (TCP Port Number) - MQTT server port. Default port is 1883. 180 | - Username and password - MQTT server username an password 181 | Please note MQTT SSL/TLS connections are not supported. 182 | 6. Operational Time Intervals are used to define time frames when broker/source node will wake up from deep sleep to operational mode. Specify hour interval From and To in format `hh:mm`. Up to 10 intervals can be specified. Recurring intervals (e.g. wake up every 10 minutes) are not yet supported. 183 | 184 | #### Measurement settings 185 | 186 | 1. Measurer settings: 187 | - Measure weight (Yes/No) 188 | - Measure temperature and humidity (Yes/No) 189 | 2. Scale settings 190 | - Factor (Decimal number) - a calibration factor 191 | - Offset (Whole number) - calibration offset. By pressing button __Tare__ this value is determined based on current load. 192 | __Measure weight__ button does weight measurement with current calibration factor and offset 193 | 194 | 195 | #### Advanced settings 196 | 197 | 1. Inbound mode. Provides inbound channels for other source nodes - when device act as broker: 198 | 199 | - None - No inbound connection available 200 | - WiFi - AP Wifi is initiated. Source nodes can connect on broker AP and publish settings based on source node Setting URL prefix to path `//v1/measurements` 201 | - Bluetooth(BLE) - BT radio and BLE service is established. Used for BLE source nodes. This funcitonality is still under heavy development 202 | - WiFi+Bluetooth(BLE) - both AP WiFi and BLE service are established 203 | 204 | 2. Refresh (Milliseconds) - refresh interval for measures and publishes. Default 60000 (1 minute). 205 | 3. Time settings (Timezone selector). Sets standard and summer timezones. 206 | 207 | 208 | -------------------------------------------------------------------------------- /esp32/beegl_broker/src/SettingsManagement.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | SettingsManagement.cpp - settings & time management: 3 | 4 | - Settings FS read/write 5 | - Central server read/write 6 | - Other setting | time related functions 7 | 8 | This file is part of the BeeGl distribution (https://github.com/bsorgo/beegl). 9 | Copyright (c) 2019 Bostjan Sorgo 10 | 11 | This program is free software: you can redistribute it and/or modify 12 | it under the terms of the GNU General Public License as published by 13 | the Free Software Foundation, version 3. 14 | 15 | This program is distributed in the hope that it will be useful, but 16 | WITHOUT ANY WARRANTY; without even the implied warranty of 17 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 18 | General Public License for more details. 19 | 20 | You should have received a copy of the GNU General Public License 21 | along with this program. If not, see . 22 | 23 | */ 24 | 25 | #include "SettingsManagement.h" 26 | 27 | SettingsManagement::SettingsManagement(Settings *settings, Connection *connection, Service *service, Runtime *runtime) 28 | { 29 | 30 | m_server = service; 31 | m_connection = connection; 32 | m_settings = settings; 33 | m_runtime = runtime; 34 | webServerBind(); 35 | } 36 | 37 | void SettingsManagement::webServerBind() 38 | { 39 | m_server->getWebServer()->on("/", HTTP_GET, [](AsyncWebServerRequest *request) { 40 | request->send_P(200, "text/html", (char *)index_html_start); 41 | }); 42 | m_server->getWebServer()->on("/index.html", HTTP_GET, [](AsyncWebServerRequest *request) { 43 | request->send_P(200, "text/html", (char *)index_html_start); 44 | }); 45 | 46 | m_server->getWebServer()->on("/modem", HTTP_POST, [&](AsyncWebServerRequest *request) { 47 | if (request->hasParam("at", true, false)) 48 | { 49 | const String value = request->getParam("at", true, false)->value(); 50 | blog_d("[MODEM] at value: %s", value.c_str()); 51 | m_connection->getModem()->sendAT(value.c_str()); 52 | String response = String(); 53 | if (m_connection->getModem()->waitResponse(3000, response, GSM_OK)) 54 | { 55 | request->send(200, "text/plain", response); 56 | } 57 | else 58 | { 59 | request->send(405, "text/plain", response); 60 | } 61 | } 62 | else 63 | { 64 | request->send(405); 65 | } 66 | }); 67 | 68 | m_server->getWebServer()->on("/rest/settings", HTTP_GET, [](AsyncWebServerRequest *request) { 69 | request->send(SPIFFS, CONFIGJSON, String(), false); 70 | }); 71 | 72 | m_server->getWebServer()->on("/rest/settings/sync", HTTP_POST, 73 | [&](AsyncWebServerRequest *request) { 74 | if (writeSettingsToServer()) 75 | { 76 | request->send(200); 77 | } 78 | else 79 | { 80 | request->send(500); 81 | } 82 | }); 83 | m_server->getWebServer()->on("/rest/settings/save", HTTP_POST, 84 | [&](AsyncWebServerRequest *request) { 85 | if (writeConfig()) 86 | { 87 | request->send(200); 88 | } 89 | else 90 | { 91 | request->send(500); 92 | } 93 | }); 94 | m_server->getWebServer()->on("/rest/settings", HTTP_POST, 95 | [](AsyncWebServerRequest *request) {}, 96 | NULL, 97 | [&](AsyncWebServerRequest *request, uint8_t *data, size_t len, size_t index, size_t total) { 98 | StaticJsonBuffer jsonBuffer; 99 | File file; 100 | if (!index) 101 | { 102 | blog_i("[WEB] BodyStart: %u B\n", total); 103 | file = SPIFFS.open(CONFIGJSONTEMP, FILE_WRITE); 104 | } 105 | else 106 | { 107 | file = SPIFFS.open(CONFIGJSONTEMP, FILE_APPEND); 108 | } 109 | if (file) 110 | { 111 | for (size_t i = 0; i < len; i++) 112 | { 113 | file.write(data[i]); 114 | } 115 | file.close(); 116 | } 117 | else 118 | { 119 | request->send(500); 120 | } 121 | if (index + len == total) 122 | { 123 | blog_i("[WEB] BodyEnd: %u B\n", total); 124 | file = SPIFFS.open(CONFIGJSONTEMP, FILE_READ); 125 | if (file) 126 | { 127 | JsonObject &root = jsonBuffer.parse(file); 128 | file.close(); 129 | if (root.success()) 130 | { 131 | if (writeConfig(root)) 132 | { 133 | blog_i("[WEB] Writing config\n"); 134 | jsonBuffer.clear(); 135 | readConfig(); 136 | request->send(200); 137 | } 138 | else 139 | { 140 | request->send(500); 141 | } 142 | } 143 | else 144 | { 145 | request->send(500); 146 | } 147 | } 148 | else 149 | { 150 | request->send(500); 151 | } 152 | } 153 | }); 154 | } 155 | 156 | /* 157 | Writes config to file 158 | */ 159 | bool SettingsManagement::writeConfig(JsonObject &input) 160 | { 161 | 162 | blog_i("[SETTINGS] Writing settings"); 163 | 164 | StaticJsonBuffer jsonBuffer; 165 | JsonObject &root = jsonBuffer.createObject(); 166 | 167 | root[STR_OUTBOUNDMODE] = m_settings->outboundMode; 168 | root[STR_INBOUNDMODE] = m_settings->inboundMode; 169 | root[STR_DEVICENAME] = m_settings->deviceName; 170 | root[STR_RESTARTINTERVAL] = m_settings->restartInterval; 171 | root[STR_REFRESH] = m_settings->refreshInterval; 172 | root[STR_VER] = m_settings->firmwareVersion; 173 | root[STR_SETTINGSURL] = m_settings->httpTimeAndSettingsPrefix; 174 | root[STR_SETTINGSUSERNAME] = m_settings->httpTimeAndSettingUsername; 175 | root[STR_SETTINGSPASSWORD] = m_settings->httpTimeAndSettingPassword; 176 | 177 | merge(root, input); 178 | 179 | JsonObject &wifiSettings = root.createNestedObject(STR_WIFISETTINGS); 180 | wifiSettings[STR_WIFISSID] = m_settings->wifiSSID; 181 | wifiSettings[STR_WIFIPASSWORD] = m_settings->wifiPassword; 182 | wifiSettings[STR_WIFICUSTOMIP] = m_settings->wifiCustomIp; 183 | wifiSettings[STR_WIFIIP] = m_settings->wifiIp.toString(); 184 | wifiSettings[STR_WIFIGATEWAY] = m_settings->wifiGateway.toString(); 185 | wifiSettings[STR_WIFINETMASK] = m_settings->wifiSubnet.toString(); 186 | 187 | merge(wifiSettings, input[STR_WIFISETTINGS]); 188 | 189 | JsonObject &scaleSettings = root.createNestedObject(STR_SCALESETTINGS); 190 | scaleSettings[STR_SCALEFACTOR] = m_settings->scaleFactor; 191 | scaleSettings[STR_SCALEOFFSET] = m_settings->scaleOffset; 192 | scaleSettings[STR_SCALEUNIT] = m_settings->scaleUnit; 193 | 194 | merge(scaleSettings, input[STR_SCALESETTINGS]); 195 | 196 | JsonObject &apSettings = root.createNestedObject(STR_APSETTINGS); 197 | apSettings[STR_APPASSWORD] = m_settings->apPassword; 198 | apSettings[STR_APIP] = m_settings->apIp.toString(); 199 | apSettings[STR_APGATEWAY] = m_settings->apGateway.toString(); 200 | apSettings[STR_APNETMASK] = m_settings->apSubnet.toString(); 201 | 202 | merge(apSettings, input[STR_APSETTINGS]); 203 | 204 | JsonObject &mqttSettings = root.createNestedObject(STR_MQTTSETTINGS); 205 | mqttSettings[STR_MQTTSERVER] = m_settings->mqttServer; 206 | mqttSettings[STR_MQTTPORT] = m_settings->mqttPort; 207 | mqttSettings[STR_MQTTUSERNAME] = m_settings->mqttUsername; 208 | mqttSettings[STR_MQTTPASSWORD] = m_settings->mqttPassword; 209 | mqttSettings[STR_MQTTTOPIC] = m_settings->sensorTopic; 210 | 211 | merge(mqttSettings, input[STR_MQTTSETTINGS]); 212 | JsonObject &gprsSettings = root.createNestedObject(STR_GPRSSETTINGS); 213 | gprsSettings[STR_GPRSAPN] = m_settings->apn; 214 | gprsSettings[STR_GPRSPASSWORD] = m_settings->apnPass; 215 | gprsSettings[STR_GPRSUSERNAME] = m_settings->apnUser; 216 | 217 | merge(gprsSettings, input[STR_GPRSSETTINGS]); 218 | 219 | JsonObject &timeSettings = root.createNestedObject(STR_TIMESETTINGS); 220 | timeSettings[STR_TIMESZONE] = m_settings->summerTimeZone; 221 | timeSettings[STR_TIMEZONE] = m_settings->standardTimeZone; 222 | 223 | merge(timeSettings, input[STR_TIMESETTINGS]); 224 | 225 | JsonObject &measurerSettings = root.createNestedObject(STR_MEASURERSETTINGS); 226 | measurerSettings[STR_MEASURERWEIGHT] = m_settings->measureWeight; 227 | measurerSettings[STR_MEASURERTEMPHUM] = m_settings->measureTempAndHumidity; 228 | 229 | merge(measurerSettings, input[STR_MEASURERSETTINGS]); 230 | 231 | JsonObject &publisherSettings = root.createNestedObject(STR_PUBLISHERSETTINGS); 232 | publisherSettings[STR_PUBLISHERPROTOCOL] = m_settings->protocol; 233 | 234 | merge(publisherSettings, input[STR_PUBLISHERSETTINGS]); 235 | 236 | JsonArray &schSettings = root.createNestedArray(STR_SCHSETTINGS); 237 | if (input[STR_SCHSETTINGS]) 238 | { 239 | m_settings->schEntriesLength = input[STR_SCHSETTINGS].size(); 240 | for (JsonObject &schEntryInput : input[STR_SCHSETTINGS].asArray()) 241 | { 242 | JsonObject &schEntry = schSettings.createNestedObject(); 243 | merge(schEntry, schEntryInput); 244 | } 245 | } 246 | else 247 | { 248 | for (int i = 0; i < m_settings->schEntriesLength; i++) 249 | { 250 | JsonObject &schEntry = schSettings.createNestedObject(); 251 | schEntry[STR_SCHHOURFROM] = m_settings->schEntries[i].schedulerHourFrom; 252 | schEntry[STR_SCHMINFROM] = m_settings->schEntries[i].schedulerMinFrom; 253 | schEntry[STR_SCHHOURTO] = m_settings->schEntries[i].schedulerHourTo; 254 | schEntry[STR_SCHMINTO] = m_settings->schEntries[i].schedulerMinTo; 255 | schEntry[STR_SCHUPDATE] = m_settings->schEntries[i].updateFromServer; 256 | } 257 | } 258 | blog_i("[SETTINGS] Merge configuration "); 259 | return writeConfigToFS(CONFIGJSON, root); 260 | jsonBuffer.clear(); 261 | } 262 | /* 263 | Writes config to file 264 | */ 265 | bool SettingsManagement::writeConfig() 266 | { 267 | StaticJsonBuffer<100> jsonBuffer; 268 | JsonObject &root = jsonBuffer.createObject(); 269 | return this->writeConfig(root); 270 | } 271 | 272 | bool SettingsManagement::readAndParseJson(const char *filename, JsonObject **root, StaticJsonBuffer *jsonBuffer) 273 | { 274 | File file = SPIFFS.open(filename); 275 | if (!file) 276 | { 277 | return false; 278 | } 279 | JsonObject& rootObj= jsonBuffer->parseObject(file); 280 | 281 | file.close(); 282 | if (!rootObj.success()) 283 | { 284 | return false; 285 | } 286 | *root = &rootObj; 287 | return true; 288 | } 289 | 290 | /* Reads config from file 291 | */ 292 | bool SettingsManagement::readConfig() 293 | { 294 | StaticJsonBuffer jsonBuffer; 295 | JsonObject* rootObj; 296 | if (!readAndParseJson(CONFIGJSON, &rootObj, &jsonBuffer)) 297 | { 298 | blog_e("[SETTINGS] Failed to open config file for reading."); 299 | blog_i("[SETTINGS] Trying last good config."); 300 | if (!readAndParseJson(CONFIGJSONLASTGOOD, &rootObj, &jsonBuffer)) 301 | { 302 | blog_e("[SETTINGS] Failed to open last good config file for reading."); 303 | blog_i("[SETTINGS] Trying backup config."); 304 | if (!readAndParseJson(CONFIGJSONBACKUP, &rootObj, &jsonBuffer)) 305 | { 306 | blog_e("[SETTINGS] Failed to open backup config file for reading."); 307 | blog_i("[SETTINGS] Trying default minimalistic config."); 308 | if (!readAndParseJson(CONFIGJSONDEFAULT, &rootObj, &jsonBuffer)) 309 | { 310 | blog_e("[SETTINGS] Failed to open default config file for reading."); 311 | blog_i("[SETTINGS] Creating config from defaults."); 312 | writeConfig(); 313 | if (!readAndParseJson(CONFIGJSON, &rootObj, &jsonBuffer)) 314 | { 315 | blog_e("[SETTINGS] Failed even to open config from defaults. Returning"); 316 | return false; 317 | } 318 | } 319 | } 320 | } 321 | } 322 | JsonObject &root = *rootObj; 323 | 324 | strlcpy(m_settings->deviceName, root[STR_DEVICENAME] | m_settings->deviceName, 16); 325 | m_settings->inboundMode = root.get(STR_INBOUNDMODE); 326 | m_settings->outboundMode = (root.get(STR_OUTBOUNDMODE) ? root.get(STR_OUTBOUNDMODE) : m_settings->outboundMode); 327 | m_settings->deviceType = (root.get(STR_DEVICETYPE) ? root.get(STR_DEVICETYPE) : m_settings->deviceType); 328 | m_settings->refreshInterval = (root.get(STR_REFRESH) ? root.get(STR_REFRESH) : m_settings->refreshInterval); 329 | strlcpy(m_settings->firmwareVersion, root[STR_VER] | m_settings->firmwareVersion, 8); 330 | strlcpy(m_settings->httpTimeAndSettingsPrefix, root[STR_SETTINGSURL] | m_settings->httpTimeAndSettingsPrefix, 64); 331 | strlcpy(m_settings->httpTimeAndSettingUsername, root[STR_SETTINGSUSERNAME] | m_settings->httpTimeAndSettingUsername, 16); 332 | strlcpy(m_settings->httpTimeAndSettingPassword, root[STR_SETTINGSPASSWORD] | m_settings->httpTimeAndSettingPassword, 16); 333 | 334 | // wifi settings 335 | 336 | JsonObject &wifiSettings = root[STR_WIFISETTINGS]; 337 | strlcpy(m_settings->wifiSSID, wifiSettings[STR_WIFISSID] | m_settings->wifiSSID, 32); 338 | strlcpy(m_settings->wifiPassword, wifiSettings[STR_WIFIPASSWORD] | m_settings->wifiPassword, 32); 339 | m_settings->wifiCustomIp = wifiSettings.get(STR_WIFICUSTOMIP) ? wifiSettings.get(STR_WIFICUSTOMIP) : m_settings->wifiCustomIp; 340 | m_settings->wifiIp.fromString(wifiSettings[STR_WIFIIP] | "192.168.0.155"); 341 | m_settings->wifiGateway.fromString(wifiSettings[STR_WIFIGATEWAY] | "192.168.0.1"); 342 | m_settings->wifiSubnet.fromString(wifiSettings[STR_WIFINETMASK] | "255.255.255.0"); 343 | // scale settings 344 | 345 | JsonObject &scaleSettings = root[STR_SCALESETTINGS]; 346 | if (scaleSettings[STR_SCALEFACTOR].success()) 347 | { 348 | m_settings->scaleFactor = scaleSettings.get(STR_SCALEFACTOR); 349 | } 350 | m_settings->scaleOffset = scaleSettings.get(STR_SCALEOFFSET) | 0; 351 | strlcpy(m_settings->scaleUnit, scaleSettings[STR_SCALEUNIT] | m_settings->scaleUnit, 5); 352 | 353 | // wifi ap settings 354 | 355 | JsonObject &apSettings = root[STR_APSETTINGS]; 356 | strlcpy(m_settings->apPassword, apSettings[STR_APPASSWORD] | m_settings->apPassword, 16); 357 | m_settings->apIp.fromString(apSettings[STR_APIP] | "192.168.4.1"); 358 | m_settings->apGateway.fromString(apSettings[STR_APGATEWAY] | "192.168.4.1"); 359 | m_settings->apSubnet.fromString(apSettings[STR_APNETMASK] | "255.255.255.0"); 360 | // mqtt settings 361 | 362 | JsonObject &mqttSettings = root[STR_MQTTSETTINGS]; 363 | strlcpy(m_settings->mqttServer, mqttSettings[STR_MQTTSERVER] | m_settings->mqttServer, 32); 364 | m_settings->mqttPort = mqttSettings.get(STR_MQTTPORT) | 1883; 365 | strlcpy(m_settings->mqttUsername, mqttSettings[STR_MQTTUSERNAME] | m_settings->mqttUsername, 16); 366 | strlcpy(m_settings->mqttPassword, mqttSettings[STR_MQTTPASSWORD] | m_settings->mqttPassword, 16); 367 | strlcpy(m_settings->sensorTopic, mqttSettings[STR_MQTTTOPIC] | m_settings->sensorTopic, 64); 368 | 369 | // gprs settings 370 | 371 | JsonObject &gprsSettings = root[STR_GPRSSETTINGS]; 372 | strlcpy(m_settings->apn, gprsSettings[STR_GPRSAPN] | m_settings->apn, 32); 373 | strlcpy(m_settings->apnUser, gprsSettings[STR_GPRSUSERNAME] | m_settings->apnUser, 16); 374 | strlcpy(m_settings->apnPass, gprsSettings[STR_GPRSPASSWORD] | m_settings->apnPass, 16); 375 | 376 | // time settings 377 | 378 | JsonObject &timeSettings = root[STR_TIMESETTINGS]; 379 | m_settings->standardTimeZone = timeSettings.get(STR_TIMEZONE) ? timeSettings.get(STR_TIMEZONE) : m_settings->standardTimeZone; 380 | m_settings->summerTimeZone = timeSettings.get(STR_TIMESZONE) ? timeSettings.get(STR_TIMESZONE) : m_settings->summerTimeZone; 381 | 382 | m_settings->setTimezone(); 383 | 384 | // measurer settings 385 | 386 | JsonObject &measurerSettings = root[STR_MEASURERSETTINGS]; 387 | m_settings->measureWeight = measurerSettings.get(STR_MEASURERWEIGHT) ? measurerSettings.get(STR_MEASURERWEIGHT) : m_settings->measureWeight; 388 | m_settings->measureTempAndHumidity = measurerSettings.get(STR_MEASURERTEMPHUM) ? measurerSettings.get(STR_MEASURERTEMPHUM) : m_settings->measureTempAndHumidity; 389 | 390 | // publisher settings 391 | 392 | JsonObject &publisherSettings = root[STR_PUBLISHERSETTINGS]; 393 | m_settings->protocol = publisherSettings.get(STR_PUBLISHERPROTOCOL); 394 | 395 | // scheduler settings 396 | JsonArray &schSettings = root[STR_SCHSETTINGS]; 397 | m_settings->schEntriesLength = schSettings.size(); 398 | int i = 0; 399 | for (JsonObject &schEntryInput : schSettings) 400 | { 401 | m_settings->schEntries[i].schedulerHourFrom = schEntryInput.get(STR_SCHHOURFROM); 402 | m_settings->schEntries[i].schedulerMinFrom = schEntryInput.get(STR_SCHMINFROM); 403 | m_settings->schEntries[i].schedulerHourTo = schEntryInput.get(STR_SCHHOURTO); 404 | m_settings->schEntries[i].schedulerMinTo = schEntryInput.get(STR_SCHMINTO); 405 | m_settings->schEntries[i].updateFromServer = schEntryInput.get(STR_SCHUPDATE); 406 | i++; 407 | } 408 | jsonBuffer.clear(); 409 | return true; 410 | } 411 | 412 | bool SettingsManagement::writeSettingsToServer() 413 | { 414 | char *hostname = m_settings->getSettingsHostname(); 415 | char *path = m_settings->getSettingsPath(); 416 | 417 | blog_d("[SETTINGS] Hostname: %s", hostname); 418 | blog_d("[SETTINGS] Path: %s", path); 419 | blog_d("[SETTINGS] Username: %s, password: %s", m_settings->httpTimeAndSettingUsername, m_settings->httpTimeAndSettingPassword); 420 | 421 | HttpClient httpClient = HttpClient(*m_connection->getClient(), hostname, 80); 422 | bool res = writeSettings(&httpClient, path, m_settings->httpTimeAndSettingUsername, m_settings->httpTimeAndSettingPassword); 423 | free(hostname); 424 | free(path); 425 | return res; 426 | } 427 | 428 | void SettingsManagement::syncTimeAndSettings() 429 | { 430 | 431 | blog_d("[SETTINGS] Time and setting prefix: %s", m_settings->httpTimeAndSettingsPrefix); 432 | 433 | char *hostname = m_settings->getSettingsHostname(); 434 | char *path = m_settings->getSettingsPath(); 435 | 436 | blog_d("[SETTINGS] Hostname: %s", hostname); 437 | blog_d("[SETTINGS] Path: %s", path); 438 | blog_d("[SETTINGS] Username: %s, password: %s", m_settings->httpTimeAndSettingUsername, m_settings->httpTimeAndSettingPassword); 439 | m_connection->checkConnect(); 440 | HttpClient httpClient = HttpClient(*m_connection->getClient(), hostname, 80); 441 | this->readTimeAndSettings(&httpClient, path); 442 | free(hostname); 443 | free(path); 444 | } 445 | 446 | bool SettingsManagement::readTimeAndSettings(HttpClient *httpClient, char *path) 447 | { 448 | 449 | httpClient->connectionKeepAlive(); 450 | httpClient->beginRequest(); 451 | int res = httpClient->get(path); 452 | httpClient->sendBasicAuth(m_settings->httpTimeAndSettingUsername, m_settings->httpTimeAndSettingPassword); 453 | httpClient->endRequest(); 454 | int responseCode = httpClient->responseStatusCode(); 455 | blog_d("[SETTINGS] Response code from server: %u", responseCode); 456 | if (res == 0) 457 | { 458 | while (httpClient->headerAvailable()) 459 | { 460 | String headerName = httpClient->readHeaderName(); 461 | if (headerName.equals("Date") || headerName.equals("date")) 462 | { 463 | String dateStr = httpClient->readHeaderValue(); 464 | 465 | blog_i("[SETTINGS] Header date string: %s", dateStr.c_str()); 466 | char p[32]; 467 | 468 | char *token; 469 | 470 | dateStr.toCharArray(p, 32, 0); 471 | 472 | // day of week 473 | token = strtok(p, " "); 474 | 475 | // day 476 | token = strtok(NULL, " "); 477 | int days = atoi(token); 478 | // month 479 | token = strtok(NULL, " "); 480 | 481 | int months = getMonthFromString(token); 482 | // year 483 | token = strtok(NULL, " "); 484 | 485 | int years = atoi(token); 486 | // hour 487 | token = strtok(NULL, " "); 488 | 489 | token = strtok(token, ":"); 490 | 491 | int hours = atoi(token); 492 | // minute 493 | token = strtok(NULL, ":"); 494 | 495 | int minutes = atoi(token); 496 | // seconds 497 | token = strtok(NULL, ": "); 498 | int seconds = atoi(token); 499 | blog_d("[SETTINGS] Time fractions: %04d-%02d-%02dT%02d:%02d:%02d.000Z", years, months, days, hours, minutes, seconds); 500 | 501 | setTime(hours, minutes, seconds, days, months, years); 502 | } 503 | } 504 | } 505 | SchEntryType schEntry = m_settings->getCurrentSchedulerEntry(); 506 | if (schEntry.updateFromServer && res == 0 && responseCode == 200) 507 | { 508 | String responseBody = httpClient->responseBody(); 509 | if (responseBody.startsWith("{")) 510 | { 511 | StaticJsonBuffer jsonBuffer; 512 | JsonObject &root = jsonBuffer.parseObject(responseBody.c_str()); 513 | 514 | if (root.success()) 515 | { 516 | String preMD5 = getLocalFileMd5(CONFIG_JSON); 517 | 518 | writeConfig(root); 519 | String postMD5 = getLocalFileMd5(CONFIG_JSON); 520 | if (preMD5.compareTo(postMD5) != 0) 521 | { 522 | m_runtime->setSafeModeOnRestart(0); 523 | delay(100); 524 | ESP.restart(); 525 | } 526 | } 527 | } 528 | } 529 | httpClient->stop(); 530 | } 531 | 532 | bool SettingsManagement::copyFile(const char *source, const char *destination) 533 | { 534 | File sourceFile = SPIFFS.open(source, FILE_READ); 535 | if (!sourceFile) 536 | { 537 | return false; 538 | } 539 | File destinationFile = SPIFFS.open(destination, FILE_WRITE); 540 | while (sourceFile.available()) 541 | { 542 | destinationFile.write(sourceFile.read()); 543 | } 544 | sourceFile.close(); 545 | destinationFile.close(); 546 | 547 | return true; 548 | } 549 | 550 | void SettingsManagement::storeLastGood() 551 | { 552 | copyFile(CONFIGJSON, CONFIGJSONLASTGOOD); 553 | } 554 | 555 | /** 556 | * Merges json objects from src -> dest 557 | */ 558 | void SettingsManagement::merge(JsonObject &dest, JsonObject &src) 559 | { 560 | if (src.success()) 561 | { 562 | for (auto kvp : src) 563 | { 564 | dest[kvp.key] = kvp.value; 565 | } 566 | } 567 | } 568 | /** 569 | * Writes json to SPIFFS file 570 | */ 571 | bool SettingsManagement::writeConfigToFS(const char *filename, JsonObject &root) 572 | { 573 | 574 | SPIFFS.rename(CONFIGJSON, CONFIGJSONBACKUP); 575 | blog_i("[SPIFFS] Writing file %s", filename); 576 | File file = SPIFFS.open(filename, FILE_WRITE); 577 | if (!file) 578 | { 579 | blog_e("[SPIFFS] Failed to create file %s ", filename); 580 | Serial.println(filename); 581 | return false; 582 | } 583 | if (root.printTo(file) == 0) 584 | { 585 | blog_e("[SPIFFS] Failed to write to file %s", filename); 586 | return false; 587 | } 588 | file.close(); 589 | return true; 590 | } 591 | 592 | /** 593 | * Writes settings through HTTP client 594 | */ 595 | bool SettingsManagement::writeSettings(HttpClient *httpClient, char *path, char *username, char *password) 596 | { 597 | File sourceFile = SPIFFS.open(CONFIGJSON, FILE_READ); 598 | if (!sourceFile) 599 | { 600 | return false; 601 | } 602 | 603 | String settingsStr = sourceFile.readString(); 604 | sourceFile.close(); 605 | httpClient->connectionKeepAlive(); 606 | httpClient->beginRequest(); 607 | httpClient->post(path); 608 | httpClient->sendBasicAuth(m_settings->httpTimeAndSettingUsername, m_settings->httpTimeAndSettingPassword); 609 | httpClient->sendHeader(CONTENTLENGTH, settingsStr.length()); 610 | httpClient->sendHeader(CONTENTTYPE, CONTENTTYPEJSON); 611 | httpClient->beginBody(); 612 | httpClient->print(settingsStr); 613 | httpClient->endRequest(); 614 | 615 | int responseCode = httpClient->responseStatusCode(); 616 | httpClient->stop(); 617 | if (responseCode != 200) 618 | { 619 | blog_e("[SETTINGS] Error. Response code from server: %u", responseCode); 620 | return false; 621 | } 622 | return true; 623 | } 624 | 625 | int SettingsManagement::getMonthFromString(char *s) 626 | { 627 | if (strcmp(s, "Jan") == 0) 628 | { 629 | return 1; 630 | } 631 | if (strcmp(s, "Feb") == 0) 632 | { 633 | return 2; 634 | } 635 | if (strcmp(s, "Mar") == 0) 636 | { 637 | return 3; 638 | } 639 | if (strcmp(s, "Apr") == 0) 640 | { 641 | return 4; 642 | } 643 | if (strcmp(s, "May") == 0) 644 | { 645 | return 5; 646 | } 647 | if (strcmp(s, "Jun") == 0) 648 | { 649 | return 6; 650 | } 651 | if (strcmp(s, "Jul") == 0) 652 | { 653 | return 7; 654 | } 655 | if (strcmp(s, "Aug") == 0) 656 | { 657 | return 8; 658 | } 659 | if (strcmp(s, "Sep") == 0) 660 | { 661 | return 9; 662 | } 663 | if (strcmp(s, "Oct") == 0) 664 | { 665 | return 10; 666 | } 667 | if (strcmp(s, "Nov") == 0) 668 | { 669 | return 11; 670 | } 671 | if (strcmp(s, "Dec") == 0) 672 | { 673 | return 12; 674 | } 675 | 676 | return -1; 677 | } 678 | 679 | String SettingsManagement::getLocalFileMd5(const char *path) 680 | { 681 | File file = SPIFFS.open(path, FILE_READ); 682 | if (!file) 683 | { 684 | blog_e("[SETTINGS] Error. File %s not found.", path); 685 | ; 686 | return "0"; 687 | } 688 | MD5Builder md5; 689 | md5.begin(); 690 | md5.addStream(file, 50000); 691 | md5.calculate(); 692 | String md5str = md5.toString(); 693 | blog_i("[SETTINGS] Local file:%s MD5:%s", path, md5str.c_str()); 694 | file.close(); 695 | return md5str; 696 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | GNU GENERAL PUBLIC LICENSE 2 | Version 3, 29 June 2007 3 | 4 | Copyright (C) 2007 Free Software Foundation, Inc. 5 | Everyone is permitted to copy and distribute verbatim copies 6 | of this license document, but changing it is not allowed. 7 | 8 | Preamble 9 | 10 | The GNU General Public License is a free, copyleft license for 11 | software and other kinds of works. 12 | 13 | The licenses for most software and other practical works are designed 14 | to take away your freedom to share and change the works. By contrast, 15 | the GNU General Public License is intended to guarantee your freedom to 16 | share and change all versions of a program--to make sure it remains free 17 | software for all its users. We, the Free Software Foundation, use the 18 | GNU General Public License for most of our software; it applies also to 19 | any other work released this way by its authors. You can apply it to 20 | your programs, too. 21 | 22 | When we speak of free software, we are referring to freedom, not 23 | price. Our General Public Licenses are designed to make sure that you 24 | have the freedom to distribute copies of free software (and charge for 25 | them if you wish), that you receive source code or can get it if you 26 | want it, that you can change the software or use pieces of it in new 27 | free programs, and that you know you can do these things. 28 | 29 | To protect your rights, we need to prevent others from denying you 30 | these rights or asking you to surrender the rights. Therefore, you have 31 | certain responsibilities if you distribute copies of the software, or if 32 | you modify it: responsibilities to respect the freedom of others. 33 | 34 | For example, if you distribute copies of such a program, whether 35 | gratis or for a fee, you must pass on to the recipients the same 36 | freedoms that you received. You must make sure that they, too, receive 37 | or can get the source code. And you must show them these terms so they 38 | know their rights. 39 | 40 | Developers that use the GNU GPL protect your rights with two steps: 41 | (1) assert copyright on the software, and (2) offer you this License 42 | giving you legal permission to copy, distribute and/or modify it. 43 | 44 | For the developers' and authors' protection, the GPL clearly explains 45 | that there is no warranty for this free software. For both users' and 46 | authors' sake, the GPL requires that modified versions be marked as 47 | changed, so that their problems will not be attributed erroneously to 48 | authors of previous versions. 49 | 50 | Some devices are designed to deny users access to install or run 51 | modified versions of the software inside them, although the manufacturer 52 | can do so. This is fundamentally incompatible with the aim of 53 | protecting users' freedom to change the software. The systematic 54 | pattern of such abuse occurs in the area of products for individuals to 55 | use, which is precisely where it is most unacceptable. Therefore, we 56 | have designed this version of the GPL to prohibit the practice for those 57 | products. If such problems arise substantially in other domains, we 58 | stand ready to extend this provision to those domains in future versions 59 | of the GPL, as needed to protect the freedom of users. 60 | 61 | Finally, every program is threatened constantly by software patents. 62 | States should not allow patents to restrict development and use of 63 | software on general-purpose computers, but in those that do, we wish to 64 | avoid the special danger that patents applied to a free program could 65 | make it effectively proprietary. To prevent this, the GPL assures that 66 | patents cannot be used to render the program non-free. 67 | 68 | The precise terms and conditions for copying, distribution and 69 | modification follow. 70 | 71 | TERMS AND CONDITIONS 72 | 73 | 0. Definitions. 74 | 75 | "This License" refers to version 3 of the GNU General Public License. 76 | 77 | "Copyright" also means copyright-like laws that apply to other kinds of 78 | works, such as semiconductor masks. 79 | 80 | "The Program" refers to any copyrightable work licensed under this 81 | License. Each licensee is addressed as "you". "Licensees" and 82 | "recipients" may be individuals or organizations. 83 | 84 | To "modify" a work means to copy from or adapt all or part of the work 85 | in a fashion requiring copyright permission, other than the making of an 86 | exact copy. The resulting work is called a "modified version" of the 87 | earlier work or a work "based on" the earlier work. 88 | 89 | A "covered work" means either the unmodified Program or a work based 90 | on the Program. 91 | 92 | To "propagate" a work means to do anything with it that, without 93 | permission, would make you directly or secondarily liable for 94 | infringement under applicable copyright law, except executing it on a 95 | computer or modifying a private copy. Propagation includes copying, 96 | distribution (with or without modification), making available to the 97 | public, and in some countries other activities as well. 98 | 99 | To "convey" a work means any kind of propagation that enables other 100 | parties to make or receive copies. Mere interaction with a user through 101 | a computer network, with no transfer of a copy, is not conveying. 102 | 103 | An interactive user interface displays "Appropriate Legal Notices" 104 | to the extent that it includes a convenient and prominently visible 105 | feature that (1) displays an appropriate copyright notice, and (2) 106 | tells the user that there is no warranty for the work (except to the 107 | extent that warranties are provided), that licensees may convey the 108 | work under this License, and how to view a copy of this License. If 109 | the interface presents a list of user commands or options, such as a 110 | menu, a prominent item in the list meets this criterion. 111 | 112 | 1. Source Code. 113 | 114 | The "source code" for a work means the preferred form of the work 115 | for making modifications to it. "Object code" means any non-source 116 | form of a work. 117 | 118 | A "Standard Interface" means an interface that either is an official 119 | standard defined by a recognized standards body, or, in the case of 120 | interfaces specified for a particular programming language, one that 121 | is widely used among developers working in that language. 122 | 123 | The "System Libraries" of an executable work include anything, other 124 | than the work as a whole, that (a) is included in the normal form of 125 | packaging a Major Component, but which is not part of that Major 126 | Component, and (b) serves only to enable use of the work with that 127 | Major Component, or to implement a Standard Interface for which an 128 | implementation is available to the public in source code form. A 129 | "Major Component", in this context, means a major essential component 130 | (kernel, window system, and so on) of the specific operating system 131 | (if any) on which the executable work runs, or a compiler used to 132 | produce the work, or an object code interpreter used to run it. 133 | 134 | The "Corresponding Source" for a work in object code form means all 135 | the source code needed to generate, install, and (for an executable 136 | work) run the object code and to modify the work, including scripts to 137 | control those activities. However, it does not include the work's 138 | System Libraries, or general-purpose tools or generally available free 139 | programs which are used unmodified in performing those activities but 140 | which are not part of the work. For example, Corresponding Source 141 | includes interface definition files associated with source files for 142 | the work, and the source code for shared libraries and dynamically 143 | linked subprograms that the work is specifically designed to require, 144 | such as by intimate data communication or control flow between those 145 | subprograms and other parts of the work. 146 | 147 | The Corresponding Source need not include anything that users 148 | can regenerate automatically from other parts of the Corresponding 149 | Source. 150 | 151 | The Corresponding Source for a work in source code form is that 152 | same work. 153 | 154 | 2. Basic Permissions. 155 | 156 | All rights granted under this License are granted for the term of 157 | copyright on the Program, and are irrevocable provided the stated 158 | conditions are met. This License explicitly affirms your unlimited 159 | permission to run the unmodified Program. The output from running a 160 | covered work is covered by this License only if the output, given its 161 | content, constitutes a covered work. This License acknowledges your 162 | rights of fair use or other equivalent, as provided by copyright law. 163 | 164 | You may make, run and propagate covered works that you do not 165 | convey, without conditions so long as your license otherwise remains 166 | in force. You may convey covered works to others for the sole purpose 167 | of having them make modifications exclusively for you, or provide you 168 | with facilities for running those works, provided that you comply with 169 | the terms of this License in conveying all material for which you do 170 | not control copyright. Those thus making or running the covered works 171 | for you must do so exclusively on your behalf, under your direction 172 | and control, on terms that prohibit them from making any copies of 173 | your copyrighted material outside their relationship with you. 174 | 175 | Conveying under any other circumstances is permitted solely under 176 | the conditions stated below. Sublicensing is not allowed; section 10 177 | makes it unnecessary. 178 | 179 | 3. Protecting Users' Legal Rights From Anti-Circumvention Law. 180 | 181 | No covered work shall be deemed part of an effective technological 182 | measure under any applicable law fulfilling obligations under article 183 | 11 of the WIPO copyright treaty adopted on 20 December 1996, or 184 | similar laws prohibiting or restricting circumvention of such 185 | measures. 186 | 187 | When you convey a covered work, you waive any legal power to forbid 188 | circumvention of technological measures to the extent such circumvention 189 | is effected by exercising rights under this License with respect to 190 | the covered work, and you disclaim any intention to limit operation or 191 | modification of the work as a means of enforcing, against the work's 192 | users, your or third parties' legal rights to forbid circumvention of 193 | technological measures. 194 | 195 | 4. Conveying Verbatim Copies. 196 | 197 | You may convey verbatim copies of the Program's source code as you 198 | receive it, in any medium, provided that you conspicuously and 199 | appropriately publish on each copy an appropriate copyright notice; 200 | keep intact all notices stating that this License and any 201 | non-permissive terms added in accord with section 7 apply to the code; 202 | keep intact all notices of the absence of any warranty; and give all 203 | recipients a copy of this License along with the Program. 204 | 205 | You may charge any price or no price for each copy that you convey, 206 | and you may offer support or warranty protection for a fee. 207 | 208 | 5. Conveying Modified Source Versions. 209 | 210 | You may convey a work based on the Program, or the modifications to 211 | produce it from the Program, in the form of source code under the 212 | terms of section 4, provided that you also meet all of these conditions: 213 | 214 | a) The work must carry prominent notices stating that you modified 215 | it, and giving a relevant date. 216 | 217 | b) The work must carry prominent notices stating that it is 218 | released under this License and any conditions added under section 219 | 7. This requirement modifies the requirement in section 4 to 220 | "keep intact all notices". 221 | 222 | c) You must license the entire work, as a whole, under this 223 | License to anyone who comes into possession of a copy. This 224 | License will therefore apply, along with any applicable section 7 225 | additional terms, to the whole of the work, and all its parts, 226 | regardless of how they are packaged. This License gives no 227 | permission to license the work in any other way, but it does not 228 | invalidate such permission if you have separately received it. 229 | 230 | d) If the work has interactive user interfaces, each must display 231 | Appropriate Legal Notices; however, if the Program has interactive 232 | interfaces that do not display Appropriate Legal Notices, your 233 | work need not make them do so. 234 | 235 | A compilation of a covered work with other separate and independent 236 | works, which are not by their nature extensions of the covered work, 237 | and which are not combined with it such as to form a larger program, 238 | in or on a volume of a storage or distribution medium, is called an 239 | "aggregate" if the compilation and its resulting copyright are not 240 | used to limit the access or legal rights of the compilation's users 241 | beyond what the individual works permit. Inclusion of a covered work 242 | in an aggregate does not cause this License to apply to the other 243 | parts of the aggregate. 244 | 245 | 6. Conveying Non-Source Forms. 246 | 247 | You may convey a covered work in object code form under the terms 248 | of sections 4 and 5, provided that you also convey the 249 | machine-readable Corresponding Source under the terms of this License, 250 | in one of these ways: 251 | 252 | a) Convey the object code in, or embodied in, a physical product 253 | (including a physical distribution medium), accompanied by the 254 | Corresponding Source fixed on a durable physical medium 255 | customarily used for software interchange. 256 | 257 | b) Convey the object code in, or embodied in, a physical product 258 | (including a physical distribution medium), accompanied by a 259 | written offer, valid for at least three years and valid for as 260 | long as you offer spare parts or customer support for that product 261 | model, to give anyone who possesses the object code either (1) a 262 | copy of the Corresponding Source for all the software in the 263 | product that is covered by this License, on a durable physical 264 | medium customarily used for software interchange, for a price no 265 | more than your reasonable cost of physically performing this 266 | conveying of source, or (2) access to copy the 267 | Corresponding Source from a network server at no charge. 268 | 269 | c) Convey individual copies of the object code with a copy of the 270 | written offer to provide the Corresponding Source. This 271 | alternative is allowed only occasionally and noncommercially, and 272 | only if you received the object code with such an offer, in accord 273 | with subsection 6b. 274 | 275 | d) Convey the object code by offering access from a designated 276 | place (gratis or for a charge), and offer equivalent access to the 277 | Corresponding Source in the same way through the same place at no 278 | further charge. You need not require recipients to copy the 279 | Corresponding Source along with the object code. If the place to 280 | copy the object code is a network server, the Corresponding Source 281 | may be on a different server (operated by you or a third party) 282 | that supports equivalent copying facilities, provided you maintain 283 | clear directions next to the object code saying where to find the 284 | Corresponding Source. Regardless of what server hosts the 285 | Corresponding Source, you remain obligated to ensure that it is 286 | available for as long as needed to satisfy these requirements. 287 | 288 | e) Convey the object code using peer-to-peer transmission, provided 289 | you inform other peers where the object code and Corresponding 290 | Source of the work are being offered to the general public at no 291 | charge under subsection 6d. 292 | 293 | A separable portion of the object code, whose source code is excluded 294 | from the Corresponding Source as a System Library, need not be 295 | included in conveying the object code work. 296 | 297 | A "User Product" is either (1) a "consumer product", which means any 298 | tangible personal property which is normally used for personal, family, 299 | or household purposes, or (2) anything designed or sold for incorporation 300 | into a dwelling. In determining whether a product is a consumer product, 301 | doubtful cases shall be resolved in favor of coverage. For a particular 302 | product received by a particular user, "normally used" refers to a 303 | typical or common use of that class of product, regardless of the status 304 | of the particular user or of the way in which the particular user 305 | actually uses, or expects or is expected to use, the product. A product 306 | is a consumer product regardless of whether the product has substantial 307 | commercial, industrial or non-consumer uses, unless such uses represent 308 | the only significant mode of use of the product. 309 | 310 | "Installation Information" for a User Product means any methods, 311 | procedures, authorization keys, or other information required to install 312 | and execute modified versions of a covered work in that User Product from 313 | a modified version of its Corresponding Source. The information must 314 | suffice to ensure that the continued functioning of the modified object 315 | code is in no case prevented or interfered with solely because 316 | modification has been made. 317 | 318 | If you convey an object code work under this section in, or with, or 319 | specifically for use in, a User Product, and the conveying occurs as 320 | part of a transaction in which the right of possession and use of the 321 | User Product is transferred to the recipient in perpetuity or for a 322 | fixed term (regardless of how the transaction is characterized), the 323 | Corresponding Source conveyed under this section must be accompanied 324 | by the Installation Information. But this requirement does not apply 325 | if neither you nor any third party retains the ability to install 326 | modified object code on the User Product (for example, the work has 327 | been installed in ROM). 328 | 329 | The requirement to provide Installation Information does not include a 330 | requirement to continue to provide support service, warranty, or updates 331 | for a work that has been modified or installed by the recipient, or for 332 | the User Product in which it has been modified or installed. Access to a 333 | network may be denied when the modification itself materially and 334 | adversely affects the operation of the network or violates the rules and 335 | protocols for communication across the network. 336 | 337 | Corresponding Source conveyed, and Installation Information provided, 338 | in accord with this section must be in a format that is publicly 339 | documented (and with an implementation available to the public in 340 | source code form), and must require no special password or key for 341 | unpacking, reading or copying. 342 | 343 | 7. Additional Terms. 344 | 345 | "Additional permissions" are terms that supplement the terms of this 346 | License by making exceptions from one or more of its conditions. 347 | Additional permissions that are applicable to the entire Program shall 348 | be treated as though they were included in this License, to the extent 349 | that they are valid under applicable law. If additional permissions 350 | apply only to part of the Program, that part may be used separately 351 | under those permissions, but the entire Program remains governed by 352 | this License without regard to the additional permissions. 353 | 354 | When you convey a copy of a covered work, you may at your option 355 | remove any additional permissions from that copy, or from any part of 356 | it. (Additional permissions may be written to require their own 357 | removal in certain cases when you modify the work.) You may place 358 | additional permissions on material, added by you to a covered work, 359 | for which you have or can give appropriate copyright permission. 360 | 361 | Notwithstanding any other provision of this License, for material you 362 | add to a covered work, you may (if authorized by the copyright holders of 363 | that material) supplement the terms of this License with terms: 364 | 365 | a) Disclaiming warranty or limiting liability differently from the 366 | terms of sections 15 and 16 of this License; or 367 | 368 | b) Requiring preservation of specified reasonable legal notices or 369 | author attributions in that material or in the Appropriate Legal 370 | Notices displayed by works containing it; or 371 | 372 | c) Prohibiting misrepresentation of the origin of that material, or 373 | requiring that modified versions of such material be marked in 374 | reasonable ways as different from the original version; or 375 | 376 | d) Limiting the use for publicity purposes of names of licensors or 377 | authors of the material; or 378 | 379 | e) Declining to grant rights under trademark law for use of some 380 | trade names, trademarks, or service marks; or 381 | 382 | f) Requiring indemnification of licensors and authors of that 383 | material by anyone who conveys the material (or modified versions of 384 | it) with contractual assumptions of liability to the recipient, for 385 | any liability that these contractual assumptions directly impose on 386 | those licensors and authors. 387 | 388 | All other non-permissive additional terms are considered "further 389 | restrictions" within the meaning of section 10. If the Program as you 390 | received it, or any part of it, contains a notice stating that it is 391 | governed by this License along with a term that is a further 392 | restriction, you may remove that term. If a license document contains 393 | a further restriction but permits relicensing or conveying under this 394 | License, you may add to a covered work material governed by the terms 395 | of that license document, provided that the further restriction does 396 | not survive such relicensing or conveying. 397 | 398 | If you add terms to a covered work in accord with this section, you 399 | must place, in the relevant source files, a statement of the 400 | additional terms that apply to those files, or a notice indicating 401 | where to find the applicable terms. 402 | 403 | Additional terms, permissive or non-permissive, may be stated in the 404 | form of a separately written license, or stated as exceptions; 405 | the above requirements apply either way. 406 | 407 | 8. Termination. 408 | 409 | You may not propagate or modify a covered work except as expressly 410 | provided under this License. Any attempt otherwise to propagate or 411 | modify it is void, and will automatically terminate your rights under 412 | this License (including any patent licenses granted under the third 413 | paragraph of section 11). 414 | 415 | However, if you cease all violation of this License, then your 416 | license from a particular copyright holder is reinstated (a) 417 | provisionally, unless and until the copyright holder explicitly and 418 | finally terminates your license, and (b) permanently, if the copyright 419 | holder fails to notify you of the violation by some reasonable means 420 | prior to 60 days after the cessation. 421 | 422 | Moreover, your license from a particular copyright holder is 423 | reinstated permanently if the copyright holder notifies you of the 424 | violation by some reasonable means, this is the first time you have 425 | received notice of violation of this License (for any work) from that 426 | copyright holder, and you cure the violation prior to 30 days after 427 | your receipt of the notice. 428 | 429 | Termination of your rights under this section does not terminate the 430 | licenses of parties who have received copies or rights from you under 431 | this License. If your rights have been terminated and not permanently 432 | reinstated, you do not qualify to receive new licenses for the same 433 | material under section 10. 434 | 435 | 9. Acceptance Not Required for Having Copies. 436 | 437 | You are not required to accept this License in order to receive or 438 | run a copy of the Program. Ancillary propagation of a covered work 439 | occurring solely as a consequence of using peer-to-peer transmission 440 | to receive a copy likewise does not require acceptance. However, 441 | nothing other than this License grants you permission to propagate or 442 | modify any covered work. These actions infringe copyright if you do 443 | not accept this License. Therefore, by modifying or propagating a 444 | covered work, you indicate your acceptance of this License to do so. 445 | 446 | 10. Automatic Licensing of Downstream Recipients. 447 | 448 | Each time you convey a covered work, the recipient automatically 449 | receives a license from the original licensors, to run, modify and 450 | propagate that work, subject to this License. You are not responsible 451 | for enforcing compliance by third parties with this License. 452 | 453 | An "entity transaction" is a transaction transferring control of an 454 | organization, or substantially all assets of one, or subdividing an 455 | organization, or merging organizations. If propagation of a covered 456 | work results from an entity transaction, each party to that 457 | transaction who receives a copy of the work also receives whatever 458 | licenses to the work the party's predecessor in interest had or could 459 | give under the previous paragraph, plus a right to possession of the 460 | Corresponding Source of the work from the predecessor in interest, if 461 | the predecessor has it or can get it with reasonable efforts. 462 | 463 | You may not impose any further restrictions on the exercise of the 464 | rights granted or affirmed under this License. For example, you may 465 | not impose a license fee, royalty, or other charge for exercise of 466 | rights granted under this License, and you may not initiate litigation 467 | (including a cross-claim or counterclaim in a lawsuit) alleging that 468 | any patent claim is infringed by making, using, selling, offering for 469 | sale, or importing the Program or any portion of it. 470 | 471 | 11. Patents. 472 | 473 | A "contributor" is a copyright holder who authorizes use under this 474 | License of the Program or a work on which the Program is based. The 475 | work thus licensed is called the contributor's "contributor version". 476 | 477 | A contributor's "essential patent claims" are all patent claims 478 | owned or controlled by the contributor, whether already acquired or 479 | hereafter acquired, that would be infringed by some manner, permitted 480 | by this License, of making, using, or selling its contributor version, 481 | but do not include claims that would be infringed only as a 482 | consequence of further modification of the contributor version. For 483 | purposes of this definition, "control" includes the right to grant 484 | patent sublicenses in a manner consistent with the requirements of 485 | this License. 486 | 487 | Each contributor grants you a non-exclusive, worldwide, royalty-free 488 | patent license under the contributor's essential patent claims, to 489 | make, use, sell, offer for sale, import and otherwise run, modify and 490 | propagate the contents of its contributor version. 491 | 492 | In the following three paragraphs, a "patent license" is any express 493 | agreement or commitment, however denominated, not to enforce a patent 494 | (such as an express permission to practice a patent or covenant not to 495 | sue for patent infringement). To "grant" such a patent license to a 496 | party means to make such an agreement or commitment not to enforce a 497 | patent against the party. 498 | 499 | If you convey a covered work, knowingly relying on a patent license, 500 | and the Corresponding Source of the work is not available for anyone 501 | to copy, free of charge and under the terms of this License, through a 502 | publicly available network server or other readily accessible means, 503 | then you must either (1) cause the Corresponding Source to be so 504 | available, or (2) arrange to deprive yourself of the benefit of the 505 | patent license for this particular work, or (3) arrange, in a manner 506 | consistent with the requirements of this License, to extend the patent 507 | license to downstream recipients. "Knowingly relying" means you have 508 | actual knowledge that, but for the patent license, your conveying the 509 | covered work in a country, or your recipient's use of the covered work 510 | in a country, would infringe one or more identifiable patents in that 511 | country that you have reason to believe are valid. 512 | 513 | If, pursuant to or in connection with a single transaction or 514 | arrangement, you convey, or propagate by procuring conveyance of, a 515 | covered work, and grant a patent license to some of the parties 516 | receiving the covered work authorizing them to use, propagate, modify 517 | or convey a specific copy of the covered work, then the patent license 518 | you grant is automatically extended to all recipients of the covered 519 | work and works based on it. 520 | 521 | A patent license is "discriminatory" if it does not include within 522 | the scope of its coverage, prohibits the exercise of, or is 523 | conditioned on the non-exercise of one or more of the rights that are 524 | specifically granted under this License. You may not convey a covered 525 | work if you are a party to an arrangement with a third party that is 526 | in the business of distributing software, under which you make payment 527 | to the third party based on the extent of your activity of conveying 528 | the work, and under which the third party grants, to any of the 529 | parties who would receive the covered work from you, a discriminatory 530 | patent license (a) in connection with copies of the covered work 531 | conveyed by you (or copies made from those copies), or (b) primarily 532 | for and in connection with specific products or compilations that 533 | contain the covered work, unless you entered into that arrangement, 534 | or that patent license was granted, prior to 28 March 2007. 535 | 536 | Nothing in this License shall be construed as excluding or limiting 537 | any implied license or other defenses to infringement that may 538 | otherwise be available to you under applicable patent law. 539 | 540 | 12. No Surrender of Others' Freedom. 541 | 542 | If conditions are imposed on you (whether by court order, agreement or 543 | otherwise) that contradict the conditions of this License, they do not 544 | excuse you from the conditions of this License. If you cannot convey a 545 | covered work so as to satisfy simultaneously your obligations under this 546 | License and any other pertinent obligations, then as a consequence you may 547 | not convey it at all. For example, if you agree to terms that obligate you 548 | to collect a royalty for further conveying from those to whom you convey 549 | the Program, the only way you could satisfy both those terms and this 550 | License would be to refrain entirely from conveying the Program. 551 | 552 | 13. Use with the GNU Affero General Public License. 553 | 554 | Notwithstanding any other provision of this License, you have 555 | permission to link or combine any covered work with a work licensed 556 | under version 3 of the GNU Affero General Public License into a single 557 | combined work, and to convey the resulting work. The terms of this 558 | License will continue to apply to the part which is the covered work, 559 | but the special requirements of the GNU Affero General Public License, 560 | section 13, concerning interaction through a network will apply to the 561 | combination as such. 562 | 563 | 14. Revised Versions of this License. 564 | 565 | The Free Software Foundation may publish revised and/or new versions of 566 | the GNU General Public License from time to time. Such new versions will 567 | be similar in spirit to the present version, but may differ in detail to 568 | address new problems or concerns. 569 | 570 | Each version is given a distinguishing version number. If the 571 | Program specifies that a certain numbered version of the GNU General 572 | Public License "or any later version" applies to it, you have the 573 | option of following the terms and conditions either of that numbered 574 | version or of any later version published by the Free Software 575 | Foundation. If the Program does not specify a version number of the 576 | GNU General Public License, you may choose any version ever published 577 | by the Free Software Foundation. 578 | 579 | If the Program specifies that a proxy can decide which future 580 | versions of the GNU General Public License can be used, that proxy's 581 | public statement of acceptance of a version permanently authorizes you 582 | to choose that version for the Program. 583 | 584 | Later license versions may give you additional or different 585 | permissions. However, no additional obligations are imposed on any 586 | author or copyright holder as a result of your choosing to follow a 587 | later version. 588 | 589 | 15. Disclaimer of Warranty. 590 | 591 | THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY 592 | APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT 593 | HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY 594 | OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, 595 | THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 596 | PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM 597 | IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF 598 | ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 599 | 600 | 16. Limitation of Liability. 601 | 602 | IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 603 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS 604 | THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY 605 | GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE 606 | USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF 607 | DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD 608 | PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), 609 | EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF 610 | SUCH DAMAGES. 611 | 612 | 17. Interpretation of Sections 15 and 16. 613 | 614 | If the disclaimer of warranty and limitation of liability provided 615 | above cannot be given local legal effect according to their terms, 616 | reviewing courts shall apply local law that most closely approximates 617 | an absolute waiver of all civil liability in connection with the 618 | Program, unless a warranty or assumption of liability accompanies a 619 | copy of the Program in return for a fee. 620 | 621 | END OF TERMS AND CONDITIONS 622 | 623 | How to Apply These Terms to Your New Programs 624 | 625 | If you develop a new program, and you want it to be of the greatest 626 | possible use to the public, the best way to achieve this is to make it 627 | free software which everyone can redistribute and change under these terms. 628 | 629 | To do so, attach the following notices to the program. It is safest 630 | to attach them to the start of each source file to most effectively 631 | state the exclusion of warranty; and each file should have at least 632 | the "copyright" line and a pointer to where the full notice is found. 633 | 634 | 635 | Copyright (C) 636 | 637 | This program is free software: you can redistribute it and/or modify 638 | it under the terms of the GNU General Public License as published by 639 | the Free Software Foundation, either version 3 of the License, or 640 | (at your option) any later version. 641 | 642 | This program is distributed in the hope that it will be useful, 643 | but WITHOUT ANY WARRANTY; without even the implied warranty of 644 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 645 | GNU General Public License for more details. 646 | 647 | You should have received a copy of the GNU General Public License 648 | along with this program. If not, see . 649 | 650 | Also add information on how to contact you by electronic and paper mail. 651 | 652 | If the program does terminal interaction, make it output a short 653 | notice like this when it starts in an interactive mode: 654 | 655 | Copyright (C) 656 | This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. 657 | This is free software, and you are welcome to redistribute it 658 | under certain conditions; type `show c' for details. 659 | 660 | The hypothetical commands `show w' and `show c' should show the appropriate 661 | parts of the General Public License. Of course, your program's commands 662 | might be different; for a GUI interface, you would use an "about box". 663 | 664 | You should also get your employer (if you work as a programmer) or school, 665 | if any, to sign a "copyright disclaimer" for the program, if necessary. 666 | For more information on this, and how to apply and follow the GNU GPL, see 667 | . 668 | 669 | The GNU General Public License does not permit incorporating your program 670 | into proprietary programs. If your program is a subroutine library, you 671 | may consider it more useful to permit linking proprietary applications with 672 | the library. If this is what you want to do, use the GNU Lesser General 673 | Public License instead of this License. But first, please read 674 | . 675 | --------------------------------------------------------------------------------