User Tools

Site Tools


doc:appunti:hardware:raspberrypi_thermostat

This is an old revision of the document!


Programmable Thermostat with the Raspberry Pi

ProTherm assembled into a 3D-printed case This is a do-it-yourself project to make a programmable thermostat with the Raspberry Pi, a credit-card sized computer costing about 35 €. I was frustrated by a low-cost thermostat which has only one daily program with just two temperatures. Time accuracy was of a quarter of an hour and temperature accuracy above one Celsius degree.

So I embarked on this project, whose main features are:

  • Programmable with the accuracy of the minute and tenths of a degree.
  • Can keep several weekly programs, plus MANUAL OFF and MANUAL ON modes.
  • Show current status via an LCD.
  • Just one push button to cycle through all the programs.
  • Talks with the boiler with a ON/OFF relay (which controls the burner status).
  • Remote operation and alerting messages via Telegram Messenger chat.
  • Remote operation using a web page.
  • Extensive graphing capabilities via SNMP.

This is the list of the hardware required, with their cost:

  • Raspberry Pi model B with power supply (44 €)
  • Dallas DS18B20 temperature sensor (2 €)
  • Relay board with two realys, but used just one (3.50 €)
  • Push button (1 €)
  • LCD display (Nokia 5110-3310) (5 €)
  • Edimax WiFi nano USB adapter (10 €)

At the total cost of 65.50 € you must add the price of a suitable case.

Here there are some images of the first prototype I made:

ProTherm prototype assembled in a clear clase Assembling the prototype Prototype without the case

The LCD display

Printing a 3D Case

So I decided to give my thermostat a better case; that was the moment to make some 3D printing experiences. The corner shop in my city has a 3D printer, but it cannot print undercuts; so I tried Shapeways.com.

I am a programmer, not a designer, so I found that OpenSCAD was the right tool to make the 3D model. After several days of work the model was ready: I uploaded the file and after four days the shiny box arrived!

The OpenSCAD project The actual 3D printed case ProTherm assembled

To check the dimensions, I had to make the 3D model of several components: the Raspberry PI (model B v.2), the relays board (I used a SainSmart like model), the LCD (red PCB, pin on the top side) and several other parts. You can download the OpenSCAD source files from the GitHub repository.

Testing dimensions into OpenSCAD OpenSCAD module for the Raspberry Pi Model B v.2 OpenSCAD modules for the relay board and LCD

You can also buy the case directly from the Shapeways page (I know, it is a little pricey!).

Wiring Diagram

This is the wiring diagram for temperature sensor, relay board, push button and LCD display:

ProTherm wiring diagram

To keep the wiring simple, we have tried to group the pins by devices; e.g. the relays board uses four pins contiguous and aligned. This has caused the use of non-standard GPIOs for INPUT and OUTPUT.

Little doughter board for DS18B20 and push button By default pins of GPIO 14 and 15 are used as a serial line (TXD and RXD respectively). At boot time the kernel writes diagnostic data to that line, at runtime a login process is spawned on it by init. To avoid some flickering in the relay status at boot time we disabled the serial line as explained here.

GPIO2 is not generally used for INPUT/OUTPUT because it has a strong PULL-UP resistor (1.8 kΩ) in addition to the software controlled one. Infact GPIO2 and GPIO3 are generally used for I2C communication, e.g. to add a realtime clock module. Neverthless we used it for the push button because it is near the three pins used by the temperature sensor, so we arranged a 5 pin doughter board which also accomodates two resistors and a capacitor. The 4.7 kΩ resistor is required by the DS18B20, the capacitor should help in debouncing (see notes about push button). To avoid problems remember to blacklist the i2c-bcm2708 module as stated in this paragraph.

Unsolved Problems

Fake (erratic) button keypresses

This problem is described in Push Button paragraph. It seems solved simply by wiring far apart the GPIO wires and the boiler controlling cable, where 220 v A/C flows.

The Software

The ProTherm software I wrote is mantained into the ProTherm GitHub repository. It is a collection of software:

  • A Python daemon which polls the temperature sensors and starts or stops the relay accordingly a program. It also updates the LCD contents.
  • A Python daemon which acts as a Telegram Bot. It is used to control the thermostat from remote.
  • Some cron-job scripts used to update an RRA archive, which is needed to plot temperature graphs.
  • There are also some PHP scripts, to give a simple web interface.

