// sensor.ino; 1-channel LoRa Gateway for ESP8266 // Copyright (c) 2016, 2017, 2018 Maarten Westenberg // Verison 5.3.2 // Date: 2018-07-07 // // All rights reserved. This program and the accompanying materials // are made available under the terms of the MIT License // which accompanies this distribution, and is available at // https://opensource.org/licenses/mit-license.php // // NO WARRANTY OF ANY KIND IS PROVIDED // // Author: Maarten Westenberg (mw12554@hotmail.com) // // This file contains code for using the single channel gateway also as a sensor node. // Please specify the DevAddr and the AppSKey below (and on your LoRa backend). // Also you will have to choose what sensors to forward to your application. // // Note: disable sensors not used in ESP-sc-gway.h // - The GPS is included on TTGO T-Beam ESP32 boards by default. // - The battery sensor works by connecting the VCC pin to A0 analog port // ============================================================================ #if GATEWAYNODE==1 //#include "sensor.h" // Contains definitions for the sensor and TRUSTED info, ilcuded in ESP-sc-gway.ino #include "LoRaCode.h" unsigned char DevAddr[4] = _DEVADDR ; // see ESP-sc-gway.h // Only used by GPS sensor code #if _GPS==1 // ---------------------------------------------------------------------------- // Smartdelay is a function to delay processing but in the loop get info // from the GPS device // ---------------------------------------------------------------------------- static void smartDelay(unsigned long ms) { unsigned long start = millis(); do { while (Serial1.available()) gps.encode(Serial1.read()); } while (millis() - start < ms); } #endif //_GPS // ---------------------------------------------------------------------------- // LoRaSensors() is a function that puts sensor values in the MACPayload and // sends these values up to the server. For the server it is impossible to know // whther or not the message comes from a LoRa node or from the gateway. // // The example code below adds a battery value in lCode (encoding protocol) but // of-course you can add any byte string you wish // // Parameters: // - buf: contains the buffer to put the sensor values in (max==xx); // Returns: // - The amount of sensor characters put in the buffer // // NOTE: The code in LoRaSensors() is provided as an example only. // The amount of sensor values as well as their message layout may differ // for each implementation. // Also, the message format used by this gateway is LoraCode, a message format // developed by me for sensor values. Each value is uniquely coded with an // id and a value, and the total message contains its length (less than 64 bytes) // and a parity value in byte[0] bit 7. // ---------------------------------------------------------------------------- static int LoRaSensors(uint8_t *buf) { uint8_t tchars = 1; buf[0] = 0x86; // 134; User code =1 if (debug>=0) Serial.print(F("LoRaSensors:: ")); #endif #if _BATTERY==1 #if DUSB>=1 if (debug>=0) Serial.print(F("Battery ")); #endif #if defined(ARDUINO_ARCH_ESP8266) || defined(ESP32) // For ESP there is no standard battery library // What we do is to measure GPIO35 pin which has a 100K voltage divider pinMode(35, INPUT); #if defined(ESP32) int devider=4095; #else int devider=1023; #endif //ESP32 float volts=3.3 * analogRead(35) / 4095 * 2; // T_Beam connects to GPIO35 #else // For ESP8266 no sensor defined float volts=0; #endif tchars += lcode.eBattery(volts, buf + tchars); #endif #if _GPS==1 #if DUSB>=1 if (debug>=0) Serial.print(F("GPS ")); if (( debug>=1 ) && ( pdebug & P_MAIN )) { Serial.print("\tLatitude : "); Serial.println(gps.location.lat(), 5); Serial.print("\tLongitude : "); Serial.println(gps.location.lng(), 4); Serial.print("\tSatellites: "); Serial.println(gps.satellites.value()); Serial.print("\tAltitude : "); Serial.print(gps.altitude.feet() / 3.2808); Serial.println("M"); Serial.print("\tTime : "); Serial.print(gps.time.hour()); Serial.print(":"); Serial.print(gps.time.minute()); Serial.print(":"); Serial.println(gps.time.second()); } #endif smartDelay(1000); if (millis() > 5000 && gps.charsProcessed() < 10) { #if DUSB>=1 Serial.println(F("No GPS data received: check wiring")); #endif return(0); } // Assuming we have a value, put it in the buf // The layout of this message is specific to the user, // so adapt as needed. tchars += lcode.eGpsL(gps.location.lat(), gps.location.lng(), gps.altitude.value(), gps.satellites.value(), buf + tchars); #endif #if DUSB>=1 if (debug>=0) Serial.println(); #endif // If all sensor data is encoded, we encode the buffer lcode.eMsg(buf, tchars); // Fill byte 0 with bytecount and Parity return(tchars); // return the number of bytes added to payload } // ---------------------------------------------------------------------------- // XOR() // perform x-or function for buffer and key // Since we do this ONLY for keys and X, Y we know that we need to XOR 16 bytes. // // ---------------------------------------------------------------------------- static void mXor(uint8_t *buf, uint8_t *key) { for (uint8_t i = 0; i < 16; ++i) buf[i] ^= key[i]; } // ---------------------------------------------------------------------------- // SHIFT-LEFT // Shift the buffer buf left one bit // Parameters: // - buf: An array of uint8_t bytes // - len: Length of the array in bytes // ---------------------------------------------------------------------------- static void shift_left(uint8_t * buf, uint8_t len) { while (len--) { uint8_t next = len ? buf[1] : 0; // len 0 to 15 uint8_t val = (*buf << 1); if (next & 0x80) val |= 0x01; *buf++ = val; } } // ---------------------------------------------------------------------------- // generate_subkey // RFC 4493, para 2.3 // ---------------------------------------------------------------------------- static void generate_subkey(uint8_t *key, uint8_t *k1, uint8_t *k2) { memset(k1, 0, 16); // Fill subkey1 with 0x00 // Step 1: Assume k1 is an all zero block AES_Encrypt(k1,key); // Step 2: Analyse outcome of Encrypt operation (in k1), generate k1 if (k1[0] & 0x80) { shift_left(k1,16); k1[15] ^= 0x87; } else { shift_left(k1,16); } // Step 3: Generate k2 for (uint8_t i=0; i<16; i++) k2[i]=k1[i]; if (k1[0] & 0x80) { // use k1(==k2) according rfc shift_left(k2,16); k2[15] ^= 0x87; } else { shift_left(k2,16); } // step 4: Done, return k1 and k2 return; } // ---------------------------------------------------------------------------- // ENCODEPACKET // In Sensor mode, we have to encode the user payload before sending. // The library files for AES are added to the library directory in AES. // For the moment we use the AES library made by ideetron as this library // is also used in the LMIC stack and is small in size. // // The function below follows the LoRa spec exactly. // // The resulting mumber of Bytes is returned by the functions. This means // 16 bytes per block, and as we add to the last block we also return 16 // bytes for the last block. // // The LMIC code does not do this, so maybe we shorten the last block to only // the meaningful bytes in the last block. This means that encoded buffer // is exactly as big as the original message. // // NOTE:: Be aware that the LICENSE of the used AES library files // that we call with AES_Encrypt() is GPL3. It is used as-is, // but not part of this code. // // cmac = aes128_encrypt(K, Block_A[i]) // ---------------------------------------------------------------------------- uint8_t encodePacket(uint8_t *Data, uint8_t DataLength, uint16_t FrameCount, uint8_t *DevAddr, uint8_t *AppSKey, uint8_t Direction) { #if DUSB>=1 if (debug>=2) { Serial.print(F("encodePacket:: DevAddr=")); for (int i=0; i<4; i++ ) { Serial.print(DevAddr[i],HEX); Serial.print(' '); } Serial.print(F("encodePacket:: AppSKey=")); for (int i=0; i<16; i++ ) { Serial.print(AppSKey[i],HEX); Serial.print(' '); } Serial.println(); } #endif //unsigned char AppSKey[16] = _APPSKEY ; // see ESP-sc-gway.h uint8_t i, j; uint8_t Block_A[16]; uint8_t bLen=16; // Block length is 16 except for last block in message uint8_t restLength = DataLength % 16; // We work in blocks of 16 bytes, this is the rest uint8_t numBlocks = DataLength / 16; // Number of whole blocks to encrypt if (restLength>0) numBlocks++; // And add block for the rest if any for(i = 1; i <= numBlocks; i++) { Block_A[0] = 0x01; Block_A[1] = 0x00; Block_A[2] = 0x00; Block_A[3] = 0x00; Block_A[4] = 0x00; Block_A[5] = Direction; // 0 is uplink Block_A[6] = DevAddr[3]; // Only works for and with ABP Block_A[7] = DevAddr[2]; Block_A[8] = DevAddr[1]; Block_A[9] = DevAddr[0]; Block_A[10] = (FrameCount & 0x00FF); Block_A[11] = ((FrameCount >> 8) & 0x00FF); Block_A[12] = 0x00; // Frame counter upper Bytes Block_A[13] = 0x00; // These are not used so are 0 Block_A[14] = 0x00; Block_A[15] = i; // Encrypt and calculate the S AES_Encrypt(Block_A, AppSKey); // Last block? set bLen to rest if ((i == numBlocks) && (restLength>0)) bLen = restLength; for(j = 0; j < bLen; j++) { *Data = *Data ^ Block_A[j]; Data++; } } //return(numBlocks*16); // Do we really want to return all 16 bytes in lastblock return(DataLength); // or only 16*(numBlocks-1)+bLen; } // ---------------------------------------------------------------------------- // MICPACKET() // Provide a valid MIC 4-byte code (par 2.4 of spec, RFC4493) // see also https://tools.ietf.org/html/rfc4493 // // Although our own handler may choose not to interpret the last 4 (MIC) bytes // of a PHYSPAYLOAD physical payload message of in internal sensor, // The official TTN (and other) backends will intrpret the complete message and // conclude that the generated message is bogus. // So we sill really simulate internal messages coming from the -1ch gateway // to come from a real sensor and append 4 MIC bytes to every message that are // perfectly legimate // Parameters: // - data: uint8_t array of bytes = ( MHDR | FHDR | FPort | FRMPayload ) // - len: 8=bit length of data, normally less than 64 bytes // - FrameCount: 16-bit framecounter // - dir: 0=up, 1=down // // B0 = ( 0x49 | 4 x 0x00 | Dir | 4 x DevAddr | 4 x FCnt | 0x00 | len ) // MIC is cmac [0:3] of ( aes128_cmac(NwkSKey, B0 | Data ) // // ---------------------------------------------------------------------------- uint8_t micPacket(uint8_t *data, uint8_t len, uint16_t FrameCount, uint8_t * NwkSKey, uint8_t dir) { //uint8_t NwkSKey[16] = _NWKSKEY; uint8_t Block_B[16]; uint8_t X[16]; uint8_t Y[16]; // ------------------------------------ // build the B block used by the MIC process Block_B[0]= 0x49; // 1 byte MIC code Block_B[1]= 0x00; // 4 byte 0x00 Block_B[2]= 0x00; Block_B[3]= 0x00; Block_B[4]= 0x00; Block_B[5]= dir; // 1 byte Direction Block_B[6]= DevAddr[3]; // 4 byte DevAddr Block_B[7]= DevAddr[2]; Block_B[8]= DevAddr[1]; Block_B[9]= DevAddr[0]; Block_B[10]= (FrameCount & 0x00FF); // 4 byte FCNT Block_B[11]= ((FrameCount >> 8) & 0x00FF); Block_B[12]= 0x00; // Frame counter upper Bytes Block_B[13]= 0x00; // These are not used so are 0 Block_B[14]= 0x00; // 1 byte 0x00 Block_B[15]= len; // 1 byte len // ------------------------------------ // Step 1: Generate the subkeys // uint8_t k1[16]; uint8_t k2[16]; generate_subkey(NwkSKey, k1, k2); // ------------------------------------ // Copy the data to a new buffer which is prepended with Block B0 // uint8_t micBuf[len+16]; // B0 | data for (uint8_t i=0; i<16; i++) micBuf[i]=Block_B[i]; for (uint8_t i=0; i restBits) Y[i] = 0x00; } mXor(Y, k2); } else { for (uint8_t i=0; i<16; i++) { Y[i] = micBuf[((numBlocks-1)*16)+i]; } mXor(Y, k1); } mXor(Y, X); AES_Encrypt(Y,NwkSKey); // ------------------------------------ // Step 7: done, return the MIC size. // Only 4 bytes are returned (32 bits), which is less than the RFC recommends. // We return by appending 4 bytes to data, so there must be space in data array. // data[len+0]=Y[0]; data[len+1]=Y[1]; data[len+2]=Y[2]; data[len+3]=Y[3]; return 4; } #if _CHECK_MIC==1 // ---------------------------------------------------------------------------- // CHECKMIC // Function to check the MIC computed for existing messages and for new messages // Parameters: // - buf: LoRa buffer to check in bytes, last 4 bytes contain the MIC // - len: Length of buffer in bytes // - key: Key to use for MIC. Normally this is the NwkSKey // // ---------------------------------------------------------------------------- static void checkMic(uint8_t *buf, uint8_t len, uint8_t *key) { uint8_t cBuf[len+1]; uint8_t NwkSKey[16] = _NWKSKEY; if (debug>=2) { Serial.print(F("old=")); for (uint8_t i=0; i=2) { Serial.print(F("new=")); for (uint8_t i=0; i=1 if ((debug>=2) && (pdebug & P_RADIO )) { Serial.print(F("old: ")); for (int i=0; i=1 if ((debug>=2) && (pdebug & P_RADIO )) { Serial.print(F("new: ")); for (int i=0; i=1 if ((debug>=2) && (pdebug & P_RADIO )) { Serial.print(F("mic: ")); for (int i=0; i 512) { if (debug>0) Serial.println(F("sensorPacket:: ERROR buffer size too large")); return(-1); } #ifdef _TTNSERVER if (!sendUdp(ttnServer, _TTNPORT, buff_up, buff_index)) { return(-1); } #endif #ifdef _THINGSERVER if (!sendUdp(thingServer, _THINGPORT, buff_up, buff_index)) { return(-1); } #endif #if DUSB>=1 // If all is right, we should after decoding (which is the same as encoding) get // the original message back again. if ((debug>=2) && (pdebug & P_RADIO )) { CodeLength = encodePacket((uint8_t *)(LUP.payLoad + 9), PayLength, (uint16_t)frameCount-1, DevAddr, AppSKey, 0); Serial.print(F("rev: ")); for (int i=0; i