Beispiel #1
0
// Returns true iff RFM22 (or RFM23) appears to be correctly connected.
bool RFM22CheckConnected()
  {
  const bool neededEnable = powerUpSPIIfDisabled();
  bool isOK = false;
  const uint8_t rType = _RFM22ReadReg8Bit(0); // May read as 0 if not connected at all.
  if(RFM22_SUPPORTED_DEVICE_TYPE == rType)
    {
    const uint8_t rVersion = _RFM22ReadReg8Bit(1);
    if(RFM22_SUPPORTED_DEVICE_VERSION == rVersion)
      { isOK = true; }
#if 1 && defined(DEBUG)
    else
      {
      DEBUG_SERIAL_PRINT_FLASHSTRING("RFM22 bad version: ");
      DEBUG_SERIAL_PRINTFMT(rVersion, HEX);
      DEBUG_SERIAL_PRINTLN();
      }
#endif
    }
#if 1 && defined(DEBUG)
  else
    {
    DEBUG_SERIAL_PRINT_FLASHSTRING("RFM22 bad type: ");
    DEBUG_SERIAL_PRINTFMT(rType, HEX);
    DEBUG_SERIAL_PRINTLN();
    }
#endif
  if(neededEnable) { powerDownSPI(); }
  return(isOK);
  }
Beispiel #2
0
// Read the user 'temperature pot' setting in range [0,1023]; higher value implies higher target temperature.
// This may consume significant power and time.
int readTempPot()
  {
  power_intermittent_peripherals_enable(false); // No need to wait for anything to stablise as direct of IO_POWER_UP.
  const int tpRaw = analogueNoiseReducedRead(TEMP_POT_AIN, DEFAULT); // Vcc reference.
  power_intermittent_peripherals_disable();

#if defined(TEMP_POT_REVERSE)
  const int tp = TEMP_POT_RAW_MAX - tpRaw; // Travel is in opposite direction to natural!
#else
  const int tp = tpRaw;
#endif

  // TODO: capture entropy from changed LS bits esp if reduced-noise version doesn't change.

  // Store new value.
  tempPot = tp;

  // Capture reduced-noise value with a little hysteresis.
  const int shifted = tp >> 2; // Keep signed to avoid wrap-round confusion.
  if(((shifted > tempPotReducedNoise) && (shifted - tempPotReducedNoise >= RN_HYST)) ||
     ((shifted < tempPotReducedNoise) && (tempPotReducedNoise - shifted >= RN_HYST)))
    {
    const uint8_t rn = (uint8_t) shifted;

    // Smart responses to adjustment/movement of temperature pot.
    // Possible to get reasonable functionality without using MODE button.
    //
    // NOTE: without ignoredFirst this will also respond to the initial position of the pot
    //   as the first reading is taken, ie may force to WARM or BAKE.
    static bool ignoredFirst;
    if(!ignoredFirst) { ignoredFirst = true; }
    // Force FROST mode when right at bottom of dial.
    else if(rn < RN_FRBO) { setWarmModeDebounced(false); }
#ifdef SUPPORT_BAKE // IF DEFINED: this unit supports BAKE mode.
    // Start BAKE mode when dial turned up to top.
    else if(rn > (255-RN_FRBO)) { startBakeDebounced(); }
#endif
    // Force WARM mode if pot/temperature turned up.
    else if(rn > tempPotReducedNoise) { setWarmModeDebounced(true); }

    tempPotReducedNoise = rn;
    markUIControlUsed(); // Note user operation of pot.
    }

#if 0 && defined(DEBUG)
  DEBUG_SERIAL_PRINT_FLASHSTRING("Temp pot: ");
  DEBUG_SERIAL_PRINT(tp);
  DEBUG_SERIAL_PRINT_FLASHSTRING(", rn: ");
  DEBUG_SERIAL_PRINT(tempPotReducedNoise);
  DEBUG_SERIAL_PRINTLN();
#endif

  return(tp);
  }
Beispiel #3
0
// Enter standby mode.
// SPI must already be configured and running.
static void _RFM22ModeStandby()
  {
  _RFM22WriteReg8Bit(RFM22REG_OP_CTRL1, 0);
#if 0 && defined(DEBUG)
  DEBUG_SERIAL_PRINT_FLASHSTRING("Sb");
#endif
  }
