frequent tx version

frequent tx version

error: pathspec 'Arduino/hardware/V0p2_Main_PCB_REV7_DORM1_and_REV8/REV7RadValveFreqTX/REV7RadValveFreqTX.ino' did not match any file(s) known to git

--- Arduino/hardware/V0p2_Main_PCB_REV7_DORM1_and_REV8/REV7RadValveFreqTX/REV7RadValveFreqTX.ino
+++ Arduino/hardware/V0p2_Main_PCB_REV7_DORM1_and_REV8/REV7RadValveFreqTX/REV7RadValveFreqTX.ino
@@ -0,0 +1,921 @@
+/*
+The OpenTRV project licenses this file to you
+under the Apache Licence, Version 2.0 (the "Licence");
+you may not use this file except in compliance
+with the Licence. You may obtain a copy of the Licence at
+
+http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing,
+software distributed under the Licence is distributed on an
+"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+KIND, either express or implied. See the Licence for the
+specific language governing permissions and limitations
+under the Licence.
+
+Author(s) / Copyright (s): Damon Hart-Davis 2013--2017
+                           Deniz Erbilgin 2015--2017
+*/
+
+/******************************************************************************
+ * NOTES
+ *****************************************************************************/
+/*
+  V0p2 (V0.2) with frequent (every 2 secs) TX.
+
+  DHD20130417: hardware setup on bare board.
+    * 1MHz CPU clock (from 8MHz internal RC clock with /8 prescaler) ATmega328P running at 1.8V--5V (typically 2V--3.3V).
+    * Fuse set for BOD-managed additional clock settle time, ie as fast a restart from sleep as possible.
+    * All unused pins unconnected and nominally floating (though driven low as output where possible).
+    * 32768Hz xtal between pins XTAL1 and XTAL2, async timer 2, for accurate timekeeping and low-power sleep.
+    * All unused system modules turned off.
+
+  Basic AVR power consumption ticking an (empty) control loop at ~0.5Hz should be ~1uA.
+ */
+
+
+/******************************************************************************
+ * INCLUDES
+ *****************************************************************************/
+////// GLOBAL flags that alter system build and behaviour.
+
+// If defined, do extra checks and serial logging.  Will take more code space and power.
+#undef DEBUG
+
+// *** Global flag for REVx configuration here *** //
+#define V0p2_REV 7
+
+// --------------------------------------------
+// I/O pin allocation and setup: include ahead of I/O module headers.
+#include <OTV0p2_Board_IO_Config.h>
+
+
+////// MAIN LIBRARIES
+
+#include <Arduino.h>
+#include <OTV0p2Base.h>
+#include <OTRadioLink.h>
+
+
+////// ADDITIONAL LIBRARIES/PERIPHERALS
+
+#include <OTRadValve.h>
+#include <OTRFM23BLink.h>
+#include <OTAESGCM.h>
+
+
+////// ADDITIONAL USER HEADERS
+
+
+/******************************************************************************
+ * GENERAL CONSTANTS AND VARIABLES
+ * Core constants and variables required for V0p2 loop.
+ *****************************************************************************/
+// Controller's notion/cache of seconds within major cycle.
+static uint_fast8_t TIME_LSD;
+
+// 'Elapsed minutes' count of minute/major cycles; cheaper than accessing RTC and not tied to real time.
+// Starts at or just above zero (within the first 4-minute cycle) to help avoid collisions between units after mass power-up.
+// Wraps at its maximum (0xff) value.
+static uint8_t minuteCount;
+
+// Controller's view of Least Significant Digits of the current (local) time, in this case whole seconds.
+// TIME_LSD ranges from 0 to TIME_CYCLE_S-1, also major cycle length.
+constexpr uint_fast8_t TIME_CYCLE_S = 60;
+
+// Period in minutes for simple learned on-time; strictly positive (and less than 256).
+static constexpr uint8_t LEARNED_ON_PERIOD_M = 60;
+// Period in minutes for simple learned on-time with comfort bias; strictly positive (and less than 256).
+// Defaults to twice LEARNED_ON_PERIOD_M.
+// Should be no shorter/less than LEARNED_ON_PERIOD_M to avoid confusion.
+static constexpr uint8_t LEARNED_ON_PERIOD_COMFORT_M = (OTV0P2BASE::fnmin(2*(LEARNED_ON_PERIOD_M),255));
+
+// Send the underlying stats binary/text 'whitened' message.
+// This must be terminated with an 0xff (which is not sent),
+// and no longer than STATS_MSG_MAX_LEN bytes long in total (excluding the terminating 0xff).
+// This must not contain any 0xff and should not contain long runs of 0x00 bytes.
+// The message to be sent must be written at an offset of STATS_MSG_START_OFFSET from the start of the buffer.
+// This routine will alter the content of the buffer for transmission,
+// and the buffer should not be re-used as is.
+//   * doubleTX  double TX to increase chance of successful reception
+//   * RFM23BfriendlyPremable  if true then add an extra preamble
+//     to allow RFM23B-based receiver to RX this
+// This will use whichever transmission medium/carrier/etc is available.
+static constexpr uint8_t RFM22_PREAMBLE_BYTES = 5; // Recommended number of preamble bytes for reliable reception.
+static constexpr uint8_t RFM22_SYNC_MIN_BYTES = 3; // Minimum number of sync bytes.
+static constexpr uint8_t STATS_MSG_START_OFFSET = (RFM22_PREAMBLE_BYTES + RFM22_SYNC_MIN_BYTES);
+static constexpr uint8_t STATS_MSG_MAX_LEN = (64 - STATS_MSG_START_OFFSET);
+
+// Mask for Port D input change interrupts.
+constexpr uint8_t SERIALRX_INT_MASK = 0b00000001; // Serial RX
+#if BUTTON_MODE_L > 7
+  #error BUTTON_MODE_L expected to be on port D
+#endif
+constexpr uint8_t MODE_INT_MASK = (1 << (BUTTON_MODE_L&7));
+constexpr uint8_t MASK_PD = (SERIALRX_INT_MASK | MODE_INT_MASK); // MODE button interrupt (et al).
+
+// Counter containing current no. of resets.
+static uint8_t resetCount;
+
+////// SCRATCHSPACE
+
+// Create scratch space for secure stats TX
+// Buffer need be no larger than leading length byte + typical 64-byte radio module TX buffer limit + optional terminator.
+constexpr uint8_t MSG_BUF_SIZE = 1 + 64 + 1;
+constexpr uint8_t bufEncJSONlen = OTRadioLink::ENC_BODY_SMALL_FIXED_PTEXT_MAX_SIZE + 1;
+constexpr uint8_t ptextBuflen = bufEncJSONlen + 2;
+static_assert(ptextBuflen == 34, "ptextBuflen wrong");
+constexpr uint8_t scratchSpaceNeeded = MSG_BUF_SIZE + ptextBuflen;
+constexpr size_t StatsTX_WorkspaceSize = OTRadioLink::SimpleSecureFrame32or0BodyTXBase::encodeValveFrame_total_scratch_usage_OTAESGCM_2p0 + scratchSpaceNeeded;
+static_assert(StatsTX_WorkspaceSize == 384, "StatsTX workspace size wrong!");  // Correct as of 20170704
+
+// Create a scratchspace for CLI
+// Suggested minimum buffer size for pollUI() to ensure maximum-sized commands can be received.
+static constexpr uint8_t MAXIMUM_CLI_RESPONSE_CHARS = 1 + OTV0P2BASE::CLI::MAX_TYPICAL_CLI_BUFFER;
+static constexpr uint8_t PollUI_WorkspaceSize = 1 + MAXIMUM_CLI_RESPONSE_CHARS;
+
+// Create workspace
+union GlobalWorkSpace {
+    uint8_t statsTX[StatsTX_WorkspaceSize];
+    uint8_t cli[PollUI_WorkspaceSize];
+};
+static GlobalWorkSpace globalWorkSpace;
+
+
+/******************************************************************************
+ * PERIPHERALS
+ * Peripheral drivers and config
+ *****************************************************************************/
+
+////// FORWARD DECLARATIONS
+
+// Valve physical UI controller.
+typedef OTRadValve::ModeButtonAndPotActuatorPhysicalUI valveUI_t;
+extern valveUI_t valveUI;  // Extern as otherwise compiler attempts to instantiate.
+
+
+////// RADIOS
+
+// Config
+// Nodes talking on fast GFSK channel 0.
+static constexpr uint8_t nPrimaryRadioChannels = 1;
+static const OTRadioLink::OTRadioChannelConfig RFM23BConfigs[nPrimaryRadioChannels] = {
+    // GFSK channel 0 full config, RX/TX, not in itself secure.
+    OTRadioLink::OTRadioChannelConfig(OTRFM23BLink::StandardRegSettingsGFSK57600, true), };
+
+// Brings in necessary radio libs.
+static constexpr uint8_t RFM23B_RX_QUEUE_SIZE = OTV0P2BASE::fnmax(uint8_t(2), uint8_t(OTRFM23BLink::DEFAULT_RFM23B_RX_QUEUE_CAPACITY)) - 1;
+static constexpr int8_t RFM23B_IRQ_PIN = PIN_RFM_NIRQ;
+static constexpr bool RFM23B_allowRX = false;
+OTRFM23BLink::OTRFM23BLink<OTV0P2BASE::V0p2_PIN_SPI_nSS, RFM23B_IRQ_PIN, RFM23B_RX_QUEUE_SIZE, RFM23B_allowRX> PrimaryRadio;
+
+////// SENSORS
+
+// Sensor for supply (eg battery) voltage in millivolts.
+OTV0P2BASE::SupplyVoltageCentiVolts Supply_cV;
+
+// Support for general timed and multi-input occupancy detection / use.
+typedef OTV0P2BASE::PseudoSensorOccupancyTracker OccupancyTracker;
+// Singleton implementation for entire node.
+OccupancyTracker Occupancy;
+
+// Sensor for temperature potentiometer/dial UI control.
+typedef OTV0P2BASE::SensorTemperaturePot
+    <
+    decltype(Occupancy), &Occupancy,
+    48, 296, // Correct for DORM1/TRV1 with embedded REV7.
+    false // REV7 does not drive pot from IO_POWER_UP.
+    > TempPot_t;
+TempPot_t TempPot;
+
+// Sensor for ambient light level; 0 is dark, 255 is bright.
+typedef OTV0P2BASE::SensorAmbientLight AmbientLight;
+AmbientLight AmbLight;
+
+// Singleton implementation/instance.
+typedef OTV0P2BASE::HumiditySensorSHT21 RelHumidity_t;
+// Singleton implementation/instance.
+OTV0P2BASE::HumiditySensorSHT21 RelHumidity;
+
+// Ambient/room temperature sensor, usually on main board.
+OTV0P2BASE::RoomTemperatureC16_SHT21 TemperatureC16; // SHT21 impl.
+
+
+////// ACTUATORS
+
+// DORM1/REV7 direct drive motor actuator.
+static constexpr bool binaryOnlyValveControl = false;
+static constexpr uint8_t m1 = MOTOR_DRIVE_ML;
+static constexpr uint8_t m2 = MOTOR_DRIVE_MR;
+typedef OTRadValve::ValveMotorDirectV1<OTRadValve::ValveMotorDirectV1HardwareDriver, m1, m2, MOTOR_DRIVE_MI_AIN, MOTOR_DRIVE_MC_AIN, OTRadValve::MOTOR_DRIVE_NSLEEP_UNUSED, decltype(Supply_cV), &Supply_cV> ValveDirect_t;
+// DORM1/REV7 direct drive actuator.
+// Singleton implementation/instance.
+// Suppress unnecessary activity when room dark, eg to avoid disturbance if device crashes/restarts,
+// unless recent UI use because value is being fitted/adjusted.
+ValveDirect_t ValveDirect([](){return((!valveUI.veryRecentUIControlUse()) && AmbLight.isRoomDark());});
+
+
+////// CONTROL
+
+// Singleton non-volatile stats store instance.
+OTV0P2BASE::EEPROMByHourByteStats eeStats;
+
+// Singleton stats-updater object.
+typedef
+    OTV0P2BASE::ByHourSimpleStatsUpdaterSampleStats <
+      decltype(eeStats), &eeStats,
+      decltype(Occupancy), &Occupancy,
+      decltype(AmbLight), &AmbLight,
+      decltype(TemperatureC16), &TemperatureC16,
+      decltype(RelHumidity), &RelHumidity,
+      2
+      > StatsU_t;
+// Stats updater singleton.
+StatsU_t statsU;
+
+// Singleton scheduler instance.
+// Dummy scheduler to simplify coding.
+typedef OTRadValve::NULLValveSchedule Scheduler_t;
+Scheduler_t Scheduler;
+
+// Radiator valve mode (FROST, WARM, BAKE).
+OTRadValve::ValveMode valveMode;
+
+// Settings for room TRV.
+typedef OTRadValve::DEFAULT_ValveControlParameters PARAMS;
+// Choose which subtype to use depending on enabled settings and board type.
+typedef OTRadValve::TempControlTempPot<decltype(TempPot), &TempPot, PARAMS, decltype(RelHumidity), &RelHumidity> TempControl_t;
+// Temperature control object.
+TempControl_t tempControl;
+
+static OTV0P2BASE::EEPROMByHourByteStats ebhs;
+
+// Create setback lockout if needed.
+typedef bool(*setbackLockout_t)();
+// If allowing setback lockout, eg for testing, then inject suitable lambda.
+static bool setbackLockout() { return(0 != OTRadValve::getSetbackLockout()); }
+
+// Algorithm for computing target temperature.
+constexpr OTRadValve::ModelledRadValveComputeTargetTempBasic<
+  PARAMS,
+  &valveMode,
+  decltype(TemperatureC16),   &TemperatureC16,
+  decltype(tempControl),      &tempControl,
+  decltype(Occupancy),        &Occupancy,
+  decltype(AmbLight),         &AmbLight,
+  decltype(valveUI),          &valveUI,
+  decltype(Scheduler),        &Scheduler,
+  decltype(ebhs),             &ebhs,
+  decltype(RelHumidity),      &RelHumidity,
+  setbackLockout>
+  cttBasic;
+
+// Internal model of controlled radiator valve position.
+OTRadValve::ModelledRadValve NominalRadValve(
+  &cttBasic,
+  &valveMode,
+  &tempControl,
+  &ValveDirect,
+    false,
+    100);
+
+// Valve physical UI controller.
+valveUI_t valveUI(
+  &valveMode,
+  &tempControl,
+  &NominalRadValve,
+  &Occupancy,
+  &AmbLight,
+  &TempPot,
+  &Supply_cV,
+  OTV0P2BASE::LED_HEATCALL_ON,
+  OTV0P2BASE::LED_HEATCALL_OFF,
+  OTV0P2BASE::LED_HEATCALL_ON_ISR_SAFE);
+
+////// MESSAGING
+
+// Managed JSON stats.
+OTV0P2BASE::SimpleStatsRotation<13> ss1; // Configured for maximum different stats.
+
+// Error reporting
+extern OTV0P2BASE::ErrorReport OTV0P2BASE::ErrorReporter;
+
+
+/******************************************************************************
+ * INTERRUPT SERVICE ROUTINES
+ *****************************************************************************/
+
+// Previous state of port D pins to help detect changes.
+static volatile uint8_t prevStatePD;
+// Interrupt service routine for PD I/O port transition changes (including RX).
+ISR(PCINT2_vect)
+{
+    const uint8_t pins = PIND;
+    const uint8_t changes = pins ^ prevStatePD;
+    prevStatePD = pins;
+    // Mode button detection is on the falling edge (button pressed).
+    if((changes & MODE_INT_MASK) && !(pins & MODE_INT_MASK)) { valveUI.startBakeFromInt(); }
+    // If an interrupt arrived from the serial RX then wake up the CLI.
+    // Use a nominally rising edge to avoid spurious trigger when other interrupts are handled.
+    // The will ensure that it is possible to wake the CLI subsystem with an extra CR or LF.
+    // It is OK to trigger this from other things such as button presses.
+    // TODO: ensure that resetCLIActiveTimer() is inlineable to minimise ISR prologue/epilogue time and space.
+    if((changes & SERIALRX_INT_MASK) && !(pins & SERIALRX_INT_MASK)) { OTV0P2BASE::CLI::resetCLIActiveTimer(); }
+}
+
+
+/******************************************************************************
+ * LOCAL FUNCTIONS
+ *****************************************************************************/
+
+////// PANIC
+
+// Indicate that the system is broken in an obvious way (distress flashing the main LED).
+// DOES NOT RETURN.
+// Tries to turn off most stuff safely that will benefit from doing so, but nothing too complex.
+// Tries not to use lots of energy so as to keep distress beacon running for a while.
+void panic() {
+    // Reset radio and go into low-power mode.
+    PrimaryRadio.panicShutdown();
+    // Power down almost everything else...
+    OTV0P2BASE::minimisePowerWithoutSleep();
+    pinMode(OTV0P2BASE::LED_HEATCALL_L, OUTPUT);
+    for( ; ; ) {
+        OTV0P2BASE::LED_HEATCALL_ON();
+        OTV0P2BASE::nap(WDTO_15MS);
+        OTV0P2BASE::LED_HEATCALL_OFF();
+        OTV0P2BASE::nap(WDTO_120MS);
+    }
+}
+
+// Panic with fixed message.
+void panic(const __FlashStringHelper *s) {
+    OTV0P2BASE::serialPrintlnAndFlush(); // Start new line to highlight error.  // May fail.
+    OTV0P2BASE::serialPrintAndFlush('!'); // Indicate error with leading '!' // May fail.
+    OTV0P2BASE::serialPrintlnAndFlush(s); // Print supplied detail text. // May fail.
+    panic();
+}
+
+////// SENSORS
+
+// Update sensors with historic/trailing statistics information where needed.
+// Should be called at least hourly after all stats have been updated,
+// but can also be called whenever the user adjusts settings for example.
+void updateSensorsFromStats() {
+    // Update with rolling stats to adapt to sensors and local environment...
+    // ...and prevailing bias, so may take a while to adjust.
+    AmbLight.setTypMinMax(
+          eeStats.getByHourStatRTC(OTV0P2BASE::NVByHourByteStatsBase::STATS_SET_AMBLIGHT_BY_HOUR_SMOOTHED),
+          eeStats.getMinByHourStat(OTV0P2BASE::NVByHourByteStatsBase::STATS_SET_AMBLIGHT_BY_HOUR_SMOOTHED),
+          eeStats.getMaxByHourStat(OTV0P2BASE::NVByHourByteStatsBase::STATS_SET_AMBLIGHT_BY_HOUR_SMOOTHED),
+          !tempControl.hasEcoBias());
+}
+
+
+////// CONTROL
+
+// Call this to do an I/O poll if needed; returns true if something useful definitely happened.
+// This call should typically take << 1ms at 1MHz CPU.
+// Does not change CPU clock speeds, mess with interrupts (other than possible brief blocking), or sleep.
+// Should also do nothing that interacts with Serial.
+// Limits actual poll rate to something like once every 8ms, unless force is true.
+//   * force if true then force full poll on every call (ie do not internally rate-limit)
+// Note that radio poll() can be for TX as well as RX activity.
+// Not thread-safe, eg not to be called from within an ISR.
+bool pollIO(const bool force = false) {
+    static volatile uint8_t _pO_lastPoll;
+    // Poll RX at most about every ~8ms.
+    const uint8_t sct = OTV0P2BASE::getSubCycleTime();
+    if(force || (sct != _pO_lastPoll)) {
+        _pO_lastPoll = sct;
+        // Poll for inbound frames.
+        // If RX is not interrupt-driven then
+        // there will usually be little time to do this
+        // before getting an RX overrun or dropped frame.
+        PrimaryRadio.poll();
+    }
+    return(false);
+}
+
+// Run tasks needed at the end of each day (nominal midnight).
+// Should be run once at a fixed slot in the last minute of the last hour of each day.
+// Will be run after all stats for the current hour have been updated.
+static void endOfDayTasks() {
+    // Count down the setback lockout if not finished...  (TODO-786, TODO-906)
+    OTRadValve::countDownSetbackLockout();
+}
+
+
+////// MESSAGING
+
+// Do bare stats transmission.
+// Output should be filtered for items appropriate
+// to current channel security and sensitivity level.
+// This may be binary or JSON format.
+//   * allowDoubleTX  allow double TX to increase chance of successful reception
+// Sends stats on primary radio channel 0 with possible duplicate to secondary channel.
+// If sending encrypted then ID/counter fields (eg @ and + for JSON) are omitted
+// as assumed supplied by security layer to remote recipent.
+void bareStatsTX() {
+
+    // Capture heavy stack usage from local allocations here.
+    OTV0P2BASE::MemoryChecks::recordIfMinSP();
+    const bool neededWaking = OTV0P2BASE::powerUpSerialIfDisabled<>();
+    static_assert(OTV0P2BASE::FullStatsMessageCore_MAX_BYTES_ON_WIRE <= STATS_MSG_MAX_LEN, "FullStatsMessageCore_MAX_BYTES_ON_WIRE too big");
+    static_assert(OTV0P2BASE::MSG_JSON_MAX_LENGTH+1 <= STATS_MSG_MAX_LEN, "MSG_JSON_MAX_LENGTH too big"); // Allow 1 for trailing CRC.
+    OTV0P2BASE::ScratchSpaceL sW(globalWorkSpace.statsTX, sizeof(globalWorkSpace.statsTX));
+
+    bool sendingJSONFailed = false; // Set true and stop attempting JSON send in case of error.
+
+    uint8_t *const buf = sW.buf;
+    // Leading length offset in bytes
+    // When sending on a channel with framing, do not explicitly send the frame length byte.
+    constexpr uint8_t offset = 1;
+    // Where to write the real frame content, leaving space for leading
+    // frame-length byte for encrypted frame.
+    uint8_t *const realTXFrameStart = buf + offset;
+    // If forcing encryption or if unconditionally suppressed
+    // then suppress the "@" ID field entirely,
+    // assuming that the encrypted commands will carry the ID, ie in the 'envelope'.
+    ss1.setID(V0p2_SENSOR_TAG_F(""));
+    // Managed JSON stats.
+    // Enable "+" count field for diagnostic purposes, eg while TX is lossy,
+    // if the primary radio channel does not include a sequence number itself.
+    // Assume that an encrypted channel will provide its own (visible) sequence counter.
+    ss1.enableCount(false);  // Always encrypted
+    ss1.put(TemperatureC16);
+    ss1.put(RelHumidity);
+    ss1.put(Occupancy.twoBitTag(), Occupancy.twoBitOccupancyValue()); // Reduce spurious TX cf percentage.
+    ss1.put(Occupancy.vacHSubSensor);
+    // OPTIONAL items
+    // Only TX supply voltage for units apparently not mains powered, and TX with low priority as slow changing.
+    ss1.put(Supply_cV, true);
+    ss1.put(AmbLight); // Always send ambient light level (assuming sensor is present).
+    // Show TRV-related stats since enabled.
+    ss1.put(NominalRadValve); // Show modelled value to be able to deduce call-for-heat.
+    ss1.put(NominalRadValve.targetTemperatureSubSensor);
+    ss1.put(NominalRadValve.setbackSubSensor);
+    ss1.put(NominalRadValve.cumulativeMovementSubSensor);
+    // Show state of setback lockout.
+    ss1.put(V0p2_SENSOR_TAG_F("gE"), OTRadValve::getSetbackLockout(), true);
+
+    // Show reset counter. Low priority.
+    ss1.put(V0p2_SENSOR_TAG_F("R"), resetCount, true);
+    // Send error reports.
+    ss1.put(OTV0P2BASE::ErrorReporter, true);
+
+    const uint8_t privacyLevel = OTV0P2BASE::stTXalwaysAll;
+    // Redirect JSON output appropriately.
+    // Part of sW, directly after the message buffer.
+    // |    0    |     1    | 2 |  3:n | n+1 | n+2 | n is the end of the stats message. n+2 <= 34
+    // | valvePC | hasStats | { | json | '}' | 0x0 |
+    // ptextBuf is the entire frame
+    uint8_t * const ptextBuf = sW.buf + MSG_BUF_SIZE;
+    // Redirect JSON output appropriately.
+    uint8_t *const bufJSON = ptextBuf + 2;
+    const uint8_t bufJSONlen = bufEncJSONlen;
+    // Number of bytes written for body.
+    // For non-secure, this is the size of the JSON text.
+    // For secure this is overridden with the secure frame size.
+    int8_t wrote = 0;
+    // Generate JSON text.
+    if(!sendingJSONFailed) {
+        // Generate JSON and write to appropriate buffer:
+        // direct to TX buffer if not encrypting, else to separate buffer.
+        wrote = ss1.writeJSON(bufJSON, bufJSONlen, privacyLevel, true); // make best use of JSON stats;
+        if(0 == wrote) { sendingJSONFailed = true; }
+    }
+    // Push the JSON output to Serial.
+    if(!sendingJSONFailed) {
+        // Insert synthetic full ID/@ field for local stats, but no sequence number for now.
+        Serial.print(F("{\"@\":\""));
+        for(int i = 0; i < OTV0P2BASE::OpenTRV_Node_ID_Bytes; ++i) { Serial.print(eeprom_read_byte((uint8_t *)V0P2BASE_EE_START_ID+i), HEX); }
+        Serial.print(F("\","));
+        Serial.write(bufJSON+1, wrote-1);
+        Serial.println();
+        OTV0P2BASE::flushSerialSCTSensitive(); // Ensure all flushed since system clock may be messed with...
+    }
+    // Get the 'building' key for stats sending.
+    uint8_t key[16];
+    if(!sendingJSONFailed) {
+        if(!OTV0P2BASE::getPrimaryBuilding16ByteSecretKey(key)) {
+            sendingJSONFailed = true;
+            OTV0P2BASE::serialPrintlnAndFlush(F("!TX key")); // Know why TX failed.
+        }
+    }
+    // If doing encryption
+    // then build encrypted frame from raw JSON.
+    if(!sendingJSONFailed) {
+        // Explicit-workspace version of encryption.
+        const OTRadioLink::SimpleSecureFrame32or0BodyTXBase::fixed32BTextSize12BNonce16BTagSimpleEnc_fn_t &eW = OTAESGCM::fixed32BTextSize12BNonce16BTagSimpleEnc_DEFAULT_WITH_LWORKSPACE;
+        // Create subscratch space for encryption functions
+        OTV0P2BASE::ScratchSpaceL subScratch(sW, scratchSpaceNeeded);
+        constexpr uint8_t txIDLen = OTRadioLink::ENC_BODY_DEFAULT_ID_BYTES;
+
+        // Struct to pass data into encode function.
+        OTRadioLink::OTEncodeData_T fd(
+            ptextBuf,
+            ptextBuflen,
+            buf,            // (realTXFrameStart - offset),
+            MSG_BUF_SIZE);  // (MSG_BUF_SIZE - (realTXFrameStart-buf) + offset));
+
+        // Assumed to be at least one free writeable byte ahead of bptr.
+        // Get current modelled valve position.
+        const uint8_t valvePC = NominalRadValve.get();
+        const uint8_t bodylen = OTRadioLink::SimpleSecureFrame32or0BodyTXV0p2::getInstance().encodeValveFrame(
+                                    fd,
+                                    txIDLen,
+                                    valvePC,
+                                    eW,
+                                    subScratch,
+                                    key);
+        sendingJSONFailed = (0 == bodylen);
+        wrote = bodylen - offset;
+        // Serial.println(wrote);
+        // for(auto ip = fd.outbuf; ip != fd.outbuf+fd.outbufSize; ++ip) { Serial.print(*ip, HEX); Serial.print(F(" "));}
+        // Serial.println();
+
+    }
+    // Send directly to the primary radio...
+    if((!sendingJSONFailed) && (!PrimaryRadio.queueToSend(realTXFrameStart, wrote))) { sendingJSONFailed = true; }
+    if(neededWaking) { OTV0P2BASE::flushSerialProductive(); OTV0P2BASE::powerDownSerial(); }
+}
+
+
+////// UI
+
+// Dump some brief CLI usage instructions to serial TX, which must be up and running.
+// If this gets too big there is a risk of overrunning and missing the next tick...
+static void dumpCLIUsage()
+{
+    OTV0P2BASE::CLI::InvalidIgnored(); // Minimal placeholder.
+    Serial.println();
+}
+
+// Used to poll user side for CLI input until specified sub-cycle time.
+// Commands should be sent terminated by CR *or* LF; both may prevent 'E' (exit) from working properly.
+// A period of less than (say) 500ms will be difficult for direct human response on a raw terminal.
+// A period of less than (say) 100ms is not recommended to avoid possibility of overrun on long interactions.
+// Times itself out after at least a minute or two of inactivity.
+// NOT RENTRANT (eg uses static state for speed and code space).
+
+void pollCLI(const uint8_t maxSCT, const bool startOfMinute, const OTV0P2BASE::ScratchSpace &s)
+{
+    // Perform any once-per-minute operations.
+    if(startOfMinute) { OTV0P2BASE::CLI::countDownCLI(); }
+    const bool neededWaking = OTV0P2BASE::powerUpSerialIfDisabled<V0P2_UART_BAUD>();
+    // Wait for input command line from the user (received characters may already have been queued)...
+    // Read a line up to a terminating CR, either on its own or as part of CRLF.
+    // (Note that command content and timing may be useful to fold into PRNG entropy pool.)
+    // A static buffer generates better code but permanently consumes previous SRAM.
+    const uint8_t n = OTV0P2BASE::CLI::promptAndReadCommandLine(maxSCT, s, [](){pollIO();});
+    char *buf = (char *)s.buf;
+    if(n > 0) {
+        // Got plausible input so keep the CLI awake a little longer.
+        OTV0P2BASE::CLI::resetCLIActiveTimer();
+        // Process the input received, with action based on the first char...
+        switch(buf[0]) {
+            // Explicit request for help, or unrecognised first character.
+            // Avoid showing status as may already be rather a lot of output.
+            default: case '?': { dumpCLIUsage(); break; }
+            // Exit/deactivate CLI immediately.
+            // This should be followed by JUST CR ('\r') OR LF ('\n')
+            // else the second will wake the CLI up again.
+            case 'E': { OTV0P2BASE::CLI::makeCLIInactive(); break; }
+            // Show/set generic parameter values (eg "G N [M]").
+            case 'G': { OTV0P2BASE::CLI::GenericParam().doCommand(buf, n); break; }
+            // Reset or display ID.
+            case 'I': { OTV0P2BASE::CLI::NodeIDWithSet().doCommand(buf, n); break; }
+            // Status line stats print and TX.
+//            case 'S': {
+//                Serial.print(F("Resets: "));
+//                const uint8_t resetCount = eeprom_read_byte((uint8_t *)V0P2BASE_EE_START_RESET_COUNT);
+//                Serial.print(resetCount);
+//                Serial.println();
+//                // Show stack headroom.
+//                OTV0P2BASE::serialPrintAndFlush(F("SH ")); OTV0P2BASE::serialPrintAndFlush(OTV0P2BASE::MemoryChecks::getMinSPSpaceBelowStackToEnd()); OTV0P2BASE::serialPrintlnAndFlush();
+//                // Default light-weight print and TX of stats.
+////                bareStatsTX();  // FIXME No way this won't overflow!
+//                break; // Note that status is by default printed after processing input line.
+//            }
+            // Switch to FROST mode OR set FROST/setback temperature (even with temp pot available).
+            // With F! force to frost and holiday (long-vacant) mode.  Useful for testing and for remote CLI use.
+            case 'F': {
+                valveMode.setWarmModeDebounced(false); // No parameter supplied; switch to FROST mode.
+                break;
+            }
+            /**
+            * Set secret key.
+            * @note  The OTRadioLink::SimpleSecureFrame32or0BodyTXV0p2::resetRaw3BytePersistentTXRestartCounterCond
+            *        function pointer MUST be passed here to ensure safe handling of the key and the Tx message
+            *        counter.
+            */
+            case 'K': { OTV0P2BASE::CLI::SetSecretKey(OTRadioLink::SimpleSecureFrame32or0BodyTXV0p2::resetRaw3BytePersistentTXRestartCounterCond).doCommand(buf, n); break; }
+            // Switch to WARM (not BAKE) mode OR set WARM temperature.
+            case 'W':{
+                valveMode.cancelBakeDebounced(); // Ensure BAKE mode not entered.
+                valveMode.setWarmModeDebounced(true); // No parameter supplied; switch to WARM mode.
+                break;
+            }
+            // Zap/erase learned statistics.
+            case 'Z': { OTV0P2BASE::CLI::ZapStats().doCommand(buf, n); break; }
+        }
+        // Else show ack of command received.
+        Serial.println(F("OK"));
+    } else { Serial.println(); } // Terminate empty/partial CLI input line after timeout.
+    // Force any pending output before return / possible UART power-down.
+    OTV0P2BASE::flushSerialSCTSensitive();
+    if(neededWaking) { OTV0P2BASE::powerDownSerial(); }
+}
+
+
+/******************************************************************************
+ * SETUP
+ *****************************************************************************/
+
+// Setup routine: runs once after reset.
+// Does some limited board self-test and will panic() if anything is obviously broken.
+void setup()
+{
+    // Set appropriate low-power states, interrupts, etc, ASAP.
+    OTV0P2BASE::powerSetup();
+    // IO setup for safety, and to avoid pins floating.
+    OTV0P2BASE::IOSetup();
+    // Restore previous RTC state if available.
+    OTV0P2BASE::restoreRTC();
+    OTV0P2BASE::serialPrintAndFlush(F("\r\nOpenTRV: ")); // Leading CRLF to clear leading junk, eg from bootloader.
+    V0p2Base_serialPrintlnBuildVersion();
+    // Count resets to detect unexpected crashes/restarts.
+    const uint8_t oldResetCount = eeprom_read_byte((uint8_t *)V0P2BASE_EE_START_RESET_COUNT);
+    resetCount = oldResetCount + 1;
+    eeprom_write_byte((uint8_t *)V0P2BASE_EE_START_RESET_COUNT, 1 + oldResetCount);
+#if 0  // Print reset count. Intended for testing purposes.
+    OTV0P2BASE::serialPrintAndFlush(F("\rResets: "));
+    OTV0P2BASE::serialPrintAndFlush(oldResetCount);
+    OTV0P2BASE::serialPrintlnAndFlush();
+#endif
+
+    // Have 32678Hz clock at least running before going any further.
+    // Check that the slow clock is running reasonably OK, and tune the fast one to it.
+    if(!::OTV0P2BASE::HWTEST::calibrateInternalOscWithExtOsc()) { panic(F("Xtal")); } // Async clock not running or can't tune.
+
+    // Signal that xtal is running AND give it time to settle.
+    OTV0P2BASE::sleepLowPowerMs(1000);
+    OTV0P2BASE::LED_HEATCALL_OFF();
+    OTV0P2BASE::sleepLowPowerMs(250); // TODO: use this time to gather entropy.
+    OTV0P2BASE::LED_HEATCALL_ON();
+    OTV0P2BASE::sleepLowPowerMs(1000); // TODO: use this time to gather entropy.
+
+    // Initialise the radio, if configured, ASAP because it can suck a lot of power until properly initialised.
+    PrimaryRadio.preinit(NULL);
+    // Check that the radio is correctly connected; panic if not...
+    if(!PrimaryRadio.configure(nPrimaryRadioChannels, RFM23BConfigs) || !PrimaryRadio.begin()) { panic(F("r1")); }
+
+    // Buttons should not be activated DURING boot for user-facing boards; an activated button implies a fault.
+    // Check buttons not stuck in the activated position.
+    if(fastDigitalRead(BUTTON_MODE_L) == LOW) { panic(F("b")); }
+
+    // Collect full set of environmental values before entering loop() in normal mode.
+    // This should also help ensure that sensors are properly initialised.
+    TemperatureC16.read();
+    AmbLight.read();
+    RelHumidity.read();
+    TempPot.read();
+    Supply_cV.read();
+    OTV0P2BASE::seedPRNGs();
+    // Update targets, output to TRV and boiler, etc, to be sensible before main loop starts.
+    NominalRadValve.read();
+    // Ensure that the unique node ID is set up (mainly on first use).
+    // Have one attempt (don't want to stress an already failing EEPROM) to force-reset if not good, then panic.
+    // Needs to have had entropy gathered, etc.
+    if(!OTV0P2BASE::ensureIDCreated()) {
+        if(!OTV0P2BASE::ensureIDCreated(true)) { panic(F("ID")); } // Force reset.
+    }
+    // Initialised: turn main/heatcall UI LED off.
+    OTV0P2BASE::LED_HEATCALL_OFF();
+    // Do OpenTRV-specific (late) setup.
+    // Radio not listening to start with.
+    // Ignore any initial spurious RX interrupts for example.
+    PrimaryRadio.listen(false);
+    // Set up async edge interrupts.
+    ATOMIC_BLOCK (ATOMIC_RESTORESTATE) {
+        //PCMSK0 = PB; PCINT  0--7    (LEARN1 and Radio)
+        //PCMSK1 = PC; PCINT  8--15
+        //PCMSK2 = PD; PCINT 16--24   (Serial RX and LEARN2 and MODE and Voice)
+        PCICR = 4 | 0; // 0x4 enables PD/PCMSK2.
+        PCMSK2 = MASK_PD;
+    }
+
+    // Wire components together, eg. for occupancy sensing.
+    AmbLight.setOccCallbackOpt([](bool prob){if(prob){Occupancy.markAsPossiblyOccupied();}else{Occupancy.markAsJustPossiblyOccupied();}});
+    // Callbacks to set various mode combinations.
+    // Typically at most one call would be made on any appropriate pot adjustment.
+    TempPot.setWFBCallbacks([](bool x){valveUI.setWarmModeFromManualUI(x);},
+                            [](bool x){valveUI.setBakeModeFromManualUI(x);});
+    // Initialise sensors with stats info where needed.
+    updateSensorsFromStats();
+
+    // Do early 'wake-up' stats transmission if possible // XXX
+    // when everything else is set up and ready and allowed (TODO-636)
+    // including all set-up and inter-wiring of sensors/actuators.
+    // Attempt to maximise chance of reception with a double TX.
+    // Assume not in hub mode (yet).
+    // Send all possible formats, binary first (assumed complete in one message).
+    bareStatsTX();
+    // Send JSON stats repeatedly (typically once or twice)
+    // until all values pushed out (no 'changed' values unsent)
+    // or limit reached.
+    for(uint8_t i = 5; --i > 0; ) {
+        ::OTV0P2BASE::nap(WDTO_120MS, false); // Sleep long enough for receiver to have a chance to process previous TX.
+        bareStatsTX();
+        if(!ss1.changedValue()) { break; }
+    }
+    // Start local counters in randomised positions to help avoid inter-unit collisions,
+    // eg for mains-powered units starting up together after a power cut,
+    // but without (eg) breaking any of the logic about what order things will be run first time through.
+    // Uses some decent noise to try to start the units separated.
+    const uint8_t b = OTV0P2BASE::getSecureRandomByte(); // randRNG8();
+    // Start within bottom half of minute (or close to); sensor readings happen in second half.
+    OTV0P2BASE::setSeconds(b >> 2);
+    // Start anywhere in first 4 minute cycle.
+    minuteCount = b & 3;
+    // Set appropriate loop() values just before entering it.
+    TIME_LSD = OTV0P2BASE::getSecondsLT();
+}
+
+
+/******************************************************************************
+ * MAIN LOOP
+ *****************************************************************************/
+
+void loop()
+{
+    OTV0P2BASE::stackCheck();
+
+    // Main loop for OpenTRV radiator control.
+    // Note: exiting and re-entering can take a little while, handling Arduino background tasks such as serial.
+    // Sensor readings are taken late in each minute (where they are taken)
+    // and if possible noise and heat and light should be minimised in this part of each minute to improve readings.
+    //  const bool sensorReading30s = (TIME_LSD >= 30);
+    // Sensor readings and (stats transmissions) are nominally on a 4-minute cycle.
+    const uint8_t minuteFrom4 = (minuteCount & 3);
+    // The 0th minute in each group of four is always used for measuring where possible (possibly amongst others)
+    // and where possible locally-generated noise and heat and light should be minimised in this minute
+    // to give the best possible readings.
+    // True if this is the first (0th) minute in each group of four.
+    const bool minute0From4ForSensors = (0 == minuteFrom4);
+    // Note last-measured battery status.
+    const bool batteryLow = Supply_cV.isSupplyVoltageLow();
+    // Run some tasks less often when not demanding heat (at the valve or boiler), so as to conserve battery/energy.
+    // Spare the batteries if they are low, or the unit is in FROST mode, or if the room/area appears to be vacant.
+    // Stay responsive if the valve is open and/or we are otherwise calling for heat.
+    // Run at full speed until not nominally demanding heat, eg even during FROST mode or pre-heating.
+    const bool conserveBattery = (batteryLow || !valveMode.inWarmMode() || Occupancy.longVacant()) && (!NominalRadValve.isCallingForHeat());
+    // Try if very near to end of cycle and thus causing an overrun.
+    // Conversely, if not true, should have time to safely log outputs, etc.
+    constexpr uint8_t nearOverrunThreshold = OTV0P2BASE::GSCT_MAX - 8; // ~64ms/~32 serial TX chars of grace time...
+
+    // Go to sleep until the start of the next cycle.
+    TIME_LSD = OTV0P2BASE::sleepUntilNewCycle<>(TIME_LSD);
+
+    // Reset and immediately re-prime the RTC-based watchdog.
+    OTV0P2BASE::resetRTCWatchDog();
+    OTV0P2BASE::enableRTCWatchdog(false);
+
+    // START LOOP BODY
+    // ===============
+    // High-priority UI handing, every other/even second.
+    // Show status if the user changed something significant.
+    // Must take ~300ms or less so as not to run over into next half second if two TXs are done.
+    bool recompute = false; // Set true if an extra recompute of target temperature should be done.
+    // Run the OpenTRV button/LED UI if required.
+    if(0 != valveUI.read()) { recompute = true; }
+    // Handling the UI may have taken a little while, so process I/O a little.
+    if(recompute || valveUI.veryRecentUIControlUse()) {
+        // Force immediate recompute of target temperature for (UI) responsiveness.
+        NominalRadValve.computeTargetTemperature();
+        // Keep dynamic adjustment of sensors up to date.
+        updateSensorsFromStats();
+    }
+
+    bareStatsTX();
+
+    // DO SCHEDULING
+
+    // Once-per-minute tasks: all must take << 0.3s unless particular care is taken.
+    // Run tasks spread throughout the minute to be as kind to batteries (etc) as possible.
+    // Only when runAll is true run less-critical tasks that be skipped sometimes when particularly conserving energy.
+    // Run all for first full 4-minute cycle, eg because unit may start anywhere in it.
+    // Note: ensure only take ambient light reading at times when all LEDs are off (or turn them off).
+    // TODO: coordinate temperature reading with time when radio and other heat-generating items are off for more accurate readings.
+    const bool runAll = (!conserveBattery) || minute0From4ForSensors || (minuteCount < 4);
+    switch(TIME_LSD) { // With V0P2BASE_TWO_S_TICK_RTC_SUPPORT only even seconds are available.
+        case 0: {
+            // Tasks that must be run every minute.
+            ++minuteCount; // Note simple roll-over to 0 at max value.
+            // Force to user's programmed schedule(s), if any, at the correct time.
+            Scheduler.applyUserSchedule(&valveMode, OTV0P2BASE::getMinutesSinceMidnightLT());
+            // Ensure that the RTC has been persisted promptly when necessary.
+            OTV0P2BASE::persistRTC();
+            // Run hourly tasks at the end of the hour.
+            if((59 == OTV0P2BASE::getMinutesLT() ) && (23 == OTV0P2BASE::getHoursLT() )) { endOfDayTasks(); }
+            break;
+        }
+        // Churn/reseed PRNG(s) a little to improve unpredictability in use: should be lightweight.
+        case 2: { if(runAll) { OTV0P2BASE::seedRNG8(minuteCount ^ OTV0P2BASE::getCPUCycleCount() ^ (uint8_t)Supply_cV.get(), OTV0P2BASE::_getSubCycleTime() ^ AmbLight.get(), (uint8_t)TemperatureC16.get()); } break; }
+        // Force read of supply/battery voltage; measure and recompute status (etc) less often when already thought to be low, eg when conserving.
+        case 4: { if(runAll) { Supply_cV.read(); } break; }
+        // Periodic transmission of stats if NOT driving a local valve (else stats can be piggybacked onto that).
+        // Randomised somewhat between slots and also within the slot to help avoid collisions.
+        // Disabled due to frequent TX.
+        case 6: case 8: case 10: case 12: case 14: case 16: case 18: case 20: case 22: {
+            break;
+        }
+        // SENSOR READ AND STATS
+        //
+        // All external sensor reads should be in the second half of the minute (>32) if possible.
+        // This is to have them as close to stats collection at the end of the minute as possible,
+        // and to allow randomisation of the start-up cycle position in the first 32s to help avoid inter-unit collisions.
+        // Also all sources of noise, self-heating, etc, may be turned off for the 'sensor read minute'
+        // and thus will have diminished by this point.
+
+        // Sample the user-selected WARM temperature target at a fixed rate.
+        // This allows the unit to stay reasonably responsive to adjusting the temperature dial.
+        case 48: { TempPot.read(); break; }
+        // Read all environmental inputs, late in the cycle.
+        // Sample humidity.
+        case 50: { if(runAll) { RelHumidity.read(); } break; }
+        // Poll ambient light level at a fixed rate.
+        // This allows the unit to respond consistently to (eg) switching lights on (eg TODO-388).
+        // Force all UI lights off before sampling ambient light level.
+        case 52: { OTV0P2BASE::LED_HEATCALL_OFF(); AmbLight.read(); break; }
+        // At a hub, sample temperature regularly as late as possible in the minute just before recomputing valve position.
+        // Force a regular read to make stats such as rate-of-change simple and to minimise lag.
+        // TODO: optimise to reduce power consumption when not calling for heat.
+        // TODO: optimise to reduce self-heating jitter when in hub/listen/RX mode.
+        case 54: { TemperatureC16.read(); break; }
+        // Compute targets and heat demand based on environmental inputs and occupancy.
+        // This should happen as soon after the latest readings as possible (temperature especially).
+        case 56: {
+            // Update occupancy status (fresh for target recomputation) at a fixed rate.
+            Occupancy.read();
+            // Recompute target, valve position and call for heat, etc.
+            // Should be called once per minute to work correctly.
+            NominalRadValve.read();
+            // Update/age errors and warnings.
+            OTV0P2BASE::ErrorReporter.read();
+            break;
+        }
+        // Stats samples; should never be missed.
+        case 58: {
+            // Update non-volatile stats.
+            // Make the final update as near the end of the hour as possible to reduce glitches (TODO-1086),
+            // and with other optional non-full samples evenly spaced throughout the hour.
+            // Race-free.
+            const uint_least16_t msm = OTV0P2BASE::getMinutesSinceMidnightLT();
+            const uint8_t mm = msm % 60;
+            if(59 == mm) { statsU.sampleStats(true, uint8_t(msm / 60)); }
+            else if((statsU.maxSamplesPerHour > 1) && (29 == mm)) { statsU.sampleStats(false, uint8_t(msm / 60)); }
+            break;
+        }
+    }
+    // End-of-loop processing, that may be slow.
+    // Handle local direct-drive valve, eg DORM1.
+    // If waiting for for verification that the valve has been fitted
+    // then accept any manual interaction with controls as that signal.
+    // Also have a backup timeout of at least ~10m from startup
+    // for automatic recovery after a crash and restart,
+    // or where fitter simply forgets to initiate cablibration.
+    if(ValveDirect.isWaitingForValveToBeFitted()) {
+        // Defer automatic recovery when battery low or in dark in case crashing/restarting
+        // to try to avoid disturbing/waking occupants and/or entering battery death spiral.  (TODO-1037, TODO-963)
+        // The initial minuteCount value can be anywhere in the range [0,3];
+        // pick threshold to give user at least a couple of minutes to fit the device
+        // if they do so with the battery in place.
+        const bool delayRecalibration = batteryLow || AmbLight.isRoomDark();
+        if(valveUI.veryRecentUIControlUse() || (minuteCount >= (delayRecalibration ? 240 : 5)))
+            { ValveDirect.signalValveFitted(); }
+    }
+    // Provide regular poll to motor driver.
+    // May take significant time to run
+    // so don't call when timing is critical
+    // nor when not much time left this cycle,
+    // nor some of the time during startup if possible,
+    // so as (for example) to allow the CLI to be operable.
+    // Only calling this after most other heavy-lifting work is likely done.
+    // Note that FHT8V sync will take up at least the first 1s of a 2s subcycle.
+    if(OTV0P2BASE::getSubCycleTime() < ((OTV0P2BASE::GSCT_MAX/4)*3))
+        { ValveDirect.read(); }
+    // Command-Line Interface (CLI) polling.
+    // If a reasonable chunk of the minor cycle remains after all other work is done
+    // AND the CLI is / should be active OR a status line has just been output
+    // then poll/prompt the user for input
+    // using a timeout which should safely avoid overrun, ie missing the next basic tick,
+    // and which should also allow some energy-saving sleep.
+    if(OTV0P2BASE::CLI::isCLIActive()) {
+        const uint8_t stopBy = nearOverrunThreshold - 1;
+        OTV0P2BASE::ScratchSpace s((uint8_t*)globalWorkSpace.cli, sizeof(globalWorkSpace.cli));
+        pollCLI(stopBy, 0 == TIME_LSD, s);
+    }
+}
+


