diff --git a/README.md b/README.md index 5fa9696b..8f414aab 100644 --- a/README.md +++ b/README.md @@ -158,7 +158,7 @@ You can add up to 3 user defined sensors. Insert sensor's payload scheme in [*se Output of user sensor data can be switched by user remote control command 0x14 sent to Port 2. -Output of sensor and peripheral data is internally switched by a bitmask register. Default mask (0xFF) can be tailored by editing *cfg.payloadmask* initialization value in [*configmanager.cpp*](src/configmanager.cpp) following this scheme: +Output of sensor and peripheral data is internally switched by a bitmask register. Default mask can be tailored by editing *cfg.payloadmask* initialization value in [*configmanager.cpp*](src/configmanager.cpp) following this scheme: | Bit | Sensordata | | --- | ------------- | diff --git a/include/lorawan.h b/include/lorawan.h index 58b0f3e2..9094c132 100644 --- a/include/lorawan.h +++ b/include/lorawan.h @@ -4,7 +4,7 @@ #include "globals.h" #include "rcommand.h" #include "timekeeper.h" -#if(TIME_SYNC_LORASERVER) +#if (TIME_SYNC_LORASERVER) #include "timesync.h" #endif @@ -23,6 +23,13 @@ extern QueueHandle_t LoraSendQueue; extern TaskHandle_t lmicTask, lorasendTask; +// table of LORAWAN MAC commands +typedef struct { + const uint8_t opcode; + const char cmdname[20]; + const uint8_t params; +} mac_t; + esp_err_t lora_stack_init(); void lmictask(void *pvParameters); void onEvent(ev_t ev); @@ -37,6 +44,11 @@ void switch_lora(uint8_t sf, uint8_t tx); void lora_send(void *pvParameters); void lora_enqueuedata(MessageBuffer_t *message); void lora_queuereset(void); +void myRxCallback(void *pUserData, uint8_t port, const uint8_t *pMsg, + size_t nMsg); +void myTxCallback(void *pUserData, int fSuccess); +void mac_decode(const uint8_t cmd[], const uint8_t cmdlength); + #if (TIME_SYNC_LORAWAN) void user_request_network_time_callback(void *pVoidUserUTCTime, int flagSuccess); diff --git a/include/rcommand.h b/include/rcommand.h index e964eb85..78e1bae4 100644 --- a/include/rcommand.h +++ b/include/rcommand.h @@ -19,11 +19,11 @@ typedef struct { const uint8_t opcode; void (*func)(uint8_t []); - uint8_t params; + const uint8_t params; const bool store; } cmd_t; -void rcommand(uint8_t cmd[], uint8_t cmdlength); +void rcommand(const uint8_t cmd[], const uint8_t cmdlength); void do_reset(); #endif diff --git a/include/timesync.h b/include/timesync.h index 7a0013ec..c102c1c4 100644 --- a/include/timesync.h +++ b/include/timesync.h @@ -12,7 +12,7 @@ void timesync_init(void); void send_timesync_req(void); -int recv_timesync_ans(uint8_t seq_no, uint8_t buf[], uint8_t buf_len); +int recv_timesync_ans(const uint8_t seq_no, const uint8_t buf[], const uint8_t buf_len); void process_timesync_req(void *taskparameter); void store_time_sync_req(uint32_t t_millisec); diff --git a/platformio.ini b/platformio.ini index 75905821..e4f84112 100644 --- a/platformio.ini +++ b/platformio.ini @@ -42,7 +42,7 @@ description = Paxcounter is a device for metering passenger flows in realtime. I [common] ; for release_version use max. 10 chars total, use any decimal format like "a.b.c" -release_version = 1.7.974 +release_version = 1.7.979 ; DEBUG LEVEL: For production run set to 0, otherwise device will leak RAM while running! ; 0=None, 1=Error, 2=Warn, 3=Info, 4=Debug, 5=Verbose debug_level = 3 diff --git a/src/bmesensor.cpp b/src/bmesensor.cpp index c6b77fc4..4a53bf89 100644 --- a/src/bmesensor.cpp +++ b/src/bmesensor.cpp @@ -144,7 +144,7 @@ int checkIaqSensorStatus(void) { // store current BME sensor data in struct void bme_storedata(bmeStatus_t *bme_store) { - if ((cfg.payloadmask && MEMS_DATA) & + if ((cfg.payloadmask & MEMS_DATA) && (I2C_MUTEX_LOCK())) { // block i2c bus access #ifdef HAS_BME680 diff --git a/src/gpsread.cpp b/src/gpsread.cpp index bcefd2a2..a4dced58 100644 --- a/src/gpsread.cpp +++ b/src/gpsread.cpp @@ -138,7 +138,7 @@ void gps_loop(void *pvParameters) { while (1) { - if (cfg.payloadmask && GPS_DATA) { + if (cfg.payloadmask & GPS_DATA) { #ifdef GPS_SERIAL // feed GPS decoder with serial NMEA data from GPS device while (GPS_Serial.available()) { diff --git a/src/lmic_config.h b/src/lmic_config.h index f4ddf4ab..fb6fc771 100644 --- a/src/lmic_config.h +++ b/src/lmic_config.h @@ -19,10 +19,15 @@ // LMIC LORAWAN STACK SETTINGS // --> adapt to your device only if necessary +// use interrupts only if LORA_IRQ and LORA_DIO are connected to interrupt +// capable GPIO pins on your board, if not disable interrupts //#define LMIC_USE_INTERRUPTS 1 -//time sync via LoRaWAN network, is not yet supported by TTN (LoRaWAN spec v1.0.3) -//#define LMIC_ENABLE_DeviceTimeReq 1 +// needed for paxcounter code +#define LMIC_ENABLE_user_events 1 + +// time sync via LoRaWAN network, note: not supported by TTNv2 +// #define LMIC_ENABLE_DeviceTimeReq 1 // 16 μs per tick // LMIC requires ticks to be 15.5μs - 100 μs long @@ -33,7 +38,7 @@ // This tells LMIC to make the receive windows bigger, in case your clock is // faster or slower. This causes the transceiver to be earlier switched on, // so consuming more power. You may sharpen (reduce) this value if you are -// limited on battery. +// limited on battery. // ATTN: VALUES > 7 WILL CAUSE RECEPTION AND JOIN PROBLEMS WITH HIGH SF RATES #define CLOCK_ERROR_PROCENTAGE 7 diff --git a/src/lorawan.cpp b/src/lorawan.cpp index 2329eed3..31448f08 100644 --- a/src/lorawan.cpp +++ b/src/lorawan.cpp @@ -244,38 +244,6 @@ void onEvent(ev_t ev) { strcpy_P(buff, (LMIC.txrxFlags & TXRX_ACK) ? PSTR("RECEIVED ACK") : PSTR("TX COMPLETE")); sprintf(display_line6, " "); // clear previous lmic status - - if (LMIC.dataLen) { // did we receive payload data -> display info - ESP_LOGI(TAG, "Received %d bytes of payload, RSSI %d SNR %d", - LMIC.dataLen, LMIC.rssi, LMIC.snr / 4); - sprintf(display_line6, "RSSI %d SNR %d", LMIC.rssi, LMIC.snr / 4); - - if (LMIC.txrxFlags & TXRX_PORT) { // FPort -> use to switch - - switch (LMIC.frame[LMIC.dataBeg - 1]) { - - case RCMDPORT: // opcode -> call rcommand interpreter - rcommand(LMIC.frame + LMIC.dataBeg, LMIC.dataLen); - break; - - default: - -#if (TIME_SYNC_LORASERVER) - // timesync answer -> call timesync processor - if ((LMIC.frame[LMIC.dataBeg - 1] >= TIMEANSWERPORT_MIN) && - (LMIC.frame[LMIC.dataBeg - 1] <= TIMEANSWERPORT_MAX)) { - recv_timesync_ans(LMIC.frame[LMIC.dataBeg - 1], - LMIC.frame + LMIC.dataBeg, LMIC.dataLen); - break; - } -#endif - // unknown port -> display info - ESP_LOGI(TAG, "Received data on unsupported port #%d", - LMIC.frame[LMIC.dataBeg - 1]); - break; - } - } - } break; case EV_LOST_TSYNC: @@ -402,14 +370,15 @@ void lora_send(void *pvParameters) { // attempt to transmit payload else { - switch (LMIC_setTxData2(SendBuffer.MessagePort, SendBuffer.Message, - SendBuffer.MessageSize, - (cfg.countermode & 0x02))) { + switch (LMIC_sendWithCallback( + SendBuffer.MessagePort, SendBuffer.Message, SendBuffer.MessageSize, + (cfg.countermode & 0x02), myTxCallback, NULL)) { + case 0: - ESP_LOGI(TAG, "%d byte(s) delivered to LMIC", SendBuffer.MessageSize); + ESP_LOGI(TAG, "%d byte(s) sent to LORA", SendBuffer.MessageSize); break; case -1: // LMIC already has a tx message pending - ESP_LOGD(TAG, "LMIC busy, message re-enqueued"); + // ESP_LOGD(TAG, "LMIC busy, message re-enqueued"); vTaskDelay(pdMS_TO_TICKS(1000 + random(500))); // wait a while lora_enqueuedata(&SendBuffer); // re-enqueue the undeliverd message break; @@ -554,6 +523,9 @@ void lmictask(void *pvParameters) { // rate for 125 kHz channels, and it minimizes air time and battery power. // Set the transmission power to 14 dBi (25 mW). LMIC_setDrTxpow(DR_SF7, 14); + // register a callback for downlink messages. We aren't trying to write + // reentrant code, so pUserData is NULL. + LMIC_registerRxMessageCb(myRxCallback, NULL); #if defined(CFG_US915) || defined(CFG_au921) // in the US, with TTN, it saves join time if we start on subband 1 @@ -569,4 +541,118 @@ void lmictask(void *pvParameters) { } } // lmictask -#endif // HAS_LORA +// receive message handler +void myRxCallback(void *pUserData, uint8_t port, const uint8_t *pMsg, + size_t nMsg) { + + // tell the compiler that pUserData is required by the API, but we don't + // happen to use it. + LMIC_API_PARAMETER(pUserData); + + // display type of received data + if (nMsg) + ESP_LOGI(TAG, "Received %u bytes of payload on port %u", nMsg, port); + else if (port) + ESP_LOGI(TAG, "Received empty message on port %u", port); + + // list MAC messages, if any + uint8_t nMac = pMsg - &LMIC.frame[0]; + if (port != MACPORT) + --nMac; + if (nMac) { + ESP_LOGI(TAG, "Received %u MAC messages:", nMac); + // NOT WORKING YET + // whe need to strip some protocol overhead from LMIC.frame to unwrap the + // MAC command + mac_decode(LMIC.frame, nMac); + } + + switch (port) { + + // ignore mac messages + case MACPORT: + break; + + // rcommand received -> call interpreter + case RCMDPORT: + rcommand(pMsg, nMsg); + break; + + default: + +#if (TIME_SYNC_LORASERVER) + // valid timesync answer -> call timesync processor + if ((port >= TIMEANSWERPORT_MIN) && (port <= TIMEANSWERPORT_MAX)) + recv_timesync_ans(port, pMsg, nMsg); + break; +#endif + + // unknown port -> display info + ESP_LOGI(TAG, "Received data on unsupported port %u", port); + break; + } // switch +} + +// transmit complete message handler +void myTxCallback(void *pUserData, int fSuccess) { + + /* currently no code here */ + + // tell the compiler that pUserData is required by the API, but we don't + // happen to use it. + LMIC_API_PARAMETER(pUserData); +} + +// LORAWAN MAC interpreter + +// table of LORAWAN MAC messages sent by the network to the device +// format: opcode, cmdname (max 19 chars), #bytes params +// source: LoRaWAN 1.1 Specification (October 11, 2017) + +static mac_t table[] = { + {0x01, "ResetConf", 1}, {0x02, "LinkCheckAns", 2}, + {0x03, "LinkADRReq", 4}, {0x04, "DutyCycleReq", 1}, + {0x05, "RXParamSetupReq", 4}, {0x06, "DevStatusReq", 0}, + {0x07, "NewChannelReq", 5}, {0x08, "RxTimingSetupReq", 1}, + {0x09, "TxParamSetupReq", 1}, {0x0A, "DlChannelReq", 4}, + {0x0B, "RekeyConf", 1}, {0x0C, "ADRParamSetupReq", 1}, + {0x0D, "DeviceTimeAns", 5}, {0x0E, "ForceRejoinReq", 2}, + {0x0F, "RejoinParamSetupReq", 1}}; + +static const uint8_t cmdtablesize = + sizeof(table) / sizeof(table[0]); // number of commands in command table + +// decode mac message +void mac_decode(const uint8_t cmd[], const uint8_t cmdlength) { + + if (!cmdlength) + return; + + uint8_t foundcmd[cmdlength], cursor = 0; + + while (cursor < cmdlength) { + + int i = cmdtablesize; + while (i--) { + if (cmd[cursor] == table[i].opcode) { // lookup command in opcode table + cursor++; // strip 1 byte opcode + if ((cursor + table[i].params) <= cmdlength) { + memmove(foundcmd, cmd + cursor, + table[i].params); // strip opcode from cmd array + cursor += table[i].params; + ESP_LOGI(TAG, "Network command %s", table[i].cmdname); + } else + ESP_LOGI(TAG, "MAC message 0x%02X with missing parameter(s)", + table[i].opcode); + break; // command found -> exit table lookup loop + } // end of command validation + } // end of command table lookup loop + if (i < 0) { // command not found -> skip it + ESP_LOGI(TAG, "Unknown MAC message 0x%02X", cmd[cursor]); + cursor++; + } + } // command parsing loop + +} // mac_decode() + +#endif // HAS_LORA \ No newline at end of file diff --git a/src/paxcounter.conf b/src/paxcounter.conf index 7382d8d8..d78d0035 100644 --- a/src/paxcounter.conf +++ b/src/paxcounter.conf @@ -85,6 +85,7 @@ // Ports on which the device sends and listenes on LoRaWAN and SPI #define COUNTERPORT 1 // counts +#define MACPORT 0 // network commands #define RCMDPORT 2 // remote commands #define STATUSPORT 2 // remote command results #define CONFIGPORT 3 // config query results diff --git a/src/rcommand.cpp b/src/rcommand.cpp index 33acf703..a5cca590 100644 --- a/src/rcommand.cpp +++ b/src/rcommand.cpp @@ -127,7 +127,7 @@ void set_gps(uint8_t val[]) { if (val[0]) { cfg.payloadmask |= (uint8_t)GPS_DATA; // set bit in mask } else { - cfg.payloadmask &= ~(uint8_t)GPS_DATA; // clear bit in mask + cfg.payloadmask &= (uint8_t)~GPS_DATA; // clear bit in mask } } @@ -136,7 +136,7 @@ void set_bme(uint8_t val[]) { if (val[0]) { cfg.payloadmask |= (uint8_t)MEMS_DATA; // set bit in mask } else { - cfg.payloadmask &= ~(uint8_t)MEMS_DATA; // clear bit in mask + cfg.payloadmask &= (uint8_t)~MEMS_DATA; // clear bit in mask } } @@ -146,7 +146,7 @@ void set_batt(uint8_t val[]) { if (val[0]) { cfg.payloadmask |= (uint8_t)BATT_DATA; // set bit in mask } else { - cfg.payloadmask &= ~(uint8_t)BATT_DATA; // clear bit in mask + cfg.payloadmask &= (uint8_t)~BATT_DATA; // clear bit in mask } } @@ -332,7 +332,7 @@ void set_flush(uint8_t val[]) { // format: opcode, function, #bytes params, // flag (true = do make settings persistent / false = don't) // -cmd_t table[] = { +static cmd_t table[] = { {0x01, set_rssi, 1, true}, {0x02, set_countmode, 1, true}, {0x03, set_gps, 1, true}, {0x04, set_display, 1, true}, {0x05, set_lorasf, 1, true}, {0x06, set_lorapower, 1, true}, @@ -349,11 +349,11 @@ cmd_t table[] = { {0x85, get_bme, 0, false}, {0x86, get_time, 0, false}, {0x87, set_time, 0, false}, {0x99, set_flush, 0, false}}; -const uint8_t cmdtablesize = +static const uint8_t cmdtablesize = sizeof(table) / sizeof(table[0]); // number of commands in command table // check and execute remote command -void rcommand(uint8_t cmd[], uint8_t cmdlength) { +void rcommand(const uint8_t cmd[], const uint8_t cmdlength) { if (cmdlength == 0) return; diff --git a/src/timesync.cpp b/src/timesync.cpp index 524ac10f..8ce3238f 100644 --- a/src/timesync.cpp +++ b/src/timesync.cpp @@ -154,7 +154,7 @@ void store_time_sync_req(uint32_t timestamp) { } // process timeserver timestamp answer, called from lorawan.cpp -int recv_timesync_ans(uint8_t seq_no, uint8_t buf[], uint8_t buf_len) { +int recv_timesync_ans(const uint8_t seq_no, const uint8_t buf[], const uint8_t buf_len) { // if no timesync handshake is pending then exit if (!timeSyncPending)