Table of Contents

Installation

Prepare a Raspberry Pi (RPi) to read a DHT22 (digital humidity and temperature sensor) and post values as an EPICS IOC server.

  1. Initial configuration of the RPi
  2. Installation of required libraries
  3. Installation of the project code
  4. Run the project

Initial Configuration

There are several steps to configure a new RaspberryPi for use. We’ll follow (more or less) this guide from Adafruit: https://learn.adafruit.com/circuitpython-on-raspberrypi-linux/installing-circuitpython-on-raspberry-pi

  1. Parts list
  2. Connect a DHT22 sensor to the RPi
  3. Prepare the Operating System media
  4. Boot the RPi
  5. Pick a unique host name
  6. raspi-config
  7. Apply Operating system updates
  8. Enable the hardware interfaces used by this project
  9. Make Python v3 be the default python
  10. Reboot

Connect the DHT22 sensor

  1. If needed, solder a single-row, 6-pin socket header to the RPi board, covering GPIO pins 1,3,5,7,9,11

  2. Press a single-row, 5-pin strip of right-angle header pins into the sockets for pins 1,3,5,7,9, as shown in the next two photos.

    _images/zerow-back-pins-into-6header-sockets.jpg

    Right-angle header pins in 6-socket header soldered to RPi Zero W board.

    _images/zerow-back-pins-into-full-header-sockets.jpg

    Right-angle header pins in full socket header soldered to RPi Zero W board.

  3. connect the 3 pins of the DHT22 as follows:

    DHT22 pin GPIO pin meaning
    + 1 3v3 Power (+3.3 VDC)
    out 7 GPIO 4 (Pin 4, to match the software)
    - 9 Ground

Prepare the Operating System media

  1. OS Media choice: micro SD card, 4 GB (larger is not needed for this project but works fine, takes longer to flash AND backup, takes more space to backup)
  2. flash a new Raspberry Pi OS Lite onto a micro SD card (Balena Etcher recommended)
  3. enable SSH login: create empty ssh file on boot partition
  4. configure WiFi: create wpa_supplicant.conf file on boot partition per https://desertbot.io/blog/headless-raspberry-pi-4-ssh-wifi-setup
  5. Make Python v3 be the default python (python v2 is EOL starting 2020).

Boot the RPi

  1. install micro SD card in RPi and apply power
  2. identify new RPi IP number on your subnet and login: ssh pi@new.I.P.number
  3. password is raspberry until you change it (highly recommended)

Pick a unique host name

If you plan on having more than one RPi on your local subnet, then you should give a unique to each and every one of them. You can be creative, or mundane. Here, we name our pi based on its Serial number (from /proc/cpuinfo). We’ll start with rpi (to make the host name recognizable), then pick the last four characters of the serial number, expecting that to make a unique name:

# Suggested host name:
echo rpi$(cat /proc/cpuinfo  | grep Serial | tail -c 5)

Use this name in raspi-config below.

raspi-config

Run sudo raspi-config and configure these settings:

  • 1 change password for user pi
  • 2 Network Options: N1 Hostname – pick a unique name, see suggestion above
  • 4 Localisation Options: I2 Change Timezone – (if not set in wpa_supplicant.conf file)
  • 5 Interfacing Options: P4 SPI – Yes
  • 5 Interfacing Options: P5 I2C – Yes
  • 5 Interfacing Options: P8 Remote GPIO – No
  • 8 Update – select it

You may be prompted to reboot now. Probably best to reboot if you changed the hostname.

In different versions of RaspberryPi OS and raspi-config, these settings may be moved to other submenus. You might have to hunt for them.

Apply Operating system updates

Update the operating system with latest changes, patches, and security items. This command only runs the install if the first command (identify the packages with available upgrades) succeeds:

sudo apt-get update && sudo apt-get upgrade -y

This step could take some time (5-60 or more), depending on how many updates have been released since your download of the OS image was released.

Make Python v3 be the default python

By default, python v2 is what you get when you type python. Since python v2 reached the end-of-life after 2019, we want python3 to be called when we type python. Here’s how to make that happen:

# make python3 the default python
sudo apt-get install -y python3 git python3-pip
sudo pip3 install --upgrade setuptools
sudo update-alternatives --install /usr/bin/python python $(which python2) 1
sudo update-alternatives --install /usr/bin/python python $(which python3) 2
sudo update-alternatives --config python

Reboot

Finally, after all these steps, reboot the RPi.

sudo reboot

Installation of required libraries

Enable the _I2C_ and _SPI_ interfaces:

sudo apt-get install -y python3-smbus i2c-tools

This command will show any I2C or SPI devices in the system:

ls -l /dev/{i2c,spi}*

Any i2c-connected devices will report their address here:

sudo i2cdetect -y 1
# install python modules to support our Python code
# need module adafruit_dht
# https://learn.adafruit.com/circuitpython-on-raspberrypi-linux/installing-circuitpython-on-raspberry-pi
pip3 install RPI.GPIO adafruit-blinka adafruit-circuitpython-dht
sudo apt-get install -y libgpiod2

# need module caproto
pip3 install caproto  --no-warn-script-location

# to run EPICS IOC in a detached shell
sudo apt-get install -y screen

Installation of the project code

mkdir ~/Documents
cd ~/Documents
git clone https://github.com/prjemian/dhtioc
cd dhtioc/
pip3 install -e .
chmod +x dhtioc/dhtioc_manage.sh
pushd ${HOME}/.local/bin
ln -s ${HOME}/Documents/dhtioc/dhtioc/dhtioc_manage.sh ./

Run the IOC : command line

dhtioc -h
dhtioc --list-pvs --prefix ${HOSTNAME}:

Run the IOC : automatically

With a bash shell script, the dhtioc program can be started or stopped. When this script is added as a periodic cron task, the program will start automatically if it has stopped.

pi@rpi170f:~/Documents/dhtioc $ dhtioc_manage.sh
Usage: dhtioc_manage.sh {start|stop|restart|status|checkup|console|run}

    COMMANDS
        console   attach to IOC console if IOC is running in screen
        checkup   check that IOC is running, restart if not
        restart   restart IOC
        run       run IOC in console (not screen)
        start     start IOC
        status    report if IOC is running
        stop      stop IOC
  • start the IOC: dhtioc_manage.sh start
  • stop the IOC: dhtioc_manage.sh stop
  • restart the IOC: dhtioc_manage.sh restart
  • is the IOC running: dhtioc_manage.sh status
  • start IOC if not running: dhtioc_manage.sh checkup

Add checkup and restart to periodic tasks

The cron program runs periodic tasks. It is flexible to configure. The following first line is the configuration to run the checkup every two minutes (*/2). Any output (both print and error) will be discarded.

Sometimes, the dhtioc will stop logging temperature, such as shown in the next chart. Until the source of that is resolved, the second line below will restart the IOC at 3 minutes past the hour, every hour of every day.

_images/plot-stalled.png

The IOC stalled ~2:15 pm, then restarted at 3:03 pm via cron task.

*/2 * * * * /home/pi/.local/bin/dhtioc_manage.sh checkup 2>&1 > /dev/null
3 * * * * /home/pi/.local/bin/dhtioc_manage.sh restart 2>&1 > /dev/null

Add these lines to the list of periodic tasks using an editor (you’ll be asked which editor, pick nano if you aren’t sure which):

crontab -e

Scroll to the bottom of the file and enter the line above on a new line. Save the file and exit the editor. Within a couple minutes, the IOC should start automatically.

Look for the data log files

Once the IOC is running and has started collecting valid readings from the DHT22 sensor, there should be log files under ~/Documents/dhtioc_raw/ based on the year, month, and day. A new log file will be written each day (so no file get more than about 1 MB). These are text files with whitespace as separator between columns.

EXAMPLE

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
# file: /home/pi/Documents/dhtioc_raw/2020/12/2020-12-05.txt
# created: 2020-12-05 00:00:00.126145
# program: dhtioc
# version: 1.1.1+1.gcd2796d
# URL: https://dhtioc.readthedocs.io/
#
# IOC prefix: rpidec7:
#
# time: python timestamp (``time.time()``), seconds (since 1970-01-01T00:00:00 UTC)
# RH: relative humidity, %
# T: temperature, C
#
# time  RH  T
1607148000.12 43.0 22.5
1607148002.13 43.0 22.5
1607148004.13 43.0 22.5
1607148006.12 42.9 22.5
1607148008.13 42.9 22.5
1607148010.13 42.9 22.5
1607148012.12 42.9 22.5

Parts, Purchasing

  • RPi Zero W, preferred for this project:

    • RPi Zero series, because it easily runs headless, and only needs a 5 VDC, 1A power supply with micro USB connector.

    • Unlike the plain RPi Zero, has built-in WiFi.

    • Unlike the RPi Zero WH, you can solder just the pins you want, so you don’t expose possible short circuits of supply Voltage to ground from the unused pins.

      _images/exposed-pins.jpg

      Exposed pins when a full GPIO header is soldered to the RPi Zero board.

  • RPi 4 running NextCloudPi : Follow the wiring and installation instructions to add dhtioc to your NextCloudPi server!

