IoT
Storm Switch

Storm Switch

As we live in the country, the power supply is not reliable. Any issue downstream results in power loss. This is frustrating as we have solar cells and actually sell into the grid. During winter even minor storms usually result in short periods of no power and summer is worse as the heat stresses the grid, brownouts and again storms will result in loss of power.

No desire to go off grid but to want resiliency. The solution is to add batteries to existing system. This technology has come a long way and is cheaper then the initial 1.5kW system we installed (2006) so we added another 6Kw of panels and an intelligent inverter, battery charger and 9kW (3 X 3kW in 6RU) of Lithium Ferro Phosphate batteries. We are also surrounded by wind farms so despite what agreements we may have with energy provider, the electrons being supplied are carbon neutral. Works fine despite what Scotty and Angus tell us.

I want to control a device in the shed remotely.

Called a ‘Storm’ switch the intent is to manually activate the charger when a storm is approaching as likely mains power will go off and we will be running on batteries so want to ensure they are full. The normal switching between batteries and mains is controlled by the inverter and charger and we will be able to control sending power into the grid when and buying when optimal. The API exposed by Fronius will allow me to monitor what is coming and going where and have no desire to interfere. This switch will allow us to place the batteries on charge to ensure they are full when conditions indicate high risk of loosing external mains.

Simply a relay that will place the battery charger into charge mode controlled by a Pi. The battery charger supports a Storm Switch which is a digital input, NO contact or open collector transistor which is capable of switching 12VDC and 5mA.

There is also a wealth of APIs available to source information as to current and forecast conditions including storms;

  • The Fronius Inverter has a API (V1, V0) and returns JSON as to status.
  • Python code to access inverter JSON – > https://github.com/nielstron/pyfronius
  • via ftp calls to BOM will pickup weather alerts -> ftp.bom.gov.au
  • public RSS feed from Vic emergency -> https://data.emergency.vic.gov.au/Show?pageId=getIncidentRSS
  • paid API -> https://api.willyweather.com.au/v2/APIKEY/locations/11297/weather.json?forecasts=weather&days=5

and these are already integrated into Dolos who hosts a web page for device and current conditions.

Here will exploit WebIOPI to create a API that can trigger a switch allowing the batteries to be placed into charge when a storm is pending. Once enabled it counts down the predefined time. It can be cancelled and monitored remotely. Device shutdown and startup ensure the relay is off. There is basic checking to ensure it has not been false triggered and has a OLED display showing time, temp and status of GPIO or countdown when active.

The language of preference for devices on the Raspberry Pi appears to be Python. Have hacked Python for other projects but still haven’t had enough motivation to learn it from the ground up, so a lot of the code here is based on rapid googling with trial and error. Perhaps I’ll find motivation to study the basics some day but it is living up to the reputation of being easy to use.

Setup the Pi and install WebIOPi as per these instructions. Might also be time to develop Kubernetes and Ansible skills as standard build and deployment, perfect candidate for automated containers. Check the service is running OK before we change it;

pi@raspberrypi:~/WebIOPi-0.7.1 $ systemctl status webiopi
 ● webiopi.service - WebIOPi
    Loaded: loaded (/etc/systemd/system/webiopi.service; enabled; vendor preset: enabled)
    Active: active (running) since Fri 2021-08-06 10:41:10 BST; 10s ago
  Main PID: 1814 (python3)
     Tasks: 3 (limit: 2059)
    CGroup: /system.slice/webiopi.service
            └─1814 /usr/bin/python3 -m webiopi -l /var/log/webiopi -c /etc/webiopi/config
 Aug 06 10:41:10 raspberrypi systemd[1]: Started WebIOPi.

and these commands will help

webiopi -h
tail -f /var/log/webiopi
sudo systemctl restart webiopi


I removed login/password protection by clearing /etc/webiopi/passwd, then restart the webiopi server.

Going to add a ‘script’ supporting relay enable disable and status as well as ‘routes’ to make the endpoints nicer. Here is the config file ;

[GPIO]
# Initialize following GPIOs with given function and optional value
# This is used during WebIOPi start process
#23 = OUT 0
#24 = OUT 0
4 = OUT 0
#25 = OUT 1
#------------------------------------------------------------------------#

[~GPIO]
# Reset following GPIOs with given function and optional value
# This is used at the end of WebIOPi stop process
#23 = IN
#24 = IN
4 = OUT
#------------------------------------------------------------------------#

[SCRIPTS]
# Load custom scripts syntax :
# name = sourcefile
#   each sourcefile may have setup, loop and destroy functions and macros
chargerScript = /usr/share/webiopi/htdocs/app/charger/chargerFunctions.py 
#------------------------------------------------------------------------#

[HTTP]
# HTTP Server configuration
enabled = true
port = 8000

# File containing sha256(base64("user:password"))
# Use webiopi-passwd command to generate it
#passwd-file = /etc/webiopi/passwd
#------------------------------------------------------------------------#

[COAP]
# CoAP Server configuration
enabled = false
port = 5683
# Enable CoAP multicast
multicast = true
#------------------------------------------------------------------------#
[ROUTES]
# Custom REST API route syntax :
# source = destination
#   source      : URL to route
#   destination : Resulting URL
# Adding routes allows to simplify access with Human comprehensive URLs
/CHARGER = /GPIO/4/value

