/* 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] = { ERSETZEN }; // LoRaWAN AppSKey, application session key // Copy from TTN Console MSB first! static const u1_t PROGMEM APPSKEY[16] = { ERSETZEN }; // LoRaWAN end-device address (DevAddr) static const u4_t DEVADDR = { 0x2601XXXX }; // <-- 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