Beispiel #4
0
// Configure the radio from a list of register/value pairs in readonly PROGMEM/Flash, terminating with an 0xff register value.
// NOTE: argument is not a pointer into SRAM, it is into PROGMEM!
// Could optimise case where multiple values are for successive RFM22 registers by using burst write.
void RFM22RegisterBlockSetup(const uint8_t registerValues[][2])
  {
  const bool neededEnable = powerUpSPIIfDisabled();
  for( ; ; )
    {
    const uint8_t reg = pgm_read_byte(&(registerValues[0][0]));
    const uint8_t val = pgm_read_byte(&(registerValues[0][1]));
    if(0xff == reg) { break; }
#if 0 && defined(DEBUG)
    DEBUG_SERIAL_PRINT_FLASHSTRING("RFM22 reg 0x");
    DEBUG_SERIAL_PRINTFMT(reg, HEX);
    DEBUG_SERIAL_PRINT_FLASHSTRING(" = 0x");
    DEBUG_SERIAL_PRINTFMT(val, HEX);
    DEBUG_SERIAL_PRINTLN();
#endif
    _RFM22WriteReg8Bit(reg, val);
    ++registerValues;
    }
  if(neededEnable) { powerDownSPI(); }
  }
Beispiel #5
0
// Called from startup() after some initial setup has been done.
// Can abort with panic() if need be.
void POSTalt()
  {
#ifdef USE_MODULE_RFM22RADIOSIMPLE
#if !defined(RFM22_IS_ACTUALLY_RFM23) && defined(DEBUG)
  DEBUG_SERIAL_PRINTLN_FLASHSTRING("(Using RFM22.)");
#endif
  // Initialise the radio, if configured, ASAP, because it can suck a lot of power until properly initialised.
  RFM22PowerOnInit();
  // Check that the radio is correctly connected; panic if not...
  if(!RFM22CheckConnected()) { panic(); }
  // Configure the radio.
  RFM22RegisterBlockSetup(FHT8V_RFM22_Reg_Values);
  // Put the radio in low-power standby mode.
  RFM22ModeStandbyAndClearState();
#endif
  // Force initialisation into low-power state.
  const int heat = TemperatureC16.read();
#if 1 && defined(DEBUG)
  DEBUG_SERIAL_PRINT_FLASHSTRING("temp: ");
  DEBUG_SERIAL_PRINT(heat);
  DEBUG_SERIAL_PRINTLN();
#endif
  const int light = AmbLight.read();
#if 1 && defined(DEBUG)
  DEBUG_SERIAL_PRINT_FLASHSTRING("light: ");
  DEBUG_SERIAL_PRINT(light);
  DEBUG_SERIAL_PRINTLN();
#endif



  // Trailing setup for the run
  // --------------------------




//  const bool foundSlaves = MinOW.reset();
  }
Beispiel #6
0
// Get approximate internal temperature in nominal C/16.
// Only accurate to +/- 10C uncalibrated.
// May set sleep mode to SLEEP_MODE_ADC, and disables sleep on exit.
int readInternalTemperatureC16()
  {
  // Measure internal temperature sensor against internal voltage source.
  // Response is ~1mv/C with 0C at ~289mV according to the data sheet.
  const uint16_t raw = OTV0P2BASE::_analogueNoiseReducedReadM(_BV(REFS1) | _BV(REFS0) | _BV(MUX3), 1);
#if 0 && defined(DEBUG)
  DEBUG_SERIAL_PRINT_FLASHSTRING("Int temp raw: ");
  DEBUG_SERIAL_PRINT(raw);
  DEBUG_SERIAL_PRINTLN_FLASHSTRING("");
#endif
  //const int degC = (raw - 328) ; // Crude fast adjustment for one sensor at ~20C (DHD20130429).
  const int degC = ((((int)raw) - 324) * 210) >> 4; // Slightly less crude adjustment, see http://playground.arduino.cc//Main/InternalTemperatureSensor
  return(degC);
  }
