display – James Batchelor https://james-batchelor.com Useful I.T & VoIP Ramblings Sun, 16 Feb 2025 16:27:48 +0000 en-US hourly 1 https://wordpress.org/?v=6.8.5 https://james-batchelor.com/wp-content/uploads/2025/05/cropped-cropped-logo-jb-202505-32x32.png display – James Batchelor https://james-batchelor.com 32 32 Pi Weather Display Project https://james-batchelor.com/index.php/2024/12/03/pi-weather-display-project/ Tue, 03 Dec 2024 17:50:00 +0000 https://james-batchelor.com/?p=975 Continue reading "Pi Weather Display Project"]]> This project is so cobbled together I’m almost proud of it, I don’t expect anyone to be able to recreate this exactly but if there are parts of it that help, here you go.

My dog prefers a walk early in the morning as the sun is dawning, so I’d like info on when dawn is along with the current weather forecast, plus very localised info on what the temperature is now and importantly, if it’s raining out.

The go to choice to present this info would be a Raspberry Pi with a small display to allow a quick glance at in the morning.

The info to display on the Pi screen can be sources from a couple of sources;

  • An API call from a weather service (weatherbit.io) for more general sunrise, sunset and current weather.
  • A SDR radio picking up a nearby weather station for more localised temperature and rainfall values.

This is how it comes together…

Concept

The plan is to keep this as simple as possible.

General weather data is to be provided from a weather service via an API call and can parse the required data for display on the screen. weatherbit.io is able to provide this for free up to 50 calls per day.

For local data from a nearby weather station, a SDR is needed to pick up the signals for decoding and parsing. In my case I already have an SDR connected to another Pi located in the attic for amateur radio, so can tap into this to pick up local stations.

To display this for quick reference, a local webpage that runs on Chromium in kiosk seems the easiest way to get the info on the screen. Rather than creating a dynamic webpage to pull the data, have a script create the html that includes a simple refresh to deal with the page changing when the data updates.

Hardware

In its most basic form, the main hardware in use:

However, the Pi 4 is a bit overkill, so I would like it to do double duty as a data store, and house it neatly in a custom case. Additional components would include:

Case

For the most compact packaging a custom 3D printed case would be created.

From previous attempts its designed to be a self-contained box, the design of this incorporated the following features:

  • Screwless / glueless design.
  • Incorporate a RJ45 socket to allow all ports to be located at the back.
  • Main io of Pi facing the rear for easy power connection.
  • Uses ribbon cable to secure / cushion HDD in place and to allow for tolerances.

Download STL files

Weatherbit API

After some trial and error, weatherbit.io seemed the best choice for easy access to weather data for the minimal of personal data, it does require signup for access to the API but is not too intrusive.

The free version for personal use allows up to 50 calls per day, allowing for a weather update every 30 minutes.

The output is quite detailed, and can be used as the only source for a weather display:

{"alerts":[],"count":1,"data":[{"app_temp":17.6,"aqi":65,"city_name":"NA","clouds":0,"country_code":"GB","datetime":"2024-08-31:12","dewpt":12.4,"dhi":110,"dni":864,"elev_angle":45.91,"ghi":723,"gust":12.3,"h_angle":0,"lat":NA,"lon":NA,"ob_time":"2024-08-31 12:48","pod":"d","precip":0,"pres":1008,"rh":72,"slp":1022,"snow":0,"solar_rad":723,"sources":["analysis","radar","satellite"],"state_code":"NA","station":"E0000","sunrise":"05:25","sunset":"19:00","temp":17.6,"timezone":"Europe/London","ts":1725108496,"uv":6,"vis":16,"weather":{"description":"Clear sky","code":800,"icon":"c01d"},"wind_cdir":"NE","wind_cdir_full":"northeast","wind_dir":55,"wind_spd":6.5}]}

Radio

I already have a separate Raspiberry Pi with an SDR attached. This uses rtl_tcp to allow connections to the SDR from software on other machines via the network. This works great to allow the SDR / antenna as high up as possible in my loft, and control it from my main PC at ground level.

