From 504463c5c2b7d8c6a2a06a1c51a8e57cdaac62ad Mon Sep 17 00:00:00 2001 From: xopr Date: Sun, 12 Mar 2023 18:09:45 +0100 Subject: [PATCH] [LORA] Added WiPhone LoRa support (#1458) * Added WiPhone LoRa support * Centralised raw-hex functions --- main/ZgatewayLORA.ino | 150 +++++++++++++++++++++++++++++++++++++++--- main/ZgatewaySRFB.ino | 32 ++------- main/config_LORA.h | 1 + main/main.ino | 24 +++++++ 4 files changed, 171 insertions(+), 36 deletions(-) diff --git a/main/ZgatewayLORA.ino b/main/ZgatewayLORA.ino index 7f61ba3..82b40dd 100644 --- a/main/ZgatewayLORA.ino +++ b/main/ZgatewayLORA.ino @@ -33,6 +33,96 @@ # include # include +# define WIPHONE_MESSAGE_MAGIC 0x6c6d +# define WIPHONE_MESSAGE_MIN_LEN sizeof(wiphone_message) - WIPHONE_MAX_MESSAGE_LEN +# define WIPHONE_MAX_MESSAGE_LEN 230 + +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"]; + + if (strcmp(protocol_name, "WiPhone") == 0) + return WIPHONE; + + // No matches + return UNKNOWN_DEVICE; +} + +/* +Create JSON information from WiPhone packet + */ +boolean _WiPhoneToMQTT(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 setupLORA() { SPI.begin(LORA_SCK, LORA_MISO, LORA_MOSI, LORA_SS); LoRa.setPins(LORA_SS, LORA_RST, LORA_DI0); @@ -69,16 +159,38 @@ void LORAtoMQTT() { taskMessage = taskMessage + xPortGetCoreID(); //trc(taskMessage); # endif - String packet; - packet = ""; + // Create packet and reserve null terminator space + byte packet[packetSize + 1]; + boolean binary = false; for (int i = 0; i < packetSize; i++) { - packet += (char)LoRa.read(); + 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; + LORAdata["rssi"] = (int)LoRa.packetRssi(); LORAdata["snr"] = (float)LoRa.packetSnr(); LORAdata["pferror"] = (float)LoRa.packetFrequencyError(); LORAdata["packetSize"] = (int)packetSize; - LORAdata["message"] = (char*)packet.c_str(); + + uint8_t deviceId = _determineDevice(packet, packetSize); + if (deviceId == WIPHONE) { + _WiPhoneToMQTT(packet, LORAdata); + } else if (binary) { + // We have non-ascii data: create hex string of the data + char hex[packetSize * 2 + 1]; + _rawToHex(packet, hex, packetSize); + // Terminate with a null character + hex[packetSize * 2] = 0; + + LORAdata["hex"] = hex; + } else { + // ascii payload + LORAdata["message"] = packet; + } pub(subjectLORAtoMQTT, LORAdata); if (repeatLORAwMQTT) { Log.trace(F("Pub LORA for rpt" CR)); @@ -92,6 +204,7 @@ void MQTTtoLORA(char* topicOri, JsonObject& LORAdata) { // json object decoding if (cmpToMainTopic(topicOri, subjectMQTTtoLORA)) { 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; @@ -99,8 +212,9 @@ void MQTTtoLORA(char* topicOri, JsonObject& LORAdata) { // json object decoding 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; - if (message) { + bool crc = LORAdata["enablecrc"] | DEFAULT_CRC; + bool invertIQ = LORAdata["invertiq"] | INVERT_IQ; + if (message || hex) { LoRa.setTxPower(txPower); LoRa.setFrequency(frequency); LoRa.setSpreadingFactor(spreadingFactor); @@ -108,10 +222,30 @@ void MQTTtoLORA(char* topicOri, JsonObject& LORAdata) { // json object decoding LoRa.setCodingRate4(codingRateDenominator); LoRa.setPreambleLength(preambleLength); LoRa.setSyncWord(syncWord); - if (Crc) + if (crc) LoRa.enableCrc(); + else + LoRa.disableCrc(); + + if (invertIQ) + LoRa.enableInvertIQ(); + else + LoRa.disableInvertIQ(); + LoRa.beginPacket(); - LoRa.print(message); + 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]; + _hexToRaw(hex, raw, sizeof(raw)); + LoRa.print((char*)raw); + } else { + // ascii payload + LoRa.print(message); + } + LoRa.endPacket(); Log.trace(F("MQTTtoLORA OK" CR)); pub(subjectGTWLORAtoMQTT, LORAdata); // 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 diff --git a/main/ZgatewaySRFB.ino b/main/ZgatewaySRFB.ino index 54e27ff..23e442d 100644 --- a/main/ZgatewaySRFB.ino +++ b/main/ZgatewaySRFB.ino @@ -54,7 +54,7 @@ void _rfbSend(byte* message) { void _rfbSend(byte* message, int times) { char buffer[RF_MESSAGE_SIZE]; - _rfbToChar(message, buffer); + _rawToHex(message, buffer, RF_MESSAGE_SIZE); Log.notice(F("[RFBRIDGE] Sending MESSAGE" CR)); for (int i = 0; i < times; i++) { @@ -99,7 +99,7 @@ void _rfbDecode() { char buffer[RF_MESSAGE_SIZE * 2 + 1] = {0}; if (action == RF_CODE_RFIN) { - _rfbToChar(&_uartbuf[1], buffer); + _rawToHex(&_uartbuf[1], buffer, RF_MESSAGE_SIZE); Log.trace(F("Creating SRFB buffer" CR)); StaticJsonDocument jsonBuffer; @@ -142,30 +142,6 @@ void _rfbAck() { Serial.println(); } -/* -From an hexa char array ("A220EE...") to a byte array (half the size) - */ -bool _rfbToArray(const char* in, byte* out) { - if (strlen(in) != RF_MESSAGE_SIZE * 2) - return false; - char tmp[3] = {0}; - for (unsigned char p = 0; p < RF_MESSAGE_SIZE; p++) { - memcpy(tmp, &in[p * 2], 2); - out[p] = strtol(tmp, NULL, 16); - } - return true; -} - -/* -From a byte array to an hexa char array ("A220EE...", double the size) - */ -bool _rfbToChar(byte* in, char* out) { - for (unsigned char p = 0; p < RF_MESSAGE_SIZE; p++) { - sprintf_P(&out[p * 2], PSTR("%02X" CR), in[p]); - } - return true; -} - # if simpleReceiving void MQTTtoSRFB(char* topicOri, char* datacallback) { // RF DATA ANALYSIS @@ -255,7 +231,7 @@ void MQTTtoSRFB(char* topicOri, char* datacallback) { valueRPT = 1; byte message_b[RF_MESSAGE_SIZE]; - _rfbToArray(datacallback, message_b); + _hexToRaw(datacallback, message_b, RF_MESSAGE_SIZE); _rfbSend(message_b, valueRPT); // Acknowledgement to the GTWRF topic pub(subjectGTWSRFBtoMQTT, datacallback); // 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 @@ -272,7 +248,7 @@ void MQTTtoSRFB(char* topicOri, JsonObject& SRFBdata) { if (raw) { // send raw in priority when defined in the json Log.trace(F("MQTTtoSRFB raw ok" CR)); byte message_b[RF_MESSAGE_SIZE]; - _rfbToArray(raw, message_b); + _hexToRaw(raw, message_b, RF_MESSAGE_SIZE); _rfbSend(message_b, valueRPT); } else { unsigned long data = SRFBdata["value"]; diff --git a/main/config_LORA.h b/main/config_LORA.h index cb43356..d76442c 100644 --- a/main/config_LORA.h +++ b/main/config_LORA.h @@ -46,6 +46,7 @@ extern void MQTTtoLORA(char* topicOri, JsonObject& RFdata); #define LORA_PREAMBLE_LENGTH 8 #define LORA_SYNC_WORD 0x12 #define DEFAULT_CRC true +#define INVERT_IQ false #define repeatLORAwMQTT false // do we repeat a received signal by using MQTT with LORA gateway diff --git a/main/main.ino b/main/main.ino index b4e2a3b..56af426 100644 --- a/main/main.ino +++ b/main/main.ino @@ -330,6 +330,30 @@ long value_from_hex_data(const char* service_data, int offset, int data_length, return value; } +/* +From an hexa char array ("A220EE...") to a byte array (half the size) + */ +bool _hexToRaw(const char* in, byte* out, int rawSize) { + if (strlen(in) != rawSize * 2) + return false; + char tmp[3] = {0}; + for (unsigned char p = 0; p < rawSize; p++) { + memcpy(tmp, &in[p * 2], 2); + out[p] = strtol(tmp, NULL, 16); + } + return true; +} + +/* +From a byte array to an hexa char array ("A220EE...", double the size) + */ +bool _rawToHex(byte* in, char* out, int rawSize) { + for (unsigned char p = 0; p < rawSize; p++) { + sprintf_P(&out[p * 2], PSTR("%02X" CR), in[p]); + } + return true; +} + char* ip2CharArray(IPAddress ip) { //from Nick Lee https://stackoverflow.com/questions/28119653/arduino-display-ethernet-localip static char a[16]; sprintf(a, "%d.%d.%d.%d", ip[0], ip[1], ip[2], ip[3]);