Beispiel #7
0
// Read the user 'temperature pot' setting in range [0,1023]; higher value implies higher target temperature.
// This may consume significant power and time.
int readTempPot()
  {
  power_intermittent_peripherals_enable(false); // No need to wait for anything to stablise as direct of IO_POWER_UP.
  const int tpRaw = analogueNoiseReducedRead(TEMP_POT_AIN, DEFAULT); // Vcc reference.
  power_intermittent_peripherals_disable();

#if defined(TEMP_POT_REVERSE)
  const int tp = TEMP_POT_RAW_MAX - tpRaw; // Travel is in opposite direction to natural!
#else
  const int tp = tpRaw;
#endif


  // TODO: capture entropy from changed LS bits esp if reduced-noise version doesn't change.

  // Store new value.
  tempPot = tp;

  // Capture reduced-noise value with a little hysteresis.
  const int shifted = tp >> 2; // Keep signed to avoid wrap-round confusion.
  if(((shifted > tempPotReducedNoise) && (shifted - tempPotReducedNoise >= RN_HYST)) ||
     ((shifted < tempPotReducedNoise) && (tempPotReducedNoise - shifted >= RN_HYST)))
    {
    tempPotReducedNoise = (uint8_t) shifted;
    markUIControlUsed(); // Note user operation of pot.
    }

#if 0 && defined(DEBUG)
  DEBUG_SERIAL_PRINT_FLASHSTRING("Temp pot: ");
  DEBUG_SERIAL_PRINT(tp);
  DEBUG_SERIAL_PRINT_FLASHSTRING(", rn: ");
  DEBUG_SERIAL_PRINT(tempPotReducedNoise);
  DEBUG_SERIAL_PRINTLN();
#endif

  return(tp);
  }
// Measure/store/return the current room ambient light levels in range [0,1023].
// This may consume significant power and time.
// Probably no need to do this more than (say) once per minute.
// This implementation expects LDR (1M dark resistance) from IO_POWER_UP to LDR_SENSOR_AIN and 100k to ground.
// (Not intended to be called from ISR.)
int readAmbientLight()
  {
  power_intermittent_peripherals_enable(false); // No need to wait for anything to stablise as direct of IO_POWER_UP.
  const int al = analogueNoiseReducedRead(LDR_SENSOR_AIN, DEFAULT); // Vcc reference.
  power_intermittent_peripherals_disable();

  // TODO: capture entropy from changed LS bits esp if reduced-noise version doesn't change.

  // Adjust room-lit flag, with hysteresis.
  if(al < LDR_THR_LOW)
    { isRoomLitFlag = false; }
  else if(al > LDR_THR_HIGH)
    {
    // Take sharp transition from dark to light as possible indication of occupancy, eg light flicked on.
    // TODO: consider refusal to trigger from zero to avoid power-up in light conditions causing transition. 
    if((!isRoomLitFlag) && (ambientLightLevel < LDR_THR_LOW)) { markAsPossiblyOccupied(); }

    isRoomLitFlag = true;
    }

  // Store new value.
  ambientLightLevel = al;

#if 0 && defined(DEBUG)
  DEBUG_SERIAL_PRINT_FLASHSTRING("Ambient light: ");
  DEBUG_SERIAL_PRINT(al);
  DEBUG_SERIAL_PRINTLN();
#endif

#if 0 && defined(DEBUG)
  DEBUG_SERIAL_PRINT_FLASHSTRING("isRoomLit: ");
  DEBUG_SERIAL_PRINT(isRoomLitFlag);
  DEBUG_SERIAL_PRINTLN();
#endif

  return(al);
  }
