diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..42ea2ae --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +CMakeLists.txt +TODO.md +cmake-build-debug +waspmote-pro-ide-v04-macosx diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..ab342d5 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,5 @@ +# Changelog + +## 1.0.0 (2018-10-21) + +Initial release. diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..bc16684 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2018 Nobody + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..95bb19b --- /dev/null +++ b/README.md @@ -0,0 +1,229 @@ +# Archived code for 2016 Libelium Waspmote PRO v1.2 with Gas Sensor Board v2.0 and LoRaWAN + +This is just an archive of an Arduino sketch (and its required development tools), used to get more details from a +Libelium Waspmote "Plug & Sense!" Smart Environment device as used by the City of Haarlem from May 2016 until +October 2017. + +## Contents + +- [Code quality](#code-quality) +- [Sensor quality](#sensor-quality) +- [Hardware configuration](#hardware-configuration) +- [Project contents](#project-contents) +- [LoRaWAN](#lorawan) + - [Registering with The Things Network](#registering-with-the-things-network) + - [Uplink payload format](#uplink-payload-format) + - [TTN Payload function](#ttn-payload-function) + - [RN2483 known issues](#rn2483-known-issues) +- [Programming using the Waspmote PRO IDE v04](#programming-using-the-waspmote-pro-ide-v04) + - [Installing the correct IDE](#installing-the-correct-ide) + - [Enabling debug logging](#enabling-debug-logging) + - [Using the LEDs](#using-the-leds) + - [Waspmote IDE known issues](#waspmote-ide-known-issues) + + +## Code quality + +This is by no means intended to be production-ready software. Things that come to mind: + +- No power optimization has been done, at all. Like maybe the main board can sleep while the sensor board is awaiting + the initialization of a sensor? Maybe one can take measurements from one sensor while another is already initializing? +- The used LoRaWAN packet format allows for ridiculously accurate temperature readings, and for negative values for + atmospheric pressure and NO2. All this will/should not happen, and hence is not quite efficient. +- Only use the LED for the first few measurements after a restart. +- Only take measurements when the battery level is good. +- Allow for remote configuration, using downlinks. (Store remote settings in non-volatile memory to survive restarts.) +- Allow for remotely restarting the device to join another LoRaWAN network. +- Calculate the measurement interval based on the start of measurements, rather than time between measurements. +- Confirm that the retry-until-success in the `setLoRaWAN` helper functions does not cause problems. + +Be sure to read the full documentation as provided by Libelium; this sketch was merely based on examples without diving +into the technical documentation too much. + + +## Sensor quality + +Two of these devices were used in the field from May 2016 until October 2017. Their hardware was already outdated when +first installed, and never gave us any reliable results. One unit broke down due to external factors, and the other was +taken into the office to see if its output could be improved by using different settings, but to no avail. + +The source code as used in the field was never released by an intermediate supplier. But peeking into its serial output +revealed that for each sensor it was taking 10 samples, each 1 second apart, after which only the mean (average) value +was transmitted. But the serial output also proved that the actual samples often showed a large variation. Rather than +the mean, this sketch determines the median to discard the outliers, and also sends the minimum and maximum values for +analysis. But as even temperature measurements would often show large variations within only 10 seconds, the device was +declared crap and this sketch has never been used in the field. + +So, I do not endorse the early 2016 device, as its measurements were really bad. Later models may be okay, but I have +not used those. The control hardware is quite nice, with great support for deep sleep and watchdog timers, and even +offering a real-time clock. So if the sensors have meanwhile improved, and when not using the generated code, then this +might be a nice unit. + +Beware that reported NO2 values will always need to be compensated for temperature and atmospheric pressure. +This sketch simply transmits the reported values without any further postprocessing. + + +## Hardware configuration + +Socket configuration for this sketch: + +- A: temperature, 9203, MCP9700A +- B: atmospheric pressure, 9250, MPX4115A +- D: NO2 9238-Pb, MiCS-2714; https://www.libelium.com/forum/viewtopic.php?f=28&t=22007&p=63548#p63568 + +The LoRaWAN module identified itself as `RN2483 1.0.1 Dec 15 2015 09:38:09`. + +The NO2 sensor needs some parameters to be set, typically through some calibration. Without calibration, +neither the documentation nor the support forum are helpful: + +- Gain: depends on the concentrations to be measured; changing the gain keeps the sensor from getting saturated. + + > As a general rule, gain will be fixed at 1 in almost every application, only in very specific situations, such as + > operation in the limits of the sensor range, it will be necessary a different value. + +- Load resistance: depends on the actual calibration. + + > Recommended values of load resistor: NO2 20 KOhm typical to 100K + +Also, the NO2 sensor needs to be pre-heated for at least 30 seconds. + + +## Project contents + + - [src/waspmote-otaa](./src/waspmote-otaa): the actual sketch and its dependencies. + - [src/generated](./src/generated): the sketch as generated by a Libelium tool, given the sensor configuration. As the + tool did not support LoRaWAN, and the code yields a very verbose text-based payload, this is only included as + documentation. + - [tools](./tools): the outdated Libelium Waspmote PRO IDE for Windows and OS X/macOS, and USB drivers. + - [docs](./docs): some outdated Libelium documentation, valid for this configuration. + + +## LoRaWAN + +### Registering with The Things Network + +To register the device with The Things Network: + +- Use the Waspmote PRO IDE to upload the sketch, and peek into the serial output to see its hardware Device EUI. +- Go to https://console.thethingsnetwork.org +- Register an application. This will get you a public AppEUI. +- Register a new OTAA device to the application, using the device's EUI. This will get you a device-specific AppKey. +- Copy the application's public AppEUI and the device's secret AppKey into the sketch. +- Upload the updated sketch. + +### Uplink payload format + +The values are sent in an 18 bytes MSB format. All values, except the battery level, are signed 16 bits integers where +their original float value has been multiplied by 100 to retain 2 decimals. + +| bytes | data +| ----- | ---------------------------------------------------------------------------- +| 00-05 | 3 x 16 bits minimum, median and maximum values for temperature, Celcius +| 06-11 | 3 x 16 bits minimum, median and maximum values for atmospheric pressure, kPa +| 12-17 | 3 x 16 bits minimum, median and maximum values for NO2, ppm +| 18 | 8 bits unsigned battery level, percentage + +This is not at all optimal: + +- The decimals in the temperature readings are probably not very accurate and can be discarded, maybe allowing one + to limit temperature readings to use 3 x 8 bits. +- Readings for pressure and NO2 can, if all is well, not be negative. +- After determining that the readings are okay, one should stop sending the minimum and maximum values. + +### TTN Payload function + +To decode the values back into their original values: + +```javascript +function Decoder(bytes, port) { + var i = 0; + + function nextFloat() { + // Sign-extend to 32 bits to support negative values, by shifting 24 bits + // (too far) to the left, followed by a sign-propagating right shift: + return (bytes[i++]<<24>>16 | bytes[i++]) / 100; + } + + function nextMinMedianMax() { + return { + min: nextFloat(), + median: nextFloat(), + max: nextFloat() + } + } + + return { + temperature: nextMinMedianMax(), + pressure: nextMinMedianMax(), + no2: nextMinMedianMax(), + battery: bytes[i++] + } +} +``` + +### RN2483 known issues + +- After a factory reset, make sure to set (dummy) values for DevAddr, AppSKey and NwkSKey, for otherwise calling + `LoRaWAN.saveConfig` (actually `mac save`) does not save the OTAA settings, and/or `LoRaWAN.joinABP` does not + recognize that they were saved. +- Limited tests show that joining on low data rates might be troublesome, and that ADR might not be working. This has + not been investigated. + + +## Programming using the Waspmote PRO IDE v04 + +### Installing the correct IDE + +This device needs the old API, version 023, which is not included in the latest Waspmote PRO IDE. One could manually +install it, but that's probably not worth the effort. The old IDE also does not support C++11 out of the box (while +the latest IDE does), but even though that adds support for lambdas which would be a nice replacement of the function +pointers used in `_execLoRaWAN` now, that's probably not worth the effort either. + +So, use the December 2013 Waspmote PRO IDE v04, as availble in the [tools](./tools) folder. + +Beware that Libelium warns one should never replace its proprietary bootloader, so beware when using different tooling: + +> The microcontroller Flash (128KB) contains both the uploaded program and the bootloader. The bootloader is a small +> program which is executed at the beginning and proceeds to run the uploaded program. Libelium provides the Waspmote +> IDE which won’t permit rewriting the bootloader. +> +> Do **NOT** use other IDEs, **only** the Waspmote IDE is recommended. +> +> Libelium does not recommend to implement Watchdog timers as some users have had some problems and the microcontroller +> has needed to be re-flashed. +> +> If the bootloader is overwritten by using any of the previous practices, the warranty will be voided. + +### Enabling debug logging + +Even when enabling debugging, the Waspmote UART library hides most of the responses as soon as it finds a match. Like +to see the actual RN2483 version information, enabling debugging does not help. Instead, one needs: + +```cpp +uint8_t result = LoRaWAN.sendCommand("sys get ver\r\n", "\r\n", "invalid_param"); +``` + +...which tells the `WaspUART` superclass to send `sys get ver` and then wait for either a newline (as returned after the +RN2483 has printed its version details) or for the `invalid_param` error. + +To enable debugging see the header files in the IDE's `hardware/waspmote/cores/waspmote-api` folder, specifically the +file `WaspUART.h` for debugging of its derived `WaspLoRaWAN`. + +### Using the LEDs + +The Waspmote being installed in a Plug & Sense! product, the blue LED in its power button is actually addressed using +`Utils.blinkGreenLED`, not using `Utils.externalLEDBlink`. + +### Waspmote IDE known issues + +- Folder names should not include dashes. + +- On OS X and macOS, the old IDE needs the legacy Java SE 6 runtime; OS X and macOS will prompt one to download from + https://support.apple.com/kb/DL1572 However, though the names of the downloads are always `javaforosx.dmg`, an old + 2017 download from that very URL will not work for macOS 10.14 Mojava. After upgrading macOS make sure to download a + more recent release from the same URL. + +- Using the ancient Java, one will see errors like the following, but all works fine: + + > Exception in thread "AWT-EventQueue-0" java.lang.RuntimeException: Non-Java exception raised, not handled! + > (Original problem: Deprecated in 10_12... DO NOT EVER USE CGSEventRecord directly. Bad things, man.... bad things.) diff --git a/docs/gases-sensor-board-2.0.pdf b/docs/gases-sensor-board-2.0.pdf new file mode 100644 index 0000000..9f9c4da Binary files /dev/null and b/docs/gases-sensor-board-2.0.pdf differ diff --git a/docs/sensor-compatibility.pdf b/docs/sensor-compatibility.pdf new file mode 100644 index 0000000..c18d07b Binary files /dev/null and b/docs/sensor-compatibility.pdf differ diff --git a/docs/waspmote-datasheet.pdf b/docs/waspmote-datasheet.pdf new file mode 100644 index 0000000..472e0f3 Binary files /dev/null and b/docs/waspmote-datasheet.pdf differ diff --git a/docs/waspmote-ide-user-guide.pdf b/docs/waspmote-ide-user-guide.pdf new file mode 100644 index 0000000..e18643c Binary files /dev/null and b/docs/waspmote-ide-user-guide.pdf differ diff --git a/docs/waspmote-interruptions-programming-guide.pdf b/docs/waspmote-interruptions-programming-guide.pdf new file mode 100644 index 0000000..414625c Binary files /dev/null and b/docs/waspmote-interruptions-programming-guide.pdf differ diff --git a/docs/waspmote-lorawan-networking-guide-4.3.pdf b/docs/waspmote-lorawan-networking-guide-4.3.pdf new file mode 100644 index 0000000..eda6393 Binary files /dev/null and b/docs/waspmote-lorawan-networking-guide-4.3.pdf differ diff --git a/docs/waspmote-lorawan-networking-guide-7.2.pdf b/docs/waspmote-lorawan-networking-guide-7.2.pdf new file mode 100644 index 0000000..4377d9e Binary files /dev/null and b/docs/waspmote-lorawan-networking-guide-7.2.pdf differ diff --git a/docs/waspmote-plug-and-sense-overview.pdf b/docs/waspmote-plug-and-sense-overview.pdf new file mode 100644 index 0000000..e8672bd Binary files /dev/null and b/docs/waspmote-plug-and-sense-overview.pdf differ diff --git a/docs/waspmote-plug-and-sense-sensors-guide.pdf b/docs/waspmote-plug-and-sense-sensors-guide.pdf new file mode 100644 index 0000000..aaa7a5d Binary files /dev/null and b/docs/waspmote-plug-and-sense-sensors-guide.pdf differ diff --git a/docs/waspmote-plug-and-sense-technical-guide.pdf b/docs/waspmote-plug-and-sense-technical-guide.pdf new file mode 100644 index 0000000..df3b753 Binary files /dev/null and b/docs/waspmote-plug-and-sense-technical-guide.pdf differ diff --git a/docs/waspmote-programming-guide.pdf b/docs/waspmote-programming-guide.pdf new file mode 100644 index 0000000..ee67d59 Binary files /dev/null and b/docs/waspmote-programming-guide.pdf differ diff --git a/docs/waspmote-quickstart-guide.pdf b/docs/waspmote-quickstart-guide.pdf new file mode 100644 index 0000000..d8c3bad Binary files /dev/null and b/docs/waspmote-quickstart-guide.pdf differ diff --git a/docs/waspmote-rtc-programming-guide.pdf b/docs/waspmote-rtc-programming-guide.pdf new file mode 100644 index 0000000..2076cff Binary files /dev/null and b/docs/waspmote-rtc-programming-guide.pdf differ diff --git a/docs/waspmote-technical-guide.pdf b/docs/waspmote-technical-guide.pdf new file mode 100644 index 0000000..f957179 Binary files /dev/null and b/docs/waspmote-technical-guide.pdf differ diff --git a/docs/waspmote-utilities-programming-guide.pdf b/docs/waspmote-utilities-programming-guide.pdf new file mode 100644 index 0000000..bcf3728 Binary files /dev/null and b/docs/waspmote-utilities-programming-guide.pdf differ diff --git a/src/WaspmoteOTAA/RunningMedian.cpp b/src/WaspmoteOTAA/RunningMedian.cpp new file mode 100644 index 0000000..595a26d --- /dev/null +++ b/src/WaspmoteOTAA/RunningMedian.cpp @@ -0,0 +1,136 @@ +// +// FILE: RunningMedian.cpp +// AUTHOR: Rob dot Tillaart at gmail dot com +// VERSION: 0.1.04.waspmote +// PURPOSE: RunningMedian library for Arduino +// +// HISTORY: +// 0.1.00 - 2011-02-16 initial version +// 0.1.01 - 2011-02-22 added remarks from CodingBadly +// 0.1.02 - 2012-03-15 added +// 0.1.03 - 2013-09-30 added _sorted flag, minor refactor +// 0.1.04 - 2013-10-17 added getAverage(uint8_t) - kudo's to Sembazuru +// 2017-10-20 minor changes in .h file to support Libelium Waspmote +// +// Released to the public domain +// + +#include "RunningMedian.h" + +RunningMedian::RunningMedian(uint8_t size) +{ + _size = constrain(size, MEDIAN_MIN_SIZE, MEDIAN_MAX_SIZE); + // array's could be allocated by malloc here, + // but using fixed size is easier. + clear(); +} + +RunningMedian::RunningMedian() +{ + _size = MEDIAN_DEF_SIZE; + clear(); +} +// resets all counters +void RunningMedian::clear() +{ + _cnt = 0; + _idx = 0; + _sorted = false; +} + +// adds a new value to the data-set +// or overwrites the oldest if full. +void RunningMedian::add(float value) +{ + _ar[_idx++] = value; + if (_idx >= _size) _idx = 0; // wrap around + if (_cnt < _size) _cnt++; + _sorted = false; +} + +float RunningMedian::getMedian() +{ + if (_cnt > 0) + { + if (_sorted == false) sort(); + return _as[_cnt/2]; + } + return NAN; +} + +#ifdef RUNNING_MEDIAN_ALL +float RunningMedian::getHighest() +{ + if (_cnt > 0) + { + if (_sorted == false) sort(); + return _as[_cnt-1]; + } + return NAN; +} + +float RunningMedian::getLowest() +{ + if (_cnt > 0) + { + if (_sorted == false) sort(); + return _as[0]; + } + return NAN; +} + +float RunningMedian::getAverage() +{ + if (_cnt > 0) + { + float sum = 0; + for (uint8_t i=0; i< _cnt; i++) sum += _ar[i]; + return sum / _cnt; + } + return NAN; +} + +float RunningMedian::getAverage(uint8_t nMedians) +{ + if ((_cnt > 0) && (nMedians > 0)) + { + if (_cnt < nMedians) nMedians = _cnt; // when filling the array for first time + uint8_t start = ((_cnt - nMedians)/2); + uint8_t stop = start + nMedians; + sort(); + float sum = 0; + for (uint8_t i = start; i < stop; i++) sum += _as[i]; + return sum / nMedians; + } + return NAN; +} + +uint8_t RunningMedian::getSize() { return _size; }; + +uint8_t RunningMedian::getCount() { return _cnt; }; +#endif + +void RunningMedian::sort() +{ + // copy + for (uint8_t i=0; i< _cnt; i++) _as[i] = _ar[i]; + + // sort all + for (uint8_t i=0; i< _cnt-1; i++) + { + uint8_t m = i; + for (uint8_t j=i+1; j< _cnt; j++) + { + if (_as[j] < _as[m]) m = j; + } + if (m != i) + { + float t = _as[m]; // PATCH from 0.1.05 (was long) + _as[m] = _as[i]; + _as[i] = t; + } + } + _sorted = true; +} + +// END OF FILE \ No newline at end of file diff --git a/src/WaspmoteOTAA/RunningMedian.h b/src/WaspmoteOTAA/RunningMedian.h new file mode 100644 index 0000000..b022de3 --- /dev/null +++ b/src/WaspmoteOTAA/RunningMedian.h @@ -0,0 +1,68 @@ +#ifndef RunningMedian_h +#define RunningMedian_h +// +// FILE: RunningMedian.h +// AUTHOR: Rob dot Tillaart at gmail dot com +// PURPOSE: RunningMedian library for Arduino +// VERSION: 0.1.04.waspmote +// URL: http://arduino.cc/playground/Main/RunningMedian +// HISTORY: See RunningMedian.cpp +// +// Released to the public domain +// + +// Start of fixes for Waspmote +// #if defined(ARDUINO) && ARDUINO >= 100 +// #include "Arduino.h" +// #else +// #include "WProgram.h" +// #endif +#include +#include "wiring.h" +typedef uint8_t boolean; +// End of fixes for Waspmote + +#include + +#define RUNNING_MEDIAN_VERSION "0.1.04" + +// should at least be 5 to be practical +#define MEDIAN_MIN_SIZE 1 +#define MEDIAN_MAX_SIZE 19 +#define MEDIAN_DEF_SIZE 5 + +// conditional compile to minimize lib +#define RUNNING_MEDIAN_ALL + +class RunningMedian +{ +public: + RunningMedian(uint8_t); + RunningMedian(); + + void clear(); + void add(float); + float getMedian(); + +#ifdef RUNNING_MEDIAN_ALL + float getAverage(); + float getAverage(uint8_t); + float getHighest(); + float getLowest(); + + uint8_t getSize(); + uint8_t getCount(); +#endif + +protected: + boolean _sorted; + uint8_t _size; + uint8_t _cnt; + uint8_t _idx; + float _ar[MEDIAN_MAX_SIZE]; + float _as[MEDIAN_MAX_SIZE]; + void sort(); +}; + +#endif +// END OF FILE \ No newline at end of file diff --git a/src/WaspmoteOTAA/WaspmoteOTAA.pde b/src/WaspmoteOTAA/WaspmoteOTAA.pde new file mode 100644 index 0000000..8ebef10 --- /dev/null +++ b/src/WaspmoteOTAA/WaspmoteOTAA.pde @@ -0,0 +1,441 @@ +/** + * An example sketch for a 2016 Libelium Waspmote PRO v1.2, with Gas Sensor Board v2.0 and RN2483 LoRaWAN for EU868, + * using API v023. + * + * Hardware configuration of sockets: + * - A = temperature, 9203, MCP9700A + * - B = atmospheric pressure 9250, MPX4115A + * - D = NO2 9238-Pb, MiCS-2714 -- https://www.libelium.com/forum/viewtopic.php?f=28&t=22007&p=63548#p63568 + * + * Use of this source code is governed by the MIT license that can be found in the LICENSE file. + */ + +#include +#include +// The old Arduino/Waspmote IDE does not support project sub-folders for third-party libraries. +#include "RunningMedian.h" + +/** + * LoRaWAN OTAA 64-bit public Application EUI. MSB; a TTN-generated AppEUI always starts with 0x70. + */ +char APP_EUI[] = "7000000000000000"; + +/** + * LoRaWAN OTAA 128-bit secret Application Key. MSB. + */ +char APP_KEY[] = "00000000000000000000000000000000"; + +/** + * LoRaWAN initial data rate, used for OTAA Join. Low data rates seem troublesome for OTAA; needs investigation. + * + * 0: SF = 12, BW = 125 kHz, bitrate = 250 bps + * 1: SF = 11, BW = 125 kHz, bitrate = 440 bps + * 2: SF = 10, BW = 125 kHz, bitrate = 980 bps + * 3: SF = 9, BW = 125 kHz, bitrate = 1760 bps + * 4: SF = 8, BW = 125 kHz, bitrate = 3125 bps + * 5: SF = 7, BW = 125 kHz, bitrate = 5470 bps + */ +uint8_t INITIAL_DR = 3; + +/** + * LoRaWAN application port; any value from 1 to 223. + */ +uint8_t PORT = 1; + +/** + * Interval between the completion of one transmission, and the initialization of the next measurement cycle. + */ +char *sleepTime = "00:00:15:00"; + +/** + * Number of seconds to wait after powering on the gases board. + */ +uint8_t boardInitSeconds = 2; + +/** + * Number of samples to take for temperature. As the median is taken, an odd number makes most sense. + */ +uint8_t temperatureSampleCount = 9; + +/** + * Number of samples to take for atmospheric pressure. As the median is taken, an odd number makes most sense. + */ +uint8_t pressureSampleCount = 9; + +/** + * Number of samples to take for NO2. As the median is taken, an odd number makes most sense. + */ +uint8_t no2SampleCount = 9; + +/** + * Number of seconds to pre-heat the NO2 sensor. This must be at least 30 seconds. + */ +uint8_t no2InitSeconds = 30; + +/** + * Gain for the NO2 sensor, 1 to 100. This depends on the concentrations to be measured; changing the gain keeps the + * sensor from getting saturated. As a general rule, gain will be fixed at 1 in almost every application; only in very + * specific situations, such as operation in the limits of the sensor range, a different value will be necessary. + */ +uint8_t no2Gain = 1; + +/** + * Load resistance for the NO2 sensor, 1 to 100 kOhm. This depends on calibration and is typically 20. + */ +float no2Resistance = 20; + + +// ==================================================================================================================== +// End of user configuration +// ==================================================================================================================== + + +/** + * Socket (UART) to which the LoRaWAN module is connected + */ +uint8_t socket = SOCKET0; + +/** + * Buffer to holdthe binary LoRaWAN uplink. MSB. + */ +byte data[50]; + +void printLoRaWANResult(uint8_t result, char *charResult[] = NULL, uint32_t *intResult = NULL) { + switch (result) { + case LORAWAN_ANSWER_OK: + if (charResult != NULL) { + USB.println(*charResult); + } else if (intResult != NULL) { + USB.println(*intResult); + } else { + USB.println(F("OK")); + } + break; + case LORAWAN_ANSWER_ERROR: + USB.println(F("ANSWER_ERROR - Module communication error / erratic response to a function")); + break; + case LORAWAN_NO_ANSWER: + // includes duty cycle proplems after 3 x OTAA on SF12? + USB.println(F("NO_ANSWER - Module didn't respond")); + break; + case LORAWAN_INIT_ERROR: + USB.println(F("INIT_ERROR - Required keys to join to a network were not initialized (ABP)")); + break; + case LORAWAN_LENGTH_ERROR: + USB.println(F("LENGTH_ERROR - Error with data length / data to be sent length limit exceeded")); + break; + case LORAWAN_SENDING_ERROR: + USB.println(F("SENDING_ERROR - Sending error / server did not respond")); + break; + case LORAWAN_NOT_JOINED: + USB.println(F("NOT_JOINED - Module hasn't joined a network")); + break; + case LORAWAN_INPUT_ERROR: + USB.println(F("INPUT_ERROR - Invalid parameter")); + break; + case LORAWAN_VERSION_ERROR: + USB.println(F("VERSION_ERROR - Invalid version")); + break; + default: + USB.print(F("ERROR ")); + USB.println(result, DEC); + } +} + +/** + * Executes a function from the WaspLoRaWAN class, and repeats if unsuccessful. + */ +void _execLoRaWAN(const __FlashStringHelper *msg, + char *charResult[] = NULL, uint32_t *intResult = NULL, + uint8_t(WaspLoRaWAN::*f)() = NULL, + uint8_t(WaspLoRaWAN::*g)(char *) = NULL, char *c = 0, + uint8_t(WaspLoRaWAN::*h)(uint8_t) = NULL, uint8_t i = 0) { + + // loop until result is LORAWAN_ANSWER_OK + while (1) { + Utils.blinkGreenLED(100, 1); + + USB.print(msg); + uint8_t result; + if (f) { + result = (LoRaWAN.*f)(); + } else if (g) { + result = (LoRaWAN.*g)(c); + } else { + result = (LoRaWAN.*h)(i); + } + + USB.print(F(": ")); + printLoRaWANResult(result, charResult, intResult); + if (result == LORAWAN_ANSWER_OK) { + break; + } + if (result != LORAWAN_ANSWER_ERROR) { + // When not resetting, then the module does not seem to recover from LORAWAN_NO_ANSWER. For OTAA during + // testing only LORAWAN_ANSWER_ERROR (recoverable twice) and LORAWAN_NO_ANSWER occurred. + LoRaWAN.reset(); + } + } +} + +/** + * Prints the given message and invokes the given function without any parameters until it returns success, and upon + * success prints the value of the given reference. + * + * @param msg the message to print + * @param f the function to invoke + * @param charResult the reference to the char result to print + */ +void getLoRaWAN(const __FlashStringHelper *msg, uint8_t(WaspLoRaWAN::*f)(), char *charResult) { + _execLoRaWAN(msg, &charResult, NULL, f); +} + +/** + * Prints the given message and invokes the given function without any parameters until it returns success, and upon + * success prints the value of the given reference. + * + * @param msg the message to print + * @param f the function to invoke + * @param intResult the reference to the numeric result to print + */ +void getLoRaWAN(const __FlashStringHelper *msg, uint8_t(WaspLoRaWAN::*f)(), uint32_t *intResult) { + _execLoRaWAN(msg, NULL, intResult, f); +} + +/** + * Prints the given message and invokes the given function without any parameters until it returns success. + * + * @param msg the message to print + * @param f the function to invoke + */ +void setLoRaWAN(const __FlashStringHelper *msg, uint8_t(WaspLoRaWAN::*f)()) { + _execLoRaWAN(msg, NULL, NULL, f); +} + +/** + * Prints the given message and invokes the given function with the given char* parameter, until it returns success. + * + * @param msg the message to print + * @param g the function to invoke + * @param param the parameter to pass to the function + */ +void setLoRaWAN(const __FlashStringHelper *msg, uint8_t(WaspLoRaWAN::*g)(char *), char *param) { + _execLoRaWAN(msg, NULL, NULL, NULL, g, param); +} + +/** + * Prints the given message and invokes the given function with the given int parameter, until it returns success. + * + * @param msg the message to print + * @param h the function to invoke + * @param param the parameter to pass to the function + */ +void setLoRaWAN(const __FlashStringHelper *msg, uint8_t(WaspLoRaWAN::*h)(uint8_t), uint8_t param) { + _execLoRaWAN(msg, NULL, NULL, NULL, NULL, NULL, h, param); +} + +void printFloat(const __FlashStringHelper *msg, float f) { + // USB.printf does not Flash-strings, nor floats + USB.print(msg); + char s[10]; + Utils.float2String(f, s, 2); + USB.print(s); +} + +void printInt(const __FlashStringHelper *msg, int8_t i) { + USB.print(msg); + USB.print(i, DEC); +} + +void printMemory() { + USB.printf(" - Free memory: %d bytes\n", freeMemory()); +} + +void setup() { + USB.ON(); + USB.println(F("Configuring LoRaWAN module")); + setLoRaWAN(F(" - Switch on"), &WaspLoRaWAN::ON, socket); + setLoRaWAN(F(" - Factory reset"), &WaspLoRaWAN::factoryReset); + // Print the hardware EUI required to register the device at The Things Network + getLoRaWAN(F(" - Get hardware EUI"), &WaspLoRaWAN::getEUI, LoRaWAN._eui); + + USB.print(F(" - Get version: ")); + uint8_t result = LoRaWAN.sendCommand("sys get ver\r\n", "\r\n", "invalid_param"); + if (result == 1) { + USB.print(LoRaWAN._buffer, LoRaWAN._length); + } else { + USB.println(F("FAILED")); + } + + USB.print(F(" - Get Waspmote serial number: ")); + USB.println(Utils.readSerialID()); + + // Some dummy values need to be set, for otherwise joinABP after a deep sleep yields error 3, Required keys to join + // to a network were not initialized. + setLoRaWAN(F(" - Set dummy DevAdr"), &WaspLoRaWAN::setDeviceAddr, "00000000"); + setLoRaWAN(F(" - Set dummy AppSKey"), &WaspLoRaWAN::setAppSessionKey, "00000000000000000000000000000000"); + setLoRaWAN(F(" - Set dummy NwkSKey"), &WaspLoRaWAN::setNwkSessionKey, "00000000000000000000000000000000"); + // Not specifying a specific DevEUI will use the hardware EUI + setLoRaWAN(F(" - Set DevEUI to hardware EUI"), &WaspLoRaWAN::setDeviceEUI); + setLoRaWAN(F(" - Set AppEUI"), &WaspLoRaWAN::setAppEUI, APP_EUI); + setLoRaWAN(F(" - Set AppKey"), &WaspLoRaWAN::setAppKey, APP_KEY); + setLoRaWAN(F(" - Set data rate"), &WaspLoRaWAN::setDataRate, INITIAL_DR); + setLoRaWAN(F(" - Set ADR"), &WaspLoRaWAN::setADR, "on"); + setLoRaWAN(F(" - Save configuration"), &WaspLoRaWAN::saveConfig); + setLoRaWAN(F(" - Join OTAA"), &WaspLoRaWAN::joinOTAA); + setLoRaWAN(F(" - Save configuration"), &WaspLoRaWAN::saveConfig); +} + +void readSensor(uint8_t sensor, RunningMedian &samples) { + USB.print(F(" ")); + samples.clear(); + // Initiate a dummy reading for analog-to-digital converter channel selection + SensorGasv20.readValue(sensor); + for (uint8_t i = 0; i < samples.getSize(); i++) { + float f = SensorGasv20.readValue(sensor); + samples.add(f); + Utils.blinkGreenLED(); + printFloat(F(" "), f); + delay(1000); + } + USB.println(); + printInt(F(" count="), samples.getSize()); + printFloat(F(" mean="), samples.getAverage()); + printFloat(F(" median="), samples.getMedian()); + printFloat(F(" min="), samples.getLowest()); + printFloat(F(" max="), samples.getHighest()); + USB.println(); +} + +uint8_t setSampleData(RunningMedian samples, uint8_t i) { + int16_t median = samples.getMedian() * 100; + int16_t min = samples.getLowest() * 100; + int16_t max = samples.getHighest() * 100; + + data[i++] = min >> 8; + data[i++] = min; + data[i++] = median >> 8; + data[i++] = median; + data[i++] = max >> 8; + data[i++] = max; + return i; +} + +/** + * Takes all measurements, and populates the LoRaWAN data. + * + * @return the length of the data + */ +uint8_t takeMeasurements() { + printMemory(); + + // Turn on the sensor board + SensorGasv20.ON(); + USB.printf(" - Power on sensor board (waiting %u seconds for initialization)\n", boardInitSeconds); + USB.flush(); + delay(1000L * boardInitSeconds); + + // Temperature + USB.println(F(" - Temperature")); + RunningMedian temperatureSamples = RunningMedian(temperatureSampleCount); + readSensor(SENS_TEMPERATURE, temperatureSamples); + + // Atmospheric pressure + USB.println(F(" - Atmospheric pressure")); + SensorGasv20.setSensorMode(SENS_ON, SENS_PRESSURE); + delay(30); + + RunningMedian pressureSamples = RunningMedian(pressureSampleCount); + readSensor(SENS_PRESSURE, pressureSamples); + + SensorGasv20.setSensorMode(SENS_OFF, SENS_PRESSURE); + + // NO2 + USB.printf(" - NO2 (waiting %u seconds for initialization)\n", no2InitSeconds); + SensorGasv20.configureSensor(SENS_SOCKET3B, no2Gain, no2Resistance); + SensorGasv20.setSensorMode(SENS_ON, SENS_SOCKET3B); + USB.flush(); + delay(1000L * no2InitSeconds); + + RunningMedian no2Samples = RunningMedian(no2SampleCount); + readSensor(SENS_SOCKET3B, no2Samples); + + SensorGasv20.setSensorMode(SENS_OFF, SENS_SOCKET3B); + + // Turn off sensor board + USB.println(F(" - Switch off sensor board")); + SensorGasv20.OFF(); + delay(10); + + // Battery + USB.println(F(" - Battery")); + USB.print(F(" ")); + // First dummy reading for analog-to-digital converter channel selection + PWR.getBatteryLevel(); + Utils.blinkGreenLED(); + uint8_t batteryLevel = PWR.getBatteryLevel(); + printInt(F(" "), batteryLevel); + printFloat(F("% ("), PWR.getBatteryVolts()); + USB.println(" Volt)"); + + // Prepare a LoRaWAN message; MSB + uint8_t i = 0; + i = setSampleData(temperatureSamples, i); + i = setSampleData(pressureSamples, i); + i = setSampleData(no2Samples, i); + data[i++] = batteryLevel; + return i; +} + +void loop() { + Utils.blinkGreenLED(500, 5); + + USB.println(F("\nTaking measurements")); + + uint8_t dataLen = takeMeasurements(); + // In the Waspmote API v23, data must be passed as a HEX string. + char text[100]; + Utils.hex2str((uint8_t *) data, text, dataLen); + + USB.println(F("\nSending measurements")); + setLoRaWAN(F(" - Switch on LoRaWAN module"), &WaspLoRaWAN::ON, socket); + setLoRaWAN(F(" - Set ABP keys"), &WaspLoRaWAN::joinABP); + getLoRaWAN(F(" - Application EUI"), &WaspLoRaWAN::getAppEUI, LoRaWAN._appEUI); + getLoRaWAN(F(" - Device EUI"), &WaspLoRaWAN::getDeviceEUI, LoRaWAN._devEUI); + getLoRaWAN(F(" - Device Address"), &WaspLoRaWAN::getDeviceAddr, LoRaWAN._devAddr); + getLoRaWAN(F(" - Uplink counter"), &WaspLoRaWAN::getUpCounter, &LoRaWAN._upCounter); + getLoRaWAN(F(" - Downlink counter"), &WaspLoRaWAN::getDownCounter, &LoRaWAN._downCounter); + + USB.print(F(" - Sending packet ")); + USB.print(text); + + // Only try sending once + uint8_t result = LoRaWAN.sendUnconfirmed(PORT, text); + USB.print(F(": ")); + printLoRaWANResult(result); + + if (result == LORAWAN_ANSWER_OK) { + if (LoRaWAN._dataReceived) { + USB.print(F(" - Received downlink on port ")); + USB.println(LoRaWAN._port, DEC); + USB.print(F(" ")); + USB.println(LoRaWAN._data); + } + } + + getLoRaWAN(F(" - Uplink counter"), &WaspLoRaWAN::getUpCounter, &LoRaWAN._upCounter); + getLoRaWAN(F(" - Downlink counter"), &WaspLoRaWAN::getDownCounter, &LoRaWAN._downCounter); + + // Do we really want to repeat this until we get success? + setLoRaWAN(F(" - Switch off LoRaWAN module"), &WaspLoRaWAN::OFF, socket); + + printMemory(); + + Utils.blinkGreenLED(500, 3); + + USB.print(F("\nEntering deep sleep for ")); + USB.println(sleepTime); + // Deep sleep with all sensors off + PWR.deepSleep(sleepTime, RTC_OFFSET, RTC_ALM1_MODE1, ALL_OFF); + USB.println("\nWake up"); +} diff --git a/src/generated/Waspmote_Plug_and_sense_test_code.txt b/src/generated/Waspmote_Plug_and_sense_test_code.txt new file mode 100644 index 0000000..1482d38 --- /dev/null +++ b/src/generated/Waspmote_Plug_and_sense_test_code.txt @@ -0,0 +1,220 @@ +/* + * -------- Waspmote - Plug & Sense! - Code Generator ------------ + * + * Code generated with Waspmote Plug & Sense! Code Generator. + * This code is intended to be used only with Waspmote Plug & Sense! + * series (encapsulated line) and is not valid for Waspmote. Use only + * with Waspmote Plug & Sense! IDE (do not confuse with Waspmote IDE). + * + * Copyright (C) 2012 Libelium Comunicaciones Distribuidas S.L. + * http://www.libelium.com + * + * 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 . + * + * Version: 0.1 + * Generated: 04/10/2017 + * + */ + +// Step 1. Includes of the Sensor Board and Communications modules used + +#include + + + +#include + +// Step 2. Variables declaration + +char CONNECTOR_A[3] = "CA"; +char CONNECTOR_B[3] = "CB"; +char CONNECTOR_C[3] = "CC"; +char CONNECTOR_D[3] = "CD"; +char CONNECTOR_E[3] = "CE"; +char CONNECTOR_F[3] = "CF"; + +long sequenceNumber = 0; + +char nodeID[10] = "hlm-24-139"; + +char* sleepTime = "00:00:05:00"; + +char data[100]; + +float connectorAFloatValue; +float connectorBFloatValue; +float connectorCFloatValue; +float connectorDFloatValue; +float connectorEFloatValue; +float connectorFFloatValue; + +int connectorAIntValue; +int connectorBIntValue; +int connectorCIntValue; +int connectorDIntValue; +int connectorEIntValue; +int connectorFIntValue; + +char connectorAString[10]; +char connectorBString[10]; +char connectorCString[10]; +char connectorDString[10]; +char connectorEString[10]; +char connectorFString[10]; + +int batteryLevel; +char batteryLevelString[10]; +char BATTERY[4] = "BAT"; + +char* macAddress=""; + +packetXBee* packet; + + +void setup() +{ + +// Step 3. Communication module initialization + +// Step 4. Communication module to ON + + xbee802.ON(); + +// Step 5. Initial message composition + + // Memory allocation + packet=(packetXBee*) calloc(1,sizeof(packetXBee)); + // Choose transmission mode: UNICAST or BROADCAST + packet->mode=UNICAST; + // Set destination XBee parameters to packet + xbee802.setDestinationParams( packet, macAddress, "Hello, this is Waspmote Plug & Sense!\r\n", MAC_TYPE); + +// Step 6. Initial message transmission + + xbee802.sendXBee(packet); + // Free variables + free(packet); + packet=NULL; + +// Step 7. Communication module to OFF + + xbee802.OFF(); + delay(100); + + +} + +void loop() +{ +// Step 8. Turn on the Sensor Board + + //Turn on the sensor board + SensorGasv20.ON(); + //Turn on the RTC + RTC.ON(); + //supply stabilization delay + delay(100); + +// Step 9. Turn on the sensors + + //En el caso de la placa de eventos no aplica + + delay(10000); + + //Turning on NO2 Sensor + SensorGasv20.setSensorMode(SENS_ON, SENS_SOCKET3B); + delay(30000); + +// Step 10. Read the sensors + + + + // First dummy reading for analog-to-digital converter channel selection + PWR.getBatteryLevel(); + // Getting Battery Level + batteryLevel = PWR.getBatteryLevel(); + // Conversion into a string + itoa(batteryLevel, batteryLevelString, 10); + + //First dummy reading for analog-to-digital converter channel selection + SensorGasv20.readValue(SENS_TEMPERATURE); + //Sensor temperature reading + connectorAFloatValue = SensorGasv20.readValue(SENS_TEMPERATURE); + //Conversion into a string + Utils.float2String(connectorAFloatValue, connectorAString, 2); + + //First dummy reading for analog-to-digital converter channel selection + SensorGasv20.readValue(SENS_HUMIDITY); + //Sensor temperature reading + connectorBFloatValue = SensorGasv20.readValue(SENS_HUMIDITY); + //Conversion into a string + Utils.float2String(connectorBFloatValue, connectorBString, 2); + + // Configuring NO2 sensor + SensorGasv20.configureSensor(SENS_SOCKET3B, 1, 2); + delay(10); + //First dummy reading to set analog-to-digital channel + SensorGasv20.readValue(SENS_SOCKET3B); + connectorDFloatValue = SensorGasv20.readValue(SENS_SOCKET3B); + //Conversion into a string + Utils.float2String(connectorDFloatValue, connectorDString, 2); + +// Step 11. Turn off the sensors + + //En el caso de la placa de eventos no aplica + + SensorGasv20.setSensorMode(SENS_OFF, SENS_SOCKET3B); + +// Step 12. Message composition + + //Data payload composition + sprintf(data,"I:%s#N:%li#%s:%s#%s:%s#%s:%s#%s:%s\r\n", + nodeID , + sequenceNumber, + BATTERY, batteryLevelString, + CONNECTOR_A , connectorAString, + CONNECTOR_B , connectorBString, + CONNECTOR_D , connectorDString); + + // Memory allocation + packet=(packetXBee*) calloc(1,sizeof(packetXBee)); + // Choose transmission mode: UNICAST or BROADCAST + packet->mode=UNICAST; + // Set destination XBee parameters to packet + xbee802.setDestinationParams( packet, macAddress, data, MAC_TYPE); + +// Step 13. Communication module to ON + + xbee802.ON(); + +// Step 14. Message transmission + + xbee802.sendXBee(packet); + // Free variables + free(packet); + packet=NULL; + +// Step 15. Communication module to OFF + + xbee802.OFF(); + delay(100); + +// Step 16. Entering Sleep Mode + + PWR.deepSleep(sleepTime,RTC_OFFSET,RTC_ALM1_MODE1,ALL_OFF); + //Increase the sequence number after wake up + sequenceNumber++; + + +} diff --git a/tools/README.md b/tools/README.md new file mode 100644 index 0000000..0d58b95 --- /dev/null +++ b/tools/README.md @@ -0,0 +1,4 @@ +# Tools + +These are the tools as downloaded from the public Libelium website, November 2017. Newer versions might be available, +but might work with the older hardware. diff --git a/tools/macos/FTDIUSBSerialDriver_v2_4_2.dmg b/tools/macos/FTDIUSBSerialDriver_v2_4_2.dmg new file mode 100644 index 0000000..3ba6099 Binary files /dev/null and b/tools/macos/FTDIUSBSerialDriver_v2_4_2.dmg differ diff --git a/tools/macos/javaforosx-OSX-2015-001.dmg b/tools/macos/javaforosx-OSX-2015-001.dmg new file mode 100644 index 0000000..632cb31 Binary files /dev/null and b/tools/macos/javaforosx-OSX-2015-001.dmg differ diff --git a/tools/macos/javaforosx-macOS-2017-001.dmg b/tools/macos/javaforosx-macOS-2017-001.dmg new file mode 100644 index 0000000..939c491 Binary files /dev/null and b/tools/macos/javaforosx-macOS-2017-001.dmg differ diff --git a/tools/macos/waspmote-pro-ide-v04-macosx.zip b/tools/macos/waspmote-pro-ide-v04-macosx.zip new file mode 100644 index 0000000..6dd28d7 Binary files /dev/null and b/tools/macos/waspmote-pro-ide-v04-macosx.zip differ diff --git a/tools/waspmote-ide-user-guide.pdf b/tools/waspmote-ide-user-guide.pdf new file mode 100644 index 0000000..e18643c Binary files /dev/null and b/tools/waspmote-ide-user-guide.pdf differ diff --git a/tools/windows/waspmote-pro-ide-v04-windows.zip b/tools/windows/waspmote-pro-ide-v04-windows.zip new file mode 100644 index 0000000..5a71a35 Binary files /dev/null and b/tools/windows/waspmote-pro-ide-v04-windows.zip differ