Skip to content

Installation on the Raspberry Pi

jbaumann edited this page Mar 1, 2022 · 20 revisions

Prerequisites

User

When installing the ATTiny Daemon as a service we let it run as the user pi. Since the Daemon has to shut down the RPi when the battery is low, it needs to call the system command shutdown, which can only be executed by the super user root. Raspbian (and Unix in general) has an elegant solution, the command sudo, which we use for this.

The necessary prerequisite is that the user pi can execute sudowithout entering a password. This is the case in a normal Raspbian installation, but may not be the case in yours. To test it, enter the following command:

sudo ls

If this works without requesting a password, then everything is fine.

If the system requests a password, then we have to reconfigure it to work without that. Check the file /etc/sudoers.d/010_pi-nopasswd. In a default Raspbian installation it should contain the following:

pi ALL=(ALL) NOPASSWD: ALL

Using the command sudo visudo /etc/sudoers.d/010_pi-nopasswd you can change this to:

#pi ALL=(ALL) NOPASSWD: ALL
pi ALL=(ALL) NOPASSWD: /sbin/shutdown

Please be very! careful when editing this file, because if this file contains errors you cannot use sudo anymore.

System

We are going to communicate with the ATTiny Daemon using I2C. This means that we have to enable it on our Raspberry Pi (most probably you have done that already to read out the voltage from the Geekworm UPS HAT).

If you have not done so, then start raspi-config with sudo raspi-config. Go to Interfacing Options and choose to enable I2C. You can now check that the I2C-bus is enabled:

  • After a reboot you should find message in the syslog that states that the i2c-module has been inserted.
  • Install the i2c-tools with sudo apt install i2c-tools. Now the command ì2cdetect -y 1 should output a table of entries. If there is a number, then the Raspberry has detected a device on this address. If you have connected both Geekwrom UPS HAT and ATTiny Daemon, you should get a result comparable to this:
pi@raspberrypi:~ $ i2cdetect -y 1
     0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f
00:          -- -- -- -- -- -- -- -- -- -- -- -- -- 
10: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 
20: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 
30: -- -- -- -- -- -- 36 37 -- -- -- -- -- -- -- -- 
40: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 
50: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 
60: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 
70: -- -- -- -- -- -- -- -- 

In the next step you can use one of the supplied Python scripts (useATTiny.py, readFuses.py or showStateAndBattery.py) to access the ATTiny. These scripts should print out some values.

Changing the I2C Clock

If you do not see the register printout of i2cdetect or if the scripts return only errors, and you are sure that you have installed everything correctly, you can try to change the clock rate of the I2C bus. You can do this by changing a line in the file /boot/config.txt. This file contains a lot of system-relevant configuration and a wrong entry here might stop your system from booting. You have been warned... Search for the following line:

dtparam=i2c_arm=on

This is the line that raspi-config has added to this file to turn on the I2C bus. Now change this to the following:

dtparam=i2c_arm=on,i2c_arm_baudrate=60000

This lowers the I2C clockrate from 100KHz to 60KHz and lets us work around one bug/limitation in the Raspberry Pi implementation of the I2C bus (if you are interested in the details, look up clock stretching on the RPi). If everything works now, then you can try the same with the value 80000 instead to increase the I2C speed (but 60KHz is definitely good enough for us).

Switching to a Software-Driven I2C Implementation (Bit-Banging)

An alternative with today's fast Raspberries is to use a software implementation instead of the hardware implementation without clock stretching. You can do this by changing the line

dtparam=i2c_arm=on

in the file /boot/config.txt to

dtoverlay=i2c-gpio,i2c_gpio_sda=2,i2c_gpio_scl=3,i2c_gpio_delay_us=2,bus=1

It is important that you do not have both lines active. The parameter i2c_gpio_delay_us defines the speed of the I2C bus. The value 2 results in a speed of 100KHz, higher values result in a slower bus speed.

The Last Resort

If you get errors at irregular intervals and the message Lost connection to ATTiny again and again, regardless of what you do, then the last resort might be to reinstall the whole OS. I had this problem with one RPi 3 with an older (but always updated) installation of Raspbian.

After months of tests and different approaches to solve this problem I finally installed the same version of Raspbian from scratch. Since then this installation works without any problems whatsoever. Test this with a new installation on another SD card first to verify that the problem is not the result of another underlying issue.

Python

The daemon is written in Python 3. Even though Python 2 is still used very often, it is deprecated and support for it will stop soon (at the beginning of 2020).

