Raspberry Pi – James Batchelor https://james-batchelor.com Useful I.T & VoIP Ramblings Mon, 29 Dec 2025 18:07:40 +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 Raspberry Pi – James Batchelor https://james-batchelor.com 32 32 Grafana Static Displays https://james-batchelor.com/index.php/2025/12/29/grafana-static-displays/ Mon, 29 Dec 2025 18:07:40 +0000 https://james-batchelor.com/?p=1080 Continue reading "Grafana Static Displays"]]> In the last post I migrated a Grafana database to a new server and took the opportunity to upgrade the Grafana version in the process.

This created a new issue, since Grafana’s supported browser list moves with the times, my two “NOC” screens consisting each of a Raspberry Pi 3A running Raspbian Buster and Hyperpixel 4.0 screens no longer worked. Instead, the outdated Chromium browsers now displayed the “If you’re seeing this Grafana has failed to load its application files” page when visiting the Grafana web interface.

Bringing the OS on the Pi’s up to date is the logical resolution, however, there are a couple of issues with this:

  • One of the Hyperpixel 4.0 screens is an early revision model, meaning the official and legacy drivers do not work.
  • Even with working screen drivers, compatible browsers will not load on the 512MB RAM available to the Pi 3A.

Not wanting to obsolete a pair of Pi 3A’s with a now unique form factor, instead a workaround was found to continue displaying Grafana dashboards on this older hardware. The solution even gave a slight benefit over the original URL…

To achieve this, Grafana has released grafana-image-renderer, essentially a binary with a self-contained and headless Chromium instance that will load a dashboard and capture an image of the output.

Until recently, this was available as a Grafana plugin, however this plugin has since been depreciated in favour of it being a standalone application or docker container. A lot of online documentation still refers to it in its plugin form and so is now outdated.

The current iteration of grafana-image-renderer recommends using it within a docker container due to the potential resource usage it can generate. However, for this guide, only the standalone binary will be used as its only generating two images, and I find resource usage is at a minimum.

Installation

Firstly, grab the binary and store it in a easy but sensible location:

cd /opt
wget https://github.com/grafana/grafana-image-renderer/releases/download/v5.0.6/grafana-image-renderer-linux-amd64

Then make the file executable:

chmod +x grafana-image-renderer-linux-amd64

To have it run as a service, create a service file for it:

nano /etc/systemd/system/grafana-image-renderer.service

And populate the file with the following:

[Unit]
Description=Grafana Image Renderer service
After=network.target

[Service]
ExecStart=/opt/grafana-image-renderer-linux-amd64 server --
WorkingDirectory=/opt/
Restart=on-failure
User=root
Environment=GRAFANA_RENDERER_PLUGIN_STARTUP_TIMEOUT=30s 
[Install]
WantedBy=multi-user.target

Reload the service daemon, start the new service, and check it is running:

systemctl daemon-reload
systemctl start grafana-image-renderer.service
systemctl status grafana-image-renderer.service

You can check If the service is running by visiting the web page of the server on port 8081:

Authentication

A default Grafana server install requires login before being able to see any dashboards, this extends to the image renderer. As we can’t login to the web interface interactively within the service, a service account needs to be created.

In the main Grafana web interface, login and navigate to Home > Administration > Users and access > Service accounts, then click “Add service account”.

Give it a display name and set the role to viewer:

Now in the newly created service account, click “Add service token”, then on the popup dialog, click “Generate Token”.

A token with a “glsa_” prefix is displayed, make a copy of this as it will not be available to view after closing this dialog box:

This will be used as authentication when generating our dashboard images…

Generating Images

With the renderer installed, and authentication available, images can now be generated.

Images are generated by requesting a URL from the service with the dashboard within the variables, the syntax is:

http://{grafana-image-renderer}:8081/render?encoding=png&url=http://{grafana-dashboard}?{options}

Where:
Grafana-image-renderer – The IP of the machine the renderer is running on.
Grafana-dashboard – The URL of the dashboard you’d like to capture.
Options – Options to manipulate the output/how the dashboard is displayed.

However, the authentication header is needed to successfully access the dashboard. For this reason its easier to use a curl command to set the headers and grab the image

For example, if the renderer is running on the same machine as the main Grafana, a dashboard can be generated by executing:

curl -H "X-Auth-Token: {glsa_token}" "http://localhost:8081/render?encoding=png&url=http://localhost:3000/d/{id}/{dashboad}?kiosk&width=800&height=600" -o image.png

The options used here is kiosk; to remove the menus from the output, and height and width to match the intended displays.

Automation:

One minute updates are fine for this use case, so the easiest way automate this is via cron. Using crontab -e, add the following line:

/usr/bin/curl -H "X-Auth-Token: {glsa_token}" "http://localhost:8081/render?encoding=png&url=http://localhost:3000/d/{id}/{dashboard}?kiosk&width=800&height=600" -o /var/www/html/dash1.png

This line generates the dashboard every minute and stores it on the default web server directory, allowing it to be viewed on the network via an installed web server.

But this will only create a static image, to allow the image to update on a display, a simple webpage can be created and stored on the webserver for easier access and reference.

Create a new webpage:

nano /var/www/html/dashboard.html

And paste the following:

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Grafana Dashboard</title>
  <style>
    body {
      margin: 0;
      background-color: black;
      display: flex;
      justify-content: center;
      align-items: center;
      height: 100vh;
    }
    img {
      max-width: 100%;
      max-height: 100%;
      object-fit: contain;
    }
  </style>