Tip

Enclosures

For the best measurement of ambient conditions, place the DHT22 sensor head outside of the enclosure. Place the sensor inside to measure the RPi operating temperature.

Parts List

  1. Raspberry Pi (any model)
    1. power supply for the RPi (if you don’t have)
    2. SD card for the RPi, 4GB or larger (if you don’t have)
    3. enclosure or case for the RPi (suggested, optional)
  1. DHT22 (AM2302) Digital Humidity & Temperature sensor
    1. solderable header pins, sockets, jumper wires (optional), as needed
    2. connector wiring (optional), as needed

Source : StatsReg

source code: StatsReg

Implement a set of statistics registers in the style of a pocket calculator.

The available routines are:

def Clear():                        clear the stats registers
def Show():                         print the contents of the stats registers
def Add(x, y):                      add an X,Y pair
def Subtract(x, y):                 remove an X,Y pair
def AddWeighted(x, y, z):           add an X,Y pair with weight Z
def SubtractWeighted(x, y, z):      remove an X,Y pair with weight Z
def Mean():                         arithmetic mean of X & Y
def StdDev():                       standard deviation on X & Y
def StdErr():                       standard error on X & Y
def LinearRegression():             linear regression
def LinearRegressionVariance():     est. errors of slope & intercept
def LinearRegressionCorrelation():  the regression coefficient
def CorrelationCoefficient():       relation of errors in slope & intercept
see:http://stattrek.com/AP-Statistics-1/Regression.aspx?Tutorial=Stat

pocket calculator Statistical Registers, Pete Jemian, 2003-Apr-18

Source code documentation

class dhtioc.StatsReg.StatsRegClass[source]

pocket calculator Statistical Registers class

Add(x, y)[source]

add an X,Y pair to the statistics registers

Parameters:
  • x (float) – value to accumulate
  • y (float) – value to accumulate
AddWeighted(x, y, z)[source]

add a weighted X,Y, +/- Z trio to the statistics registers

Parameters:
  • x (float) – value to accumulate
  • y (float) – value to accumulate
  • z (float) – variance (weight = 1/z^2) of y
Clear()[source]

clear the statistics registers: \(N=w=\sum{x}=\sum{x^2}=\sum{y}=\sum{y^2}=\sum{xy}=0\)

CorrelationCoefficient()[source]

relation of errors in slope and intercept

Returns:correlation coefficient
Return type:float
LinearEval(x)[source]

Evaluate a linear fit at the given value: \(y = a + b x\)

Parameters:x (float) – independent value, x
Returns:y
Return type:float
LinearRegression()[source]

For (x,y) data pairs added to the registers, fit and find (a,b) that satisfy:

\[y = a + b x\]
Returns:(a, b) for fit of y=a+b*x
Return type:(float, float)
LinearRegressionCorrelation()[source]

the regression coefficient

Returns:(corr_a, corr_b) – is this correct?
Return type:(float, float)
See:http://stattrek.com/AP-Statistics-1/Correlation.aspx?Tutorial=Stat Look at “Product-moment correlation coefficient”
LinearRegressionVariance()[source]

est. errors of slope & intercept

Returns:(var_a, var_b) – is this correct?
Return type:(float, float)
Mean()[source]

arithmetic mean of X & Y

\[(1 / N) \sum_i^N x_i\]
Returns:mean X and Y values
Return type:float
Show()[source]

contents of the statistics registers

StdDev()[source]

standard deviation on X & Y

Returns:standard deviation of mean X and Y values
Return type:(float, float)
StdErr()[source]

standard error on X & Y

Returns:standard error of mean X and Y values
Return type:(float, float)
Subtract(x, y)[source]

remove an X,Y pair from the statistics registers

Parameters:
  • x (float) – value to remove
  • y (float) – value to remove
SubtractWeighted(x, y, z)[source]

remove a weighted X,Y+/-Z trio from the statistics registers

Parameters:
  • x (float) – value to remove
  • y (float) – value to remove
  • z (float) – variance (weight = 1/z^2) of y
determinant()[source]

Compute and return the determinant of the square matrices.

|  sum_w   sum_x      |          |  sum_w   sum_y      |
|  sum_x   sum_(x^2)  |          |  sum_y   sum_(y^2)  |
Returns:determinants of x and y summation matrices
Return type:(float, float)

Source : datalogger

source code: datalogger

class dhtioc.datalogger.DataLogger(ioc_prefix, path=None)[source]

