====== Programmable Thermostat with the Raspberry Pi ====== {{ .:raspberrypi:protherm-assembled-3d-printed-case.jpg?200|ProTherm assembled into a 3D-printed case}} This is a do-it-yourself project to make a **programmable thermostat** with the **[[https://www.raspberrypi.org/|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: {{.:raspberrypi:protherm.jpg?112|ProTherm prototype assembled in a clear clase}} {{.:raspberrypi:protherm_assembling.jpg?200|Assembling the prototype}} {{.:raspberrypi:programmable_thermostat.jpg?direct&200|Prototype without the case}} {{.:raspberrypi:protherm-lcd_image.png?direct&120|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 **[[http://www.shapeways.com/|Shapeways.com]]**. I am a programmer, not a designer, so I found that **[[http://www.openscad.org/|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! {{.:raspberrypi:protherm-openscad-screenshot.png?direct&240|The OpenSCAD project}} {{.:raspberrypi:protherm-3d-printed-case.jpg?direct&240|The actual 3D printed case}} {{.:raspberrypi:protherm-assembled.jpg?direct&240|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 [[https://github.com/RigacciOrg/ProTherm/tree/master/case/scad|download the OpenSCAD source files]] from the GitHub repository. {{.:raspberrypi:openscad-protherm-complete.png?direct&200|Testing dimensions into OpenSCAD}} {{.:raspberrypi:openscad-raspberry-pi-model-b-v2.png?direct&200|OpenSCAD module for the Raspberry Pi Model B v.2}} {{.:raspberrypi:openscad-relay-lcd.png?direct&184|OpenSCAD modules for the relay board and LCD}} You can also **[[https://www.shapeways.com/product/2DF8UQXNN/protherm-enclosure|buy the case directly from the Shapeways page]]** (I know, it is a little pricey!). {{.:raspberrypi:protherm-lasercut-cover.jpg?direct&240 |The cover, made from laser cut plexiglass}} The **cover** is made with a piece of **plexiglass**, 2 mm thick. I tried a **lasercut** service, which cutted two covers from a single A4 sheet of plexiglass for 18.60 €. The service required a 2D project in DXF format, so I used the export function from OpenSCAD. The screw holes are too near to the edge, if you want to replicate this object I suggest to modify the ''screw_flange()'' module of the OpenSCAD project. ===== Wiring Diagram ===== This is the wiring diagram for **temperature sensor, relay board, push button and LCD display**: {{.:raspberrypi:protherm-schematic.png?direct&512|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. {{.:raspberrypi:protherm-doughter-board.jpg?direct&90 |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 [[#Disabling the serial console|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|push button]]). To avoid problems remember to blacklist the **i2c-bcm2708** module as stated in [[#Kernel modules|this paragraph]]. ===== The Custom Software ===== The ProTherm software I wrote is mantained into the **[[https://github.com/RigacciOrg/ProTherm|ProTherm GitHub repository]]**. It is a collection of software: * **protherm**: a Python daemon which polls the temperature sensors and starts or stops the relay accordingly a program. It also updates the LCD contents. * **protherm-tbot**: a Python daemon which acts as a [[https://telegram.org/|Telegram]] [[https://core.telegram.org/bots|Bot]]. It is used to control the thermostat from remote. * Some **cron-job scripts** used to update an [[http://oss.oetiker.ch/rrdtool/index.en.html|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. ===== The System Software ===== The overall system uses several pieces of software, most of them from the Raspbian distribution, others are installed via PIP packaging system. * **Python**: protherm and protherm-tbot daemons are written in Python. * **python-rpi.gpio** library used to access the GPIO lines for button and relay operations. * **python-imaging** library used to create the LCD display image. * **PIP** Python package installer, to get libraries which are not available as Debian packages. * **Adafruit LCD library** Python library to drive the LCD via the SPI interface. * **Telepot**: Python framework for Telegram Bot API. * **rrdtool** used to store statistical data and to create nice graphs. * **Nginx**: a lighweight web server. * **php5-fpm** PHP server side language, for a simple web interface. * **php5-gd** and **php5-imagick** PHP library for creating graphs. * **SNMPD**: daemon for remote logging of thermostat data. * **busybox-syslogd** syslog daemon replacement to log in RAM. ===== Solved Problems ===== === Fake (erratic) button keypresses === This problem is described in [[#Push Button]] paragraph. It seems solved simply by keeping the button wires as short as possible and by wiring far apart the GPIO wires and the boiler controlling cable, where 220 v A/C flows. === LCD goes blank after some time === This problem is described in [[#the_nokia_51103310_lcd|Nokia LCD]] paragraph. The Adafruit LCD library is not thread safe, it is necessary to add a semaphore to avoid conflicting updates of LCD content. ===== Adafruit LCD library ===== To drive the PCD8544 LCD module we used the **Adafruit_Nokia_LCD** Python library, version 0.1.0 (this was in 2016, in 2021 we used version 0.2.0, which proved to work well and uses the same dependencies). It depends upon other Python libraries: * **Adafruit_Nokia_LCD 0.1.0** * **Adafruit_Python_GPIO 1.0.4** * **Adafruit_PureIO 0.2.1 ** * **RPi.GPIO 0.6.3** ==== System software and libraries ==== Some packages are installed from the standard Raspbian repository. Git is required to download the Adafruit LCD library, **python-dev**, **spidev** and **python-setuptools** are required to compile it. The Adafruit library uses **python-imaging** to draw LCD images. apt-get install git python-dev python-imaging python-rpi.gpio python-setuptools spidev We used version **0.2.1** of the **Adafruit-PureIO** library, more recent versions may have requirements which are not easy to satisfy on Debian 8 Jessie environment. pip install --download /usr/local/src adafruit-pureio==0.2.1 pip install /usr/local/src/Adafruit_PureIO-0.2.1-py2-none-any.whl Download and install the **[[https://github.com/adafruit/Adafruit_Nokia_LCD|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 **[[http://www.dafont.com/it/pf-tempesta-seven.font|PF Tempesta Seven]]**, a free TrueType font for the LCD display. TrueType files were saved into **''/usr/local/share/fonts/''** directory. ==== The RPi.GPIO library is now a Debian package ==== Before python-rpi.gpio became available as a Debian package, the library was installed using the alternative package manager PIP. So now **you don't need the following steps!** In the old days you ''apt-get install python-pip'' and then ''pip install RPi.GPIO''. PIP uses the [[https://pypi.python.org/pypi/|Python Package Index]] repository. The **[[https://pypi.python.org/pypi/RPi.GPIO/0.5.11|RPi.GPIO]]** package installed by PIP will go into ''/usr/local/lib/python2.7/dist-packages/''. ===== Configuration Optimization ===== ==== Disabling console and login shell from the serial line ==== We decided to use GPIO 14 and 15 to drive the relays board, so we had to **prevent the use of the serial line** by the kernel console output and by the login shell which is spawned on it by the system, otherwise there is an annoying flickering of the realys at boot time caused by data written on the serial line. We found some relevant info on this [[http://raspberrypihobbyist.blogspot.it/2012/08/raspberry-pi-serial-port.html|post]]. The **kernel console** on serial line is activated by some **''/boot/cmdline.txt''** options. Depending on Debian version (7 Wheezy or 8 Jessie) we have to remove: console=ttyAMA0,115200 kgdboc=ttyAMA0,115200 console=serial0,115200 Also **removing the login shell** is different upon the version of the operating system. 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 Do not use the **raspi-config** option to diasble the serial line, because it disables the serial device **''/dev/ttyAMA0''** entirely, adding an **enable_uart=0** option in **''/boot/config.txt''**. ==== 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 Starting with Raspbian based upon Debian 8 Jessie, the I2C interface is disabled using the [[https://www.raspberrypi.org/documentation/configuration/device-tree.md|Device Tree]]. You can use the **raspi-config** tool, or just add to **''/boot/config.txt''** the following line: dtparam=i2c_arm=off ==== 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: {{.:raspberrypi:nokia-5110-3310-lcd-python-library.pdf|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 [[http://binerry.de/post/25787954149/pcd8544-library-for-raspberry-pi|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 [[http://www.cacti.net/|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 [[http://www.raspipress.com/2014/06/tutorial-install-nginx-and-php-on-raspbian/|Tutorial – Install Nginx and PHP on Raspbian]]. We want also the relative PHP module: apt-get install nginx php5-fpm php5-gd php5-imagick {{.:raspberrypi:protherm-web.png?200 |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 [[https://www.raspberrypi.org/forums/viewtopic.php?f=44&t=46525|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 [[http://rpi.science.uoit.ca/lab/gpio/|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 [[http://coder-tronics.com/switch-debouncing-tutorial-pt1/|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 [[http://pinout.xyz/pinout/pin3_gpio2|page]] (see also this [[https://www.raspberrypi.org/forums/viewtopic.php?f=32&t=96128|post]] and this [[https://www.raspberrypi.org/forums/viewtopic.php?t=112329&p=769722|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: **[[https://www.raspberrypi.org/documentation/configuration/device-tree.md|Device Trees, Overlays and Parameters]]**. Older Raspbian versions do things in a different way, see this [[https://www.raspberrypi.org/forums/viewtopic.php?t=98407|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 **[[https://pypi.python.org/pypi/telepot/|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 **[[https://core.telegram.org/bots|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 two function names: * **bot.notifyOnMessage(handle)** => **bot.message_loop(handle)** * **telepot.glance2(msg)** => **telepot.glance(msg)** ===== Installing on Raspberry Pi OS 2021-10-30 Bullseye ===== The latest version of the ProTherm software should work on Debian 11 Bullseye, this means using **Python 3.9** both for the **ProTherm** package and for the **Telegram bot** daemon. Also the **Adafruit libraries** used to drive the LCD should run on Python 3. I copied the **Raspberry Pi OS Lite** image onto the SD card, before the first bootstrap I enabled remote ssh and WiFi by creating two files into the boot partitions: * **/boot/ssh** - Just create an empty file. * **/boot/wpa_supplicant.conf** - Create a suitable file with your home ESSID and PSK. ==== Debian Packages ==== The extra Debian packages required by the ProTherm software are: * **busybox-syslogd** * **ntp** * **snmpd**, **rrdtool** * **nginx**, **php-fpm**, **php-gd**, **php-imagick** * **python3-pip** * **python3-daemon**, **python3-lockfile**, **python3-netifaces**, **python3-ntplib**, **python3-pil** * **python3-python-telegram-bot** * **python3-rpi.gpio** * Required by the adafruit-circuitpython-pcd8544 library: * **python3-serial** * **python3-usb** ==== Operating System Customization ==== This is a brief check-out list of customizations required by the operating system. See above for more details. * Disabled the **serial line console** from **/boot/cmdline.txt**, removing ''console=serial0,115200''. * Disabled **login on serial console** with ''systemctl disable serial-getty@ttyAMA0.service''. * Using **raspi-config**: * Interface Options => I2C => No (this will insert ''dtparam=i2c_arm=off'' into **/boot/config.txt**). * Interface Options => SPI => Yes (this will insert ''dtparam=spi=on'' into **/boot/config.txt**). * Localisation Options => Timezone => **Europe/Rome** * Localisation Options => Locale => **en_US.UTF-8** * Copied **PF Tempesta Seven** TTF fonts into ''/usr/local/share/fonts/''. === CPU overload and kworker === There is a problem with the old **Raspberry Pi model B** and the new Debian Bullseye, with **top** you can view several processes takin about 10% od the CPU during idle time: kworker/0:0+events_power_efficient kworker/0:5-events I turned out that an idle eth0 causes that, just disable eth0 to restore the CPU to 0.3%: ifconfig eth0 down To leave the interface down at bootstrap you must configure the **dhcpcd** daemon, which is repsponsible for network configuration in Raspberry Pi OS. Edit the file **/etc/dhcpcd.conf** and add the following line: denyinterfaces eth0 * **[[https://forums.raspberrypi.com/viewtopic.php?t=317429|kworker processes consume CPU with the latest Raspberry Pi OS]]** * **[[https://forums.raspberrypi.com/viewtopic.php?t=293974#p1775552|kworker processes consume CPU with kernel 5.4.79]]** === Nginx and PHP === You can verify that the web server and PHP are running: systemctl status nginx.service systemctl status php7.4-fpm.service Check also if the socket **/run/php/php7.4-fpm.sock** exists. Enable PHP by editing **/etc/nginx/sites-available/default**: index index.php index.html index.htm index.nginx-debian.html; location ~ \.php$ { include snippets/fastcgi-php.conf; # With php-fpm (or other unix sockets): fastcgi_pass unix:/run/php/php7.4-fpm.sock; } If you want Nginx to log to syslog instead that into a file, edit **/etc/nginx/nginx.conf**: access_log syslog:server=unix:/dev/log; error_log syslog:server=unix:/dev/log; ==== Installing the new Adafruit LCD library ==== **WARNING**: Do not use this library on the old Raspberry Pi model B, it is too CPU intensive! Use the old one. **WARNING**: The new library is more CPU intensive than the old one. I opened an **[[https://github.com/adafruit/Adafruit_CircuitPython_PCD8544/issues/16|issue on GitHub]]**. So I decided to give-up with the new library and use the old one (see below for installation tips). Here you can see a CPU utilization graph, the first half is using the old library, the second half is using the new one: {{.:raspberrypi:protherm-cpu-percent.png?direct&640|CPU usage with different Adafruit Python libraries}} Nowdays (2021), to operate the Nokia LCD, we should use the **[[https://github.com/adafruit/Adafruit_CircuitPython_PCD8544|Adafruit CircuitPython PCD8544]]** Python library. Detailed instructions can be found on the page **[[https://learn.adafruit.com/nokia-5110-3310-monochrome-lcd|Nokia 5110/3310 Monochrome LCD]]**. In the past (2016) it was used the **[[https://github.com/adafruit/Adafruit_Nokia_LCD|Adafruit Nokia LCD]]** library, which is now phased-out. To download the library **adafruit-circuitpython-pcd8544** and its dependencies without installing them: mkdir -p /usr/local/download/pcd8544 cd /usr/local/download/pcd8544 pip3 download adafruit-circuitpython-pcd8544 Some dependencies (libraries) exist as Debian Bullseye packages, but the version does not meet the requirements, so I uninstalled the Debian packages to install the PIP downloaded ones: dpkg --purge python3-sysv-ipc dpkg --purge python3-ftdi1 To avoid automatic dependency resolution (and related download/installation), I used the **%%--no-deps%%** option for each installation, checking manually the proper installation order (see the **Requires-Dist** entries into each packages METADATA): pip3 install --no-deps adafruit_circuitpython_pcd8544-1.2.6-py3-none-any.whl pip3 install --no-deps Adafruit_Blinka-6.18.0-py3-none-any.whl pip3 install --no-deps adafruit_circuitpython_busdevice-5.1.1-py3-none-any.whl pip3 install --no-deps adafruit_circuitpython_framebuf-1.4.8-py3-none-any.whl pip3 install --no-deps Adafruit_PlatformDetect-3.19.1-py3-none-any.whl pip3 install --no-deps Adafruit_PureIO-1.1.9-py3-none-any.whl pip3 install --no-deps pyftdi-0.53.3-py3-none-any.whl pip3 install --no-deps rpi_ws281x-4.3.1-cp39-cp39-linux_armv6l.whl pip3 install --no-deps sysv_ipc-1.1.0-cp39-cp39-linux_armv6l.whl We can test the library using the **[[https://github.com/adafruit/Adafruit_CircuitPython_PCD8544/blob/main/examples/pcd8544_simpletest.py|pcd8544_simpletest.py]]** example. Beware to modify the source code, because the **GPIOs** used to communicate to the SPI device do not fit our model **RASPBERRY_PI_B_REV2**. Here are the values needed by our hardware wiring: dc = digitalio.DigitalInOut(board.D23) # data/command cs = digitalio.DigitalInOut(board.D8) # Chip select reset = digitalio.DigitalInOut(board.D24) # reset Our LCD hardware works at best with **display.bias = 5** and **display.contrast = 57**. ==== Installing the old Adafruit LCD library ==== As reported above, the old and discontinued library **[[https://github.com/adafruit/Adafruit_Nokia_LCD|Adafruit Nokia LCD]]** is less CPU intensive than the new **Adafruit CircuitPython PCD8544**. Fortunately enough, the old library does run with Python 3, and I was able to use it on my old Raspberry Pi model B over the operating system derived from Debian 11 Bullseye. So the average CPU usage is **lower than 10%**, instead than **above 30%**! The library is not available from the **[[https://pypi.org/|PyPI]]** repository, so it must be downloaded from the GitHub repository. The library depends upon **Adafruit_GPIO**, which in turn requires **Adafruit_PureIO** and **spidev**. The two Adafruit libraries are available from PyPI, for **spidev** we installed the Debian packaged version. apt install python3-spidev mkdir -p /usr/local/download/Adafruit-GPIO cd /usr/local/download/Adafruit-GPIO pip3 download Adafruit-GPIO pip3 install Adafruit_PureIO-1.1.9-py3-none-any.whl pip3 install Adafruit_GPIO-1.0.3-py3-none-any.whl We download the library from its **GitHub repository** and install it using **pip3**: cd /usr/local/download git clone https://github.com/adafruit/Adafruit_Nokia_LCD.git cd Adafruit_Nokia_LCD/ pip3 install . The dependencies tree is the following: * Adafruit_Nokia_LCD * Adafruit_GPIO * Adafruit_PureIO * spidev ==== Installing the Telegram Bot Python library ==== FIXME: Into the Debian 11 Bullseye distribution there is the **python3-python-telegram-bot** package, which is a Telegram Bot Python library actively mantained, whereas the old Telepot library was abandoned. So we need to port our program to the new library and update these instruction. We wish to save locally the Telegram client library **telepot**, so we can redo the installation in the future, even if the library will not be longer available in the current version. So into a directory e.g. called **/usr/local/download/telepot/** we execute: pip3 download telepot ... Successfully downloaded telepot aiohttp urllib3 typing-extensions attrs async-timeout multidict chardet yarl idna The **library** and its **dependencies** will be downloaded (not installed) in current directory. Now we install the dependencies from the Debian respository, if they are available (we prefer the Debian packaging over the Pip repository) apt-get install python3-aiohttp python3-urllib3 python3-chardet python3-idna Finally we install the telepot package from the local copy: pip3 install telepot-12.7-py3-none-any.whl ==== Starting the protherm service at bootstrap ==== Raspberry Pi OS based on **Debian 11 Bullseye** uses **systemd** to start services on bootstrap. The **protherm** service requires the **/dev/spidev0.0** device to be available on start, otherwise it starts with the LCD disabled. Unfortunately the timing at which the kernel enables the device cannot be predicted, so we have to take some precautions to let systemd wait for the spidev device before starting protherm. First of all we create an //udev// rule; the rule will force //systemd// to create a "systemd device" when the kernel device becomes available. The rule is created into a file named **/etc/udev/rules.d/protherm-spidev.rules**: # Inform systemd when the kernel device /dev/spidev0.0 becomes available. # Systemd will create a systemd device named "dev-spidev0.0.device", which # can be used to trigger the start of a service. ACTION=="add", SUBSYSTEM=="spidev", KERNEL=="spidev0.0", TAG+="systemd" We use three keywords that must matched (because of the **%%==%%** sign) during an udev event. If the event is of type device **add**, the device belongs to the **spidev** subsystem and the kernel device name is **spidev0.0**, //udev// adds the string **systemd** to the **TAG** key associated to the device itself. Systemd includes the **systemd-udevd.service** which will notice every device tagged witht the string **systemd** and dynamically creates a **device unit**. In this case the unit will be called **dev-spidev0.0.device**. You can ask systemd to list all the existing device units (because of the **%%--all%%** option, also the short name is listed): systemctl list-units --all --type=device ... dev-spidev0.0.device loaded active plugged /dev/spidev0.0 ... If the systemd device does not exist, it probably means that the udev rule did not worked as expected. Thanks to this systemd device unit, we can write a protherm systemd unit that will start the service only after the device is instantiated by the kernel. The systemd unit will be **/etc/systemd/system/protherm.service**: [Unit] Description=Programmable Thermostat Service After=syslog.target # Wait for spidev0.0 systemd device to be available. After=dev-spidev0.0.device Requires=dev-spidev0.0.device [Service] Type=simple WorkingDirectory=/tmp/ ExecStart=/usr/local/sbin/protherm -f [Install] WantedBy=multi-user.target ===== Web references ===== * [[http://raspi.tv/2014/the-raspberry-pi-family|The Raspberry Pi Family]] * [[http://pinout.xyz/pinout/uart|Raspberry Pinout]] * [[http://elinux.org/RPiconfig|RPiconfig]]: ''/boot/config.txt'' file reference * [[https://www.cl.cam.ac.uk/projects/raspberrypi/tutorials/temperature/|Connecting the Temperature Sensor]] * [[https://learn.adafruit.com/nokia-5110-3310-lcd-python-library/|Nokia 5110/3310 LCD Python Library]] * [[http://sourceforge.net/p/raspberry-gpio-python/wiki/Inputs/|Python library raspberry-gpio-python]] * [[http://razzpisampler.oreilly.com/ch07.html|Connecting a Push Switch]] (internal, software configured PULL-UP) * [[https://www.cl.cam.ac.uk/projects/raspberrypi/tutorials/robot/buttons_and_switches/|Buttons and switches]] (external PULL-UP) * [[http://raspberrywebserver.com/gpio/detecting-a-button-press-through-GPIO.html|Detecting a button press through GPIO]] * [[http://raspberrywebserver.com/gpio/using-interrupt-driven-gpio.html|Using interrupt driven GPIO]] * [[http://coder-tronics.com/switch-debouncing-tutorial-pt1/|Switch Debouncing Tutorial Pt/1]] * [[http://raspberrypihobbyist.blogspot.it/2014/11/debouncing-gpio-input.html|Debouncing GPIO Input]] * [[https://www.raspberrypi.org/forums/viewtopic.php?t=87307&p=614853|False signals on GPIO inputs when touching Raspberry headers]] * [[https://www.raspberrypi.org/forums/viewtopic.php?f=32&t=130368|GPIO - Button triggers itself]] * [[https://www.raspberrypi.org/forums/viewtopic.php?f=28&t=82723|Symptom: False GPIO edge detections]] * [[http://spellfoundry.com/2013/03/27/basics-of-ferrite-beads-for-filters-emi-suppression-parasitic-oscillation-suppression-tutorial/|Basics of Ferrite Beads for Filters...]] * [[https://pi-plates.com/the-problem-with-relays/|The Problem with Relays]] * [[http://www.dafont.com/it/pf-tempesta-seven.font|Tempesta Seven font]] **Similar Projects** * [[http://ve2zaz.net/RasTherm/RasTherm.htm|RasTherm: a smart thermostat built on the Raspberry Pi, by Bertrand Zauhar]] * [[https://www.raspberrypi.org/forums/viewtopic.php?f=37&t=24115|Web enabled thermostat project]] **Telegram BOT** * [[https://core.telegram.org/bots|Bots: An introduction for developers]] * [[https://github.com/nickoala/telepot#examples|Telegram Bot API Examples]] * [[http://www.instructables.com/id/Set-up-Telegram-Bot-on-Raspberry-Pi/?ALLSTEPS|Set up Telegram Bot on Raspberry Pi]]