</head>
<body>
  <img id="liveImage" src="dash1.png" alt="Live Image">
  <script>
    const img = document.getElementById("liveImage");
    setInterval(() => {
      const timestamp = new Date().getTime(); // avoid browser cache
      img.src = "dash1.png?t=" + timestamp;
    }, 60000); // 60 seconds
  </script>
</body>
</html>

When visited, this webpage will update the same image (dash1.png) every 60 seconds to ensure the browser updates with the latest available image instead of caching.

Displays

Viewing this on the display is as simple as visiting the above webpage. As the html is served from the Grafana server and where the auto refresh is taken care of, as long as the display is able to load the page, all is taken care of at the Grafana server.

Summary

Since all that is required from a display is being able to load and render a static image, this allows the oldest and slowest machines to be able to display a Grafana dashboard, negating the need to keep the displays up to date with Grafana’s evolving browser support.

I wish I learnt of this sooner, as it gives more standardised control of the layout of a dashboard. The HyperPixel displays are quite high resolution for their size, and loading a dashboard natively often lead to frustrations with the layout of panels not matching that displyed on a laptop/desktop screen. Presumably due to scaling. Using the image render allows you to test and fix the layout before deploying to a screen, and with a higher panel density too.

Another benefit is that most processing is taken away from the Pi 3A’s, as it is now only responsible for displaying a simple webpage with a single image, rather than loading the full Grafana UI. The Pis have been running on this new method for over a month without any lockups, which was previously seen on a weekly basis.

]]>
SIP device as Pi Audio Output https://james-batchelor.com/index.php/2025/02/16/sip-device-as-pi-audio-output/ Sun, 16 Feb 2025 16:55:45 +0000 https://james-batchelor.com/?p=992 Continue reading "SIP device as Pi Audio Output"]]> In my Development Den (the spare room) I have a Raspberry Pi 4 setup with a monitor for use as a quick reference station when working on nearby devices.

With no speakers connected this can sometimes pose an issue when trying to follw a tutorial video, and when I do need audio, a Bluetooth speaker is never around.

There is a SIP phone next to the Pi on my desk, and so I thought; that has a decent network connected speaker, why not use that?

This is what ensued…

The idea is to have a SIP endpoint running as a service on the Pi in auto-answer mode. This will allow a desk phone to dial the Pi extension and receive the Pi’s audio through the loudspeaker.

The Pi is running stock Raspbian Bullseye with the desktop environment.

SIP Endpoint

Baresip looks a good choice for this project due to its modularity.

As this will be a service running in the background, only the core module of Baresip needs to be installed:

sudo apt install --no-install-recommends baresip-core

When installed, run the program briefly to have it create its default configuration files:

baresip

This will create the default template files in a .baresip directory within the home folder. We’ll need to edit the accounts and config files to get it to a call answering state.

Starting with the accounts file:

nano .baresip/accounts

Add the following line to the bottom of the file:

<sip:{endpoint}@{sip_server}>;auth_pass={sip_secret};answermode=auto

Where:
{endpoint} – SIP extension number
{sip_server} – IP/hostname of the PBX
{sip_secret} – Extension password

Answermode flag has been added to allow calls to this extension to be answered automatically.

After the accounts file, move onto the config:

nano .baresip/config

This file can be left as default, however a few quality-of-life improvements will be made…

Uncomment the following lines:

module                  opus.so
module                  g722.so

These allow the use of the higher quality codecs commonly in use; g722 is the elder and while it offers higher quality from a phone call audio point of view, may fall short for music. Opus is the newer and can be configured for excellent quality overall, but being newer may not be an available choice on older phones.

If Opus is available, the bitrate can be increased via this line further down the config file (higher bitrate, higher audio quality):

opus_bitrate            28000 # 6000-510000

Make sure both you SIP devices and PBX are capable and configured to offer these codecs at the highest priority.

Testing

Good time for a sanity check, for this an audio file or stream playing through VLC, or any source of audio will do.

Run the application in the terminal

baresip

And make a call to its extension, you should see output in the terminal, and hear the audio through the phone.

If it’s successful, a service can be created to have this running in the background on startup…

Create Service

To allow baresip to start at boot, its best to create a service for it and restart it if it ever stops.

Create and edit a service file:

sudo nano /etc/systemd/system/sipaudio.service

Add the following to this file:

[Unit]
Description=SIP endpoint for Pi audio
After=sound.target network.target

[Service]
ExecStart=/usr/bin/baresip
Restart=always
User=pi
WorkingDirectory=/home/{username}
StandardOutput=journal
StandardError=journal
Environment=HOME=/home/{username}
Environment=XDG_RUNTIME_DIR=/run/user/1000

[Install]
WantedBy=multi-user.target

When saved, reload services:

sudo systemctl daemon-reload

Start the service and enable it to start at boot:

sudo systemctl enable --now sipaudio

With some audio playing, try another call to make sure its answering and picking up audio from the desktop environment.

Conclusion

It’s a niche solution for those who have an audio-less Pi and a SIP phone next to it, but the results are plesently convenient for those rare times when audio is needed.