Collection of data from a weather station comes from RTL 433, a fantastic tool that decodes data from a variety of devices on the 433mhz.

In order to integrate with my current setup, RTL 433 will connect to the SDR via rtl_tcp occasionally to collect latest data as only one device can use the SDR at a time. This allows my to “hijack” the SDR should I want to scan the radio.

My original scope was to use my personal weather station that only recorded temperature and humidity, but since using RTL 433 I’ve found a more advanced weather station nearby that can record rainfall and wind data, great for my project.

This station transmits its data every 60 seconds. So, to get this data I’ll run a scan via the SDR for 90 seconds every 10 minutes. This allows at least a recent transmit of the station but giving enough down time for me to step in and hijack the SDR when needed.

A SDR stream is 40Mbps, to save this going across the network every 10 minutes the script to collect data will be ran locally. The RTL 433 program can output to a json, and SNMP will be used to serve weather data to the display Pi.

Script

Bringing everything together requires some timing; SDR every ten minutes, API every half hour. Making this as easy as possible, the script is split into different sections and run at different times via cron jobs.

Collect SDR Data

This script is run on the SDR Pi and collects the radio data for 90 seconds, outputting data to a json file. This is set to run every 10 minutes:

Cron:

*/10 * * * * /home/pi/433_sensors/433_run.sh

Script:

#!/bin/bash
# Collect data for 90 seconds

timeout 90 rtl_433 -d rtl_tcp:10.0.1.242:1234 -F json:/home/pi/433_sensors/433_json.txt

The latest data is then offered to the rest of the network via SNMP…

Addition to snmpd.conf:

# Temperature - 433mhz radio
extend-sh temp_radio /etc/snmp/snmp-433mhz-temps.sh

snmp-433mhz-temps.sh

#!/bin/bash

######## Output order #########
#
# my sensor – temperature
# my sensor – humidity
# local ws – temperature
# local ws – humidity
# local ws - wind direction (degrees)
# local ws - wind average (mph)
# local ws - wind max (mph)
# local ws - rain (mm)
#
###############################

## Bresser-3CH - My temperature sensor ##

# JSON data file
JSON_FILE="/home/pi/433_sensors/433_json.txt"

# Grab latest entry for model Bresser-3CH
latest_entry=$(jq -r 'select(.model == "Bresser-3CH") | [.time, .temperature_F, .humidity] | @tsv' "$JSON_FILE" | sort -r | head -n 1)

# Check if the latest_entry is empty
if [ -z "$latest_entry" ]; then
  echo "No data found for model Bresser-3CH"
  exit 1
fi

# Extract time, temp and humidity from Bresser-3CH
latest_time=$(echo "$latest_entry" | awk -F'\t' '{print $1}')
temperature_F=$(echo "$latest_entry" | awk -F'\t' '{print $2}')
humidity=$(echo "$latest_entry" | awk -F'\t' '{print $3}')

# Convert temperature from Fahrenheit to Celsius and format to 1 decimal place
temperature_C=$(echo "scale=1; ($temperature_F - 32) * 5 / 9" | bc)

# Output results for SNMP collection
echo "$temperature_C"
echo "$humidity"

## WHx080 local weather station ##

# Grab latest entry for model Fineoffset-WHx080
latest_fineoffset_entry=$(jq -r 'select(.model == "Fineoffset-WHx080") | [.time, .temperature_C, .humidity, .wind_dir_deg, .wind_avg_km_h, .wind_max_km_h, .rain_mm] | @tsv' "$JSON_FILE" | sort -r | head -n 1)

# Check if the latest_fineoffset_entry is empty
if [ -z "$latest_fineoffset_entry" ]; then
  echo "No data found for model Fineoffset-WHx080"
  exit 1
fi

