//-------------------------------------------------------------------------- // Copyright 2016, RIoT International Pty Ltd // // @author Aaron Ardiri //-------------------------------------------------------------------------- // microTLS - weather station #include #include "microTLS.h" //-------------------------------------------------------------------------- // configuration //-------------------------------------------------------------------------- const uint8_t SERVER[] PROGMEM = { xxx, xxx, xxx, xxx }; #define SERVER_NAME "xxx.xxxxxxxx.xxx" #define SERVER_URI "/xxx/index.php" #define CLIENT_GUID "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" #define CLIENT_INTERVAL 60 // 1 minute //-------------------------------------------------------------------------- // pre-shared keys //-------------------------------------------------------------------------- const uint8_t aes_shared_key[] PROGMEM = { 0xXX, 0xXX, 0xXX, 0xXX, 0xXX, 0xXX, 0xXX, 0xXX, 0xXX, 0xXX, 0xXX, 0xXX, 0xXX, 0xXX, 0xXX, 0xXX // must exist on server }; //-------------------------------------------------------------------------- // modules //-------------------------------------------------------------------------- #include "microTLS-module-aes128.h" #define SESS_SECURITY "aes-128" #define PING_SECURITY "aes-128" //-------------------------------------------------------------------------- // additional globals //-------------------------------------------------------------------------- typedef struct xGlobals { uint8_t aeskey[AES_KEYLEN]; // https://github.com/ControlEverythingCommunity/SI7020-A20 float humidity; float temperature; // https://github.com/ControlEverythingCommunity/CPS120 struct { double visible; double ir; double uv; } light; // https://github.com/ControlEverythingCommunity/SI1132 float pressure; } xGlobals; xGlobals gx; //-------------------------------------------------------------------------- // entry point //-------------------------------------------------------------------------- #define TEMP_ADDR 0x40 #define BAR_ADDR 0x28 #define LIGHT_ADDR 0x60 void microTLS_setup() { int res; SYS_RAM_CHECK; // initialise I2C communication as MASTER Wire.begin(); // https://github.com/ControlEverythingCommunity/SI7020-A20 // https://github.com/ControlEverythingCommunity/CPS120 // https://github.com/ControlEverythingCommunity/SI1132 // enable UVindex measurements coefficients Wire.beginTransmission(LIGHT_ADDR); Wire.write(0x13); Wire.write(0x29); Wire.endTransmission(); Wire.beginTransmission(LIGHT_ADDR); Wire.write(0x14); Wire.write(0x89); Wire.endTransmission(); Wire.beginTransmission(LIGHT_ADDR); Wire.write(0x15); Wire.write(0x02); Wire.endTransmission(); Wire.beginTransmission(LIGHT_ADDR); Wire.write(0x16); Wire.write(0x00); Wire.endTransmission(); // enable uv, visible, ir Wire.beginTransmission(LIGHT_ADDR); Wire.write(0x17); Wire.write(0xF0); // enable uv, visible, IR Wire.endTransmission(); Wire.beginTransmission(LIGHT_ADDR); Wire.write(0x18); Wire.write(0x01 | 0xA0); Wire.endTransmission(); delay(10); Wire.beginTransmission(LIGHT_ADDR); Wire.write(0x2E); Wire.endTransmission(); Wire.requestFrom(LIGHT_ADDR, 1); res = Wire.read(); // INT pin driven low Wire.beginTransmission(LIGHT_ADDR); Wire.write(0x03); Wire.write(0x01); Wire.endTransmission(); Wire.beginTransmission(LIGHT_ADDR); Wire.write(0x04); Wire.write(0x01); Wire.endTransmission(); Wire.beginTransmission(LIGHT_ADDR); Wire.write(0x07); Wire.write(0x17); Wire.endTransmission(); // small IR photodiode, ALS_IR_ADCMUX in RAM Wire.beginTransmission(LIGHT_ADDR); Wire.write(0x17); Wire.write(0x00); Wire.endTransmission(); Wire.beginTransmission(LIGHT_ADDR); Wire.write(0x18); Wire.write(0x0E | 0xA0); Wire.endTransmission(); delay(10); Wire.beginTransmission(LIGHT_ADDR); Wire.write(0x2E); Wire.endTransmission(); Wire.requestFrom(LIGHT_ADDR, 1); res = Wire.read(); // set ADC clock divided / 1, ALS_IR_ADC_GAIN register in RAM Wire.beginTransmission(LIGHT_ADDR); Wire.write(0x17); Wire.write(0x00); Wire.endTransmission(); Wire.beginTransmission(LIGHT_ADDR); Wire.write(0x18); Wire.write(0x1E | 0xA0); Wire.endTransmission(); delay(10); Wire.beginTransmission(LIGHT_ADDR); Wire.write(0x2E); Wire.endTransmission(); Wire.requestFrom(LIGHT_ADDR, 1); res = Wire.read(); // set 511 ADC clock, ALS_IR_ADC_COUNTER register in RAM Wire.beginTransmission(LIGHT_ADDR); Wire.write(0x17); Wire.write(0x70); Wire.endTransmission(); Wire.beginTransmission(LIGHT_ADDR); Wire.write(0x18); Wire.write(0x1D | 0xA0); Wire.endTransmission(); delay(10); Wire.beginTransmission(LIGHT_ADDR); Wire.write(0x2E); Wire.endTransmission(); Wire.requestFrom(LIGHT_ADDR, 1); res = Wire.read(); // set ADC clock divided / 1, ALS_VIS_ADC_GAIN register in RAM Wire.beginTransmission(LIGHT_ADDR); Wire.write(0x17); Wire.write(0x00); Wire.endTransmission(); Wire.beginTransmission(LIGHT_ADDR); Wire.write(0x18); Wire.write(0x11 | 0xA0); Wire.endTransmission(); delay(10); Wire.beginTransmission(LIGHT_ADDR); Wire.write(0x2E); Wire.endTransmission(); Wire.requestFrom(LIGHT_ADDR, 1); res = Wire.read(); // high signal range, ALS_IR_ADC_MISC register in RAM Wire.beginTransmission(LIGHT_ADDR); Wire.write(0x17); Wire.write(0x20); Wire.endTransmission(); Wire.beginTransmission(LIGHT_ADDR); Wire.write(0x18); Wire.write(0x1F | 0xA0); Wire.endTransmission(); delay(10); Wire.beginTransmission(LIGHT_ADDR); Wire.write(0x2E); Wire.endTransmission(); Wire.requestFrom(LIGHT_ADDR, 1); res = Wire.read(); // set 511 ADC clock, ALS_VIS_ADC_COUNTER register in RAM Wire.beginTransmission(LIGHT_ADDR); Wire.write(0x17); Wire.write(0x70); Wire.endTransmission(); Wire.beginTransmission(LIGHT_ADDR); Wire.write(0x18); Wire.write(0x10 | 0xA0); Wire.endTransmission(); delay(10); Wire.beginTransmission(LIGHT_ADDR); Wire.write(0x2E); Wire.endTransmission(); Wire.requestFrom(LIGHT_ADDR, 1); res = Wire.read(); // high signal range, ALS_VIS_ADC_MISC register in RAM Wire.beginTransmission(LIGHT_ADDR); Wire.write(0x17); Wire.write(0x20); Wire.endTransmission(); Wire.beginTransmission(LIGHT_ADDR); Wire.write(0x18); Wire.write(0x12 | 0xA0); Wire.endTransmission(); delay(10); Wire.beginTransmission(LIGHT_ADDR); Wire.write(0x2E); Wire.endTransmission(); Wire.requestFrom(LIGHT_ADDR, 1); res = Wire.read(); } void microTLS_read_sensors() { unsigned int data[4]; float val; // https://github.com/ControlEverythingCommunity/SI7020-A20 // obtain humidity Wire.beginTransmission(TEMP_ADDR); Wire.write(0xF5); // humidity, no hold master Wire.endTransmission(); delay(500); Wire.requestFrom(TEMP_ADDR, 2); if (Wire.available() == 2) { data[0] = Wire.read(); data[1] = Wire.read(); gx.humidity = ((data[0] * 256.0) + data[3]); gx.humidity = ((125 * gx.humidity) / 65536.0) - 6; } else gx.humidity = 0; // obtain temperature Wire.beginTransmission(TEMP_ADDR); Wire.write(0xF3); // temperature, no hold master Wire.endTransmission(); delay(500); Wire.requestFrom(TEMP_ADDR, 2); if (Wire.available() == 2) { data[0] = Wire.read(); data[1] = Wire.read(); val = ((data[0] * 256.0) + data[1]); gx.temperature = ((175.72 * val) / 65536.0) - 46.85; } else { gx.temperature = 0; } // https://github.com/ControlEverythingCommunity/CPS120 // obtain barometric pressure Wire.beginTransmission(BAR_ADDR); Wire.write(0x80); // barometric pressure, no hold master Wire.endTransmission(); delay(500); Wire.requestFrom(BAR_ADDR, 4); if (Wire.available() == 4) { data[0] = Wire.read(); data[1] = Wire.read(); data[2] = Wire.read(); data[3] = Wire.read(); gx.pressure = (((data[0] & 0x3F) * 256 + data[1]) * (90 / 16384.00)) + 30; } else gx.pressure = 0; // https://github.com/ControlEverythingCommunity/SI1132 // obtain visible and ir values Wire.beginTransmission(LIGHT_ADDR); Wire.write(0x18); Wire.write(0x0E); Wire.endTransmission(); delay(500); // obtain visible, ir values Wire.beginTransmission(LIGHT_ADDR); Wire.write(0x22); Wire.endTransmission(); Wire.requestFrom(LIGHT_ADDR, 4); if (Wire.available() == 4) { data[0] = Wire.read(); data[1] = Wire.read(); data[2] = Wire.read(); data[3] = Wire.read(); gx.light.visible = (data[1] * 256.0 + data[0]); gx.light.ir = (data[3] * 256.0 + data[2]); } else { gx.light.visible = 0; gx.light.ir = 0; } // obtain uv value Wire.beginTransmission(LIGHT_ADDR); Wire.write(0x2C); Wire.endTransmission(); Wire.requestFrom(LIGHT_ADDR, 2); if (Wire.available() == 2) { data[0] = Wire.read(); data[1] = Wire.read(); gx.light.uv = (data[1] * 256.0 + data[0]); } else gx.light.uv = 0; } //-------------------------------------------------------------------------- // session: creation //-------------------------------------------------------------------------- void microTLS_sess_prepare() { HashEngine ctx_hash; AESEngine ctx_aes; uint8_t aeskey[AES_KEYLEN]; int enc_len; int i; int ofs; SYS_RAM_CHECK; // how much memory do we need for this operation? enc_len = (g.scratch.token.len + AES_BLOCKSIZE) & ~(AES_BLOCKSIZE-1); enc_len += AES_BLOCKSIZE; // don't forget the IV // we need to make sure both buffers fit in our scratch area if ((g.scratch.token.len + enc_len) < (CLIENT_SCRATCH_LEN - 2)) { // we need to generate an integrity hash for the buffer we are sending hashInit(&ctx_hash); hashUpdate(&ctx_hash, g.scratch.token.buf, g.scratch.token.len); hashFinal(&ctx_hash); g.scratch.send.digest = ctx_hash.digest; // we must prepare our send buffer g.scratch.send.len = 0; ofs = g.scratch.token.len + g.scratch.param.len + g.scratch.send.len + g.scratch.recv.len; g.scratch.send.buf = &g.scratch.reserved[ofs]; // we need to copy our pre-shared key into a buffer memcpy_P(aeskey, aes_shared_key, AES_KEYLEN); // we must create a random IV - put it in first bytes for (i=0;i < AES_BLOCKSIZE; i++) g.scratch.send.buf[i] = xrand(); // encode our message AESinitState(&ctx_aes, aeskey, g.scratch.send.buf); AESencrypt(&ctx_aes, g.scratch.token.buf, g.scratch.token.len, g.scratch.send.buf+AES_BLOCKSIZE); // the encrypted value is now stored in g.scratch.buf g.scratch.send.len = enc_len; } } bool microTLS_sess_process() { AESEngine ctx_aes; uint8_t aeskey[AES_KEYLEN]; int len, enc_len; uint8_t *p; int i; int ofs; SYS_RAM_CHECK; enc_len = g.scratch.recv.len; len = enc_len - AES_BLOCKSIZE; // maximum size it can be // we need to make sure both buffers fit in our scratch area if ((len + enc_len) < (CLIENT_SCRATCH_LEN - 2)) { // we'll put our encoded stuff at the end of scratch p = &g.scratch.reserved[CLIENT_SCRATCH_LEN - enc_len - 1]; memcpy(p, g.scratch.recv.buf, g.scratch.recv.len); // we need to copy our pre-shared key into a buffer memcpy_P(aeskey, aes_shared_key, AES_KEYLEN); // decode our message - IV is at start of packet AESinitState(&ctx_aes, aeskey, p); p += AES_BLOCKSIZE; AESdecrypt(&ctx_aes, p, len, g.scratch.recv.buf); // find the PKCS7 padding bytes - correct the length if needed if (g.scratch.recv.buf[len-1] < (AES_BLOCKSIZE+1)) len -= g.scratch.recv.buf[len-1]; // the decrypted value is now stored in g.scratch.buf g.scratch.recv.len = len; // the buffer should now have been shortened // did we receive a key? if (len == AES_KEYLEN) { // let's move it back into scratch for debugging purposes memcpy(gx.aeskey, g.scratch.recv.buf, len); } } return true; } //-------------------------------------------------------------------------- // session: update //-------------------------------------------------------------------------- // JSON message structure // '{ "temperature": -99.99, "humidity": 99.99, "pressure": 99.99, "light": 999 }' const char JSON_TAG_TMP[] PROGMEM = "\"temperature\":"; const char JSON_TAG_HUM[] PROGMEM = "\"humidity\":"; const char JSON_TAG_PRS[] PROGMEM = "\"pressure\":"; const char JSON_TAG_LGT[] PROGMEM = "\"light\":"; void microTLS_ping_prepare() { HashEngine ctx_hash; AESEngine ctx_aes; int len, enc_len; uint8_t *p; int i; int ofs; uint32_t val; char *_p; SYS_RAM_CHECK; // we need to read the sensors microTLS_read_sensors(); // we'll put our source at the end of scratch len = 100; // should be plenty of space p = &g.scratch.reserved[CLIENT_SCRATCH_LEN - len - 1]; // we need to simulate a sprintf() and build the JSON string _p = (char *)p; *_p++ = '{'; { strcpy_P(_p, JSON_TAG_TMP); _p += strlen(_p); { // whole number val = (int)gx.temperature; u32toa(val, _p); _p += strlen(_p); // remainder *_p++ = '.'; val = (int)((gx.temperature * 100)) % 100; if (val < 10) *_p++ = '0'; u32toa(val, _p); _p += strlen(_p); } *_p++ = ','; strcpy_P(_p, JSON_TAG_HUM); _p += strlen(_p); { // whole number val = (int)gx.humidity; u32toa(val, _p); _p += strlen(_p); // remainder *_p++ = '.'; val = (int)((gx.humidity * 100)) % 100; if (val < 10) *_p++ = '0'; u32toa(val, _p); _p += strlen(_p); } *_p++ = ','; strcpy_P(_p, JSON_TAG_PRS); _p += strlen(_p); { // whole number val = (int)gx.pressure; u32toa(val, _p); _p += strlen(_p); // remainder *_p++ = '.'; val = (int)((gx.pressure * 100)) % 100; if (val < 10) *_p++ = '0'; u32toa(val, _p); _p += strlen(_p); } *_p++ = ','; strcpy_P(_p, JSON_TAG_LGT); _p += strlen(_p); { // whole number val = (int)gx.light.ir; u32toa(val, _p); _p += strlen(_p); // remainder *_p++ = '.'; val = (int)((gx.light.ir * 100)) % 100; if (val < 10) *_p++ = '0'; u32toa(val, _p); _p += strlen(_p); } } *_p++ = '}'; *_p = 0; // we must not forget the NULL terminator len = strlen((char *)p); // how much memory do we need for this operation? enc_len = (len + AES_BLOCKSIZE) & ~(AES_BLOCKSIZE-1); enc_len += AES_BLOCKSIZE; // don't forget the IV // we need to make sure both buffers fit in our scratch area if ((len + enc_len) < (CLIENT_SCRATCH_LEN - 2)) { // we need to generate an integrity hash for the buffer we are sending hashInit(&ctx_hash); hashUpdate(&ctx_hash, p, len); hashFinal(&ctx_hash); g.scratch.send.digest = ctx_hash.digest; // we must prepare our send buffer g.scratch.send.len = 0; ofs = g.scratch.token.len + g.scratch.param.len + g.scratch.send.len + g.scratch.recv.len; g.scratch.send.buf = &g.scratch.reserved[ofs]; // we must create a random IV - put it in first bytes for (i=0;i < AES_BLOCKSIZE; i++) g.scratch.send.buf[i] = xrand(); // encode our message AESinitState(&ctx_aes, gx.aeskey, g.scratch.send.buf); AESencrypt(&ctx_aes, p, len, g.scratch.send.buf+AES_BLOCKSIZE); // the encrypted value is now stored in g.scratch.buf g.scratch.send.len = enc_len; } } bool microTLS_ping_process() { AESEngine ctx_aes; int len, enc_len; uint8_t *p; int i; int ofs; SYS_RAM_CHECK; enc_len = g.scratch.recv.len; len = enc_len - AES_BLOCKSIZE; // maximum size it can be // we need to make sure both buffers fit in our scratch area if ((len + enc_len) < (CLIENT_SCRATCH_LEN - 2)) { // we'll put our encoded stuff at the end of scratch p = &g.scratch.reserved[CLIENT_SCRATCH_LEN - enc_len - 1]; memcpy(p, g.scratch.recv.buf, g.scratch.recv.len); // decode our message - IV is at start of packet AESinitState(&ctx_aes, gx.aeskey, p); p += AES_BLOCKSIZE; AESdecrypt(&ctx_aes, p, len, g.scratch.recv.buf); // find the PKCS7 padding bytes - correct the length if needed if (g.scratch.recv.buf[len-1] < (AES_BLOCKSIZE+1)) len -= g.scratch.recv.buf[len-1]; // the decrypted value is now stored in g.scratch.buf g.scratch.recv.len = len; // the buffer should now have been shortened } return true; } //--------------------------------------------------------------------------