My accompanying desk phone (Yealink T46S) only offers g722 has the higher codec, but still is perfectly fine for speech output and fine (not great) for audio. I’m sure using Opus at the higher bitrate will put it on par with some of the streaming services. Afterall, YouTube uses opus as audio for its videos, as noted by the “stats for nerds” section:

]]>
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.

]]>
Install MediaMTX on Raspbian Bookworm https://james-batchelor.com/index.php/2023/11/10/install-mediamtx-on-raspbian-bookworm/ Fri, 10 Nov 2023 19:50:43 +0000 https://james-batchelor.com/?p=915 Continue reading "Install MediaMTX on Raspbian Bookworm"]]> For a number of years, I’ve been using MotionEyeOS on my CCTV cameras, exclusively for the “Fast Network Camera” mode that enables RTSP for low bandwidth ingress to the MotionEye (running on CentOS) while maintaining good image quality.

Finding more uses for these Pi’s necessitates moving to Raspbian with MotionEye as the OS version is very bare bones by. Moving from the OS also means losing the Fast Network Camera and streams on the network jump from 2 Mbps to 25 Mbps.

Over multiple camera’s this really adds up, so I need a way replicate the low bandwidth, high quality streams with the versatility of Raspbian. The solution found is to install MediaMTX (formally rtsp-simple-server) …

This guide is based on a Raspberry Pi 3 board and using a Pi Camera Module v2.1. The micro SD card has a new install of Raspbian Bookworm 32bit.

Install

Logging in via SSH, run an update to have the latest repo’s available…

sudo apt-get update

Using MediaMTX with the Raspberry Pi camera module requires a couple of libraries, install or upgrade them

sudo apt-get install libfreetype6 libcamera0

MediaMTX can now be downloaded. Which version you use depends on Raspbian version and Pi used. For 32bit versions of Raspbian use the “armv7” variant, and “arm4” for 64bit. To check which one is running…

uname -m

32bit versions will respond with armv71 and 64bit with arm64.

Visit https://github.com/bluenviron/mediamtx/releases for latest versions and download links.

Copy the link and enter in the terminal with wget prefix, below example I’m using a 32bit version…

wget https://github.com/bluenviron/mediamtx/releases/download/v1.2.1/mediamtx_v1.2.1_linux_armv7.tar.gz

Extract the files…

tar xzvf mediamtx_v1.2.1_linux_armv7.tar.gz

Of the files extracted, mediamtx is the binary and mediamtx.yml contains its configuration.

Config and Test

Firstly edit the yml file so it uses the Pi camera

nano mediamtx.yml

Scroll to the bottom of the file, replace the following lines of code at the end of the file…

paths:
  # example:
  # my_camera:
  #   source: rtsp://my_camera
  # Settings under path "all_others" are applied to all paths that
  # do not match another entry.
  all_others:

With the following…

paths:
  cam:
    source: rpiCamera
    rpiCameraWidth: 1280
    rpiCameraHeight: 720
    rpiCameraVFlip: true
    rpiCameraHFlip: true
    rpiCameraBitrate: 1500000

Notes:

  • YML files require the indentation.
  • cam: – this will be included in the path when accessing the stream.
  • rpiCamera… – These are extra settings that can be specified, for example the VFlip and HFlip are due the camera being mounted upside down, a full list of options are included further up in the file.

I also took this opportunity to disable all other protocols, just leaving RTSP on. When finished editing, Ctrl O and Ctrl X to same and exit.

To test this config, run the binary…

./mediamtx

While running, this can be tested using VLC on another computer on the local network, open a network stream and enter the following, replacing {ip address} with the IP of the PI….

All well and the camera output appears.

Use Ctrl C to stop the program, as we can now add it as a service.

Add as Service

To allow it to start automatically and for easier control of it the program can be created as service. To start let’s move the files to a safer directory…

sudo mkdir /opt/mediamtx
sudo cp mediamtx /opt/mediamtx/
sudo cp mediamtx.yml /opt/mediamtx/

Create a new service file for editing…

sudo nano /etc/systemd/system/mediamtx.service

and add the following…

[Unit] 
Wants=network.target
[Service] 
ExecStart=/opt/mediamtx/mediamtx /opt/mediamtx/mediamtx.yml
[Install] 
WantedBy=multi-user.target

Ctrl O and Ctrl X to same and exit.

As it’s a new service file, need to reload systemctl…

sudo systemctl daemon-reload

Now can start the service and enable at the same time so it starts when the Pi starts…

sudo systemctl enable --now mediamtx

To check its running, use the following

sudo systemctl status mediamtx

Should get the following…

Now you can access the stream again via VLC, or add as a network camera to a MotionEye setup.

Results

To goal of this exercise was to reduce bandwidth on the network and return to similar levels observed when using MotionEyeOS while using a Raspbian OS.

Below are iftop comparisons while a camera stream is open and at 1280 x 720.

Using MotionEye, with http connection to a MotionEye server:

Using MediaMTX streaming via RTSP to VLC:

Considerations

While searching for a solution I tried StreamEye, a simple program written by the same developer as MotionEye.

While a very easy program to get up and running and it offered features close to “Fast Network Camera” on MotionEyeOS, the pure MJPEG stream resulted in approx. 40Mbps bandwidth utilisation on a 720p stream.

]]>
Working From Home – Pi Camera as Windows Webcam https://james-batchelor.com/index.php/2020/04/07/working-from-home-pi-camera-as-windows-webcam/ Tue, 07 Apr 2020 18:10:16 +0000 http://james-batchelor.com/?p=658 Continue reading "Working From Home – Pi Camera as Windows Webcam"]]> Hopefully the mad dash for home working is over, and now everyone who can has settled in to a comfortable home setup with new knowledge of what a VPN and remote desktops are all about.