# Extract data from Fineoffset-WHx080
temperature_C_fineoffset=$(echo "$latest_fineoffset_entry" | awk -F'\t' '{print $2}')
humidity_fineoffset=$(echo "$latest_fineoffset_entry" | awk -F'\t' '{print $3}')
wind_dir_deg=$(echo "$latest_fineoffset_entry" | awk -F'\t' '{print $4}')
wind_avg_km_h=$(echo "$latest_fineoffset_entry" | awk -F'\t' '{print $5}')
wind_max_km_h=$(echo "$latest_fineoffset_entry" | awk -F'\t' '{print $6}')
rain_mm=$(echo "$latest_fineoffset_entry" | awk -F'\t' '{print $7}')

# Convert wind speeds from km/h to mph
wind_avg_mph=$(echo "scale=1; $wind_avg_km_h * 0.621371" | bc)
wind_max_mph=$(echo "scale=1; $wind_max_km_h * 0.621371" | bc)

# Output results for SNMP collection
echo "$temperature_C_fineoffset"
echo "$humidity_fineoffset"
echo "$wind_dir_deg"
echo "$wind_avg_mph"
echo "$wind_max_mph"
echo "$rain_mm"

Collect SDR data on Weather Display

This will collect the SDR data presented over the network via SNMP. This also runs every 10 minutes but is offset by a couple of minutes to ensure the most up to date values are collected.

Cron:

2,12,22,32,42,52 * * * * /home/pi/scripts/weather_data/generate_radio.sh

Script:

#!/bin/bash

# Collect weather data from SNMP and output to file.

# File paths
DATA_SNMP_TEMP="/home/pi/scripts/weather_data/data_snmp_temp.txt"
DATA_SNMP_HUMID="/home/pi/scripts/weather_data/data_snmp_humid.txt"
DATA_SNMP_RAIN="/home/pi/scripts/weather_data/data_snmp_rain.txt"
DATA_SNMP_RAIN_PREV="/home/pi/scripts/weather_data/data_snmp_rain_prev.txt"
DATA_SNMP_RAIN_FLAG="/home/pi/scripts/weather_data/data_snmp_rain_flag.txt"
DATA_SNMP_RAIN_COL="/home/pi/scripts/weather_data/data_snmp_rain_col.txt"

# Fetch temperature
SNMP_COMMAND='snmpget -v 2c 10.0.1.242 -c public NET-SNMP-EXTEND-MIB::nsExtendOutLine."temp_radio".1'

# Execute command
SNMP_RESULT=$($SNMP_COMMAND 2>&1)

# Check command did not return a timeout
if echo "$SNMP_RESULT" | grep -qi "timeout"; then
    echo "SNMP temperature request timed out, skipping."
else
    # If no timeout, save the output to file
    echo "$SNMP_RESULT" | awk '{print $NF}' > "$DATA_SNMP_TEMP"
    echo "SNMP temperature data saved"
fi

# Fetch humidity
SNMP_COMMAND='snmpget -v 2c 10.0.1.242 -c public NET-SNMP-EXTEND-MIB::nsExtendOutLine."temp_radio".2'

# Execute command
SNMP_RESULT=$($SNMP_COMMAND 2>&1)
# Check command did not return a timeout
if echo "$SNMP_RESULT" | grep -qi "timeout"; then
    echo "SNMP humidity request timed out, skipping."
else
    # If no timeout, save the output to file
    echo "$SNMP_RESULT" | awk '{print $NF}' > "$DATA_SNMP_HUMID"
    echo "SNMP humidity data saved"
fi

# Prepare files for rain flag
cp "$DATA_SNMP_RAIN" "$DATA_SNMP_RAIN_PREV"

# Fetch rain
SNMP_COMMAND='snmpget -v 2c 10.0.1.242 -c public NET-SNMP-EXTEND-MIB::nsExtendOutLine."temp_radio".8'

# Execute command
SNMP_RESULT=$($SNMP_COMMAND 2>&1)

# Check command did not return a timeout
if echo "$SNMP_RESULT" | grep -qi "timeout"; then
    echo "SNMP rain request timed out, skipping."
else
    # If no timeout, save the output to file
    echo "$SNMP_RESULT" | awk '{print $NF}' > "$DATA_SNMP_RAIN"
    echo "SNMP rain data saved"