Beispiel #9
0
// Returns true iff room likely to be occupied and need warming at the specified hour's sample point based on collected stats.
// Used for predictively warming a room in smart mode and for choosing setback depths.
// Returns false if no good evidence to warm the room at the given time based on past history over about one week.
//   * hh hour to check for predictive warming [0,23]
bool shouldBeWarmedAtHour(const uint_least8_t hh)
  {
#ifndef OMIT_MODULE_LDROCCUPANCYDETECTION
  // Return false if the sample hour's historic ambient light level falls in the bottom quartile.
  // Thus avoid any 'smart' warming for at least 25% of the daily cycle.
  const uint8_t smoothedAmbLight = eeprom_read_byte((uint8_t *)(EE_START_LAST_AMBLIGHT_BY_HOUR_SMOOTHED + hh));
  if((STATS_UNSET_INT != smoothedAmbLight) && inBottomQuartile((uint8_t *)EE_START_LAST_AMBLIGHT_BY_HOUR_SMOOTHED, smoothedAmbLight))
    { return(false); }
#endif

  // Return false if no WARM mode this hour for the last week (ie the unit needs reminding at least once per week).
  // Return true if this hour was in WARM mode yesterday or a week ago, and at least one other day.
  const uint8_t warmHistory = eeprom_read_byte((uint8_t *)(EE_START_LAST_WARMMODE_BY_HOUR + hh));
  if(0 == (0x80 & warmHistory)) // This hour has a history.
    {
    if(0 == warmHistory) // No explicit WARM for a week at this hour, so prevent 'smart' warming.
      { return(false); }
    if((0 != (0x41 & warmHistory)) && (0 != (0x3e & warmHistory)))
      { return(true); }
    }

  // Return true if the sample hour is usually warm, ie at or above WARM target.
  const int smoothedTempHHNext = expandTempC16(eeprom_read_byte((uint8_t *)(EE_START_LAST_TEMP_BY_HOUR_SMOOTHED + hh)));
#if 0 && defined(DEBUG)
  DEBUG_SERIAL_PRINT_FLASHSTRING("Smoothed C for ");
  DEBUG_SERIAL_PRINT(hh);
  DEBUG_SERIAL_PRINT_FLASHSTRING("h is ");
  DEBUG_SERIAL_PRINT(smoothedTempHHNext >> 4);
  DEBUG_SERIAL_PRINTLN();
#endif
  if((STATS_UNSET_INT != smoothedTempHHNext) && (((smoothedTempHHNext+8)>>4) >= getWARMTargetC()))
    { return(true); }

  // No good evidence for room to be warmed for specified hour.
  return(false);
  }
Beispiel #10
0
// 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.
//#define STATS_MSG_START_OFFSET (RFM22_PREAMBLE_BYTES + RFM22_SYNC_MIN_BYTES)
//#define STATS_MSG_MAX_LEN (64 - STATS_MSG_START_OFFSET)
void RFM22RawStatsTXFFTerminated(uint8_t * const buf, const bool doubleTX, bool RFM23BFramed)
  {
  if(RFM23BFramed) RFM22RXPreambleAdd(buf);	// Only needed for RFM23B. This should be made more clear when refactoring
  const uint8_t buflen = OTRadioLink::frameLenFFTerminated(buf);
#if 0 && defined(DEBUG)
    DEBUG_SERIAL_PRINT_FLASHSTRING("buflen=");
    DEBUG_SERIAL_PRINT(buflen);
    DEBUG_SERIAL_PRINTLN();
#endif // DEBUG
  if(!PrimaryRadio.queueToSend(buf, buflen, 0, (doubleTX ? OTRadioLink::OTRadioLink::TXmax : OTRadioLink::OTRadioLink::TXnormal)))
    {
#if 0 && defined(DEBUG)
    DEBUG_SERIAL_PRINTLN_FLASHSTRING("!TX failed");
#endif
    } // DEBUG
  //DEBUG_SERIAL_PRINTLN_FLASHSTRING("RS");
  }
// Initialise the device (if any) before first use.
// Returns true iff successful.
// Uses specified order DS18B20 found on bus.
// May need to be reinitialised if precision changed.
bool TemperatureC16_DS18B20::init()
  {
//  DEBUG_SERIAL_PRINTLN_FLASHSTRING("DS18B20 init...");
  bool found = false;

  // Ensure no bad search state.
  minOW.reset_search();

  for( ; ; )
    {
    if(!minOW.search(address))
      {
      minOW.reset_search(); // Be kind to any other OW search user.
      break;
      }

#if 0 && defined(DEBUG)
    // Found a device.
    DEBUG_SERIAL_PRINT_FLASHSTRING("addr:");
    for(int i = 0; i < 8; ++i)
      {
      DEBUG_SERIAL_PRINT(' ');
      DEBUG_SERIAL_PRINTFMT(address[i], HEX);
      }
    DEBUG_SERIAL_PRINTLN();
#endif

    if(0x28 != address[0])
      {
#if 0 && defined(DEBUG)
      DEBUG_SERIAL_PRINTLN_FLASHSTRING("Not a DS18B20, skipping...");
#endif
      continue;
      }

#if 0 && defined(DEBUG)
    DEBUG_SERIAL_PRINTLN_FLASHSTRING("Setting precision...");
#endif
    minOW.reset();
    // Write scratchpad/config
    minOW.select(address);
    minOW.write(0x4e);
    minOW.write(0); // Th: not used.
    minOW.write(0); // Tl: not used.
//    MinOW.write(DS1820_PRECISION | 0x1f); // Config register; lsbs all 1.
    minOW.write(((precision - 9) << 6) | 0x1f); // Config register; lsbs all 1.

    // Found one and configured it!
    found = true;
    }

  // Search has been run (whether DS18B20 was found or not).
  initialised = true;

  if(!found)
    {
//    DEBUG_SERIAL_PRINTLN_FLASHSTRING("DS18B20 not found");
    address[0] = 0; // Indicate that no DS18B20 was found.
    }
  return(found);
  }
