Heating control for elderly/partially sighted people

wayneh

Joined Sep 9, 2010
17,498
Press any button and it acknowledges the person e.g. “Hello Ken” (Note 1)
Just some first reactions. My 2¢. Take it or leave it.

A simple cheery tone would be good enough and not require any reprogramming for different users. I recommend lighting all the buttons when any of them is first pressed. In this regard there would be no difference between the buttons - pressing any one of them triggers the same chime and lights-on.
“It’s currently 21.5 degrees.”
Both the target and actual should be read, and maybe the HVAC status ("furnace running").
The stat then continues, “Press the upper button to make it warmer and the lower button to make it cooler. If you are going out, or off to bed, press both buttons together until the beep sounds to make it cooler for that time.”
Too much talking. The arrangement and lit color of the buttons should be more than clear and not need explanation over and over. As for the multi-button press command, I'm confused and I'm not elderly. :eek: Why the complexity? If you want automated features (cooling at night, less HVAC when user is gone), then automate it. I thought the whole idea was to enable easy manual control.
No input for 50 secs, ‘Going back to sleep…”
No need for speech, just go dark and shut up.
1 – Reads temp every 1 sec.
Hysterisis. Plus/minus 0.5 degree to give a 1 degree band. So, when set at 21.0 the boiler will not fire until the temp drops to 20.5 but then it stays firing until 21.5 degrees is reached.
This is fine. Most thermostats offer some control over the amount of hysteresis.
2 - Basically, sits there:
a) polling buttons in ‘asleep mode’ every 0.5 sec till it picks up an input. BLUE LED solid on to show ‘listening’
b) runs the temp and control loop every second. Part of running the control loop could be the red LED on the button being solid on when the boiler is firing. Like solid blue to indicate listening this changes to hard alternate flashes when thing 'woken'.
I'm not qualified to comment on what the micro is capable of but my first reaction is that this is too slow to respond to a button.
If both pressed before two seconds is up, ignore previous ‘adjustments’ and announce, “The temperature is now set at XX for XX hours.” This also sets a flag called ‘setback’ which then looks up the time and temperature setting assigned to it.

If the setback flag is still set when it senses a single press to wake up, it could say, “Currently xx degrees. Setting temperatures back to normal.”

The setback temp, and the number of hours it is setback for, must be pre-programmable by users support.