I thought my established home setup was great, however I didn’t envisage words like Teams and Zoom to become the buzzwords of companies the world over.

This posed an issue for myself. Even though my 2017 Dell XPS has a webcam ready to go, it spends its home office days docked with the lid closed. When opening the 4K screen combined with the Full HD monitors, Windows implementation of scaling rears is appalling head and just looks terrible. Combined with the Dell’s decision to locate the webcam below the screen to give maximum nostril-cam angle, its not something I like to use.

Buying a USB webcam didn’t seem worth it for what is (hopefully at time of writing) a temporary solution.

So in comes another Pi project, this time using a spare Raspberry Pi Camera module connected to an aptly placed Pi, that can be used on a Windows machine for the software likes of Zoom, Skype and Teams…

Pi Setup

Enable Camera

Using a fresh install of Raspbian Buster, locate and login to SSH of the Pi and run the configuration utility:

sudo raspi-config

In the menu, navigate to 5. Interfacing Options, then P1 Camera, and enable.

Navigate back to the main menu and Enter on Finish, select Yes if prompted to reboot.

Set Static IP

The camera will be accessed over the network, so makes life easier and more reliable for it to always be found on the network in the same location. In an SSH session, enter:

sudo nano /etc/dhcpcd.conf

Scroll to the bottom of the page and enter the following:

static ip_address={IP ADDRESS}/24
static routers={ROUTER ADDRESS}
static domain_name_servers={ROUTER ADDRESS}

Install Software

The software I’d prefer for this task is MJPEG Streamer, a lightweight camera image generator that has come to my aid when setting up a CCTV system.

Start with installing the dependencies:

sudo apt-get install build-essential imagemagick libv4l-dev
libjpeg-dev cmake

Download the MJPEG Streamer files from git:

git clone https://github.com/jacksonliam/mjpg-streamer.git

Navigate the folder in order to compile:

cd mjpg-streamer/mjpg-streamer-experimental

Then compile:

make

No errors? Go and install it:

sudo make install

This will install the program, and leave you at the command line with little fanfare…

Testing

Sanity check, and time to see if the software will work, from the command line run the following:

/usr/local/bin/mjpg_streamer -i "input_uvc.so -r
1280x720 -d /dev/video0 -f 30" -o "output_http.so -p 8080 -w
/usr/local/share/mjpg-streamer/www"

These are the common variables should you need to adjust anything to get started:

-r 1280x720 – Set stream output resolution
-f 30 – Framerate
-p 8080 – Port the stream binds to

From another PC, open a browser and navigate to http://{IP ADDRESS}:8080/?action=stream where {IP ADDRESS} is that of the Raspberry Pi.

Launch at Startup

You may notice when testing the above test command that it occupies the command line whilst running, and doesn’t allow any other tasks, exiting the program by using Ctrl + C also stops the video stream.

Therefore its more convenient to have the camera stream start when the Pi starts, and have it run unhindered in the background to give easy access to the Windows machine.

Script File

Before it can start automatically, the command needs to be wrapped in a script file and executed:

Open a new text file with nano, located in the Pi (Default user) home directory:

sudo nano /home/pi/camstart.sh

Paste the command tested above into the file, then save and exit with Ctrl+O and Ctrl+X

This file needs to be executed, so give it execute permissions:

sudo Chmod +x /home/pi/camstart.sh

Script made, it needs to be run at boot, this can be done with cron:

sudo crontab -e

Enter the following on a new line at the bottom of the file:

@reboot /home/pi/camstart.sh

Use Ctrl+O and Ctrl+X to save and exit.

Now to see if it worked, restart the Pi gracefully with:

sudo shutdown -r now

When it re-appears, visit http://{IP ADDRESS}:8080/?action=stream again and see if you have a live stream.

Windows Setup

Now the camera is setup and broadcasting the image over the local network, we need something on the windows machine to capture and convert it and make it recognisable by video conferencing software.

There are a number of applications that can achieve this, but often it is an addon to a much larger software suite and would be considered to be bloatware to what we want to achieve.

Cue IP Camera Adapter 3.1 https://ip-webcam.appspot.com/ as a lightweight application that has just does the conversion process and nothing else.

Run the setup wizard and upon installation, run the “Configure IP Camera Adapter” from the start menu:

Enter http://{IP ADDRESS}:8080/?action=stream into the URL field.

And test the link by pressing Autodetect, the message with tell you if it was successful:

Final Testing

From here the camera should be available on most video software, you will just have to update it to use the newly added camera, this will be labelled as MJPEG Camera:

]]>
Automated Timelapse: 2019 Update https://james-batchelor.com/index.php/2019/12/31/automated-timelapse-2019-update/ Tue, 31 Dec 2019 22:14:24 +0000 http://james-batchelor.com/?p=644 Continue reading "Automated Timelapse: 2019 Update"]]> I like this time of year, a chance to reflect on the last 12 months and take stock of accomplishments and realise the achievements. And something I like to gauge a success on is the longevity of a solution, and a time-lapse comparison 6 months apart is seemingly my go to example.

To elaborate on this achievement, earlier this year was the setup of a homebrew CCTV solution using an array of Raspberry Pi’s with cameras, and a VM Cent OS server acting as a PVR host. A surplus Pi W Zero was pointed at the hills and used as a time-lapse experiment.

The real achievement is that since its conception in early June, it has been stable enough to run in the background, capturing footage for such an occasion.

So here I present my latest time-lapse, a split screen video on the difference between a June day and a December day:

With time a solution also brings some observations and learning opportunities that would have been hard to imagine on conception:

Storage Capacity: In hindsight it was wise to start this project in June and during the summer solstice of the northern hemisphere. The maximised daylight hours created the most capture data stored to disk; therefore, the retention times of footage could be set to ensure the disk does not reach capacity.

While this works for MotionEye’s capture settings, the time-lapse script has been able to run without capacity constraints, so the 83GB (on a 3TB disk) will need to be moved / deleted off the disk as the nights grow longer to avoid exhaustion of storage space.

Camera Movement: During its conception, an elegant solution to create a makeshift mount for the camera was to fashion some bell wire (single core insulated wire) from the mount points of the Pi to the mount points of the camera, twisted in-between to add rigidity.

What I didn’t account for was the effect heat would have on twisted wire, this was apparent in the summer but seemed organic as the movement was in line with the temperature fluctuations of a normal day. However unforeseen was the winter and with that the part my central heating and radiators would manipulate the cameras position.

The combination of the radiator below the camera and the heating set on a timer resulted in large skews of the camera’s position, to the point where you could tell when the heating was on and off during the day. As a result, the footage used for winter was recorded while I wasn’t home and so the heating was off.

]]>
LibreElec – Pi Camera Mjpeg Streaming https://james-batchelor.com/index.php/2019/08/10/libreelec-pi-camera-mjpeg-streaming/ https://james-batchelor.com/index.php/2019/08/10/libreelec-pi-camera-mjpeg-streaming/#comments Sat, 10 Aug 2019 19:20:39 +0000 http://james-batchelor.com/?p=623 Continue reading "LibreElec – Pi Camera Mjpeg Streaming"]]> Following the setup of a Cent OS CCTV server, I’ve been using Raspberry Pi’s as video sources. But what if there was a Raspberry Pi in perfect situ for a CCTV camera, but was already in use as a media player?

A Linux system has always had the impression that it is versatile, so this should be an achievable task. A barrier would be how to get this done with the operating system installed, in this case it is LibreElec, an OS with the tagline “Just enough OS for Kodi”. Therefore, it would be more of a challenge than a usual Debian install.

The team at LibreElec saw this type of thing coming, and included the Docker service as a Kodi addon to allow the curious tinkerer to add more than Kodi to a Pi.

If you have the LibreElec based Pi in the opportune placement to add a camera, here is how to add Mjpeg streaming capabilities…

Add the Addons

Via Kodi on the screen, goto:

Addons --> Install from Repositories --> Services

Then install the two addons required:

RaspiTools
Docker

CLI Access

To setup Docker and its container (the Mjpeg streamer) requires Command Line Interface (CLI) access to the Pi, if not already enabled during setup enable it via Kodi by navigating to:

Settings --> LibreElec --> Services --> SSH

Mjpeg streamer

The M-Jpeg-streamer is a well-used Linux library, and have chosen the Open-Horizon version of a Docker image for this task.

Log in to the Pi via SSH and run the following:

docker pull openhorizon/mjpg-streamer-pi3:latest

Follow the prompts and be prepared to wait as the Docker image builds.

When complete, start the Docker container with:

docker run --restart=always -it -d --privileged -p 8081:8080
openhorizon/mjpg-streamer-pi3 ./mjpg_streamer -o "output_http.so -w
./www" -i "input_raspicam.so -x 1280 -y 720 -fps 10 -ex night"

This code explained:

docker run: start a Docker container
–restart=always: Restarts a container if the system is restarted
-it: allocate a pseudo-TTY for debugging and to stop it.
-d: Run in background, enables CLI to exit without stopping container.
–privileged: Give privileges that allows access to the camera.
-p 8081:8080: Translate streaming port from 8080 to 8081, as not to conflict with Kodi.
openhorizon/mjpg-streamer-pi3 ./mjpg_streamer: name of Docker image.
-o “output_http.so -w ./www”: internal reference.
-i “input_raspicam.so -x 1280 -y 720 -fps 10 -ex night”: Camera settings, set resolution, frames per second and Pi camera filters.

Run

The Docker container should now be running, check this by entering this in the CLI:

docker ps -a

To show running containers.

Now you can access the web GUI by visiting http://ip_address:8081 to test.

From here it can be added as a Network Camera to MotionEye.

Configuration

During setup its important to consider the hardware it running on. This was running on a Wi-Fi only Raspberry Pi 3A+, while it’s processing power was more than adequate for my initial setup of 1920×1080@20fps this saturated the network connection, leaving no bandwidth left to stream the media LibreElec was designed for. The reduction to 1280×720@10fps reduced the active bandwidth enough as not to interrupt the media players primary function.

]]>
https://james-batchelor.com/index.php/2019/08/10/libreelec-pi-camera-mjpeg-streaming/feed/ 1
Motioneye – Cent OS CCTV Server https://james-batchelor.com/index.php/2019/06/29/motioneye-cent-os-cctv-server/ https://james-batchelor.com/index.php/2019/06/29/motioneye-cent-os-cctv-server/#comments Sat, 29 Jun 2019 16:43:25 +0000 http://james-batchelor.com/?p=608 Continue reading "Motioneye – Cent OS CCTV Server"]]> If you’d ever searched for Raspberry Pi projects that involved a camera then the results would certainly include Motioneye OS, an easy to use self-contained operating system that is truly (write then) plug and play.