Beispiel #12
0
// Called from startup() after some initial setup has been done.
// Can abort with panic() if need be.
void POSTalt()
{
#ifdef USE_OTNULLRADIO
// FIXME
#elif defined USE_MODULE_SIM900
//The config for the GSM depends on if you want it stored in flash or EEPROM.
//
//The SIM900LinkConfig object is located at the start of POSTalt() in AltMain.cpp and takes a set of void pointers to a \0 terminated string, either stored in flash or EEPROM.
//
//For EEPROM:
//- Set the first field of SIM900LinkConfig to true.
//- The configs are stored as \0 terminated strings starting at 0x300.
//- You can program the eeprom using ./OTRadioLink/dev/utils/sim900eepromWrite.ino

//  static const void *SIM900_PIN      = (void *)0x0300; // TODO confirm this address
//  static const void *SIM900_APN      = (void *)0x0305;
//  static const void *SIM900_UDP_ADDR = (void *)0x031B;
//  static const void *SIM900_UDP_PORT = (void *)0x0329;
//  static const OTSIM900Link::OTSIM900LinkConfig_t SIM900Config {
//                                                  true,
//                                                  SIM900_PIN,
//                                                  SIM900_APN,
//                                                  SIM900_UDP_ADDR,
//                                                  SIM900_UDP_PORT };
//For Flash:
//- Set the first field of SIM900LinkConfig to false.
//- Make a set of \0 terminated strings with the PROGMEM attribute holding the config details.
//- set the void pointers to point to the strings (or just cast the strings and pass them to SIM900LinkConfig directly)
//
//  const char myPin[] PROGMEM = "0000";
//  const char myAPN[] PROGMEM = "m2mkit.telefonica.com"; // FIXME check this
//  const char myUDPAddr[] PROGMEM = "0.0.0.0";
//  const char myUDPPort[] PROGMEM = "9999";
//  static const OTSIM900Link::OTSIM900LinkConfig_t SIM900Config {
//                                                  false,
//                                                  SIM900_PIN,
//                                                  SIM900_APN,
//                                                  SIM900_UDP_ADDR,
//                                                  SIM900_UDP_PORT };

    static const void *SIM900_PIN      = (void *)myPin;
    static const void *SIM900_APN      = (void *)myAPN;
    static const void *SIM900_UDP_ADDR = (void *)myUDPAddr;
    static const void *SIM900_UDP_PORT = (void *)myUDPPort;
    static const OTSIM900Link::OTSIM900LinkConfig_t SIM900Config {
        false,
        SIM900_PIN,
        SIM900_APN,
        SIM900_UDP_ADDR,
        SIM900_UDP_PORT };
    static const OTRadioLink::OTRadioChannelConfig RFMConfig(&SIM900Config, true, true, true);
    fastDigitalWrite(A3, LOW);  // This turns power to the shield on
    pinMode(A3, OUTPUT);

#elif defined(ENABLE_RADIO_PRIMARY_RFM23B)
    static const OTRadioLink::OTRadioChannelConfig RFMConfig(NULL, true, true, true);
#endif // USE_MODULE_SIM900

#if defined(ENABLE_RADIO_PRIMARY_RFM23B)
    // 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(1, &RFMConfig) || !PrimaryRadio.begin()) {
        panic(F("PANIC!"));
    }
#endif


    // Force initialisation into low-power state.
    const int heat = TemperatureC16.read();
#if 0 && defined(DEBUG)
    DEBUG_SERIAL_PRINT_FLASHSTRING("temp: ");
    DEBUG_SERIAL_PRINT(heat);
    DEBUG_SERIAL_PRINTLN();
#endif
    const int light = AmbLight.read();