Add main MIDI controller sketch

Add main MIDI controller sketch

error: pathspec 'firmware/midi_controller/midi_controller.ino' did not match any file(s) known to git

--- firmware/midi_controller/midi_controller.ino
+++ firmware/midi_controller/midi_controller.ino
@@ -0,0 +1,28 @@
+#include <MIDI.h>
+
+#define PIN_KEY_IN   2
+#define PIN_MIDI_OUT 3
+
+MIDI_CREATE_DEFAULT_INSTANCE();
+
+void setup()
+{
+    pinMode(PIN_KEY_IN, INPUT);
+    pinMode(PIN_MIDI_OUT, OUTPUT);
+
+    MIDI.begin();
+}
+
+void loop()
+{
+    // Read digital value from piano key
+    int in_value = digitalRead(PIN_KEY_IN);
+
+    // Output a tone if key was pressed
+    if (in_value == HIGH) {
+        MIDI.sendNoteOn(40, 127, 1);      // Note 40 (E3), velocity 127, channel 1
+    }
+
+    // TODO: Come up with a way to choose which note to play based on which key
+    // is being pressed.
+}


Added Mr. Ferguson's license

Added Mr. Ferguson's license

git-svn-id: d8bfcd070c2c81762cca1b3912331324182919ba@21147 0d6f1817-ed0e-0410-87c9-987e46238f29