fi

# Check if rain counter increased since last result
RAIN_PREV=$(cat "$DATA_SNMP_RAIN_PREV")
RAIN_NOW=$(cat "$DATA_SNMP_RAIN")

if (( $(echo "$RAIN_NOW > $RAIN_PREV" | bc -l) )); then
    # Output for display
    RAIN_STATUS="Yes"
    # Set text colour for display
    RAIN_COLOUR="#49b2fc"
else
    RAIN_STATUS="No"
    RAIN_COLOUR="#ffffff"
fi

echo "Raining: $RAIN_STATUS"
echo "$RAIN_STATUS" > "$DATA_SNMP_RAIN_FLAG"
echo "$RAIN_COLOUR" > "$DATA_SNMP_RAIN_COL"

Collect API Data

This pulls the weather from the API and extracts the bits we need to a file. While the API allows 50 calls a day, I’d found it returning a 403 in the early afternoon when call was set to 30 minutes. Therefore, its set to call every hour.

Cron:

0 * * * * /home/pi/scripts/weather_data/generate_api.sh

Script:

#!/bin/bash
# Pulls latest data from weatherbit api

# File paths
DATA_WEATHER_API="/home/pi/scripts/weather_data/data_api.json"
DATA_API_SUNRISE="/home/pi/scripts/weather_data/data_api_sunrise.txt"
DATA_API_SUNSET="/home/pi/scripts/weather_data/data_api_sunset.txt"
DATA_API_DESC="/home/pi/scripts/weather_data/data_api_desc.txt"
DATA_API_ICON="/home/pi/scripts/weather_data/data_api_icon.txt"

# Fetch Data
WEATHER_DATA=$(curl -X GET --header 'Accept: application/json' 'https://api.weatherbit.io/v2.0/current?lat=51.605817&lon=-3.091024&include=alerts&key=00000000000000000000000000000000')

# Check fetch was successful
if [ $? -ne 0 ]; then
    echo "API Call failed" >&2
    exit 1
else
    # If successful, write this data to file
    echo "$WEATHER_DATA" > "$DATA_WEATHER_API"
    echo "Weather data saved"
fi

# Extract data we need
SUNRISE=$(jq -r '.data[0].sunrise' < "$DATA_WEATHER_API")
SUNSET=$(jq -r '.data[0].sunset' < "$DATA_WEATHER_API")
WEATHER_DESC=$(jq -r '.data[0].weather.description' < "$DATA_WEATHER_API")
WEATHER_ICON=$(jq -r '.data[0].weather.icon' < "$DATA_WEATHER_API")

# API returns UTC time, need to adjust for BST
# Function to check current date is within the BST period
is_bst() {
    # Use system time to check if we’re in BST
    [[ $(date +%Z) == "BST" ]]
}

# Function to add 1 hour to the time
adjust_for_bst() {
    local time="$1"
    date -d "$time today + 1 hour" +%H:%M
}

# Check if it's currently BST
if is_bst; then
    # Add an hour to sunrise and sunset
    SUNRISE=$(adjust_for_bst "$SUNRISE")
    SUNSET=$(adjust_for_bst "$SUNSET")
fi

# Save extracted API data to separate files
echo "$SUNRISE" > "$DATA_API_SUNRISE"
echo "$SUNSET" > "$DATA_API_SUNSET"
echo "$WEATHER_DESC" > "$DATA_API_DESC"
echo "$WEATHER_ICON" > "$DATA_API_ICON"

Generate HTML

This brings together all collected values and inserts them into a html temple, the html file is replaced each run so also has a refresh mechanism to ensure it updates on the screen.

Like the SDR generation, this is ran every 10 minutes and offset to ensure most recently data is available to display.

Cron:

3,13,23,33,43,53 * * * * /home/pi/scripts/weather_data/generate_html.sh

Script:

#!/bin/bash
# Combines data from radio and api and creates html file for display