A modern Raspbian installation should have all necessary Python 3 packages, so there should be no need to install additional packages. Sometimes the SMBus-module is missing, though. In that case the following command will install it:

sudo apt install python3-smbus

MQTT Functionality

Prerequisites

If you want to use the MQTT-functionality, then you have to install paho-mqtt as a python package, which is done with pip3. If pip3 is not yet installed on your system, you can do it with the following command:

sudo apt install python3-pip

Now you can use pip3 to install the mqtt library used:

pip3 install paho-mqtt

Installing the Script

Copy the script attiny_daemon_mqtt_status.py to a place of your liking together with the file attiny_i2c.py. I suggest keeping it in the same directory as the attiny_daemon. Edit the copied script and edit at least the variable _topic to set the mqtt topic to which the state should be published. If your MQTT server is not situated on localhost and/or the default port then edit those as well. You can also enter a user and password, and finally, add any additional information you want to the posted json.

The last step is to add this script to the crontab of the user pi. This done by executing `crontab -e. Here you add a line with the following content:

* * * * * /opt/attiny_daemon/attiny_daemon_mqtt_status.py

Now the script will be executed once a minute and publish the state to MQTT. If anything goes wrong during execution, then a mail will be sent to the user pi. And that is it.

Installation as a Daemon

The very short version

Put the daemon files into a directory. The unit file references /opt/attiny_daemon, so either use this or change the unit file. Install the unit as a system service, enable and start it. Check logs.

The short version

  1. Download the files in the daemon sub directory to your Raspberry Pi.
  2. Create a directory /opt/attiny_daemon/ as user root sudo mkdir /opt/attiny_daemon
  3. Change the owner of this directory to user pi sudo chown pi /opt/attiny_daemon
  4. Place everything in the directory /opt/attiny_daemon as the user pi.
  5. Change the execution flag of the daemon: chmod +x /opt/attiny_daemon/attiny_daemon.py
  6. Review the entries in the configuration file attiny_daemon.cfg.
  7. Copy, as superuser, the service file to /etc/systemd/system/: sudo cp attiny_daemon.service /etc/systemd/system/
  8. Enable the service: sudo systemctl enable attiny_daemon
  9. Check the result: sudo systemctl status attiny_daemon
  10. Start the service: sudo systemctl start attiny_daemon
  11. Check the log for irregularities: journalctl -r -u attiny_daemon
  12. Every sleeptime seconds the daemon should write a log entry

The long version (or, what is a unit file?)

Writing a daemon today is simple work, all the heavy lifting is done by our trusty systemd (which today is prevalent on most Linux systems).

The unit description

Systemd takes a description file for a service (called a unit) that contains all necessary information for starting, restarting and stopping the service and uses this to guarantee that the service is run according to our wishes.

Our unit file describes a very simple service that should be started very early after booting, but only after e.g., the syslog becomes available. So we start our description of the service with the name, the default dependencies (none), when to start (after the sysinit.target has been reached).

[Unit]
Description=ATTiny UPS Daemon
DefaultDependencies=no
After=sysinit.target
StartLimitIntervalSec=0

The final line is pretty important for our use case. Normally, when a service fails and systemd tries to restart it, it uses start rate limiting to ensure that the system is not overload by it trying to restart a service. But when the rate limit is exceeded, the service is no longer restarted at all. And this behavior can be turned off by setting the value of StartLimitIntervalSec to the value 0.

The service description

The service description contains the specifics of how to start our service. The service type we choose is simple, which means that the service is available simply after starting the executable referenced after ExecStart.

The next step is to define the restart characteristics for our service. We want to restart always (even if the daemon exits without a set return code) and want it to restart after one second pause.

And finally we want the daemon to run as user pi.

[Service]
Type=simple
ExecStart=/opt/attiny_daemon/attiny_daemon.py
Restart=always
RestartSec=1
User=pi

The installation description

Until now we have described the prerequisites for and the specifics of the service. But until now we do not define that systemd should actually start the service at all. This is done by the last part of our service definition that states that we want the service to be started by the sysinit.target. This is used by systemd when we enable as service to decide when and how to start it.

[Install]
WantedBy=sysinit.target

And this is it. Naturally, systemd offers a lot more flexibility and complexity than shown here, but as shown it can be very simple to define a service with it and thus a system daemon for Linux.

You still need to execute the commands given before, but now it should be clear what happens behind the scenes.

Clone this wiki locally