The goal of this project is to start charging the car when my solar panels generate so much energy, that I can charge the car with my own electric energy. This way, I do not have to pay the electricity company. Consuming your own generated energy is much more financially efficient than selling energy when the sun shines and buying it back when there are clouds or when it is dark. I do not have a battery system (since I automate consumption like described here).
My hybrid car is charging at my home via a standard 16A wall socket. This outlet is wired in the house via an extra 30A circuit breaker / relay that is controlled by Tasmota from Athom. I have set the charger to charge with 6A (1400 Watt).
The electricity entering my house is measured by the electricity supplier measurement system, which has a “P1” digital data output, which I capture with a Youless module. It has a simple API over HTTP to provide all the measurements every second to the domotica system.
The domotica system I use in the house is Domoticz, and it has registered both the energy measurements device and the outlet relay.
Then I use the Lua language in Domoticz to control the relay according the measurements: If there is an excess of 1400W of energy, then I switch the car charger on, and if I start buying energy from the electricity company again, I switch the charger off.
All this has to happen with some delays and margins, since the consumption and production of energy can vary wildly.
So, I drew an UML statechart to clarify the Lua code before I wrote it:
In the code, I really used the statechart to the letter:
- The transitions are implemented as separate functions.
- The guards are implemented as separate functions, each returning a Boolean.
- On every “tick” of the clock, we receive new electricity measurements, and determine if there is any transition to be taken in the function determineTransition.
- This function determineTransition checks in which of the 3 states we are, and checks the appropriate guards - just like in the diagram. Its return parameter is the transition function!
- If a transition is found, execute the transition:
local t = determineTransition();
if (t) then t() end
This is the full code:
-- This script uses a Tasmota 30A Breaker to switch the car charger
-- on and off, depending on the availability of excess power
-- from the solar panels.
-- So, if there is no sun, we do not have enough electricity production, and we
-- do not charge the car.
-- If the sun is shining, and we do not consume all the produced electricity
-- ourselves, then, instead of delivering the excess to the grid,
-- we charge the car.
-- The car charger is powered via a 30A circuit breaker flashed with Tasmota,
-- registered in Domoticz as device 422, named "Car Charger" in Domoticz.
-- Then there is a way to demand to charge,
-- i.e. you can switch the automaton on or off:
-- domoticz.devices(479) is a dummy device with a car icon
-- that can be toggled manually.
-- If this is off, then the algorithm here
-- switches the car charger off unconditionally.
-- The car charger used here consumes (minimum) 1400W, which is
-- 6A at 235V, the minimum setting on the charger cable.
-- A global data setting named CarChargerConsumedPower contains this value.
-- The car charger can be set to another power level,
-- and if this is done, we need to adjust
-- the CarChargerConsumedPower value, too.
-- Maybe this can be turned into an adjustable value later.
return {
on = {
devices = {
'Youless Power' -- 393
}
},
logging = {
level = domoticz.LOG_ERROR,
marker = 'SolarCarCharger',
},
data = {
-- The ccenablecounter counts down until
-- the excess power is available for long enough:
ccenablecounter = { initial = 10 }; -- domoticz.helpers.CarChargerEnableDelay
CarChargerStatus = {initial = 1}; -- the status of the car charger: OFF=1, EXCESS_DETECTED=2, CHARGING=3
},
execute = function(domoticz, device)
local actualPowerUsage -- The house is consuming this amount of power (measured in Watt), negative for production
actualPowerUsage = (device.usage - device.usageDelivered);
domoticz.log('House Power usage ' .. actualPowerUsage .. ' W.', domoticz.LOG_INFO);
local carChargeDemand -- the device for the user to demand heat
carChargeDemand = domoticz.devices(479);
domoticz.log('Solar car charger demand switch is ' .. carChargeDemand.state, domoticz.LOG_INFO);
--domoticz.log('Solar car charger demand switch is ' .. (carChargeDemand.active and "T" or "F"), domoticz.LOG_INFO);
local carChargerSwitchDevice -- the device that switches the car charger on and off
carChargerSwitchDevice = domoticz.devices(422);
-- the status of the car charger: OFF=1, EXCESS_DETECTED=2, CHARGING=3
-- ==================================
-- Transitions
-- ==================================
local function transition_charging_off()
-- the car charger shall be off
carChargerSwitchDevice.switchOff().checkFirst();
domoticz.data.CarChargerStatus = 1 --OFF;
end
local function transition_excessdetected_off()
-- the car charger shall be off
carChargerSwitchDevice.switchOff().checkFirst();
domoticz.data.CarChargerStatus = 1 --OFF;
end
local function transition_off_excessdetected()
domoticz.data.ccenablecounter = domoticz.helpers.CarChargerEnableDelay;
domoticz.data.CarChargerStatus = 2 --EXCESS_DETECTED;
-- domoticz.log('Transition: 1 executed.', domoticz.LOG_INFO);
end
local function transition_excessdetected_excessdetected()
domoticz.data.ccenablecounter = domoticz.data.ccenablecounter - 1;
-- domoticz.data.CarChargerStatus = 2 --EXCESS_DETECTED;
end
local function transition_excessdetected_charging()
-- the car charger shall be on
carChargerSwitchDevice.setState('On');
domoticz.data.CarChargerStatus = 3 --CHARGING;
end
-- ==================================
-- Guards
-- ==================================
local function guard_demand()
return carChargeDemand.active;
end
local function guard_nodemand()
return carChargeDemand.inActive;
end
local function guard_excessdetected()
-- the house is producing more power than the car charger would consume
return ( (domoticz.helpers.CarChargerConsumedPower / 1) < (- actualPowerUsage) );
end
local function guard_nopoweravailable()
-- the sun is gone
-- there is no available excess power
return ( 300 < (actualPowerUsage) );
end
local function guard_delaycountnotreached()
return (domoticz.data.ccenablecounter > 0);
end
local function guard_delaycountreached()
return (domoticz.data.ccenablecounter < 1);
end
-- ==================================
-- Determine Transition
-- ==================================
local function determineTransition()
if (domoticz.data.CarChargerStatus == 1 )--OFF
then
-- domoticz.log('Current status 1: ' .. domoticz.data.CarChargerStatus, domoticz.LOG_INFO);
if (guard_demand() and guard_excessdetected())
then
domoticz.log('Transition: 1 ', domoticz.LOG_INFO);
return transition_off_excessdetected
-- else
-- domoticz.log('NO transition: 1, demand = '
-- .. (guard_demand()and "T" or "F")
-- .. ", excessdetected = "
-- .. (guard_excessdetected()and "T" or "F"),
-- domoticz.LOG_INFO);
end
elseif ((domoticz.data.CarChargerStatus) == 2) --EXCESS_DETECTED
then
-- domoticz.log('Current status 2: ' .. domoticz.data.CarChargerStatus, domoticz.LOG_INFO);
if (guard_nodemand() or not guard_excessdetected())
then
domoticz.log('Transition: 2 ', domoticz.LOG_INFO);
-- the car charger shall be off
return transition_excessdetected_off
end
if (guard_delaycountnotreached() and guard_excessdetected() and guard_demand())
then
domoticz.log('Transition: 3 ', domoticz.LOG_INFO);
return transition_excessdetected_excessdetected
end
if (guard_delaycountreached() and guard_excessdetected() and guard_demand())
then
domoticz.log('Transition: 4 ', domoticz.LOG_INFO);
-- the car charger shall be on
return transition_excessdetected_charging
end
elseif (domoticz.data.CarChargerStatus == 3) --CHARGING
then
-- domoticz.log('Current status 3: ' .. domoticz.data.CarChargerStatus, domoticz.LOG_INFO);
if (guard_nodemand() or guard_nopoweravailable())
then
domoticz.log('Transition: 5 ', domoticz.LOG_INFO);
-- the car charger shall be off
return transition_charging_off
end
else
domoticz.log('Case: 99 : serious trouble', domoticz.LOG_ERROR);
end
end
local t = determineTransition();
if (t) then t() end
domoticz.log('Current status: ' .. domoticz.data.CarChargerStatus, domoticz.LOG_INFO);
if ((domoticz.data.CarChargerStatus) == 2) then
domoticz.log('Current enable counter: ' .. domoticz.data.ccenablecounter, domoticz.LOG_INFO);
end
domoticz.log('The Car Charger is now ' .. carChargerSwitchDevice.state .. '.',
domoticz.LOG_INFO);
end
}
To make this work in Domoticz, the global_data script contains the following:
return {
helpers = {
CarChargerConsumedPower = 1400, -- (minimum) consumption of the car charger
CarChargerEnableDelay = 20, -- the delay before the consumer is enabled, measured in 5 second periods
}
}