Something is still missing in the repo, eventually I will upload everything in near future.

System software and libraries

Some packages are required from the standard Raspbian repository. Git is required to download the Adafruit LCD library and python-dev is required to compile it. The Adafruit library uses python-imaging to draw LCD images.

apt-get install python-rpi.gpio python-imaging python-dev git

Then download and install the Adafruit Nokia LCD for the Raspberry Pi:

cd /usr/local/src
git clone https://github.com/adafruit/Adafruit_Nokia_LCD.git
cd Adafruit_Nokia_LCD
python setup.py install

We also used PF Tempesta Seven, a free TrueType font for the LCD display. TrueType files were saved into /usr/local/share/fonts/ directory.

Disabling the serial console

We decided to use GPIO 14 and 15 to drive the relays board, so we had to prevent the use of the serial line which uses that pins, otherwise there was an annoying flickering of the realys at boot time. Following the instructions of this post, we changed /boot/cmdline.txt removing the options referring to ttyAMA0:

console=ttyAMA0,115200 kgdboc=ttyAMA0,115200

in Debian 7 Wheezy (running sysvinit) we commented out the line which spawns a login process from /etc/inittab:

#Spawn a getty on Raspberry Pi serial line
#T0:23:respawn:/sbin/getty -L ttyAMA0 115200 vt100

in Debian 8 Jessie (running systemd) we have to disable the service instead (it removes a symlink from /etc/systemd/system/getty.target.wants/):

systemctl disable serial-getty@ttyAMA0.service

Kernel modules

Temperature sensor DS18B20 requires the w1-gpio and w1-therm kernel modules, which are not generally blacklisted. Driving the LCD requires the SPI kernel module, ensure that it is not blacklisted. Finally we use one of the I2C pins (GPIO2) as a generic INPUT, so we blacklist I2C. In /etc/modprobe.d/raspi-blacklist.conf:

# We need SPI (for the Nokia LCD), blacklist only I2C
#blacklist spi-bcm2708
blacklist i2c-bcm2708

Obsolete: RPi.GPIO from PIP repository

NOTICE: This step is no longer required! The python-rpi.gpio package is entered into the Raspbian repository in recent days, before that we had to install the library from the alternative package manager PIP. First of all install the PIP package manager:

apt-get install python-pip

From PIP (which uses the Python Package Index repository) we install the RPi.GPIO package (PIP installed packages will go into /usr/local/lib/python2.7/dist-packages/):

pip install RPi.GPIO

Configuration Optimization

Headless Configuration

The Raspberry Pi is used without a monitor attached (headless configuration), so we can set the minimum for GPU memory, in /boot/config.txt we set:

gpu_mem=16

If we plan to never attach an HDMI monitor, we can power off the display adding this command into /etc/rc.local (before the exit statement):

/opt/vc/bin/tvservice --off

The Nokia 5110/3310 LCD

Follow this guide to make an Hello world program with the LCD display: Nokia 5110/3310 LCD Python Library.

In a nutshell run this as root:

apt-get install python-rpi.gpio python-imaging python-dev git

cd /usr/local/src
git clone https://github.com/adafruit/Adafruit_Nokia_LCD.git
cd Adafruit_Nokia_LCD
python setup.py install

Enabled Device Tree and SPI module in /boot/config.txt:

#device_tree=
dtparam=spi=on

Do not blacklist SPI module in /etc/modprobe.d/raspi-blacklist.conf:

#blacklist spi-bcm2708

Solved: LCD display goes blank from time to time

I had a long-lasting problem with the Adafruit Python library and the LCD, which was going blank after a random amount of time. A similar problem was reported in this post.

For a long time I had just a workaround to mitigate the problem, make an LCD reset every (say) three minutes:

lcd_disp.reset()
lcd_disp.begin(contrast=LCD_CONTRAST)
lcd_disp.clear()

Now I hope to have solved the problem: it seems that the library is not thread-safe. I have several program threads updating different parts of the LCD, I solved the problem putting lcd_disp.image() and lcd_disp.display() function calls into a semaphore block. Something like this:

import threading
 
lcd_sema = threading.BoundedSemaphore()
 
