diff --git a/Catena4610_cMeasurementLoop.cpp b/Catena4610_cMeasurementLoop.cpp new file mode 100644 index 0000000..b9d3b5d --- /dev/null +++ b/Catena4610_cMeasurementLoop.cpp @@ -0,0 +1,587 @@ +/* + +Module: Catena4610_cMeasurementLoop.cpp + +Function: + Class for transmitting accumulated measurements. + +Copyright: + See accompanying LICENSE file for copyright and license information. + +Author: + Rama subbu, MCCI Corporation june 2021 + +*/ + +#include "Catena4610_cMeasurementLoop.h" + +#include +#include +#include +#include + +using namespace McciCatena4610; +using namespace McciCatena; + +extern Catena::LoRaWAN gLoRaWAN; +extern cMeasurementLoop gMeasurementLoop; + +static constexpr uint8_t kVddPin = D11; + +/****************************************************************************\ +| +| An object to represent the uplink activity +| +\****************************************************************************/ + +void cMeasurementLoop::begin() + { + // register for polling. + if (! this->m_registered) + { + this->m_registered = true; + + gCatena.registerObject(this); + + this->m_UplinkTimer.begin(this->m_txCycleSec * 1000); + } + + Wire.begin(); + if (this->m_BME280.begin(BME280_ADDRESS, Adafruit_BME280::OPERATING_MODE::Sleep)) + { + this->m_fBme280 = true; + } + else + { + this->m_fBme280 = false; + gCatena.SafePrintf("No BME280 found: check wiring\n"); + } + + if (this->m_si1133.begin()) + { + this->m_fSi1133 = true; + + auto const measConfig = Catena_Si1133::ChannelConfiguration_t() + .setAdcMux(Catena_Si1133::InputLed_t::LargeWhite) + .setSwGainCode(0) + .setHwGainCode(0) + .setPostShift(1) + .set24bit(false); + + this->m_si1133.configure(0, measConfig, 0); + } + else + { + this->m_fSi1133 = false; + gCatena.SafePrintf("No Si1133 found: check hardware\n"); + } + + // start (or restart) the FSM. + if (! this->m_running) + { + this->m_exit = false; + this->m_fsm.init(*this, &cMeasurementLoop::fsmDispatch); + } + } + +void cMeasurementLoop::end() + { + if (this->m_running) + { + this->m_exit = true; + this->m_fsm.eval(); + } + } + +void cMeasurementLoop::requestActive(bool fEnable) + { + if (fEnable) + this->m_rqActive = true; + else + this->m_rqInactive = true; + + this->m_fsm.eval(); + } + +cMeasurementLoop::State +cMeasurementLoop::fsmDispatch( + cMeasurementLoop::State currentState, + bool fEntry + ) + { + State newState = State::stNoChange; + + if (fEntry && this->isTraceEnabled(this->DebugFlags::kTrace)) + { + gCatena.SafePrintf("cMeasurementLoop::fsmDispatch: enter %s\n", + this->getStateName(currentState) + ); + } + + switch (currentState) + { + case State::stInitial: + newState = State::stInactive; + this->resetMeasurements(); + break; + + case State::stInactive: + if (fEntry) + { + // turn off anything that should be off while idling. + } + if (this->m_rqActive) + { + // when going active manually, start the measurement + // cycle immediately. + this->m_rqActive = this->m_rqInactive = false; + this->m_active = true; + this->m_UplinkTimer.retrigger(); + newState = State::stWarmup; + } + break; + + case State::stSleeping: + if (fEntry) + { + // set the LEDs to flash accordingly. + gLed.Set(McciCatena::LedPattern::Sleeping); + } + + if (this->m_rqInactive) + { + this->m_rqActive = this->m_rqInactive = false; + this->m_active = false; + newState = State::stInactive; + } + else if (this->m_UplinkTimer.isready()) + newState = State::stMeasure; + else if (this->m_UplinkTimer.getRemaining() > 1500) + this->sleep(); + break; + + // get some data. This is only called while booting up. + case State::stWarmup: + if (fEntry) + { + //start the timer + this->setTimer(5 * 1000); + } + if (this->timedOut()) + newState = State::stMeasure; + break; + + + // fill in the measurement + case State::stMeasure: + if (fEntry) + { + // start SI1133 measurement (one-time) + this->m_si1133.start(true); + this->updateSynchronousMeasurements(); + this->setTimer(1000); + } + + if (this->m_si1133.isOneTimeReady()) + { + this->updateLightMeasurements(); + newState = State::stTransmit; + } + else if (this->timedOut()) + { + this->m_si1133.stop(); + newState = State::stTransmit; + if (this->isTraceEnabled(this->DebugFlags::kError)) + gCatena.SafePrintf("S1133 timed out\n"); + } + break; + + case State::stTransmit: + if (fEntry) + { + TxBuffer_t b; + this->fillTxBuffer(b, this->m_data); + + this->m_FileTxBuffer.begin(); + for (auto i = 0; i < b.getn(); ++i) + this->m_FileTxBuffer.put(b.getbase()[i]); + + this->resetMeasurements(); + + if (gLoRaWAN.IsProvisioned()) + this->startTransmission(b); + } + if (! gLoRaWAN.IsProvisioned()) + { + newState = State::stSleeping; + } + if (this->txComplete()) + { + newState = State::stSleeping; + + // calculate the new sleep interval. + this->updateTxCycleTime(); + } + break; + + case State::stFinal: + break; + + default: + break; + } + + return newState; + } + +/****************************************************************************\ +| +| Take a measurement +| +\****************************************************************************/ + +void cMeasurementLoop::resetMeasurements() + { + memset((void *) &this->m_data, 0, sizeof(this->m_data)); + this->m_data.flags = Flags(0); + } + +void cMeasurementLoop::updateSynchronousMeasurements() + { + this->m_data.Vbat = gCatena.ReadVbat(); + this->m_data.flags |= Flags::FlagVbat; + + this->m_data.Vbus = gCatena.ReadVbus(); + + if (gCatena.getBootCount(this->m_data.BootCount)) + { + this->m_data.flags |= Flags::FlagBoot; + } + + if (this->m_fBme280) + { + auto m = this->m_BME280.readTemperaturePressureHumidity(); + this->m_data.env.Temperature = m.Temperature; + this->m_data.env.Pressure = m.Pressure; + this->m_data.env.Humidity = m.Humidity; + this->m_data.flags |= Flags::FlagTPH; + } + } + // SI1133 is handled separately + +void cMeasurementLoop::updateLightMeasurements() + { + uint16_t data[1]; + + this->m_si1133.readMultiChannelData(data, 1); + this->m_si1133.stop(); + + this->m_data.flags |= Flags::FlagLux; + this->m_data.light.White = data[0]; + } +/****************************************************************************\ +| +| Start uplink of data +| +\****************************************************************************/ + +void cMeasurementLoop::startTransmission( + cMeasurementLoop::TxBuffer_t &b + ) + { + auto const savedLed = gLed.Set(McciCatena::LedPattern::Off); + gLed.Set(McciCatena::LedPattern::Sending); + + + // by using a lambda, we can access the private contents + auto sendBufferDoneCb = + [](void *pClientData, bool fSuccess) + { + auto const pThis = (cMeasurementLoop *)pClientData; + pThis->m_txpending = false; + pThis->m_txcomplete = true; + pThis->m_txerr = ! fSuccess; + pThis->m_fsm.eval(); + }; + + bool fConfirmed = false; + if (gCatena.GetOperatingFlags() & + static_cast(gCatena.OPERATING_FLAGS::fConfirmedUplink)) + { + gCatena.SafePrintf("requesting confirmed tx\n"); + fConfirmed = true; + } + + this->m_txpending = true; + this->m_txcomplete = this->m_txerr = false; + + if (! gLoRaWAN.SendBuffer(b.getbase(), b.getn(), sendBufferDoneCb, (void *)this, fConfirmed)) + { + // uplink wasn't launched. + this->m_txcomplete = true; + this->m_txerr = true; + this->m_fsm.eval(); + } + } + +void cMeasurementLoop::sendBufferDone(bool fSuccess) + { + this->m_txpending = false; + this->m_txcomplete = true; + this->m_txerr = ! fSuccess; + this->m_fsm.eval(); + } + +/****************************************************************************\ +| +| The Polling function -- +| +\****************************************************************************/ + +void cMeasurementLoop::poll() + { + bool fEvent; + + // no need to evaluate unless something happens. + fEvent = false; + + // if we're not active, and no request, nothing to do. + if (! this->m_active) + { + if (! this->m_rqActive) + return; + + // we're asked to go active. We'll want to eval. + fEvent = true; + } + + if (this->m_fTimerActive) + { + if ((millis() - this->m_timer_start) >= this->m_timer_delay) + { + this->m_fTimerActive = false; + this->m_fTimerEvent = true; + fEvent = true; + } + } + + // check the transmit time. + if (this->m_UplinkTimer.peekTicks() != 0) + { + fEvent = true; + } + + if (fEvent) + this->m_fsm.eval(); + + this->m_data.Vbus = gCatena.ReadVbus(); + setVbus(this->m_data.Vbus); + } + +/****************************************************************************\ +| +| Update the TxCycle count. +| +\****************************************************************************/ + +void cMeasurementLoop::updateTxCycleTime() + { + auto txCycleCount = this->m_txCycleCount; + + // update the sleep parameters + if (txCycleCount > 1) + { + // values greater than one are decremented and ultimately reset to default. + this->m_txCycleCount = txCycleCount - 1; + } + else if (txCycleCount == 1) + { + // it's now one (otherwise we couldn't be here.) + gCatena.SafePrintf("resetting tx cycle to default: %u\n", this->m_txCycleSec_Permanent); + + this->setTxCycleTime(this->m_txCycleSec_Permanent, 0); + } + else + { + // it's zero. Leave it alone. + } + } + +/****************************************************************************\ +| +| Handle sleep between measurements +| +\****************************************************************************/ + +void cMeasurementLoop::sleep() + { + const bool fDeepSleep = checkDeepSleep(); + + if (! this->m_fPrintedSleeping) + this->doSleepAlert(fDeepSleep); + + if (fDeepSleep) + this->doDeepSleep(); + } + +// for now, we simply don't allow deep sleep. In the future might want to +// use interrupts on activity to wake us up; then go back to sleep when we've +// seen nothing for a while. +bool cMeasurementLoop::checkDeepSleep() + { + bool const fDeepSleepTest = gCatena.GetOperatingFlags() & + static_cast(gCatena.OPERATING_FLAGS::fDeepSleepTest); + bool fDeepSleep; + std::uint32_t const sleepInterval = this->m_UplinkTimer.getRemaining() / 1000; + + if (sleepInterval < 2) + fDeepSleep = false; + else if (fDeepSleepTest) + { + fDeepSleep = true; + } +#ifdef USBCON + else if (Serial.dtr()) + { + fDeepSleep = false; + } +#endif + else if (gCatena.GetOperatingFlags() & + static_cast(gCatena.OPERATING_FLAGS::fDisableDeepSleep)) + { + fDeepSleep = false; + } + else if ((gCatena.GetOperatingFlags() & + static_cast(gCatena.OPERATING_FLAGS::fUnattended)) != 0) + { + fDeepSleep = true; + } + else + { + fDeepSleep = false; + } + + return fDeepSleep; + } + +void cMeasurementLoop::doSleepAlert(bool fDeepSleep) + { + this->m_fPrintedSleeping = true; + + if (fDeepSleep) + { + bool const fDeepSleepTest = + gCatena.GetOperatingFlags() & + static_cast(gCatena.OPERATING_FLAGS::fDeepSleepTest); + const uint32_t deepSleepDelay = fDeepSleepTest ? 10 : 30; + + gCatena.SafePrintf("using deep sleep in %u secs" +#ifdef USBCON + " (USB will disconnect while asleep)" +#endif + ": ", + deepSleepDelay + ); + + // sleep and print + gLed.Set(McciCatena::LedPattern::TwoShort); + + for (auto n = deepSleepDelay; n > 0; --n) + { + uint32_t tNow = millis(); + + while (uint32_t(millis() - tNow) < 1000) + { + gCatena.poll(); + yield(); + } + gCatena.SafePrintf("."); + } + gCatena.SafePrintf("\nStarting deep sleep.\n"); + uint32_t tNow = millis(); + while (uint32_t(millis() - tNow) < 100) + { + gCatena.poll(); + yield(); + } + } + else + gCatena.SafePrintf("using light sleep\n"); + } + +void cMeasurementLoop::doDeepSleep() + { + // bool const fDeepSleepTest = gCatena.GetOperatingFlags() & + // static_cast(gCatena.OPERATING_FLAGS::fDeepSleepTest); + std::uint32_t const sleepInterval = this->m_UplinkTimer.getRemaining() / 1000; + + if (sleepInterval == 0) + return; + + /* ok... now it's time for a deep sleep */ + gLed.Set(McciCatena::LedPattern::Off); + this->deepSleepPrepare(); + + /* sleep */ + gCatena.Sleep(sleepInterval); + + /* recover from sleep */ + this->deepSleepRecovery(); + + /* and now... we're awake again. trigger another measurement */ + this->m_fsm.eval(); + } + +void cMeasurementLoop::deepSleepPrepare(void) + { + pinMode(kVddPin, INPUT); + + Serial.end(); + Wire.end(); + SPI.end(); + if (this->m_pSPI2 && this->m_fSpi2Active) + { + this->m_pSPI2->end(); + this->m_fSpi2Active = false; + } + } + +void cMeasurementLoop::deepSleepRecovery(void) + { + pinMode(kVddPin, OUTPUT); + digitalWrite(kVddPin, HIGH); + + Serial.begin(); + Wire.begin(); + SPI.begin(); + //if (this->m_pSPI2) + // this->m_pSPI2->begin(); + } + +/****************************************************************************\ +| +| Time-out asynchronous measurements. +| +\****************************************************************************/ + +// set the timer +void cMeasurementLoop::setTimer(std::uint32_t ms) + { + this->m_timer_start = millis(); + this->m_timer_delay = ms; + this->m_fTimerActive = true; + this->m_fTimerEvent = false; + } + +void cMeasurementLoop::clearTimer() + { + this->m_fTimerActive = false; + this->m_fTimerEvent = false; + } + +bool cMeasurementLoop::timedOut() + { + bool result = this->m_fTimerEvent; + this->m_fTimerEvent = false; + return result; + } diff --git a/Catena4610_cMeasurementLoop.h b/Catena4610_cMeasurementLoop.h new file mode 100644 index 0000000..71a9a6c --- /dev/null +++ b/Catena4610_cMeasurementLoop.h @@ -0,0 +1,337 @@ +/* + +Module: Catena4610_cMeasurementLoop.h + +Function: + cMeasurementLoop definitions. + +Copyright: + See accompanying LICENSE file for copyright and license information. + +Author: + Rama subbu, MCCI Corporation june 2021 + +*/ + +#ifndef _Catena4610_cMeasurementLoop_h_ +# define _Catena4610_cMeasurementLoop_h_ + +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +extern McciCatena::Catena gCatena; +extern McciCatena::Catena::LoRaWAN gLoRaWAN; +extern McciCatena::StatusLed gLed; + +namespace McciCatena4610 { + +/****************************************************************************\ +| +| An object to represent the uplink activity +| +\****************************************************************************/ + +class cMeasurementBase + { + + }; + +class cMeasurementFormat : public cMeasurementBase + { +public: + // buffer size for uplink data + static constexpr size_t kTxBufferSize = 16; + + // the structure of a measurement + struct Measurement + { + //---------------- + // the subtypes: + //---------------- + + // environmental measurements + struct Env + { + // temperature (in degrees C) + float Temperature; + // pressure (in millibars/hPa) + float Pressure; + // humidity (in % RH) + float Humidity; + }; + + // ambient light measurements + struct Light + { + // "white" light, in w/m^2 + uint16_t White; + }; + + //--------------------------- + // the actual members as POD + //--------------------------- + + // flags of entries that are valid. + McciCatena::FlagsSensor2 flags; + // measured battery voltage, in volts + float Vbat; + // measured system Vdd voltage, in volts + float Vsystem; + // measured USB bus voltage, in volts. + float Vbus; + // boot count + uint32_t BootCount; + // environmental data + Env env; + // ambient light + Light light; + }; + }; + +class cMeasurementLoop : public McciCatena::cPollableObject + { +public: + // some parameters + using MeasurementFormat = McciCatena4610::cMeasurementFormat; + using Measurement = MeasurementFormat::Measurement; + using Flags = McciCatena::FlagsSensor2; + static constexpr std::uint8_t kMessageFormat = McciCatena::FormatSensor2; + + enum OPERATING_FLAGS : uint32_t + { + fUnattended = 1 << 0, + fManufacturingTest = 1 << 1, + fConfirmedUplink = 1 << 16, + fDisableDeepSleep = 1 << 17, + fQuickLightSleep = 1 << 18, + fDeepSleepTest = 1 << 19, + }; + + enum DebugFlags : std::uint32_t + { + kError = 1 << 0, + kWarning = 1 << 1, + kTrace = 1 << 2, + kInfo = 1 << 3, + }; + + // constructor + cMeasurementLoop( + ) + : m_txCycleSec_Permanent(6 * 60) // default uplink interval + , m_txCycleSec(30) // initial uplink interval + , m_txCycleCount(10) // initial count of fast uplinks + , m_DebugFlags(DebugFlags(kError | kTrace)) + {}; + + // neither copyable nor movable + cMeasurementLoop(const cMeasurementLoop&) = delete; + cMeasurementLoop& operator=(const cMeasurementLoop&) = delete; + cMeasurementLoop(const cMeasurementLoop&&) = delete; + cMeasurementLoop& operator=(const cMeasurementLoop&&) = delete; + + enum class State : std::uint8_t + { + stNoChange = 0, // this name must be present: indicates "no change of state" + stInitial, // this name must be present: it's the starting state. + stInactive, // parked; not doing anything. + stSleeping, // active; sleeping between measurements + stWarmup, // transition from inactive to measure, get some data. + stMeasure, // take measurents + stTransmit, // transmit data + stFinal, // this name must be present, it's the terminal state. + }; + + static constexpr const char *getStateName(State s) + { + switch (s) + { + case State::stNoChange: return "stNoChange"; + case State::stInitial: return "stInitial"; + case State::stInactive: return "stInactive"; + case State::stSleeping: return "stSleeping"; + case State::stWarmup: return "stWarmup"; + case State::stMeasure: return "stMeasure"; + case State::stTransmit: return "stTransmit"; + case State::stFinal: return "stFinal"; + default: return "<>"; + } + } + + // concrete type for uplink data buffer + using TxBuffer_t = McciCatena::AbstractTxBuffer_t; + + // initialize measurement FSM. + void begin(); + void end(); + void setTxCycleTime( + std::uint32_t txCycleSec, + std::uint32_t txCycleCount + ) + { + this->m_txCycleSec = txCycleSec; + this->m_txCycleCount = txCycleCount; + + this->m_UplinkTimer.setInterval(txCycleSec * 1000); + if (this->m_UplinkTimer.peekTicks() != 0) + this->m_fsm.eval(); + } + std::uint32_t getTxCycleTime() + { + return this->m_txCycleSec; + } + virtual void poll() override; + void setBme280(bool fEnable) + { + this->m_fBme280 = fEnable; + } + void setVbus(float Vbus) + { + // set threshold value as 4.0V as there is reverse voltage + // in vbus(~3.5V) while powered from battery in 4610. + this->m_fUsbPower = (Vbus > 4.0f) ? true : false; + } + + // request that the measurement loop be active/inactive + void requestActive(bool fEnable); + + // return true if a given debug mask is enabled. + bool isTraceEnabled(DebugFlags mask) const + { + return this->m_DebugFlags & mask; + } + + // register an additional SPI for sleep/resume + // can be called before begin(). + void registerSecondSpi(SPIClass *pSpi) + { + this->m_pSPI2 = pSpi; + } +private: + // sleep handling + void sleep(); + bool checkDeepSleep(); + void doSleepAlert(bool fDeepSleep); + void doDeepSleep(); + void deepSleepPrepare(); + void deepSleepRecovery(); + + // read data + void updateSynchronousMeasurements(); + void updateLightMeasurements(); + void resetMeasurements(); + + // telemetry handling. + void fillTxBuffer(TxBuffer_t &b, Measurement const & mData); + void startTransmission(TxBuffer_t &b); + void sendBufferDone(bool fSuccess); + + bool txComplete() + { + return this->m_txcomplete; + } + void updateTxCycleTime(); + + // timeout handling + + // set the timer + void setTimer(std::uint32_t ms); + // clear the timer + void clearTimer(); + // test (and clear) the timed-out flag. + bool timedOut(); + + // instance data +private: + McciCatena::cFSM m_fsm; + // evaluate the control FSM. + State fsmDispatch(State currentState, bool fEntry); + + Adafruit_BME280 m_BME280; + McciCatena::Catena_Si1133 m_si1133; + bool fHasCompostTemp; + + // second SPI class + SPIClass *m_pSPI2; + + // debug flags + DebugFlags m_DebugFlags; + + // true if object is registered for polling. + bool m_registered : 1; + // true if object is running. + bool m_running : 1; + // true to request exit + bool m_exit : 1; + // true if in active uplink mode, false otehrwise. + bool m_active : 1; + + // set true to request transition to active uplink mode; cleared by FSM + bool m_rqActive : 1; + // set true to request transition to inactive uplink mode; cleared by FSM + bool m_rqInactive : 1; + + // set true if event timer times out + bool m_fTimerEvent : 1; + // set true while evenet timer is active. + bool m_fTimerActive : 1; + // set true if USB power is present. + bool m_fUsbPower : 1; + // set true if BME280 is present + bool m_fBme280 : 1; + // set true if SI1133 is present + bool m_fSi1133: 1; + + // set true while a transmit is pending. + bool m_txpending : 1; + // set true when a transmit completes. + bool m_txcomplete : 1; + // set true when a transmit complete with an error. + bool m_txerr : 1; + // set true when we've printed how we plan to sleep + bool m_fPrintedSleeping : 1; + // set true when SPI2 is active + bool m_fSpi2Active: 1; + + // uplink time control + McciCatena::cTimer m_UplinkTimer; + std::uint32_t m_txCycleSec; + std::uint32_t m_txCycleCount; + std::uint32_t m_txCycleSec_Permanent; + + // simple timer for timing-out sensors. + std::uint32_t m_timer_start; + std::uint32_t m_timer_delay; + + // the current measurement + Measurement m_data; + + TxBuffer_t m_FileTxBuffer; + }; + +static constexpr cMeasurementLoop::Flags operator& (const cMeasurementLoop::Flags lhs, const cMeasurementLoop::Flags rhs) + { + return cMeasurementLoop::Flags(uint8_t(lhs) & uint8_t(rhs)); + }; + +}// namespace McciCatena4610 + +#endif /* _Catena4610_cMeasurementLoop_h_ */ diff --git a/Catena4610_cMeasurementLoop_fillTxBuffer.cpp b/Catena4610_cMeasurementLoop_fillTxBuffer.cpp new file mode 100644 index 0000000..89c5fcf --- /dev/null +++ b/Catena4610_cMeasurementLoop_fillTxBuffer.cpp @@ -0,0 +1,106 @@ +/* + +Module: Catena4610_cMeasurementLoop.cpp + +Function: + Class for transmitting accumulated measurements. + +Copyright: + See accompanying LICENSE file for copyright and license information. + +Author: + Rama subbu, MCCI Corporation june 2021 + +*/ + +#include + +#include "Catena4610_cMeasurementLoop.h" + +#include + +using namespace McciCatena; +using namespace McciCatena4610; + +/* + +Name: McciCatena4610::cMeasurementLoop::fillTxBuffer() + +Function: + Prepare a messages in a TxBuffer with data from current measurements. + +Definition: + void McciCatena4610::cMeasurementLoop::fillTxBuffer( + cMeasurementLoop::TxBuffer_t& b + ); + +Description: + A format 0x22 message is prepared from the data in the cMeasurementLoop + object. + +*/ + +void +cMeasurementLoop::fillTxBuffer( + cMeasurementLoop::TxBuffer_t& b, Measurement const &mData + ) + { + gLed.Set(McciCatena::LedPattern::Measuring); + + + // initialize the message buffer to an empty state + b.begin(); + + // insert format byte + b.put(kMessageFormat); + + // the flags in Measurement correspond to the over-the-air flags. + b.put(std::uint8_t(this->m_data.flags)); + + // send Vbat + if (((this->m_data.flags) & (Flags::FlagVbat))!= Flags(0)) + { + float Vbat = mData.Vbat; + gCatena.SafePrintf("Vbat: %d mV\n", (int) (Vbat * 1000.0f)); + b.putV(Vbat); + } + + // send Vdd if we can measure it. + + // Vbus is sent as 5000 * v + float Vbus = mData.Vbus; + gCatena.SafePrintf("Vbus: %d mV\n", (int) (Vbus * 1000.0f)); + + // send boot count + if (((this->m_data.flags) & (Flags::FlagBoot))!= Flags(0)) + { + b.putBootCountLsb(mData.BootCount); + } + + if (((this->m_data.flags) & (Flags::FlagTPH))!= Flags(0)) + { + gCatena.SafePrintf( + "BME280: T: %d P: %d RH: %d\n", + (int) mData.env.Temperature, + (int) mData.env.Pressure, + (int) mData.env.Humidity + ); + b.putT(mData.env.Temperature); + b.putP(mData.env.Pressure); + // no method for 2-byte RH, directly encode it. + b.putRH(mData.env.Humidity); + } + + // put light + if (((this->m_data.flags) & (Flags::FlagLux))!= Flags(0)) + { + gCatena.SafePrintf( + "Si1133: %u White\n", + mData.light.White + ); + + b.putLux(mData.light.White); + } + + gLed.Set(McciCatena::LedPattern::Off); + } diff --git a/Catena4610_cmd.h b/Catena4610_cmd.h new file mode 100644 index 0000000..d6c042f --- /dev/null +++ b/Catena4610_cmd.h @@ -0,0 +1,25 @@ +/* + +Module: Catena4610_cmd.h + +Function: + Linkage for Catena4610 command processing. + +Copyright: + See accompanying LICENSE file for copyright and license information. + +Author: + Rama subbu, MCCI Corporation june 2021 + +*/ + +#ifndef _Catena4610_cmd_h_ +#define _Catena4610_cmd_h_ + +#pragma once + +#include + +McciCatena::cCommandStream::CommandFn cmdLog; + +#endif /* _Catena4610_cmd_h_ */ diff --git a/README.md b/README.md index b1d1c80..2773b42 100644 --- a/README.md +++ b/README.md @@ -10,6 +10,7 @@ - [List of required libraries](#list-of-required-libraries) - [Build and Download](#build-and-download) - [Load the sketch into the Catena](#load-the-sketch-into-the-catena) +- [Fsm features in Catena 4610 sensor](#fsm-features-in-catena-4610-sensor) - [Set the identity of your Catena 4610](#set-the-identity-of-your-catena-4610) - [Check platform and serial number setup](#check-platform-and-serial-number-setup) - [Platform Provisioning](#platform-provisioning) @@ -59,22 +60,22 @@ This is best done from a command line. You can use a number of techniques, but s On Windows, we strongly recommend use of "git bash", available from [git-scm.org](https://git-scm.com/download/win). Then use the "git bash" command line system that's installed by the download. -The goal of this process is to create a directory called `{somewhere}/Catena-Sketches`. You get to choose `{somewhere}`. Everyone has their own convention; the author typically has a directory in his home directory called `sandbox`, and then puts projects there. +The goal of this process is to create a directory called `{somewhere}/Catena4610_Sensor`. You get to choose `{somewhere}`. Everyone has their own convention; the author typically has a directory in his home directory called `sandbox`, and then puts projects there. -Once you have a suitable command line open, you can enter the following commands. In the following, change `{somewhere}` to the directory path where you want to put `catena4610_simple`. +Once you have a suitable command line open, you can enter the following commands. In the following, change `{somewhere}` to the directory path where you want to put `Catena4610_Sensor`. ```console $ cd {somewhere} -$ git clone https://github.com/mcci-catena/catena4610_simple -Cloning into 'catena4610_simple'... +$ git clone https://github.com/mcci-catena/Catena4610_Sensor +Cloning into 'Catena4610_Sensor'... ... $ # get to the right subdirectory -$ cd catena4610_simple +$ cd Catena4610_Sensor $ # confirm that you're in the right place. $ ls -catena4610_simple.ino git-repos.dat README.md +Catena4610_Sensor.ino git-repos.dat README.md ``` ### Install the MCCI STM32 board support library @@ -108,21 +109,24 @@ Either clone `[Catena-Sketches`]https://github.com/mcci-catena/Catena-Sketches), Then you can make sure your library directory is populated using `git-boot.sh`, which can be found here: [github.com/mcci-catena/Catena-Sketches/blob/master/git-boot.sh`](https://github.com/mcci-catena/Catena-Sketches/blob/master/git-boot.sh). ```console -$ cd catena4610_simple +$ cd Catena4610_Sensor $ git-boot.sh -Cloning into 'Catena-Arduino-Platform'... -remote: Counting objects: 1201, done. -remote: Compressing objects: 100% (36/36), done. -remote: Total 1201 (delta 27), reused 24 (delta 14), pack-reused 1151 -Receiving objects: 100% (1201/1201), 275.99 KiB | 0 bytes/s, done. -Resolving deltas: 100% (900/900), done. +Cloning into 'Adafruit_BME280_Library'... +remote: Enumerating objects: 134, done. +remote: Total 134 (delta 0), reused 0 (delta 0), pack-reused 134 +Receiving objects: 100% (134/134), 39.31 KiB | 275.00 KiB/s, done. +Resolving deltas: 100% (68/68), done. + ... ==== Summary ===== -No repos with errors -No repos skipped. -*** no repos were pulled *** -Repos downloaded: Catena-Arduino-Platform arduino-lorawan Catena-mcciadk arduino-lmic MCCI_FRAM_I2C MCCI-Catena-HS300x +*** No repos with errors *** +*** No existing repos skipped *** +*** No existing repos were updated *** +New repos cloned: +Adafruit_BME280_Library Catena-mcciadk arduino-lorawan Adafruit_Sensor MCCI_FRAM_I2C Catena-Arduino-Platform arduino-lmic + + ``` The `git-boot.sh` script has a number of advanced options. Use `git-boot.sh -h` to get help, or look at the source code [here](https://github.com/mcci-catena/Catena-Sketches/blob/master/git-boot.sh). @@ -133,12 +137,14 @@ The `git-boot.sh` script has a number of advanced options. Use `git-boot.sh -h` This sketch depends on the following libraries. -* https://github.com/mcci-catena/Catena4410-Arduino-Library +* https://github.com/mcci-catena/Catena-Arduino-Platform * https://github.com/mcci-catena/arduino-lorawan * https://github.com/mcci-catena/Catena-mcciadk * https://github.com/mcci-catena/arduino-lmic * https://github.com/mcci-catena/MCCI_FRAM_I2C -* https://github.com/mcci-catena/MCCI-Catena-HS300x +* https://github.com/mcci-catena/Adafruit_BME280_Library +* https://github.com/mcci-catena/Adafruit_Sensor + ### Build and Download @@ -146,7 +152,7 @@ Shutdown the Arduino IDE and restart it, just in case. Ensure selected board is 'MCCI Catena 4610' (in the GUI, check that `Tools`>`Board "..."` says `"MCCI Catena 4610"`. -In the IDE, use File>Open to load the `catena4610_simple.ino` sketch. (Remember, in step 1 you cloned `Catena-Sketches` -- find that, and navigate to `{somewhere}/Catena-Sketches/catena4610_simple/`) +In the IDE, use File>Open to load the `Catena4610_Sensor.ino` sketch. (Remember, in step 1 you cloned `Catena4610_Sensor` -- find that, and navigate to `{somewhere}/Catena4610_Sensor/`) Follow normal Arduino IDE procedures to build the sketch: `Sketch`>`Verify/Compile`. If there are no errors, go to the next step. @@ -156,6 +162,20 @@ Make sure the correct port is selected in `Tools`>`Port`. Load the sketch into the Catena using `Sketch`>`Upload` and move on to provisioning. +## Fsm features in Catena 4610 sensor + +1. The Catena4610 sensor is initially in Idle state. +2. The current state is in no change and take it through the state initial .Here we are reset the measurements and takes it through the state inactive. +3. Uplink timer is retriggering in this inactive state and move it to the warmup state. +4. The device is ready to measure the data .It takes it through to the measure state. +5. In measure state the (TPH ,Vbat ,Vboot)data has been read after some delay read the light sensor data. +6. Finally the data has been transmitted to the lorawan and goes to sleep .Then transfer to new state and the process is repeated. + +Here is the state diagram + +[![**FSM State Diagram](http://www.plantuml.com/plantuml/png/LL9DRzim33rRluAt0aEJRBijXw55cWuTwc9eKkmmx43RZ2rK7uP4nVE_7-sqWdk9d_ZqIBwdt4J6mCaQjRbIglHDChGjxWyb_KFR6rht1jtCg_laQjRURLNnQog-b8yGw4I1N4qGVGhoEVa0jSX1-Z91ex4DQDUgK3lVZq6t7Ol-G0HOrzxsw4RjMdXysEsV3diuQICCgw9ZxkDskzcqchjKhUNknjQrNjN8v72pcp-hsz0axVnA5c8PRMNERUBE1nbof116ABqFT0tbRlS0Enzw7t0oKVnCPeIldxzyK-h9CyMbAssnfGZQmVF-jhpRhsq360XQSYHoQg0QeSV8q-hSwNZPMplsgk2E858uKm1ac7o8Ys4OSfApb_V3DQ1hG3AggaipKaMWcCpS--NvCQu1ZjtK2IEmXugWyMuQ8HFG06jByHe6YKdIFitF8VcTqIInG8rcQkRDfjvmng2nCi9KPFombjO4ieyLqRlXkt8oh8f16sbFyZFw18FiS1N14JNKh9MwsgW_d_x2QlKT8ZyupHeDR0K_-Ls7hYMbBd-1p2xEMBC-KouS354lVdFXDmQR-dmi2MCAbC_7W2vQpRbmvmTdj7lDXNljPA1QRdPW37Eeyvq59HOMIu5Qu2qupvBt94CtSYTWoOjqkUq5YEe7CKHl2HfapCJbUdQJaGYE-4hmTfpVOi5EggNd34N7xt0QjAH4b6Du-qvZA5Y1J3c4Zu95a1cAx9bgqkVi9ZEJPlbw6Oecmufu879WFxhB_CVurGsv9bdp7m00)](http://www.plantuml.com/plantuml/svg/LL9DRzim33rRluAt0aEJRBijXw55cWuTwc9eKkmmx43RZ2rK7uP4nVE_7-sqWdk9d_ZqIBwdt4J6mCaQjRbIglHDChGjxWyb_KFR6rht1jtCg_laQjRURLNnQog-b8yGw4I1N4qGVGhoEVa0jSX1-Z91ex4DQDUgK3lVZq6t7Ol-G0HOrzxsw4RjMdXysEsV3diuQICCgw9ZxkDskzcqchjKhUNknjQrNjN8v72pcp-hsz0axVnA5c8PRMNERUBE1nbof116ABqFT0tbRlS0Enzw7t0oKVnCPeIldxzyK-h9CyMbAssnfGZQmVF-jhpRhsq360XQSYHoQg0QeSV8q-hSwNZPMplsgk2E858uKm1ac7o8Ys4OSfApb_V3DQ1hG3AggaipKaMWcCpS--NvCQu1ZjtK2IEmXugWyMuQ8HFG06jByHe6YKdIFitF8VcTqIInG8rcQkRDfjvmng2nCi9KPFombjO4ieyLqRlXkt8oh8f16sbFyZFw18FiS1N14JNKh9MwsgW_d_x2QlKT8ZyupHeDR0K_-Ls7hYMbBd-1p2xEMBC-KouS354lVdFXDmQR-dmi2MCAbC_7W2vQpRbmvmTdj7lDXNljPA1QRdPW37Eeyvq59HOMIu5Qu2qupvBt94CtSYTWoOjqkUq5YEe7CKHl2HfapCJbUdQJaGYE-4hmTfpVOi5EggNd34N7xt0QjAH4b6Du-qvZA5Y1J3c4Zu95a1cAx9bgqkVi9ZEJPlbw6Oecmufu879WFxhB_CVurGsv9bdp7m00 "Click for SVG version") + + ## Set the identity of your Catena 4610 This can be done with any terminal emulator, but it's easiest to do it with the serial monitor built into the Arduino IDE or with the equivalent monitor that's part of the Visual Micro IDE. It can also be done using Tera Term. @@ -184,7 +204,7 @@ If you get an error message, please follow the **Platform Provisioning** instruc The Catena 4610 has a number of build options. We have a single firmware image to support the various options. The firmware learns the build options using the platform GUID data stored in the FRAM, so if the factory settings are not present or have been lost, you need to do the following. -The Catena 4610 is shipped with the catena4610_simple sketch installed, and properly provisioned with serial number and platform GUID. +The Catena 4610 is shipped with the Catena4610_Sensor sketch installed, and properly provisioned with serial number and platform GUID. If you reset the FRAM in your Catena 4610, you will need to enter the following commands. diff --git a/catena4610_simple.h b/catena4610_simple.h new file mode 100644 index 0000000..7fc1363 --- /dev/null +++ b/catena4610_simple.h @@ -0,0 +1,49 @@ +/* + +Name: catena4610_simple.h + +Function: + Global linkage for catena4610_simple.ino + +Copyright: + See accompanying LICENSE file for copyright and license information. + +Author: + Rama subbu, MCCI Corporation june 2021 + +*/ + +#ifndef _catena4610_simple_h_ +# define _catena4610_simple_h_ + +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include "Catena4610_cMeasurementLoop.h" + +// the global clock object + +extern McciCatena::Catena gCatena; +extern McciCatena::cTimer ledTimer; +extern McciCatena::Catena::LoRaWAN gLoRaWAN; +extern McciCatena::StatusLed gLed; + +extern SPIClass gSPI2; +extern McciCatena4610::cMeasurementLoop gMeasurementLoop; + +// The flash +extern McciCatena::Catena_Mx25v8035f gFlash; + +// the bootloader +extern McciCatena::cBootloaderApi gBootloaderApi; + +// the downloaer +extern McciCatena::cDownload gDownload; + +#endif // !defined(_catena4610_simple_h_) diff --git a/catena4610_simple.ino b/catena4610_simple.ino index 8dd9b29..d581919 100644 --- a/catena4610_simple.ino +++ b/catena4610_simple.ino @@ -1,808 +1,222 @@ /* -Module: catena4610_simple.ino +Module: Catena4610_Simple.ino Function: - Sensor program for Catena 4610. + Remote sensor example for the Catena 4610. -Copyright notice: - This file copyright (C) 2019 by - - MCCI Corporation - 3520 Krums Corners Road - Ithaca, NY 14850 - - See project LICENSE file for license information. +Copyright: + See accompanying LICENSE file for copyright and license information. Author: - Terry Moore, MCCI Corporation June 2019 + Rama subbu, MCCI Corporation June 2021 */ -#include - -#include -#include -#include -#include - +#include #include -#include -#include -#include -#include -#include -#include - -#include -#include - +#include +#include "catena4610_simple.h" +#include +#include +#include "Catena4610_cMeasurementLoop.h" +#include "Catena4610_cmd.h" + +extern McciCatena::Catena gCatena; +using namespace McciCatena4610; using namespace McciCatena; +static_assert( + CATENA_ARDUINO_PLATFORM_VERSION_COMPARE_GE( + CATENA_ARDUINO_PLATFORM_VERSION, + CATENA_ARDUINO_PLATFORM_VERSION_CALC(0, 21, 0, 5) + ), + "This sketch requires Catena-Arduino-Platform v0.21.0-5 or later" + ); + +static const char sVersion[] = "2.0.0"; + /****************************************************************************\ | -| Manifest Constants & Typedefs +| Variables. | \****************************************************************************/ +Catena gCatena; +cTimer ledTimer; +Catena::LoRaWAN gLoRaWAN; +StatusLed gLed (Catena::PIN_STATUS_LED); +cMeasurementLoop gMeasurementLoop; -/* adjustable timing parameters */ -enum { - // set this to interval between transmissions, in seconds - // Actual time will be a little longer because have to - // add measurement and broadcast time, but we attempt - // to compensate for the gross effects below. - CATCFG_T_CYCLE = 6 * 60, // every 6 minutes - CATCFG_T_CYCLE_TEST = 30, // every 30 seconds - CATCFG_T_CYCLE_INITIAL = 30, // every 30 seconds initially - CATCFG_INTERVAL_COUNT_INITIAL = 10, // repeat for 5 minutes - CATCFG_T_REBOOT = 30 * 24 * 60 * 60, // reboot every 30 days - }; - -/* additional timing parameters; ususually you don't change these. */ -enum { - CATCFG_T_WARMUP = 1, - CATCFG_T_SETTLE = 5, - CATCFG_T_OVERHEAD = (CATCFG_T_WARMUP + CATCFG_T_SETTLE + 4), - CATCFG_T_MIN = CATCFG_T_OVERHEAD, - CATCFG_T_MAX = CATCFG_T_CYCLE < 60 * 60 ? 60 * 60 : CATCFG_T_CYCLE, // normally one hour max. - CATCFG_INTERVAL_COUNT = 30, - }; - -constexpr uint32_t CATCFG_GetInterval(uint32_t tCycle) - { - return (tCycle < CATCFG_T_OVERHEAD + 1) - ? 1 - : tCycle - CATCFG_T_OVERHEAD - ; - } - -enum { - CATCFG_T_INTERVAL = CATCFG_GetInterval(CATCFG_T_CYCLE), - }; +/* instantiate SPI */ +SPIClass gSPI2( + Catena::PIN_SPI2_MOSI, + Catena::PIN_SPI2_MISO, + Catena::PIN_SPI2_SCK + ); -// forwards -static void settleDoneCb(osjob_t *pSendJob); -static void warmupDoneCb(osjob_t *pSendJob); -static void txNotProvisionedCb(osjob_t *pSendJob); -static void sleepDoneCb(osjob_t *pSendJob); -static Arduino_LoRaWAN::SendBufferCbFn sendBufferDoneCb; -static Arduino_LoRaWAN::ReceivePortBufferCbFn receiveMessage; +/* instantiate the flash */ +Catena_Mx25v8035f gFlash; /****************************************************************************\ | -| handy constexpr to extract the base name of a file +| User commands | \****************************************************************************/ -// two-argument version: first arg is what to return if we don't find -// a directory separator in the second part. -static constexpr const char *filebasename(const char *s, const char *p) - { - return p[0] == '\0' ? s : - (p[0] == '/' || p[0] == '\\') ? filebasename(p + 1, p + 1) : - filebasename(s, p + 1) ; - } - -static constexpr const char *filebasename(const char *s) - { - return filebasename(s, s); - } - +// the individual commmands are put in this table +static const cCommandStream::cEntry sMyExtraCommmands[] = + { + { "log", cmdLog }, + // other commands go here.... + }; -/****************************************************************************\ -| -| Read-only data -| -\****************************************************************************/ +/* a top-level structure wraps the above and connects to the system table */ +/* it optionally includes a "first word" so you can for sure avoid name clashes */ +static cCommandStream::cDispatch +sMyExtraCommands_top( + sMyExtraCommmands, /* this is the pointer to the table */ + sizeof(sMyExtraCommmands), /* this is the size of the table */ + nullptr /* this is no "first word" for all the commands in this table */ + ); -static const char sVersion[] = "0.1.0"; /****************************************************************************\ | -| VARIABLES +| Setup | \****************************************************************************/ -// the primary object -Catena gCatena; - -// -// the LoRaWAN backhaul. Note that we use the -// Catena version so it can provide hardware-specific -// information to the base class. -// -Catena::LoRaWAN gLoRaWAN; - -// -// the LED -// -StatusLed gLed (Catena::PIN_STATUS_LED); - -// The temperature/humidity sensor -Adafruit_BME280 gBme; // The default initalizer creates an I2C connection -bool fBme; - -// The LUX sensor -Catena_Si1133 gSi1133; -bool fLight; - -SPIClass gSPI2( - Catena::PIN_SPI2_MOSI, - Catena::PIN_SPI2_MISO, - Catena::PIN_SPI2_SCK - ); - -// The flash -Catena_Mx25v8035f gFlash; -bool fFlash; - -// USB power -bool fUsbPower; - -// have we printed the sleep info? -bool g_fPrintedSleeping = false; - -// the job that's used to synchronize us with the LMIC code -static osjob_t sensorJob; -void sensorJob_cb(osjob_t *pJob); - -// the cycle time to use -unsigned gTxCycle; -// remaining before we reset to default -unsigned gTxCycleCount; - - - -/* - -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(); - - setup_platform(); - setup_light(); - setup_bme280(); - setup_flash(); - setup_uplink(); - } - -void setup_platform(void) - { -#ifdef USBCON - // if running unattended, don't wait for USB connect. - if (! (gCatena.GetOperatingFlags() & - static_cast(gCatena.OPERATING_FLAGS::fUnattended))) - { - while (!Serial) - /* wait for USB attach */ - yield(); - } -#endif - - gCatena.SafePrintf("\n"); - gCatena.SafePrintf("-------------------------------------------------------------------------------\n"); - gCatena.SafePrintf("This is %s V%s.\n", filebasename(__FILE__), sVersion); - { - char sRegion[16]; - gCatena.SafePrintf("Target network: %s / %s\n", - gLoRaWAN.GetNetworkName(), - gLoRaWAN.GetRegionString(sRegion, sizeof(sRegion)) - ); - } - gCatena.SafePrintf("Enter 'help' for a list of commands.\n"); - gCatena.SafePrintf("(remember to select 'Line Ending: Newline' at the bottom of the monitor window.)\n"); - - gCatena.SafePrintf("SYSCLK: %u MHz\n", unsigned(gCatena.GetSystemClockRate() / (1000 * 1000))); - -#ifdef USBCON - gCatena.SafePrintf("USB enabled\n"); -#else - gCatena.SafePrintf("USB disabled\n"); -#endif - - gLoRaWAN.SetReceiveBufferBufferCb(receiveMessage); - setTxCycleTime(CATCFG_T_CYCLE_INITIAL, CATCFG_INTERVAL_COUNT_INITIAL); - - Catena::UniqueID_string_t CpuIDstring; - - gCatena.SafePrintf( - "CPU Unique ID: %s\n", - gCatena.GetUniqueIDstring(&CpuIDstring) - ); - - gCatena.SafePrintf("--------------------------------------------------------------------------------\n"); - gCatena.SafePrintf("\n"); - - // set up the LED - gLed.begin(); - gCatena.registerObject(&gLed); - gLed.Set(LedPattern::FastFlash); - - // set up LoRaWAN - gCatena.SafePrintf("LoRaWAN init: "); - if (!gLoRaWAN.begin(&gCatena)) - { - gCatena.SafePrintf("failed\n"); - } - else - { - gCatena.SafePrintf("succeeded\n"); - } - - gCatena.registerObject(&gLoRaWAN); - - /* find the platform */ - const Catena::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; - } - } - -void setup_light(void) - { - if (gSi1133.begin()) - { - fLight = true; - gSi1133.configure(0, CATENA_SI1133_MODE_SmallIR); - gSi1133.configure(1, CATENA_SI1133_MODE_White); - gSi1133.configure(2, CATENA_SI1133_MODE_UV); - } - else - { - fLight = false; - gCatena.SafePrintf("No Si1133 found: check hardware\n"); - } - } - -void setup_bme280(void) - { - if (gBme.begin(BME280_ADDRESS, Adafruit_BME280::OPERATING_MODE::Sleep)) - { - fBme = true; - } - else - { - fBme = false; - gCatena.SafePrintf("No BME280 found: check hardware\n"); - } - } - -void setup_flash(void) - { - if (gFlash.begin(&gSPI2, Catena::PIN_SPI2_FLASH_SS)) - { - fFlash = true; - gFlash.powerDown(); - gCatena.SafePrintf("FLASH found, put power down\n"); - } - else - { - fFlash = false; - gFlash.end(); - gSPI2.end(); - gCatena.SafePrintf("No FLASH found: check hardware\n"); - } - } - -uint32_t gRebootMs; - -void setup_uplink(void) - { - LMIC_setClockError(1*65536/100); - - /* figure out when to reboot */ - gRebootMs = (CATCFG_T_REBOOT + os_getRndU2() - 32768) * 1000; - - /* trigger a join by sending the first packet */ - if (!(gCatena.GetOperatingFlags() & - static_cast(gCatena.OPERATING_FLAGS::fManufacturingTest))) - { - if (! gLoRaWAN.IsProvisioned()) - gCatena.SafePrintf("LoRaWAN not provisioned yet. Use the commands to set it up.\n"); - else - { - gLed.Set(LedPattern::Joining); - - /* 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 fillBuffer(TxBuffer_t &b); - -void loop() - { - gCatena.poll(); - - /* for mfg test, don't tx, just fill -- this causes output to Serial */ - if (gCatena.GetOperatingFlags() & - static_cast(gCatena.OPERATING_FLAGS::fManufacturingTest)) - { - TxBuffer_t b; - fillBuffer(b); - delay(1000); - // since the light sensor was stopped in fillbuffer, restart it. - } - } - -void fillBuffer(TxBuffer_t &b) - { - if (fLight) - gSi1133.start(true); - - 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; - - // vBus is sent as 5000 * v - float vBus = gCatena.ReadVbus(); - gCatena.SafePrintf("vBus: %d mV\n", (int) (vBus * 1000.0f)); - fUsbPower = (vBus > 3.0) ? true : false; - - uint32_t bootCount; - if (gCatena.getBootCount(bootCount)) - { - b.putBootCountLsb(bootCount); - flag |= FlagsSensor2::FlagBoot; - } - - if (fBme) - { - Adafruit_BME280::Measurements m = gBme.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. - gCatena.SafePrintf( - "BME280: T: %d P: %d RH: %d\n", - (int) m.Temperature, - (int) m.Pressure, - (int) m.Humidity - ); - b.putT(m.Temperature); - b.putP(m.Pressure); - b.putRH(m.Humidity); - - flag |= FlagsSensor2::FlagTPH; - } - - if (fLight) - { - /* Get a new sensor event */ - uint16_t data[3]; - - while (! gSi1133.isOneTimeReady()) - { - yield(); - } - - gSi1133.readMultiChannelData(data, 3); - gSi1133.stop(); - gCatena.SafePrintf( - "Si1133: %u IR, %u White, %u UV\n", - data[0], - data[1], - data[2] - ); - b.putLux(data[1]); - flag |= FlagsSensor2::FlagLux; - } - +void setup() + { + setup_platform(); + setup_printSignOn(); + + setup_flash(); + setup_measurement(); + setup_radio(); + setup_commands(); + setup_start(); + } - *pFlag = uint8_t(flag); - } +void setup_platform() + { + gCatena.begin(); -void startSendingUplink(void) + // if running unattended, don't wait for USB connect. + if (! (gCatena.GetOperatingFlags() & + static_cast(gCatena.OPERATING_FLAGS::fUnattended))) { - TxBuffer_t b; - LedPattern savedLed = gLed.Set(LedPattern::Measuring); - - fillBuffer(b); - if (savedLed != LedPattern::Joining) - gLed.Set(LedPattern::Sending); - else - gLed.Set(LedPattern::Joining); - - bool fConfirmed = false; - if (gCatena.GetOperatingFlags() & - static_cast(gCatena.OPERATING_FLAGS::fConfirmedUplink)) - { - gCatena.SafePrintf("requesting confirmed tx\n"); - fConfirmed = true; - } - - constexpr unsigned kUplinkPort = 1; - gLoRaWAN.SendBuffer(b.getbase(), b.getn(), sendBufferDoneCb, NULL, fConfirmed, kUplinkPort); + while (!Serial) + /* wait for USB attach */ + yield(); } + } -static void sendBufferDoneCb( - void *pContext, - bool fStatus - ) - { - osjobcb_t pFn; - - gLed.Set(LedPattern::Settling); - - pFn = settleDoneCb; - if (! fStatus) - { - if (!gLoRaWAN.IsProvisioned()) - { - // we'll talk about it at the callback. - pFn = txNotProvisionedCb; - - // but prevent join attempts now. - gLoRaWAN.Shutdown(); - } - else - gCatena.SafePrintf("send buffer failed\n"); - } - - os_setTimedCallback( - &sensorJob, - os_getTime()+sec2osticks(CATCFG_T_SETTLE), - pFn - ); - } +static constexpr const char *filebasename(const char *s) + { + const char *pName = s; -static void txNotProvisionedCb( - osjob_t *pSendJob - ) + for (auto p = s; *p != '\0'; ++p) { - gCatena.SafePrintf("LoRaWAN not provisioned yet. Use the commands to set it up.\n"); - gLoRaWAN.Shutdown(); - gLed.Set(LedPattern::NotProvisioned); + if (*p == '/' || *p == '\\') + pName = p + 1; } + return pName; + } -static void settleDoneCb( - osjob_t *pSendJob - ) - { - const bool fDeepSleep = checkDeepSleep(); - - if (uint32_t(millis()) > gRebootMs) - { - // time to reboot - NVIC_SystemReset(); - } - - if (! g_fPrintedSleeping) - doSleepAlert(fDeepSleep); - - /* count what we're up to */ - updateSleepCounters(); +void setup_printSignOn() + { + static const char dashes[] = "------------------------------------"; - if (fDeepSleep) - doDeepSleep(pSendJob); - else - doLightSleep(pSendJob); - } + gCatena.SafePrintf("\n%s%s\n", dashes, dashes); -bool checkDeepSleep(void) - { - bool const fDeepSleepTest = gCatena.GetOperatingFlags() & - static_cast(gCatena.OPERATING_FLAGS::fDeepSleepTest); - bool fDeepSleep; - - if (fDeepSleepTest) - { - fDeepSleep = true; - } -#ifdef USBCON - else if (Serial.dtr()) - { - fDeepSleep = false; - } -#endif - else if (gCatena.GetOperatingFlags() & - static_cast(gCatena.OPERATING_FLAGS::fDisableDeepSleep)) - { - fDeepSleep = false; - } - else if ((gCatena.GetOperatingFlags() & - static_cast(gCatena.OPERATING_FLAGS::fUnattended)) != 0) - { - fDeepSleep = true; - } - else - { - fDeepSleep = false; - } - - return fDeepSleep; - } + gCatena.SafePrintf("This is %s v%s\n", + filebasename(__FILE__), + sVersion + ); -void doSleepAlert(const bool fDeepSleep) + do { - g_fPrintedSleeping = true; - - if (fDeepSleep) - { - bool const fDeepSleepTest = gCatena.GetOperatingFlags() & - static_cast(gCatena.OPERATING_FLAGS::fDeepSleepTest); - const uint32_t deepSleepDelay = fDeepSleepTest ? 10 : 30; - - gCatena.SafePrintf("using deep sleep in %u secs" -#ifdef USBCON - " (USB will disconnect while asleep)" -#endif - ": ", - deepSleepDelay - ); - - // sleep and print - gLed.Set(LedPattern::TwoShort); - - for (auto n = deepSleepDelay; n > 0; --n) - { - uint32_t tNow = millis(); - - while (uint32_t(millis() - tNow) < 1000) - { - gCatena.poll(); - yield(); - } - gCatena.SafePrintf("."); - } - gCatena.SafePrintf("\nStarting deep sleep.\n"); - uint32_t tNow = millis(); - while (uint32_t(millis() - tNow) < 100) - { - gCatena.poll(); - yield(); - } - } - else - gCatena.SafePrintf("using light sleep\n"); - } - -void updateSleepCounters(void) - { - // update the sleep parameters - if (gTxCycleCount > 1) - { - // values greater than one are decremented and ultimately reset to default. - --gTxCycleCount; - } - else if (gTxCycleCount == 1) - { - // it's now one (otherwise we couldn't be here.) - gCatena.SafePrintf("resetting tx cycle to default: %u\n", CATCFG_T_CYCLE); - - gTxCycleCount = 0; - gTxCycle = CATCFG_T_CYCLE; - } - else - { - // it's zero. Leave it alone. - } - } - -void doDeepSleep(osjob_t *pJob) - { - bool const fDeepSleepTest = gCatena.GetOperatingFlags() & - static_cast(gCatena.OPERATING_FLAGS::fDeepSleepTest); - uint32_t const sleepInterval = CATCFG_GetInterval( - fDeepSleepTest ? CATCFG_T_CYCLE_TEST : gTxCycle + char sRegion[16]; + gCatena.SafePrintf("Target network: %s / %s\n", + gLoRaWAN.GetNetworkName(), + gLoRaWAN.GetRegionString(sRegion, sizeof(sRegion)) ); + } while (0); - /* ok... now it's time for a deep sleep */ - gLed.Set(LedPattern::Off); - deepSleepPrepare(); - - /* sleep */ - gCatena.Sleep(sleepInterval); + gCatena.SafePrintf("System clock rate is %u.%03u MHz\n", + ((unsigned)gCatena.GetSystemClockRate() / (1000*1000)), + ((unsigned)gCatena.GetSystemClockRate() / 1000 % 1000) + ); + gCatena.SafePrintf("Enter 'help' for a list of commands.\n"); + gCatena.SafePrintf("%s%s\n" "\n", dashes, dashes); - /* recover from sleep */ - deepSleepRecovery(); + Catena::UniqueID_string_t CpuIDstring; - /* and now... we're awake again. trigger another measurement */ - sleepDoneCb(pJob); - } + gCatena.SafePrintf( + "CPU Unique ID: %s\n", + gCatena.GetUniqueIDstring(&CpuIDstring) + ); + } -void deepSleepPrepare(void) +void setup_flash(void) + { + gSPI2.begin(); + if (gFlash.begin(&gSPI2, Catena::PIN_SPI2_FLASH_SS)) { - Serial.end(); - Wire.end(); - SPI.end(); - if (fFlash) - gSPI2.end(); + gMeasurementLoop.registerSecondSpi(&gSPI2); + gFlash.powerDown(); + gCatena.SafePrintf("FLASH found, put power down\n"); } - -void deepSleepRecovery(void) + else { - Serial.begin(); - Wire.begin(); - SPI.begin(); - if (fFlash) - gSPI2.begin(); + gFlash.end(); + gSPI2.end(); + gCatena.SafePrintf("No FLASH found: check hardware\n"); } + } -void doLightSleep(osjob_t *pJob) - { - uint32_t interval = sec2osticks(CATCFG_GetInterval(gTxCycle)); - - gLed.Set(LedPattern::Sleeping); - - if (gCatena.GetOperatingFlags() & - static_cast(gCatena.OPERATING_FLAGS::fQuickLightSleep)) - { - interval = 1; - } - - gLed.Set(LedPattern::Sleeping); - os_setTimedCallback( - &sensorJob, - os_getTime() + interval, - sleepDoneCb - ); - } +void setup_radio() + { + gLoRaWAN.begin(&gCatena); + gCatena.registerObject(&gLoRaWAN); + LMIC_setClockError(5 * MAX_CLOCK_ERROR / 100); + } -static void sleepDoneCb( - osjob_t *pJob - ) - { - gLed.Set(LedPattern::WarmingUp); +void setup_measurement() + { + gMeasurementLoop.begin(); + } - os_setTimedCallback( - &sensorJob, - os_getTime() + sec2osticks(CATCFG_T_WARMUP), - warmupDoneCb - ); - } +void setup_commands() + { + /* add our application-specific commands */ + gCatena.addCommands( + /* name of app dispatch table, passed by reference */ + sMyExtraCommands_top, + /* + || optionally a context pointer using static_cast(). + || normally only libraries (needing to be reentrant) need + || to use the context pointer. + */ + nullptr + ); + } -static void warmupDoneCb( - osjob_t *pJob - ) - { - startSendingUplink(); - } +void setup_start() + { + gMeasurementLoop.requestActive(true); + } -static void receiveMessage( - void *pContext, - uint8_t port, - const uint8_t *pMessage, - size_t nMessage - ) - { - unsigned txCycle; - unsigned txCount; - - if (port == 0) - { - gCatena.SafePrintf("MAC message:"); - for (unsigned i = 0; i < LMIC.dataBeg; ++i) - { - gCatena.SafePrintf(" %02x", LMIC.frame[i]); - } - gCatena.SafePrintf("\n"); - return; - } - - else if (! (port == 1 && 2 <= nMessage && nMessage <= 3)) - { - gCatena.SafePrintf("invalid message port(%02x)/length(%x)\n", - port, nMessage - ); - return; - } - - txCycle = (pMessage[0] << 8) | pMessage[1]; - - if (txCycle < CATCFG_T_MIN || txCycle > CATCFG_T_MAX) - { - gCatena.SafePrintf("tx cycle time out of range: %u\n", txCycle); - return; - } - - // byte [2], if present, is the repeat count. - // explicitly sending zero causes it to stick. - txCount = CATCFG_INTERVAL_COUNT; - if (nMessage >= 3) - { - txCount = pMessage[2]; - } - - setTxCycleTime(txCycle, txCount); - } +/****************************************************************************\ +| +| Loop +| +\****************************************************************************/ -void setTxCycleTime( - unsigned txCycle, - unsigned txCount - ) - { - if (txCount > 0) - gCatena.SafePrintf( - "message cycle time %u seconds for %u messages\n", - txCycle, txCount - ); - else - gCatena.SafePrintf( - "message cycle time %u seconds indefinitely\n", - txCycle - ); +void loop() + { + gCatena.poll(); + } - gTxCycle = txCycle; - gTxCycleCount = txCount; - } + diff --git a/cmdLog.cpp b/cmdLog.cpp new file mode 100644 index 0000000..b3b393a --- /dev/null +++ b/cmdLog.cpp @@ -0,0 +1,93 @@ +/* + +Module: cmdLog.cpp + +Function: + Process the "log" command + +Copyright and License: + This file copyright (C) 2021 by + + MCCI Corporation + 3520 Krums Corners Road + Ithaca, NY 14850 + + See accompanying LICENSE file for copyright and license information. + +Author: + Rama subbu, MCCI Corporation june 2021 + +*/ + +#include "Catena4610_cmd.h" + +#include + +using namespace McciCatena; + +/* + +Name: ::cmdLog() + +Function: + Command dispatcher for "log" command. + +Definition: + McciCatena::cCommandStream::CommandFn cmdLog; + + McciCatena::cCommandStream::CommandStatus cmdLog( + cCommandStream *pThis, + void *pContext, + int argc, + char **argv + ); + +Description: + The "log" command has the following syntax: + + log + Display the current log mask. + + log {number} + Set the log mask to {number} + +Returns: + cCommandStream::CommandStatus::kSuccess if successful. + Some other value for failure. + +*/ + +// argv[0] is "log" +// argv[1] is new log flag mask; if omitted, mask is printed +cCommandStream::CommandStatus cmdLog( + cCommandStream *pThis, + void *pContext, + int argc, + char **argv + ) + { + if (argc > 2) + return cCommandStream::CommandStatus::kInvalidParameter; + + if (argc == 1) + { + pThis->printf("log flags: %#x\n", gLog.getFlags()); + return cCommandStream::CommandStatus::kSuccess; + } + else + { + cCommandStream::CommandStatus status; + uint32_t newFlags; + + // get arg 1 as newFlags; default is irrelevant + status = cCommandStream::getuint32(argc, argv, 1, /*radix*/ 0, newFlags, /* default */ 0); + if (status == cCommandStream::CommandStatus::kSuccess) + { + cLog::DebugFlags const oldFlags = + gLog.setFlags(cLog::DebugFlags(newFlags)); + + pThis->printf("log flags: %#x -> %#x\n", oldFlags, newFlags); + } + return status; + } + } diff --git a/extras/FSM_Catena4610_Sensor.plantuml b/extras/FSM_Catena4610_Sensor.plantuml new file mode 100644 index 0000000..a315b82 --- /dev/null +++ b/extras/FSM_Catena4610_Sensor.plantuml @@ -0,0 +1,53 @@ +@startuml +/' + +Module: FSMexample.plantuml + +Function: + PlantUML reference source for cFSM state diagram. + +Copyright: + See accompanying LICENSE file at + https:://github.com/mcci-catena/Catena-Arduino-Platform + +Author: + Terry Moore, MCCI Corporation July 2019 + +Notes: + PlantUML images in REAMDE.md are generated by pasting this file into + the server at http://www.plantuml.com/plantuml, and grabbing the + resulting URLs. That has to be done several times, with different + values edited into the variables below. The comments in README.md + will tell you what's needed. + +'/ +[*] --> stInitial : stNoChange + +state stInitial +state stInactive +state stSleeping +state stWarmup +state stMeasure +state stTransmit +state stDownlink +state stFinal + +stInitial : Starting state +stInitial --> stInactive +stInactive : not doing anything +stInactive --> stWarmup +stWarmup : Get some data +stWarmup --> stMeasure +stMeasure : Take Measurements +stMeasure --> stTransmit +stTransmit : transmit data +stTransmit --> stFinal +stFinal : Terminate state +stTransmit --> stDownlink +stDownlink : Receive data +stDownlink --> stSleeping +stSleeping : Sleeping between measurements +stSleeping --> stInactive + +@enduml + \ No newline at end of file diff --git a/git-boot.sh b/git-boot.sh new file mode 100644 index 0000000..a61f61b --- /dev/null +++ b/git-boot.sh @@ -0,0 +1,329 @@ +#!/bin/bash + +############################################################################## +# +# Module: gitboot.sh +# +# Function: +# Install the libraries needed for building a given sketch. +# +# Copyright notice: +# This file copyright (C) 2017-2018 by +# +# MCCI Corporation +# 3520 Krums Corners Road +# Ithaca, NY 14850 +# +# Distributed under license. +# +# Author: +# Terry Moore, MCCI April 2017 +# +############################################################################## + +PNAME=$(basename "$0") +PDIR=$(dirname "$0") +OPTDEBUG=0 +OPTVERBOSE=0 + +############################################################################## +# verbose output +############################################################################## + +function _verbose { + if [ "$OPTVERBOSE" -ne 0 ]; then + echo "$PNAME:" "$@" 1>&2 + fi +} + +function _debug { + if [ "$OPTDEBUG" -ne 0 ]; then + echo "$@" 1>&2 + fi +} + +#### _error: define a function that will echo an error message to STDERR. +#### using "$@" ensures proper handling of quoting. +function _error { + echo "$@" 1>&2 +} + +#### _fatal: print an error message and then exit the script. +function _fatal { + _error "$@" ; exit 1 +} + +function _report { + echo "$1:" + echo "$2" | tr ' ' '\n' | column +} + + +############################################################################## +# LIBRARY_ROOT_DEFAULT: the path to the Arduino libraries on your +# system. If set in the environment, we use that; otherwise we set it +# according to the OS in use. +############################################################################## +UNAME=$(uname) +if [ X"$LIBRARY_ROOT_DEFAULT" != X ]; then + if [ ! -d "$LIBRARY_ROOT_DEFAULT" ]; then + _error "LIBRARY_ROOT_DEFAULT not a directory: ${LIBRARY_ROOT_DEFAULT}" + elif [ ! -x "$LIBRARY_ROOT_DEFAULT" ]; then + _error "LIBRARY_ROOT_DEFAULT not searchable: ${LIBRARY_ROOT_DEFAULT}" + elif [ ! -w "$LIBRARY_ROOT_DEFAULT" ]; then + _error "LIBRARY_ROOT_DEFAULT not writable: ${LIBRARY_ROOT_DEFAULT}" + fi +elif [ "$UNAME" = "Linux" ]; then + LIBRARY_ROOT_DEFAULT=~/Arduino/libraries +elif [ "$UNAME" = "Darwin" ]; then + LIBRARY_ROOT_DEFAULT=~/Documents/Arduino/libraries +elif [ "${UNAME:0:5}" = "MINGW" ]; then + LIBRARY_ROOT_DEFAULT=~/Documents/Arduino/libraries +else + echo "Can't detect OS: set LIBRARY_ROOT_DEFAULT to path to Arduino libraries" 1>&2 + exit 1 +fi + +############################################################################## +# Scan the options +############################################################################## + +LIBRARY_ROOT="${LIBRARY_ROOT_DEFAULT}" +USAGE="${PNAME} -[D g l* S T u v] [datafile...]; ${PNAME} -H for help" + +#OPTDEBUG and OPTVERBOSE are above +OPTDRYRUN=0 +OPTGITACCESS=0 +OPTUPDATE=0 +OPTSKIPBLOCKING=0 + +NEXTBOOL=1 +while getopts DgHl:nSTuv c +do + # postcondition: NEXTBOOL=0 iff previous option was -n + # in all other cases, NEXTBOOL=1 + if [ $NEXTBOOL -eq -1 ]; then + NEXTBOOL=0 + else + NEXTBOOL=1 + fi + + case "$c" in + D) OPTDEBUG=$NEXTBOOL;; + g) OPTGITACCESS=$NEXTBOOL;; + l) LIBRARY_ROOT="$OPTARG";; + n) NEXTBOOL=-1;; + S) OPTSKIPBLOCKING=$NEXTBOOL;; + T) OPTDRYRUN=$NEXTBOOL;; + u) OPTUPDATE=$NEXTBOOL;; + v) OPTVERBOSE=$NEXTBOOL;; + H) less 1>&2 <<. +Pull all the repos for this project from github. +Usage: + $USAGE +Switches: + -n negates the following option. + -D turn on debug mode; -nD is the default. + -g use git access method. -ng requests + https access method. Default is -ng. + -l {path} sets the target "arduino library path". + 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 + but don't do anything). + -u Do a git pull if repo already is found. + -nu just skips the repository if it already + exists. -nu is the default. + -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. + Repositories may be specified with access method in the form + https://github.com/orgname/repo.git (or similar). + Repositories may also be specified as + location path + Where location and path are separated by whitespace. In tihs gase, + the access method (git or https) is controlled by the -g flag. + Blank lines and comments beginning with '#' are ignored. +. + exit 0;; + \?) echo "$USAGE" 1>&2 + exit 1;; + esac +done + +#### get rid of scanned options #### +shift `expr $OPTIND - 1` + +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 +# convert two-field lines to the appropriate method, using https://$1/$2 or git@$1:$2, +# as determined by template. +LIBRARY_REPOS=$(sed -e 's/#.*$//' "$@" | LC_ALL=C sort -u | + awk -v OPTGITACCESS="$OPTGITACCESS" ' + (NF == 0) { next; } + (NF == 1) { + gsub(/\.git$/, ""); + printf("%s.git\n", $1); + } + (NF == 2) { + gsub(/\.git$/, "", $2); + if (OPTGITACCESS != 0) + printf("git@%s:%s.git\n", $1, $2); + else + printf("https://%s/%s.git\n", $1, $2); + } + ' | LC_ALL=C sort -u ) + +_debug "LIBRARY_REPOS: ${LIBRARY_REPOS}" + +#### make sure LIBRARY_ROOT is really a directory +if [ ! -d "$LIBRARY_ROOT" ]; then + _fatal "LIBRARY_ROOT: Can't find Arduino libraries:" "$LIBRARY_ROOT" +fi + +#### make sure we can cd to that directory +cd "$LIBRARY_ROOT" || _fatal "can't cd:" "$LIBRARY_ROOT" + +#### keep track of successes in CLONED_REPOS, failures in NG_REPOS, +#### and skipped repos in SKIPPED_REPOS +CLONED_REPOS= #empty +NG_REPOS= #empty +SKIPPED_REPOS= #empty +PULLED_REPOS= #empty + +#### if OPTUPDATE is zero, refuse to run if there are already any libraries +#### with the target names +if [ $OPTUPDATE -eq 0 -a $OPTSKIPBLOCKING -eq 0 ]; then + BLOCKING_REPOS= + for r in $LIBRARY_REPOS ; do + + # given "https://github.com/something/somerepo.git", + # set rname to "somerepo" + rname=$(basename $r .git) + + # + # if there already is a target Arduino library of that name, add to the + # list of blocking libraries + # + if [ -d $rname ]; then + BLOCKING_REPOS="${BLOCKING_REPOS}${BLOCKING_REPOS:+ }$rname" + fi + done + if [ X"$BLOCKING_REPOS" != X ]; then + echo + _report "The following repos already exist in $LIBRARY_ROOT" \ + "${BLOCKING_REPOS}" + echo + _fatal "Check for conficts, or use -u to update or -S to skip blocking repos" + fi +fi + +#### scan through each of the libraries. Don't quote LIBRARY_REPOS +#### because we want bash to split it into words. +for r in $LIBRARY_REPOS ; do + # given "https://github.com/something/somerepo.git", set rname to "somerepo" + rname=$(basename $r .git) + + # + # if there already is a target Arduino library of that name, + # skip the download, or pull + # + if [ -d $rname ]; then + if [ $OPTUPDATE -eq 0 ]; then + echo "repo $r already exists as $rname, and -u not specfied" + SKIPPED_REPOS="${SKIPPED_REPOS}${SKIPPED_REPOS:+ }$rname" + else + if [ "$OPTDRYRUN" -ne 0 ]; then + _verbose Dry-run: skipping git pull "$r" + elif ( cd $rname && _verbose $rname: && git pull ; ); then + # add to the list; ${PULLED_REPOS:+ } + # inserts a space after each repo (but nothing + # if PULLED_REPOS is empty) + PULLED_REPOS="${PULLED_REPOS}${PULLED_REPOS:+ }$rname" + else + # error; print message and remember. + _error "Can't pull $r to $rname" + NG_REPOS="${NG_REPOS}${NG_REPOS:+ }$rname" + fi + fi + # + # otherwise try to clone the repo. + # + else + # clone the repo, and record results. + if [ "$OPTDRYRUN" -ne 0 ]; then + _verbose Dry-run: skipping git clone "$r" + elif git clone "$r" ; then + # add to the list; ${CLONED_REPOS:+ } + # inserts a space after each repo (but nothing + # if CLONED_REPOS is empty) + CLONED_REPOS="${CLONED_REPOS}${CLONED_REPOS:+ }$rname" + else + # error; print message and remember. + _error "Can't clone $r to $rname" + NG_REPOS="${NG_REPOS}${NG_REPOS:+ }$rname" + fi + fi +done + +#### print final messages +echo +echo "==== Summary =====" +if [ -z "${NG_REPOS}" ]; then + echo "*** No repos with errors ***" +else + _report "Repos with errors" "${NG_REPOS}" +fi +echo + +if [ -z "${SKIPPED_REPOS}" ]; then + echo "*** No existing repos skipped ***" +else + _report "Existing repos skipped" "${SKIPPED_REPOS}" +fi +echo + +if [ -z "${PULLED_REPOS}" ]; then + echo "*** No existing repos were updated ***" +else + _report "Existing repos updated" "${PULLED_REPOS}" +fi +echo + +if [ -z "${CLONED_REPOS}" ]; then + echo "*** No new repos were cloned ***" +else + _report "New repos cloned" "${CLONED_REPOS}" +fi +echo + +if [ -z "${NG_REPOS}" ]; then + exit 1 +else + exit 0 +fi \ No newline at end of file