#include "lib/config.h" #include "lib/telemetry/telemetry.h" #include "meshcore/meshframing.h" #include "meshcore/packets/ack.h" #include "meshcore/packets/advert.h" #include "meshcore/packetstructs.h" #include "meshcore/stats.h" #include "util/hexdump.h" #include "util/log.h" #include #include #include #include "encrypted.h" #include "FreeRTOS.h" #include "task.h" #include "lib/adc/temperature.h" #include "lib/rtc/rtc.h" #include "sx1262.h" #define TICKS_TO_MS(xTicks) (((uint32_t)(xTicks)*1000U) / (uint32_t)configTICK_RATE_HZ) #define TAG "EncryptedMessage" void sendEncryptedFrame (const NodeEntry *targetNode, uint8_t payloadType, const uint8_t *plain, size_t plainLen) { FrameStruct frame; uint8_t offset = 0; // 1. Header frame.header = (targetNode->path.pathLen > 0 ? ROUTE_TYPE_DIRECT : ROUTE_TYPE_FLOOD) | // currently flood payloadType | PAYLOAD_VERSION_0; // 2. Destination + source frame.payload[offset++] = targetNode->pubKey[0]; frame.payload[offset++] = persistent.pubkey[0]; // 4. Encrypt + MAC size_t encLen; encrypt_then_mac ( targetNode->secret, 32, plain, plainLen, frame.payload + offset, &encLen); offset += encLen; // 5. Finalize frame.payloadLen = offset; memcpy (&frame.path, &targetNode->path, sizeof (frame.path)); hexdump ("Encrypted frame", frame.payload, frame.payloadLen); LoRaTransmit (&frame); } void sendEncryptedTextMessage (const NodeEntry *targetNode, const PlainTextMessagePayload *msg) { if (targetNode == NULL) { MESH_LOGW (TAG, "Node is null"); return; } if (targetNode->last_seen_lt == 0) { MESH_LOGW (TAG, "Node is not populated"); return; } uint8_t buf[256]; uint8_t index = 0; uint8_t msgLen = strlen (msg->message) + 1; buf[index++] = msg->timestamp; buf[index++] = msg->timestamp >> 8; buf[index++] = msg->timestamp >> 16; buf[index++] = msg->timestamp >> 24; buf[index++] = (msg->textType << 2) | (msg->attempt & 0x03); memcpy (&buf[index], msg->message, msgLen); index += msgLen; sendEncryptedFrame ( targetNode, PAYLOAD_TYPE_TXT_MSG, buf, index); } void sendEncryptedResponse (const NodeEntry *targetNode, const Response *resp) { uint8_t buf[256]; uint8_t index = 0; buf[index++] = (resp->tag) & 0xFF; buf[index++] = (resp->tag >> 8) & 0xFF; buf[index++] = (resp->tag >> 16) & 0xFF; buf[index++] = (resp->tag >> 24) & 0xFF; memcpy (&(buf[index]), resp->data, resp->dataLen); index += resp->dataLen; sendEncryptedFrame ( targetNode, PAYLOAD_TYPE_RESPONSE, buf, index); } void sendEncryptedRequest (const NodeEntry *targetNode, const Request *req) { uint8_t buf[256]; uint8_t index = 0; buf[index++] = req->timestamp; buf[index++] = req->timestamp >> 8; buf[index++] = req->timestamp >> 16; buf[index++] = req->timestamp >> 24; buf[index++] = req->requestType; memcpy (&(buf[index]), req->data, req->dataLen); index += req->dataLen; sendEncryptedFrame ( targetNode, PAYLOAD_TYPE_REQ, buf, index); } void sendEncryptedPathPayload (const NodeEntry *targetNode, const ReturnedPathPayload *path) { uint8_t buf[256]; uint8_t index = 0; buf[index++] = path->path.pathLen; memcpy (&buf[index], path->path.path, path->path.pathLen); index += path->path.pathLen; if (path->extra.dataLen > 0) { buf[index++] = path->extra.type; memcpy (&buf[index], path->extra.data, path->extra.dataLen); } else { buf[index++] = 0xFF; uint32_t timestamp = RTC_GetCounter(); buf[index++] = timestamp; buf[index++] = timestamp >> 8; buf[index++] = timestamp >> 16; buf[index++] = timestamp >> 24; } sendEncryptedFrame ( targetNode, PAYLOAD_TYPE_PATH, buf, index); } void printRequest (const Request *req) { printf ("Request:\n"); printf (" Timestamp: %u\n", req->timestamp); printf (" Type: 0x%02X\n", req->requestType); printf (" Data: "); hexdump (" Data", req->data, req->dataLen); } void printResponse (const Response *resp) { printf ("Response:\n"); printf (" Tag: %u\n", resp->tag); printf (" Data: "); hexdump (" Data", resp->data, resp->dataLen); } void printPlainTextMessage (const PlainTextMessagePayload *msg) { printf ("PlainTextMessage:\n"); printf (" Timestamp: %u\n", msg->timestamp); printf (" Attempt: %u\n", msg->attempt); printf (" TextType: %u\n", msg->textType); printf (" Message: %.*s\n", (int)strlen (msg->message), msg->message); } void printReturnedPathPayload (const ReturnedPathPayload *path) { printf ("ReturnedPathPayload:\n"); printf (" Path Length: %u\n", path->path.pathLen); printf (" Path: "); hexdump (" Path:", path->path.path, path->path.pathLen); printf (" Extra Type: %u\n", path->extra.type); printf (" Extra Data: "); hexdump (" Extra data:", path->extra.data, path->extra.dataLen); } void printEncryptedPayload (const EncryptedPayloadStruct *enc) { printf ("EncryptedPayload:\n"); printf (" Type: 0x%02X\n", enc->type); printf (" DestinationHash: 0x%02X\n", enc->destinationHash); printf (" SourceHash: 0x%02X\n", enc->sourceHash); printf (" CipherMAC: 0x%04X\n", enc->cipherMAC); printf (" PayloadLen: %zu\n", enc->payloadLen); printf (" Payload: "); for (size_t i = 0; i < enc->payloadLen; i++) { printf ("%02X ", enc->payload[i]); } printf ("\n"); } void decodeEncryptedPayload (const FrameStruct *frame) { EncryptedPayloadStruct enc; memset (&enc, 0, sizeof (enc)); enc.path = &(frame->path); enc.origFrame = frame; enc.type = frame->header & PAYLOAD_TYPE_MASK; unsigned char index = 0; enc.destinationHash = frame->payload[index++]; enc.sourceHash = frame->payload[index++]; enc.cipherMAC = frame->payload[index]; enc.cipherMAC |= frame->payload[index + 1] << 8; if (enc.destinationHash != persistent.pubkey[0]) { return; } MESH_LOGI (TAG, "Finding remote node, sourceHash is %d", enc.sourceHash); NodeEntry *remNode = getNode (enc.sourceHash); enc.remNode = remNode; if (remNode == NULL) { MESH_LOGW (TAG, "Node not in DB"); return; } remNode->last_seen_lt = RTC_GetCounter(); MESH_LOGI (TAG, "Found node with index %d", remNode - persistent.contacts); if (mac_then_decrypt (remNode->secret, 32, &(frame->payload[index]), frame->payloadLen - index, enc.payload) != 0) { MESH_LOGW (TAG, "HMAC failed on encrypted message %s", remNode->name); } else { enc.payloadLen = frame->payloadLen - HMAC_SIZE; MESH_LOGI (TAG, "HMAC success from %s, %u bytes long", remNode->name, enc.payloadLen); sendDiscreteAck (enc.payload, 5 + strlen ((char *)&enc.payload[5]), remNode->pubKey); } printf (" Typexdd: 0x%02X\n", enc.type); if (enc.payloadLen > 0) { parseEncryptedPayload (&enc); } } void sendPathBack (const NodeEntry *node, const Path *path) { ReturnedPathPayload retPath; retPath.extra.dataLen = 0; // redo to send the resp in path retPath.extra.type = 0xFF; retPath.path.pathLen = path->pathLen; memcpy (retPath.path.path, path->path, path->pathLen); sendEncryptedPathPayload (node, &retPath); } void parseEncryptedPayload (const EncryptedPayloadStruct *enc) { // printEncryptedPayload(&enc); printf ("EncryptedPayload:\n"); printf (" Type: 0x%02X\n", enc->type); printf (" DestinationHash: 0x%02X\n", enc->destinationHash); printf (" SourceHash: 0x%02X\n", enc->sourceHash); printf (" CipherMAC: 0x%04X\n", enc->cipherMAC); printf (" PayloadLen: %u\n", enc->payloadLen); hexdump (" Payload: ", enc->payload, enc->payloadLen); printf ("\n"); uint8_t index = 0; if (enc->type == PAYLOAD_TYPE_PATH) { ReturnedPathPayload retPath; retPath.path.pathLen = enc->payload[index++]; if (retPath.path.pathLen > 64) { MESH_LOGW (TAG, "Path too long\n"); return; } memcpy (retPath.path.path, &(enc->payload[index]), retPath.path.pathLen); index += retPath.path.pathLen; retPath.extra.type = enc->payload[index++]; retPath.extra.dataLen = enc->payloadLen - index; memcpy (retPath.extra.data, &(enc->payload[index]), retPath.extra.dataLen); if ((enc->origFrame->header & ROUTE_TYPE_MASK) == ROUTE_TYPE_FLOOD || (enc->origFrame->header & ROUTE_TYPE_MASK) == ROUTE_TYPE_TRANSPORT_FLOOD) { sendPathBack (enc->remNode, enc->path); } } else if (enc->type == PAYLOAD_TYPE_REQ) { Request req; req.timestamp = enc->payload[index++]; req.timestamp |= enc->payload[index++] << 8; req.timestamp |= enc->payload[index++] << 16; req.timestamp |= enc->payload[index++] << 24; enc->remNode->last_seen_rt = req.timestamp; req.requestType = enc->payload[index++]; req.dataLen = enc->payloadLen - index; memcpy (req.data, &(enc->payload[index]), req.dataLen); printRequest (&req); if ((enc->origFrame->header & ROUTE_TYPE_MASK) == ROUTE_TYPE_FLOOD || (enc->origFrame->header & ROUTE_TYPE_MASK) == ROUTE_TYPE_TRANSPORT_FLOOD) { sendPathBack (enc->remNode, enc->path); } switch (req.requestType) { case REQUEST_GET_STATS: { Response resp; resp.tag = RTC_GetCounter(); stats.totalUpTimeSeconds = RTC_GetCounter() - startupTime; stats.totalAirTimeSeconds = TICKS_TO_MS (tickAirtime / 1000); memcpy (resp.data, &stats, sizeof (stats)); resp.dataLen = sizeof (stats); sendEncryptedResponse (enc->remNode, &resp); break; } case REQUEST_KEEPALIVE: break; case REQUEST_GET_TELEMETRY_DATA: { Response resp; resp.tag = req.timestamp; enc->remNode->last_seen_rt = req.timestamp; uint8_t index2 = 0; resp.data[index2++] = TELEM_CHANNEL_SELF; resp.data[index2++] = LPP_TEMPERATURE; int16_t dataTemp = getDeciTemperature(); printf ("The temperature is %d decicelsius\n", dataTemp); resp.data[index2++] = (dataTemp >> 8) & 0xFF; resp.data[index2++] = dataTemp & 0xFF; resp.data[index2++] = TELEM_CHANNEL_SELF; resp.data[index2++] = LPP_VOLTAGE; int16_t dataVolt = stats.millivolts / 10; resp.data[index2++] = (dataVolt >> 8) & 0xFF; resp.data[index2++] = dataVolt & 0xFF; if (enc->remNode->authenticated) { encode_gps (TELEM_CHANNEL_SELF, persistent.latitude / 1000000.0f, persistent.longitude / 1000000.0f, persistent.altitude / 100.0f, &(resp.data[index2])); // encode_gps(TELEM_CHANNEL_SELF, 48.1909f, 17.0303f, 234.0f, &(resp.data[index2])); index2 += LPP_GPS_SIZE; } if (enc->remNode->authenticated) { resp.data[index2++] = 2; resp.data[index2++] = LPP_TEMPERATURE; int16_t jokeTemp = 6942; resp.data[index2++] = (jokeTemp >> 8) & 0xFF; resp.data[index2++] = jokeTemp & 0xFF; resp.dataLen = index2; } sendEncryptedResponse (enc->remNode, &resp); break; } case REQUEST_GET_MIN_MAX_AVG: break; case REQUEST_GET_ACCESS_LIST: break; } } else if (enc->type == PAYLOAD_TYPE_RESPONSE) { Response resp; resp.tag = enc->payload[index++]; resp.tag |= enc->payload[index++] << 8; resp.tag |= enc->payload[index++] << 16; resp.tag |= enc->payload[index++] << 24; resp.dataLen = enc->payloadLen - index; memcpy (resp.data, &(enc->payload[index]), resp.dataLen); printResponse (&resp); } else if (enc->type == PAYLOAD_TYPE_TXT_MSG) { PlainTextMessagePayload plaintext; plaintext.timestamp = enc->payload[index++]; plaintext.timestamp |= enc->payload[index++] << 8; plaintext.timestamp |= enc->payload[index++] << 16; plaintext.timestamp |= enc->payload[index++] << 24; enc->remNode->last_seen_rt = plaintext.timestamp; plaintext.attempt = enc->payload[index] & 0x03; plaintext.textType = enc->payload[index++] >> 2; memcpy (plaintext.message, &(enc->payload[index]), enc->payloadLen - index); if ((enc->origFrame->header & ROUTE_TYPE_MASK) == ROUTE_TYPE_FLOOD || (enc->origFrame->header & ROUTE_TYPE_MASK) == ROUTE_TYPE_TRANSPORT_FLOOD) { sendPathBack (enc->remNode, enc->path); } switch (plaintext.textType) { case TXT_TYPE_PLAIN: printf ("Plaintext message from %s, attempt %d, timestamp %d: %s", enc->remNode->name, plaintext.attempt, plaintext.timestamp, plaintext.message); break; case TXT_TYPE_CLI_DATA: if (enc->remNode->authenticated) { processCommand (plaintext.message, enc->remNode); } break; case TXT_TYPE_SIGNED_PLAIN: { uint8_t senderPubKeyPrefix[4]; memcpy (senderPubKeyPrefix, plaintext.message, sizeof (senderPubKeyPrefix)); NodeEntry *senderNode = getNodePrefix (senderPubKeyPrefix); printf ("Plaintext message from server %s, sender is %s, attempt %d, timestamp %d: %s", enc->remNode->name, senderNode->name, plaintext.attempt, plaintext.timestamp, &(plaintext.message[4])); break; } default: MESH_LOGW (TAG, "Unknown text type: %d", plaintext.textType); break; } } } #define STR_EQ_LIT(s, lit) (memcmp ((s), (lit), sizeof (lit) - 1) == 0) void processCommand (char *cmd, NodeEntry *remNode) { PlainTextMessagePayload replyPayload; replyPayload.timestamp = RTC_GetCounter(); replyPayload.attempt = 0; replyPayload.textType = TXT_TYPE_CLI_DATA; uint8_t *reply = replyPayload.message; reply[0] = 0; while (*cmd == ' ') cmd++; // skip leading spaces // Optional CLI prefix (xx|) for companion radio if (strlen (cmd) > 4 && cmd[2] == '|') { memcpy (reply, cmd, 3); reply += 3; cmd += 3; } /* ---------------- System ---------------- */ if (STR_EQ_LIT (cmd, "reboot")) { NVIC_SystemReset(); } else if (STR_EQ_LIT (cmd, "advert")) { sendAdvert (1); // 1500ms delay in reference strcpy ((char *)reply, "OK - Advert sent"); } else if (STR_EQ_LIT (cmd, "clear stats")) { memset (&stats, 0, sizeof (stats)); strcpy ((char *)reply, "(OK - stats reset)"); } else if (STR_EQ_LIT (cmd, "ver")) { sprintf ((char *)reply, "%s (Build: %s)", VERSION, __DATE__); } else if (STR_EQ_LIT (cmd, "board")) { sprintf ((char *)reply, "%s", BOARD); } /* ---------------- Clock ---------------- */ else if (STR_EQ_LIT (cmd, "clock")) { RTC_Get(); sprintf ((char *)reply, "%02d:%02d:%02d - %d/%d/%d UTC", calendar.hour, calendar.min, calendar.sec, calendar.w_date, calendar.w_month, calendar.w_year); } else if (STR_EQ_LIT (cmd, "time ")) { uint32_t secs = atoi (&cmd[5]); uint32_t curr = RTC_GetCounter(); if (secs > curr) { RTC_SetCounter (secs); RTC_Get(); sprintf ((char *)reply, "OK - clock set: %02d:%02d:%02d - %d/%d/%d UTC", calendar.hour, calendar.min, calendar.sec, calendar.w_date, calendar.w_month, calendar.w_year); } else { strcpy ((char *)reply, "(ERR: clock cannot go backwards)"); } } else if (STR_EQ_LIT (cmd, "clock sync")) { uint32_t curr = RTC_GetCounter(); uint32_t sender = replyPayload.timestamp; if (sender > curr) { RTC_SetCounter (sender + 1); RTC_Get(); sprintf ((char *)reply, "OK - clock set: %02d:%02d:%02d - %d/%d/%d UTC", calendar.hour, calendar.min, calendar.sec, calendar.w_date, calendar.w_month, calendar.w_year); } else { strcpy ((char *)reply, "ERR: clock cannot go backwards"); } } /* else if (STR_EQ_LIT (cmd, "tempradio ")) { char tmp[64]; strcpy (tmp, &cmd[10]); const char *parts[5]; int num = mesh_ParseTextParts (tmp, parts, 5); // assume helper float freq = num > 0 ? strtof (parts[0], NULL) : 0.0f; float bw = num > 1 ? strtof (parts[1], NULL) : 0.0f; uint8_t sf = num > 2 ? atoi (parts[2]) : 0; uint8_t cr = num > 3 ? atoi (parts[3]) : 0; int timeout_mins = num > 4 ? atoi (parts[4]) : 0; if (freq >= 300.0f && freq <= 2500.0f && bw >= 7.0f && bw <= 500.0f && sf >= 5 && sf <= 12 && cr >= 5 && cr <= 8 && timeout_mins > 0) { Callbacks_ApplyTempRadioParams (freq, bw, sf, cr, timeout_mins); LoraApply(); sprintf ((char *)reply, "OK - temp params for %d mins", timeout_mins); } else { strcpy ((char *)reply, "Error, invalid params"); } } */ /* ---------------- Password ---------------- */ else if (STR_EQ_LIT (cmd, "password ")) { strncpy (persistent.password, &cmd[9], sizeof (persistent.password)); // savePrefs(); TODO add this sprintf ((char *)reply, "password now: %s", persistent.password); } /* ---------------- GET / SET Config ---------------- */ else if (STR_EQ_LIT (cmd, "get ")) { const char *config = &cmd[4]; if (memcmp (config, "af", 2) == 0) { sprintf (reply, "> %f", persistent.airtimeFactor); /* } else if (memcmp (config, "int.thresh", 10) == 0) { sprintf (reply, "> %d", (uint32_t)_prefs->interference_threshold); } else if (memcmp (config, "agc.reset.interval", 18) == 0) { sprintf (reply, "> %d", ((uint32_t)_prefs->agc_reset_interval) * 4); } else if (memcmp (config, "multi.acks", 10) == 0) { sprintf (reply, "> %d", (uint32_t)_prefs->multi_acks); } else if (memcmp (config, "allow.read.only", 15) == 0) { sprintf (reply, "> %s", _prefs->allow_read_only ? "on" : "off"); } else if (memcmp (config, "flood.advert.interval", 21) == 0) { sprintf (reply, "> %d", (uint32_t)_prefs->flood_advert_interval); } else if (memcmp (config, "advert.interval", 15) == 0) { sprintf (reply, "> %d", ((uint32_t)_prefs->advert_interval) * 2);1 */ } else if (memcmp (config, "guest.password", 14) == 0) { sprintf (reply, "> %s", persistent.guestPassword); } else if (memcmp (config, "name", 4) == 0) { sprintf (reply, "> %s", persistent.nodeName); } else if (memcmp (config, "repeat", 6) == 0) { sprintf (reply, "> %s", persistent.doRepeat ? "on" : "off"); } else if (memcmp (config, "lat", 3) == 0) { sprintf (reply, "> %d", persistent.latitude); } else if (memcmp (config, "lon", 3) == 0) { sprintf (reply, "> %d", persistent.longitude); /* } else if (memcmp (config, "radio", 5) == 0) { char freq[16], bw[16]; snprintf(freq, sizeof(freq), "%lf", persistent.frequencyInHz / 1000000.0); snprintf(bw, sizeof(bw), "%lf", loraBwToFloat(persistent.bandwidth)); sprintf (reply, "> %s,%s,%d,%d", freq, bw, persistent.spreadingFactor, persistent.codingRate + 4); } else if (memcmp (config, "rxdelay", 7) == 0) { sprintf (reply, "> %s", StrHelper::ftoa (_prefs->rx_delay_base)); } else if (memcmp (config, "txdelay", 7) == 0) { sprintf (reply, "> %s", StrHelper::ftoa (_prefs->tx_delay_factor)); } else if (memcmp (config, "flood.max", 9) == 0) { sprintf (reply, "> %d", (uint32_t)_prefs->flood_max); } else if (memcmp (config, "direct.txdelay", 14) == 0) { sprintf (reply, "> %s", StrHelper::ftoa (_prefs->direct_tx_delay_factor)); } else if (memcmp (config, "tx", 2) == 0 && (config[2] == 0 || config[2] == ' ')) { sprintf (reply, "> %d", (uint32_t)_prefs->tx_power_dbm); } else if (memcmp (config, "freq", 4) == 0) { sprintf (reply, "> %s", StrHelper::ftoa (_prefs->freq)); */ } else if (memcmp (config, "public.key", 10) == 0) { strcpy (reply, "> "); hexdump_compact(persistent.pubkey, sizeof(persistent.pubkey), &(reply[2]), 70); } else if (memcmp (config, "role", 4) == 0) { sprintf (reply, "> %s", getStringRole(persistent.nodeType)); } else if (memcmp (config, "adc.multiplier", 14) == 0) { sprintf (reply, "> %.3f", persistent.adcMultiplier); } else { sprintf (reply, "??: %s", config); } } else if (STR_EQ_LIT (cmd, "set ")) { const char *config = &cmd[4]; if (memcmp (config, "af ", 3) == 0) { persistent.airtimeFactor = atof (&config[3]); // savePrefs(); strcpy (reply, "OK"); /* } else if (memcmp (config, "int.thresh ", 11) == 0) { _prefs->interference_threshold = atoi (&config[11]); // savePrefs(); strcpy (reply, "OK"); } else if (memcmp (config, "agc.reset.interval ", 19) == 0) { _prefs->agc_reset_interval = atoi (&config[19]) / 4; // savePrefs(); sprintf (reply, "OK - interval rounded to %d", ((uint32_t)_prefs->agc_reset_interval) * 4); } else if (memcmp (config, "multi.acks ", 11) == 0) { _prefs->multi_acks = atoi (&config[11]); // savePrefs(); strcpy (reply, "OK"); */ } else if (memcmp (config, "allow.read.only ", 16) == 0) { if (memcmp (&config[16], "on", 2) == 0) { persistent.allowReadOnly = 1; strcpy (reply, "OK"); } else if (memcmp (&config[16], "off", 3) == 0) { persistent.allowReadOnly = 0; strcpy (reply, "OK"); } //savePrefs(); /* } else if (memcmp (config, "flood.advert.interval ", 22) == 0) { int hours = _atoi (&config[22]); if ((hours > 0 && hours < 3) || (hours > 48)) { strcpy (reply, "Error: interval range is 3-48 hours"); } else { _prefs->flood_advert_interval = (uint8_t)hours; _callbacks->updateFloodAdvertTimer(); savePrefs(); strcpy (reply, "OK"); } } else if (memcmp (config, "advert.interval ", 16) == 0) { int mins = _atoi (&config[16]); if ((mins > 0 && mins < MIN_LOCAL_ADVERT_INTERVAL) || (mins > 240)) { sprintf (reply, "Error: interval range is %d-240 minutes", MIN_LOCAL_ADVERT_INTERVAL); } else { _prefs->advert_interval = (uint8_t)(mins / 2); _callbacks->updateAdvertTimer(); savePrefs(); strcpy (reply, "OK"); } */ } else if (memcmp (config, "guest.password ", 15) == 0) { strncpy (persistent.guestPassword, &config[15], sizeof (persistent.guestPassword)); // savePrefs(); strcpy (reply, "OK"); } else if (memcmp (config, "name ", 5) == 0) { strncpy (persistent.nodeName, &config[5], sizeof (persistent.nodeName)); // savePrefs(); strcpy (reply, "OK"); } else if (memcmp (config, "repeat ", 7) == 0) { if (memcmp (&config[7], "off", 3) == 0) { persistent.doRepeat = 0; } else if (memcmp (&config[7], "on", 2) == 0) { persistent.doRepeat = 1; } // savePrefs(); strcpy (reply, persistent.doRepeat ? "OK - repeat is now ON" : "OK - repeat is now OFF"); /* } else if (memcmp (config, "radio ", 6) == 0) { strcpy (tmp, &config[6]); const char *parts[4]; int num = mesh::Utils::parseTextParts (tmp, parts, 4); float freq = num > 0 ? strtof (parts[0], 0) : 0.0f; float bw = num > 1 ? strtof (parts[1], 0) : 0.0f; uint8_t sf = num > 2 ? atoi (parts[2]) : 0; uint8_t cr = num > 3 ? atoi (parts[3]) : 0; if (freq >= 300.0f && freq <= 2500.0f && sf >= 5 && sf <= 12 && cr >= 5 && cr <= 8 && bw >= 7.0f && bw <= 500.0f) { _prefs->sf = sf; _prefs->cr = cr; _prefs->freq = freq; _prefs->bw = bw; _callbacks->savePrefs(); strcpy (reply, "OK - reboot to apply"); } else { strcpy (reply, "Error, invalid radio params"); } */ } else if (memcmp (config, "lat ", 4) == 0) { double lat = atof (&config[4]); lat *= 1000000; persistent.latitude = lat; // savePrefs(); strcpy (reply, "OK"); } else if (memcmp (config, "lon ", 4) == 0) { double lon = atof (&config[4]); lon *= 1000000; persistent.longitude = lon; // savePrefs(); strcpy (reply, "OK"); /* } else if (memcmp (config, "rxdelay ", 8) == 0) { float db = atof (&config[8]); if (db >= 0) { _prefs->rx_delay_base = db; savePrefs(); strcpy (reply, "OK"); } else { strcpy (reply, "Error, cannot be negative"); } } else if (memcmp (config, "txdelay ", 8) == 0) { float f = atof (&config[8]); if (f >= 0) { _prefs->tx_delay_factor = f; savePrefs(); strcpy (reply, "OK"); } else { strcpy (reply, "Error, cannot be negative"); } } else if (memcmp (config, "flood.max ", 10) == 0) { uint8_t m = atoi (&config[10]); if (m <= 64) { _prefs->flood_max = m; savePrefs(); strcpy (reply, "OK"); } else { strcpy (reply, "Error, max 64"); } } else if (memcmp (config, "direct.txdelay ", 15) == 0) { float f = atof (&config[15]); if (f >= 0) { _prefs->direct_tx_delay_factor = f; savePrefs(); strcpy (reply, "OK"); } else { strcpy (reply, "Error, cannot be negative"); } } else if (memcmp (config, "tx ", 3) == 0) { _prefs->tx_power_dbm = atoi (&config[3]); savePrefs(); _callbacks->setTxPower (_prefs->tx_power_dbm); strcpy (reply, "OK"); */ } else if (memcmp (config, "freq ", 5) == 0) { double freq = atof (&config[5]); uint32_t newFreq = mhzToHzLimited (persistent.loraSettings.frequencyInHz); if (newFreq != 0) { persistent.loraSettings.frequencyInHz = newFreq; } // savePrefs(); strcpy (reply, "OK - reboot to apply"); } else if (memcmp (config, "adc.multiplier ", 15) == 0) { persistent.adcMultiplier = atof (&config[15]); if (persistent.adcMultiplier == 0.0f) { strcpy (reply, "OK - using default board multiplier"); } else { sprintf (reply, "OK - multiplier set to %.3f", persistent.adcMultiplier); } // savePrefs(); } else { sprintf (reply, "unknown config: %s", config); } } /* ---------------- Stats ---------------- */ else if (STR_EQ_LIT (cmd, "stats-packets")) { sprintf (reply, "{\"recv\":%u,\"sent\":%u,\"flood_tx\":%u,\"direct_tx\":%u,\"flood_rx\":%u,\"direct_rx\":%u}", stats.packetsReceivedCount, stats.packetsSentCount, stats.sentFloodCount, stats.sentDirectCount, stats.receivedFloodCount, stats.receivedDirectCount); } else if (STR_EQ_LIT (cmd, "stats-radio")) { sprintf (reply, "{\"noise_floor\":%d,\"last_rssi\":%d,\"last_snr\":%.2f,\"tx_air_secs\":%u,\"rx_air_secs\":%u}", stats.noiseFloor, stats.lastRSSI, stats.lastSNR / 4.0, stats.totalAirTimeSeconds, stats.total_rx_air_time_secs); } else if (STR_EQ_LIT (cmd, "stats-core")) { stats.totalUpTimeSeconds = RTC_GetCounter() - startupTime; stats.totalAirTimeSeconds = TICKS_TO_MS (tickAirtime / 1000); sprintf (reply, "{\"battery_mv\":%u,\"uptime_secs\":%u,\"errors\":%u,\"queue_len\":%u}", getVoltage(), stats.totalUpTimeSeconds, stats.err_events, stats.txQueueLength); } /* ---------------- Unknown ---------------- */ else { strcpy ((char *)reply, "Unknown command"); } sendEncryptedTextMessage (remNode, &replyPayload); }