sub display_something():
    global lcd_disp, lcd_draw, lcd_image, lcd_sema
    lcd_draw.rectangle((0, 32, 43, 38), outline=255, fill=255)
    with lcd_sema:
        lcd_disp.image(lcd_image)
        lcd_disp.display()

Networking

In our configuration the Raspberry Pi will connect automatically (with DHCP auto-configuration) either using the Ethernet or a known WiFi network. I used the standard Raspbian configuration, just confirm that eth0 and wlan0 are enabled in /etc/network/interfaces:

auto lo

iface lo inet loopback
iface eth0 inet dhcp

allow-hotplug wlan0
iface wlan0 inet manual
wpa-roam /etc/wpa_supplicant/wpa_supplicant.conf

iface default inet dhcp

The ESSID and password for the WiFi network are stored into /etc/wpa_supplicant/wpa_supplicant.conf:

ctrl_interface=DIR=/var/run/wpa_supplicant GROUP=netdev
update_config=1

network={
        ssid="Rigacci.Org"
        psk="MySecret"
}

If you have additional network connections (e.g. an OpenVPN tunnel) check also /etc/default/ifplugd and make sure that HOTPLUG_INTERFACES is set to eth0 and wlan0 only:

HOTPLUG_INTERFACES="eth0 wlan0"

SNMP

We can export data (current temperature, programmed temperature and relay status) to remote systems using SNMP, in this way we can - e.g. - plot a nice temperature graph into a Cacti system. The required packages are:

apt-get install snmpd snmp snmp-mibs-downloader 

The shell script /usr/local/bin/protherm-snmp reads values from the shared memory file /run/shm/protherm/stats, then we add some extend commands into /etc/snmp/snmpd.conf:

extend     temp    /usr/local/bin/protherm-snmp temp
extend     tprog   /usr/local/bin/protherm-snmp tprog
extend     switch  /usr/local/bin/protherm-snmp switch

If everything is working well, we can query the current temperature via SNMP on the command line:

snmpwalk -v 2c -c public 127.0.0.1 'NET-SNMP-EXTEND-MIB::nsExtendOutput1Line."temp"'

syslog

We want to replace the file-based syslog (provided by the rsyslog package), with a memory ring-buffer logger, like the one provided by the busybox-syslogd package (this will remove rsyslog automatically):

apt-get install busybox-syslogd

The memory buffer can be read with logread, use the -f switch to see data as it arrives.

The size of the memory buffer is configured into /etc/default/busybox-syslogd, the default option is -C128, which allocates only 128 kb.

We also decreased the verbosity of cron: we want to log only errors in cron jobs, not every started job. In /etc/default/cron we added:

EXTRA_OPTS="-L 4"

Nginx Webserver

Apache does not fit well in a low-memory device like the Raspberry Pi, so we looked at lighttpd an nginx alternatives. It seems that nginx is more modern and suitable for the work. See also this Tutorial – Install Nginx and PHP on Raspbian. We want also the relative PHP module:

apt-get install nginx php5-fpm

ProTherm web interface The web interface is rather simple, it offers a snapshot of the LCD display and a graph of the temperature of the last day. It is also possible to emulate the button press, to change the program.

Hardware Test and Debug

Once assembled, we can test each piece of hardware separately.

Push Button

WARNINIG :!: Current version of ProTherm uses GPIO2 for push button. Beware that GPIO2 and GPIO3 are not intended as generic INPUT ports, see notes below.

Let's say that the push button is attached to the GPIO #18, we need to expose the port and configure it as an input port:

echo "18" > /sys/class/gpio/export
echo "in" > /sys/class/gpio/gpio18/direction

reading the port will return 1 if button is released, zero if it is pressed (the Raspberry Pi has a default pull-up configured via software):

cat /sys/class/gpio/gpio18/value

This is a Python program which prints some text as the button is pressed:

import RPi.GPIO as GPIO
import time
GPIO.setmode(GPIO.BCM)
GPIO.setup(18, GPIO.IN, pull_up_down=GPIO.PUD_UP)
while True:
    input_state = GPIO.input(18)
    if input_state == False:
        print('Button Pressed')
        time.sleep(0.2)

Fake (erratic) button presses

Strange to say, the push button was the most challenging task of the entire project. At the first I connected the button directly to the GPIO and to the ground, the GPIO was programmed with the software PULL-UP. In this way the GPIO reads normally an HIGH state, when the button is pressed, the state becomes LOW.