Record raw values in data files.

PARAMETERS

ioc_prefix
str : EPICS IOC prefix
path
str : Base directory path under which to store data files. (default: ~/Documents/dhtioc_raw)
create_file(fname)[source]

Create the data file (and path as necessary)

PARAMETERS

fname
str : File to be created. Absolute path.
get_daily_file(when=None)[source]

Return absolute path to daily file.

PARAMETERS

when
obj : Path will be based on this instance of datetime.datetime. (default: now)
record(humidity, temperature, when=None)[source]

Record new values of humidity & temperature.

Create new file and path as needed.

PARAMETERS

humidity
float : Relative humidity, %.
temperature
float : Temperature, C.
when
obj : datetime.datetime of these values. (default: now)

Source : ioc

source code: ioc

Provide humidity and temperature using EPICS and Raspberry Pi

DHT_IOC(*args, sensor, report_period, **kwargs) EPICS server (IOC) with humidity & temperature (read-only) PVs.
main() Entry point for command-line program.
class dhtioc.ioc.DHT_IOC(*args, sensor, report_period, **kwargs)[source]

EPICS server (IOC) with humidity & temperature (read-only) PVs.

counter Used by autodoc_mock_imports.
humidity(instance, async_lib) Set the humidity, temperature and other PVs.
humidity_raw Used by autodoc_mock_imports.
humidity_trend Used by autodoc_mock_imports.
humidity_trend_array Used by autodoc_mock_imports.
temperature Used by autodoc_mock_imports.
temperature_raw Used by autodoc_mock_imports.
temperature_f Used by autodoc_mock_imports.
temperature_f_raw Used by autodoc_mock_imports.
temperature_trend Used by autodoc_mock_imports.
temperature_trend_array Used by autodoc_mock_imports.
humidity(instance, async_lib)[source]

Set the humidity, temperature and other PVs.

dhtioc.ioc.main()[source]

Entry point for command-line program.

Source : reader

source code: reader

class dhtioc.reader.DHT_sensor(pin, period)[source]

Get readings from DH22 sensor.

read() Read signals from the DHT22 sensor.
read_in_background_thread(**kwargs)
ready Has a value been read for both humidity and temperature?
terminate_background_thread(*args, **kwargs) Signal the background thread to stop.
read()[source]

Read signals from the DHT22 sensor.

ready

Has a value been read for both humidity and temperature?

terminate_background_thread(*args, **kwargs)[source]

Signal the background thread to stop.

Source : trend_analysis

source code: trend_analysis

Analyze signal for its recent trend.

Trend() Compute the current trend in signal values
class dhtioc.trend_analysis.Trend[source]

Compute the current trend in signal values

Apply smoothing with various factors, and take the slope of the smoothed signal v. the smoothing factor.

compute(reading) (Re)compute the trend.
slope Set the trend as the slope of smoothed v.
compute(reading)[source]

(Re)compute the trend.

Actually, reset the stats registers and load new values

slope

Set the trend as the slope of smoothed v. (1-smoothing factor).

Source : utils

source code: utils

Utility functions.

C2F(celsius) Convert celsius to fahrenheit.
run_in_thread(func) (decorator) run func in thread USAGE.
smooth(reading, factor, previous) apply smoothing function
dhtioc.utils.C2F(celsius)[source]

Convert celsius to fahrenheit.

dhtioc.utils.run_in_thread(func)[source]

(decorator) run func in thread USAGE:

@run_in_thread
def progress_reporting():
    logger.debug("progress_reporting is starting")
    # ...
#...
progress_reporting()   # runs in separate thread
#...
dhtioc.utils.smooth(reading, factor, previous)[source]

apply smoothing function

smoothed = k*raw + (1-k)*previous

Change History

1.1.2:

release expected -tba-

add version number to data files

1.1.1:

released 2020-08-20

OSError stopped acquisition

1.1.0:

released 2020-08-18

add (local) raw data logger feature

1.0.0:

released 2020-08-17

Initial release

0.3.3:

waveforms

0.3.2:

IOC shell management

0.3.1:

packaging, versioning, and publishing

0.3.0:

pre-release

  • rename repository as dhtioc
  • refactor to use (latest) adafruit_dht API
0.2.0:

use older DHT API

0.1.2:

analyze signal trends

0.1.1:

add caproto IOC

0.1.0:

restart

0.0.4:

statistics

0.0.3:

EPICS client version

0.0.2:

initial custom code

0.0.1:

proof of example


This project uses [sematic versioning](https://semver.org).

Indices and tables