This is a Ruby script to parse Octopus ToU tariff prices and control a LuxPower ACS inverter according to rules you specify.
The particular use-case of this is to charge your home batteries when prices are cheap, and use that power at peak times.
There's also support for toggling Raspberry Pi GPIO pins as an added bonus.
You'll need Ruby - at least 2.3 should be fine, which can be found in all good Linux distributions.
This apt-get command also installs the Ruby development headers and a compiler so Ruby can build extensions as part of installing dependencies:
sudo apt-get install ruby ruby-dev ruby-bundler git build-essential
Clone this repository to your machine:
git clone https://github.com/celsworth/octolux.git
cd octolux
Now install the gems. You may occasionally need to re-run this as I update the repository and bring in new dependencies or update existing ones. This will install gems to ./vendor/bundle
, and so should not need root:
bundle update
If you are running on a Raspberry Pi and want to use the GPIO support, you can install it with: (you only need to do this once, subsequent bundle installs
will remember you want the pi package)
bundle update --with pi
Create a config.ini
using the doc/config.ini.example
as a template:
cp doc/config.ini.example config.ini
This script needs to know:
- where to find your Lux inverter, host and port.
- the serial numbers of your inverter and datalogger (the plug-in WiFi unit), which are normally printed on the sides.
- how many batteries you have, which determines the maximum charge rate (used in agile_cheap_slots rules)
- which Octopus tariff you're on, AGILE-18-02-21 is my current one for Octopus Agile.
- if you're using MQTT, where to find your MQTT server.
- optionally on the Pi, a list of GPIOs you'll be controlling.
Copy rules.rb
from the example as a starting point:
cp doc/rules.example.5p.rb rules.rb
The idea behind keeping the rules separate is you can edit it and be unaffected by any changes to the main script in the git repository (hopefully).
Moved to a separate document, see INVERTER_SETUP.md.
There are two components.
server.rb
is a long-running process that we use for background work. In particular, it monitors the inverter for status packets (these include things like battery state-of-charge).
It starts a HTTP server which octolux.rb
can then query to get realtime inverter data. It can also connect to MQTT and publish inverter information there. See MQ.md for more information about this.
It's split like this because there's no way to ask the inverter for the current battery SOC. You just have to wait (up to two minutes) for it to tell you. The server will return the latest SOC on-demand via HTTP.
The simplest thing to do is just start it in screen:
screen
./server.rb
A systemd unit file will be added at some point so it starts automatically on boot.
octolux.rb
is intended to be from cron, and enables or disables AC charge depending on the logic written in rules.rb
(you'll need to copy/edit an example from docs/).
There's also a wrapper script, octolux.sh
, which will divert output to a logfile (octolux.log
), and also re-runs octolux.rb
if it fails the first time (usually due to transient failures like the inverter not responding, which can occasionally happen). You'll want something like this in cron:
0,30 * * * * /home/pi/octolux/octolux.sh
To complement the wrapper script, there's a log rotation script which you can use like this:
59 23 * * * /home/pi/octolux/rotate.sh
This will move the current octolux.log
into logs/octolux.YYYYMMDD.log
at 23:59 each night.
In your rules.rb
, you have access to a few objects to do some heavy lifting.
octopus
contains Octopus tariff price data. The most interesting method here is price
:
octopus.price
- the current tariff price, in penceoctopus.prices
- a Hash of tariff prices, starting with the current price. Keys are the start time of the price, values are the prices in pence.
lc
is a LuxController, which can do the following:
lc.charge(true)
- enable AC charginglc.charge(false)
- disable AC charginglc.discharge(true)
- enable forced dischargelc.discharge(false)
- disable forced dischargelc.charge_pct
- get AC charge power rate, 0-100%lc.charge_pct = 50
- set AC charge power rate to 50%lc.discharge_pct
- get discharge power rate, 0-100%lc.discharge_pct = 50
- set discharge power rate to 50%
Forced discharge may be useful if you're paid for export and you have a surplus of stored power when the export rate is high.
Setting the power rates is probably a bit of a niche requirement. Note that discharge rate is all discharging, not just forced discharge. This can be used to cap the power being produced by the inverter. Setting it to 0 will disable discharging, even if not charging.
gpio
is a GPIO controller (only available on the Raspberry Pi). These two methods take a string which corresponds to your configuration. See the example config under the [gpios]
section.
gpio.on('zappi')
- turn on the GPIO pin identified as zappi in your config.gpio.off('zappi')
- turn off the GPIO pin identified as zappi in your config.gpio.set('zappi', true)
- alternative way of turning on a GPIO. Passfalse
to turn it off.