# File paths
DATA_SNMP_TEMP="/home/pi/scripts/weather_data/data_snmp_temp.txt"
DATA_SNMP_HUMID="/home/pi/scripts/weather_data/data_snmp_humid.txt"
#DATA_SNMP_RAIN="/home/pi/scripts/weather_data/data_snmp_rain.txt"
#DATA_SNMP_RAIN_PREV="/home/pi/scripts/weather_data/data_snmp_rain_prev.txt"
DATA_SNMP_RAIN_FLAG="/home/pi/scripts/weather_data/data_snmp_rain_flag.txt"
DATA_SNMP_RAIN_COL="/home/pi/scripts/weather_data/data_snmp_rain_col.txt"
DATA_WEATHER_API="/home/pi/scripts/weather_data/data_api.json"
DATA_API_SUNRISE="/home/pi/scripts/weather_data/data_api_sunrise.txt"
DATA_API_SUNSET="/home/pi/scripts/weather_data/data_api_sunset.txt"
DATA_API_DESC="/home/pi/scripts/weather_data/data_api_desc.txt"
DATA_API_ICON="/home/pi/scripts/weather_data/data_api_icon.txt"
DATA_HTML="/var/www/html/weather.html"

# Load latest data
TEMP=$(cat "$DATA_SNMP_TEMP")
HUMID=$(cat "$DATA_SNMP_HUMID")
RAIN=$(cat "$DATA_SNMP_RAIN_FLAG")
RAIN_COL=$(cat "$DATA_SNMP_RAIN_COL")
SUNRISE=$(cat "$DATA_API_SUNRISE")
SUNSET=$(cat "$DATA_API_SUNSET")
DESC=$(cat "$DATA_API_DESC")
ICON=$(cat "$DATA_API_ICON")

# Generate HTML
echo "Generating HTML content..."
cat << EOF > "$DATA_HTML"
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="refresh" content="60"> <!-- Refresh every 60 seconds -->
    <title>Weather Display</title>
    <style>
        body { font-family: Arial, sans-serif; font-weight: bold; color: white;}
        .container { width: 100%; margin: 0 auto; text-align: center; }
    </style>
</head>
<body style="background-color:black;">
    <div class="container">
       <table style="width:100%"><thead>
         <tr>
           <th colspan="2" rowspan="2"><img src="images/icons/$ICON.png" width="120" height="120"></th>
           <th colspan="2" style="text-align: left; font-size: 36px;">$DESC</th>
         </tr>
         <tr>
           <th style="text-align: right;"><img src="images/temperature.png" width="64" height="64"></th>
           <th style="font-size: 56px;">${TEMP}C</th>
         </tr></thead>
       <tbody>
         <tr>
           <td><img src="images/sunrise.png" width="64" height="64"></td>
           <td style="font-size: 48px;">$SUNRISE</td>
           <td style="text-align: right;"><img src="images/humidity.png" width="64" height="64"></td>
       <td style="font-size: 56px;">$HUMID%</td>
         </tr>
         <tr>
           <td><img src="images/sunset.png" width="64" height="64"></td>
           <td style="font-size: 48px;">$SUNSET</td>
           <td style="text-align: right;"><img src="images/rain.png" width="64" height="64"></td>
       <td style="font-size: 56px; color:${RAIN_COL};">$RAIN</td>
         </tr>
       </tbody>
       </table>
    </div>
</body>
</html>
EOF

Screen Display

Finally, time to get the data on the screen. For ease of use the webpage should start with the system so there is no configuring or manual execution after each reboot.

A service would be best, create the file:

/etc/systemd/system/pidisplay.service

Add this to the file:

[Unit]
Description=Launches Chromium in kiosk mode for my display.

[Service]
ExecStart=/home/pi/scripts/display_launch.sh
WorkingDirectory=/home/pi/scripts/
User=pi
Group=pi
Restart=always
Environment=DISPLAY=:0
Environment=XAUTHORITY=/home/pi/.Xauthority

[Install]
WantedBy=multi-user.target

Reload services and enable / start the service:

sudo systemctl daemon-reload
sudo systemctl enable --now pidisplay.service

This launches the display_launch.sh script which in essence loads a Chromium tab in kiosk mode (fullscreen) and with the minimum amount of disruptions:

#!/bin/bash


# Wait for X to start up properly
sleep 10
# Launch Chromium in kiosk mode with the HTML page
export DISPLAY=:0
chromium-browser --kiosk --disable-restore-session-state --no-sandbox file:///home/pi/scripts/display_output.html

Conclusion

All this in place and you have yourself a weather display. As mentioned at the top it is a bit of a cobbled together setup and way overkill. But the result is a quick reference display for when its still dark out.

Update

After a few months of operation it is working well, and with a relief that the daylight savings adjustment to the sunrise / sunset times worked without issue.

One issue that cropped up after a month’s deployment saw the radio data not being updated anymore.

The cause was the rtl433 output file becoming too large, as the JQ query scans the whole file to find the latest record, the SNMP get request was timing out before JQ had a change to find the most recent entry.

This is something a cron job to split and archive the output can solve.

Cron:

Run the scricpt every Monday at 00:05.

5 0 * * 1 /home/pi/433_sensors/433_json_archive.sh

Script:

#!/bin/bash

# Script to cleanup database, leave newest 1M at original filename.
# Note: If lines are not split cleanly, it'll cause snmp to fail.

# File paths
ORIGINAL_LOG="/home/pi/433_sensors/433_json.txt"
RECENT_LOG="/home/pi/433_sensors/temp_recent.txt"
LOG_ARCHIVE_DIR="/home/pi/433_sensors/log_archive"

# Timestamp in format YYYYMMDD-HHMMSS)
TIMESTAMP=$(date +"%Y%m%d-%H%M%S")

# Give a filename for old records (will gzip later)
OLDER_LOG="/home/pi/433_sensors/433_json_$TIMESTAMP.log"

# Split last 1000 lines to new file
tail -n 1000 $ORIGINAL_LOG > $RECENT_LOG

# Split previous lines upto 1000 to another file
head -n -1000 $ORIGINAL_LOG > $OLDER_LOG

# Put last 1000 lines back to original filename
mv $RECENT_LOG $ORIGINAL_LOG

# Compress and move old log to archive dir.
gzip $OLDER_LOG
mv "${OLDER_LOG}.gz" $LOG_ARCHIVE_DIR

Why am I keeping the old logs? Well I may one day like to expand what I can monitor via this method, namely tire pressures on my car. However I need to identify and note the frequency of thesse transmissions before I can add to monitoring. This may be a future project.

]]>
Update: Raspberry Pi Temperature Display https://james-batchelor.com/index.php/2016/10/30/update-raspberry-pi-temperature-display/ Sun, 30 Oct 2016 17:33:47 +0000 http://james-batchelor.com/?p=501 Continue reading "Update: Raspberry Pi Temperature Display"]]> With the cooler outdoor temperatures, a bug in my original code for the temperature display has cropped up:

IMG_6295

The DS18B20 returns temperatures with up to three decimal places, with the decimal point omitted, therefore a temperature of 10oC would be shown as 10000 by the sensor. To deal with this and for the display to show the temp to one decimal, I used this code:

        temperature1a = str (tempdata1[2:-3])

temperature1b = str (tempdata1[4:-2])

temperature1 = str ((temperature1a) + ‘.’ + temperature1b))

Here the value is essentially split in two, the number before the decimal and the number after, then the third line of code joins them back together with the decimal point in place. This is achieved by reading the value, then chopping off a number of characters using the [2:-3], this would chop off 2 characters at the start of the string, and 3 from the end.

The bug arises when the ambient temperature falls below 10oC as the sensor will output a 4 integer value as opposed to 5. This in turn will cause the cropping function to read incorrect values as its operation depends on a constant integer length.

As a result, the code will still function, but lower temperature values would be displayed without any decimalisation. Not happy with this I worked on another solution:

First Attempt

