// 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); }
// 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); }
// 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 }
// 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(); } }
// 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(); }
// 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); }
// 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); }
// 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); }
// 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); }
// 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); }
// 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); }