diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 8fc4fcb087..b32cb0e3f8 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -44,11 +44,10 @@ jobs: - "lilygo-ble" - "esp32dev-multi_receiver" - "tinypico-ble" - - "ttgo-lora32-v1-868" - - "ttgo-lora32-v1-915" + - "ttgo-lora32-v1" + - "ttgo-lora32-v21" - "ttgo-t-beam" - - "heltec-wifi-lora-32-868" - - "heltec-wifi-lora-32-915" + - "heltec-wifi-lora-32" - "shelly-plus1" - "nodemcuv2-all-test" - "nodemcuv2-fastled-test" diff --git a/docs/img/OpenMQTTGateway_LORA_Configuration.png b/docs/img/OpenMQTTGateway_LORA_Configuration.png new file mode 100644 index 0000000000..03c43e4fa3 Binary files /dev/null and b/docs/img/OpenMQTTGateway_LORA_Configuration.png differ diff --git a/docs/img/OpenMQTTgateway_ESP32_LORA_MSG.png b/docs/img/OpenMQTTgateway_ESP32_LORA_MSG.png new file mode 100644 index 0000000000..0eb0865b48 Binary files /dev/null and b/docs/img/OpenMQTTgateway_ESP32_LORA_MSG.png differ diff --git a/docs/use/lora.md b/docs/use/lora.md index adf66938e6..39ca494a13 100644 --- a/docs/use/lora.md +++ b/docs/use/lora.md @@ -1,11 +1,39 @@ --- title: LORA MQTT Gateway -description: Explore the LORA MQTT gateway, designed for integration with devices utilizing LORA technology, including the MakerFab soil and moisture sensor. Unlock long range communication with the power of LORA.. +description: Explore the LORA MQTT gateway, designed for integration with devices utilizing LORA technology, including the MakerFab soil and moisture sensor, devices from PricelessTookit. Unlock long range communication with the power of LORA.. --- # LORA gateway -Tutorial on how to leverage LORA for a mailbox sensor from [PricelessToolkit](https://www.youtube.com/channel/UCz75N6inuLHXnRC5tqagNLw): - +## What is a LORA gateway +A LoRa (Long Range) gateway is a device that facilitates communication between LoRa nodes and networks, enabling the transmission and reception of data over long distances using the LoRa modulation technique. It's designed to work with devices that utilize LoRa technology, such as the MakerFab soil and moisture sensor, devices from PricelessTookit and DIY sensors. + +The primary distinction between a LoRa gateway and a LoRaWAN gateway lies in the protocol and network architecture: + +LoRa Gateway: Focuses solely on the physical layer, utilizing the LoRa modulation for communication. It's responsible for receiving and transmitting raw LoRa signals without concerning itself with network protocols or data handling at higher layers. Being focused solely on the physical layer, a LoRa Gateway offers greater flexibility for customization and experimentation. The OpenMQTTGateway LoRa gateway receives raw LoRa signals, processes them, and publishes the data to an MQTT topic. Conversely, it can subscribe to MQTT topics and send commands to LoRa devices. This gateway is particularly useful for DIY projects, home automation enthusiasts, and scenarios where direct integration of LoRa devices with MQTT is desired. + +LoRaWAN Gateway: Operates at a higher layer and is part of the LoRaWAN network architecture. LoRaWAN is a protocol specification built on top of the LoRa technology, providing features like adaptive data rate, encryption, and multi-channel/multi-modulation. A LoRaWAN gateway handles the data from multiple LoRa nodes, forwards it to a centralized network server, which then manages the data and communicates back to the nodes. + +In essence, while both gateways utilize LoRa technology for communication, a LoRaWAN gateway is more sophisticated, offering advanced features and integration with the LoRaWAN network infrastructure. The LoRa gateway, with its simpler architecture, is ideal for small networks of nodes, offering easier setup and configuration, making it an interesting choice for users keen on experimenting with LoRa technology. + +## Configuring the LORA gateway + +The LORA gateway can be configured by MQTT commands or by using the WebUI, here are the parameters available, they can be combined with the key "save" or "erase": +* txpower: 0 to 14 +* spreadingfactor: 7 to 12 +* frequency: 433000000, 868000000, 915000000 +* signalbandwidth: 7800, 10400, 15500, 20800, 31250, 41700, 62500, 125000, and 250000 +* codingrate: 5 to 8 +* preamblelength: 6 to 65535 +* syncword: byte +* enablecrc: boolean +* invertiq: boolean +* onlyknown: boolean + +With the WebUI: +![LORA configuration page](../img/OpenMQTTGateway_LORA_Configuration.png) + +With MQTT commands: +`mosquitto_pub -t home/OpenMQTTGateway/commands/MQTTtoLORA/config -m '{"frequency":"433000000","save":true}'` ## Receiving data from LORA signal @@ -13,22 +41,24 @@ Subscribe to all the messages with mosquitto or open your MQTT client software: ` sudo mosquitto_sub -t +/# -v` -Generate your LORA signals by using another TTGO LORA module, you can flash the sender program from [this repository](https://github.com/LilyGO/TTGO-LORA32-V2.0) and the SSD1306 library [there](https://github.com/ThingPulse/esp8266-oled-ssd1306) - -![TTGO Lora sending packets](../img/OpenMQTTGateway_TTGO32_LORA_Send.jpg) +Generate your LORA signals by using another LORA module, you can flash the sender program from [this example](../../examples/LoraTemperature/) to an ESP32 LORA board, this sample node will generate a LORa signal containing the ESP32 internal temperature. -Once one board flashed with OMG and the other with the sender program you should receive regular packets into `home/OpenMQTTGateway_ESP32_LORA_TEST/LORAtoMQTT` like below: +Once one board flashed with OMG and the other with the sender program you should receive regular packets into `home/OpenMQTTGateway_ESP32_LORA_TEST/LORAtoMQTT/AABBCCDDEEFF` like below: ```json -{"rssi":-16,"snr":9.25,"pferror":-3598,"packetSize":9,"message":"hello 35"} -{"rssi":-26,"snr":9,"pferror":-3598,"packetSize":9,"message":"hello 36"} -{"rssi":-16,"snr":9.5,"pferror":-3581,"packetSize":9,"message":"hello 37"} +{"id":"AA:BB:CC:DD:EE:FF","rssi":-16,"snr":9.25,"pferror":-3598,"packetSize":9,"tempc":"55.3"} +{"id":"AA:BB:CC:DD:EE:FF","rssi":-26,"snr":9,"pferror":-3598,"packetSize":9,"tempc":"55.4"} +{"id":"AA:BB:CC:DD:EE:FF","rssi":-16,"snr":9.5,"pferror":-3581,"packetSize":9,"tempc":"57"} ``` +![LORA board receiving data](../img/OpenMQTTgateway_ESP32_LORA_MSG.png) + Messages that contain non-printable characters will be converted to hexadecimal and look like this: ```json {"rssi":-121,"snr":-11.75,"pferror":-29116,"packetSize":3,"hex":"C0FFEE"} ``` +They can be filtered by setting the "onlyknown" command to `true` or by an activation into the WebUI or Home Assistant. + And from a supported device (in this case, a WiPhone), looks like this: ```json {"rssi":-50,"snr":9.25,"pferror":20728,"packetSize":30,"from":"123ABC","to":"000000","message":"Hi from WiPhone","type":"WiPhone"} @@ -44,23 +74,14 @@ If you want to test that your sending works you can use another TTGO LORA module ## Send data by MQTT with advanced LORA parameters -LORA sending support the following parameters that should be specified in the json message; -* txpower: 2 to 20 -* spreadingfactor: 6 to 12. If a spreading factor of 6 is set, implicit header mode must be used to transmit and receive * * packets. -* frequency: 433E6, 866E6, 915E6 -* signalbandwidth: 7.8E3, 10.4E3, 15.6E3, 20.8E3, 31.25E3, 41.7E3, 62.5E3, 125E3, and 250E3 -* codingrate: 5 to 8 -* preamblelength: 6 to 65535 -* syncword: byte -* enablecrc: boolean - -More info on where the LORA library is born [@sandeepmistry](https://github.com/sandeepmistry/arduino-LoRa/blob/master/API.md#radio-parameters) - -Examples: - * Plain text message: `mosquitto_pub -t home/OpenMQTTGateway/commands/MQTTtoLORA -m '{"message":"test8","txpower":17}'`\ will make LORA use the a txpower of 17 when sending the message "test8" * Binary message: `mosquitto_pub -t "home/OpenMQTTGateway/commands/MQTTtoLORA" -m '{"hex":"01C0FFEE"}'`\ will send binary 0x01C0FFEE * WiPhone message: `mosquitto_pub -t "home/OpenMQTTGateway/commands/MQTTtoLORA" -m '{"message":"test","type":"WiPhone","to":"123ABC","from":"FFFFFF"}'`\ will send "test" to a WiPhone with chip ID 123ABC + +More info on where the LORA library is born [@sandeepmistry](https://github.com/sandeepmistry/arduino-LoRa/blob/master/API.md#radio-parameters) + +Tutorial on how to leverage LORA for a mailbox sensor from [PricelessToolkit](https://www.youtube.com/channel/UCz75N6inuLHXnRC5tqagNLw): + diff --git a/environments.ini b/environments.ini index dcb8db0b49..36e9e521da 100644 --- a/environments.ini +++ b/environments.ini @@ -869,33 +869,50 @@ board_build.flash_mode = dio custom_description = BLE gateway custom_hardware = ESP32 TinyPICO -[env:ttgo-lora32-v1-868] +[env:ttgo-lora32-v1] platform = ${com.esp32_platform} board = ttgo-lora32-v1 lib_deps = ${com-esp32.lib_deps} + ${libraries.ssd1306} ${libraries.lora} build_flags = ${com-esp32.build_flags} '-DZgatewayLORA="LORA"' '-DLORA_BAND=868E6' + '-DZdisplaySSD1306="LilyGo_SSD1306"' + '-DSDA_OLED=4' + '-DSCL_OLED=15' + '-DRST_OLED=16' + '-DWIFI_LoRa_32=true' '-DGateway_Name="OMG_ESP32_LORA"' -custom_description = LORA communication 868Mhz using arduino-LoRA +custom_description = LORA communication using arduino-LoRA configurable frequency custom_hardware= ESP32 TTGO LORA V1 -[env:ttgo-lora32-v1-915] +[env:ttgo-lora32-v21] platform = ${com.esp32_platform} -board = ttgo-lora32-v1 +board = ttgo-lora32-v21 lib_deps = ${com-esp32.lib_deps} + ${libraries.ssd1306} ${libraries.lora} build_flags = ${com-esp32.build_flags} - '-DZgatewayLORA="LORA"' - '-DLORA_BAND=915E6' +; *** OpenMQTTGateway Config *** + ;'-UZmqttDiscovery' ; disables MQTT Discovery '-DGateway_Name="OMG_ESP32_LORA"' -custom_description = LORA communication 915Mhz using arduino-LoRA -custom_hardware= ESP32 TTGO LORA V1 +; *** OpenMQTTGateway Modules *** + '-DZgatewayLORA="LORA"' +; *** ssd1306 Display Options *** + '-DZdisplaySSD1306="LilyGo_SSD1306"' +; '-DLOG_TO_OLED=true' ; Enable log to OLED +; '-DJSON_TO_OLED=true' +; '-DLOG_LEVEL_OLED=LOG_LEVEL_NOTICE' +; '-DDISPLAY_IDLE_LOGO=false' +; '-DDISPLAY_BRIGHTNESS=80' +; '-DDISPLAY_METRIC=false' +custom_description = For ESP32, Gateway using LORA +custom_hardware = ESP32 LILYGO LoRa32 V2.1 [env:ttgo-t-beam] # See version pinout differences here @@ -928,7 +945,7 @@ build_flags = custom_description = BLE gateway with battery holder custom_hardware = TTGO T BEAM -[env:heltec-wifi-lora-32-868] ; Heltec ESP32 Board with SSD1306 display +[env:heltec-wifi-lora-32] ; Heltec ESP32 Board with SSD1306 display platform = ${com.esp32_platform} board = heltec_wifi_lora_32 lib_deps = @@ -952,34 +969,7 @@ build_flags = ; '-DDISPLAY_IDLE_LOGO=false' ; '-DDISPLAY_BRIGHTNESS=80' ; '-DDISPLAY_METRIC=false' -custom_description = LORA communication 868Mhz using arduino-LoRA -custom_hardware = ESP32 HELTEC LORA32 - -[env:heltec-wifi-lora-32-915] ; Heltec ESP32 Board with SSD1306 display -platform = ${com.esp32_platform} -board = heltec_wifi_lora_32 -lib_deps = - ${com-esp32.lib_deps} - ${libraries.lora} - ${libraries.ssd1306} -build_flags = - ${com-esp32.build_flags} -; *** OpenMQTTGateway Modules *** - '-DZgatewayLORA="LORA"' - '-DLORA_BAND=915E6' - '-DGateway_Name="OMG_ESP32_LORA"' - '-DLED_SEND_RECEIVE=25' - '-DTimeLedON=0.1' - '-DLED_SEND_RECEIVE_ON=1' -; *** ssd1306 Display Options *** - '-DZdisplaySSD1306="HELTEC_SSD1306"' -; '-DLOG_TO_OLED=true' ; Enable log to OLED -; '-DJSON_TO_OLED=true' -; '-DLOG_LEVEL_OLED=LOG_LEVEL_NOTICE' -; '-DDISPLAY_IDLE_LOGO=false' -; '-DDISPLAY_BRIGHTNESS=80' -; '-DDISPLAY_METRIC=false' -custom_description = LORA communication 915Mhz using arduino-LoRA +custom_description = LORA communication using arduino-LoRA custom_hardware = ESP32 HELTEC LORA32 ; For testing only, not for production use diff --git a/examples/LoraTemperature/.gitignore b/examples/LoraTemperature/.gitignore new file mode 100644 index 0000000000..89cc49cbd6 --- /dev/null +++ b/examples/LoraTemperature/.gitignore @@ -0,0 +1,5 @@ +.pio +.vscode/.browse.c_cpp.db* +.vscode/c_cpp_properties.json +.vscode/launch.json +.vscode/ipch diff --git a/examples/LoraTemperature/README.md b/examples/LoraTemperature/README.md new file mode 100644 index 0000000000..9efaa5c176 --- /dev/null +++ b/examples/LoraTemperature/README.md @@ -0,0 +1,59 @@ +# OpenMQTTGateway LoRa Node Example +This repository contains an example of a LoRa node program designed for the ESP32 platform. The program reads the internal temperature of the ESP32, packages the data into a JSON format, and sends it over LoRa. + +## Features: +* Uses an SX12XX LoRa module. +* Displays packet sending status and temperature data on an SSD1306 OLED display. +* Sends the ESP32's MAC address as the node ID. +* Sends temperature data in Celsius. + +## Hardware Requirements: +* ESP32 development board. +* SX12XX LoRa module. +* SSD1306 OLED display. + +## Pin Configuration: +* SCK - GPIO5 +* MISO - GPIO19 +* MOSI - GPIO27 +* SS - GPIO18 +* RST - GPIO14 +* DI0 - GPIO26 + +## Setup: +### Hardware Setup: + +Connect the SX1278 LoRa module and the SSD1306 OLED display to the ESP32 according to the pin configuration. +Ensure that the OLED display is powered correctly. + +### Software Setup: + +* Clone this repository. +* Open the provided node program with PlatformIO +* Upload the program to your ESP32. + +## Usage: +Power on the ESP32. +The OLED display will show the status of the packet being sent and the current temperature reading. +The built-in LED on the ESP32 will blink once every time a packet is sent. +Monitor the serial output (at 115200 baud rate) to see the JSON formatted data being sent. + +## Data Format: +The data is sent in the following JSON format: + +```json +{ + "model": "ESP32TEMP", + "id": "ESP32_MAC_ADDRESS", + "tempc": "TEMPERATURE_IN_CELSIUS" +} +``` + +## Troubleshooting: +LoRa Initialization Failed: Ensure that the SX1278 LoRa module is connected correctly and powered on. +OLED Display Not Working: Check the connections and ensure that the display is powered correctly. +No Temperature Data: Ensure that the ESP32's internal temperature sensor is functional. +Contributing: +Feel free to contribute to this example by opening issues or submitting pull requests. Any feedback or improvements are welcome! + +I hope this README helps users understand and use your program! Adjustments can be made as necessary to fit any additional details or changes. \ No newline at end of file diff --git a/examples/LoraTemperature/platformio.ini b/examples/LoraTemperature/platformio.ini new file mode 100644 index 0000000000..bba2b7f513 --- /dev/null +++ b/examples/LoraTemperature/platformio.ini @@ -0,0 +1,36 @@ +; PlatformIO Project Configuration File +; +; Build options: build flags, source filter +; Upload options: custom upload port, speed and extra flags +; Library options: dependencies, extra library storages +; Advanced options: extra scripting +; +; Please visit documentation for the other options and examples +; https://docs.platformio.org/page/projectconf.html + +[env:ttgo-lora32-v1] +platform = espressif32 +board = ttgo-lora32-v1 +framework = arduino +lib_deps = + https://github.com/sandeepmistry/arduino-LoRa.git#f4a1d27 + https://github.com/ThingPulse/esp8266-oled-ssd1306.git#f96fd6a +monitor_speed = 115200 + +[env:ttgo-lora32-v21] +platform = espressif32 +board = ttgo-lora32-v21 +framework = arduino +lib_deps = + https://github.com/sandeepmistry/arduino-LoRa.git#f4a1d27 + https://github.com/ThingPulse/esp8266-oled-ssd1306.git#f96fd6a +monitor_speed = 115200 + +[env:heltec-wifi-lora-32] ; Heltec ESP32 Board with SSD1306 display +platform = ${com.esp32_platform} +board = heltec_wifi_lora_32 +framework = arduino +lib_deps = + https://github.com/sandeepmistry/arduino-LoRa.git#f4a1d27 + https://github.com/ThingPulse/esp8266-oled-ssd1306.git#f96fd6a +monitor_speed = 115200 \ No newline at end of file diff --git a/examples/LoraTemperature/src/main.cpp b/examples/LoraTemperature/src/main.cpp new file mode 100644 index 0000000000..f8fb9d6f26 --- /dev/null +++ b/examples/LoraTemperature/src/main.cpp @@ -0,0 +1,104 @@ +#include +#include +#include +#include +#include + +#include "SSD1306.h" +#include "rom/ets_sys.h" +#include "soc/rtc_cntl_reg.h" +#include "soc/sens_reg.h" + +#define SCK 5 // GPIO5 -- SX1278's SCK +#define MISO 19 // GPIO19 -- SX1278's MISnO +#define MOSI 27 // GPIO27 -- SX1278's MOSI +#define SS 18 // GPIO18 -- SX1278's CS +#define RST 14 // GPIO14 -- SX1278's RESET +#define DI0 26 // GPIO26 -- SX1278's IRQ(Interrupt Request) +#define BAND 915E6 + +unsigned int counter = 0; + +SSD1306 display(0x3c, 4, 15); +String rssi = "RSSI --"; +String packSize = "--"; +String packet; + +float intTemperatureRead() { + SET_PERI_REG_BITS(SENS_SAR_MEAS_WAIT2_REG, SENS_FORCE_XPD_SAR, 3, + SENS_FORCE_XPD_SAR_S); + SET_PERI_REG_BITS(SENS_SAR_TSENS_CTRL_REG, SENS_TSENS_CLK_DIV, 10, + SENS_TSENS_CLK_DIV_S); + CLEAR_PERI_REG_MASK(SENS_SAR_TSENS_CTRL_REG, SENS_TSENS_POWER_UP); + CLEAR_PERI_REG_MASK(SENS_SAR_TSENS_CTRL_REG, SENS_TSENS_DUMP_OUT); + SET_PERI_REG_MASK(SENS_SAR_TSENS_CTRL_REG, SENS_TSENS_POWER_UP_FORCE); + SET_PERI_REG_MASK(SENS_SAR_TSENS_CTRL_REG, SENS_TSENS_POWER_UP); + ets_delay_us(100); + SET_PERI_REG_MASK(SENS_SAR_TSENS_CTRL_REG, SENS_TSENS_DUMP_OUT); + ets_delay_us(5); + float temp_f = (float)GET_PERI_REG_BITS2(SENS_SAR_SLAVE_ADDR3_REG, + SENS_TSENS_OUT, SENS_TSENS_OUT_S); + float temp_c = (temp_f - 32) / 1.8; + return temp_c; +} + +void setup() { + pinMode(16, OUTPUT); + pinMode(2, OUTPUT); + + digitalWrite(16, LOW); // set GPIO16 low to reset OLED + delay(50); + digitalWrite(16, HIGH); // while OLED is running, must set GPIO16 in high + + Serial.begin(115200); + while (!Serial) + ; + Serial.println(); + Serial.println("LoRa Sender Test"); + + SPI.begin(SCK, MISO, MOSI, SS); + LoRa.setPins(SS, RST, DI0); + if (!LoRa.begin(BAND)) { + Serial.println("Starting LoRa failed!"); + while (1) + ; + } + + Serial.println("init ok"); + display.init(); + display.flipScreenVertically(); + display.setFont(ArialMT_Plain_10); + + delay(1500); +} + +void loop() { + display.clear(); + display.setTextAlignment(TEXT_ALIGN_LEFT); + display.setFont(ArialMT_Plain_10); + + display.drawString(0, 0, "Sending packet: "); + display.drawString(90, 0, String(counter)); + + String NodeId = WiFi.macAddress(); + float temp = intTemperatureRead(); + // send packet + LoRa.beginPacket(); + // Build json string to send + String msg = "{\"model\":\"ESP32TEMP\",\"id\":\"" + NodeId + "\",\"tempc\":" + String(temp) + "}"; + // Send json string + LoRa.print(msg); + LoRa.endPacket(); + + Serial.println(String(msg)); + + display.drawString(0, 15, String(NodeId)); + display.drawString(0, 30, "tempc: " + String(temp) + " C"); + display.display(); + + counter++; + digitalWrite(2, HIGH); // turn the LED on (HIGH is the voltage level) + delay(1000); // wait for a second + digitalWrite(2, LOW); // turn the LED off by making the voltage LOW + delay(60000); // wait for 60 seconds +} \ No newline at end of file diff --git a/main/User_config.h b/main/User_config.h index a81a566246..ee413427d6 100644 --- a/main/User_config.h +++ b/main/User_config.h @@ -719,7 +719,7 @@ Adafruit_NeoPixel leds2(ANEOPIX_IND_NUM_LEDS, ANEOPIX_IND_DATA_GPIO2, ANEOPIX_IN #endif /*-----------PLACEHOLDERS FOR WebUI DISPLAY--------------*/ -#define pubWebUI(...) // display the published message onto the OLED display +#define pubWebUI(...) // display the published message onto the WebUI display /*-----------PLACEHOLDERS FOR OLED/LCD DISPLAY--------------*/ // The real definitions are in config_M5.h / config_SSD1306.h diff --git a/main/ZgatewayLORA.ino b/main/ZgatewayLORA.ino index 48ac2ee17e..511bb601e8 100644 --- a/main/ZgatewayLORA.ino +++ b/main/ZgatewayLORA.ino @@ -37,6 +37,141 @@ # define WIPHONE_MESSAGE_MIN_LEN sizeof(wiphone_message) - WIPHONE_MAX_MESSAGE_LEN # define WIPHONE_MAX_MESSAGE_LEN 230 +LORAConfig_s LORAConfig; + +# ifdef ZmqttDiscovery +SemaphoreHandle_t semaphorecreateOrUpdateDeviceLORA; +std::vector LORAdevices; +int newLORADevices = 0; + +static LORAdevice NO_LORA_DEVICE_FOUND = {{0}, + 0, + false}; + +LORAdevice* getDeviceById(const char* id); // Declared here to avoid pre-compilation issue (misplaced auto declaration by pio) +LORAdevice* getDeviceById(const char* id) { + Log.trace(F("getDeviceById %s" CR), id); + + for (std::vector::iterator it = LORAdevices.begin(); it != LORAdevices.end(); ++it) { + if ((strcmp((*it)->uniqueId, id) == 0)) { + return *it; + } + } + return &NO_LORA_DEVICE_FOUND; +} + +void dumpLORADevices() { + for (std::vector::iterator it = LORAdevices.begin(); it != LORAdevices.end(); ++it) { + LORAdevice* p = *it; + Log.trace(F("uniqueId %s" CR), p->uniqueId); + Log.trace(F("modelName %s" CR), p->modelName); + Log.trace(F("isDisc %d" CR), p->isDisc); + } +} + +void createOrUpdateDeviceLORA(const char* id, const char* model, uint8_t flags) { + if (xSemaphoreTake(semaphorecreateOrUpdateDeviceLORA, pdMS_TO_TICKS(30000)) == pdFALSE) { + Log.error(F("[LORA] semaphorecreateOrUpdateDeviceLORA Semaphore NOT taken" CR)); + return; + } + + LORAdevice* device = getDeviceById(id); + if (device == &NO_LORA_DEVICE_FOUND) { + Log.trace(F("add %s" CR), id); + //new device + device = new LORAdevice(); + if (strlcpy(device->uniqueId, id, uniqueIdSize) > uniqueIdSize) { + Log.warning(F("[LORA] Device id %s exceeds available space" CR), id); // Remove from production release ? + }; + if (strlcpy(device->modelName, model, modelNameSize) > modelNameSize) { + Log.warning(F("[LORA] Device model %s exceeds available space" CR), id); // Remove from production release ? + }; + device->isDisc = flags & device_flags_isDisc; + LORAdevices.push_back(device); + newLORADevices++; + } else { + Log.trace(F("update %s" CR), id); + + if (flags & device_flags_isDisc) { + device->isDisc = true; + } + } + + xSemaphoreGive(semaphorecreateOrUpdateDeviceLORA); +} + +// This function always should be called from the main core as it generates direct mqtt messages +// When overrideDiscovery=true, we publish discovery messages of known LORAdevices (even if no new) +void launchLORADiscovery(bool overrideDiscovery) { + if (!overrideDiscovery && newLORADevices == 0) + return; + if (xSemaphoreTake(semaphorecreateOrUpdateDeviceLORA, pdMS_TO_TICKS(1000)) == pdFALSE) { + Log.error(F("[LORA] semaphorecreateOrUpdateDeviceLORA Semaphore NOT taken" CR)); + return; + } + newLORADevices = 0; + std::vector localDevices = LORAdevices; + xSemaphoreGive(semaphorecreateOrUpdateDeviceLORA); + for (std::vector::iterator it = localDevices.begin(); it != localDevices.end(); ++it) { + LORAdevice* pdevice = *it; + Log.trace(F("Device id %s" CR), pdevice->uniqueId); + // Do not launch discovery for the LORAdevices already discovered (unless we have overrideDiscovery) or that are not unique by their MAC Address (Ibeacon, GAEN and Microsoft Cdp) + if (overrideDiscovery || !isDiscovered(pdevice)) { + size_t numRows = sizeof(LORAparameters) / sizeof(LORAparameters[0]); + for (int i = 0; i < numRows; i++) { + if (strstr(pdevice->uniqueId, LORAparameters[i][0]) != 0) { + // Remove the key from the unique id to extract the device id + String idWoKey = pdevice->uniqueId; + idWoKey.remove(idWoKey.length() - (strlen(LORAparameters[i][0]) + 1)); + Log.trace(F("idWoKey %s" CR), idWoKey.c_str()); + String value_template = "{{ value_json." + String(LORAparameters[i][0]) + " | is_defined }}"; + + String topic = idWoKey; + topic = String(subjectLORAtoMQTT) + "/" + topic; + + createDiscovery("sensor", //set Type + (char*)topic.c_str(), LORAparameters[i][1], pdevice->uniqueId, //set state_topic,name,uniqueId + "", LORAparameters[i][3], (char*)value_template.c_str(), //set availability_topic,device_class,value_template, + "", "", LORAparameters[i][2], //set,payload_on,payload_off,unit_of_meas, + 0, //set off_delay + "", "", false, "", //set,payload_available,payload_not available ,is a gateway entity, command topic + (char*)idWoKey.c_str(), "", pdevice->modelName, (char*)idWoKey.c_str(), false, // device name, device manufacturer, device model, device ID, retain + stateClassMeasurement //State Class + ); + pdevice->isDisc = true; // we don't need the semaphore and all the search magic via createOrUpdateDevice + dumpLORADevices(); + break; + } + } + if (!pdevice->isDisc) { + Log.trace(F("Device id %s was not discovered" CR), pdevice->uniqueId); // Remove from production release ? + } + } else { + Log.trace(F("Device already discovered or that doesn't require discovery %s" CR), pdevice->uniqueId); + } + } +} + +void storeLORADiscovery(JsonObject& RFLORA_ESPdata, const char* model, const char* uniqueid) { + //Sanitize model name + String modelSanitized = model; + modelSanitized.replace(" ", "_"); + modelSanitized.replace("/", "_"); + modelSanitized.replace(".", "_"); + modelSanitized.replace("&", ""); + + //Sensors translation matrix for sensors that requires statistics by using stateClassMeasurement + size_t numRows = sizeof(LORAparameters) / sizeof(LORAparameters[0]); + + for (int i = 0; i < numRows; i++) { + if (RFLORA_ESPdata.containsKey(LORAparameters[i][0])) { + String key_id = String(uniqueid) + "-" + String(LORAparameters[i][0]); + createOrUpdateDeviceLORA((char*)key_id.c_str(), (char*)modelSanitized.c_str(), device_flags_init); + } + } +} +# endif + typedef struct __attribute__((packed)) { // WiPhone uses RadioHead library which has additional (unused) headers uint8_t rh_to; @@ -127,7 +262,109 @@ boolean _MQTTtoWiPhone(JsonObject& LORAdata) { return true; } +void LORAConfig_init() { + LORAConfig.frequency = LORA_BAND; + LORAConfig.txPower = LORA_TX_POWER; + LORAConfig.spreadingFactor = LORA_SPREADING_FACTOR; + LORAConfig.signalBandwidth = LORA_SIGNAL_BANDWIDTH; + LORAConfig.codingRateDenominator = LORA_CODING_RATE; + LORAConfig.preambleLength = LORA_PREAMBLE_LENGTH; + LORAConfig.syncWord = LORA_SYNC_WORD; + LORAConfig.crc = DEFAULT_CRC; + LORAConfig.invertIQ = INVERT_IQ; + LORAConfig.onlyKnown = LORA_ONLY_KNOWN; +} + +void LORAConfig_load() { + StaticJsonDocument jsonBuffer; + preferences.begin(Gateway_Short_Name, true); + if (preferences.isKey("LORAConfig")) { + auto error = deserializeJson(jsonBuffer, preferences.getString("LORAConfig", "{}")); + preferences.end(); + Log.notice(F("LORA Config loaded" CR)); + if (error) { + Log.error(F("LORA Config deserialization failed: %s, buffer capacity: %u" CR), error.c_str(), jsonBuffer.capacity()); + return; + } + if (jsonBuffer.isNull()) { + Log.warning(F("LORA Config is null" CR)); + return; + } + JsonObject jo = jsonBuffer.as(); + LORAConfig_fromJson(jo); + Log.notice(F("LORA Config loaded" CR)); + } else { + preferences.end(); + Log.notice(F("LORA Config not found" CR)); + } +} + +byte hexStringToByte(const String& hexString) { + return (byte)strtol(hexString.c_str(), NULL, 16); +} + +void LORAConfig_fromJson(JsonObject& LORAdata) { + Config_update(LORAdata, "frequency", LORAConfig.frequency); + Config_update(LORAdata, "txpower", LORAConfig.txPower); + Config_update(LORAdata, "spreadingfactor", LORAConfig.spreadingFactor); + Config_update(LORAdata, "signalbandwidth", LORAConfig.signalBandwidth); + Config_update(LORAdata, "codingrate", LORAConfig.codingRateDenominator); + Config_update(LORAdata, "preamblelength", LORAConfig.preambleLength); + Config_update(LORAdata, "onlyknown", LORAConfig.onlyKnown); + // Handle syncword separately + if (LORAdata.containsKey("syncword")) { + String syncWordStr = LORAdata["syncword"].as(); + LORAConfig.syncWord = hexStringToByte(syncWordStr); + Log.notice(F("Config syncword changed: %d" CR), LORAConfig.syncWord); + } + Config_update(LORAdata, "enablecrc", LORAConfig.crc); + Config_update(LORAdata, "invertiq", LORAConfig.invertIQ); + + LoRa.setFrequency(LORAConfig.frequency); + LoRa.setTxPower(LORAConfig.txPower); + LoRa.setSpreadingFactor(LORAConfig.spreadingFactor); + LoRa.setSignalBandwidth(LORAConfig.signalBandwidth); + LoRa.setCodingRate4(LORAConfig.codingRateDenominator); + LoRa.setPreambleLength(LORAConfig.preambleLength); + LoRa.setSyncWord(LORAConfig.syncWord); + LORAConfig.crc ? LoRa.enableCrc() : LoRa.disableCrc(); + LORAConfig.invertIQ ? LoRa.enableInvertIQ() : LoRa.disableInvertIQ(); + + if (LORAdata.containsKey("erase") && LORAdata["erase"].as()) { + // Erase config from NVS (non-volatile storage) + preferences.begin(Gateway_Short_Name, false); + if (preferences.isKey("LORAConfig")) { + int result = preferences.remove("LORAConfig"); + Log.notice(F("LORA config erase result: %d" CR), result); + preferences.end(); + return; // Erase prevails on save, so skipping save + } else { + Log.notice(F("LORA config not found" CR)); + preferences.end(); + } + } + if (LORAdata.containsKey("save") && LORAdata["save"].as()) { + StaticJsonDocument jsonBuffer; + JsonObject jo = jsonBuffer.to(); + jo["frequency"] = LORAConfig.frequency; + // Save config into NVS (non-volatile storage) + String conf = ""; + serializeJson(jsonBuffer, conf); + preferences.begin(Gateway_Short_Name, false); + int result = preferences.putString("LORAConfig", conf); + preferences.end(); + Log.notice(F("LORA Config_save: %s, result: %d" CR), conf.c_str(), result); + } +} + void setupLORA() { + LORAConfig_init(); + LORAConfig_load(); +# ifdef ZmqttDiscovery + semaphorecreateOrUpdateDeviceLORA = xSemaphoreCreateBinary(); + xSemaphoreGive(semaphorecreateOrUpdateDeviceLORA); +# endif + Log.notice(F("LORA Frequency: %d" CR), LORAConfig.frequency); # ifdef ESP8266 SPI.begin(); # else @@ -136,7 +373,7 @@ void setupLORA() { LoRa.setPins(LORA_SS, LORA_RST, LORA_DI0); - if (!LoRa.begin(LORA_BAND)) { + if (!LoRa.begin(LORAConfig.frequency)) { Log.error(F("ZgatewayLORA setup failed!" CR)); while (1) ; @@ -148,12 +385,6 @@ void setupLORA() { Log.notice(F("LORA_SS: %d" CR), LORA_SS); Log.notice(F("LORA_RST: %d" CR), LORA_RST); Log.notice(F("LORA_DI0: %d" CR), LORA_DI0); - LoRa.setTxPower(LORA_TX_POWER); - LoRa.setSpreadingFactor(LORA_SPREADING_FACTOR); - LoRa.setSignalBandwidth(LORA_SIGNAL_BANDWIDTH); - LoRa.setCodingRate4(LORA_CODING_RATE); - LoRa.setPreambleLength(LORA_PREAMBLE_LENGTH); - LoRa.setSyncWord(LORA_SYNC_WORD); Log.trace(F("ZgatewayLORA setup done" CR)); } @@ -179,16 +410,14 @@ void LORAtoMQTT() { } // Terminate with a null character in case we have a string packet[packetSize] = 0; - - LORAdata["rssi"] = (int)LoRa.packetRssi(); - LORAdata["snr"] = (float)LoRa.packetSnr(); - LORAdata["pferror"] = (float)LoRa.packetFrequencyError(); - LORAdata["packetSize"] = (int)packetSize; - uint8_t deviceId = _determineDevice(packet, packetSize); if (deviceId == WIPHONE) { _WiPhoneToMQTT(packet, LORAdata); } else if (binary) { + if (LORAConfig.onlyKnown) { + Log.trace(F("Ignoring non identifiable packet" CR)); + return; + } // We have non-ascii data: create hex string of the data char hex[packetSize * 2 + 1]; _rawToHex(packet, hex, packetSize); @@ -198,9 +427,37 @@ void LORAtoMQTT() { LORAdata["hex"] = hex; } else { // ascii payload - LORAdata["message"] = packet; + deserializeJson(jsonBuffer, packet, packetSize); + } + + LORAdata["rssi"] = (int)LoRa.packetRssi(); + LORAdata["snr"] = (float)LoRa.packetSnr(); + LORAdata["pferror"] = (float)LoRa.packetFrequencyError(); + LORAdata["packetSize"] = (int)packetSize; + + if (LORAdata.containsKey("id")) { + // Replace ":" in topic + std::string topic = LORAdata["id"].as(); + size_t pos = topic.find(":"); + while (pos != std::string::npos) { + topic.erase(pos, 1); + pos = topic.find(":", pos); + } + std::string id = topic; + std::string subjectStr(subjectLORAtoMQTT); + topic = subjectStr + "/" + topic; + +# ifdef ZmqttDiscovery + if (SYSConfig.discovery) { + if (!LORAdata.containsKey("model")) + LORAdata["model"] = "LORA_NODE"; + storeLORADiscovery(LORAdata, LORAdata["model"].as(), id.c_str()); + } +# endif + pub(topic.c_str(), LORAdata); + } else { + pub(subjectLORAtoMQTT, LORAdata); } - pub(subjectLORAtoMQTT, LORAdata); if (repeatLORAwMQTT) { Log.trace(F("Pub LORA for rpt" CR)); pub(subjectMQTTtoLORA, LORAdata); @@ -214,33 +471,8 @@ void MQTTtoLORA(char* topicOri, JsonObject& LORAdata) { // json object decoding Log.trace(F("MQTTtoLORA json" CR)); const char* message = LORAdata["message"]; const char* hex = LORAdata["hex"]; - int txPower = LORAdata["txpower"] | LORA_TX_POWER; - int spreadingFactor = LORAdata["spreadingfactor"] | LORA_SPREADING_FACTOR; - long int frequency = LORAdata["frequency "] | LORA_BAND; - long int signalBandwidth = LORAdata["signalbandwidth"] | LORA_SIGNAL_BANDWIDTH; - int codingRateDenominator = LORAdata["codingrate"] | LORA_CODING_RATE; - int preambleLength = LORAdata["preamblelength"] | LORA_PREAMBLE_LENGTH; - byte syncWord = LORAdata["syncword"] | LORA_SYNC_WORD; - bool crc = LORAdata["enablecrc"] | DEFAULT_CRC; - bool invertIQ = LORAdata["invertiq"] | INVERT_IQ; + LORAConfig_fromJson(LORAdata); if (message || hex) { - LoRa.setTxPower(txPower); - LoRa.setFrequency(frequency); - LoRa.setSpreadingFactor(spreadingFactor); - LoRa.setSignalBandwidth(signalBandwidth); - LoRa.setCodingRate4(codingRateDenominator); - LoRa.setPreambleLength(preambleLength); - LoRa.setSyncWord(syncWord); - if (crc) - LoRa.enableCrc(); - else - LoRa.disableCrc(); - - if (invertIQ) - LoRa.enableInvertIQ(); - else - LoRa.disableInvertIQ(); - LoRa.beginPacket(); uint8_t deviceId = _determineDevice(LORAdata); if (deviceId == WIPHONE) { @@ -262,6 +494,26 @@ void MQTTtoLORA(char* topicOri, JsonObject& LORAdata) { // json object decoding Log.error(F("MQTTtoLORA Fail json" CR)); } } + if (cmpToMainTopic(topicOri, subjectMQTTtoLORAset)) { + Log.trace(F("MQTTtoLORA json set" CR)); + /* + * Configuration modifications priorities: + * First `init=true` and `load=true` commands are executed (if both are present, INIT prevails on LOAD) + * Then parameters included in json are taken in account + * Finally `erase=true` and `save=true` commands are executed (if both are present, ERASE prevails on SAVE) + */ + if (LORAdata.containsKey("init") && LORAdata["init"].as()) { + // Restore the default (initial) configuration + LORAConfig_init(); + } else if (LORAdata.containsKey("load") && LORAdata["load"].as()) { + // Load the saved configuration, if not initialised + LORAConfig_load(); + } + + // Load config from json if available + LORAConfig_fromJson(LORAdata); + stateLORAMeasures(); + } } # endif # if simpleReceiving @@ -275,4 +527,28 @@ void MQTTtoLORA(char* topicOri, char* LORAdata) { // json object decoding } } # endif +String stateLORAMeasures() { + //Publish LORA state + StaticJsonDocument jsonBuffer; + JsonObject LORAdata = jsonBuffer.to(); + LORAdata["frequency"] = LORAConfig.frequency; + LORAdata["txpower"] = LORAConfig.txPower; + LORAdata["spreadingfactor"] = LORAConfig.spreadingFactor; + LORAdata["signalbandwidth"] = LORAConfig.signalBandwidth; + LORAdata["codingrate"] = LORAConfig.codingRateDenominator; + LORAdata["preamblelength"] = LORAConfig.preambleLength; + // Convert syncWord to a hexadecimal string and store it in the JSON + char syncWordHex[5]; // Enough space for 0xXX and null terminator + snprintf(syncWordHex, sizeof(syncWordHex), "0x%02X", LORAConfig.syncWord); + LORAdata["syncword"] = syncWordHex; + LORAdata["enablecrc"] = LORAConfig.crc; + LORAdata["invertiq"] = LORAConfig.invertIQ; + LORAdata["onlyknown"] = LORAConfig.onlyKnown; + + pub(subjectGTWLORAtoMQTT, LORAdata); + + String output; + serializeJson(LORAdata, output); + return output; +} #endif diff --git a/main/ZmqttDiscovery.ino b/main/ZmqttDiscovery.ino index 92ecb937cb..ba008a44e8 100644 --- a/main/ZmqttDiscovery.ino +++ b/main/ZmqttDiscovery.ino @@ -1031,6 +1031,39 @@ void pubMqttDiscovery() { "", "", "", "", false, // device name, device manufacturer, device model, device ID, retain stateClassNone //State Class ); + + createDiscovery("switch", //set Type + subjectLORAtoMQTT, "LORA: CRC", (char*)getUniqueId("enablecrc", "").c_str(), //set state_topic,name,uniqueId + will_Topic, "", "{{ value_json.enablecrc }}", //set availability_topic,device_class,value_template, + "{\"enablecrc\":true,\"save\":true}", "{\"enablecrc\":false,\"save\":true}", "", //set,payload_on,payload_off,unit_of_meas, + 0, //set off_delay + Gateway_AnnouncementMsg, will_Message, true, subjectMQTTtoLORAset, //set,payload_available,payload_not available ,is a gateway entity, command topic + "", "", "", "", false, // device name, device manufacturer, device model, device MAC, retain + stateClassNone, //State Class + "false", "true" //state_off, state_on + ); + + createDiscovery("switch", //set Type + subjectLORAtoMQTT, "LORA: Invert IQ", (char*)getUniqueId("invertiq", "").c_str(), //set state_topic,name,uniqueId + will_Topic, "", "{{ value_json.invertiq }}", //set availability_topic,device_class,value_template, + "{\"invertiq\":true,\"save\":true}", "{\"invertiq\":false,\"save\":true}", "", //set,payload_on,payload_off,unit_of_meas, + 0, //set off_delay + Gateway_AnnouncementMsg, will_Message, true, subjectMQTTtoLORAset, //set,payload_available,payload_not available ,is a gateway entity, command topic + "", "", "", "", false, // device name, device manufacturer, device model, device MAC, retain + stateClassNone, //State Class + "false", "true" //state_off, state_on + ); + + createDiscovery("switch", //set Type + subjectLORAtoMQTT, "LORA: Only Known", (char*)getUniqueId("onlyknown", "").c_str(), //set state_topic,name,uniqueId + will_Topic, "", "{{ value_json.onlyknown }}", //set availability_topic,device_class,value_template, + "{\"onlyknown\":true,\"save\":true}", "{\"onlyknown\":false,\"save\":true}", "", //set,payload_on,payload_off,unit_of_meas, + 0, //set off_delay + Gateway_AnnouncementMsg, will_Message, true, subjectMQTTtoLORAset, //set,payload_available,payload_not available ,is a gateway entity, command topic + "", "", "", "", false, // device name, device manufacturer, device model, device MAC, retain + stateClassNone, //State Class + "false", "true" //state_off, state_on + ); # endif # ifdef ZgatewaySRFB diff --git a/main/ZwebUI.ino b/main/ZwebUI.ino index 0640e779c4..a11a0a0d2e 100644 --- a/main/ZwebUI.ino +++ b/main/ZwebUI.ino @@ -852,6 +852,146 @@ void handleLO() { server.send(200, "text/html", response); } +# ifdef ZgatewayLORA +/** + * @brief /LA - Configure LORA Page + * T: handleLA: uri: /la, args: 11, method: 1 + * T: handleLA Arg: 0, lf=868100000 + * T: handleLA Arg: 1, lt=14 + * T: handleLA Arg: 2, ls=12 + * T: handleLA Arg: 3, lb=125 + * T: handleLA Arg: 4, lc=5 + * T: handleLA Arg: 5, ll=8 + * T: handleLA Arg: 6, lw=0 + * T: handleLA Arg: 7, lr=1 + * T: handleLA Arg: 8, li=0 + * T: handleLA Arg: 9, ok=0 + * T: handleLA Arg: 10, save= + */ +void handleLA() { + WEBUI_TRACE_LOG(F("handleLA: uri: %s, args: %d, method: %d" CR), server.uri(), server.args(), server.method()); + WEBUI_SECURE + if (server.args()) { + for (uint8_t i = 0; i < server.args(); i++) { + WEBUI_TRACE_LOG(F("handleLA Arg: %d, %s=%s" CR), i, server.argName(i).c_str(), server.arg(i).c_str()); + } + if (server.hasArg("save")) { + StaticJsonDocument jsonBuffer; + JsonObject WEBtoLORA = jsonBuffer.to(); + + if (server.hasArg("lf")) { + WEBtoLORA["frequency"] = server.arg("lf"); + } + + if (server.hasArg("lt")) { + WEBtoLORA["txpower"] = server.arg("lt"); + } + + if (server.hasArg("ls")) { + WEBtoLORA["spreadingfactor"] = server.arg("ls"); + } + + if (server.hasArg("lb")) { + WEBtoLORA["signalbandwidth"] = server.arg("lb"); + } + + if (server.hasArg("lc")) { + WEBtoLORA["codingrate"] = server.arg("lc"); + } + + if (server.hasArg("ll")) { + WEBtoLORA["preamblelength"] = server.arg("ll"); + } + + if (server.hasArg("lw")) { + WEBtoLORA["syncword"] = server.arg("lw"); + } + + if (server.hasArg("lr")) { + WEBtoLORA["enablecrc"] = server.arg("lr"); + } else { + WEBtoLORA["enablecrc"] = false; + } + + if (server.hasArg("li")) { + WEBtoLORA["invertiq"] = server.arg("li"); + } else { + WEBtoLORA["invertiq"] = false; + } + + if (server.hasArg("ok")) { + WEBtoLORA["onlyknown"] = server.arg("ok"); + } else { + WEBtoLORA["onlyknown"] = false; + } + + LORAConfig_fromJson(WEBtoLORA); + } + } + char jsonChar[100]; + serializeJson(modules, jsonChar, measureJson(modules) + 1); + + char buffer[WEB_TEMPLATE_BUFFER_MAX_SIZE]; + snprintf(buffer, WEB_TEMPLATE_BUFFER_MAX_SIZE, header_html, (String(gateway_name) + " - Configure LORA").c_str()); + String response = String(buffer); + response += String(script); + response += String(style); + snprintf(buffer, WEB_TEMPLATE_BUFFER_MAX_SIZE, config_lora_body, + jsonChar, + gateway_name, + LORAConfig.frequency == 868000000 ? "selected" : "", + LORAConfig.frequency == 915000000 ? "selected" : "", + LORAConfig.frequency == 433000000 ? "selected" : "", + LORAConfig.txPower == 0 ? "selected" : "", + LORAConfig.txPower == 1 ? "selected" : "", + LORAConfig.txPower == 2 ? "selected" : "", + LORAConfig.txPower == 3 ? "selected" : "", + LORAConfig.txPower == 4 ? "selected" : "", + LORAConfig.txPower == 5 ? "selected" : "", + LORAConfig.txPower == 6 ? "selected" : "", + LORAConfig.txPower == 7 ? "selected" : "", + LORAConfig.txPower == 8 ? "selected" : "", + LORAConfig.txPower == 9 ? "selected" : "", + LORAConfig.txPower == 10 ? "selected" : "", + LORAConfig.txPower == 11 ? "selected" : "", + LORAConfig.txPower == 12 ? "selected" : "", + LORAConfig.txPower == 13 ? "selected" : "", + LORAConfig.txPower == 14 ? "selected" : "", + LORAConfig.spreadingFactor == 7 ? "selected" : "", + LORAConfig.spreadingFactor == 8 ? "selected" : "", + LORAConfig.spreadingFactor == 9 ? "selected" : "", + LORAConfig.spreadingFactor == 10 ? "selected" : "", + LORAConfig.spreadingFactor == 11 ? "selected" : "", + LORAConfig.spreadingFactor == 12 ? "selected" : "", + LORAConfig.signalBandwidth == 7800 ? "selected" : "", + LORAConfig.signalBandwidth == 10400 ? "selected" : "", + LORAConfig.signalBandwidth == 15600 ? "selected" : "", + LORAConfig.signalBandwidth == 20800 ? "selected" : "", + LORAConfig.signalBandwidth == 31250 ? "selected" : "", + LORAConfig.signalBandwidth == 41700 ? "selected" : "", + LORAConfig.signalBandwidth == 62500 ? "selected" : "", + LORAConfig.signalBandwidth == 125000 ? "selected" : "", + LORAConfig.signalBandwidth == 250000 ? "selected" : "", + LORAConfig.signalBandwidth == 500000 ? "selected" : "", + LORAConfig.codingRateDenominator == 5 ? "selected" : "", + LORAConfig.codingRateDenominator == 6 ? "selected" : "", + LORAConfig.codingRateDenominator == 7 ? "selected" : "", + LORAConfig.codingRateDenominator == 8 ? "selected" : "", + LORAConfig.preambleLength, + LORAConfig.syncWord, + LORAConfig.crc ? "checked" : "", + LORAConfig.invertIQ ? "checked" : "", + LORAConfig.onlyKnown ? "checked" : ""); + + response += String(buffer); + snprintf(buffer, WEB_TEMPLATE_BUFFER_MAX_SIZE, footer, OMG_VERSION); + response += String(buffer); + server.send(200, "text/html", response); + stateLORAMeasures(); + Log.trace(F("[WebUI] LORAConfig end" CR)); +} +# endif + /** * @brief /RT - Reset configuration ( Erase and Restart ) from Configuration menu * @@ -1015,6 +1155,10 @@ void handleIN() { # if defined(ZgatewayCloud) informationDisplay += "1
Cloud}2}1"; informationDisplay += stateCLOUDStatus(); +# endif +# if defined(ZgatewayLORA) + informationDisplay += "1
LORA}2}1"; + informationDisplay += stateLORAMeasures(); # endif informationDisplay += "1
WebUI}2}1"; informationDisplay += stateWebUIStatus(); @@ -1276,6 +1420,9 @@ void WebUISetup() { server.on("/wi", handleWI); // Configure Wifi server.on("/mq", handleMQ); // Configure MQTT server.on("/wu", handleWU); // Configure WebUI +# ifdef ZgatewayLORA + server.on("/la", handleLA); // Configure LORA +# endif # if defined(ZgatewayCloud) server.on("/cl", handleCL); // Configure Cloud server.on("/tk", handleTK); // Store Device Token @@ -2019,6 +2166,58 @@ void webUIPubPrint(const char* topicori, JsonObject& data) { break; } # endif +# ifdef ZgatewayLORA + case webUIHash("LORAtoMQTT"): { + // {"tempc":25.4,"hum":0,"batt":0} + + String line1 = ""; + if (data.containsKey("tempc")) { + char temp[5]; + float temperature_C = data["tempc"]; + + if (displayMetric) { + dtostrf(temperature_C, 3, 1, temp); + line1 = "temp: " + (String)temp + "°C "; + } else { + dtostrf(convertTemp_CtoF(temperature_C), 3, 1, temp); + line1 = "temp: " + (String)temp + "°F "; + } + } + line1.toCharArray(message->line1, WEBUI_TEXT_WIDTH); + + // Line 2 + + String line2 = ""; + float humidity = data["hum"]; + if (data.containsKey("hum") && humidity <= 100 && humidity >= 0) { + char hum[5]; + dtostrf(humidity, 3, 1, hum); + line2 += "hum: " + (String)hum + "% "; + } + line2.toCharArray(message->line2, WEBUI_TEXT_WIDTH); + + // Line 3 + + String line3 = ""; + float adc = data["adc"]; + if (data.containsKey("adc") && adc <= 100 && adc >= 0) { + char cAdc[5]; + dtostrf(adc, 3, 1, cAdc); + line3 += "adc: " + (String)cAdc + "µS/cm "; + } + line3.toCharArray(message->line2, WEBUI_TEXT_WIDTH); + + // Queue completed message + + if (xQueueSend(webUIQueue, (void*)&message, 0) != pdTRUE) { + Log.error(F("[ WebUI ] webUIQueue full, discarding signal %s" CR), message->title); + free(message); + } else { + // Log.notice(F("[ WebUI ] Queued %s" CR), message->title); + } + break; + } +# endif default: Log.verbose(F("[ WebUI ] unhandled topic %s" CR), message->title); free(message); diff --git a/main/config_LORA.h b/main/config_LORA.h index 5a147e2a1c..2db7f66351 100644 --- a/main/config_LORA.h +++ b/main/config_LORA.h @@ -34,19 +34,39 @@ extern void MQTTtoLORA(char* topicOri, JsonObject& RFdata); #define subjectLORAtoMQTT "/LORAtoMQTT" #define subjectMQTTtoLORA "/commands/MQTTtoLORA" #define subjectGTWLORAtoMQTT "/LORAtoMQTT" +#define subjectMQTTtoLORAset "/commands/MQTTtoLORA/config" //Default parameters used when the parameters are not set in the json data #ifndef LORA_BAND # define LORA_BAND 868E6 #endif -#define LORA_SIGNAL_BANDWIDTH 125E3 -#define LORA_TX_POWER 17 -#define LORA_SPREADING_FACTOR 7 -#define LORA_CODING_RATE 5 -#define LORA_PREAMBLE_LENGTH 8 -#define LORA_SYNC_WORD 0x12 -#define DEFAULT_CRC true -#define INVERT_IQ false +#ifndef LORA_SIGNAL_BANDWIDTH +# define LORA_SIGNAL_BANDWIDTH 125E3 +#endif +#ifndef LORA_TX_POWER +# define LORA_TX_POWER 14 +#endif +#ifndef LORA_SPREADING_FACTOR +# define LORA_SPREADING_FACTOR 7 +#endif +#ifndef LORA_CODING_RATE +# define LORA_CODING_RATE 5 +#endif +#ifndef LORA_PREAMBLE_LENGTH +# define LORA_PREAMBLE_LENGTH 8 +#endif +#ifndef LORA_SYNC_WORD +# define LORA_SYNC_WORD 0x12 +#endif +#ifndef DEFAULT_CRC +# define DEFAULT_CRC true +#endif +#ifndef INVERT_IQ +# define INVERT_IQ false +#endif +#ifndef LORA_ONLY_KNOWN +# define LORA_ONLY_KNOWN false +#endif #define repeatLORAwMQTT false // do we repeat a received signal by using MQTT with LORA gateway @@ -78,4 +98,39 @@ extern void MQTTtoLORA(char* topicOri, JsonObject& RFdata); # define LORA_DI0 26 // GPIO26 -- SX1278's IRQ(Interrupt Request) #endif +struct LORAConfig_s { + long frequency; + int txPower; + int spreadingFactor; + long signalBandwidth; + int codingRateDenominator; + int preambleLength; + byte syncWord; + bool crc; + bool invertIQ; + bool onlyKnown; +}; + +#ifdef ZmqttDiscovery +extern void launchLORADiscovery(bool overrideDiscovery); +// This structure stores the entities of the devices and is they have been discovered or not +// The uniqueId is composed of the device id + the key + +# define uniqueIdSize 30 +# define modelNameSize 30 + +struct LORAdevice { + char uniqueId[uniqueIdSize]; + char modelName[modelNameSize]; + bool isDisc; +}; + +const char LORAparameters[3][4][12] = { + // LORA key, name, unit, device_class + {"tempc", "temperature", "°C", "temperature"}, + {"hum", "humidity", "%", "humidity"}, + {"moi", "moisture", "%", "humidity"}}; + +#endif + #endif diff --git a/main/config_WebContent.h b/main/config_WebContent.h index 0ace34d128..90a7b2f189 100644 --- a/main/config_WebContent.h +++ b/main/config_WebContent.h @@ -50,7 +50,11 @@ #endif #define configure_4 "