//#if 0 && defined(DEBUG)
//  DEBUG_SERIAL_PRINT_FLASHSTRING("light: ");
//  DEBUG_SERIAL_PRINT(light);
//  DEBUG_SERIAL_PRINTLN();
//#endif



    // Trailing setup for the run
    // --------------------------

    // 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   (LEARN2 and MODE, RX)

        PCICR =
#if defined(MASK_PB) && (MASK_PB != 0) // If PB interrupts required.
            1 | // 0x1 enables PB/PCMSK0.
#endif
#if defined(MASK_PC) && (MASK_PC != 0) // If PC interrupts required.
            2 | // 0x2 enables PC/PCMSK1.
#endif
#if defined(MASK_PD) && (MASK_PD != 0) // If PD interrupts required.
            4 | // 0x4 enables PD/PCMSK2.
#endif
            0;

#if defined(MASK_PB) && (MASK_PB != 0) // If PB interrupts required.
        PCMSK0 = MASK_PB;
#endif
#if defined(MASK_PC) && (MASK_PC != 0) // If PC interrupts required.
        PCMSK1 = MASK_PC;
#endif
#if defined(MASK_PD) && (MASK_PD != 0) // If PD interrupts required.
        PCMSK2 = MASK_PD;
#endif
    }


//  pinMode(3, INPUT);        // FIXME Move to where they are set automatically
//  digitalWrite(3, LOW);

    bareStatsTX(false, false, false);

}
// Force a read/poll of the temperature pot and return the value sensed [0,255] (cold to hot).
// Potentially expensive/slow.
// This value has some hysteresis applied to reduce noise.
// Not thread-safe nor usable within ISRs (Interrupt Service Routines).
uint8_t SensorTemperaturePot::read()
  {
  // No need to wait for voltage to stabilise as pot top end directly driven by IO_POWER_UP.
  OTV0P2BASE::power_intermittent_peripherals_enable(false);
  const uint16_t tpRaw = OTV0P2BASE::analogueNoiseReducedRead(V0p2_PIN_TEMP_POT_AIN, DEFAULT); // Vcc reference.
  OTV0P2BASE::power_intermittent_peripherals_disable();

  const bool reverse = isReversed();
  const uint16_t tp = reverse ? (TEMP_POT_RAW_MAX - tpRaw) : tpRaw;

  // Capture entropy from changed LS bits.
  if((uint8_t)tp != (uint8_t)raw) { ::OTV0P2BASE::addEntropyToPool((uint8_t)tp, 0); } // Claim zero entropy as may be forced by Eve.

  // Capture reduced-noise value with a little hysteresis.
  // Only update the value if changed significantly.
  const uint8_t oldValue = value;
  const uint8_t shifted = tp >> 2;
  if(((shifted > oldValue) && (shifted - oldValue >= RN_HYST)) ||
     ((shifted < oldValue) && (oldValue - shifted >= RN_HYST)))
    {
    const uint8_t rn = (uint8_t) shifted;
    // Atomically store reduced-noise normalised value.
    value = rn;

    // Smart responses to adjustment/movement of temperature pot.
    // Possible to get reasonable functionality without using MODE button.
    //
    // Ignore first reading which might otherwise cause spurious mode change, etc.
    if((uint16_t)~0U != (uint16_t)raw) // Ignore if raw not yet set for the first time.
      {
      const uint8_t minS = minExpected >> 2;
      const uint8_t maxS = maxExpected >> 2;
      // Compute low end stop threshold avoiding overflow.
      const uint8_t realMinScaled = reverse ? maxS : minS;
      const uint8_t loEndStop = (realMinScaled >= 255 - RN_FRBO) ? realMinScaled : (realMinScaled + RN_FRBO);
      // Compute high end stop threshold avoiding underflow.
      const uint8_t realMaxScaled = reverse ? minS : maxS;
      const uint8_t hiEndStop = (realMaxScaled < RN_FRBO) ? realMaxScaled : (realMaxScaled - RN_FRBO);
      // Force FROST mode when dial turned right down to bottom.
      if(rn < loEndStop) { if(NULL != warmModeCallback) { warmModeCallback(false); } }
      // Start BAKE mode when dial turned right up to top.
      else if(rn > hiEndStop) { if(NULL != bakeStartCallback) { bakeStartCallback(true); } }
      // Cancel BAKE mode when dial/temperature turned down.
      else if(rn < oldValue) { if(NULL != bakeStartCallback) { bakeStartCallback(false); } }
      // Force WARM mode when dial/temperature turned up.
      else if(rn > oldValue) { if(NULL != warmModeCallback) { warmModeCallback(true); } }

      // Report that the user operated the pot, ie part of the manual UI.
      // Do this regardless of whether a specific mode change was invoked.
      if(NULL != occCallback) { occCallback(); }
      }
    }

#if 0 && defined(DEBUG)
  DEBUG_SERIAL_PRINT_FLASHSTRING("Temp pot: ");
  DEBUG_SERIAL_PRINT(tp);
  DEBUG_SERIAL_PRINT_FLASHSTRING(", rn: ");
  DEBUG_SERIAL_PRINT(tempPotReducedNoise);
  DEBUG_SERIAL_PRINTLN();
#endif

  // Store new raw value last.
  raw = tp;
  // Return noise-reduced value.
  return(value);
  }