Reading the manual is a good idea about now so here is the RESTAPI page. This is the current working script;

import webiopi
 import os
 import datetime
 import logging
 def is_file_older_than (file, delta):
     cutoff = datetime.datetime.utcnow() - delta
     mtime = datetime.datetime.utcfromtimestamp(os.path.getmtime(file))
     if mtime < cutoff:
         return True
     return False
 GPIO = webiopi.GPIO
 CHKFile = "/tmp/checkFile"
 CHARGER = 4    # GPIO pin using BCM numbering
 TIMEOUT = 60    # number of seconds to turn off 3.5hrs = 12600
 setup function is automatically called at WebIOPi startup
 def setup():
     logging.basicConfig(filename='Webiopi.log', filemode='w', format='%(name)s - %(levelname)s - %(message)s')
     # set the GPIO used by the charger to output and off
     GPIO.setFunction(CHARGER, GPIO.OUT)
     GPIO.digitalWrite(CHARGER, GPIO.LOW)
     if os.path.exists(CHKFile):
         os.remove(CHKFile)
     logging.info('Startup Ok')
 loop function is repeatedly called by WebIOPi
 def loop():
     if os.path.exists(CHKFile):
         if(is_file_older_than(CHKFile, datetime.timedelta(seconds=TIMEOUT))):
             GPIO.digitalWrite(CHARGER, GPIO.LOW)
             os.remove(CHKFile)
             logging.info('time expired.Stopped.')
     elif GPIO.digitalRead(CHARGER) == GPIO.HIGH:
         logging.warning('in loop File Not exists and GPIO HIGH.Reset.')
         GPIO.digitalWrite(CHARGER, GPIO.LOW)            
     # gives CPU some time before looping again
     # logging.warning('Looping.')
     webiopi.sleep(1)
 destroy function is called at WebIOPi shutdown
 def destroy():
     GPIO.setFunction(CHARGER, GPIO.OUT)
     GPIO.digitalWrite(CHARGER, GPIO.LOW)
     if os.path.exists(CHKFile):
         os.remove(CHKFile)
     logging.info('Destroy Ok')
 A macro without args which enables the relay and returns nothing
 @webiopi.macro
 def enableCharger():
     logging.info('enabled via enableCharger.')
     GPIO.digitalWrite(CHARGER, GPIO.HIGH) 
     with open(CHKFile, 'a'):     # Create file if does not exist
         os.utime(CHKFile, None)  # Set access/modified times to now
 @webiopi.macro
 def disableCharger():
     logging.info('disabled via disableCharger.')
     GPIO.digitalWrite(CHARGER, GPIO.LOW) 
     if os.path.exists(CHKFile):
         os.remove(CHKFile)

The macros defined at the bottom of the file are called to change the GPIO state. Using GPIO 4 to control the relay. Tested using 23 and 24 and settled on 4 as allowed me to use a solid header and pickup power, I2C bus and ground and not have to juggle plugs. The I2C bus has been used to support a OLED display and BME280 sensor.

The device uses cron to send current conditions via JSON back to Dolos ->

post_BME280.py

Script called in /etc/rc.local to provide a display ->

OLED Display script

Thanks to Matt Hawkins ->  https://www.raspberrypi-spy.co.uk/ for the code to use BME280.

Postman scripts to test api ->

Postman collection for Shed

‘Dolos’ is another PI4 which hots a LAMP stack to process data from the other devices around the place. The scripts are in PHP and below is a snippet showing how I interface with the device ‘Shed’. You can see a simple call to the macros defined in the script for webiopi;

Function toggleChargeStatus(){

   // set post fields
   $postvars = [
    	'username' => 'user',
    	'password' => 'pass',
    ];

    if (getChargeStatus()){
	$url = 'http://shed:8000/macros/disableCharger';
    }else{
    	$url = 'http://shed:8000/macros/enableCharger';
    }
    
    $ch = curl_init();
    curl_setopt($ch,CURLOPT_URL,$url);
    curl_setopt($ch,CURLOPT_POST, 1);                //0 for a get request
    curl_setopt($ch,CURLOPT_POSTFIELDS,$postvars);
    curl_setopt($ch,CURLOPT_RETURNTRANSFER, true);
    curl_setopt($ch,CURLOPT_CONNECTTIMEOUT ,3);
    curl_setopt($ch,CURLOPT_TIMEOUT, 20);
 
    // execute!
    $response = curl_exec($ch);

    // close the connection, release resources used
    curl_close($ch);

    // do anything you want with your response
    // var_dump($response);
} 

Function getChargeStatus(){

	$data_url="http://shed:8000/CHARGER";
	$chargeStatus = file_get_contents($data_url);
	if ($chargeStatus){
//	if(FALSE !== ($return = @file_get_contents('$data_url'))) {
	    return TRUE;
    }else{
    	return FALSE;
    }
}

As it stands, the device is housed in a case that will be housed in a IPX65 rated enclosure and mounted in the shed close to the controlling devices. Passing tests so ready to be deployed.

To Do:

Add ability to act a WiFi access point – https://thepi.io/how-to-use-your-raspberry-pi-as-a-wireless-access-point/