With this configuration I got several fake button press, i.e. the GPIO reads a LOW state despite no-one was pressing the button. I suspected that the problem was a floating in the power supply and indeed the problem sometimes arises when the relay is switched to ON and it draws 5v from the Raspberry, may be causing a little voltage sag in the PULL-UP

Someone (see the replies to this post) states that the PULL-UP resistor activated by software is too big (may be 50 kΩ), so the PULL-UP current is too weak and voltage can drop down unexpectedly. The suggested solution is to add an external 4.7 kΩ PULL-UP resistor anyway.

I tried also to use the software PULL-DOWN resistor, in this case the push button is used to connect the GPIO to the 3.3 v. I don't know why this mode is not so widespread as I would expect, nevertheless here it is an article using the PULL-DOWN configuration. With this configuration the false signals are worse: almost every time the relay changes state (from OFF to ON or vice-versa), the Raspberry Pi gets a false GPIO signal. So it seems that it is not a voltage sag, because the GPIO registered a fake HIGH state. May be the wire acts like an antenna, getting the 50 Hz hum.

I tried also to make a full debounce circuit. Using the GPIO18 with the internal PULL-UP, I added a 39 kΩ external resistor to enforce the PULL-UP and a resistor-capacitor pair (5.6 kΩ, 100 nF) to filter the transient noise (see the schematic here).

Finally I tried the GPIO2, which has a very strong 1.8 kΩ PULL-UP resistor in hardware (in addition to the software configurable one).

No matter what I have tried, it seems then that the software PULL-UP or PULL-DOWN is unreliable whenever you attach a wire to the GPIO header, even a short one (10 cm).

THE PROBLEM SEEMS NOW SOLVED: Because no erratic pull-down events occur if no wires are attached to the GPIO, clearly it is not a voltage sag. It seems also that no erratic behaviour occur if the boiler is detached from the relay. Everything suggests that the problem is the interference with the 220 v alternate current flowing in the cable from the boiler; keeping the wires far apart and short, seems to have solved the issue!

The PULL-UP and the safeguard restistor

Several Raspberry Pi tutorials show a push button used with the GPIO configuread as an INPUT port along with a PULL-UP resistor. The PULL-UP resistor is an external one or the internal one (software configured). In this configuration the GPIO pin is keept normally in the HIGH state (3.3 v) by the current flowing through the PULL-UP resistor.

When the push button is pressed, the GPIO is connected to the ground through another external 1 kΩ resistor. This resistor is not strictly needed, but it serves just as a safeguard if someone configures (by mistake) the GPIO as an OUTPUT; in this case the GPIO can be set HIGH (3.3 v) by software and if you press the button you will short-circuit the 3.3 v to the ground. The additional resistor will prevent any damage to the GPIO circuit.

Some GPIO pins (namely GPIO2 and GPIO3) have an hardware PULL-UP resistor in addition to the software configurable one. According to this page (see also this post and this one), the PULL-UP is a 1.8 kΩ resistor. In this case the safeguard resistor is useful also to limit the current drain when the button is pushed. Some tests revealed that a 1 kΩ resistor is not sufficient to pull down the line (it does not win over the 1.8 pull-up), a 560 Ω one is OK. Beware that using such GPIOs will draw a noticeable amount of current when the button is pressed; according to the Ohm law: 3.3 V / (1800 + 560) Ω = 1.40 mA.

Relay module

Let's say that one relay is attached to GPIO #17, we need to expose the port and configure it as an output port:

echo "17" > /sys/class/gpio/export
echo "out" > /sys/class/gpio/gpio17/direction

then we can output value 1 or zero to that port; the pin will get 3.3 volt (high) or zero (low) respectively. Our 2 Relay Module uses an active-low logic, so write zero to activate the relay:

echo "1" > /sys/class/gpio/gpio17/value
sleep 2
echo "0" > /sys/class/gpio/gpio17/value

Temperature Sensor

See this how-to: Device Trees, Overlays and Parameters.

Older Raspbian versions do things in a different way, see this DS18B20 and 30-Jan firmware.

With Rasbian and firmware (kernel) Linux raspberrypi 4.1.7+, check that you have the following lines into /boot/config.txt:

# Uncomment to disable Device Tree (revert to the old way of doing things).
#device_tree=

# Load the w1_gpio module, the 1-Wire bus is connected to GPIO pin #4
dtoverlay=w1-gpio,gpiopin=4

With this configuration kernel modules w1_gpio and w1_therm will be loaded automatically with the right parameters. No explicit load should be required in /etc/modules.

Check also that kernel modules w1-gpio and w1-therm are not blacklisted in /etc/modprobe.d/raspi-blacklist.conf (notice that we use the SPI interface for the Nokia LCD, I2C bus is not used instead):

# We need SPI (for the Nokia LCD), blacklist only I2C
#blacklist spi-bcm2708
blacklist i2c-bcm2708

At bootsrap time the kernel will log something like this:

w1-gpio onewire@0: gpio pin 4, external pullup pin -1, parasitic power 0

Then you can load kernel modules (if not loaded automatically) and access data from the device. Notice that the device ID is unique:

modprobe w1-gpio
modprobe w1-therm
cd /sys/bus/w1/devices/
ls
28-0000067bc546  w1_bus_master1
cd 28-0000067bc546
cat w1_slave
ab 01 4b 46 7f ff 05 10 92 : crc=92 YES
ab 01 4b 46 7f ff 05 10 92 t=26687

If the sensor is not properly connected or broken, the kernel module logs some error messages, like this:

w1_master_driver w1_bus_master1: Family 0 for 00.800000000000.8c is not registered.

LCD Display

With Rasbian and firmware (kernel) Linux raspberrypi 4.1.7+, check that you have the following lines into /boot/config.txt:

#device_tree=
dtparam=spi=on

Check also that kernel modules spi-bcm2835 is not blacklisted in /etc/modprobe.d/raspi-blacklist.conf:

# blacklist spi and i2c by default (many users don't need them)
#blacklist spi-bcm2708
blacklist i2c-bcm2708

At bootsrap time the kernel will log something like this:

spi spi0.0: setting up native-CS0 as GPIO 8
spi spi0.1: setting up native-CS1 as GPIO 7

You should find two devices:

crw------- 1 root root 153, 0 Jan  1  1970 /dev/spidev0.0
crw------- 1 root root 153, 1 Jan  1  1970 /dev/spidev0.1

Running a Telegram Bot on the Raspberry Pi

To install the Telepot Python library, run as root:

apt-get install python-pip
pip install telepot

The Telepot Python library will be installed into /usr/local/lib/python2.7/dist-packages/telepot/.

You must create the new bot talking with the Telegram BotFather bot. This is an example of a Telegram chat session:

/newbot
Bot Short Name
newname_bot

The Bot Short Name will displayed by the Telegramm App into the contacts list, while the newname_bot is the actual bot name which must end with the bot string.

You will receive a message containing the HTTP API access token, a string like this one: 789463003:MDUxNTY0IDAwMDAwIG4gCjAwMDAwNTE1ODM.

Here are some useful commands to change the bot, using the BotFather:

/setuserpic Set a picture for the bot.
/setdescription Set a short description, stating “What can this bot do?”
/setname Change the short, descriptive name of the bot.

Upgrading to Telepot 9.1

We experience an annoying bug: the message handler seems to stop working after a while (some days), no more polling to the Telegram server is observed.

In the hope to resolve the problem, we upgraded the Telegram Telepot library from version 4.1 to 9.1:

pip install telepot --upgrade

The new Telepot version requires urllib3 >= 1.9.1 which ineed is installed. Neverthless pip downloaded and installed urllib3-1.18 from its repository. The version installed by pip overrides the one installed by Debian, even if you manually install a backport:

You can check the module version loaded by Python in this way:

# dpkg -i python-urllib3_1.12-1~bpo8+1_all.deb
# python
Python 2.7.9 (default, Mar  8 2015, 00:52:26) 
>>> import urllib3
>>> print urllib3.__version__
1.18

The new urllib3 module suggests also PySocks (verify with pip list), which we installed with the Debian (Jessie) package python-pysocks.

The main difference I noticed after the upgrade, was the change of function name: bot.notifyOnMessage(handle) must now be called as bot.message_loop(handle).

Web references

doc/appunti/hardware/raspberrypi_thermostat.1476462506.txt.gz · Last modified: 2016/10/14 18:28 by niccolo