 
 // Setup routine: runs once after reset.
 void setup()
-  {
-  //----------- Low level setup
-  // Set appropriate low-power states, interrupts, etc, ASAP.
-  OTV0P2BASE::powerSetup();
-
-  // IO setup for safety, and to avoid pins floating.
-  OTV0P2BASE::IOSetup();
-  
-  OTV0P2BASE::serialPrintAndFlush(F("\r\nOpenTRV: ")); // Leading CRLF to clear leading junk, eg from bootloader.
+{
+    //----------- Low level setup
+    // Set appropriate low-power states, interrupts, etc, ASAP.
+    OTV0P2BASE::powerSetup();
+  
+    // IO setup for safety, and to avoid pins floating.
+    OTV0P2BASE::IOSetup();
+    
+    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);
-  eeprom_write_byte((uint8_t *)V0P2BASE_EE_START_RESET_COUNT, 1 + oldResetCount);
-  
-  // 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.
-
-  //----------- Flash UI
-  // Signal that xtal is running AND give it time to settle.
-  OTV0P2BASE::sleepLowPowerMs(1000);
-  OTV0P2BASE::LED_HEATCALL_OFF();
-
-  OTV0P2BASE::sleepLowPowerMs(PP_OFF_MS); // TODO: use this time to gather entropy.
-  OTV0P2BASE::LED_HEATCALL_ON();
-  OTV0P2BASE::sleepLowPowerMs(1000); // TODO: use this time to gather entropy.
-
-
-  //----------- Init Radio
-  // 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")); }
-
-  // Turn power on for SIM900 with PFET for secondary power control.
-  fastDigitalWrite(A3, 0); // todo move into sim900link
-  pinMode(A3, OUTPUT);
-  // Initialise the radio, if configured, ASAP because it can suck a lot of power until properly initialised.
-  SecondaryRadio.preinit(NULL);
-  // Check that the radio is correctly connected; panic if not...
-  if(!SecondaryRadio.configure(1, &SecondaryRadioConfig) || !SecondaryRadio.begin()) { panic(F("r2")); }
-
-  //----------- Init sensors
-  // Collect full set of environmental values before entering loop() in normal mode.
-  // This should also help ensure that sensors are properly initialised.
-  const int heat = TemperatureC16.read();
-  const uint16_t Vcc = Supply_cV.read();
-
-  OTV0P2BASE::seedPRNGs();
-
-  //----------- Ensure has ID
-  // 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)) // Force reset.
-      { panic(F("ID")); }
-    }
-
-  // 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 = 1 ;
-
-    PCMSK0 = RFM23B_INT_MASK;
-    }
-
-  PrimaryRadio.listen(true);  // XXX may be switched off and left that way somewhere
-  
-  // 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();
-  }
+    
+    // Count resets to detect unexpected crashes/restarts.
+    const uint8_t oldResetCount = eeprom_read_byte((uint8_t *)V0P2BASE_EE_START_RESET_COUNT);
+    eeprom_write_byte((uint8_t *)V0P2BASE_EE_START_RESET_COUNT, 1 + oldResetCount);
+    
+    // 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.
+  
+    //----------- Flash UI
+    // Signal that xtal is running AND give it time to settle.
+    OTV0P2BASE::sleepLowPowerMs(1000);
+    OTV0P2BASE::LED_HEATCALL_OFF();
+  
+    OTV0P2BASE::sleepLowPowerMs(PP_OFF_MS); // TODO: use this time to gather entropy.
+    OTV0P2BASE::LED_HEATCALL_ON();
+    OTV0P2BASE::sleepLowPowerMs(1000); // TODO: use this time to gather entropy.
+  
+    //----------- Init Radio
+    // 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")); }
+    
+    // Turn power on for SIM900 with PFET for secondary power control.
+    fastDigitalWrite(A3, 0); // todo move into sim900link
+    pinMode(A3, OUTPUT);
+    // Initialise the radio, if configured, ASAP because it can suck a lot of power until properly initialised.
+    SecondaryRadio.preinit(NULL);
+    // Check that the radio is correctly connected; panic if not...
+    if(!SecondaryRadio.configure(1, &SecondaryRadioConfig) || !SecondaryRadio.begin()) { panic(F("r2")); }
+  
+    OTV0P2BASE::seedPRNGs();
+  
+    //----------- Ensure has ID
+    // 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.
+    // Enable pin change interrupt on PB, RFM23B interrupt pin.
+    ATOMIC_BLOCK (ATOMIC_RESTORESTATE) { PCICR = 1; PCMSK0 = RFM23B_INT_MASK; }
+    PrimaryRadio.listen(true);  // XXX might be switched off and left that way somewhere
+  
+    // Init sensors
+    TemperatureC16.read();
+    AmbLight.read();
+    
+    // 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