Beispiel #14
0
// Measure/store/return the current room ambient light levels in range [0,255].
// This may consume significant power and time.
// Probably no need to do this more than (say) once per minute,
// but at a regular rate to catch such events as lights being switched on.
// This implementation expects LDR (1M dark resistance) from IO_POWER_UP to LDR_SENSOR_AIN and 100k to ground,
// or a phototransistor TEPT4400 in place of the LDR.
// (Not intended to be called from ISR.)
// If possible turn off all local light sources (eg UI LEDs) before calling.
// If possible turn off all heavy current drains on supply before calling.
uint8_t AmbientLight::read()
{
    // Power on to top of LDR/phototransistor.
//  power_intermittent_peripherals_enable(false); // No need to wait for anything to stablise as direct of IO_POWER_UP.
    OTV0P2BASE::power_intermittent_peripherals_enable(false); // Will take a nap() below to allow supply to quieten.
    OTV0P2BASE::nap(WDTO_30MS); // Give supply a moment to settle, eg from heavy current draw.
    // Photosensor vs Vbandgap or Vsupply as selected by ALREFERENCE, [0,1023].
    const uint16_t al0 = OTV0P2BASE::analogueNoiseReducedRead(LDR_SENSOR_AIN, ALREFERENCE);
    const uint16_t al = al0; // Use raw value as-is.
//#if !defined(EXTEND_OPTO_SENSOR_RANGE)
//  const uint16_t al = al0; // Use raw value as-is.
//#else // defined(EXTEND_OPTO_SENSOR_RANGE)
//  uint16_t al; // Ambient light.
//  // Default shift of raw value to extend effective scale.
//  al = al0 >> shiftExtendedToRawScale;
//  // If simple reading against bandgap at full scale then compute extended range.
//  // Two extra ADC measurements take extra time and introduce noise.
//  if(al0 >= rawScale-1)
//    {
//    // Photosensor vs Vsupply reference, [0,1023].
//    const uint16_t al1 = OTV0P2BASE::analogueNoiseReducedRead(LDR_SENSOR_AIN, DEFAULT);
//    const uint16_t vcc = Supply_cV.read();
//    const uint16_t vbg = Supply_cV.getRawInv(); // Vbandgap wrt Vsupply, [0,1023].
//    // Compute value in extended range up to ~1024 * Vsupply/Vbandgap.
//    // Faster overflow-free uint16_t-only approximation to (uint16_t)((al1 * 1024L) / vbg)).
//    const uint16_t ale = fnmin(4095U, ((al1 << 6) / vbg)) << 4;
//    if(ale > al0) // Keep output scale monotonic...
//      { al = fnmin(1023U, ale >> shiftExtendedToRawScale); }
//#if 1 && defined(DEBUG)
//    DEBUG_SERIAL_PRINT_FLASHSTRING("Ambient raw: ");
//    DEBUG_SERIAL_PRINT(al0);
//    DEBUG_SERIAL_PRINT_FLASHSTRING(", against Vcc: ");
//    DEBUG_SERIAL_PRINT(al1);
//    DEBUG_SERIAL_PRINT_FLASHSTRING(", extended scale value: ");
//    DEBUG_SERIAL_PRINT(ale);
//    DEBUG_SERIAL_PRINT_FLASHSTRING(", Vref against Vcc: ");
//    DEBUG_SERIAL_PRINT(vbg);
//    DEBUG_SERIAL_PRINT_FLASHSTRING(", Vcc: ");
//    DEBUG_SERIAL_PRINT(vcc);
////    DEBUG_SERIAL_PRINT_FLASHSTRING(", es threshold: ");
////    DEBUG_SERIAL_PRINT(aleThreshold);
//    DEBUG_SERIAL_PRINT_FLASHSTRING(", compressed: ");
//    DEBUG_SERIAL_PRINT(al);
//    DEBUG_SERIAL_PRINTLN();
//#endif
//    }
//#endif // defined(EXTEND_OPTO_SENSOR_RANGE)
    // Power off to top of LDR/phototransistor.
    OTV0P2BASE::power_intermittent_peripherals_disable();

    // Capture entropy from changed LS bits.
    if((uint8_t)al != (uint8_t)rawValue) {
        ::OTV0P2BASE::addEntropyToPool((uint8_t)al, 0);    // Claim zero entropy as may be forced by Eve.
    }

    // Hold the existing/old value for comparison.
    const uint8_t oldValue = value;
    // Compute the new normalised value.
    const uint8_t newValue = (uint8_t)(al >> shiftRawScaleTo8Bit);

    // Adjust room-lit flag, with hysteresis.
    // Should be able to detect dark when darkThreshold is zero and newValue is zero.
    if(newValue <= darkThreshold)
    {
        isRoomLitFlag = false;
        // If dark enough to set isRoomLitFlag false then increment counter.
        // Do not do increment the count if the sensor seems to be unusable / dubiously usable.
        if(!unusable && (darkTicks < 255)) {
            ++darkTicks;
        }
    }
    else if(newValue > lightThreshold)
    {
        isRoomLitFlag = true;
        // If light enough to set isRoomLitFlag true then reset darkTicks counter.
        darkTicks = 0;
    }

    if(newValue != value)
    {
#ifdef ENABLE_OCCUPANCY_DETECTION_FROM_AMBLIGHT
        // Treat a sharp brightening as a possible/weak indication of occupancy, eg light flicked on.
        // Ignore false trigger at start-up.
        if((~0U != rawValue) && (newValue > oldValue) && ((newValue - oldValue) >= upDelta))
        {
            Occupancy.markAsPossiblyOccupied();
#if 0 && defined(DEBUG)
            DEBUG_SERIAL_PRINT_FLASHSTRING("  UP: ambient light rise/upDelta/newval/dt/lt: ");
            DEBUG_SERIAL_PRINT((newValue - value));
            DEBUG_SERIAL_PRINT(' ');
            DEBUG_SERIAL_PRINT(upDelta);
            DEBUG_SERIAL_PRINT(' ');
            DEBUG_SERIAL_PRINT(newValue);
            DEBUG_SERIAL_PRINT(' ');
            DEBUG_SERIAL_PRINT(darkThreshold);
            DEBUG_SERIAL_PRINT(' ');
            DEBUG_SERIAL_PRINT(lightThreshold);
            DEBUG_SERIAL_PRINTLN();
#endif

#endif // ENABLE_OCCUPANCY_DETECTION_FROM_AMBLIGHT
        }
    }

#if 0 && defined(DEBUG)
    DEBUG_SERIAL_PRINT_FLASHSTRING("Ambient light (/1023): ");
    DEBUG_SERIAL_PRINT(al);
    DEBUG_SERIAL_PRINTLN();
#endif

#if 0 && defined(DEBUG)
    DEBUG_SERIAL_PRINT_FLASHSTRING("Ambient light val/dt/lt: ");
    DEBUG_SERIAL_PRINT(value);
    DEBUG_SERIAL_PRINT(' ');
    DEBUG_SERIAL_PRINT(darkThreshold);
    DEBUG_SERIAL_PRINT(' ');
    DEBUG_SERIAL_PRINT(lightThreshold);
    DEBUG_SERIAL_PRINTLN();
#endif

#if 0 && defined(DEBUG)
    DEBUG_SERIAL_PRINT_FLASHSTRING("isRoomLit: ");
    DEBUG_SERIAL_PRINT(isRoomLitFlag);
    DEBUG_SERIAL_PRINTLN();
#endif

    // Store new value, in its various forms.
    rawValue = al;
    value = newValue;
    return(value);
}