mirror of
https://github.com/1technophile/OpenMQTTGateway.git
synced 2026-02-20 00:32:04 +01:00
This fixes a race condition where saved LORA configuration was applied to the hardware BEFORE LoRa.begin() was called, causing settings like syncword to be lost after reset/power cycle. Changes: - Created LORAConfig_apply() to centralize hardware configuration - Modified LORAConfig_load() to accept applyToHardware parameter - During boot: load config into struct first, apply AFTER LoRa.begin() - During runtime: apply immediately since hardware is initialized - Fixed syncword to properly log "unchanged" vs "changed" status Fixes #2270 Co-authored-by: Florian <1technophile@users.noreply.github.com>
609 lines
23 KiB
C++
609 lines
23 KiB
C++
/*
|
|
Theengs OpenMQTTGateway - We Unite Sensors in One Open-Source Interface
|
|
|
|
Act as a wifi or ethernet gateway between your 433mhz/infrared IR/BLE signal and a MQTT broker
|
|
Send and receiving command by MQTT
|
|
|
|
This gateway enables to:
|
|
- receive MQTT data from a topic and send LORA signal corresponding to the received MQTT data
|
|
- publish MQTT data to a different topic related to received LORA signal
|
|
|
|
Copyright: (c)Florian ROBERT
|
|
|
|
This file is part of OpenMQTTGateway.
|
|
|
|
OpenMQTTGateway is free software: you can redistribute it and/or modify
|
|
it under the terms of the GNU General Public License as published by
|
|
the Free Software Foundation, either version 3 of the License, or
|
|
(at your option) any later version.
|
|
|
|
OpenMQTTGateway is distributed in the hope that it will be useful,
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
GNU General Public License for more details.
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
*/
|
|
#include "User_config.h"
|
|
|
|
#ifdef ZgatewayLORA
|
|
# include <LoRa.h>
|
|
# include <SPI.h>
|
|
# include <TheengsUtils.h>
|
|
# include <Wire.h>
|
|
|
|
# include "TheengsCommon.h"
|
|
# include "config_LORA.h"
|
|
# include "config_mqttDiscovery.h"
|
|
|
|
# define WIPHONE_MESSAGE_MAGIC 0x6c6d
|
|
# define WIPHONE_MESSAGE_MIN_LEN sizeof(wiphone_message) - WIPHONE_MAX_MESSAGE_LEN
|
|
# define WIPHONE_MAX_MESSAGE_LEN 230
|
|
|
|
LORAConfig_s LORAConfig;
|
|
|
|
void LORAConfig_fromJson(JsonObject& LORAdata, bool applyToHardware = true);
|
|
void LORAConfig_apply();
|
|
String stateLORAMeasures();
|
|
|
|
# ifdef ZmqttDiscovery
|
|
# include "config_mqttDiscovery.h"
|
|
extern void createDiscovery(const char* sensor_type,
|
|
const char* st_topic, const char* s_name, const char* unique_id,
|
|
const char* availability_topic, const char* device_class, const char* value_template,
|
|
const char* payload_on, const char* payload_off, const char* unit_of_meas,
|
|
int off_delay,
|
|
const char* payload_available, const char* payload_not_available, bool gateway_entity, const char* cmd_topic,
|
|
const char* device_name, const char* device_manufacturer, const char* device_model, const char* device_id, bool retainCmd,
|
|
const char* state_class, const char* state_off, const char* state_on, const char* enum_options, const char* command_template);
|
|
|
|
SemaphoreHandle_t semaphorecreateOrUpdateDeviceLORA;
|
|
std::vector<LORAdevice*> 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) {
|
|
THEENGS_LOG_TRACE(F("getDeviceById %s" CR), id);
|
|
|
|
for (std::vector<LORAdevice*>::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<LORAdevice*>::iterator it = LORAdevices.begin(); it != LORAdevices.end(); ++it) {
|
|
LORAdevice* p = *it;
|
|
THEENGS_LOG_TRACE(F("uniqueId %s" CR), p->uniqueId);
|
|
THEENGS_LOG_TRACE(F("modelName %s" CR), p->modelName);
|
|
THEENGS_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) {
|
|
THEENGS_LOG_ERROR(F("[LORA] semaphorecreateOrUpdateDeviceLORA Semaphore NOT taken" CR));
|
|
return;
|
|
}
|
|
|
|
LORAdevice* device = getDeviceById(id);
|
|
if (device == &NO_LORA_DEVICE_FOUND) {
|
|
THEENGS_LOG_TRACE(F("add %s" CR), id);
|
|
//new device
|
|
device = new LORAdevice();
|
|
if (strlcpy(device->uniqueId, id, uniqueIdSize) > uniqueIdSize) {
|
|
THEENGS_LOG_WARNING(F("[LORA] Device id %s exceeds available space" CR), id); // Remove from production release ?
|
|
};
|
|
if (strlcpy(device->modelName, model, modelNameSize) > modelNameSize) {
|
|
THEENGS_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 {
|
|
THEENGS_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(QueueSemaphoreTimeOutLoop)) == pdFALSE) {
|
|
THEENGS_LOG_ERROR(F("[LORA] semaphorecreateOrUpdateDeviceLORA Semaphore NOT taken" CR));
|
|
return;
|
|
}
|
|
newLORADevices = 0;
|
|
std::vector<LORAdevice*> localDevices = LORAdevices;
|
|
xSemaphoreGive(semaphorecreateOrUpdateDeviceLORA);
|
|
for (std::vector<LORAdevice*>::iterator it = localDevices.begin(); it != localDevices.end(); ++it) {
|
|
LORAdevice* pdevice = *it;
|
|
THEENGS_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));
|
|
THEENGS_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) {
|
|
THEENGS_LOG_TRACE(F("Device id %s was not discovered" CR), pdevice->uniqueId); // Remove from production release ?
|
|
}
|
|
} else {
|
|
THEENGS_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;
|
|
uint8_t rh_from;
|
|
uint8_t rh_id;
|
|
uint8_t rh_flags;
|
|
uint16_t magic;
|
|
uint32_t to;
|
|
uint32_t from;
|
|
char message[WIPHONE_MAX_MESSAGE_LEN];
|
|
} wiphone_message;
|
|
|
|
enum LORA_ID_NUM {
|
|
UNKNOWN_DEVICE = -1,
|
|
WIPHONE,
|
|
};
|
|
typedef enum LORA_ID_NUM LORA_ID_NUM;
|
|
|
|
/*
|
|
Try and determine device given the payload
|
|
*/
|
|
uint8_t _determineDevice(byte* packet, int packetSize) {
|
|
// Check WiPhone header
|
|
if (packetSize >= WIPHONE_MESSAGE_MIN_LEN && ((wiphone_message*)packet)->magic == WIPHONE_MESSAGE_MAGIC)
|
|
return WIPHONE;
|
|
|
|
// No matches
|
|
return UNKNOWN_DEVICE;
|
|
}
|
|
|
|
/*
|
|
Try and determine device given the JSON type
|
|
*/
|
|
uint8_t _determineDevice(JsonObject& LORAdata) {
|
|
const char* protocol_name = LORAdata["type"];
|
|
|
|
// No type provided
|
|
if (!protocol_name)
|
|
return UNKNOWN_DEVICE;
|
|
|
|
if (strcmp(protocol_name, "WiPhone") == 0)
|
|
return WIPHONE;
|
|
|
|
// No matches
|
|
return UNKNOWN_DEVICE;
|
|
}
|
|
|
|
/*
|
|
Create JSON information from WiPhone packet
|
|
*/
|
|
boolean _WiPhonetoX(byte* packet, JsonObject& LORAdata) {
|
|
// Decode the LoRa packet and send over MQTT
|
|
wiphone_message* msg = (wiphone_message*)packet;
|
|
|
|
// Set the header information
|
|
char from[9] = {0};
|
|
char to[9] = {0};
|
|
snprintf(from, 9, "%06X", msg->from);
|
|
snprintf(to, 9, "%06X", msg->to);
|
|
|
|
// From and To are the last 3 octets from the WiPhone's ESP32 chip ID
|
|
// Special case is 0x000000: "broadcast"
|
|
LORAdata["from"] = from;
|
|
LORAdata["to"] = to;
|
|
|
|
LORAdata["message"] = msg->message;
|
|
LORAdata["type"] = "WiPhone";
|
|
return true;
|
|
}
|
|
|
|
/*
|
|
Create WiPhone packet from JSON
|
|
*/
|
|
boolean _MQTTtoWiPhone(JsonObject& LORAdata) {
|
|
// Prepare a LoRa packet to send to the WiPhone
|
|
wiphone_message wiphonemsg;
|
|
wiphonemsg.rh_to = 0xff;
|
|
wiphonemsg.rh_from = 0xff;
|
|
wiphonemsg.rh_id = 0x00;
|
|
wiphonemsg.rh_flags = 0x00;
|
|
|
|
wiphonemsg.magic = WIPHONE_MESSAGE_MAGIC;
|
|
wiphonemsg.from = strtol(LORAdata["from"], NULL, 16);
|
|
wiphonemsg.to = strtol(LORAdata["to"], NULL, 16);
|
|
const char* message = LORAdata["message"];
|
|
strlcpy(wiphonemsg.message, message, WIPHONE_MAX_MESSAGE_LEN);
|
|
LoRa.write((uint8_t*)&wiphonemsg, strlen(message) + WIPHONE_MESSAGE_MIN_LEN + 1);
|
|
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(bool applyToHardware) {
|
|
StaticJsonDocument<JSON_MSG_BUFFER> jsonBuffer;
|
|
preferences.begin(Gateway_Short_Name, true);
|
|
if (preferences.isKey("LORAConfig")) {
|
|
auto error = deserializeJson(jsonBuffer, preferences.getString("LORAConfig", "{}"));
|
|
preferences.end();
|
|
THEENGS_LOG_NOTICE(F("LORA Config loaded" CR));
|
|
if (error) {
|
|
THEENGS_LOG_ERROR(F("LORA Config deserialization failed: %s, buffer capacity: %u" CR), error.c_str(), jsonBuffer.capacity());
|
|
return;
|
|
}
|
|
if (jsonBuffer.isNull()) {
|
|
THEENGS_LOG_WARNING(F("LORA Config is null" CR));
|
|
return;
|
|
}
|
|
JsonObject jo = jsonBuffer.as<JsonObject>();
|
|
LORAConfig_fromJson(jo, applyToHardware);
|
|
THEENGS_LOG_NOTICE(F("LORA Config loaded" CR));
|
|
} else {
|
|
preferences.end();
|
|
THEENGS_LOG_NOTICE(F("LORA Config not found" CR));
|
|
}
|
|
}
|
|
|
|
byte hexStringToByte(const String& hexString) {
|
|
return (byte)strtol(hexString.c_str(), NULL, 16);
|
|
}
|
|
|
|
void LORAConfig_apply() {
|
|
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();
|
|
}
|
|
|
|
void LORAConfig_fromJson(JsonObject& LORAdata, bool applyToHardware) {
|
|
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 due to hex string conversion
|
|
if (LORAdata.containsKey("syncword")) {
|
|
String syncWordStr = LORAdata["syncword"].as<String>();
|
|
byte newSyncWord = hexStringToByte(syncWordStr);
|
|
if (LORAConfig.syncWord != newSyncWord) {
|
|
LORAConfig.syncWord = newSyncWord;
|
|
THEENGS_LOG_NOTICE(F("Config syncword changed to: %d" CR), LORAConfig.syncWord);
|
|
} else {
|
|
THEENGS_LOG_NOTICE(F("Config syncword unchanged, currently: %d" CR), LORAConfig.syncWord);
|
|
}
|
|
}
|
|
Config_update(LORAdata, "enablecrc", LORAConfig.crc);
|
|
Config_update(LORAdata, "invertiq", LORAConfig.invertIQ);
|
|
|
|
if (applyToHardware) {
|
|
LORAConfig_apply();
|
|
}
|
|
|
|
if (LORAdata.containsKey("erase") && LORAdata["erase"].as<bool>()) {
|
|
// Erase config from NVS (non-volatile storage)
|
|
preferences.begin(Gateway_Short_Name, false);
|
|
if (preferences.isKey("LORAConfig")) {
|
|
int result = preferences.remove("LORAConfig");
|
|
THEENGS_LOG_NOTICE(F("LORA config erase result: %d" CR), result);
|
|
preferences.end();
|
|
return; // Erase prevails on save, so skipping save
|
|
} else {
|
|
THEENGS_LOG_NOTICE(F("LORA config not found" CR));
|
|
preferences.end();
|
|
}
|
|
}
|
|
if (LORAdata.containsKey("save") && LORAdata["save"].as<bool>()) {
|
|
StaticJsonDocument<JSON_MSG_BUFFER> jsonBuffer;
|
|
JsonObject jo = jsonBuffer.to<JsonObject>();
|
|
jo["frequency"] = LORAConfig.frequency;
|
|
jo["txpower"] = LORAConfig.txPower;
|
|
jo["spreadingfactor"] = LORAConfig.spreadingFactor;
|
|
jo["signalbandwidth"] = LORAConfig.signalBandwidth;
|
|
jo["codingrate"] = LORAConfig.codingRateDenominator;
|
|
jo["preamblelength"] = LORAConfig.preambleLength;
|
|
if (LORAConfig.syncWord < 0 || LORAConfig.syncWord > 255) {
|
|
THEENGS_LOG_ERROR(F("Invalid syncWord value: %d" CR), LORAConfig.syncWord);
|
|
} else {
|
|
char syncWordHex[5];
|
|
snprintf(syncWordHex, sizeof(syncWordHex), "0x%02X", LORAConfig.syncWord);
|
|
jo["syncword"] = syncWordHex;
|
|
}
|
|
jo["enablecrc"] = LORAConfig.crc;
|
|
jo["invertiq"] = LORAConfig.invertIQ;
|
|
jo["onlyknown"] = LORAConfig.onlyKnown;
|
|
// 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();
|
|
THEENGS_LOG_NOTICE(F("LORA Config_save: %s, result: %d" CR), conf.c_str(), result);
|
|
}
|
|
}
|
|
|
|
void setupLORA() {
|
|
LORAConfig_init();
|
|
LORAConfig_load(false); // Load saved config into struct without applying to hardware yet
|
|
# ifdef ZmqttDiscovery
|
|
semaphorecreateOrUpdateDeviceLORA = xSemaphoreCreateBinary();
|
|
xSemaphoreGive(semaphorecreateOrUpdateDeviceLORA);
|
|
# endif
|
|
THEENGS_LOG_NOTICE(F("LORA Frequency: %d" CR), LORAConfig.frequency);
|
|
# ifdef ESP8266
|
|
SPI.begin();
|
|
# else
|
|
SPI.begin(LORA_SCK, LORA_MISO, LORA_MOSI, LORA_SS);
|
|
# endif
|
|
|
|
LoRa.setPins(LORA_SS, LORA_RST, LORA_DI0);
|
|
|
|
if (!LoRa.begin(LORAConfig.frequency)) {
|
|
THEENGS_LOG_ERROR(F("gatewayLORA setup failed!" CR));
|
|
while (1);
|
|
}
|
|
// Apply saved configuration to hardware now that LoRa is initialized
|
|
LORAConfig_apply();
|
|
LoRa.receive();
|
|
THEENGS_LOG_NOTICE(F("LORA_SCK: %d" CR), LORA_SCK);
|
|
THEENGS_LOG_NOTICE(F("LORA_MISO: %d" CR), LORA_MISO);
|
|
THEENGS_LOG_NOTICE(F("LORA_MOSI: %d" CR), LORA_MOSI);
|
|
THEENGS_LOG_NOTICE(F("LORA_SS: %d" CR), LORA_SS);
|
|
THEENGS_LOG_NOTICE(F("LORA_RST: %d" CR), LORA_RST);
|
|
THEENGS_LOG_NOTICE(F("LORA_DI0: %d" CR), LORA_DI0);
|
|
THEENGS_LOG_TRACE(F("gatewayLORA setup done" CR));
|
|
}
|
|
|
|
void LORAtoX() {
|
|
int packetSize = LoRa.parsePacket();
|
|
if (packetSize) {
|
|
StaticJsonDocument<JSON_MSG_BUFFER> LORAdataBuffer;
|
|
JsonObject LORAdata = LORAdataBuffer.to<JsonObject>();
|
|
THEENGS_LOG_TRACE(F("Rcv. LORA" CR));
|
|
# ifdef ESP32
|
|
String taskMessage = "LORA Task running on core ";
|
|
taskMessage = taskMessage + xPortGetCoreID();
|
|
//trc(taskMessage);
|
|
# endif
|
|
// Create packet and reserve null terminator space
|
|
byte packet[packetSize + 1];
|
|
boolean binary = false;
|
|
for (int i = 0; i < packetSize; i++) {
|
|
packet[i] = (char)LoRa.read();
|
|
|
|
if (packet[i] < 32 || packet[i] > 127)
|
|
binary = true;
|
|
}
|
|
// Terminate with a null character in case we have a string
|
|
packet[packetSize] = 0;
|
|
uint8_t deviceId = _determineDevice(packet, packetSize);
|
|
if (deviceId == WIPHONE) {
|
|
_WiPhonetoX(packet, LORAdata);
|
|
} else if (binary) {
|
|
if (LORAConfig.onlyKnown) {
|
|
THEENGS_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];
|
|
TheengsUtils::_rawToHex(packet, hex, packetSize);
|
|
// Terminate with a null character
|
|
hex[packetSize * 2] = 0;
|
|
|
|
LORAdata["hex"] = hex;
|
|
} else {
|
|
// ascii payload
|
|
std::string packetStrStd = (char*)packet;
|
|
auto result = deserializeJson(LORAdataBuffer, packetStrStd);
|
|
if (result) {
|
|
THEENGS_LOG_NOTICE(F("LORA packet deserialization failed, not a json, sending raw message" CR));
|
|
LORAdata = LORAdataBuffer.to<JsonObject>();
|
|
LORAdata["message"] = (char*)packet;
|
|
} else {
|
|
THEENGS_LOG_TRACE(F("LORA packet deserialization OK" CR));
|
|
}
|
|
}
|
|
|
|
LORAdata["rssi"] = (int)LoRa.packetRssi();
|
|
LORAdata["snr"] = (float)LoRa.packetSnr();
|
|
LORAdata["pferror"] = (float)LoRa.packetFrequencyError();
|
|
LORAdata["packetSize"] = (int)packetSize;
|
|
|
|
if (LORAdata.containsKey("id")) {
|
|
std::string id = LORAdata["id"];
|
|
id.erase(std::remove(id.begin(), id.end(), ':'), id.end());
|
|
# ifdef ZmqttDiscovery
|
|
if (SYSConfig.discovery) {
|
|
if (!LORAdata.containsKey("model"))
|
|
LORAdataBuffer["model"] = "LORA_NODE";
|
|
storeLORADiscovery(LORAdata, LORAdata["model"].as<char*>(), id.c_str());
|
|
}
|
|
# endif
|
|
buildTopicFromId(LORAdata, subjectLORAtoMQTT);
|
|
} else {
|
|
LORAdataBuffer["origin"] = subjectLORAtoMQTT;
|
|
}
|
|
|
|
enqueueJsonObject(LORAdata);
|
|
if (repeatLORAwMQTT) {
|
|
THEENGS_LOG_TRACE(F("Pub LORA for rpt" CR));
|
|
LORAdata["origin"] = subjectMQTTtoLORA;
|
|
enqueueJsonObject(LORAdata);
|
|
}
|
|
}
|
|
}
|
|
|
|
# if jsonReceiving
|
|
void XtoLORA(const char* topicOri, JsonObject& LORAdata) { // json object decoding
|
|
if (cmpToMainTopic(topicOri, subjectMQTTtoLORA)) {
|
|
THEENGS_LOG_TRACE(F("MQTTtoLORA json" CR));
|
|
const char* message = LORAdata["message"];
|
|
const char* hex = LORAdata["hex"];
|
|
LORAConfig_fromJson(LORAdata);
|
|
if (message || hex) {
|
|
LoRa.beginPacket();
|
|
uint8_t deviceId = _determineDevice(LORAdata);
|
|
if (deviceId == WIPHONE) {
|
|
_MQTTtoWiPhone(LORAdata);
|
|
} else if (hex) {
|
|
// We have hex data: create convert to binary
|
|
byte raw[strlen(hex) / 2];
|
|
TheengsUtils::_hexToRaw(hex, raw, sizeof(raw));
|
|
LoRa.write((uint8_t*)raw, sizeof(raw));
|
|
} else {
|
|
// ascii payload
|
|
LoRa.print(message);
|
|
}
|
|
|
|
LoRa.endPacket();
|
|
THEENGS_LOG_TRACE(F("MQTTtoLORA OK" CR));
|
|
// we acknowledge the sending by publishing the value to an acknowledgement topic, for the moment even if it is a signal repetition we acknowledge also
|
|
LORAdata["origin"] = subjectGTWLORAtoMQTT;
|
|
enqueueJsonObject(LORAdata);
|
|
} else {
|
|
THEENGS_LOG_ERROR(F("MQTTtoLORA Fail json" CR));
|
|
}
|
|
}
|
|
if (cmpToMainTopic(topicOri, subjectMQTTtoLORAset)) {
|
|
THEENGS_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<bool>()) {
|
|
// Restore the default (initial) configuration
|
|
LORAConfig_init();
|
|
LORAConfig_apply();
|
|
} else if (LORAdata.containsKey("load") && LORAdata["load"].as<bool>()) {
|
|
// Load the saved configuration and apply to hardware
|
|
LORAConfig_load(true);
|
|
}
|
|
|
|
// Load config from json if available
|
|
LORAConfig_fromJson(LORAdata);
|
|
stateLORAMeasures();
|
|
}
|
|
}
|
|
# endif
|
|
# if simpleReceiving
|
|
void XtoLORA(const char* topicOri, const char* LORAarray) { // json object decoding
|
|
if (cmpToMainTopic(topicOri, subjectMQTTtoLORA)) {
|
|
LoRa.beginPacket();
|
|
LoRa.print(LORAarray);
|
|
LoRa.endPacket();
|
|
THEENGS_LOG_NOTICE(F("MQTTtoLORA OK" CR));
|
|
// we acknowledge the sending by publishing the value to an acknowledgement topic, for the moment even if it is a signal repetition we acknowledge also
|
|
pub(subjectGTWLORAtoMQTT, LORAarray);
|
|
}
|
|
}
|
|
# endif
|
|
String stateLORAMeasures() {
|
|
//Publish LORA state
|
|
StaticJsonDocument<JSON_MSG_BUFFER> jsonBuffer;
|
|
JsonObject LORAdata = jsonBuffer.to<JsonObject>();
|
|
LORAdata["frequency"] = LORAConfig.frequency;
|
|
LORAdata["txpower"] = LORAConfig.txPower;
|
|
LORAdata["spreadingfactor"] = LORAConfig.spreadingFactor;
|
|
LORAdata["signalbandwidth"] = LORAConfig.signalBandwidth;
|
|
LORAdata["codingrate"] = LORAConfig.codingRateDenominator;
|
|
LORAdata["preamblelength"] = LORAConfig.preambleLength;
|
|
if (LORAConfig.syncWord < 0 || LORAConfig.syncWord > 255) {
|
|
THEENGS_LOG_ERROR(F("Invalid syncWord value: %d" CR), LORAConfig.syncWord);
|
|
} else {
|
|
char syncWordHex[5];
|
|
snprintf(syncWordHex, sizeof(syncWordHex), "0x%02X", LORAConfig.syncWord);
|
|
LORAdata["syncword"] = syncWordHex;
|
|
}
|
|
LORAdata["enablecrc"] = LORAConfig.crc;
|
|
LORAdata["invertiq"] = LORAConfig.invertIQ;
|
|
LORAdata["onlyknown"] = LORAConfig.onlyKnown;
|
|
LORAdata["origin"] = subjectGTWLORAtoMQTT;
|
|
enqueueJsonObject(LORAdata);
|
|
|
|
String output;
|
|
serializeJson(LORAdata, output);
|
|
return output;
|
|
}
|
|
#endif
|