Referencing back to the original post on REUK, the code floated the text value into and integer, then a division of the value by 1000 gave the value a decimal point. Expanding on this I thought to keep the value as an integer to add the decimal, then convert to a string to allow the chopping off of the extra decimal places.

On testing however, I was getting inconsistent values, some values displayed with a decimal, others without.

I discovered that the DS18B20 would output a constant 5 or 6 figure value, however when floating to an integer, any trailing zeros after the first decimal place is removed. Therefore the snipping could again corrupt the output.

Result

Until I can find a better solution, it is better to keep it as simple as possible and display the temperature values intact, except the division by 1000 to add the decimal. Luckily the 20×4 LCD display is large enough to accommodate this, with only minor layout tweaks.

IMG_6303

For those interested, here is the amended code to read the sensor and output the value:

        tempfile1 = open(“/sys/bus/w1/devices/28-000007f34893/w1_slave”)

temptext1 = tempfile1.read()

tempfile1.close()

tempdata1 = temptext1.split(“\n”)[1].split(” “)[9]

temperature1 = float(tempdata1[2:])

temperature1 = temperature1 / 1000

]]>
Raspberry Pi Temperature Display https://james-batchelor.com/index.php/2016/09/30/raspberry-pi-temperature-display/ Fri, 30 Sep 2016 19:30:10 +0000 http://james-batchelor.com/?p=486 Continue reading "Raspberry Pi Temperature Display"]]> Nothing grand to start with, but as a first project I thought to combine a 20×4 LCD display with two DS18B20 temperature sensors to monitor indoor and outdoor temperatures. This would replace a cheap display I had been using but was sceptical on its accuracy.

Getting the basics right involved making the device blend in to the current setup without the usual mass of wires protrude. From the last round of the seam ably endless cable management war I used a shoebox base as a stand for the webserver and network switch, with the box hiding the various power bricks and extra cable, with most coming from the Xbox 360. Still having the shoebox lid I thought it would be good to use as a modular part to house the Pi and display.

Current Setup
Current Setup

Case chosen, I used some box packaging as a spacer to allow the lid to fit on the box base, with the lid overhanging bit a few millimetres to allow the lid to fit over the base. Then a chance to layout the machine within the voids.

As the Pi and drive would have ended upside down, I decided to use wire and hot glue to suspend them from the lid as a crude cage, hanging them between the base and top allowed to maximise cooling.

Wire and hot glue keeps components on place
Wire and hot glue keeps components on place

Now for the screen, after successful breadboard testing, I converted it onto a piece of stripboard. As it was one-sided stripboard all connectors had to be on the same side, but luckily there was enough room to allow all connections and still have the screen flush to the box. The board design was modified to integrate a switch to provide power to the LCD backlight LED.

Display connection board
Display connection board

Measuring from the outside, a hole was cut for the screen and another for the switch that supplied power to the LCD backlight. Inserting and checking fit for the screen and switch the header cables were connected to the GPIO pins of the Pi.

pitemp04
Layout complete

Next the temperature sensors, connecting via the GPIO pins of the Pi, these connected to a small piece of stripboard that acted as a connector to the DS18B20 chips to allow easy, modular access to them, and positioned to the rear of the case. I used pin headers to connect the indoor sensor, connected with wire salvaged from an old PC, and a 2.54mm PCB terminal block connecter to wire the external sensor.

Temperature sensor connection board
Temperature sensor connection board

The indoor sensor was simple enough, but not having a waterproof variant of the DS18B20 sensor resulted in me creating one by soldering wires direct to the sensor, using heat shrink insulation on the pins and covering in hot glue to create a crude weatherproof version. This turned out well as the single strand wire allowed precise angling of the sensor to ensure it remained out of sunlight but not touching surfaces that were susceptible to sunlight heat.

Indoor DS18B20 chip converted to outdoor use
Indoor DS18B20 chip converted to outdoor use

A bit of cable management and it was put in place. Next up was to power it up and apply the code to provide the display.
This code is contained in a single .py file within the home folder of Debain, by entering sudonano display.py and entering the following:

from Adafruit_CharLCD import Adafruit_CharLCD
from subprocess import *
from time import sleep, strftime
from datetime import datetime

import os
import time
import Adafruit_CharLCD as LCD

# Raspberry Pi pin configuration:
lcd_rs = 27 # Note this might need to be changed to 21 for older revisi$
lcd_en = 22
lcd_d4 = 25
lcd_d5 = 24
lcd_d6 = 23
lcd_d7 = 18
lcd_backlight = 4

# Define LCD column and row size for 16×2 LCD.
lcd_columns = 20
lcd_rows = 4

# Initialize the LCD using the pins above.
lcd = LCD.Adafruit_CharLCD(lcd_rs, lcd_en, lcd_d4, lcd_d5, lcd_d6, lcd_d7,
lcd_columns, lcd_rows, lcd_backlight)

# Print a welcome message to show script has started
lcd.message(‘James-Batchelor.com\n \n Starting up…’)

# Wait 5 seconds
time.sleep(5.0)

#—-Begin Loop—–

lcd.clear()

while 1:

#Read outdoor temperature
tempfile1 = open(“/sys/bus/w1/devices/28-000007f34893/w1_slave”)
thetext1 = tempfile1.read()
tempfile1.close()
tempdata1 = thetext1.split(“\n”)[1].split(” “)[9]
temperature1a = str (tempdata1[2:-3])
temperature1b = str (tempdata1[4:-2])
temperature1 = str ((temperature1a) + ‘.’ + (temperature1b))

#Read indoor temperature
tempfile2 = open(“/sys/bus/w1/devices/28-000007f268b0/w1_slave”)
thetext2 = tempfile2.read()
tempfile2.close()
tempdata2 = thetext2.split(“\n”)[1].split(” “)[9]
temperature2a = str (tempdata2[2:-3])
temperature2b = str (tempdata2[4:-2])
temperature2 = str ((temperature2a) + ‘.’ + (temperature2b))

#Read system temperature
cputempfile = os.popen(“/opt/vc/bin/vcgencmd measure_temp”)
cputemptext = cputempfile.read()
cputempfile.close()
cputemp = str (cputemptext[5:-3])

#Read system CPU load
cpuloadfile = os.popen(“uptime | tail -c 17”)
cpuloadtext = cpuloadfile.read()
cpuloadfile.close()
cpuload = str (cpuloadtext[:-13])

#Write to screen
lcd.clear()
lcd.message(datetime.now().strftime(‘%a %d%b%Y %H:%M\n’))
lcd.message(‘ Outdoor Temp: %s’ % ( temperature1 ) + ‘\n\n’ )
lcd.message(‘ Indoor Temp: %s’ % ( temperature2 ) + ‘\n\n\n’ )
lcd.message(‘RPi: T=%s’ % ( cputemp ) + ‘ L=%s’ % (cpuload) )
#Update screen every 20 seconds
sleep(20)

Ctrl O and Ctrl X to save, then the file was converted to executable by entering on the commnd line:
Sudo chmod +x display.py

The script will continuously run when started, but to make it start in the first instance, enter:
Sudo nano /etc/rc.local

In the file, enter the following at the end of the file, before the exit(0) line:
(sleep 1;sudo python /home/james/display.py)&

A quick reboot via sudo reboot and the script will update the display.

pitemp06
Finished product

Modifications

A check via various phone weather apps shows that the outside temperature was working great. However, the high indoor temperatures left me again sceptical. Luckily I had another Raspberry Pi and spare temperature sensors and found out I needed to move the indoor sensor further away from the case to provide more accurate readings.

Indoor sensor move away from heat sources
Indoor sensor move away from heat sources

So far its working great, but forever wanting more I’m currently researching how to send these temperature readings to a database in order to create graphs on daily temperatures. The Raspberry Pi community already has a host of different ways of achieving this, it’s just about picking the right one for me and implementing it to my needs.

Resources

LCD display code: Adafruit
Original temperature display code: REUK

]]>