Does this make sense ? Is it doable ? Any suggestions to improve ? Where do I start ?:(
I'm not sure it makes sense for the thermostat to do anything on its own, to undo what the user has set. Picture the old fashioned dial thermometer on the wall. Set it and forget it. Maybe that's a fundamental thing to resolve, what this thing will automate and what it will not. Don't forget that even the old fashioned thermostats had a seasonal mode switch, heat or cool, furnace only or A/C only. Newer ones add "auto".
 
Picture the old fashioned dial thermometer on the wall. Set it and forget it. Maybe that's a fundamental thing to resolve, what this thing will automate and what it will not. Don't forget that even the old fashioned thermostats had a seasonal mode switch, heat or cool, furnace only or A/C only. Newer ones add "auto".
The newer ones have the "Fool the user" mode disguised as "calibration. e.g. set how much the sensor is off.

@wayneh I don;t tough the thermosat much either, It's an old Carrier Infinity with a simplistic interface and a hidden interfacebut you need to see the display.

I actually use it pretty much once a day to see the outdoor temperature. The sensor is mounted just under a Telephone NID outside. Works vey well. Just have fear that a repair might cause it to be cut. It blends in too well.

So, for the OP/TS Outdoor Temperature may be useful.

FWIW: I put a 24 VAC transorb between R and C at the furnace and a RFI filter. 10 years and no reall issues except one. When it's off at the stat, the backlight gets stuck on. At that first occurance is when I added the transorb and I think only 1x after that. .

Is set for a program schedule and the house is occupied nearly 100% of the time or was. Now, nearly anything is predictable except out for a few hours and a lower setting at bedtime.

there is one thing that we tend to do that would be consider "playing" with the stat. Rain and cold and sometimes snow and cold require us to up the temperature a degree. It makes all the difference in the world.

2x a year, it yells and says the filter needs replacing using what I think is a measurement of static pressure with all ducts open at a particular time of the day. It ends up being every 6 months or the recommended change interval for a huge pleated filter and electrostatic filter combo.
 

Thread Starter

YorkshireDave

Joined Jun 12, 2016
59
"A simple cheery tone would be good enough and not require any reprogramming for different users. I recommend lighting all the buttons when any of them is first pressed. In this regard there would be no difference between the buttons - pressing any one of them triggers the same chime and lights-on."

It's called empathy. I understand what my FIL can see & what he cannot see. I understand how he works. I understand his memory is not that good either which is why he needs the prompts. He has partial vision so in our experiments can differentiate between no lights, constant on and flashing. His reactions, strangely I admit, demonstrate that he feels more 'in control' when he's given choices and vitally has to think. I do not expect anyone else to appreciate what it's like from his perspective.

"I'm not sure it makes sense for the thermostat to do anything on its own, to undo what the user has set. Picture the old fashioned dial thermometer on the wall. Set it and forget it. Maybe that's a fundamental thing to resolve, what this thing will automate and what it will not. Don't forget that even the old fashioned thermostats had a seasonal mode switch, heat or cool, furnace only or A/C only. Newer ones add "auto""

It will not be undoing what the user has done per se. One has to understand that these guys are not speedy and often take too long and as such do something that's was never meant to happen. Older people here in the UK are from an era where 'waste' is despised. They do not spend where it is not necessary so like to be in control. When his sight was good, he'd go through a stat like you describe every 2/3 years literally wearing it out. It used to drive my mother in law mad!

Just one point for everyone else. The hurry up you old fool wasn't serious - just my attempt at black humour given the underlying gravity of this task and the issues involved. Hell, why would I spend all this effort and then slag him off?

Anyone have any pointers to people or groups where I could now take this for day-to-day/practical advice on shields and coding?
 

ebeowulf17

Joined Aug 12, 2014
3,307
Starting points:
https://www.arduino.cc/en/tutorial/blink

https://www.arduino.cc/en/Tutorial/Button

https://www.adafruit.com/product/39...wNR9ABDotVDP22axconydwGGBH4VEclAaAvQoEALw_wcB
(follow the link to a tutorial with code)

Support:
https://forum.allaboutcircuits.com/forums/embedded-systems-and-microcontrollers.17/

https://forum.allaboutcircuits.com/forums/programmers-corner.12/

https://forum.arduino.cc/

https://forums.adafruit.com/

In my experience, the last two forums are hit and miss. Lots of good info there, but also lots of newbies who think they know more than they do and give misleading information. Double check anything you get from these forums, unless its from staff and moderators.
 

ebeowulf17

Joined Aug 12, 2014
3,307
Here are two snippets of code that may come in handy:

For debouncing switches, here's what I use. It takes a lot of variables, but it works well, doesn't require interrupts, and doesn't rely on slow polling or delays. You can include this in your main loop and let it run as fast as you want. I'm sure there's far cleaner code out there (and of course there are hardware options too,) but here's at least one system that works if you want it:
Code:
// ***** Declare these variables before Setup() or loop()

const byte pin_1 = 3;             // pin number for button 1
const byte dbthresh = 50;         // time (ms) to debounce before accepting switch change

unsigned long timenow=2000;       // current time reading

byte switchnow_1=0;               // current switch status reading
byte switchlast_1=0;              // last switch status reading
byte switchdb_1=0;                // debounced switch status
byte dblast_1=0;                  // last debounced status
unsigned long changetime_1=0;     // time of last switch status change

// ***** In the main loop, update timenow reading at every loop, and check buttons as often as you want

void loop(){
  timenow=millis();               // record current time reading for this cycle
  debounce_1();                   // this calls the debounce function, which reads the switch status and ignores bounces
  if(switchdb_1==1){              // if debounced switch state is "on," do the following:
    // insert your code for what to do when button is pressed here
  }
}

// ***** After the main loop, place this debounce function which can be called whenever you want to update button status

void debounce_1(){                // debounce procedure filters out false switch cycles that result from physical switch bouncing
  dblast_1=switchdb_1;            // updates switch status with current one so that next cycle can see if state has changed
  switchnow_1=digitalRead(pin_1); // read switch
  if(switchnow_1!=switchlast_1){  // if switch position changed, log change time and update status
    changetime_1=timenow;
    switchlast_1=switchnow_1;
  }
  else{                           // if switch has stayed consant, check to see it it's been long enough to trigger safely
    if(timenow-changetime_1>dbthresh){
      switchdb_1=switchnow_1;     // update debounced status to current state if time threshold is reached
    }
  }
}
All of the variables are declared as global variables, so any of them can be referenced outside of the debounce function as needed. Just as an example, you can easily distinguish between new button presses (that haven't yet been handled) and presses that are a continuation of one that's already been recognized, by comparing dblast and switchdb (last debounced value and current debounced value.)

The second helpful snippet is an easy, low-memory way to smooth out noisy data. This is a type of Infinite Impulse Response (IIR) filter. It's sort of a weighted average. Higher smoothing numbers deliver less noise, but slower response. The ideal amount of smoothing will vary depending on the type of data and the sample rate. In measuring pressure and flow, I've been using factors of 3.5 - 9.5, but I suspect for a thermostat you could do with much slower, much smoother response, maybe in the 20 to 100 range, although settings really will depend on sample frequency. If you're only sampling temperature every few seconds, a factor of 5-10 may be more than enough.
Code:
// ***** Here's an example of how to smooth out noisy values with an Infinite Impulse Response (IIR) filter:

// ***** Declare these variables before Setup() or loop()
const int smoothingFactor = 95;   // smoothing factor for noise reduction, in tenths (i.e. 35=3.5, 95=9.5)
float valueRaw = 0;               // this variable stores the raw sensor reading, before it gets smoothed out
float valueSmoothed = 0;          // this variable represents the smoothed value

// ***** Include this line of code after each new reading (valueRaw) comes in from your input source
valueSmoothed = (valueSmoothed * (float(smoothingFactor) / 10) + valueRaw) / (float(smoothingFactor) / 10 + 1); // valueSmoothed is a smoothed value
 

philba

Joined Aug 17, 2017
959
That's fine for one button but generalizing to more than a couple gets kind of messy. What has worked well for me is building a finite state machine where button state is kept in a single byte. The button state is actually just a counter. Anything less than max count (say 5) is button off. The button pin is checked every so often - say 10 mS. If button is closed (pin is low) the count is incremented if it's less than max count. If the button is open (pin is high), the count is decremented unless it's 0. This way, 5 button check periods of the button being pressed gives a 40mS debounce on the button. You can arrange to call the code in an interrupt handler or in the loop() code with a timer test. This way you don't need to keep track of the time on a per button basis.

But that's not the end of it. You need to keep track of the button down operation (or button up) if you only want to do something once per button press. I usually keep some bits per button to indicate button down and button up. Set when my code detects increment to max count (or decrement to 0) and clear them when I've acted on that state change. Someday I'll clean up my library code and make it available.

I think our OP is a long way from doing button code, though.
 

ebeowulf17

Joined Aug 12, 2014
3,307
That's fine for one button but generalizing to more than a couple gets kind of messy. What has worked well for me is building a finite state machine where button state is kept in a single byte. The button state is actually just a counter. Anything less than max count (say 5) is button off. The button pin is checked every so often - say 10 mS. If button is closed (pin is low) the count is incremented if it's less than max count. If the button is open (pin is high), the count is decremented unless it's 0. This way, 5 button check periods of the button being pressed gives a 40mS debounce on the button. You can arrange to call the code in an interrupt handler or in the loop() code with a timer test. This way you don't need to keep track of the time on a per button basis.

But that's not the end of it. You need to keep track of the button down operation (or button up) if you only want to do something once per button press. I usually keep some bits per button to indicate button down and button up. Set when my code detects increment to max count (or decrement to 0) and clear them when I've acted on that state change. Someday I'll clean up my library code and make it available.

I think our OP is a long way from doing button code, though.
Your approach sounds pretty clever!

With mine, if I have lots of buttons, I use variable arrays instead of variable_1, variable_2, etc. That way, there can be just one universal debounce function. The array variables make the code harder to read, and make it less intuitive to declare variables up front, which is why I've posted a "simplified" version here, trying to make it as easy to read as possible for the OP.

I've also been reading up on classes recently and been thinking of rewriting this soon into a library which would make everything cleaner and easier to work with - just haven't found the time yet.
 

philba

Joined Aug 17, 2017
959
Here's the code. Lots of things need to be done but the core is working pretty well for me. It currently handles pressed/unpressed, toggle and repeat. Way more code/data than needed for a just simple button press. The repeat mode code is experimental, not happy with it yet and I may toss it. It does work - I use it in my focus rail controller but it's kind of heavy for what it does. Certainly the publicly visible part needs to go away.

The code currently does not handle pulled low switches, needs examples, needs more comments and the usual #ifndef stuff in the .h file. Probably some bugs still lurking. I'm also not super happy with the interactions around state clearing - messier than I would like. When I get around to putting it on github, I'll make it open source.

Buttons.cpp:
Code:
#include "WProgram.h"
#include "Buttons.h"
//
// Button class
// PL Barrett Copyright (c) 2017
// all rights reserved
//

//
// constructor
// create Button, set state to off, duration to 100 mS and toggle state to none
Button:: Button(int pin) {
    pinMode( pin, INPUT_PULLUP);
    _pin = pin;
    _state = SW_UNPRESSED;
    _toggle_st = SWT_NONE;
    _firstrepeat = 500;
    _repeatduration = 100;
    RepeatFirst = 500;
    RepeatDuration = 100;
    RepeatState = REPOFF;
}

//
// test for new Button state
//
// this is the core routine. note that the on and off Button
// state values are not 1 apart. This is to support debouncing
// A Button when pressed will start a state count down to OFF.
// if ON-OFF is 4, then it will take 4 calls of newstate() to get
// to an on state. These intermediate states are treated as
// the previous state for the purposes of getState. These
// intermediate states help avoid spurious actions based on
// switch conact generated noise.
//
// toggle state allows recognizing Button down and Button up
// actions. Low to high is recognized when the intermediate
// state is about to transition to the off state and high to
// low is recognized when transitioning from intermeniate to
// on state.
//
uint8_t Button::newState(){
    if(digitalRead(_pin)==1) { // Button is not pressed
        if(_state == SW_UNPRESSED+1) {    // is this transition to OFF state?
            _toggle_st = _toggle_st | SWT_LOWHI;
            _tuptime = millis();
        }
        if(_state >  SW_UNPRESSED) _state--;
    } else {  // Button is pressed
        if(_state == SW_PRESSED-1) {        // is this transition to ON state?
            _toggle_st = _toggle_st | SWT_HILOW;
            _tdowntime = millis();
        }
        if(_state < SW_PRESSED) _state++;
    }
    return _state;
}

uint8_t Button::getState(){
    return _state;
}

void Button::clearState(){
    _state = SW_UNPRESSED;
}

uint8_t Button::getTogglestate(){
    return _toggle_st;
}
uint8_t Button::getTogglestate(uint8_t cl){
    uint8_t    tmp;
    tmp = _toggle_st;
    if(cl) _toggle_st = SWT_NONE;
    return tmp;
}
//
// allow clearing either toggle state.
//
void Button::clearToggle(uint8_t tgl){
    _toggle_st = _toggle_st & !tgl;
}

unsigned long Button::getToggleDownTime(){
    return _tdowntime;
}

unsigned long Button::getToggleUpTime(){
    return _tuptime;
}

void Button::setToggleDownTime(unsigned long t) {
    _tdowntime = t;
}

unsigned long Button::getRepeatDuration() {
    return _repeatduration;
}

void Button::setRepeatDuration(unsigned long t) {
    _repeatduration = t;
}

unsigned long Button::getFirstRepeat(){
    return _firstrepeat;
}
buttons.h:
Code:
#include <Arduino.h>
#include "WProgram.h"

#define SW_UNPRESSED 0
#define SW_PRESSED 4
#define SWT_NONE 0
#define SWT_HILOW 0x1
#define SWT_LOWHI 0x2
#define SWT_ALL SWT_LOWHI|SWT_HILOW
#define REPOFF 0
#define REPON 1

class Button
{
    public:
        Button(int pin);
        uint8_t newState();
        uint8_t getState();
        void clearState();
        uint8_t getTogglestate();
        void clearToggle(uint8_t tgl);
        uint8_t getTogglestate(uint8_t cl);
        unsigned long getToggleDownTime();
        unsigned long getToggleUpTime();
        void setToggleDownTime(unsigned long t);
        void setRepeatDuration(unsigned long d);
        unsigned long getRepeatDuration();
        unsigned long getFirstRepeat();
        uint8_t RepeatState;
        unsigned long RepeatTime;
        unsigned long RepeatFirst;
        unsigned long RepeatDuration;
     
     
    private:
        int _pin;
        uint8_t _state;
        uint8_t _toggle_st;
        unsigned long _tdowntime;
        unsigned long _tuptime;
        unsigned long _firstrepeat;
        unsigned long _repeatduration;
};
 
Last edited:

Thread Starter

YorkshireDave

Joined Jun 12, 2016
59
Thank you guys. Much to be trying to get my head around. I've already started on the links thanks. Somehow tho the more I read the more confused I become - is it an 'age' thing or am I just daft? :)

I understood a little more of the switch discussions than I thought I should - but then my head exploded :(

I really appreciate everyones help. This is a wonderfully giving forum. Thank you. I'll let you know how I get on.
 

ebeowulf17

Joined Aug 12, 2014
3,307
Thank you guys. Much to be trying to get my head around. I've already started on the links thanks. Somehow tho the more I read the more confused I become - is it an 'age' thing or am I just daft? :)

I understood a little more of the switch discussions than I thought I should - but then my head exploded :(

I really appreciate everyones help. This is a wonderfully giving forum. Thank you. I'll let you know how I get on.
Don't feel bad. It is a lot to get your head around, which is why small steps that yield "easy" victories are a good idea. If you try to understand too much at once, you'll lose your mind and get frustrated. I'd encourage you to try running a sample sketch or two before you do anymore reading (if you haven't already.)

Don't let the switch thing bog you down. If this project spurs further interest in programming, you can go down that rabbit hole as far as you want. If you really just need to get this one program going, you can use existing code libraries or help from the members here. Although it's great to take ownership of every bit of code and completely understand it, it's not always necessary. You can do this project just fine , without truly understanding switch code, by just copying and pasting some suitable switch code. Purists may disagree, but I see no harm in that.

Cheers!
 
Top