" #define configure_5 "

" -#define configure_6 +#ifdef ZgatewayLORA +# define configure_6 "

" +#else +# define configure_6 +#endif #define configure_7 #define configure_8 @@ -98,6 +102,88 @@ const char config_logging_body[] = body_header "
Configure WebUI

Display Metric

Secure WebUI


" body_footer_config_menu; +const char config_lora_body[] = body_header + "
" + "Configure LORA" + "
" + + "

Frequency
" + "

" + + "

TX Power
" + "

" + + "

Spreading Factor
" + "

" + + "

Signal Bandwidth
" + "

" + + "

Coding Rate
" + "

" + + "

Preamble Length
" + "

" + + "

Sync Word
" + "

" + + "

CRC
" + "

" + + "

Invert IQ
" + "

" + + "

Only known
" + "

" + + "
" + "
" + "
" body_footer_config_menu; + const char footer[] = ""; // Source file - https://github.com/1technophile/OpenMQTTGateway/blob/54decb4b65c7894b926ac3a89de0c6b2a3021506/docs/.vuepress/public/favicon-16x16.png diff --git a/main/config_WebUI.h b/main/config_WebUI.h index 6249374b99..ca978a02e8 100644 --- a/main/config_WebUI.h +++ b/main/config_WebUI.h @@ -34,7 +34,7 @@ /*------------------- Optional Compiler Directives ----------------------*/ #ifndef WEB_TEMPLATE_BUFFER_MAX_SIZE -# define WEB_TEMPLATE_BUFFER_MAX_SIZE 2000 +# define WEB_TEMPLATE_BUFFER_MAX_SIZE 3000 // Max size of the template buffer #endif #ifndef DISPLAY_METRIC diff --git a/main/main.ino b/main/main.ino index b76fc787c5..2a64da9b42 100644 --- a/main/main.ino +++ b/main/main.ino @@ -1869,6 +1869,9 @@ void loop() { # ifdef ZdisplaySSD1306 stateSSD1306Display(); # endif +# ifdef ZgatewayLORA + stateLORAMeasures(); +# endif # if defined(ZwebUI) && defined(ESP32) stateWebUIStatus(); # endif @@ -1944,6 +1947,10 @@ void loop() { #endif #ifdef ZgatewayLORA LORAtoMQTT(); +# ifdef ZmqttDiscovery + if (SYSConfig.discovery) + launchLORADiscovery(publishDiscovery); +# endif #endif #ifdef ZgatewayRF RFtoMQTT(); diff --git a/platformio.ini b/platformio.ini index a634df017f..68930056c1 100644 --- a/platformio.ini +++ b/platformio.ini @@ -66,11 +66,10 @@ extra_configs = ;default_envs = lilygo-ble ;default_envs = esp32dev-multi_receiver ;default_envs = tinypico-ble -;default_envs = ttgo-lora32-v1-868 -;default_envs = ttgo-lora32-v1-915 +;default_envs = ttgo-lora32-v1 +;default_envs = ttgo-lora32-v21 ;default_envs = ttgo-t-beam -;default_envs = heltec-wifi-lora-32-868 -;default_envs = heltec-wifi-lora-32-915 +;default_envs = heltec-wifi-lora-32 ;default_envs = shelly-plus1 ;default_envs = nodemcuv2-all-test ;default_envs = nodemcuv2-fastled-test