--- zimodem/proto_hostcm.ino
+++ zimodem/proto_hostcm.ino
@@ -1,6 +1,17 @@
 #ifdef INCLUDE_SD_SHELL
 #ifdef INCLUDE_HOSTCM
-/* Converted from source reverse engineered from SP9000 roms by Rob Ferguson */
+/* Converted from Rob Ferguson's code by Bo Zimmerman
+ *
+ * Copyright (c) 2013, Robert Ferguson All rights reserved.
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS
+ * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
+ * AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER
+ * IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+ * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */

 char *basename(char *path)
 {


Updated software

Updated software

error: pathspec 'OpenCTD_master.ino' did not match any file(s) known to git

--- OpenCTD_master.ino
+++ OpenCTD_master.ino
@@ -0,0 +1,127 @@
+//Wire and MS5803_I2C libraries for communicating with the pressure sensor.
+
+#include <Wire.h>
+#include <SparkFun_MS5803_I2C.h>
+
+//EC Circuit uses software serial
+#include <SoftwareSerial.h>     // For use by EC circuit.
+
+//OneWire is used to get data from both temperature and conductivity sensors
+#include <OneWire.h>
+
+//DallasTerperature is used to query the temeprature sensors
+#include <DallasTemperature.h>
+
+// For the SD card reader.
+#include <SPI.h>
+#include <SD.h>
+
+// Declare global variables.
+float tempA;
+float tempB;
+float tempC;
+
+float EC_float = 0;  // Electrical conductivity.
+
+char EC_data[48];    // A 48 byte character array to hold incoming data from the conductivity circuit.
+char *EC;            // Character pointer for string parsing.
+
+byte received_from_sensor = 0;  // How many characters have been received.
+byte string_received = 0;       // Whether it received a string from the EC circuit.
+
+SoftwareSerial ecSerial(8, 9);      // Define the SoftwareSerial port for conductivity.
+OneWire oneWire(7);        // Define the OneWire port for temperature.
+DallasTemperature sensors(&oneWire);  // Define DallasTemperature input based on OneWire.
+MS5803 sensor(ADDRESS_HIGH);          // Define pressure sensor.
+
+double pressure_abs; //define absolute pressure variable
+
+// Starts it up.
+//
+void setup(void) {
+
+  Serial.begin(9600);   // Set baud rate.
+  ecSerial.begin(9600); // Set baud rate for conductivty circuit.
+
+  pinMode(10, OUTPUT);  // Set data output pin for the SD card reader.
+
+  while (!Serial) {}    // Initialize the SD card reader.
+
+  // Test to see if the SD card reader is functioning.
+  if (!SD.begin(4)) {
+    Serial.println("Card failed");  // Log to serial monitor if SD card doesn't work.
+    return;
+  }
+  delay(500);  // Wait half a second before continuing.
+
+  sensor.reset(); //reset pressure sensor
+  sensor.begin(); //initialize pressure sensor
+  sensors.begin();  // Intialize the temperature sensors.
+
+  delay(250);       // Wait a quarter second to continue.
+
+}
+
+void loop(void) {
+
+  // Read any pending data from the EC circuit.
+  if (ecSerial.available() > 0) {
+    received_from_sensor = ecSerial.readBytesUntil(13, EC_data, 48);
+
+    // Null terminate the data by setting the value after the final character to 0.
+    EC_data[received_from_sensor] = 0;
+  }
+
+  // Parse data, if EC_data begins with a digit, not a letter (testing ASCII values).
+  if ((EC_data[0] >= 48) && (EC_data[0] <=57)) {
+    parse_data();
+  }
+
+  delay(10);  // Wait 10 milliseconds.
+
+  // Read temperature sensors.
+  sensors.requestTemperatures();
+  tempA = sensors.getTempCByIndex(0);
+  tempB = sensors.getTempCByIndex(1);
+  tempC = sensors.getTempCByIndex(2);
+
+  // Log to the SD card...
+  File dataFile = SD.open("datalog.txt", FILE_WRITE);
+  if (dataFile) {
+    dataFile.print(sensor.getPressure(ADC_4096));
+    dataFile.print("  ");
+    dataFile.print(tempA);
+    dataFile.print("  ");
+    dataFile.print(tempB);
+    dataFile.print("  ");
+    dataFile.print(tempC);
+    dataFile.print("  ");
+    dataFile.println(EC);
+    dataFile.close();
+  }
+  else {
+
+  }
+
+  // Log to the serial monitor.
+  Serial.print(sensor.getPressure(ADC_4096));
+  Serial.print("  ");
+  Serial.print(tempA);
+  Serial.print("  ");
+  Serial.print(tempB);
+  Serial.print("  ");
+  Serial.print(tempC);
+  Serial.print("  ");
+  Serial.print(EC);
+  Serial.println("");
+
+  delay(50);  // Wait 50 milliseconds.
+}
+
+
+// Parses data from the EC Circuit.
+void parse_data() {
+
+  EC = strtok(EC_data, ",");
+
+}


set up analog output for peizo

set up analog output for peizo

remote: Support for password authentication was removed on August 13, 2021.
remote: Please see https://docs.github.com/en/get-started/getting-started-with-git/about-remote-repositories#cloning-with-https-urls for information on currently recommended modes of authentication.
fatal: Authentication failed for 'https://github.com/jeremywrnr/wampum.git/'


Added reporting of input pins. It doesn't seem to work well, though.

remote: Support for password authentication was removed on August 13, 2021.
remote: Please see https://docs.github.com/en/get-started/getting-started-with-git/about-remote-repositories#cloning-with-https-urls for information on currently recommended modes of authentication.
fatal: Authentication failed for 'https://github.com/RichoM/Uzi.git/'


Removed unnecessary #define

Removed unnecessary #define


Counterpart to nip.cc - testing on an Arduino Uno.

error: pathspec 'NetIP/examples/nip/nip.ino' did not match any file(s) known to git

--- NetIP/examples/nip/nip.ino
+++ NetIP/examples/nip/nip.ino
@@ -0,0 +1,57 @@
+#include <NetIP.hh>
+#include <Arduino.h>
+#include <EEPROM.h>
+
+class Uino : public IP_TimerClient {
+  IP_Connection * udp;
+  bool bOn;
+public:
+  Uino (IP_Connection * con) :
+    udp(con),
+    bOn(false)
+  {
+    pinMode (LED_BUILTIN, OUTPUT);
+    digitalWrite (LED_BUILTIN, LOW);
+  }
+
+  virtual bool timeout () { // should be called once a second
+    if (bOn) {
+      digitalWrite (LED_BUILTIN, LOW);
+      bOn = false;
+      udp->print ("tock");
+    } else {
+      digitalWrite (LED_BUILTIN, HIGH);
+      bOn = true;
+      udp->print ("tick");
+    }
+    // Serial.print (millis ());
+    return true; // keep going
+  }
+};
+
+void setup () {
+  Serial.begin (115200);
+
+  IP_Manager & IP = IP_Manager::manager ();
+
+  IP.host.set_local_network_id (EEPROM.read (0));
+
+  IP_SerialChannel ser0(Serial);
+  IP.channel_add (&ser0);
+
+  IP_Connection udp(p_UDP);
+  udp.connect (IP.gateway, 0xC00C);
+  IP.connection_add (&udp);
+
+  Uino uino(&udp);
+
+  IP_Timer timer(&uino);  // set up a periodic callback
+  timer.start (IP, 1000); // once a second
+
+  IP.run (); // runs forever
+}
+
+void loop() {
+  // unreachable
+}
+


Create Pixy-spark-color-codes.ino

Create Pixy-spark-color-codes.ino

error: pathspec 'Pixy-spark-color-codes.ino' did not match any file(s) known to git

--- Pixy-spark-color-codes.ino
+++ Pixy-spark-color-codes.ino
@@ -0,0 +1,162 @@
+// Use the color codes on the back of the Pixy box. Use PixyMon to set them up as CC colorcodes. Make sure it works then run this program
+
+// Note: Watch the RGB LED at startup.
+// If it quickly goes to breathing cyan, you have Wifi connectivity
+// If it takes 20 seconds to go to breathing cyan, then you have no Wifi connectivity but the Pixy should still work
+
+
+
+
+SYSTEM_MODE(SEMI_AUTOMATIC);  // so that connectivity is not blocking
+
+int myConnect = 0;      // means neighter connected or disconnected yet
+
+
+
+int myD7  = D7;      // to test if wifi is working
+
+
+//      #include "SPI.h"        // got rid of this since it would not compile
+#include "Pixy.h"
+#include "TPixy.h"
+
+
+// Not sure how to flash new code so will not do this yet.
+//SYSTEM_MODE(MANUAL);                // Set the system mode to manual. We don't need the cloud for this code
+
+
+Pixy pixy;                          // Create our pixy object
+
+// Setup - Runs Once @ Startup
+void setup() {
+
+    pinMode(myD7, OUTPUT);
+    Spark.connect();         //attempt to connect to Wifi
+
+    delay(20000);   // wait 20 seconds at startup
+
+
+   if (WiFi.ready()){
+        myConnect = 1;   // means wifi got connected
+    } else {
+        Spark.disconnect();
+        WiFi.off();
+        myConnect =2;   // means no wifi cloud connectivity
+       // digitalWrite(D7,HIGH);     // D7 high says no wifi but spark working fine.
+      }
+
+
+    RGB.brightness(1);    // 1=very low light, 255 = max
+
+    Serial.begin(9600);             // Initalize the USB Serial port
+    pixy.init();                    // Initalize the pixy object
+}
+
+// Loop - Runs over and over
+void loop(){
+
+
+
+    uint16_t    blocks;                       // Create an unsigned int to hold the number of found blocks
+    char        buf[50];                      // Create a buffer for printing over serial
+    int         myPixyInt, myWait, myWait2, i;
+
+    blocks = pixy.getBlocks();      // Do the pixy stuff. Grab the numbers of blocks the pixy finds
+
+  // If we have some blocks increment the frame counter (i) and if enough frames have passed print out data about the found blocks to the serial port
+  if (blocks) {
+
+
+
+ // for serial USB printing of the information every 25th loop
+
+ /*
+     i++;
+    if (i%25==0)
+    {
+      sprintf(buf, "Detected %d:\n", blocks);
+      Serial.print(buf);
+      for (int j = 0; j < blocks; j++)
+      {
+        sprintf(buf, "\tblock %d:", j);
+        Serial.print(buf);
+        pixy.blocks[j].print();
+        Serial.println();
+      }
+    }
+
+ */
+
+
+
+
+  // if (pixy.blocks[0].signature == 14){               //    octal numbers so color code 12 = 14-octal   color code 123 = 173-octal, 1234 = 2322-Octal
+                                                      // http://www.rapidtables.com/convert/number/decimal-to-octal.htm
+                                                      // http://www.rapidtables.com/convert/number/octal-to-decimal.htm
+
+      myPixyInt = pixy.blocks[0].angle;    // x location of the main object    0 - 320
+  //  myPixyInt = pixy.blocks[0].x;    // x location of the main object    0 - 320
+
+   // myPixyInt = pixy.blocks[0].y;
+   // myPixyInt = pixy.blocks[0].width;
+   // myPixyInt = pixy.blocks[0].height;
+
+
+  // myWait = myPixyInt;
+   // myWait = blocks * 3;  // if you want to see how many objects Pixy has detected x 3
+
+   if (myPixyInt <= 10) {
+       digitalWrite(D7, LOW);     // If color codes almost vertical then turn off D7
+    }
+
+
+    if (myPixyInt > 10 && myPixyInt <= 100) {    // if color codes slightly titled fast D7 blink
+        digitalWrite(D7, HIGH);
+        delay(5);
+        digitalWrite(D7, LOW);
+        delay(5);
+    }
+
+
+    if (myPixyInt > 100 && myPixyInt <= 200) {    // if color codes upside down then medium D7 blink
+        digitalWrite(D7, HIGH);
+        delay(100);
+        digitalWrite(D7, LOW);
+        delay(100);
+    }
+
+
+    if (myPixyInt > 200 && myPixyInt <= 300) {    // If color codes almost fully turned around D7 slow blink
+        digitalWrite(D7, HIGH);
+        delay(1000);
+        digitalWrite(D7, LOW);
+        delay(1000);
+    }
+
+
+    if (myPixyInt > 300 ) {    // Object almost completely vertical leave D7 on
+        digitalWrite(D7, HIGH);
+    }
+
+
+   delay(5); // just for the heck of it slow things down a bit
+
+
+  // } // end signature
+
+  } // end blocks
+}   // end loop
+
+
+
+
+
+
+
+
+
+
+
+
+
+


Initial commit

Initial commit

error: pathspec 'brewtus_controller.ino' did not match any file(s) known to git

--- brewtus_controller.ino
+++ brewtus_controller.ino
@@ -0,0 +1,186 @@
+// Sketch to use an Arduino as tempcontroller
+// in an Expobar Brewtus II espresso machine
+
+//--> Import libraries <--
+#include <OneWire.h>
+#include <DallasTemperature.h>
+#include <LiquidCrystal.h>
+
+
+//--> Setup variables <--
+
+// The target temperature
+float desiredTemp = 93;
+
+// Pins
+int relayPin = 11;
+int tempPin = 12;
+
+// Timing
+long previousMillis = 0;
+long tempInterval = 2000;
+
+// Defaults
+int displayMode = 0;
+float temp = 0;
+float oldTemp = 0;
+boolean relayStatus = false;
+boolean oldRelayStatus = false;
+boolean refreshDisplay = true;
+
+// Custom LCD graphics
+byte on[8] = {
+  B11111,
+  B11111,
+  B11111,
+  B11111,
+  B11111,
+  B11111,
+  B11111,
+};
+byte off[8] = {
+  B11111,
+  B10001,
+  B10001,
+  B10001,
+  B10001,
+  B10001,
+  B11111,
+};
+
+
+//--> Configure libs <--
+
+// Setup tempsensor
+#define ONE_WIRE_BUS tempPin
+
+// Setup a oneWire instance to communicate with any OneWire devices
+OneWire oneWire(ONE_WIRE_BUS);
+
+// Pass our oneWire reference to Dallas Temperature.
+DallasTemperature sensors(&oneWire);
+DeviceAddress tempProbe = { 0x28, 0x2D, 0x48, 0x4E, 0x05, 0x00, 0x00, 0xF5 };
+//DeviceAddress tempProbe = { 0x28, 0xFF, 0x70, 0xFC, 0x10, 0x14, 0x00, 0xDB };
+
+// Initialize LCD display
+LiquidCrystal lcd(5, 4, 0, 1, 2, 3);
+
+
+//--> Arduino Setup <--
+
+void setup(void)
+{
+  // Setup the LCD
+  lcd.begin(16, 2);
+  lcd.createChar(1, on);
+  lcd.createChar(2, off);
+  // Print startup message to LCD
+  lcd.clear();
+  lcd.print("-= Brewtus II =-");
+
+  // start serial port
+  //Serial.begin(9600);
+
+  // Setup relay pin
+  pinMode(relayPin, OUTPUT);
+
+  // Start up the sensors library
+  sensors.begin();
+  // set the resolution to 10 bit
+  sensors.setResolution(tempProbe, 10);
+
+  // Add a delay to show the "boot screen"
+  delay(2000);
+}
+
+
+//--> Functions <--
+
+float checkTemp(){
+  Serial.print("Getting temperature...\n\r");
+  sensors.requestTemperatures();
+  Serial.print("Temperature is: ");
+  float tempRead = sensors.getTempC(tempProbe);
+  if (tempRead == -127.00) {
+    Serial.print("Error getting temperature");
+    return 0.00;
+  } else {
+    Serial.print("C: ");
+    Serial.print(tempRead);
+  }
+  Serial.print("\n\r\n\r");
+  return tempRead;
+}
+
+boolean controlRelay(float currentTemp, float targetTemp){
+  if (currentTemp == 0.00){
+    Serial.println("Heater OFF");
+    digitalWrite(relayPin, LOW);
+    return false;
+  }
+  else if (currentTemp >= targetTemp - 0.5){
+    Serial.println("Heater OFF");
+    digitalWrite(relayPin, LOW);
+    return false;
+  }
+  else if (currentTemp <= targetTemp - 0.75){
+    Serial.println("Heater ON");
+    digitalWrite(relayPin, HIGH);
+    return true;
+  }
+}
+
+void printDisplay(int mode){
+  if (mode == 0){
+    lcd.clear();
+    lcd.print("Temp: ");
+    if (temp == 0.00) {
+      lcd.print("Read Error");
+    }
+    else {
+      lcd.print(temp);
+      lcd.print((char)223);
+    }
+    lcd.setCursor(0,1);
+    lcd.print(" Set: ");
+    lcd.print(desiredTemp, 0);
+    lcd.print((char)223);
+    lcd.print(" Heat:");
+    if (relayStatus == true){
+      lcd.write(1);
+    }
+    else {
+      lcd.write(2);
+    }
+  }
+  refreshDisplay = false;
+}
+
+
+//--> Arduino Main Loop <--
+
+void loop(void){
+  unsigned long currentMillis = millis();
+
+  // Is it time to check the temp?
+  if(currentMillis - previousMillis > tempInterval) {
+    previousMillis = currentMillis;
+
+    temp = checkTemp();
+    relayStatus = controlRelay(temp, desiredTemp);
+
+    if (temp != oldTemp){
+      refreshDisplay = true;
+      oldTemp = temp;
+    }
+    else if (relayStatus != oldRelayStatus){
+      refreshDisplay = true;
+      oldRelayStatus = relayStatus;
+    }
+  }
+
+  if (refreshDisplay == true){
+    printDisplay(displayMode);
+  }
+}
+


Add example with no devices

Add example with no devices

error: pathspec 'Photon/IoTlib/examples/nothing/nothing.ino' did not match any file(s) known to git

--- Photon/IoTlib/examples/nothing/nothing.ino
+++ Photon/IoTlib/examples/nothing/nothing.ino
@@ -0,0 +1,44 @@
+/******************************************************************
+Example starting point sketch for ParticleIoT library
+
+This example contains no devices.
+It is provided for comparison with other example sketches.
+
+http://www.github.com/rlisle/ParticleIoT
+
+Written by Ron Lisle
+
+BSD license, check LICENSE for more information.
+All text above must be included in any redistribution.
+
+Changelog:
+2017-03-11: Initial creation
+******************************************************************/
+
+/****************/
+/*** INCLUDES ***/
+/****************/
+#include <IoT.h>
+
+/*****************/
+/*** VARIABLES ***/
+/*****************/
+IoT *iot;
+
+/*************/
+/*** SETUP ***/
+/*************/
+void setup() {
+    iot = IoT::getInstance();
+    iot->setControllerName("lislerv");
+    iot->setPublishName("RonTest");
+    iot->begin();
+}
+
+/************/
+/*** LOOP ***/
+/************/
+void loop() {
+    long currentMillis = millis();
+    iot->loop(currentMillis);
+}


git init

git init

--- APC_220.ino
+++ APC_220.ino
@@ -0,0 +1,39 @@
+// include the library code:
+#include <LiquidCrystal.h>
+
+LiquidCrystal lcd(12, 11, 5, 4, 3, 2);
+int led = 9;
+
+void setup()
+{
+  pinMode(led, OUTPUT);
+  Serial.begin(19200);    //  Definition UART port rate 19200, according to serial port of APC220
+
+  // set up the LCD's number of columns and rows:
+  lcd.begin(16, 2);
+}
+void loop()
+{
+
+// when characters arrive over the serial port...
+  if (Serial.available()) {
+    // wait a bit for the entire message to arrive
+    delay(100);
+    digitalWrite(led, HIGH);   // turn the LED on (HIGH is the voltage level)
+    delay(100);               // wait for a second
+    digitalWrite(led, LOW);    // turn the LED off by making the voltage LOW
+    delay(100);
+    digitalWrite(led, HIGH);   // turn the LED on (HIGH is the voltage level)
+    delay(100);               // wait for a second
+    digitalWrite(led, LOW);    // turn the LED off by making the voltage LOW
+
+
+    // clear the screen
+    lcd.clear();
+    // read all the available characters
+    while (Serial.available() > 0) {
+      // display each character to the LCD
+      lcd.write(Serial.read());
+    }
+  }
+}


comments

comments

remote: Support for password authentication was removed on August 13, 2021.
remote: Please see https://docs.github.com/en/get-started/getting-started-with-git/about-remote-repositories#cloning-with-https-urls for information on currently recommended modes of authentication.
fatal: Authentication failed for 'https://github.com/jacksonsouza/muffet.git/'


Arduino version.

Arduino version.

error: pathspec 'VX8_GPS/vx8_gps_arduino/vx8_gps_arduino.ino' did not match any file(s) known to git

--- VX8_GPS/vx8_gps_arduino/vx8_gps_arduino.ino
+++ VX8_GPS/vx8_gps_arduino/vx8_gps_arduino.ino
@@ -0,0 +1,619 @@
+/*
+ * vx8_gps.c
+ *
+ *  Created on: 03 gen 2016
+ *      Author: 4Z7DTF
+ */
+
+#include <avr/io.h>
+#include <avr/interrupt.h>
+
+extern "C" {
+  #include ".//str_func.h"
+}
+
+#define bool uint8_t
+#define true 0x01
+#define false 0x00
+
+/* Function prototypes. */
+bool process_field(void);
+void reset_rx(void);
+void reset_tx(void);
+void copy_msg_to_tx_buf(void);
+void usart0_init(void);
+
+#define USART_BAUDRATE 9600
+#define UBRR_VALUE (((F_CPU / (USART_BAUDRATE * 16UL))) - 1)
+
+/* Different sources state that maximum sentence length is 80 characters
+ * plus CR and LF. Actual Yaesu FGPS-2 GPS output shows that this standard
+ * is ignored and GGA message reaches 86 symbols. That's why the buffer sizes
+ * are limited to 90 characters instead of 82.
+ */
+#define BUFFER_SIZE 90
+#define CR 0x0D
+#define LF 0x0A
+
+/* RX variables */
+enum rx_states
+{
+  READY = 0x00, /* Default state, ready to receive. Changes if $ is received. */
+  RX_MESSAGE = 0x01, /* Receiving the message between the $ and * delimiters. */
+  RX_CHECKSUM = 0x02, /* Receiving the checksum. Changes if \r\n  is received. */
+  COPY_TO_TX = 0x03, /* Default state, ready to receive. Changes if $ is received. */
+};
+uint8_t state; /* Current system state. */
+
+enum nmea_commands
+{
+  NONE, GGA, RMC, ZDA
+};
+uint8_t rx_command; /* NMEA command being received. */
+uint8_t rx_field; /* Current field of NMEA command. */
+uint8_t rx_field_size; /* Current field size. */
+
+char rx_buffer[BUFFER_SIZE]; /* Buffer for the received message. */
+uint8_t rx_buf_pos;
+bool rx_byte_ready; /* Received byte ready flag. */
+uint8_t rx_byte; /* Received byte. */
+uint8_t calc_checksum; /* Calculated checksum of the received message. Calculated on the fly. */
+uint8_t rx_checksum; /* Checksum of the received NMEA sentence. */
+
+/* A lookup table for converting numerical values to hexadecimal digits. */
+char hex_chars[16] = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' };
+
+/* TX variables */
+char tx_buffer[BUFFER_SIZE]; /* Buffer for the message to be sent. */
+uint8_t tx_buf_pos;
+bool tx_not_empty; /* TX has a message to send. Set to false when tx_buffer is empty. */
+bool tx_rts; /* TX ready to send. Set to true when RX is ready to send next byte. */
+
+void setup()
+{
+  /* PORTD connections:
+   * pin 7: green led
+   * pin 5: red led
+   * pin 4: green led
+   * pin 2: red led
+   */
+  DDRD = DDRD | 0B11111100;
+  PORTD = PORTD & 0B00000011;
+
+  usart0_init();
+  reset_tx();
+  tx_rts = true;
+  reset_rx();
+  state = READY;
+
+  sei();
+}
+
+void loop()
+{
+    /* Next byte is sent if there is one and TX is ready to send.
+     * If null terminator or end of buffer is reached, TX is reset.
+     */
+    if (tx_rts && tx_not_empty)
+    {
+      if (tx_buf_pos < BUFFER_SIZE && tx_buffer[tx_buf_pos] != 0x00)
+      {
+        UDR0 = tx_buffer[tx_buf_pos];
+        tx_buf_pos++;
+        tx_rts = false;
+      }
+      else
+      {
+        reset_tx();
+      }
+    }
+
+    /* RX routine */
+    switch (state)
+    {
+    case READY:
+      /* READY: The system is ready to receive and remains is this state
+       * until $ character is received.
+       */
+      if (rx_byte_ready)
+      {
+        rx_byte_ready = false;
+        if (rx_byte == '$')
+        {
+          rx_buffer[rx_buf_pos] = rx_byte;
+          rx_buf_pos++;
+          state = RX_MESSAGE;
+        }
+      }
+      break;
+    case RX_MESSAGE:
+      /* RX_MESSAGE: The system receives the message between $ and *
+       * delimiters. NMEA sentence checksum is calculated on the fly.
+       * Comma character marks end of field. Each time it is
+       * received, the field is verified and changed to VX-8 specific
+       * format if required. Changing the fields on the fly results
+       * in getting a new VX-8 compatible message at the end of reception.
+       * NMEA sentences with empty time fields are discarded. The system
+       * stops receiving current sentence and returns to READY. This is
+       * done to prevent sending false time to VX-8. Other empty fields
+       * are filled with default values, in most cases zeros. That's why
+       * VX-8 shows zeros in coordinate fields when GPS fix isn't acquired
+       * or is lost.
+       * When * character is received the state changes to RX_CHECKSUM.
+       */
+      if (rx_byte_ready)
+      {
+        rx_byte_ready = false;
+
+        /* If received character is $ or the buffer is overflown,
+         * reset and return to READY state.
+         */
+        if (rx_byte == '$' || rx_buf_pos >= BUFFER_SIZE)
+        {
+          reset_rx();
+          break;
+        }
+
+        /* Comma and marks end of field, asterisk marks end of message
+         * which is also end of the last field.
+         */
+        if (rx_byte == ',' || rx_byte == '*')
+        {
+          bool field_valid = process_field();
+          if (!field_valid)
+          {
+            reset_rx();
+            break;
+          }
+          rx_field++;
+          rx_field_size = 0;
+        }
+        else
+        {
+          rx_field_size++;
+        }
+
+        rx_buffer[rx_buf_pos] = rx_byte;
+        rx_buf_pos++;
+
+        /* If end of message, change state to RX_CHECKSUM
+         * without affecting the calculated checksum.
+         */
+        if (rx_byte == '*')
+        {
+          state = RX_CHECKSUM;
+        }
+        else
+        {
+          calc_checksum ^= rx_byte;
+        }
+      }
+      break;
+    case RX_CHECKSUM:
+      /* RX_CHECKSUM: The system receives the checksum (first two bytes
+       * after *). After CR (\r) character is received, the system
+       * compares it to the calculated value. If two values match,
+       * a new checksum is calculated and added to the message.
+       * Upon receiving the LF (\n) character which marks end of sentence
+       * system state changes to COPY_TO_TX.
+       */
+      if (rx_byte_ready)
+      {
+        rx_byte_ready = false;
+
+        /* If received character is $ or * or the buffer is overflown,
+         * reset and return to READY state.
+         */
+        if (rx_byte == '$'|| rx_byte == '*' || rx_buf_pos >= BUFFER_SIZE)
+        {
+          reset_rx();
+          break;
+        }
+        /* CR (\r) is received after the last character of checksum. */
+        else if (rx_byte == CR)
+        {
+          /* If match, calculate a new one, else reset. */
+          if (rx_checksum == calc_checksum)
+          {
+            uint8_t checksum = 0x00;
+            for (uint8_t i = 1; i < (rx_buf_pos - 1); i++)
+            {
+              checksum ^= rx_buffer[i];
+            }
+            rx_buffer[rx_buf_pos] = hex_chars[(checksum & 0xF0) >> 4];
+            rx_buf_pos++;
+            rx_buffer[rx_buf_pos] = hex_chars[checksum & 0x0F];
+            rx_buf_pos++;
+            rx_buffer[rx_buf_pos] = rx_byte;
+            rx_buf_pos++;
+          }
+          else
+          {
+            reset_rx();
+          }
+        }
+        /* LF (\n) is the last symbol of NMEA message. */
+        else if (rx_byte == LF)
+        {
+          rx_buffer[rx_buf_pos] = rx_byte;
+          rx_buf_pos++;
+          state = COPY_TO_TX;
+        }
+        /* Characters 0-9 and A-F are converted to numbers and added to checksum.
+         * Digit symbols have values 0x30-0x39. Capital letters start from 0x41.
+         * If the received byte is a letter (val. 0x4X) we substract 0x07
+         * to convert the value to 0x3A-0x3F. Bitwise AND with 0x0F converts
+         * the value to 0x00-0x0F.
+         * Previous value of the received checksum is rotated 4 bits left. If the first
+         * byte of the checksum was received, the value is 0x00 and isn't affected.
+         * If the second byte is received, the value 0x0X becomes 0xX0 leaving a
+         * place for the second digit.
+         */
+        else
+        {
+          uint8_t val = rx_byte;
+          if (val & 0x40)
+            val -= 0x07;
+          val &= 0x0F;
+          rx_checksum <<= 4;
+          rx_checksum |= val;
+        }
+      }
+      break;
+    case COPY_TO_TX:
+      /* COPY_TO_TX: The received and reformatted message is transferred
+       * to TX buffer. If the buffer isn't empty, the system remains in
+       * this state until the previous message is sent. After sending
+       * the message to TX system moves to READY state.
+       */
+
+
+      /* The new message is sent to TX only if it has no message to send. */
+      if (!tx_not_empty)
+      {
+        copy_msg_to_tx_buf();
+        tx_buf_pos = 0;
+        tx_not_empty = true;
+        reset_rx();
+      }
+      PORTD = PORTD &= 0B00000000; /* Turn all the leds off. */
+      break;
+    }
+    /* End RX routine */
+  }
+
+/*
+ * Function: process_field
+ * -----------------------
+ *   Determines message type from the 0th field and changes the length
+ *   of last received field if required.
+ *
+ *   returns: True if the field was valid, false if the message has to be discarded
+ *        due to current field's value.
+ */
+bool process_field(void)
+{
+  bool res = true;
+
+  switch (rx_command)
+  {
+  case NONE: /* Determine command type */
+    if (rx_buffer[3] == 'G' && rx_buffer[4] == 'G' && rx_buffer[5] == 'A')
+    {
+      rx_command = GGA;
+      break;
+    }
+    if (rx_buffer[3] == 'R' && rx_buffer[4] == 'M' && rx_buffer[5] == 'C')
+    {
+      rx_command = RMC;
+      break;
+    }
+    if (rx_buffer[3] == 'Z' && rx_buffer[4] == 'D' && rx_buffer[5] == 'A')
+    {
+      rx_command = ZDA;
+      break;
+    }
+    res = false;
+    break;
+
+  case GGA:
+    /* NEO-6M vs. Yaesu VX-8
+     * NEO:    $GPGGA,094053.00,3204.41475,N,03445.96499,E,1,09,1.12,28.7,M,17.5,M,,*69
+     * VX8:    $GPGGA,095142.196,4957.5953,N,00811.9616,E,0,00,99.9,00234.7,M,0047.9,M,000.0,0000*42
+     * Dif:    $GPGGA,094053.00_,3204.4147X,N,03445.9649X,E,1,09,_1.1X,___28.7,M,__17.5,M,___._,____*69
+     * Fields:      0          1          2 3           4 5 6  7     8       9 A      B C     D    E
+     */
+    if (rx_field == 0x01)
+    {
+      /* If time field is empty all the message is discarded. */
+      if (rx_field_size == 0)
+      {
+        PORTD = PORTD |= 0B00100000; /* Turn the red LED on. */
+        res = false;
+        break;
+      }
+      /* Time field is fixed to 10 characters: hhmmss.sss */
+      fix_decimal_field_len(&rx_buffer[rx_buf_pos - rx_field_size], 6, 3);
+      rx_buf_pos -= rx_field_size;
+      rx_buf_pos += 10;
+    }
+    else if (rx_field == 0x02)
+    {
+      if (rx_field_size == 0)
+        PORTD = PORTD |= 0B10100000; /* Red LED on. */
+      else
+        PORTD = PORTD |= 0B10000000; /* Green LED on. */
+      /* Latitude field is fixed to 9 characters: ddmm.ssss */
+      fix_decimal_field_len(&rx_buffer[rx_buf_pos - rx_field_size], 4, 4);
+      rx_buf_pos -= rx_field_size;
+      rx_buf_pos += 9;
+    }
+    else if (rx_field == 0x03)
+    {
+      /* Latitude N/S field is set to N if empty. */
+      if (rx_field_size == 0)
+      {
+        rx_buffer[rx_buf_pos] = 'N';
+        rx_buf_pos++;
+      }
+    }
+    else if (rx_field == 0x04)
+    {
+      /* Longitude field is fixed to 10 characters: dddmm.ssss */
+      fix_decimal_field_len(&rx_buffer[rx_buf_pos - rx_field_size], 5, 4);
+      rx_buf_pos -= rx_field_size;
+      rx_buf_pos += 10;
+    }
+    else if (rx_field == 0x05)
+    {
+      /* Longitude E/W field is set to E if empty. */
+      if (rx_field_size == 0)
+      {
+        rx_buffer[rx_buf_pos] = 'E';
+        rx_buf_pos++;
+      }
+    }
+    else if (rx_field == 0x07)
+    {
+      /* Number of satellites is integer fixed to 2 characters. */
+      fix_int_field_len(&rx_buffer[rx_buf_pos - rx_field_size], 2);
+      rx_buf_pos -= rx_field_size;
+      rx_buf_pos += 2;
+    }
+    else if (rx_field == 0x08)
+    {
+      /* Horizontal dilution of position field is fixed to 4 characters: xx.x.
+       * In the case of NEO-U-6 it means that one character after the decimal point
+       * will be truncated. */
+      fix_decimal_field_len(&rx_buffer[rx_buf_pos - rx_field_size], 2, 1);
+      rx_buf_pos -= rx_field_size;
+      rx_buf_pos += 4;
+    }
+    else if (rx_field == 0x09)
+    {
+      /* Altitude above mean sea is fixed to 7 characters: aaaaa.a */
+      fix_decimal_field_len(&rx_buffer[rx_buf_pos - rx_field_size], 5, 1);
+      rx_buf_pos -= rx_field_size;
+      rx_buf_pos += 7;
+    }
+    else if (rx_field == 0x0A)
+    {
+      /* Altitude units are set to M (meters) if this field is empty. */
+      if (rx_field_size == 0)
+      {
+        rx_buffer[rx_buf_pos] = 'M';
+        rx_buf_pos++;
+      }
+    }
+    else if (rx_field == 0x0B)
+    {
+      /* Height of geoid field is fixed to 6 characters: ddd.mm */
+      fix_decimal_field_len(&rx_buffer[rx_buf_pos - rx_field_size], 4, 1);
+      rx_buf_pos -= rx_field_size;
+      rx_buf_pos += 6;
+    }
+    else if (rx_field == 0x0C)
+    {
+      /* Altitude units are set to M (meters) if this field is empty. */
+      if (rx_field_size == 0)
+      {
+        rx_buffer[rx_buf_pos] = 'M';
+        rx_buf_pos++;
+      }
+    }
+    else if (rx_field == 0x0D)
+    {
+      /* Time since last DGPS update field is fixed to 5 characters: ddd.m */
+      fix_decimal_field_len(&rx_buffer[rx_buf_pos - rx_field_size], 3, 1);
+      rx_buf_pos -= rx_field_size;
+      rx_buf_pos += 5;
+    }
+    else if (rx_field == 0x0E)
+    {
+      /* DGPS station ID number is integer fixed to 4 characters. */
+      fix_int_field_len(&rx_buffer[rx_buf_pos - rx_field_size], 4);
+      rx_buf_pos -= rx_field_size;
+      rx_buf_pos += 4;
+    }
+    break;
+
+  case RMC:
+    /* NEO-6M vs. Yaesu VX-8
+     * NEO:    $GPRMC,094054.00,A,3204.41446,N,03445.96604,E,3.876,110.45,231215,,,A*62
+     * VX8:    $GPRMC,095142.196,V,4957.5953,N,00811.9616,E,9999.99,999.99,080810,,*2C
+     * Dif:    $GPRMC,094054.00_,A,3204.4144X,N,03445.9660X,E,___3.87X,110.45,231215,,XX*62
+     * Fields:      0          1 2          3 4           5 6        7      8      9
+     */
+    if (rx_field == 0x01)
+    {
+      /* If time field is empty all the message is discarded. */
+      if (rx_field_size == 0)
+      {
+        PORTD = PORTD |= 0B00000100; /* Turn the red LED on. */
+        res = false;
+        break;
+      }
+      /* Time field is fixed to 10 characters: hhmmss.sss */
+      fix_decimal_field_len(&rx_buffer[rx_buf_pos - rx_field_size], 6, 3);
+      rx_buf_pos -= rx_field_size;
+      rx_buf_pos += 10;
+    }
+    else if (rx_field == 0x03)
+    {
+      if (rx_field_size == 0)
+        PORTD = PORTD |= 0B00000100; /* Red LED on. */
+      else
+        PORTD = PORTD |= 0B00010000; /* Green LED on. */
+      /* Latitude field is fixed to 9 characters: ddmm.ssss */
+      fix_decimal_field_len(&rx_buffer[rx_buf_pos - rx_field_size], 4, 4);
+      rx_buf_pos -= rx_field_size;
+      rx_buf_pos += 9;
+    }
+    else if (rx_field == 0x04)
+    {
+      /* Latitude N/S field is set to N if empty. */
+      if (rx_field_size == 0)
+      {
+        rx_buffer[rx_buf_pos] = 'N';
+        rx_buf_pos++;
+      }
+    }
+    else if (rx_field == 0x05)
+    {
+      /* Longitude field is fixed to 10 characters: dddmm.ssss */
+      fix_decimal_field_len(&rx_buffer[rx_buf_pos - rx_field_size], 5, 4);
+      rx_buf_pos -= rx_field_size;
+      rx_buf_pos += 10;
+    }
+    else if (rx_field == 0x06)
+    {
+      /* Longitude E/W field is set to E if empty. */
+      if (rx_field_size == 0)
+      {
+        rx_buffer[rx_buf_pos] = 'E';
+        rx_buf_pos++;
+      }
+    }
+    else if (rx_field == 0x07)
+    {
+      /* Speed field is fixed to 7 characters: ssss.ss */
+      fix_decimal_field_len(&rx_buffer[rx_buf_pos - rx_field_size], 4, 2);
+      rx_buf_pos -= rx_field_size;
+      rx_buf_pos += 7;
+    }
+    else if (rx_field == 0x08)
+    {
+      /* Track angle field is fixed to 6 characters: ddd.mm */
+      fix_decimal_field_len(&rx_buffer[rx_buf_pos - rx_field_size], 3, 2);
+      rx_buf_pos -= rx_field_size;
+      rx_buf_pos += 6;
+    }
+    break;
+
+  case ZDA:
+    /* NEO-6M vs. Yaesu VX-8
+     * NEO:    $GPZDA,142615.00,26,12,2015,,*52
+     * VX8:    $GPZDA,095143.196,08,08,2010,,*51
+     * Dif:    $GPZDA,142615.00_,26,12,2015,,*52
+     * Fields:      0          1  2  3    4
+     */
+    if (rx_field == 1)
+    {
+      /* If time field is empty all the message is discarded. */
+      if (rx_field_size == 0)
+      {
+        res = false;
+        break;
+      }
+      /* Time field is fixed to 10 characters: hhmmss.sss */
+      fix_decimal_field_len(&rx_buffer[rx_buf_pos - rx_field_size], 6, 3);
+      rx_buf_pos -= rx_field_size;
+      rx_buf_pos += 10;
+    }
+    break;
+  }
+  return res;
+}
+
+/*
+ * Function: reset_tx
+ * ------------------
+ *   Resets transmit buffer to ready-to-send state.
+ *
+ *   returns: none
+ */
+void reset_tx(void)
+{
+  for (uint8_t i = 0; i < BUFFER_SIZE; i++)
+  {
+    tx_buffer[i] = 0x00;
+  }
+  tx_buf_pos = 0;
+  tx_not_empty = false;
+}
+
+/*
+ * Function: reset_rx
+ * ------------------
+ *   Resets receive buffer and sets system state to READY.
+ *
+ *   returns: none
+ */
+void reset_rx(void)
+{
+  for (uint8_t i = 0; i < BUFFER_SIZE; i++)
+  {
+    rx_buffer[i] = 0x00;
+  }
+  rx_buf_pos = 0;
+  calc_checksum = 0x00;
+  rx_checksum = 0x00;
+  rx_command = NONE;
+  rx_field = 0;
+  rx_field_size = 0;
+  state = READY;
+}
+
+/*
+ * Function: copy_msg_to_tx_buf
+ * ----------------------------
+ *   Copies contents of RX buffer to TX buffer.
+ *
+ *   returns: none
+ */
+void copy_msg_to_tx_buf(void)
+{
+  for (uint8_t i = 0; i < BUFFER_SIZE; i++)
+  {
+    tx_buffer[i] = rx_buffer[i];
+  }
+}
+
+/* UART routines */
+/* UART initialization */
+void usart0_init(void)
+{
+  // Set baud rate
+  //UBRR0 = UBRR_VALUE;
+  UBRR0H = (uint8_t) (UBRR_VALUE >> 8);
+  UBRR0L = (uint8_t) UBRR_VALUE;
+  // Set frame format to 8 data bits, no parity, 1 stop bit
+  UCSR0C |= (1 << UCSZ01) | (1 << UCSZ00);
+  //enable reception and RC complete interrupt
+  UCSR0B |= (1 << RXEN0) | (1 << RXCIE0);
+  //enable transmission and UDR0 empty interrupt
+  UCSR0B |= (1 << TXEN0) | (1 << UDRIE0);
+}
+
+//RX Complete interrupt service routine
+ISR(USART_RX_vect)
+{
+  rx_byte = UDR0;
+  rx_byte_ready = true;
+}
+
+ISR(USART_UDRE_vect)
+{
+  tx_rts = true;
+}
+


Add sketch_tc_hitachi

Add sketch_tc_hitachi

error: pathspec 'arduino/sketch_tc_hitachi/sketch_tc_hitachi.ino' did not match any file(s) known to git

--- arduino/sketch_tc_hitachi/sketch_tc_hitachi.ino
+++ arduino/sketch_tc_hitachi/sketch_tc_hitachi.ino
@@ -0,0 +1,443 @@
+/* serial data format:
+    header:
+      flag: 1 byte, 0xaa, filter out debug message
+      type: 1 bytes, 1: upstream, 2: downstream
+      body length: 2 bytes, network byte order
+    body:
+      json data from yunba service
+*/
+#include <ArduinoJson.h>
+
+#ifndef cbi
+#define cbi(sfr, bit) (_SFR_BYTE(sfr) &= ~_BV(bit))
+#endif
+#ifndef sbi
+#define sbi(sfr, bit) (_SFR_BYTE(sfr) |= _BV(bit))
+#endif
+
+#define MSG_TYPE_UP 0x01
+#define MSG_TYPE_DOWN 0x02
+
+#define HEADER_LEN 4
+#define BUF_LEN 256
+#define FLAG_CHAR 0xaa
+
+#define COM_NUM 3
+#define SEG_NUM 5
+
+#define COM_PIN A0
+#define SEG_PIN A3
+
+#define PIN_ON_OFF 9
+#define PIN_MODE 10
+#define PIN_FAN 11
+#define PIN_INC 12
+#define PIN_DEC 13
+
+#define MIN_ANALOG_V 256
+#define MAX_ANALOG_V 938
+
+#define MAX_STABLE_CNT 32 /* 去除波动，MAX_STABLE_CNT 次数据不变后才上报状态 */
+
+#define SIM_BTN_DELAY 32
+
+const char *g_devid = "temp_ctrl_1";
+
+uint8_t g_header[HEADER_LEN];
+uint8_t g_buf[BUF_LEN];
+int g_step = 1; // 1: recv header, 2 recv body
+uint16_t g_body_len = 0;
+uint16_t g_recv_len = 0;
+
+char g_need_report = 0;
+unsigned long g_check_ms = 0;
+
+uint8_t g_com[COM_NUM];
+uint8_t g_seg[SEG_NUM];
+uint8_t g_com_cmp[COM_NUM];
+
+uint8_t g_data_last[COM_NUM][SEG_NUM];
+uint8_t g_data[COM_NUM][SEG_NUM];
+uint8_t g_data_stable[COM_NUM][SEG_NUM];
+
+uint8_t g_on_off; /* 1: on, 2: off */
+uint8_t g_mode; /* 1: cool(制冷), 2: hot(制热), 3: dry(抽湿), 4: blast(送风) */
+uint8_t g_fan; /* 1: hi(高风), 2: mid(中风), 3: low(低风), 4: auto(自动) */
+int8_t g_set_temp;
+int8_t g_room_temp;
+
+uint8_t g_stable_cnt = 0; /* 稳定次数 */
+uint8_t g_stable_cnt_slave = 0;
+
+void recv_header() {
+  while (Serial.available() >= HEADER_LEN) {
+    Serial.readBytes(g_header, 1);
+//    //Serial.println((uint8_t)g_header[0], HEX);
+    if (g_header[0] == FLAG_CHAR) {
+      break;
+    }
+  }
+
+  if (g_header[0] != FLAG_CHAR) { // no enough data
+    return;
+  }
+
+  Serial.readBytes(g_header + 1, HEADER_LEN - 1);
+
+  if (g_header[1] != MSG_TYPE_DOWN) { // not a downstream message
+    return;
+  }
+
+  /* le */
+  ((uint8_t *)&g_body_len)[0] = g_header[3];
+  ((uint8_t *)&g_body_len)[1] = g_header[2];
+
+  //Serial.println("len:");
+  //Serial.println(g_body_len);
+
+  if (g_body_len > (BUF_LEN - HEADER_LEN - 1)) {
+    //Serial.println("too long msg");
+    return;
+  }
+
+  g_recv_len = 0;
+  g_step = 2;
+}
+
+void recv_body() {
+  uint16_t len = 0;
+
+  len = Serial.readBytes(g_buf + g_recv_len, g_body_len - g_recv_len);
+  g_recv_len += len;
+
+  if (g_recv_len != g_body_len) {
+    return;
+  }
+
+  // now got a completed msg
+  g_buf[g_body_len] = 0;
+  //Serial.println("got a msg:");
+  //Serial.println((char *)g_buf);
+
+  handle_msg();
+
+  memset(g_header, 0, HEADER_LEN);
+  g_step = 1;
+}
+
+void handle_msg() {
+  StaticJsonBuffer<256> jsonBuffer;
+
+  JsonObject& root = jsonBuffer.parseObject((char *)g_buf);
+  if (!root.success()) {
+    //Serial.println("parseObject");
+    return;
+  }
+
+  if (strcmp(root["devid"], g_devid) != 0) {
+    //Serial.println("not my devid");
+    return;
+  }
+
+  Serial.println((const char *)root["cmd"]);
+
+  if (strcmp(root["cmd"], "on_off") == 0) {
+    digitalWrite(PIN_ON_OFF, LOW);
+    delay(SIM_BTN_DELAY);
+    digitalWrite(PIN_ON_OFF, HIGH);
+  } else if (strcmp(root["cmd"], "mode") == 0) {
+    digitalWrite(PIN_MODE, LOW);
+    delay(SIM_BTN_DELAY);
+    digitalWrite(PIN_MODE, HIGH);
+  } else if (strcmp(root["cmd"], "fan") == 0) {
+    digitalWrite(PIN_FAN, LOW);
+    delay(SIM_BTN_DELAY);
+    digitalWrite(PIN_FAN, HIGH);
+  } else if (strcmp(root["cmd"], "inc") == 0) {
+    digitalWrite(PIN_INC, LOW);
+    delay(SIM_BTN_DELAY);
+    digitalWrite(PIN_INC, HIGH);
+  } else if (strcmp(root["cmd"], "dec") == 0) {
+    digitalWrite(PIN_DEC, LOW);
+    delay(SIM_BTN_DELAY);
+    digitalWrite(PIN_DEC, HIGH);
+  } else if (strcmp(root["cmd"], "get") == 0) {
+    g_need_report = 1;
+  }
+}
+
+void send_msg() {
+  g_buf[0] = FLAG_CHAR;
+  g_buf[1] = MSG_TYPE_UP; // 1: upstream
+  g_buf[2] = ((uint8_t *)&g_body_len)[1];
+  g_buf[3] = ((uint8_t *)&g_body_len)[0];
+
+  Serial.write(g_buf, g_body_len + HEADER_LEN);
+}
+
+void report_status() {
+  StaticJsonBuffer<256> jsonBuffer;
+  JsonObject& root = jsonBuffer.createObject();
+
+  root["devid"] = g_devid;
+
+  JsonArray& st = root.createNestedArray("status");
+
+  st.add(g_on_off);
+  st.add(g_mode);
+  st.add(g_fan);
+  st.add(g_set_temp);
+  st.add(g_room_temp);
+
+  g_body_len = root.printTo((char *)g_buf + HEADER_LEN, BUF_LEN - HEADER_LEN);
+
+  send_msg();
+}
+
+void handle_input() {
+  if (g_step == 1) {
+    recv_header();
+  } else if (g_step == 2) {
+    recv_body();
+  }
+}
+
+inline int trans(int i) {
+  if (i < MIN_ANALOG_V) {
+    return 0;
+  } else if (i > MAX_ANALOG_V) {
+    return 1;
+  } else {
+    return 2;
+  }
+}
+
+int make_number(int seg_index) {
+  uint8_t i = 0;
+  uint8_t j = 0;
+  uint8_t k = 0;
+
+  /* 十位: BCD */
+  i = g_data[0][seg_index + 2];
+  i += g_data[1][seg_index + 2] * 2;
+  i += g_data[2][seg_index + 2] * 4;
+
+  switch (i) {
+    case 0x03:
+      j = 1;
+      break;
+    case 0x05:
+      j = 2;
+      break;
+    case 0x07:
+      j = 3;
+      break;
+    default:
+      //j = 0;
+      //break;
+      return -1;
+  }
+
+  /* 个位: BCDFGE */
+  i = g_data[0][seg_index];
+  i += g_data[1][seg_index] * 2;
+  i += g_data[2][seg_index] * 4;
+  i += g_data[0][seg_index + 1] * 8;
+  i += g_data[1][seg_index + 1] * 16;
+  i += g_data[2][seg_index + 1] * 32;
+
+  switch (i) {
+    case 0x2f:
+      k = 0;
+      break;
+    case 0x03:
+      k = 1;
+      break;
+    case 0x35:
+      k = 2;
+      break;
+    case 0x17:
+      k = 3;
+      break;
+    case 0x1b:
+      k = 4;
+      break;
+    case 0x1e:
+      k = 5;
+      break;
+    case 0x3e:
+      k = 6;
+      break;
+    case 0x0b:
+      k = 7;
+      break;
+    case 0x3f:
+      k = 8;
+      break;
+    case 0x1f:
+      k = 9;
+      break;
+    default:
+      return -1;
+  }
+
+  return j * 10 + k;
+}
+
+void handle_status() {
+  int i = 0;
+  int j = 0;
+  int v = 0;
+  int f = 1;
+  int t = 64;
+  uint8_t z = 0; /* 过滤掉全 0 的无效数据 */
+
+  while (t--) {
+    /* 读 COM */
+    for (i = 0; i < COM_NUM; i++) {
+      g_com_cmp[i] = trans(analogRead(COM_PIN + i));
+    }
+    /* 读 SEG */
+    for (i = 0; i < SEG_NUM; i++) {
+      g_seg[i] = trans(analogRead(SEG_PIN + i));
+    }
+
+    /* 再读 COM */
+    for (i = 0; i < COM_NUM; i++) {
+      g_com[i] = trans(analogRead(COM_PIN + i));
+    }
+
+    /* 两次读的 COM 没变，才认为这次读的有效 */
+    if (memcmp(g_com, g_com_cmp, COM_NUM) == 0) {
+      //Serial.println("valid reading");
+      break;
+    }
+  }
+
+  for (i = 0; i < COM_NUM; i++) {
+    if (g_com[i] == 2) {
+      continue;
+    }
+    for (j = 0; j < SEG_NUM; j++) {
+      if (g_seg[j] == 2) {
+        g_data[i][j] = 0;
+      } else {
+        g_data[i][j] = abs(g_com[i] - g_seg[j]);
+        z |= g_data[i][j];
+      }
+    }
+  }
+  if (!z) {
+    return;
+  }
+
+  if (memcmp(g_data, g_data_last, COM_NUM * SEG_NUM) == 0) {
+    if (g_stable_cnt >= MAX_STABLE_CNT) {
+      return; /* reported */
+    }
+    g_stable_cnt++;
+    if (g_stable_cnt < MAX_STABLE_CNT) {
+      return;
+    }
+    if (memcmp(g_data_stable, g_data, COM_NUM * SEG_NUM) == 0) {
+      g_stable_cnt = 0;
+      return;
+    } else {
+      memcpy(g_data_stable, g_data, COM_NUM * SEG_NUM);
+    }
+  } else {
+    memcpy(g_data_last, g_data, COM_NUM * SEG_NUM);
+    g_stable_cnt = 0;
+    return;
+  }
+
+#if 1
+  for (i = 0; i < COM_NUM; i++) {
+    for (j = 0; j < SEG_NUM; j++) {
+      Serial.print(g_data[i][j], DEC);
+    }
+    Serial.println();
+  }
+#endif
+
+  /* g_mode */
+  if (g_data[0][4]) {
+    g_mode = 1;
+  } else if (g_data[1][4]) {
+    g_mode = 2;
+  } else if (g_data[2][4]) {
+    g_mode = 3;
+  } else {
+    g_mode = 4;
+  }
+
+  /* g_fan */
+  if (g_data[0][3]) {
+    g_fan = 2;
+  } else if (g_data[1][3]) {
+    g_fan = 3;
+  } else {
+    g_fan = 1;
+  }
+
+  /* g_set_temp */
+  g_set_temp = make_number(0);
+  g_need_report = 1;
+}
+
+void print_status() {
+  Serial.println("status:");
+  Serial.print(' ');
+  Serial.print(g_on_off, DEC);
+  Serial.print(' ');
+  Serial.print(g_mode, DEC);
+  Serial.print(' ');
+  Serial.print(g_fan, DEC);
+  Serial.print(' ');
+  Serial.print(g_set_temp, DEC);
+  Serial.print(' ');
+  Serial.println(g_room_temp);
+}
+
+void setup() {
+  // put your setup code here, to run once:
+  Serial.begin(115200);
+  Serial.println("setup...");
+
+  sbi(ADCSRA,ADPS2);
+  cbi(ADCSRA,ADPS1);
+  cbi(ADCSRA,ADPS0);
+
+  for (int i = 0; i < COM_NUM; i++) {
+    pinMode(COM_PIN + i, INPUT);
+  }
+  for (int i = 0; i < SEG_NUM; i++) {
+    pinMode(SEG_PIN + i, INPUT);
+  }
+
+  pinMode(PIN_ON_OFF, OUTPUT);
+  digitalWrite(PIN_ON_OFF, HIGH);
+  pinMode(PIN_MODE, OUTPUT);
+  digitalWrite(PIN_MODE, HIGH);
+  pinMode(PIN_FAN, OUTPUT);
+  digitalWrite(PIN_FAN, HIGH);
+  pinMode(PIN_INC, OUTPUT);
+  digitalWrite(PIN_INC, HIGH);
+  pinMode(PIN_DEC, OUTPUT);
+  digitalWrite(PIN_DEC, HIGH);
+}
+
+void loop() {
+  // put your main code here, to run repeatedly:
+  handle_input();
+
+  handle_status();
+
+  if (g_need_report) {
+    print_status();
+//    report_status();
+    g_need_report = 0;
+  }
+
+//  delay(100);
+}


Delete Esparto_SONOFF.ino

Delete Esparto_SONOFF.ino

remote: Support for password authentication was removed on August 13, 2021.
remote: Please see https://docs.github.com/en/get-started/getting-started-with-git/about-remote-repositories#cloning-with-https-urls for information on currently recommended modes of authentication.
fatal: Authentication failed for 'https://github.com/philbowles/esparto.git/'