diff --git a/decoder-ttn.txt b/decoder-ttn.txt new file mode 100644 index 0000000..e1842af --- /dev/null +++ b/decoder-ttn.txt @@ -0,0 +1,17 @@ +/********************************************************************* +* The TTN Payload function equal for all environment applications +********************************************************************** +function Decoder(bytes, port) { + var retValue = { + bytes: bytes + }; + + retValue.node = bytes[0]; + retValue.battery = bytes[1] / 10.0; + retValue.vcc = bytes[2] / 10.0; + retValue.temperature = (((bytes[3] << 8) | bytes[4]) / 10.0) - 40.0; + retValue.pressure = ((bytes[5] << 16) | (bytes[6] << 8) | bytes[7]); + retValue.humidity = ((bytes[8] << 8) | bytes[9]) / 10.0; + return retValue; +} +*********************************************************************/ diff --git a/images/board.jpg b/images/board.jpg new file mode 100644 index 0000000..1f8c3de Binary files /dev/null and b/images/board.jpg differ diff --git a/images/sensor-bme280.jpg b/images/sensor-bme280.jpg new file mode 100644 index 0000000..e5b6ef7 Binary files /dev/null and b/images/sensor-bme280.jpg differ diff --git a/sensor-abp-ttn.ino b/sensor-abp-ttn.ino new file mode 100644 index 0000000..460d639 --- /dev/null +++ b/sensor-abp-ttn.ino @@ -0,0 +1,496 @@ +/* This file has been prepared for Doxygen **************************/ + +/*! \file LoRa_Radio_Module_abp_s_BME280.ino ************************** +* +* \brief Device ID 5935br27node004 +* +* The payload for all environment nodes are standardized by: +* 1. node (node identification, 0..255) +* 2. battery (battery voltage [V]) +* 3. vcc (system power supply [V]) +* 4. temperature (environment air temperature [C]) +* 5. pressure (environment air pressure [Pa]) +* 6. humidity (environment air humidity [%]) +* +* Copyright (C) 2019 W.Nijs (ALF4all) +* +* This program is free software: you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation, either version 3 of the License, or +* (at your option) any later version. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program. If not, see . +* +* \author W.Nijs. +* \date 25/10/2019 +* \version 1.0 25/10/2019, initial revision, W.Nijs +* +*********************************************************************/ + +/********************************************************************* +* The TTN Payload function equal for all environment applications +********************************************************************** +function Decoder(bytes, port) { + var retValue = { + bytes: bytes + }; + + retValue.node = bytes[0]; + retValue.battery = bytes[1] / 10.0; + retValue.vcc = bytes[2] / 10.0; + retValue.temperature = (((bytes[3] << 8) | bytes[4]) / 10.0) - 40.0; + retValue.pressure = ((bytes[5] << 16) | (bytes[6] << 8) | bytes[7]); + retValue.humidity = ((bytes[8] << 8) | bytes[9]) / 10.0; + return retValue; +} +*********************************************************************/ + + +/*****************************************************************//** +* Include section +*********************************************************************/ + +#include +#include +#include +#include +#include + + +/*****************************************************************//** +* Define section +*********************************************************************/ + +#define LED 13 // on board LED +#define BATTERY A0 + +#define NODE_ID 1 +#define PAYLOAD_LENGHT 10 +#define SEA_LEVEL 101900 // [Pa] + + +/*****************************************************************//** +* Payload vars and objects for node, battery and the BME/P-280 +*********************************************************************/ + +float node = 0; +float battery = 0; +float vcc = 0; + +BME280 bme280; // BME280 object +float temperature = 0; +float pressure = 0; +float humidity = 0; +float altitude = 0; // not used in the payload + +unsigned char payload[PAYLOAD_LENGHT]; // Payload + + +/*****************************************************************//** +* TTN keys and addresses for device ID 5935br27node000 +*********************************************************************/ + +// LoRaWAN NwkSKey, network session key +// Copy from TTN Console MSB first! +static const PROGMEM u1_t NWKSKEY[16] = { 0x4D, 0x84, 0x88, 0x5D, 0xA6, 0xB9, 0x8C, 0x1D, 0x88, 0xE4, 0xEB, 0xB5, 0x54, 0x9D, 0x1F, 0x8E }; + +// LoRaWAN AppSKey, application session key +// Copy from TTN Console MSB first! +static const u1_t PROGMEM APPSKEY[16] = { 0x23, 0xC0, 0xDA, 0x4A, 0xE3, 0x83, 0xD3, 0xC2, 0x65, 0xCF, 0x26, 0x3E, 0x2A, 0x76, 0xE3, 0x57 }; + +// LoRaWAN end-device address (DevAddr) +static const u4_t DEVADDR = { 0x26011995 }; // <-- Change this address for every node! + +// These callbacks are only used in over-the-air activation, so they are +// left empty here (we cannot leave them out completely unless +// DISABLE_JOIN is set in config.h, otherwise the linker will complain). +void os_getArtEui (u1_t* buf) { } +void os_getDevEui (u1_t* buf) { } +void os_getDevKey (u1_t* buf) { } + + +/*****************************************************************//** +* LoRa vars +*********************************************************************/ +//int sleepcycles = 75; // every sleep cycle takes 8 secs -> 10 min +int sleepcycles = 8; // every sleep cycle takes 8 secs -> 1 min +bool joined = false; +bool sleeping = false; + +static osjob_t sendjob; + + +/*****************************************************************//** +* RFM95 pin mapping section +* Mapping for the LoRa Radio Node +*********************************************************************/ + +const lmic_pinmap lmic_pins = { + .nss = 10, + .rxtx = LMIC_UNUSED_PIN, + .rst = 9, + .dio = {/*dio0*/ 2, /*dio1*/ 5, /*dio2*/ 6}, +}; + + +/*****************************************************************//** +* readBattery() +*********************************************************************/ + +float readBattery(void) { + float sensor = 0; + float result = 0; + + sensor = analogRead(BATTERY); + + // 2M, 470K divider across battery and using the internal ADC ref + // of 1.1V. + // The sense point is bypassed with 100 nF to reduce noise at that + // point. + // Vmax = ((2e6+470e3)/470e3)*1.1V = 5,78V + // Resolution = 5,78/1023 = 5.65mV/bit + // Voltage divider current = 3V/2.47M = 1.22uA + result = sensor * 0.00565; + + return result; // Return battery voltage in V +}; + + +/*****************************************************************//** +* readVcc() +*********************************************************************/ + +float readVcc(void) { + float result = 0; + + ADMUX = _BV(REFS0) | _BV(MUX3) | _BV(MUX2) | _BV(MUX1); + delay(2); // Wait for Vref to settle + + ADCSRA |= _BV(ADSC); // Start conversion + while (bit_is_set(ADCSRA,ADSC)); // Wait conversion is ready + + uint8_t low = ADCL; // Read ADCL first - it then locks ADCH + uint8_t high = ADCH; // unlocks both + result = (float)((high<<8) | low); + + result = 1125300L/result; // Calculate battery voltage in mV + return (result/1000); // Return battery voltage in V +}; + + +/*****************************************************************//** +* getParameters() +*********************************************************************/ + +void getParameters(void) { + node = NODE_ID; + battery = readBattery(); // [V 0.1] + vcc = readVcc(); // [V 0.1] + temperature = bme280.readTempC(); // [C 0.1] + pressure = bme280.readFloatPressure(); // [Pa 1] + humidity = bme280.readFloatHumidity(); // [% 0.1] + // not a part of the payload: + altitude = bme280.readFloatAltitudeMeters(); // [m 0.01] +}; + + +/*****************************************************************//** +* payloadEncode() +*********************************************************************/ + +void payloadEncode(void) { + int i = 0; + + // Payload encode algorithm + // nod [0..255] + // bat [0.0..5.5V] => bat = bat * 10; => [0..55] + // vcc [0.0..5.5V] => vcc = vcc * 10; => [0..55] + // tmp [-40..85C] => tmp = tmp + 40; => [0..125] => tmp = tmp * 10; => [0..1250] + // prs [30000..110000Pa] + // hum [0.0..100.0% RH] => hum = hum * 10 => [0..1000] + + unsigned int nod = (unsigned char)(node); + unsigned char bat = (unsigned char)(battery * 10); + unsigned char mcu = (unsigned char)(vcc * 10); + unsigned int tmp = (unsigned int)((temperature + 40.0) * 10.0); + unsigned long prs = (unsigned long)(pressure); + unsigned int hum = (unsigned int)(humidity * 10); + + payload[i++] = nod; // nod => 1 byte + payload[i++] = bat; // bat => 1 byte + payload[i++] = mcu; // vcc => 1 byte + payload[i++] = tmp >> 8; // tmp => 2 bytes + payload[i++] = tmp; + payload[i++] = prs >> 16; // prs => 3 bytes + payload[i++] = prs >> 8; + payload[i++] = prs; + payload[i++] = hum >> 8; // hum => 2 bytes + payload[i++] = hum; +}; + + +/*****************************************************************//** +* parameter_print() +* Only for debugging +*********************************************************************/ + +void parameterPrint(void) { +/* Serial.print("Node id = "); + Serial.println(node, 0); + + Serial.print("Battery = "); + Serial.print(battery, 1); + Serial.println(" V"); + + Serial.print("Vcc = "); + Serial.print(vcc, 1); + Serial.println(" V"); + + Serial.print("Temperature = "); + Serial.print(temperature, 1); + Serial.println(" *C"); + + Serial.print("Pressure = "); + Serial.print((pressure), 0); + Serial.println(" Pa"); + + Serial.print("Humidity = "); + Serial.print(humidity, 1); + Serial.println(" %"); + + Serial.print("Altitude = "); + Serial.print(altitude, 2); + Serial.println(" m"); +*/ +}; + + +/*****************************************************************//** +* payload_print() +* Only for debugging +*********************************************************************/ + +void payloadPrint(void) { + int i = 0; + + Serial.print("Payload = "); + + for (i = 0; i < PAYLOAD_LENGHT; i++ ) { + if (payload[i] < 16) + Serial.print("0"); + Serial.print(payload[i], HEX); + Serial.print(" "); + }; + Serial.println(""); +}; + + +/*****************************************************************//** +* onEvent() +* Event handler +*********************************************************************/ + +void onEvent (ev_t ev) { + Serial.print(os_getTime()); + Serial.print(": "); + switch(ev) { + case EV_SCAN_TIMEOUT: + Serial.println(F("EV_SCAN_TIMEOUT")); + break; + case EV_BEACON_FOUND: + Serial.println(F("EV_BEACON_FOUND")); + break; + case EV_BEACON_MISSED: + Serial.println(F("EV_BEACON_MISSED")); + break; + case EV_BEACON_TRACKED: + Serial.println(F("EV_BEACON_TRACKED")); + break; + case EV_JOINING: + Serial.println(F("EV_JOINING")); + break; + case EV_JOINED: + Serial.println(F("EV_JOINED")); + break; + case EV_RFU1: + Serial.println(F("EV_RFU1")); + break; + case EV_JOIN_FAILED: + Serial.println(F("EV_JOIN_FAILED")); + break; + case EV_REJOIN_FAILED: + Serial.println(F("EV_REJOIN_FAILED")); + break; + case EV_TXCOMPLETE: + sleeping = true; + Serial.println(F("EV_TXCOMPLETE (includes waiting for RX windows)")); + if (LMIC.txrxFlags & TXRX_ACK) + Serial.println(F("Received ack")); + if (LMIC.dataLen) { + Serial.println(F("Received ")); + Serial.println(LMIC.dataLen); + Serial.println(F(" bytes of payload")); + } + delay(50); // delay to complete Serial Output before Sleeping + break; + case EV_LOST_TSYNC: + Serial.println(F("EV_LOST_TSYNC")); + break; + case EV_RESET: + Serial.println(F("EV_RESET")); + break; + case EV_RXCOMPLETE: + // data received in ping slot + Serial.println(F("EV_RXCOMPLETE")); + break; + case EV_LINK_DEAD: + Serial.println(F("EV_LINK_DEAD")); + break; + case EV_LINK_ALIVE: + Serial.println(F("EV_LINK_ALIVE")); + break; + default: + Serial.println(F("Unknown event")); + break; + }; +}; + + +/*****************************************************************//** +* sendMessage() +* +*********************************************************************/ + +void sendMessage(osjob_t* j) { + getParameters(); + parameterPrint(); // Optional, only for debugging + payloadEncode(); + payloadPrint(); // Optional, only for debugging + + // First check if there is not a current TX/RX job running + if (LMIC.opmode & OP_TXRXPEND) { + Serial.println(F("OP_TXRXPEND, not sending")); + } else { + // Prepare upstream data transmission at the next possible time. + LMIC_setTxData2(1, (uint8_t*)payload, PAYLOAD_LENGHT, 0); + Serial.println(F("Sending: ")); + }; +}; + + +/*****************************************************************//** +* Setup function +*********************************************************************/ + +void setup() { + Serial.begin(115200); + Serial.println(F("==================")); + Serial.println(F("- TTN BME280 ABP -")); + Serial.println(F("- ALF4all -")); + Serial.println(F("==================")); + + digitalWrite(LED, LOW); // init LED_BUILTIN as LOW + pinMode(LED, OUTPUT); // init LED_BUILTIN as an output + analogReference(INTERNAL); // 1.1V ref for readBattery(void) + + bme280.setI2CAddress(0x76); // Attention: + // Aliexpress is 0x76 + // Sparkfun and Adafruit is 0x77 + bme280.reset(); + bme280.begin(); + bool status; + if (bme280.beginI2C() == false) { + Serial.println(F("Sensor did not respond, check wiring.")); + // while(1); // Freeze... with no action??? + }; + + bme280.setReferencePressure(SEA_LEVEL); + + // LMIC init + os_init(); + // Reset the MAC state. Session and pending data transfers will be discarded. + LMIC_reset(); + + // Set static session parameters. Instead of dynamically establishing a session + // by joining the network, precomputed session parameters are be provided. + #ifdef PROGMEM + // On AVR, these values are stored in flash and only copied to RAM + // once. Copy them to a temporary buffer here, LMIC_setSession will + // copy them into a buffer of its own again. + uint8_t appskey[sizeof(APPSKEY)]; + uint8_t nwkskey[sizeof(NWKSKEY)]; + memcpy_P(appskey, APPSKEY, sizeof(APPSKEY)); + memcpy_P(nwkskey, NWKSKEY, sizeof(NWKSKEY)); + LMIC_setSession (0x1, DEVADDR, nwkskey, appskey); + #else + // If not running an AVR with PROGMEM, just use the arrays directly + LMIC_setSession (0x1, DEVADDR, NWKSKEY, APPSKEY); + #endif + + #if defined(CFG_eu868) + // Set up the channels used by the Things Network, which corresponds + // to the defaults of most gateways. Without this, only three base + // channels from the LoRaWAN specification are used, which certainly + // works, so it is good for debugging, but can overload those + // frequencies, so be sure to configure the full frequency range of + // your network here (unless your network autoconfigures them). + // Setting up channels should happen after LMIC_setSession, as that + // configures the minimal channel set. + // NA-US channels 0-71 are configured automatically + LMIC_setupChannel(0, 868100000, DR_RANGE_MAP(DR_SF12, DR_SF7), BAND_CENTI); // g-band + LMIC_setupChannel(1, 868300000, DR_RANGE_MAP(DR_SF12, DR_SF7B), BAND_CENTI); // g-band + LMIC_setupChannel(2, 868500000, DR_RANGE_MAP(DR_SF12, DR_SF7), BAND_CENTI); // g-band + LMIC_setupChannel(3, 867100000, DR_RANGE_MAP(DR_SF12, DR_SF7), BAND_CENTI); // g-band + LMIC_setupChannel(4, 867300000, DR_RANGE_MAP(DR_SF12, DR_SF7), BAND_CENTI); // g-band + LMIC_setupChannel(5, 867500000, DR_RANGE_MAP(DR_SF12, DR_SF7), BAND_CENTI); // g-band + LMIC_setupChannel(6, 867700000, DR_RANGE_MAP(DR_SF12, DR_SF7), BAND_CENTI); // g-band + LMIC_setupChannel(7, 867900000, DR_RANGE_MAP(DR_SF12, DR_SF7), BAND_CENTI); // g-band + LMIC_setupChannel(8, 868800000, DR_RANGE_MAP(DR_FSK, DR_FSK), BAND_MILLI); // g2-band + // TTN defines an additional channel at 869.525Mhz using SF9 for class B + // devices' ping slots. LMIC does not have an easy way to define set this + // frequency and support for class B is spotty and untested, so this + // frequency is not configured here. + #elif defined(CFG_us915) + // NA-US channels 0-71 are configured automatically + // but only one group of 8 should (a subband) should be active + // TTN recommends the second sub band, 1 in a zero based count. + // https://github.com/TheThingsNetwork/gateway-conf/blob/master/US-global_conf.json + LMIC_selectSubBand(1); + #endif + + // Disable link check validation + LMIC_setLinkCheckMode(0); + + // TTN uses SF9 for its RX2 window. + LMIC.dn2Dr = DR_SF9; + + // Set data rate and transmit power for uplink (note: txpow seems to be ignored by the library) + LMIC_setDrTxpow(DR_SF7,14); + + // Start job + sendMessage(&sendjob); +}; + + +/*****************************************************************//** +* Loop function +*********************************************************************/ + +void loop() { + sendMessage(&sendjob); // Transmit sensor values + + // go sleeping + while(sleeping == false) { + os_runloop_once(); + }; + sleeping = false; + for (int i=0;i