Looking for a CCTV project earlier this year I too was drawn in by this, and with my small abundance of RPi spares it was the cheapest choice, using a couple of RPi 3B+ for video, and a Zero W for time-lapse image capture. All processing was self-contained on each Pi with capture data passed over via SMB to a Windows file share.

This worked, but had a couple of problems that prevented it from being trustworthy. Firstly, it stops recording video after a few days of uptime, by creating empty files. And secondly the time-lapse camera seemed to reset every few minutes that created in white out image capture as the camera’s exposure setting recalibrated, ruining a time-lapse video.

Looking wider there was also the performance issue. In Motioneye OS’ default state of managing all features, the highest FPS seemed to max at 15 fps even on the Pi 3B+. Forums suggest this is due to the motion eye daemon handling all the image processing in software, putting a strain on the Pi’s modest CPU.

The idea and goal is to move the processing and IO responsibilities to my server, which would be far more capable than the then latest available RPi, and as I have chosen Cent OS to be my go-to Linux OS of choice, this is what I’ll be using.

A gateway to make this possible is an option in Motioneye OS, Fast Network Camera. This when set relinquishes the Pi of all processing duties and serves to just stream the camera capture as best as possible via MJPEG.

Here’s how to set up Motioneye on a Cent OS server to be a central data hub for a network of RPi Motioneye OS cameras.

OS Setup

In this VM setup two drives (VHD) are allocated, a 16GB drive for the OS, and a 2.6TB drive to be manually mounted and used solely for capture storage. Both VHD’s are contained on the same single 3TB 6Gbps SATA drive.

Run the Cent OS install wizard, choosing just the 16GB drive as the install destination. Its also helpful at this stage to configure a static IP for easier locating.

On successful setup and reboot, login via SSH and enter fdisk -l to discover the location of the large disk.

As the disk is larger than 2TB, we need to use the parted command to create a GPT disk format, in this case enter

parted /dev/sdb

Within the parted command, enter this to create the file system.

mklabel gpt 

Assign the size of the partition to use the entire disk (referenced from the earlier fdisk -l command).

mkpart primary 0GB 2858GB 

Exit parted.

quit

Format the new partition to make it usabl.

mkfs.ext4 /dev/sdb .

To use this new drive it first needs to be mounted to the sile system, and to do this it needs an entry point. Add a new folder as the entry point then mount the new drive to it.

mkdir /cctv
mount /dev/sdb /cctv

Check to see if its connected

df -h
Mounted on has /cctv listed with large disk size

This drive link will not work after a reboot, to do this the /etc/fstab file needs to be added to.

My file editor of choice is nano, so I enter this to start editing:

nano /etc/nano

Nano is not available with the minimal install of Cent OS, to install, enter yum install nano, follow the prompts and nano is installed.

Add the following:

/dev/sdb    /cctv     defaults              0 0

Then save and exit.

Ctrl +O 
Ctrl +X

Cent OS prerequisites

The Motioneye Install Guide had options for a number of Linux distributions but Cent OS is not listed. The closest match is Fedora which like Cent is based on the Red Hat architecture. To mimic a fedora install a couple of Repository libraries need to be added so the OS can find the install files, enter the next two commands and accept the prompts to add:

yum install epel-release

yum localinstall --nogpgcheck https://download1.rpmfusion.org/free/el/rpmfusion-free-release-7.noarch.rpm https://download1.rpmfusion.org/nonfree/el/rpmfusion-nonfree-release-7.noarch.rpm

 Motioneye Install

Before Motioneye can be installed it needs the supporting software to be installed beforehand/

yum install motion ffmpeg

Install the building language and associated software:

yum install python-pip python-devel gcc libcurl-devel
pango-devel

Now to download and install Motioneye:

pip install motioneye

Create the operating folders and copy the configuration files to their intended location:

mkdir -p /etc/motioneye

cp /usr/share/motioneye/extra/motioneye.conf.sample /etc/motioneye/motioneye.conf

Edit the motioneye.conf file to utilise the 2.6TB drive for storage:

nano /etc/motioneye/motioneye.conf

Edit the following line:

Ctrl+O to save and Ctrl+X to exit

Enable Motioneye to run as a service and to start at boot, and start it for the first time:

 cp /usr/share/motioneye/extra/motioneye.systemd-unit /etc/systemd/system/motioneye.service

systemctl daemon-reload 

systemctl enable motioneye

systemctl start motioneye

Samba Setup

To enable easier access to the footage, installing Samba allows Windows machines selective access to the Linux file system.

Install the samba service:

yum install samba samba-client samba-common

Make a backup of the config file in case anything goes wrong:

cp -pf /etc/samba/smb.conf /etc/samba/smb.conf.orig

Use nano to edit the config file:

nano /etc/samba/smb.conf

Remove the default setup and replace with the below. This creates a network share with access to the cctv folder, allowing Linux users in the “smbgrp” group access to it:

[global]
        workgroup = WORKGROUP
        security = user
        passdb backend = tdbsam
        printing = cups
        printcap name = cups
        load printers = yes
        cups options = raw
[cctv]
        comment = Camera Store
        path = /cctv
        valid users = @smbgrp
        force user = root
        browseable = Yes
        read only = No

Now lets add a new user and give it secure access to the cctv folder.

Create the access group:

groupadd smbgrp

Add the user and assign it to the group:

useradd cctv -G smbgrp

Set the password for the new user:

smbpasswd -a cctv

Next to amend the permissions of the cctv folder to grant access via Samba:

chmod -R 0777 /cctv
chcon -Rt samba_share_t /cctv

Time to set Samba to start on boot, and start for the first time:  

systemctl enable smb.service
systemctl enable nmb.service
systemctl start smb.service
systemctl start nmb.service

From a Windows machine, navigate to the server and check that the folder is accessible.

Firewall

Lets allow access to MotionEye and Samba from the outsode world (including local network) by editing the firewall:

MotionEye needs port 8765 opened to allow setup and administration:

firewall-cmd --add-port=8765/tcp –permanent

Samba is a recognised service, so this can be allowed by entering:

firewall-cmd --permanent --add-service=samba

Commit these changes by entering:

firewall-cmd --reload

Testing

From here and all things well, the MotionEye frontend should be available by visiting the server at http://server_ip:8765 .

Login with a username of admin and entering no password, which takes you to the familiar Motioneye GUI.

Camera Setup

Server now running the Pi Camera units need configuring / reconfiguring to be accessible to the server.

From Motioneye OS on the Raspberry Pi, login to access the settings options, navigate to General Settings and toggle on Advanced Settings. Click Apply.

Now navigate to Expert Settings and toggle on Fast Network Camera, click Apply.

Logging in again after the system reboot everything looks the same initially, but now capture controls are replaced with just Video and Streaming controls, and this does give more access to the finer details of the camera.

In the Streaming section, the stream port and authentication can be set. Authentication is not required, but is recommended if the server is on a shared network, as the footage will be available to view on any device on the network.

The streaming section also reveals the Stream URL, which is need to add the camera to the server…

Adding Cameras to Motioneye Server

Log back in to the Server’s Motioneye frontend, click the No Cameras dropdown box and choose Add Camera.

Choose Network Camera from the next dropdown box and add the streaming URL of the Pi camera to the address field, and credentials if set on the Pi. If correct the Camera field will auto populate:

Click OK to add camera. From here the familiar capture storage options are now available and configurable.

Additional cameras can be added in the same way.

Tweaking

Setup does require revisiting in the first few days to ascertain how many days footage can be retained before drive space runs out, if it does, MotionEye will stop recording. I had the benefit of setting this up during the height of summer, where the longer daylight hours create more capture data to store. As the nights draw the storage requirements per day will reduce.

Since the raw MJPEG video stream is being pulled by the server over the network as opposed to a data file, its worth considering what impact this will have on your network. To find out how much data is flowing iftop is a tool available on Cent OS to visualise this, to install run:

yum install iftop

Then run with:

iftop -n

Iftop shows data flow rates per IP address and gives 1min, 5min and 15min stats akin to uptime. From the screenshot above we can see the cameras are averaging between 30 and 50 Mbps, with a total of 160Mbps of the network utilised for the CCTV system.

This was based on 4 cameras, with larger setups it may be worth exploring a more robust network layout including additions like a all wired setup and configuring extra switches to reduce bottlenecks on the internal network.

Results and further research:

The functionality of this setup is great and far better than the previous where the Pi’s were doing all the processing work. Stability is much improved with the reliability of video capture and the increased frame rate, comfortably keeping 20 fps. And time-lapse images have a uniform brightness throughout which makes them much smother after processing.

Issues to work on following this install is that images fail to load in the MotionEye GUI, this can be overcome by someone used to the GUI but for newcomers the absence of login and slider icons can be confusing.

]]>
https://james-batchelor.com/index.php/2019/06/29/motioneye-cent-os-cctv-server/feed/ 2
OSMC on Pi 3A+ Problems – Switch to LibreELEC https://james-batchelor.com/index.php/2019/05/22/osmc-on-pi-3a-problems-switch-to-libreelec/ https://james-batchelor.com/index.php/2019/05/22/osmc-on-pi-3a-problems-switch-to-libreelec/#comments Wed, 22 May 2019 19:08:49 +0000 http://james-batchelor.com/?p=591 Continue reading "OSMC on Pi 3A+ Problems – Switch to LibreELEC"]]> For years, since it was XBMC on the original Pi I have been using OSMC as my Raspberry Pi media player. And following on from a whole home Pi redeployment for to include a CCTV system the latest installment was to install OSMC to two Raspberry Pi 3A+.

Raspberry Pi 3A+

Raspberry Pi 3A+

The Pi 3A+ plus is the cut down little brother to the latest 3B+ much akin to the original Pi B and A models. Both have the same quad-core ARM v8 processor, Broadcom Videocore-IV GPU and importantly the 2.4GHz and 5GHz 802.11b/g/n/ac Wi-Fi module for faster and stable WIFI out of the box. What’s cut down is the RAM, halved at 512MB, USB ports are reduced to one due to the removal of the onboard USB and gone is the ethernet port.

All the power without the ports make its perfect as a media player, all that’s needed to connect is the HDMI, with remote control provided via a CEC equipped TV.

The issue with OSMC

Here are the issues I experienced with OSMC on the Pi3A+, this is in no way a snarl at the developers who are doing an amazing job. I believe the 3 A+ is still a new and niche model so it’s understandable that development is slow for this product. I’m just hoping this will eventually be looked into and resolved, and putting it out there in case others have the same issue. Performance on the 3B+ is still exceptional.

From boot, selecting a 720p file (via Samba and h264 encoding) is fine, with subsequent auto-play files playing with no issues. However, with the next selection the issues start, selecting a file loads it but doesn’t play, having to go to the main menu and selecting Full-Screen to play the file. But then it buffers constantly. On the third play this workaround fails, and selecting Full-Screen results in a black screen.

