From 2ca05d269912f8354743822f4bb09132cd75262d Mon Sep 17 00:00:00 2001 From: Terry Moore Date: Sat, 31 Mar 2018 17:40:50 -0400 Subject: [PATCH 01/17] Ignore build directory at top, if any --- .gitignore | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.gitignore b/.gitignore index c0011a1..7a06e15 100644 --- a/.gitignore +++ b/.gitignore @@ -45,3 +45,6 @@ __vm # VS Code things .vscode + +# the build directory +build \ No newline at end of file From 7bc6377542e98044e6494721a0819d3512ef66f1 Mon Sep 17 00:00:00 2001 From: Terry Moore Date: Sat, 31 Mar 2018 17:38:52 -0400 Subject: [PATCH 02/17] As received from Suze after the Hackathon --- catena4460_aqi/catena4460_aqi.ino | 595 ++++++++++++++++++++++++++++++ 1 file changed, 595 insertions(+) create mode 100644 catena4460_aqi/catena4460_aqi.ino diff --git a/catena4460_aqi/catena4460_aqi.ino b/catena4460_aqi/catena4460_aqi.ino new file mode 100644 index 0000000..68ba561 --- /dev/null +++ b/catena4460_aqi/catena4460_aqi.ino @@ -0,0 +1,595 @@ +/* catena4450m101_sensor.ino Tue Mar 28 2017 19:52:20 tmm */ + +/* + +Module: catena4450m101_sensor.ino + +Function: + Code for the electric sensor with Catena 4450-M101. + +Version: + V0.1.1 Tue Mar 28 2017 19:52:20 tmm Edit level 1 + +Copyright notice: + This file copyright (C) 2017 by + + MCCI Corporation + 3520 Krums Corners Road + Ithaca, NY 14850 + + An unpublished work. All rights reserved. + + This file is proprietary information, and may not be disclosed or + copied without the prior permission of MCCI Corporation. + +Author: + Terry Moore, MCCI Corporation March 2017 + +Revision history: + 0.1.0 Fri Mar 10 2017 21:42:21 tmm + Module created. + + 0.1.1 Tue Mar 28 2017 19:52:20 tmm + Fix bug: not reading current lux value because sensor was not in + continuous mode. Add 150uA of current draw. + +*/ + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +/****************************************************************************\ +| +| Manifest constants & typedefs. +| +| This is strictly for private types and constants which will not +| be exported. +| +\****************************************************************************/ + +using namespace McciCatena; +using ThisCatena = Catena4450; + +/* how long do we wait between measurements (in seconds) */ +enum { + // set this to interval between measurements, in seconds + // Actual time will be a little longer because have to + // add measurement and broadcast time. + CATCFG_T_CYCLE = 30, // every 30 seconds + CATCFG_T_WARMUP = 1, + CATCFG_T_SETTLE = 5, + CATCFG_T_INTERVAL = CATCFG_T_CYCLE - (CATCFG_T_WARMUP + + CATCFG_T_SETTLE), + }; + +// forwards +static void settleDoneCb(osjob_t *pSendJob); +static void warmupDoneCb(osjob_t *pSendJob); +static void txFailedDoneCb(osjob_t *pSendJob); +static void sleepDoneCb(osjob_t *pSendJob); +static Arduino_LoRaWAN::SendBufferCbFn sendBufferDoneCb; + +/****************************************************************************\ +| +| Read-only data. +| +| If program is to be ROM-able, these must all be tagged read-only +| using the ROM storage class; they may be global. +| +\****************************************************************************/ + +static const char sVersion[] = "0.1.7"; + +/****************************************************************************\ +| +| VARIABLES: +| +| If program is to be ROM-able, these must be initialized +| using the BSS keyword. (This allows for compilers that require +| every variable to have an initializer.) Note that only those +| variables owned by this module should be declared here, using the BSS +| keyword; this allows for linkers that dislike multiple declarations +| of objects. +| +\****************************************************************************/ + +// globals +ThisCatena gCatena; + +// +// the LoRaWAN backhaul. Note that we use the +// ThisCatena version so it can provide hardware-specific +// information to the base class. +// +ThisCatena::LoRaWAN gLoRaWAN; + +// +// the LED +// +StatusLed gLed (ThisCatena::PIN_STATUS_LED); + +// the RTC instance, used for sleeping +CatenaRTC gRtc; + +// The temperature/humidity sensor +Adafruit_BME680 bme; // The default initalizer creates an I2C connection +bool fBme; + +// The LUX sensor +BH1750 bh1750; +bool fLux; + +// The contact sensors +bool fHasPower1; +uint8_t kPinPower1P1; +uint8_t kPinPower1P2; + +cTotalizer gPower1P1; +cTotalizer gPower1P2; + +// the job that's used to synchronize us with the LMIC code +static osjob_t sensorJob; +void sensorJob_cb(osjob_t *pJob); + +// function for scaling power +static uint16_t +dNdT_getFrac( + uint32_t deltaC, + uint32_t delta_ms + ); + + +/* + +Name: setup() + +Function: + Arduino setup function. + +Definition: + void setup( + void + ); + +Description: + This function is called by the Arduino framework after + basic framework has been initialized. We initialize the sensors + that are present on the platform, set up the LoRaWAN connection, + and (ultimately) return to the framework, which then calls loop() + forever. + +Returns: + No explicit result. + +*/ + +void setup(void) + { + gCatena.begin(); + + gCatena.SafePrintf("Catena 4450 sensor1 V%s\n", sVersion); + + gLed.begin(); + gCatena.registerObject(&gLed); + + // set up the RTC object + gRtc.begin(); + + gCatena.SafePrintf("LoRaWAN init: "); + if (!gLoRaWAN.begin(&gCatena)) + { + gCatena.SafePrintf("failed\n"); + gCatena.registerObject(&gLoRaWAN); + } + else + { + gCatena.SafePrintf("OK\n"); + gCatena.registerObject(&gLoRaWAN); + } + + ThisCatena::UniqueID_string_t CpuIDstring; + + gCatena.SafePrintf("CPU Unique ID: %s\n", + gCatena.GetUniqueIDstring(&CpuIDstring) + ); + + /* find the platform */ + const ThisCatena::EUI64_buffer_t *pSysEUI = gCatena.GetSysEUI(); + + uint32_t flags; + const CATENA_PLATFORM * const pPlatform = gCatena.GetPlatform(); + + if (pPlatform) + { + gCatena.SafePrintf("EUI64: "); + for (unsigned i = 0; i < sizeof(pSysEUI->b); ++i) + { + gCatena.SafePrintf("%s%02x", i == 0 ? "" : "-", pSysEUI->b[i]); + } + gCatena.SafePrintf("\n"); + flags = gCatena.GetPlatformFlags(); + gCatena.SafePrintf( + "Platform Flags: %#010x\n", + flags + ); + gCatena.SafePrintf( + "Operating Flags: %#010x\n", + gCatena.GetOperatingFlags() + ); + } + else + { + gCatena.SafePrintf("**** no platform, check provisioning ****\n"); + flags = 0; + } + + + /* initialize the lux sensor */ + if (flags & CatenaSamd21::fHasLuxRohm) + { + bh1750.begin(); + fLux = true; + bh1750.configure(BH1750_CONTINUOUS_HIGH_RES_MODE_2); + } + else + { + fLux = false; + } + + /* initialize the BME680 */ + if (! /* bme.begin(BME680_ADDRESS, Adafruit_BME680::OPERATING_MODE::Sleep) */ + bme.begin()) // the Adafruit BME680 lib doesn't have the MCCI low-power hacks + { + gCatena.SafePrintf("No BME680 found: check wiring\n"); + fBme = false; + } + else + { + fBme = true; + } + + /* is it modded? */ + uint32_t modnumber = gCatena.PlatformFlags_GetModNumber(flags); + + fHasPower1 = false; + + if (modnumber != 0) + { + gCatena.SafePrintf("Catena 4450-M%u\n", modnumber); + if (modnumber == 101) + { + fHasPower1 = true; + kPinPower1P1 = A0; + kPinPower1P2 = A1; + } + else + { + gCatena.SafePrintf("unknown mod number %d\n", modnumber); + } + } + else + { + gCatena.SafePrintf("No mods detected\n"); + } + + if (fHasPower1) + { + if (! gPower1P1.begin(kPinPower1P1) || + ! gPower1P2.begin(kPinPower1P2)) + { + fHasPower1 = false; + } + } + + /* now, we kick off things by sending our first message */ + gLed.Set(LedPattern::Joining); + + // unit testing for the scaling functions + //gCatena.SafePrintf( + // "dNdT_getFrac tests: " + // "0/0: %04x 90/6m: %04x 89/6:00.1: %04x 1439/6m: %04x\n", + // dNdT_getFrac(0, 0), + // dNdT_getFrac(90, 6 * 60 * 1000), + // dNdT_getFrac(89, 6 * 60 * 1000 + 100), + // dNdT_getFrac(1439, 6 * 60 * 1000) + // ); + //gCatena.SafePrintf( + // "dNdT_getFrac tests: " + // "1/6m: %04x 20/6m: %04x 1/60:00.1: %04x 1440/5:59.99: %04x\n", + // dNdT_getFrac(1, 6 * 60 * 1000), + // dNdT_getFrac(20, 6 * 60 * 1000), + // dNdT_getFrac(1, 60 * 60 * 1000 + 100), + // dNdT_getFrac(1440, 6 * 60 * 1000 - 10) + // ); + + /* warm up the BME680 by discarding a measurement */ + if (fBme) + (void)bme.readTemperature(); + + /* trigger a join by sending the first packet */ + startSendingUplink(); + } + +// The Arduino loop routine -- in our case, we just drive the other loops. +// If we try to do too much, we can break the LMIC radio. So the work is +// done by outcalls scheduled from the LMIC os loop. +void loop() + { + gCatena.poll(); + } + +static uint16_t dNdT_getFrac( + uint32_t deltaC, + uint32_t delta_ms + ) + { + if (delta_ms == 0 || deltaC == 0) + return 0; + + // this is a value in [0,1) + float dNdTperHour = float(deltaC * 250) / float(delta_ms); + + if (dNdTperHour <= 0) + return 0; + else if (dNdTperHour >= 1) + return 0xFFFF; + else + { + int iExp; + float normalValue; + normalValue = frexpf(dNdTperHour, &iExp); + + // dNdTperHour is supposed to be in [0..1), so useful exp + // is [0..-15] + iExp += 15; + if (iExp < 0) + iExp = 0; + if (iExp > 15) + return 0xFFFF; + + + return (uint16_t)((iExp << 12u) + (unsigned) scalbnf(normalValue, 12)); + } + } + +void startSendingUplink(void) +{ + TxBuffer_t b; + LedPattern savedLed = gLed.Set(LedPattern::Measuring); + + b.begin(); + FlagsSensor2 flag; + + flag = FlagsSensor2(0); + + b.put(FormatSensor2); /* the flag for this record format */ + uint8_t * const pFlag = b.getp(); + b.put(0x00); /* will be set to the flags */ + + // vBat is sent as 5000 * v + float vBat = gCatena.ReadVbat(); + gCatena.SafePrintf("vBat: %d mV\n", (int) (vBat * 1000.0f)); + b.putV(vBat); + flag |= FlagsSensor2::FlagVbat; + + uint32_t bootCount; + if (gCatena.getBootCount(bootCount)) + { + b.putBootCountLsb(bootCount); + flag |= FlagsSensor2::FlagBoot; + } + + if (fBme) + { + // Adafruit_BME280::Measurements m = bme.readTemperaturePressureHumidity(); + // temperature is 2 bytes from -0x80.00 to +0x7F.FF degrees C + // pressure is 2 bytes, hPa * 10. + // humidity is one byte, where 0 == 0/256 and 0xFF == 255/256. + bme.performReading(); + gCatena.SafePrintf( + "BME680: T: %d P: %d RH: %d\n", + (int) bme.temperature, + (int) bme.pressure, + (int) bme.humidity + ); + b.putT(bme.temperature); + b.putP(bme.pressure); + b.putRH(bme.humidity); + + flag |= FlagsSensor2::FlagTPH; + } + + if (fLux) + { + /* Get a new sensor event */ + uint16_t light; + + light = bh1750.readLightLevel(); + gCatena.SafePrintf("BH1750: %u lux\n", light); + b.putLux(light); + flag |= FlagsSensor2::FlagLux; + } + + if (fHasPower1) + { + uint32_t power1in, power1out; + uint32_t power1in_dc, power1in_dt; + uint32_t power1out_dc, power1out_dt; + + power1in = gPower1P1.getcurrent(); + gPower1P1.getDeltaCountAndTime(power1in_dc, power1in_dt); + gPower1P1.setReference(); + + power1out = gPower1P2.getcurrent(); + gPower1P2.getDeltaCountAndTime(power1out_dc, power1out_dt); + gPower1P2.setReference(); + + gCatena.SafePrintf( + "Power: IN: %u OUT: %u\n", + power1in, + power1out + ); + b.putWH(power1in); + b.putWH(power1out); + flag |= FlagsSensor2::FlagWattHours; + + // we know that we get at most 4 pulses per second, no matter + // the scaling. Therefore, if we convert to pulses/hour, we'll + // have a value that is no more than 3600 * 4, or 14,400. + // This fits in 14 bits. At low pulse rates, there's more + // info in the denominator than in the numerator. So FP is really + // called for. We use a simple floating point format, since this + // is unsigned: 4 bits of exponent, 12 bits of fraction, and we + // don't bother to omit the MSB of the (normalized fraction); + // and we multiply by 2^(exp+1) (so 0x0800 is 2 * 0.5 == 1, + // 0xF8000 is 2^16 * 1 = 65536). + uint16_t fracPower1In = dNdT_getFrac(power1in_dc, power1in_dt); + uint16_t fracPower1Out = dNdT_getFrac(power1out_dc, power1out_dt); + b.putPulseFraction( + fracPower1In + ); + b.putPulseFraction( + fracPower1Out + ); + + gCatena.SafePrintf( + "Power: IN: %u/%u (%04x) OUT: %u/%u (%04x)\n", + power1in_dc, power1in_dt, + fracPower1In, + power1out_dc, power1out_dt, + fracPower1Out + ); + + flag |= FlagsSensor2::FlagPulsesPerHour; + } + + *pFlag = uint8_t(flag); + if (savedLed != LedPattern::Joining) + gLed.Set(LedPattern::Sending); + else + gLed.Set(LedPattern::Joining); + + gLoRaWAN.SendBuffer(b.getbase(), b.getn(), sendBufferDoneCb, NULL); +} + +static void +sendBufferDoneCb( + void *pContext, + bool fStatus + ) + { + osjobcb_t pFn; + + gLed.Set(LedPattern::Settling); + if (! fStatus) + { + gCatena.SafePrintf("send buffer failed\n"); + pFn = txFailedDoneCb; + } + else + { + pFn = settleDoneCb; + } + os_setTimedCallback( + &sensorJob, + os_getTime()+sec2osticks(CATCFG_T_SETTLE), + pFn + ); + } + +static void +txFailedDoneCb( + osjob_t *pSendJob + ) + { + gCatena.SafePrintf("not provisioned, idling\n"); + gLoRaWAN.Shutdown(); + gLed.Set(LedPattern::NotProvisioned); + } + + +// +// the following API is added to delay.c, .h in the BSP. It adjust millis() +// forward after a deep sleep. +// +// extern "C" { void adjust_millis_forward(unsigned); }; +// +// If you don't have it, check the following commit at github: +// https://github.com/mcci-catena/ArduinoCore-samd/commit/78d8440dbcd29bf5ac659fd65514268c1334f683 +// + +static void settleDoneCb( + osjob_t *pSendJob + ) + { + uint32_t startTime; + + // if connected to USB, don't sleep + // ditto if we're monitoring pulses. + + // enable/disable sleep when connected to USB + //if (Serial.dtr() || fHasPower1) + if (Serial.dtr() | fHasPower1 || true) + { + gLed.Set(LedPattern::Sleeping); + os_setTimedCallback( + &sensorJob, + os_getTime() + sec2osticks(CATCFG_T_INTERVAL), + sleepDoneCb + ); + return; + } + + /* ok... now it's time for a deep sleep */ + gLed.Set(LedPattern::Off); + + startTime = millis(); + gRtc.SetAlarm(CATCFG_T_INTERVAL); + gRtc.SleepForAlarm( + gRtc.MATCH_HHMMSS, + gRtc.SleepMode::IdleCpuAhbApb + ); + + // add the number of ms that we were asleep to the millisecond timer. + // we don't need extreme accuracy. + adjust_millis_forward(CATCFG_T_INTERVAL * 1000); + + /* and now... we're awake again. trigger another measurement */ + sleepDoneCb(pSendJob); + } + +static void sleepDoneCb( + osjob_t *pJob + ) + { + gLed.Set(LedPattern::WarmingUp); + + os_setTimedCallback( + &sensorJob, + os_getTime() + sec2osticks(CATCFG_T_WARMUP), + warmupDoneCb + ); + } + +static void warmupDoneCb( + osjob_t *pJob + ) + { + startSendingUplink(); + } \ No newline at end of file From 071d9d523f9c768d12c5481af6b0b0208c730e0d Mon Sep 17 00:00:00 2001 From: Terry Moore Date: Sat, 31 Mar 2018 22:08:18 -0400 Subject: [PATCH 03/17] Add air-quality index example for Catena 4460 --- catena4460_aqi/catena4460_aqi.ino | 240 ++++++++++-------------------- 1 file changed, 81 insertions(+), 159 deletions(-) diff --git a/catena4460_aqi/catena4460_aqi.ino b/catena4460_aqi/catena4460_aqi.ino index 68ba561..fa3161a 100644 --- a/catena4460_aqi/catena4460_aqi.ino +++ b/catena4460_aqi/catena4460_aqi.ino @@ -1,46 +1,39 @@ -/* catena4450m101_sensor.ino Tue Mar 28 2017 19:52:20 tmm */ +/* catena4450_aqi.ino Sat Mar 31 2018 21:06:03 tmm */ /* -Module: catena4450m101_sensor.ino +Module: catena4450_aqi.ino Function: - Code for the electric sensor with Catena 4450-M101. + Code for the air-quality sensor with Catena 4460. Version: - V0.1.1 Tue Mar 28 2017 19:52:20 tmm Edit level 1 + V0.1.1 Sat Mar 31 2018 21:06:03 tmm Edit level 1 Copyright notice: - This file copyright (C) 2017 by + This file copyright (C) 2017-2018 by MCCI Corporation 3520 Krums Corners Road Ithaca, NY 14850 - An unpublished work. All rights reserved. - - This file is proprietary information, and may not be disclosed or - copied without the prior permission of MCCI Corporation. + An unpublished work. All rights reserved. All rights reserved. The license + file accompanying this file outlines the license granted. Author: - Terry Moore, MCCI Corporation March 2017 + Terry Moore, MCCI Corporation & Suzen Filke December 201u Revision history: - 0.1.0 Fri Mar 10 2017 21:42:21 tmm - Module created. - - 0.1.1 Tue Mar 28 2017 19:52:20 tmm - Fix bug: not reading current lux value because sensor was not in - continuous mode. Add 150uA of current draw. + 0.1.1 Sat Mar 31 2018 21:06:03 tmm + Adaptation for AQI. */ -#include +#include #include #include #include #include -#include #include #include @@ -64,7 +57,6 @@ Revision history: \****************************************************************************/ using namespace McciCatena; -using ThisCatena = Catena4450; /* how long do we wait between measurements (in seconds) */ enum { @@ -72,6 +64,10 @@ enum { // Actual time will be a little longer because have to // add measurement and broadcast time. CATCFG_T_CYCLE = 30, // every 30 seconds + }; + +/* Additional timing parameters */ +enum { CATCFG_T_WARMUP = 1, CATCFG_T_SETTLE = 5, CATCFG_T_INTERVAL = CATCFG_T_CYCLE - (CATCFG_T_WARMUP + @@ -84,6 +80,8 @@ static void warmupDoneCb(osjob_t *pSendJob); static void txFailedDoneCb(osjob_t *pSendJob); static void sleepDoneCb(osjob_t *pSendJob); static Arduino_LoRaWAN::SendBufferCbFn sendBufferDoneCb; +void fillBuffer(TxBuffer_t &b); +void startSendingUplink(void); /****************************************************************************\ | @@ -94,7 +92,7 @@ static Arduino_LoRaWAN::SendBufferCbFn sendBufferDoneCb; | \****************************************************************************/ -static const char sVersion[] = "0.1.7"; +static const char sVersion[] = "0.1.1"; /****************************************************************************\ | @@ -110,19 +108,19 @@ static const char sVersion[] = "0.1.7"; \****************************************************************************/ // globals -ThisCatena gCatena; +Catena gCatena; // // the LoRaWAN backhaul. Note that we use the -// ThisCatena version so it can provide hardware-specific +// Catena version so it can provide hardware-specific // information to the base class. // -ThisCatena::LoRaWAN gLoRaWAN; +Catena::LoRaWAN gLoRaWAN; // // the LED // -StatusLed gLed (ThisCatena::PIN_STATUS_LED); +StatusLed gLed (Catena::PIN_STATUS_LED); // the RTC instance, used for sleeping CatenaRTC gRtc; @@ -135,26 +133,10 @@ bool fBme; BH1750 bh1750; bool fLux; -// The contact sensors -bool fHasPower1; -uint8_t kPinPower1P1; -uint8_t kPinPower1P2; - -cTotalizer gPower1P1; -cTotalizer gPower1P2; - -// the job that's used to synchronize us with the LMIC code +// the LMIC job that's used to synchronize us with the LMIC code static osjob_t sensorJob; void sensorJob_cb(osjob_t *pJob); -// function for scaling power -static uint16_t -dNdT_getFrac( - uint32_t deltaC, - uint32_t delta_ms - ); - - /* Name: setup() @@ -183,7 +165,7 @@ void setup(void) { gCatena.begin(); - gCatena.SafePrintf("Catena 4450 sensor1 V%s\n", sVersion); + gCatena.SafePrintf("Catena 4460 sensor1 V%s\n", sVersion); gLed.begin(); gCatena.registerObject(&gLed); @@ -203,14 +185,14 @@ void setup(void) gCatena.registerObject(&gLoRaWAN); } - ThisCatena::UniqueID_string_t CpuIDstring; + Catena::UniqueID_string_t CpuIDstring; gCatena.SafePrintf("CPU Unique ID: %s\n", gCatena.GetUniqueIDstring(&CpuIDstring) ); /* find the platform */ - const ThisCatena::EUI64_buffer_t *pSysEUI = gCatena.GetSysEUI(); + const Catena::EUI64_buffer_t *pSysEUI = gCatena.GetSysEUI(); uint32_t flags; const CATENA_PLATFORM * const pPlatform = gCatena.GetPlatform(); @@ -253,8 +235,9 @@ void setup(void) } /* initialize the BME680 */ - if (! /* bme.begin(BME680_ADDRESS, Adafruit_BME680::OPERATING_MODE::Sleep) */ - bme.begin()) // the Adafruit BME680 lib doesn't have the MCCI low-power hacks + if (flags & Catena::fHasBme680 && + /* ! bme.begin(BME680_ADDRESS, Adafruit_BME680::OPERATING_MODE::Sleep) */ + ! bme.begin()) // the Adafruit BME680 lib doesn't have the MCCI low-power hacks { gCatena.SafePrintf("No BME680 found: check wiring\n"); fBme = false; @@ -267,57 +250,18 @@ void setup(void) /* is it modded? */ uint32_t modnumber = gCatena.PlatformFlags_GetModNumber(flags); - fHasPower1 = false; - if (modnumber != 0) { - gCatena.SafePrintf("Catena 4450-M%u\n", modnumber); - if (modnumber == 101) - { - fHasPower1 = true; - kPinPower1P1 = A0; - kPinPower1P2 = A1; - } - else - { - gCatena.SafePrintf("unknown mod number %d\n", modnumber); - } + gCatena.SafePrintf("Catena 4460-M%u\n", modnumber); } else { gCatena.SafePrintf("No mods detected\n"); } - if (fHasPower1) - { - if (! gPower1P1.begin(kPinPower1P1) || - ! gPower1P2.begin(kPinPower1P2)) - { - fHasPower1 = false; - } - } - /* now, we kick off things by sending our first message */ gLed.Set(LedPattern::Joining); - // unit testing for the scaling functions - //gCatena.SafePrintf( - // "dNdT_getFrac tests: " - // "0/0: %04x 90/6m: %04x 89/6:00.1: %04x 1439/6m: %04x\n", - // dNdT_getFrac(0, 0), - // dNdT_getFrac(90, 6 * 60 * 1000), - // dNdT_getFrac(89, 6 * 60 * 1000 + 100), - // dNdT_getFrac(1439, 6 * 60 * 1000) - // ); - //gCatena.SafePrintf( - // "dNdT_getFrac tests: " - // "1/6m: %04x 20/6m: %04x 1/60:00.1: %04x 1440/5:59.99: %04x\n", - // dNdT_getFrac(1, 6 * 60 * 1000), - // dNdT_getFrac(20, 6 * 60 * 1000), - // dNdT_getFrac(1, 60 * 60 * 1000 + 100), - // dNdT_getFrac(1440, 6 * 60 * 1000 - 10) - // ); - /* warm up the BME680 by discarding a measurement */ if (fBme) (void)bme.readTemperature(); @@ -332,30 +276,29 @@ void setup(void) void loop() { gCatena.poll(); + if (gCatena.GetOperatingFlags() & + static_cast(gCatena.OPERATING_FLAGS::fManufacturingTest)) + { + TxBuffer_t b; + fillBuffer(b); + } } -static uint16_t dNdT_getFrac( - uint32_t deltaC, - uint32_t delta_ms +static uint16_t f2uflt16( + float f ) { - if (delta_ms == 0 || deltaC == 0) + if (f < 0) return 0; - - // this is a value in [0,1) - float dNdTperHour = float(deltaC * 250) / float(delta_ms); - - if (dNdTperHour <= 0) - return 0; - else if (dNdTperHour >= 1) + else if (f >= 1.0) return 0xFFFF; else { int iExp; float normalValue; - normalValue = frexpf(dNdTperHour, &iExp); + normalValue = frexpf(f, &iExp); - // dNdTperHour is supposed to be in [0..1), so useful exp + // f is supposed to be in [0..1), so useful exp // is [0..-15] iExp += 15; if (iExp < 0) @@ -368,51 +311,57 @@ static uint16_t dNdT_getFrac( } } -void startSendingUplink(void) +void fillBuffer(TxBuffer_t &b) { - TxBuffer_t b; - LedPattern savedLed = gLed.Set(LedPattern::Measuring); b.begin(); - FlagsSensor2 flag; + FlagsSensor5 flag; - flag = FlagsSensor2(0); + flag = FlagsSensor5(0); b.put(FormatSensor2); /* the flag for this record format */ + + /* capture the address of the flag byte */ uint8_t * const pFlag = b.getp(); - b.put(0x00); /* will be set to the flags */ + + b.put(0x00); /* insert a byte. Value doesn't matter, will + || later be set to the final value of `flag` + */ // vBat is sent as 5000 * v float vBat = gCatena.ReadVbat(); gCatena.SafePrintf("vBat: %d mV\n", (int) (vBat * 1000.0f)); b.putV(vBat); - flag |= FlagsSensor2::FlagVbat; + flag |= FlagsSensor5::FlagVbat; uint32_t bootCount; if (gCatena.getBootCount(bootCount)) { b.putBootCountLsb(bootCount); - flag |= FlagsSensor2::FlagBoot; + flag |= FlagsSensor5::FlagBoot; } if (fBme) { - // Adafruit_BME280::Measurements m = bme.readTemperaturePressureHumidity(); + // bme.performReading() loads a consistent measurement into + // the bme. + // // temperature is 2 bytes from -0x80.00 to +0x7F.FF degrees C // pressure is 2 bytes, hPa * 10. // humidity is one byte, where 0 == 0/256 and 0xFF == 255/256. bme.performReading(); gCatena.SafePrintf( - "BME680: T: %d P: %d RH: %d\n", + "BME680: T: %d P: %d RH: %d Gas-Resistance: %d\n", (int) bme.temperature, (int) bme.pressure, - (int) bme.humidity + (int) bme.humidity, + (int) bme.gas_resistance ); b.putT(bme.temperature); b.putP(bme.pressure); b.putRH(bme.humidity); - flag |= FlagsSensor2::FlagTPH; + flag |= FlagsSensor5::FlagTPH; } if (fLux) @@ -423,63 +372,37 @@ void startSendingUplink(void) light = bh1750.readLightLevel(); gCatena.SafePrintf("BH1750: %u lux\n", light); b.putLux(light); - flag |= FlagsSensor2::FlagLux; + flag |= FlagsSensor5::FlagLux; } - if (fHasPower1) + if (fBme) { - uint32_t power1in, power1out; - uint32_t power1in_dc, power1in_dt; - uint32_t power1out_dc, power1out_dt; - - power1in = gPower1P1.getcurrent(); - gPower1P1.getDeltaCountAndTime(power1in_dc, power1in_dt); - gPower1P1.setReference(); + // compute the AQI (in 0..500/512) + constexpr float slope = 44.52282f / 512.0f; + float const AQIsimple = (logf(bme.gas_resistance) + 16.3787f) * slope; - power1out = gPower1P2.getcurrent(); - gPower1P2.getDeltaCountAndTime(power1out_dc, power1out_dt); - gPower1P2.setReference(); + uint16_t const encodedAQI = f2uflt16(AQIsimple); gCatena.SafePrintf( - "Power: IN: %u OUT: %u\n", - power1in, - power1out - ); - b.putWH(power1in); - b.putWH(power1out); - flag |= FlagsSensor2::FlagWattHours; - - // we know that we get at most 4 pulses per second, no matter - // the scaling. Therefore, if we convert to pulses/hour, we'll - // have a value that is no more than 3600 * 4, or 14,400. - // This fits in 14 bits. At low pulse rates, there's more - // info in the denominator than in the numerator. So FP is really - // called for. We use a simple floating point format, since this - // is unsigned: 4 bits of exponent, 12 bits of fraction, and we - // don't bother to omit the MSB of the (normalized fraction); - // and we multiply by 2^(exp+1) (so 0x0800 is 2 * 0.5 == 1, - // 0xF8000 is 2^16 * 1 = 65536). - uint16_t fracPower1In = dNdT_getFrac(power1in_dc, power1in_dt); - uint16_t fracPower1Out = dNdT_getFrac(power1out_dc, power1out_dt); - b.putPulseFraction( - fracPower1In - ); - b.putPulseFraction( - fracPower1Out + "BME680: AQI: %d\n", + (int) (AQIsimple * 512.0) ); - gCatena.SafePrintf( - "Power: IN: %u/%u (%04x) OUT: %u/%u (%04x)\n", - power1in_dc, power1in_dt, - fracPower1In, - power1out_dc, power1out_dt, - fracPower1Out - ); - - flag |= FlagsSensor2::FlagPulsesPerHour; + b.put2u(encodedAQI); + flag |= FlagsSensor5::FlagAqi; } *pFlag = uint8_t(flag); +} + + +void startSendingUplink(void) +{ + TxBuffer_t b; + LedPattern savedLed = gLed.Set(LedPattern::Measuring); + + fillBuffer(b); + if (savedLed != LedPattern::Joining) gLed.Set(LedPattern::Sending); else @@ -544,8 +467,7 @@ static void settleDoneCb( // ditto if we're monitoring pulses. // enable/disable sleep when connected to USB - //if (Serial.dtr() || fHasPower1) - if (Serial.dtr() | fHasPower1 || true) + if (Serial.dtr()) { gLed.Set(LedPattern::Sleeping); os_setTimedCallback( From f7ace11bdd4f67e99e571d25cdb0665cdd805850 Mon Sep 17 00:00:00 2001 From: Terry Moore Date: Sat, 31 Mar 2018 22:25:28 -0400 Subject: [PATCH 04/17] Document --- catena4460_aqi/README.md | 15 +++++++++++++++ catena4460_aqi/catena4460_aqi.ino | 4 ++-- 2 files changed, 17 insertions(+), 2 deletions(-) create mode 100644 catena4460_aqi/README.md diff --git a/catena4460_aqi/README.md b/catena4460_aqi/README.md new file mode 100644 index 0000000..62c81fb --- /dev/null +++ b/catena4460_aqi/README.md @@ -0,0 +1,15 @@ +# catena4460-aqi example + +This sketch computes a simple air-quality index based on the gas resistance returned by the BME680 that resides in the Catena 4460. + +It transmits a reading using `Catena_TxBuffer.h` format 0x17. This is similar to format 0x14, except that bit 0x20 of the flag byte indicates that a two-byte `uflt16` number is present representing the AQI. The AQI is a number ranging from 0..500, based on logarithmic gas conductivity (1/r), scaled according to the possible outputs of the BME680. It is divided by 512 prior to transmission in order to get into the [0,1) range limitation of the `uflt16` representation. So in the Javascript decoder, we multiply by 512 to recover the original data. + +Refer to [Understanding Catena Data Format 0x14](https://github.com/mcci-catena/Catena-Sketches/blob/master/extra/catena-message-0x14-format.md) for background information. + +This sketch honors the manufacturing-test bit of operating flags (bit 1, mask 0x00000002). If bit 1 is set, it will continually measure (and print results). This will interfere with LoRa operations in test mode. + +Please set the platform GUID to `3037D9BE-8EBE-4AE7-970E-91915A2484F8` during configuration: + +``` +system configure platformguid 3037D9BE-8EBE-4AE7-970E-91915A2484F8 +``` diff --git a/catena4460_aqi/catena4460_aqi.ino b/catena4460_aqi/catena4460_aqi.ino index fa3161a..e555bce 100644 --- a/catena4460_aqi/catena4460_aqi.ino +++ b/catena4460_aqi/catena4460_aqi.ino @@ -453,8 +453,8 @@ txFailedDoneCb( // // extern "C" { void adjust_millis_forward(unsigned); }; // -// If you don't have it, check the following commit at github: -// https://github.com/mcci-catena/ArduinoCore-samd/commit/78d8440dbcd29bf5ac659fd65514268c1334f683 +// If you don't have it, make sure you're running the MCCI Board Support +// Package for the Catena 4460, https://github.com/mcci-catena/arduino-boards // static void settleDoneCb( From 869fa5b79aa74b523d330132472a83c968f50c05 Mon Sep 17 00:00:00 2001 From: Terry Moore Date: Sun, 1 Apr 2018 00:48:50 -0400 Subject: [PATCH 05/17] Correct format byte --- catena4460_aqi/catena4460_aqi.ino | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/catena4460_aqi/catena4460_aqi.ino b/catena4460_aqi/catena4460_aqi.ino index e555bce..53899c3 100644 --- a/catena4460_aqi/catena4460_aqi.ino +++ b/catena4460_aqi/catena4460_aqi.ino @@ -319,7 +319,7 @@ void fillBuffer(TxBuffer_t &b) flag = FlagsSensor5(0); - b.put(FormatSensor2); /* the flag for this record format */ + b.put(FormatSensor5); /* the flag for this record format */ /* capture the address of the flag byte */ uint8_t * const pFlag = b.getp(); From 8280db9c671131e1cfdd4c25180d1eeab1d3aeea Mon Sep 17 00:00:00 2001 From: Terry Moore Date: Sun, 1 Apr 2018 00:49:13 -0400 Subject: [PATCH 06/17] Correct AQI calculation --- catena4460_aqi/catena4460_aqi.ino | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/catena4460_aqi/catena4460_aqi.ino b/catena4460_aqi/catena4460_aqi.ino index 53899c3..78fd103 100644 --- a/catena4460_aqi/catena4460_aqi.ino +++ b/catena4460_aqi/catena4460_aqi.ino @@ -379,7 +379,7 @@ void fillBuffer(TxBuffer_t &b) { // compute the AQI (in 0..500/512) constexpr float slope = 44.52282f / 512.0f; - float const AQIsimple = (logf(bme.gas_resistance) + 16.3787f) * slope; + float const AQIsimple = (-logf(bme.gas_resistance) + 16.3787f) * slope; uint16_t const encodedAQI = f2uflt16(AQIsimple); From f56dba2bb95ad758dea94d6602ee835841b06f17 Mon Sep 17 00:00:00 2001 From: Terry Moore Date: Sun, 1 Apr 2018 01:42:24 -0400 Subject: [PATCH 07/17] Add format 0x17 support --- extra/catena-generic-message-decoder-ttn.js | 458 ++++++++++++++++++ extra/catena-message-0x14-0x15-decoder-ttn.js | 358 -------------- extra/catena-message-0x17-decoder-node-red.js | 105 ++++ 3 files changed, 563 insertions(+), 358 deletions(-) create mode 100644 extra/catena-generic-message-decoder-ttn.js delete mode 100644 extra/catena-message-0x14-0x15-decoder-ttn.js create mode 100644 extra/catena-message-0x17-decoder-node-red.js diff --git a/extra/catena-generic-message-decoder-ttn.js b/extra/catena-generic-message-decoder-ttn.js new file mode 100644 index 0000000..e65345b --- /dev/null +++ b/extra/catena-generic-message-decoder-ttn.js @@ -0,0 +1,458 @@ +// This function decodes the records (port 1, format 0x11, 0x14, 0x15) +// sent by the MCCI Catena 4410/4450/4551 soil/water and power applications. +// For use with console.thethingsnetwork.org +// 2017-09-19 add dewpoints. +// 2017-12-13 fix commments, fix negative soil/water temp, add test vectors. +// 2017-12-15 add format 0x11. + +// calculate dewpoint (degrees C) given temperature (C) and relative humidity (0..100) +// from http://andrew.rsmas.miami.edu/bmcnoldy/Humidity.html +// rearranged for efficiency and to deal sanely with very low (< 1%) RH +function dewpoint(t, rh) { + var c1 = 243.04; + var c2 = 17.625; + var h = rh / 100; + if (h <= 0.01) + h = 0.01; + else if (h > 1.0) + h = 1.0; + + var lnh = Math.log(h); + var tpc1 = t + c1; + var txc2 = t * c2; + var txc2_tpc1 = txc2 / tpc1; + + var tdew = c1 * (lnh + txc2_tpc1) / (c2 - lnh - txc2_tpc1); + return tdew; +} + +function Decoder(bytes, port) { + // Decode an uplink message from a buffer + // (array) of bytes to an object of fields. + var decoded = {}; + + if (port === 1) { + cmd = bytes[0]; + if (cmd == 0x14) { + // decode Catena 4450 M101 data + + // test vectors: + // 14 01 18 00 ==> vBat = 1.5 + // 14 01 F8 00 ==> vBat = -0.5 + // 14 05 F8 00 42 ==> boot: 66, vBat: -0.5 + // 14 0D F8 00 42 17 80 59 35 80 ==> adds one temp of 23.5, rh = 50, p = 913.48 + + // i is used as the index into the message. Start with the flag byte. + var i = 1; + // fetch the bitmap. + var flags = bytes[i++]; + + if (flags & 0x1) { + // set vRaw to a uint16, and increment pointer + var vRaw = (bytes[i] << 8) + bytes[i + 1]; + i += 2; + // interpret uint16 as an int16 instead. + if (vRaw & 0x8000) + vRaw += -0x10000; + // scale and save in decoded. + decoded.vBat = vRaw / 4096.0; + } + + if (flags & 0x2) { + var vRawBus = (bytes[i] << 8) + bytes[i + 1]; + i += 2; + if (vRawBus & 0x8000) + vRawBus += -0x10000; + decoded.vBus = vRawBus / 4096.0; + } + + if (flags & 0x4) { + var iBoot = bytes[i]; + i += 1; + decoded.boot = iBoot; + } + + if (flags & 0x8) { + // we have temp, pressure, RH + var tRaw = (bytes[i] << 8) + bytes[i + 1]; + if (tRaw & 0x8000) + tRaw = -0x10000 + tRaw; + i += 2; + var pRaw = (bytes[i] << 8) + bytes[i + 1]; + i += 2; + var hRaw = bytes[i++]; + + decoded.tempC = tRaw / 256; + decoded.error = "none"; + decoded.p = pRaw * 4 / 100.0; + decoded.rh = hRaw / 256 * 100; + decoded.tDewC = dewpoint(decoded.tempC, decoded.rh); + } + + if (flags & 0x10) { + // we have lux + var luxRaw = (bytes[i] << 8) + bytes[i + 1]; + i += 2; + decoded.lux = luxRaw; + } + + if (flags & 0x20) { + // watthour + var powerIn = (bytes[i] << 8) + bytes[i + 1]; + i += 2; + var powerOut = (bytes[i] << 8) + bytes[i + 1]; + i += 2; + decoded.powerUsedCount = powerIn; + decoded.powerSourcedCount = powerOut; + } + + if (flags & 0x40) { + // normalize floating pulses per hour + var floatIn = (bytes[i] << 8) + bytes[i + 1]; + i += 2; + var floatOut = (bytes[i] << 8) + bytes[i + 1]; + i += 2; + + var exp1 = floatIn >> 12; + var exp2 = floatOut >> 12; + var mant1 = (floatIn & 0xFFF) / 4096.0; + var mant2 = (floatOut & 0xFFF) / 4096.0; + var powerPerHourIn = mant1 * Math.pow(2, exp1 - 15) * 60 * 60 * 4; + var powerPerHourOut = mant2 * Math.pow(2, exp2 - 15) * 60 * 60 * 4; + decoded.powerUsedPerHour = powerPerHourIn; + decoded.powerSourcedPerHour = powerPerHourOut; + } + } else if (cmd == 0x15) { + // decode Catena 4450 M102 data + + // test vectors: + // 15 01 18 00 ==> vBat = 1.5 + // 15 01 F8 00 ==> vBat = -0.5 + // 15 05 F8 00 42 ==> boot: 66, vBat: -0.5 + // 15 0D F8 00 42 17 80 59 35 80 ==> adds one temp of 23.5, rh = 50, p = 913.48, tDewC = 12.5 + // 15 7D 44 60 0D 15 9D 5F CD C3 00 00 1C 11 14 46 E4 ==> + // { + // "boot": 13, + // "error": "none", + // "lux": 0, + // "p": 981, + // "rh": 76.171875, + // "rhSoil": 89.0625, + // "tDewC": 17.236466758309017, + // "tSoil": 20.2734375, + // "tSoilDew": 18.411840342527178, + // "tWater": 28.06640625, + // "tempC": 21.61328125, + // "vBat": 4.2734375, + // } + // 15 7D 43 72 07 17 A4 5F CB A7 01 DB 1C 01 16 AF C3 + // { + // "boot": 7, + // "error": "none", + // "lux": 475, + // "p": 980.92, + // "rh": 65.234375, + // "rhSoil": 76.171875, + // "tDewC": 16.732001483771757, + // "tSoil": 22.68359375, + // "tSoilDew": 18.271601276518467, + // "tWater": 28.00390625, + // "tempC": 23.640625, + // "vBat": 4.21533203125 + // } + // 15 7D 42 D4 21 F5 9B 5E 5F C1 00 00 01 C1 F9 1B EC + // { + // "boot": 33, + // "error": "none", + // "lux": 0, + // "p": 966.36, + // "rh": 75.390625, + // "rhSoil": 92.1875, + // "tDewC": -13.909882718758952, + // "tSoil": -6.89453125, + // "tSoilDew": -7.948780789914008, + // "tWater": 1.75390625, + // "tempC": -10.39453125, + // "vBat": 4.1767578125 + // } + // i is used as the index into the message. Start with the flag byte. + var i = 1; + // fetch the bitmap. + var flags = bytes[i++]; + + if (flags & 0x1) { + // set vRaw to a uint16, and increment pointer + var vRaw = (bytes[i] << 8) + bytes[i + 1]; + i += 2; + // interpret uint16 as an int16 instead. + if (vRaw & 0x8000) + vRaw += -0x10000; + // scale and save in decoded. + decoded.vBat = vRaw / 4096.0; + } + + if (flags & 0x2) { + var vRawBus = (bytes[i] << 8) + bytes[i + 1]; + i += 2; + if (vRawBus & 0x8000) + vRawBus += -0x10000; + decoded.vBus = vRawBus / 4096.0; + } + + if (flags & 0x4) { + var iBoot = bytes[i]; + i += 1; + decoded.boot = iBoot; + } + + if (flags & 0x8) { + // we have temp, pressure, RH + var tRaw = (bytes[i] << 8) + bytes[i + 1]; + if (tRaw & 0x8000) + tRaw = -0x10000 + tRaw; + i += 2; + var pRaw = (bytes[i] << 8) + bytes[i + 1]; + i += 2; + var hRaw = bytes[i++]; + + decoded.tempC = tRaw / 256; + decoded.error = "none"; + decoded.p = pRaw * 4 / 100.0; + decoded.rh = hRaw / 256 * 100; + decoded.tDewC = dewpoint(decoded.tempC, decoded.rh); + } + + if (flags & 0x10) { + // we have lux + var luxRaw = (bytes[i] << 8) + bytes[i + 1]; + i += 2; + decoded.lux = luxRaw; + } + + if (flags & 0x20) { + // onewire temperature + var tempRaw = (bytes[i] << 8) + bytes[i + 1]; + i += 2; + if (tempRaw & 0x8000) + tempRaw = -0x10000 + tempRaw; + decoded.tWater = tempRaw / 256; + } + + if (flags & 0x40) { + // temperature followed by RH + var tempRaw = (bytes[i] << 8) + bytes[i + 1]; + i += 2; + if (tempRaw & 0x8000) + tempRaw = -0x10000 + tempRaw; + var tempRH = bytes[i]; + i += 1; + decoded.tSoil = tempRaw / 256; + decoded.rhSoil = tempRH / 256 * 100; + decoded.tSoilDew = dewpoint(decoded.tSoil, decoded.rhSoil); + } + } else if (cmd == 0x11) { + // decode Catena 4410 sensor data + + // test vectors: + // 11 01 18 00 ==> vBat = 1.5 + // 11 01 F8 00 ==> vBat = -0.5 + // 11 05 F8 00 17 80 59 35 80 ==> adds one temp of 23.5, rh = 50, p = 913.48, tDewC = 12.5 + // 11 3D 44 60 15 9D 5F CD C3 00 00 1C 11 14 46 E4 ==> + // { + // "error": "none", + // "lux": 0, + // "p": 981, + // "rh": 76.171875, + // "rhSoil": 89.0625, + // "tDewC": 17.236466758309017, + // "tSoil": 20.2734375, + // "tSoilDew": 18.411840342527178, + // "tWater": 28.06640625, + // "tempC": 21.61328125, + // "vBat": 4.2734375, + // } + // 11 3D 43 72 17 A4 5F CB A7 01 DB 1C 01 16 AF C3 + // { + // "error": "none", + // "lux": 475, + // "p": 980.92, + // "rh": 65.234375, + // "rhSoil": 76.171875, + // "tDewC": 16.732001483771757, + // "tSoil": 22.68359375, + // "tSoilDew": 18.271601276518467, + // "tWater": 28.00390625, + // "tempC": 23.640625, + // "vBat": 4.21533203125 + // } + // i is used as the index into the message. Start with the flag byte. + var i = 1; + // fetch the bitmap. + var flags = bytes[i++]; + + if (flags & 0x1) { + // set vRaw to a uint16, and increment pointer + var vRaw = (bytes[i] << 8) + bytes[i + 1]; + i += 2; + // interpret uint16 as an int16 instead. + if (vRaw & 0x8000) + vRaw += -0x10000; + // scale and save in decoded. + decoded.vBat = vRaw / 4096.0; + } + + if (flags & 0x2) { + var vRawBus = (bytes[i] << 8) + bytes[i + 1]; + i += 2; + if (vRawBus & 0x8000) + vRawBus += -0x10000; + decoded.vBus = vRawBus / 4096.0; + } + + if (flags & 0x4) { + // we have temp, pressure, RH + var tRaw = (bytes[i] << 8) + bytes[i + 1]; + if (tRaw & 0x8000) + tRaw = -0x10000 + tRaw; + i += 2; + var pRaw = (bytes[i] << 8) + bytes[i + 1]; + i += 2; + var hRaw = bytes[i++]; + + decoded.tempC = tRaw / 256; + decoded.error = "none"; + decoded.p = pRaw * 4 / 100.0; + decoded.rh = hRaw / 256 * 100; + decoded.tDewC = dewpoint(decoded.tempC, decoded.rh); + } + + if (flags & 0x8) { + // we have lux + var luxRaw = (bytes[i] << 8) + bytes[i + 1]; + i += 2; + decoded.lux = luxRaw; + } + + if (flags & 0x10) { + // onewire temperature + var tempRaw = (bytes[i] << 8) + bytes[i + 1]; + i += 2; + decoded.tWater = tempRaw / 256; + } + + if (flags & 0x20) { + // temperature followed by RH + var tempRaw = (bytes[i] << 8) + bytes[i + 1]; + i += 2; + var tempRH = bytes[i]; + i += 1; + decoded.tSoil = tempRaw / 256; + decoded.rhSoil = tempRH / 256 * 100; + decoded.tSoilDew = dewpoint(decoded.tSoil, decoded.rhSoil); + } + } else if (cmd == 0x17) { + // decode Catena 4460 AQI data + + // test vectors: + // 17 01 18 00 ==> vBat = 1.5 + // 17 01 F8 00 ==> vBat = -0.5 + // 17 05 F8 00 42 ==> boot: 66, vBat: -0.5 + // 17 0D F8 00 42 17 80 59 35 80 ==> adds one temp of 23.5, rh = 50, p = 913.48, tDewC = 12.5 + // 17 3D 44 60 0D 15 9D 5F CD C3 00 00 F9 07 ==> + // { + // "aqi": 288.875 + // "boot": 13, + // "error": "none", + // "lux": 0, + // "p": 981, + // "rh": 76.171875, + // "tDewC": 17.236466758309017, + // "tempC": 21.61328125, + // "vBat": 4.2734375, + // } + // 17 3D 43 72 07 17 A4 5F CB A7 01 DB E9 AF ==> + // { + // "aqi": 154.9375, + // "boot": 7, + // "error": "none", + // "lux": 475, + // "p": 980.92, + // "rh": 65.234375, + // "tDewC": 16.732001483771757, + // "tempC": 23.640625, + // "vBat": 4.21533203125 + // } + + // i is used as the index into the message. Start with the flag byte. + var i = 1; + // fetch the bitmap. + var flags = bytes[i++]; + + if (flags & 0x1) { + // set vRaw to a uint16, and increment pointer + var vRaw = (bytes[i] << 8) + bytes[i + 1]; + i += 2; + // interpret uint16 as an int16 instead. + if (vRaw & 0x8000) + vRaw += -0x10000; + // scale and save in decoded. + decoded.vBat = vRaw / 4096.0; + } + + if (flags & 0x2) { + var vRawBus = (bytes[i] << 8) + bytes[i + 1]; + i += 2; + if (vRawBus & 0x8000) + vRawBus += -0x10000; + decoded.vBus = vRawBus / 4096.0; + } + + if (flags & 0x4) { + var iBoot = bytes[i]; + i += 1; + decoded.boot = iBoot; + } + + if (flags & 0x8) { + // we have temp, pressure, RH + var tRaw = (bytes[i] << 8) + bytes[i + 1]; + if (tRaw & 0x8000) + tRaw = -0x10000 + tRaw; + i += 2; + var pRaw = (bytes[i] << 8) + bytes[i + 1]; + i += 2; + var hRaw = bytes[i++]; + + decoded.tempC = tRaw / 256; + decoded.error = "none"; + decoded.p = pRaw * 4 / 100.0; + decoded.rh = hRaw / 256 * 100; + decoded.tDewC = dewpoint(decoded.tempC, decoded.rh); + } + + if (flags & 0x10) { + // we have lux + var luxRaw = (bytes[i] << 8) + bytes[i + 1]; + i += 2; + decoded.lux = luxRaw; + } + + if (flags & 0x20) { + var rawUflt16 = (bytes[i] << 8) + bytes[i + 1]; + i += 2; + + var exp1 = rawUflt16 >> 12; + var mant1 = (rawUflt16 & 0xFFF) / 4096.0; + var f_unscaled = mant1 * Math.pow(2, exp1 - 15); + var aqi = f_unscaled * 512; + + decoded.aqi = aqi; + } + } else { + // cmd value not recognized. + } + } + + // at this point, decoded has the real values. + return decoded; +} \ No newline at end of file diff --git a/extra/catena-message-0x14-0x15-decoder-ttn.js b/extra/catena-message-0x14-0x15-decoder-ttn.js deleted file mode 100644 index 37cfe2b..0000000 --- a/extra/catena-message-0x14-0x15-decoder-ttn.js +++ /dev/null @@ -1,358 +0,0 @@ -// This function decodes the records (port 1, format 0x11, 0x14, 0x15) -// sent by the MCCI Catena 4410/4450/4551 soil/water and power applications. -// For use with console.thethingsnetwork.org -// 2017-09-19 add dewpoints. -// 2017-12-13 fix commments, fix negative soil/water temp, add test vectors. -// 2017-12-15 add format 0x11. - -// calculate dewpoint (degrees C) given temperature (C) and relative humidity (0..100) -// from http://andrew.rsmas.miami.edu/bmcnoldy/Humidity.html -// rearranged for efficiency and to deal sanely with very low (< 1%) RH -function dewpoint(t, rh) { - var c1 = 243.04; - var c2 = 17.625; - var h = rh / 100; - if (h <= 0.01) - h = 0.01; - else if (h > 1.0) - h = 1.0; - - var lnh = Math.log(h); - var tpc1 = t + c1; - var txc2 = t * c2; - var txc2_tpc1 = txc2 / tpc1; - - var tdew = c1 * (lnh + txc2_tpc1) / (c2 - lnh - txc2_tpc1); - return tdew; -} - -function Decoder(bytes, port) { - // Decode an uplink message from a buffer - // (array) of bytes to an object of fields. - var decoded = {}; - - if (port === 1) { - cmd = bytes[0]; - if (cmd == 0x14) { - // decode Catena 4450 M101 data - - // test vectors: - // 14 01 18 00 ==> vBat = 1.5 - // 14 01 F8 00 ==> vBat = -0.5 - // 14 05 F8 00 42 ==> boot: 66, vBat: -0.5 - // 14 0D F8 00 42 17 80 59 35 80 ==> adds one temp of 23.5, rh = 50, p = 913.48 - - // i is used as the index into the message. Start with the flag byte. - var i = 1; - // fetch the bitmap. - var flags = bytes[i++]; - - if (flags & 0x1) { - // set vRaw to a uint16, and increment pointer - var vRaw = (bytes[i] << 8) + bytes[i + 1]; - i += 2; - // interpret uint16 as an int16 instead. - if (vRaw & 0x8000) - vRaw += -0x10000; - // scale and save in decoded. - decoded.vBat = vRaw / 4096.0; - } - - if (flags & 0x2) { - var vRawBus = (bytes[i] << 8) + bytes[i + 1]; - i += 2; - if (vRawBus & 0x8000) - vRawBus += -0x10000; - decoded.vBus = vRawBus / 4096.0; - } - - if (flags & 0x4) { - var iBoot = bytes[i]; - i += 1; - decoded.boot = iBoot; - } - - if (flags & 0x8) { - // we have temp, pressure, RH - var tRaw = (bytes[i] << 8) + bytes[i + 1]; - if (tRaw & 0x8000) - tRaw = -0x10000 + tRaw; - i += 2; - var pRaw = (bytes[i] << 8) + bytes[i + 1]; - i += 2; - var hRaw = bytes[i++]; - - decoded.tempC = tRaw / 256; - decoded.error = "none"; - decoded.p = pRaw * 4 / 100.0; - decoded.rh = hRaw / 256 * 100; - decoded.tDewC = dewpoint(decoded.tempC, decoded.rh); - } - - if (flags & 0x10) { - // we have lux - var luxRaw = (bytes[i] << 8) + bytes[i + 1]; - i += 2; - decoded.lux = luxRaw; - } - - if (flags & 0x20) { - // watthour - var powerIn = (bytes[i] << 8) + bytes[i + 1]; - i += 2; - var powerOut = (bytes[i] << 8) + bytes[i + 1]; - i += 2; - decoded.powerUsedCount = powerIn; - decoded.powerSourcedCount = powerOut; - } - - if (flags & 0x40) { - // normalize floating pulses per hour - var floatIn = (bytes[i] << 8) + bytes[i + 1]; - i += 2; - var floatOut = (bytes[i] << 8) + bytes[i + 1]; - i += 2; - - var exp1 = floatIn >> 12; - var exp2 = floatOut >> 12; - var mant1 = (floatIn & 0xFFF) / 4096.0; - var mant2 = (floatOut & 0xFFF) / 4096.0; - var powerPerHourIn = mant1 * Math.pow(2, exp1 - 15) * 60 * 60 * 4; - var powerPerHourOut = mant2 * Math.pow(2, exp2 - 15) * 60 * 60 * 4; - decoded.powerUsedPerHour = powerPerHourIn; - decoded.powerSourcedPerHour = powerPerHourOut; - } - } else if (cmd == 0x15) { - // decode Catena 4450 M102 data - - // test vectors: - // 15 01 18 00 ==> vBat = 1.5 - // 15 01 F8 00 ==> vBat = -0.5 - // 15 05 F8 00 42 ==> boot: 66, vBat: -0.5 - // 15 0D F8 00 42 17 80 59 35 80 ==> adds one temp of 23.5, rh = 50, p = 913.48, tDewC = 12.5 - // 15 7D 44 60 0D 15 9D 5F CD C3 00 00 1C 11 14 46 E4 ==> - // { - // "boot": 13, - // "error": "none", - // "lux": 0, - // "p": 981, - // "rh": 76.171875, - // "rhSoil": 89.0625, - // "tDewC": 17.236466758309017, - // "tSoil": 20.2734375, - // "tSoilDew": 18.411840342527178, - // "tWater": 28.06640625, - // "tempC": 21.61328125, - // "vBat": 4.2734375, - // } - // 15 7D 43 72 07 17 A4 5F CB A7 01 DB 1C 01 16 AF C3 - // { - // "boot": 7, - // "error": "none", - // "lux": 475, - // "p": 980.92, - // "rh": 65.234375, - // "rhSoil": 76.171875, - // "tDewC": 16.732001483771757, - // "tSoil": 22.68359375, - // "tSoilDew": 18.271601276518467, - // "tWater": 28.00390625, - // "tempC": 23.640625, - // "vBat": 4.21533203125 - // } - // 15 7D 42 D4 21 F5 9B 5E 5F C1 00 00 01 C1 F9 1B EC - // { - // "boot": 33, - // "error": "none", - // "lux": 0, - // "p": 966.36, - // "rh": 75.390625, - // "rhSoil": 92.1875, - // "tDewC": -13.909882718758952, - // "tSoil": -6.89453125, - // "tSoilDew": -7.948780789914008, - // "tWater": 1.75390625, - // "tempC": -10.39453125, - // "vBat": 4.1767578125 - // } - // i is used as the index into the message. Start with the flag byte. - var i = 1; - // fetch the bitmap. - var flags = bytes[i++]; - - if (flags & 0x1) { - // set vRaw to a uint16, and increment pointer - var vRaw = (bytes[i] << 8) + bytes[i + 1]; - i += 2; - // interpret uint16 as an int16 instead. - if (vRaw & 0x8000) - vRaw += -0x10000; - // scale and save in decoded. - decoded.vBat = vRaw / 4096.0; - } - - if (flags & 0x2) { - var vRawBus = (bytes[i] << 8) + bytes[i + 1]; - i += 2; - if (vRawBus & 0x8000) - vRawBus += -0x10000; - decoded.vBus = vRawBus / 4096.0; - } - - if (flags & 0x4) { - var iBoot = bytes[i]; - i += 1; - decoded.boot = iBoot; - } - - if (flags & 0x8) { - // we have temp, pressure, RH - var tRaw = (bytes[i] << 8) + bytes[i + 1]; - if (tRaw & 0x8000) - tRaw = -0x10000 + tRaw; - i += 2; - var pRaw = (bytes[i] << 8) + bytes[i + 1]; - i += 2; - var hRaw = bytes[i++]; - - decoded.tempC = tRaw / 256; - decoded.error = "none"; - decoded.p = pRaw * 4 / 100.0; - decoded.rh = hRaw / 256 * 100; - decoded.tDewC = dewpoint(decoded.tempC, decoded.rh); - } - - if (flags & 0x10) { - // we have lux - var luxRaw = (bytes[i] << 8) + bytes[i + 1]; - i += 2; - decoded.lux = luxRaw; - } - - if (flags & 0x20) { - // onewire temperature - var tempRaw = (bytes[i] << 8) + bytes[i + 1]; - i += 2; - if (tempRaw & 0x8000) - tempRaw = -0x10000 + tempRaw; - decoded.tWater = tempRaw / 256; - } - - if (flags & 0x40) { - // temperature followed by RH - var tempRaw = (bytes[i] << 8) + bytes[i + 1]; - i += 2; - if (tempRaw & 0x8000) - tempRaw = -0x10000 + tempRaw; - var tempRH = bytes[i]; - i += 1; - decoded.tSoil = tempRaw / 256; - decoded.rhSoil = tempRH / 256 * 100; - decoded.tSoilDew = dewpoint(decoded.tSoil, decoded.rhSoil); - } - } else if (cmd == 0x11) { - // decode Catena 4410 sensor data - - // test vectors: - // 11 01 18 00 ==> vBat = 1.5 - // 11 01 F8 00 ==> vBat = -0.5 - // 11 05 F8 00 17 80 59 35 80 ==> adds one temp of 23.5, rh = 50, p = 913.48, tDewC = 12.5 - // 11 3D 44 60 15 9D 5F CD C3 00 00 1C 11 14 46 E4 ==> - // { - // "error": "none", - // "lux": 0, - // "p": 981, - // "rh": 76.171875, - // "rhSoil": 89.0625, - // "tDewC": 17.236466758309017, - // "tSoil": 20.2734375, - // "tSoilDew": 18.411840342527178, - // "tWater": 28.06640625, - // "tempC": 21.61328125, - // "vBat": 4.2734375, - // } - // 11 3D 43 72 17 A4 5F CB A7 01 DB 1C 01 16 AF C3 - // { - // "error": "none", - // "lux": 475, - // "p": 980.92, - // "rh": 65.234375, - // "rhSoil": 76.171875, - // "tDewC": 16.732001483771757, - // "tSoil": 22.68359375, - // "tSoilDew": 18.271601276518467, - // "tWater": 28.00390625, - // "tempC": 23.640625, - // "vBat": 4.21533203125 - // } - // i is used as the index into the message. Start with the flag byte. - var i = 1; - // fetch the bitmap. - var flags = bytes[i++]; - - if (flags & 0x1) { - // set vRaw to a uint16, and increment pointer - var vRaw = (bytes[i] << 8) + bytes[i + 1]; - i += 2; - // interpret uint16 as an int16 instead. - if (vRaw & 0x8000) - vRaw += -0x10000; - // scale and save in decoded. - decoded.vBat = vRaw / 4096.0; - } - - if (flags & 0x2) { - var vRawBus = (bytes[i] << 8) + bytes[i + 1]; - i += 2; - if (vRawBus & 0x8000) - vRawBus += -0x10000; - decoded.vBus = vRawBus / 4096.0; - } - - if (flags & 0x4) { - // we have temp, pressure, RH - var tRaw = (bytes[i] << 8) + bytes[i + 1]; - if (tRaw & 0x8000) - tRaw = -0x10000 + tRaw; - i += 2; - var pRaw = (bytes[i] << 8) + bytes[i + 1]; - i += 2; - var hRaw = bytes[i++]; - - decoded.tempC = tRaw / 256; - decoded.error = "none"; - decoded.p = pRaw * 4 / 100.0; - decoded.rh = hRaw / 256 * 100; - decoded.tDewC = dewpoint(decoded.tempC, decoded.rh); - } - - if (flags & 0x8) { - // we have lux - var luxRaw = (bytes[i] << 8) + bytes[i + 1]; - i += 2; - decoded.lux = luxRaw; - } - - if (flags & 0x10) { - // onewire temperature - var tempRaw = (bytes[i] << 8) + bytes[i + 1]; - i += 2; - decoded.tWater = tempRaw / 256; - } - - if (flags & 0x20) { - // temperature followed by RH - var tempRaw = (bytes[i] << 8) + bytes[i + 1]; - i += 2; - var tempRH = bytes[i]; - i += 1; - decoded.tSoil = tempRaw / 256; - decoded.rhSoil = tempRH / 256 * 100; - decoded.tSoilDew = dewpoint(decoded.tSoil, decoded.rhSoil); - } - } else { - // nothing - } - } - return decoded; -} \ No newline at end of file diff --git a/extra/catena-message-0x17-decoder-node-red.js b/extra/catena-message-0x17-decoder-node-red.js new file mode 100644 index 0000000..8cc4ea6 --- /dev/null +++ b/extra/catena-message-0x17-decoder-node-red.js @@ -0,0 +1,105 @@ +// JavaScript source code +// This Node-RED decoding function decodes the record sent by the Catena 4460 +// air-quality index application. + +var b = msg.payload; // pick up data for convenience; just saves typing. + +// an empty table to which we'll add result fields: +// +// result.vBat: the battery voltage (if present) +// result.vBus: the USB charger voltage (if provided) +// result.boot: the system boot counter, modulo 256 +// result.t: temperature in degrees C +// result.p: station pressure in hPa (millibars). Note that this is not +// adjusted for the height above sealevel so can't be directly compared +// to weather.gov "barometric pressure" +// result.rh: relative humidity (in %) +// result.lux: light level, in lux +// result.aqi: air quality index +var result = {}; + +// check the message type byte +if (b[0] != 0x17) { + // not one of ours: report an error, return without a value, + // so that Node-RED doesn't propagate the message any further. + node.error("not ours! " + b[0].toString()); + return; +} + +// i is used as the index into the message. Start with the flag byte. +var i = 1; +// fetch the bitmap. +var flags = b[i++]; + +if (flags & 0x1) { + // set vRaw to a uint16, and increment pointer + var vRaw = (b[i] << 8) + b[i + 1]; + i += 2; + // interpret uint16 as an int16 instead. + if (vRaw & 0x8000) + vRaw += -0x10000; + // scale and save in result. + result.vBat = vRaw / 4096.0; +} + +if (flags & 0x2) { + var vRaw = (b[i] << 8) + b[i + 1]; + i += 2; + if (vRaw & 0x8000) + vRaw += -0x10000; + result.vBus = vRaw / 4096.0; +} + +if (flags & 0x4) { + var iBoot = b[i]; + i += 1; + result.boot = iBoot; +} + +if (flags & 0x8) { + // we have temp, pressure, RH + var tRaw = (b[i] << 8) + b[i + 1]; + if (tRaw & 0x8000) + tRaw = -0x10000 + tRaw; + i += 2; + var pRaw = (b[i] << 8) + b[i + 1]; + i += 2; + var hRaw = b[i++]; + + result.t = tRaw / 256; + result.p = pRaw * 4 / 100.0; + result.rh = hRaw / 256 * 100; +} + +if (flags & 0x10) { + // we have lux + var luxRaw = (b[i] << 8) + b[i + 1]; + i += 2; + result.lux = luxRaw; +} + + +if (flags & 0x20) // get the air-quality index +{ + var rawUflt16 = (b[i] << 8) + b[i + 1]; + i += 2; + + var exp1 = rawUflt16 >> 12; + var mant1 = (rawUflt16 & 0xFFF) / 4096.0; + var f_unscaled = mant1 * Math.pow(2, exp1 - 15); + var aqi = f_unscaled * 512; + result.aqi = aqi; +} + +// now update msg with the new payload and new .local field +// the old msg.payload is overwritten. +msg.payload = result; +msg.local = + { + nodeType: "Catena 4460", + platformType: "Feather M0 LoRa", + radioType: "RF95", + applicationName: "Air Quality monitoring" + }; + +return msg; From 433a840bde9d89db3ef9790f5b472892e035de13 Mon Sep 17 00:00:00 2001 From: Terry Moore Date: Sun, 1 Apr 2018 01:44:31 -0400 Subject: [PATCH 08/17] Rename again, and add internal docs --- ...sage-decoder-ttn.js => catena-message-generic-decoder-ttn.js} | 1 + 1 file changed, 1 insertion(+) rename extra/{catena-generic-message-decoder-ttn.js => catena-message-generic-decoder-ttn.js} (99%) diff --git a/extra/catena-generic-message-decoder-ttn.js b/extra/catena-message-generic-decoder-ttn.js similarity index 99% rename from extra/catena-generic-message-decoder-ttn.js rename to extra/catena-message-generic-decoder-ttn.js index e65345b..7dc8b7f 100644 --- a/extra/catena-generic-message-decoder-ttn.js +++ b/extra/catena-message-generic-decoder-ttn.js @@ -4,6 +4,7 @@ // 2017-09-19 add dewpoints. // 2017-12-13 fix commments, fix negative soil/water temp, add test vectors. // 2017-12-15 add format 0x11. +// 2018-04-01 add format 0x17. // calculate dewpoint (degrees C) given temperature (C) and relative humidity (0..100) // from http://andrew.rsmas.miami.edu/bmcnoldy/Humidity.html From 506654ff09569d8f001038edf5ff4d9300ae94a8 Mon Sep 17 00:00:00 2001 From: Terry Moore Date: Sun, 1 Apr 2018 14:44:52 -0400 Subject: [PATCH 09/17] Honor fUnattended flag --- catena4460_aqi/catena4460_aqi.ino | 24 +++++++++++++++++++----- 1 file changed, 19 insertions(+), 5 deletions(-) diff --git a/catena4460_aqi/catena4460_aqi.ino b/catena4460_aqi/catena4460_aqi.ino index 78fd103..eba534f 100644 --- a/catena4460_aqi/catena4460_aqi.ino +++ b/catena4460_aqi/catena4460_aqi.ino @@ -462,12 +462,22 @@ static void settleDoneCb( ) { uint32_t startTime; + bool fDeepSleep; // if connected to USB, don't sleep // ditto if we're monitoring pulses. - // enable/disable sleep when connected to USB - if (Serial.dtr()) + // disable sleep if not unattended, or if USB active + if ((gCatena.GetOperatingFlags() & + static_cast(gCatena.OPERATING_FLAGS::fUnattended)) != 0) + fDeepSleep = true; + else if (! Serial.dtr()) + fDeepSleep = true; + else + fDeepSleep = false; + + /* if we can't sleep deeply, then simply schedule the sleepDoneCb */ + if (! fDeepSleep) { gLed.Set(LedPattern::Sleeping); os_setTimedCallback( @@ -478,7 +488,11 @@ static void settleDoneCb( return; } - /* ok... now it's time for a deep sleep */ + /* + || ok... now it's time for a deep sleep. do the sleep here, since + || the Arduino loop won't do it. Note that nothing will get polled + || while we sleep + */ gLed.Set(LedPattern::Off); startTime = millis(); @@ -490,9 +504,9 @@ static void settleDoneCb( // add the number of ms that we were asleep to the millisecond timer. // we don't need extreme accuracy. - adjust_millis_forward(CATCFG_T_INTERVAL * 1000); + adjust_millis_forward(CATCFG_T_INTERVAL * 1000); - /* and now... we're awake again. trigger another measurement */ + /* and now... we're fully awake. trigger another measurement */ sleepDoneCb(pSendJob); } From 192da0d800f4fbde87f799d7a8105edbd4c6b03b Mon Sep 17 00:00:00 2001 From: Terry Moore Date: Sun, 1 Apr 2018 14:45:27 -0400 Subject: [PATCH 10/17] Improve messaging at startup --- catena4460_aqi/catena4460_aqi.ino | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/catena4460_aqi/catena4460_aqi.ino b/catena4460_aqi/catena4460_aqi.ino index eba534f..2b4039c 100644 --- a/catena4460_aqi/catena4460_aqi.ino +++ b/catena4460_aqi/catena4460_aqi.ino @@ -231,6 +231,7 @@ void setup(void) } else { + gCatena.SafePrintf("No Lux sensor found: check wiring and platform\n"); fLux = false; } @@ -239,7 +240,7 @@ void setup(void) /* ! bme.begin(BME680_ADDRESS, Adafruit_BME680::OPERATING_MODE::Sleep) */ ! bme.begin()) // the Adafruit BME680 lib doesn't have the MCCI low-power hacks { - gCatena.SafePrintf("No BME680 found: check wiring\n"); + gCatena.SafePrintf("No BME680 found: check wiring and platform\n"); fBme = false; } else From 426df56499900a31464878887b0fae56acf78523 Mon Sep 17 00:00:00 2001 From: Terry Moore Date: Sun, 1 Apr 2018 14:46:22 -0400 Subject: [PATCH 11/17] Improve messages, comments for AQI --- catena4460_aqi/catena4460_aqi.ino | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/catena4460_aqi/catena4460_aqi.ino b/catena4460_aqi/catena4460_aqi.ino index 2b4039c..dd3f3b1 100644 --- a/catena4460_aqi/catena4460_aqi.ino +++ b/catena4460_aqi/catena4460_aqi.ino @@ -352,11 +352,10 @@ void fillBuffer(TxBuffer_t &b) // humidity is one byte, where 0 == 0/256 and 0xFF == 255/256. bme.performReading(); gCatena.SafePrintf( - "BME680: T: %d P: %d RH: %d Gas-Resistance: %d\n", - (int) bme.temperature, - (int) bme.pressure, - (int) bme.humidity, - (int) bme.gas_resistance + "BME680: T=%d P=%d RH=%d\n", + (int) (bme.temperature + 0.5), + (int) (bme.pressure + 0.5), + (int) (bme.humidity + 0.5) ); b.putT(bme.temperature); b.putP(bme.pressure); @@ -378,15 +377,17 @@ void fillBuffer(TxBuffer_t &b) if (fBme) { - // compute the AQI (in 0..500/512) - constexpr float slope = 44.52282f / 512.0f; + // compute the AQI (in 0..500/512). + // see README.md for a description. + constexpr float slope = 44.52282f / 512.0f; // constexpr moves calculation to compile-time float const AQIsimple = (-logf(bme.gas_resistance) + 16.3787f) * slope; uint16_t const encodedAQI = f2uflt16(AQIsimple); gCatena.SafePrintf( - "BME680: AQI: %d\n", - (int) (AQIsimple * 512.0) + "BME680: Gas-Resistance=%d AQI=%d\n", + (int) (bme.gas_resistance + 0.5), + (int) (AQIsimple * 512.0 + 0.5) ); b.put2u(encodedAQI); From 9e3a2fb6c47c958245f97d7b310f929e533cf666 Mon Sep 17 00:00:00 2001 From: Terry Moore Date: Sun, 1 Apr 2018 14:47:46 -0400 Subject: [PATCH 12/17] Trim trailing whitespace --- catena4460_aqi/catena4460_aqi.ino | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/catena4460_aqi/catena4460_aqi.ino b/catena4460_aqi/catena4460_aqi.ino index dd3f3b1..ddcb839 100644 --- a/catena4460_aqi/catena4460_aqi.ino +++ b/catena4460_aqi/catena4460_aqi.ino @@ -17,7 +17,7 @@ Copyright notice: 3520 Krums Corners Road Ithaca, NY 14850 - An unpublished work. All rights reserved. All rights reserved. The license + An unpublished work. All rights reserved. All rights reserved. The license file accompanying this file outlines the license granted. Author: @@ -455,7 +455,7 @@ txFailedDoneCb( // // extern "C" { void adjust_millis_forward(unsigned); }; // -// If you don't have it, make sure you're running the MCCI Board Support +// If you don't have it, make sure you're running the MCCI Board Support // Package for the Catena 4460, https://github.com/mcci-catena/arduino-boards // From 63b757f40958045811ab59046e5318b210d1951d Mon Sep 17 00:00:00 2001 From: Terry Moore Date: Sun, 1 Apr 2018 15:19:03 -0400 Subject: [PATCH 13/17] git-boot.sh: work with different/multiple git-repos.dat --- catena4450m101_sensor/git-boot.sh | 50 +++++++++++++++++++------------ catena4460_aqi/git-repos.dat | 13 ++++++++ 2 files changed, 44 insertions(+), 19 deletions(-) create mode 100644 catena4460_aqi/git-repos.dat diff --git a/catena4450m101_sensor/git-boot.sh b/catena4450m101_sensor/git-boot.sh index cffce77..c8733b5 100755 --- a/catena4450m101_sensor/git-boot.sh +++ b/catena4450m101_sensor/git-boot.sh @@ -5,10 +5,10 @@ # Module: gitboot.sh # # Function: -# Load the repositories for building this sketch +# Install the libraries needed for building a given sketch. # # Copyright notice: -# This file copyright (C) 2017 by +# This file copyright (C) 2017-2018 by # # MCCI Corporation # 3520 Krums Corners Road @@ -72,26 +72,12 @@ else exit 1 fi -############################################################################## -# load the list of repos -############################################################################## - -### use a long quoted string to get the repositories -### into LIBRARY_REPOS. Multiple lines for readabilty. -LIBRARY_REPOS_DAT="${PDIR}/git-repos.dat" -if [ ! -f "${LIBRARY_REPOS_DAT}" ]; then - _fatal "can't find suitable git-repos.dat:" "${LIBRARY_REPOS_DAT}" -fi - -# parse the repo file, deleting comments -LIBRARY_REPOS=$(sed -e 's/#.*$//' ${PDIR}/git-repos.dat) - ############################################################################## # Scan the options ############################################################################## LIBRARY_ROOT="${LIBRARY_ROOT_DEFAULT}" -USAGE="${PNAME} -[D l* T u v]; ${PNAME} -H for help" +USAGE="${PNAME} -[D l* T u v] [datafile...]; ${PNAME} -H for help" #OPTDEBUG and OPTVERBOS are above OPTDRYRUN=0 @@ -139,6 +125,11 @@ Switches: -v turns on verbose mode; -nv is the default. -H prints this help message. +Data files: + The arguments specify repositories to be fetched, one repository + per line, in the form https://github.com/orgname/repo.git (or + similar). Blank lines and comments beginning with '#' are ignored. + . exit 0;; \?) echo "$USAGE" 1>&2 @@ -149,10 +140,31 @@ done #### get rid of scanned options #### shift `expr $OPTIND - 1` -if [ $# -ne 0 ]; then - _error "extra arguments: $@" +if [ $# -eq 0 ]; then + if [ -f "${PWD}/git-repos.dat" ]; then + LIBRARY_REPOS_DAT="${PWD}/git-repos.dat" + else + LIBRARY_REPOS_DAT="${PDIR}/git-repos.dat" + fi + _verbose "setting LIBRARY_REPOS_DAT to ${LIBRARY_REPOS_DAT}" + set -- "${LIBRARY_REPOS_DAT}" fi +############################################################################## +# load the list of repos +############################################################################## + +### use a long quoted string to get the repositories +### into LIBRARY_REPOS. Multiple lines for readabilty. +for LIBRARY_REPOS_DAT in "$@" ; do + if [ ! -f "${LIBRARY_REPOS_DAT}" ]; then + _fatal "can't find git-repos data file:" "${LIBRARY_REPOS_DAT}" + fi +done + +# parse the repo file, deleting comments and eliminating duplicates +LIBRARY_REPOS=$(sed -e 's/#.*$//' "$@" | LC_ALL=C sort -u) + #### make sure LIBRARY_ROOT is really a directory if [ ! -d "$LIBRARY_ROOT" ]; then _fatal "LIBRARY_ROOT: Can't find Arduino libraries:" "$LIBRARY_ROOT" diff --git a/catena4460_aqi/git-repos.dat b/catena4460_aqi/git-repos.dat new file mode 100644 index 0000000..1815656 --- /dev/null +++ b/catena4460_aqi/git-repos.dat @@ -0,0 +1,13 @@ +# +# list of git repositories needed by this directory. +# Use git-boot.sh to be fetch them to the Arduino libraries directory. +# +https://github.com/mcci-catena/Adafruit_FRAM_I2C.git +https://github.com/mcci-catena/Catena-Arduino-Platform.git +https://github.com/mcci-catena/arduino-lorawan.git +https://github.com/mcci-catena/Catena-mcciadk.git +https://github.com/mcci-catena/arduino-lmic.git +https://github.com/mcci-catena/Adafruit_BME680.git +https://github.com/mcci-catena/Adafruit_Sensor.git +https://github.com/mcci-catena/RTCZero.git +https://github.com/mcci-catena/BH1750.git From 30f647c58e092fe29eaf4bda3fdf4eeaec6dcac6 Mon Sep 17 00:00:00 2001 From: Terry Moore Date: Sun, 1 Apr 2018 15:20:02 -0400 Subject: [PATCH 14/17] Fix whitespace --- catena4450m101_sensor/git-boot.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/catena4450m101_sensor/git-boot.sh b/catena4450m101_sensor/git-boot.sh index c8733b5..03521ed 100755 --- a/catena4450m101_sensor/git-boot.sh +++ b/catena4450m101_sensor/git-boot.sh @@ -116,8 +116,8 @@ Switches: Default is $LIBRARY_ROOT_DEFAULT. -S Skip repos that already exist; -nS means don't run if any repo already exist. - Only consulted if -nu. - -T Do a trial run (go through the motions + Only consulted if -nu. + -T Do a trial run (go through the motions but don't do anything). -u Do a git pull if repo already is found. -nu just skips the repository if it already From b31524d9da5f8e2ae13a2d7983ed9ccdc8f64741 Mon Sep 17 00:00:00 2001 From: Terry Moore Date: Sun, 1 Apr 2018 15:37:16 -0400 Subject: [PATCH 15/17] Improve catena4460_aqi documentation --- catena4460_aqi/README.md | 142 ++++++++++++++++++++++++++++++++++++++- 1 file changed, 140 insertions(+), 2 deletions(-) diff --git a/catena4460_aqi/README.md b/catena4460_aqi/README.md index 62c81fb..ed11eec 100644 --- a/catena4460_aqi/README.md +++ b/catena4460_aqi/README.md @@ -1,15 +1,153 @@ -# catena4460-aqi example +# Catena4460-aqi example -This sketch computes a simple air-quality index based on the gas resistance returned by the BME680 that resides in the Catena 4460. +This sketch captures a simple air-quality index, based on the gas resistance returned by the BME680 that resides in the MCCI Catena 4460. It then uses LoRaWAN technology to transmit the captured data over a suitable network, such as The Things Network. + + + +- [Description](#description) + - [Prerequisites](#prerequisites) + - [Air Quality Index](#air-quality-index) + - [Known Unknowns](#known-unknowns) + - [Published Data about Air Conductivity and Pollution](#published-data-about-air-conductivity-and-pollution) + - [Data formats](#data-formats) +- [Configuration](#configuration) + - [Unattended mode](#unattended-mode) + - [Test mode](#test-mode) + - [Platform GUID](#platform-guid) +- [Boilerplate and acknowledgements](#boilerplate-and-acknowledgements) + - [Trademarks](#trademarks) + + + +## Description + +The application is very simple. It sets up the measurements, then periodically measures and transmits a collection of readings: + +* Temperature. +* Pressure. +* Relative Humidity. +* "Air Quality Index". This is an MCCI concept, and is different than the (closed source) IAQ offered by Bosch. +* System health indications (boot count, battery voltage). + +You must provision the Catena as normal, and set up your application to understand the data format. + +### Prerequisites + +- Make sure you're running the MCCI Board Support +Package for the Catena 4460. See https://github.com/mcci-catena/arduino-boards for information on how to set up the Arduino IDE. +- Select Catena 4460 as the target board in the Arduino IDE. +- Use `git-boot.sh` (elsewhere in the Catena-Sketches repository) to fetch the required libraries, by using the command: + + ```shell + cd catena4460_aqi + {path}/git-boot.sh -u ./git-repos.dat + ``` + + We recommend the `-u` switch because this will ensure that libraries will be updated if already present on your system. However, this may not be what you want. Use `{path}/git-boot.sh -H` to get help on the various options. + +- Make sure you have at least v0.9.0 of the `Catena-Arduino-Platform`; otherwise the sketch will not compile. + +### Air Quality Index + +This sketch measures gas resistance and computes an air quality index. The method chosen is not based on the proprietary Bosch method, but we chose to use a scale from 0 to 500, as they did. + +The AQI is a number ranging from 0..500, based on logarithmic gas conductivity (1/r), scaled according to the possible outputs of the BME680. The minimum possible output of the algorithm in the public code is mapped to 500; and the maximum possible output is mapped to 0, using a linear transformation of log(1/r). No claims about this index are made in terms of how this maps to pollution. However, [public data](https://forums.pimoroni.com/t/bme680-observed-gas-ohms-readings/6608) suggests the following table (descriptive tags taken from reference): + + Quality | R (ohms) | AQI +:-----------:|:-----------:|:--------: +"good" | >360k | < 160 +"average" | 184k - 360k | 160 - 190 +"little bad" | 93k - 180k | 190 - 220 +"bad" | 48k - 93k | 220 - 250 +"worse" | 24k - 48k | 250 - 280 +"very bad" | 12.5k - 24k | 280 - 310 +"can't see the exit" | < 12.5k | > 310 + +#### Known Unknowns + +Bosch measures gas resistance, but does not publish how this gas resistance converts to ohm-meters (the reference units for resistivity); they also do not publish the *resistance* values for their reference gases. They only publish "IAQ values", but do not disclose the algorithms. This prevents direct comparison of BME680 measurements with industry standard measurement of atmospheric conductivity. + +We hope to be able to research the actual response of the BME680 so that we can make better use of the published data. + +#### Published Data about Air Conductivity and Pollution + +We reviewed several papers, of which the following seemed the most important: + +1. Pawar et al, [Effect of relative humidity and sea level pressure on electrical conductivity of air over Indian Ocean][Pawar2009], [[Pawar2009]]". + +2. Kamalsi et al, [The Electrical Conductivity as an Index of Air Pollution in the Atmosphere][Kamalsi2011], [[Kamalsi2011]] + +3. US Environmental Protection Agency, [EPA-450/3-82-019: Measurement of Volatile Organic Compunds - Supplement 1][EPA1982], July 1982 [[EPA1982]]. + +[[Pawar2009]] was peer-reviewed, [[Kamalsi2011]] was not, but the results looked consistent. [[EPA1982]] is a good overview of laboratory measurement techniques for VOCs and is useful background. + +[[Pawar2009]] shows that relative humidity inversely affects conductivity, but the remarks, references, and data show that this is non-linear, increasing sharply when the RH > 75%. In other words, if RH > 75%, we should expect to measure a higher resistance than we would otherwise see, and therefore the AQI should be biased upwards. The current calculation doesn't include this bias. The published effect on conductivity is modest, about 5x; this translates to about 60 points on our AQI, or two levels. In other words, we might want to fit some non-linear curve to RH, so that above 75% increases AQI, and below 75% decreases, but less strongly. On the other hand, Section 4.1 concludes that the effect of RH is less at higher pollution levels. We conclude that we don't have enough information to know how to accommodate RH; rather RH and AQI should be reported together (as is done by this sketch). + +They also mention that small ion mobility decreases with increases in relative humidity; they believe that this is a smaller effect in their data, but it might be + +[[Pawar2009]] also shows that there is some contribution from atmospheric pressure. It is well known that higher pressure reduces small ion mobility, so it's likely that a given value of conductance indicates more small ions if pressure is higher. Again, however, we + +[Pawar2009]: https://doi.org/10.1029/2007JD009716 "Effect of relative humidity and sea level pressure on electrical conductivity of air over Indian Ocean" +[Kamalsi2011]: https://mts.intechopen.com/books/advanced-air-pollution/the-electrical-conductivity-as-an-index-of-air-pollution-in-the-atmosphere "The Electrical Conductivity as an Index of Air Pollution in the Atmosphere" +[EPA1982]: https://nepis.epa.gov/Exe/ZyNET.exe/2000MHV5.txt?ZyActionD=ZyDocument&Client=EPA&Index=2011%20Thru%202015%7C1995%20Thru%201999%7C1981%20Thru%201985%7C2006%20Thru%202010%7C1991%20Thru%201994%7C1976%20Thru%201980%7C2000%20Thru%202005%7C1986%20Thru%201990%7CPrior%20to%201976%7CHardcopy%20Publications&Docs=&Query=450382019&Time=&EndTime=&SearchMethod=2&TocRestrict=n&Toc=&TocEntry=&QField=&QFieldYear=&QFieldMonth=&QFieldDay=&UseQField=&IntQFieldOp=0&ExtQFieldOp=0&XmlQuery=&File=D%3A%5CZYFILES%5CINDEX%20DATA%5C81THRU85%5CTXT%5C00000005%5C2000MHV5.txt&User=ANONYMOUS&Password=anonymous&SortMethod=h%7C-&MaximumDocuments=15&FuzzyDegree=0&ImageQuality=r85g16/r85g16/x150y150g16/i500&Display=hpfr&DefSeekPage=x&SearchBack=ZyActionL&Back=ZyActionS&BackDesc=Results%20page&MaximumPages=1&ZyEntry=1&SeekPage=x "Measurement of Volatile Organic Compunds - Supplement 1, search link for retrieval" + +### Data formats It transmits a reading using `Catena_TxBuffer.h` format 0x17. This is similar to format 0x14, except that bit 0x20 of the flag byte indicates that a two-byte `uflt16` number is present representing the AQI. The AQI is a number ranging from 0..500, based on logarithmic gas conductivity (1/r), scaled according to the possible outputs of the BME680. It is divided by 512 prior to transmission in order to get into the [0,1) range limitation of the `uflt16` representation. So in the Javascript decoder, we multiply by 512 to recover the original data. Refer to [Understanding Catena Data Format 0x14](https://github.com/mcci-catena/Catena-Sketches/blob/master/extra/catena-message-0x14-format.md) for background information. +## Configuration + +Most configuration is done via USB. + +### Unattended mode + +The Catena library defines operating flags bit 0 (mask 0x00000001) as "unattended mode". If set, the device is supposed to use low-power sleep; if clear, the device is presumably connected to a USB host, and so doesn't go to low-power sleep (so that the USB interface continues to work). + +**Note**: early versions of this sketch omitted this check. Verify in the code that your + +To enable unattended mode during configuration: + +``` +system configure operatingflags 1 +``` + +To disable: + +``` +system configure operatingflags 1 +``` + +### Test mode + This sketch honors the manufacturing-test bit of operating flags (bit 1, mask 0x00000002). If bit 1 is set, it will continually measure (and print results). This will interfere with LoRa operations in test mode. +To enable manufacturing test mode during configuration + +``` +system configure operatingflags 2 +``` + +To disable: + +``` +system configure operatingflags 2 +``` + +### Platform GUID + Please set the platform GUID to `3037D9BE-8EBE-4AE7-970E-91915A2484F8` during configuration: ``` system configure platformguid 3037D9BE-8EBE-4AE7-970E-91915A2484F8 ``` + +## Boilerplate and acknowledgements + +### Trademarks + +- MCCI and MCCI Catena are registered trademarks of MCCI Corporation. +- LoRa is a registered trademark of Semtech Corporation. +- LoRaWAN is a trademark of the LoRa Alliance +- All other trademarks are properties of their respective owners. From 8929cb94dbf47705a48b7cd4129526740a80df7e Mon Sep 17 00:00:00 2001 From: Terry Moore Date: Sun, 1 Apr 2018 16:03:51 -0400 Subject: [PATCH 16/17] Update top-level README --- README.md | 80 +++++++++++++++++++++++++++++++++---------------------- 1 file changed, 48 insertions(+), 32 deletions(-) diff --git a/README.md b/README.md index 19dad3c..e643d34 100644 --- a/README.md +++ b/README.md @@ -10,13 +10,15 @@ This repository is the top-level repository for the software. In order to build, - [Sketch Overview](#sketch-overview) - - [catena4450m101_sensor1](#catena4450m101_sensor1) - - [catena4410_sensor1](#catena4410_sensor1) - - [catena4410_test3](#catena4410_test3) - - [catena4410_test1, catena4410_test2](#catena4410_test1-catena4410_test2) - - [catena4450_test01](#catena4450_test01) - - [catena4450m101_sensor](#catena4450m101_sensor) - - [catena4450m102_pond](#catena4450m102_pond) + - [Full Sensor Programs](#full-sensor-programs) + - [catena4450m101_sensor1](#catena4450m101_sensor1) + - [catena4450m102_pond](#catena4450m102_pond) + - [catena4460_aqi](#catena4460_aqi) + - [catena4410_sensor1](#catena4410_sensor1) + - [Test programs](#test-programs) + - [catena4410_test3](#catena4410_test3) + - [catena4410_test1, catena4410_test2](#catena4410_test1-catena4410_test2) + - [catena4450_test01](#catena4450_test01) - [Extras](#extras) - [Required Board-Support Packages](#required-board-support-packages) - [Required Libraries](#required-libraries) @@ -29,79 +31,87 @@ This repository is the top-level repository for the software. In order to build, ## Sketch Overview -There are two kinds of sketches here: test programs (catena4410_test1, catene4410_test2, catena4410_test3, catena4450_test1), and full sensor programs (catena4410_sensor1, catena4420_test1, catena4450_sensor1). +There are two kinds of sketches here: test programs (catena4410_test1, catene4410_test2, catena4410_test3, catena4450_test1), and full sensor programs (catena4410_sensor1, catena4420_test1, catena4450m101_sensor1, catena4460_aqi). The sketches that use LoRaWAN take advantage of the [MCCI](http://www.mcci.com) [arduino-lorawan](https://github.com/mcci-catena/arduino-lorawan) library to cleanly separate the application from the network transport. The sensor sketches also use the RTCZero library to sleep the CPU. -### catena4450m101_sensor1 +### Full Sensor Programs + +#### catena4450m101_sensor1 This is the application written for the Catena 4450 power monitoring node used in the [Ithaca Power Project](https://ithaca-power.mcci.com). It uses FRAM-based provisioning (so there is no need to edit code to change LoRaWAN keys or other settings). -### catena4410_sensor1 +#### catena4450m102_pond + +This is the Tzu Chi University / Asolar Hualian research farm project sketch, upgraded for use with the Catena 4450. It uses the integrated FRAM for provisioning, auto-detects the attached sensors, and transmits data in format 0x15. + +#### catena4460_aqi + +This sketch collects and transmits air-quality information using the Bosch BME680 sensor on the MCCI Catena 4460. It transmits data in format 0x17. + +#### catena4410_sensor1 This sketch is the application written for the Tzu Chi University / Asolar Hualian research farm project. One firmware image is used for a variety of sensors. You can configure a given sensor as a general purpose device or as a specific subset, referencing back to the Atmel SAMD21 CPU's unique identifier. All provisioning is done at compile time, but the network keys and other sensitive information is placed in a special library that is outside the normal set of repositories. The sketch transmits data in format 0x11. -### catena4410_test3 +### Test programs + +#### catena4410_test3 This is the primary test app used when bringing up and provisioning Catena 4410 units for use with The Things Network. -### catena4410_test1, catena4410_test2 +#### catena4410_test1, catena4410_test2 These are simpler test programs. They were rarely used after test3 was ready, but they may be useful for test of future Catena 441x variants with different sensor configurations. -### catena4450_test01 +#### catena4450_test01 This is the primary (non-LoRaWAN) test sketch for the Catena 4450. -### catena4450m101_sensor - -This is the sketch used with the Catena 4450 for power sensing for the Ithaca Power project. It uses the integrated FRAM for provisioning, and transmits data in format 0x14. - -### catena4450m102_pond - -This is the Tzu Chi University / Asolar Hualian research farm project sketch, upgraded for use with the Catena 4450. It uses the integrated FRAM for provisioning, auto-detects the attached sensors, and transmits data in format 0x15. - ## Extras -The directory `extras` contains documenation and sample scripts for decoding the various formats. +The directory `extras` contains documentation and sample scripts for decoding the various formats. ## Required Board-Support Packages -All board support packages are maintained by MCCI. You should add the path to the reference Json file in your Arduino preferences. As of this writing, the file name to add to the list is: - -``` -https://github.com/mcci-catena/arduino-boards/raw/master/BoardManagerFiles/package_mcci_index.json -``` - -Check the [MCCI board support repository](https://github.com/mcci-catena/arduino-boards) for the latest information. +All board support packages are maintained by MCCI. You should add the path to the reference Json file in your Arduino preferences. See the README for [arduino-boards](https://github.com/mcci-catena/arduino-boards) for more informatin. ## Required Libraries A number of libraries are required by this code. `catena4450m101_sensor1` contains a Bash script [`git-boot.sh`](https://github.com/mcci-catena/Catena-Sketches/blob/master/catena4450m101_sensor/git-boot.sh) that can be used to download all the libraries, using a simple database stored in [`git-repos.dat`](https://github.com/mcci-catena/Catena-Sketches/blob/master/catena4450m101_sensor/git-repos.dat). -* [MCCI's Catena Platform library](https://github.com/mcci-catena/CatenaArduinoPlatform) provides an enhanced environment for portable sketch development. It includes an command-processing framework, an elaborate persistant storage framework, encoding libraries, support for storing the persistent data from the `arduino-lorawan` library, and so forth. -* [MCCI's Arduino-LoRaWAN library](https://github.com/mcci-catena/arduino-lorawan) is a structured wrapper for the Arduino LMIC library, with the neccessary hooks for interfacing to persistent storage. +* [MCCI's Catena Platform library](https://github.com/mcci-catena/Catena-Arduino-Platform) provides an enhanced environment for portable sketch development. It includes an command-processing framework, an elaborate persistent storage framework, encoding libraries, support for storing the persistent data from the `arduino-lorawan` library, and so forth. + +* [MCCI's Arduino-LoRaWAN library](https://github.com/mcci-catena/arduino-lorawan) is a structured wrapper for the Arduino LMIC library, with the necessary hooks for interfacing to persistent storage. + * [MCCI's Ardino LMIC library](https://github.com/mcci-catena/arduino-lmic) is MCCI's fork of [The Things Network New York Arduino LMIC code](https://github.com/things-nyc/arduino-lmic). + * [MCCI's ADK](https://github.com/mcci-catena/Catena-mcciadk) is MCCI's general-purpose cross-platform "XDK" library, ported to the Arduino environment. + * [MCCI's Fork of the SAMD RTCZero library](https://github.com/mcci-catena/RTCZero) has the somewhat more substantial changes needed to allow the various processor sleep modes to be accessed, and to allow for some debuggging of the sleep mode chosen. ## Libraries for sensor work * [MCCI's Adafruit BME280 library](https://github.com/mcci-catena/Adafruit_BME280_Library) is used to make temperature, humidity and barometric pressure measurements using the [Adafruit BME280 breakout board](https://www.adafruit.com/products/2652), which we connect via I2C. It's based on the Adafruit library, but updated so that temperature, humidity and pressure are all read at the same time, to avoid data instability. + * The [OneWire](https://github.com/mcci-catena/OneWire) and [Arduino Temperature Control Library](https://github.com/mcci-catena/Arduino-Temperature-Control-Library) are used for making measurements from Dallas-Semiconductor-based temperature sensors such as the [immersible sensor](https://www.adafruit.com/products/381) from Adafruit. + * The [Adafruit Sensor Library](https://github.com/mcci-catena/Adafruit_Sensor) and the [Adafruit TSL2561 Lux Sensor Library](https://github.com/mcci-catena/Adafruit_TSL2561) are used for making ambient light measurements with the Adafruit [TSL2561 Lux Sensor](https://www.adafruit.com/products/439) + * The [SHT1x library](https://github.com/mcci-catena/SHT1x) is used for measuring soil temperature and humidity using the Adafruit [SHT10 sensor](https://www.adafruit.com/products/1298). ## Related Work + * [MCCI's Fork of the Map The Things Arduino Sketch](https://github.com/mcci-catena/mapthethings-arduino) contains, on the MCCI-Catena branch, a port of that app supporting OTAA, using the Catena libraries. + * [MCCI's Catena Hardware Repository](https://github.com/mcci-catena/HW-Designs) contains hardware design information and schematics. ## Boilerplate + MCCI work is released under the MIT public license. All other work from contributors (repositories forked to the MCCI Catena [github page](https://github.com/mcci-catena/)) are licensed according to the terms of those modules. Support inquiries may be filed at [https:://portal.mcci.com](https:://portal.mcci.com) or as tickets on [github](https://github.com/mcci-catena). We are very busy, so we can't promise to help; but we'll do our best. @@ -114,4 +124,10 @@ Thanks to Amy Chen of Asolar, Josh Yu, and to Tzu-Chih University for funding th Further thanks to [Adafruit](https://www.adafruit.com/) for the wonderful Feather M0 LoRa platform, to [The Things Network](https://www.thethingsnetwork.org) for the LoRaWAN-based infrastructure, to [The Things Network New York](https://thethings.nyc) and [TTN Ithaca](https://ttni.tech) for the inspiration and support, and to the myriad people who have contributed to the Arduino and LoRaWAN infrastructure. -**MCCI** and **Catena** are registered trademarks of MCCI Corporation. \ No newline at end of file +MCCI and MCCI Catena are registered trademarks of MCCI Corporation. + +LoRa is a registered trademark of Semtech Corporation. + +LoRaWAN is a trademark of the LoRa Alliance. + +All other trademarks are the property of their respective owners. \ No newline at end of file From 50454f767d158c675b06e8db3021ee9c4282f0e3 Mon Sep 17 00:00:00 2001 From: Terry Moore Date: Sun, 1 Apr 2018 16:08:42 -0400 Subject: [PATCH 17/17] Minor doc changes from review --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index e643d34..6619524 100644 --- a/README.md +++ b/README.md @@ -31,7 +31,7 @@ This repository is the top-level repository for the software. In order to build, ## Sketch Overview -There are two kinds of sketches here: test programs (catena4410_test1, catene4410_test2, catena4410_test3, catena4450_test1), and full sensor programs (catena4410_sensor1, catena4420_test1, catena4450m101_sensor1, catena4460_aqi). +There are two kinds of sketches here: test programs (catena4410_test1, catene4410_test2, catena4410_test3, catena4420_test1,catena4450_test1), and full sensor programs (catena4410_sensor1, catena4450m101_sensor1, catena4450m102_pond, catena4460_aqi). The sketches that use LoRaWAN take advantage of the [MCCI](http://www.mcci.com) [arduino-lorawan](https://github.com/mcci-catena/arduino-lorawan) library to cleanly separate the application from the network transport.