// which may mean deferring work at certain times
// such as the end of minor cycle.
// The Print object pointer must not be NULL.
bool handleQueuedMessages(Print *p, bool wakeSerialIfNeeded, OTRadioLink::OTRadioLink *rl)
  {
  // Avoid starting any potentially-slow processing very late in the minor cycle.
  // This is to reduce the risk of loop overruns
  // at the risk of delaying some processing
  // or even dropping some incoming messages if queues fill up.
  // Decoding (and printing to serial) a secure 'O' frame takes ~60 ticks (~0.47s).
  // Allow for up to 0.5s of such processing worst-case,
  // ie don't start processing anything later that 0.5s before the minor cycle end.
  const uint8_t sctStart = OTV0P2BASE::getSubCycleTime();
  if(sctStart >= ((OTV0P2BASE::GSCT_MAX/4)*3)) { return(false); }

  // Deal with any I/O that is queued.
    // 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();
    SecondaryRadio.poll();

  // XXX can probably strip these out.
  bool workDone = false;
  bool neededWaking = false; // Set true once this routine wakes Serial.
  const volatile uint8_t *pb;
  if(NULL != (pb = rl->peekRXMsg()))
    {
    if(wakeSerialIfNeeded && OTV0P2BASE::powerUpSerialIfDisabled<V0P2_UART_BAUD>()) { neededWaking = true; } // FIXME
    // Don't currently regard anything arriving over the air as 'secure'.
    // FIXME: shouldn't have to cast away volatile to process the message content.
    decodeAndHandleOTSecureableFrame(p, (const uint8_t *)pb);
    rl->removeRXMsg();
    // Note that some work has been done.
    workDone = true;
    }

  // Turn off serial at end, if this routine woke it.
  if(neededWaking) { OTV0P2BASE::flushSerialProductive(); OTV0P2BASE::powerDownSerial(); } // XXX move into if clause
  return(workDone);
  }

//========================================
// INTERRUPT SERVICE ROUTINES
//========================================
  //// Interrupt count.  Marked volatile so safe to read without a lock as is a single byte.
//static volatile uint8_t intCountPB;
// Previous state of port B pins to help detect changes.
static volatile uint8_t prevStatePB;
// Interrupt service routine for PB I/O port transition changes.
ISR(PCINT0_vect)
  {
//  ++intCountPB;
  const uint8_t pins = PINB;
  const uint8_t changes = pins ^ prevStatePB;
  prevStatePB = pins;
  // RFM23B nIRQ falling edge is of interest.
  // Handler routine not required/expected to 'clear' this interrupt.
  // TODO: try to ensure that OTRFM23BLink.handleInterruptSimple() is inlineable to minimise ISR prologue/epilogue time and space.
  if((changes & RFM23B_INT_MASK) && !(pins & RFM23B_INT_MASK))
    { PrimaryRadio.handleInterruptSimple(); }
  }



// Main loop for OpenTRV radiator control.
// Note: exiting and re-entering can take a little while, handling Arduino background tasks such as serial.
void loop()
{
    // CHECK STACK
    // ===============
    // Force restart if SPAM/heap/stack likely corrupt.
    OTV0P2BASE::MemoryChecks::forceResetIfStackOverflow();
    // Complain and keep complaining when getting near stack overflow.
    // TODO: make DEBUG-only when confident all configs OK.
    const int16_t minsp = OTV0P2BASE::MemoryChecks::getMinSPSpaceBelowStackToEnd();
    if(minsp < 64) { OTV0P2BASE::serialPrintlnAndFlush(F("!SH")); }
  
    // SLEEP
    // ===============
    // Sleep in low-power mode (waiting for interrupts) until seconds roll.
    // NOTE: sleep at the top of the loop to minimise timing jitter/delay from Arduino background activity after loop() returns.
    // DHD20130425: waking up from sleep and getting to start processing below this block may take >10ms.
    // Ensure that serial I/O is off while sleeping.
    OTV0P2BASE::powerDownSerial();
    // Power down most stuff (except radio for hub RX).
    OTV0P2BASE::minimisePowerWithoutSleep();
    uint_fast8_t newTLSD;
    while(TIME_LSD == (newTLSD = OTV0P2BASE::getSecondsLT())) {
        // Poll I/O and process message incrementally (in this otherwise idle time)
        // before sleep and on wakeup in case some IO needs further processing now,
        // eg work was accrued during the previous major slow/outer loop
        // or the in a previous orbit of this loop sleep or nap was terminated by an I/O interrupt.
        // May generate output to host on Serial.
        // Come back and have another go immediately until no work remaining.
        if(handleQueuedMessages(&Serial, &PrimaryRadio)) { continue; }
    
        // Normal long minimal-power sleep until wake-up interrupt.
        // Rely on interrupt to force quick loop round to I/O poll.
        OTV0P2BASE::sleepUntilInt();
    }
    TIME_LSD = newTLSD;
    // Reset and immediately re-prime the RTC-based watchdog.
    OTV0P2BASE::resetRTCWatchDog();
    OTV0P2BASE::enableRTCWatchdog(true);
  
  
    // START LOOP BODY
    // ===============  
    // Handling the UI may have taken a little while, so process I/O a little.
    handleQueuedMessages(&Serial, &PrimaryRadio); // Deal with any pending I/O.
    
    // 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.
    switch(TIME_LSD) { // With V0P2BASE_TWO_S_TICK_RTC_SUPPORT only even seconds are available.
        // Tasks that must be run every minute.
        // Note simple roll-over to 0 at max value.
        case 0: { ++minuteCount; break; }
        // Churn/reseed PRNG(s) a little to improve unpredictability in use: should be lightweight.
        case 2: { if(minuteCount & 3) { OTV0P2BASE::seedRNG8(minuteCount ^ OTV0P2BASE::getCPUCycleCount(), OTV0P2BASE::_getSubCycleTime(), 0xff); } break; }
    }
    
    // End-of-loop processing, that may be slow.
    // Ensure progress on queued messages ahead of slow work.  (TODO-867)
    handleQueuedMessages(&Serial, &PrimaryRadio); // Deal with any pending I/O.
}