In addition, even from boot any 1080p content fails to play with a black screen in its place, and playing h265 encoded files results in an immediate system crash.

480p content remains unaffected and plays perfectly.

LibreELEC to the rescue

Without resorting to buying a 3 B+, your media experience can still be made on 3 A+ by using LibreELEC, an alternative to OSMC that has the same goal of getting Kodi on the Raspberry Pi.

This distro gives the exact same experience, assuming you use the Kodi default Confluence skin and not the custom default on OSMC.

Installation is just as easy with installer package available on Windows, however its not as refined as the OSMC equivalent, first there is no option to pre-configure the wireless settings needed on the Pi A models, but is included in the setup wizard when booting up on your TV.

Second, and the point if this post, the Windows installer as problems overwriting SD cards with an existing file system, and gives a write error before the program hangs.

To overcome this, the SD card needs to be purged of its previous partitions:

In a File Explorer window, right click This PC and click Management

In Computer Management, click on Disk Manager

Identify the SD card in the lower graphic, the best way to achieve this is to match the drive capacity of the SD card, in this case it is the 8GB drive.

Right click every allocated partition, identified by the upper blue band, and click delete volume.

When complete, the drive will look like this.

From here, re-run the installer and choose the drive that matches the SD card capacity, the installer should now write to the card with no issues.

LibreELEC in action

If you are moving from Pi 2/3 B model to a 3A+ then it’s business as normal, 1080p h264 files, and up to 720p h265 files play without issues. Although those hoping for 1080p h265 encoded playback will be eternally disappointed given that this exceeds the Pi’s hardware capability.

One curious note for a UK resident, there is no regional setting for the United Kingdom, so have to resort in using an Isle of Man profile to get the correct time, and manually setting the region variables.

]]>
https://james-batchelor.com/index.php/2019/05/22/osmc-on-pi-3a-problems-switch-to-libreelec/feed/ 1
Raspberry Pi NoIR Camera https://james-batchelor.com/index.php/2016/12/02/raspberry-pi-noir-camera/ Fri, 02 Dec 2016 19:40:49 +0000 http://james-batchelor.com/?p=510 Continue reading "Raspberry Pi NoIR Camera"]]> A little treat when ordering the latest Raspberry Pi was to add a camera module to it, at a price of £7 for the Noir (Not French, just meaning No Infrared filter) it was easy to justify getting even if there was not a set purpose to it.

For the price the Pi Noir camera was generous on the specs, with a 2592 x 1944, 5 Megapixel sensor it seemed capable of capturing high detail images. However, the 5Mp tagline applies to still images only, with video capped at a still respectful 1920 x 1080p.

PiCam

The difference between the Noir and standard camera module is the lack of an IR filter on the lens, resulting in some washed out colours in daylight but still acceptable in a surveillance capacity, but has the ability to capture images in darkness with help of separate Infrared lighting.
Using the camera module on the Pi is pretty straight forward, connecting is done via a ribbon cable plugged into a dedicated port on the Pi board.

Then the module needs to be enabled in raspi-config and after a reboot it’s good to go. A neat addition is that Debian Wheezy comes with two small applications that enables a user to test the camera module straight away, raspistill for stills and raspivid for video are both command line applications usable via SSH and have enough switches to satisfy most and usual features of the camera.

So what to do with the camera? Not wanting to stretch resources on the Pi as it’s already a server, the lightweight apps included with Debian can be manipulated for recording or time-lapse purposes, to keep resources down further the scheduling will be done via Cron, which gives the ability run scripts at certain intervals.

Setting Up Cron

Cron, or Crontab, comes installed on Debian Squeeze for Pi so it is ready to go, but as the program is designed to run scripts, one must be created first.

Connect to the Pi via SSH and in the default (home) folder, create a folder called scripts by using sudo mkdir scripts. Then navigate into the folder with cd scripts.

Create a new file called capture.sh, using sudo nano capture.sh
Enter the following lines into the new file:

#!/bin/sh
DATE=$(date +”%Y-%m-%d_%H%M”)
raspistill -o /media/storage/Camera/$DATE.jpg

This script sets DATE variable to read the system time in the format Year-Month-Date HoursMinutes, then the raspistill program is called with an output to a folder with DATE as the filename.

Ctrl + O to save the file and Ctrl + X to exit.

Then make the file executable so it can be used by using sudo chmod +x capture.sh.

With the script done its time to move to the cron folder and add a file that calls the capture.sh script.

Navigate to /etc/cron.d
Create a new file with sudo nano capture

Enter the following line:

* * * * * pi /home/james/scripts/capture.sh

The 5 asterisks determine when the script is run, in its current form the script will be run at the shortest possible interval which is each minute, but this can be changed to suit the user.

From left to right, each asterisk represents a value of time; minute (0-59), hour (0-23), date (1-31), month (1-12), day (0-6 where 0 is Sunday and 6 is Saturday). So as an example 0 0 1 * * means the script would run at midnight on the 1st of every month. And to run every hour, use 0 * * * *.
Again, Ctrl + O and Ctrl + X to save and exit, as soon as the file is saved the job is active and based on the system time will start on the next trigger point.

With the capture file in the cron folder, it will run the script at the set times until the file is deleted, that’s why it is a good idea to keep a copy of that file in a different location, so it can be easily copied to the cron folder when needed to run and deleted when no longer needed.

]]>