In the previous part of this series about implementing a "dispatchable" for solar-efficient charging of (AA and AAA) batteries, I'd worked out that with a combination of the Google Assistant's Energy Storage trait (made visible through the openHAB Google Assistant Charger integration) and a small amount of local state, it looked like in theory, I could achieve my aim of a voice-commanded (and -queryable) system that would allow efficient charging for a precise amount of time. Let's now see if we can turn theory into practice.
First step is to copy all the configuration from the openHAB Charger device type into an items file:
$OPENHAB_CONF/items/dispatchable.items
Group chargerGroup
{ ga="Charger" [ isRechargeable=true, unit="SECONDS" ] }
Switch chargingItem (chargerGroup)
{ ga="chargerCharging" }
Switch pluggedInItem (chargerGroup)
{ ga="chargerPluggedIn" }
Number capacityRemainItem (chargerGroup)
{ ga="chargerCapacityRemaining" }
Number capacityFullItem (chargerGroup)
{ ga="chargerCapacityUntilFull" }
You'll note the only alterations I made was to change the unit to SECONDS as that's the best fit for our timing system,
and a couple of renames for clarity. Here's what they're all representing:
- chargingItem: are the batteries being charged at this instant?
- pluggedInItem: has a human requested that batteries be charged?
- capacityRemainSecondsItem: how many seconds the batteries have been charging for
- capacityFullSecondsItem: how many seconds of charging remain
I
could have used the "proper"
dead timer pattern of saying "any non-zero
capacityFullSecondsItem indicates intent" but given the
Charger type requires all four variables to be implemented anyway, I went for a crisper definition. It also helps with the rule-writing as we'll shortly see.
If we look at the openHAB UI at this point we'll just have a pile of NULL values for all these items:
Now it's time to write some
rules that will get sensible values into them all. There are four in total, and I'll explain each one in turn rather than dumping a wall of code.
Rule 1: Only charge if it's wanted, AND if we have power to spare
$OPENHAB_CONF/rules/dispatchable.rules
rule "Make charging flag true if wanted and in power surplus"
when
Item currentPowerUsage changed
then
if (pluggedInItem.state == ON) {
if (currentPowerUsage.state > 0|W) {
logInfo("dispatchable", "[CPU] Non-zero power usage");
chargingItem.postUpdate(OFF);
} else {
logInfo("dispatchable", "[CPU] Zero power usage");
chargingItem.postUpdate(ON);
}
}
end
This one looks pretty similar to
the old naïve rule we had way back in version 1.0.0, and it pretty-much is. We've just wrapped it with the "intent" check (
pluggedInItem) to make sure we actually need to do something, and offloaded the hardware control elsewhere. Which brings us to...
Rule 2: Make the hardware track the state of chargingItem
$OPENHAB_CONF/rules/dispatchable.rules
rule "Charge control toggled - drive hardware"
when
Item chargingItem changed to ON or
Item chargingItem changed to OFF
then
logInfo("dispatchable", "[HW] Charger: " + chargingItem.state);
SP2_Power.sendCommand(chargingItem.state.toString());
end
The simplest rule of all, it's a little redundant but it does prevent hardware control "commands" getting mixed up with software state "updates".
Rule 3: Allow charging to be requested and cancelled
$OPENHAB_CONF/rules/dispatchable.rules
rule "Charge intent toggled (pluggedIn)"
when
Item pluggedInItem changed
then
if (pluggedInItem.state == ON) {
// Human has requested charging
logInfo("dispatchable", "[PIN] charge desired for: ");
logInfo("dispatchable", capacityFullSecondsItem.state + "s");
capacityRemainSecondsItem.postUpdate(0);
// If possible, begin charging immediately:
if (currentPowerUsage.state > 0|W) {
logInfo("dispatchable", "[PIN] Awaiting power-neutrality");
} else {
logInfo("dispatchable", "[PIN] Beginning charging NOW");
chargingItem.postUpdate(ON);
}
} else {
logInfo("dispatchable", "[PIN] Cancelling charging");
// Clear out all state
capacityFullSecondsItem.postUpdate(0);
capacityRemainSecondsItem.postUpdate(0);
chargingItem.postUpdate(OFF);
}
end
This rule is where things start to get a little trickier, but it's pretty straightforward. The key thing is setting or resetting the three other variables to reflect the user's
intent.
If
charging is desired we assume that the "how long for" variable has already been set correctly and zero the "how long have you been charging for" counter. Then, if the house is
already power-neutral, we start. Otherwise we wait for conditions to be right (
Rule 1).
If
charging has been cancelled we can just clear out all our state. The hardware will turn off almost-immediately because of
Rule 2.
Rule 4: Keep timers up-to-date
$OPENHAB_CONF/rules/dispatchable.rules
rule "Update charging timers"
when
Time cron "0 0/1 * * * ?"
then
if (pluggedInItem.state == ON) {
// Charging has been requested
if (chargingItem.state == ON) {
// We're currently charging
var secLeft = capacityFullSecondsItem.state as Number - 60;
capacityFullSecondsItem.postUpdate(secLeft);
logInfo("dispatchable", "[CRON] " + secLeft + "s left");
var inc = capacityRemainSecondsItem.state as Number + 60;
capacityRemainSecondsItem.postUpdate(inc);
// Check for end-charging condition:
if (secLeft <= 0) {
// Same as if user hit cancel:
logInfo("dispatchable", "[CRON] Reached target.");
pluggedInItem.postUpdate(OFF);
}
}
}
end
This last rule runs once a minute, but only does anything if the user asked for charging AND we're doing so. If that's the case, we decrement the time left" by 60 seconds, and conversely increase the "how long have they been charging for" by 60 seconds. Yes, I know this might not be strictly accurate but it's good enough for my needs.
The innermost
if statement checks for the happy-path termination condition - we've hit zero time left! - and toggles the flag which will once-again lower the intent flag, thus causing
Rule 3 to fire, which in turn will cause
Rule 2 to fire, and turn off the hardware.
UI Setup
This has ended up being quite the journey, and we haven't even got the Google integration going yet! The last thing for this installment is to knock up a quick control/status UI so that we can see that it actually works correctly. Here's what I've got in my openHAB "Overview" page:
The slider is wired to
capacityFullSecondsItem, with a range of
0 - 21600 (6 hours) in 60-second increments, and 6 "steps" marked on the slider corresponding to integer numbers of hours for convenience.
The toggle is wired to
pluggedInItem.
When I want to charge some batteries, I pull the slider to my desired charge time and flip the switch. Here's a typical example of what I get in the logs if I do this during a sunny day:
[PIN] charge desired for: 420 seconds
[PIN] Beginning charging immediately
[HW] Charger: ON
[CRON] 360s left
...
[CRON] 120s left
[CRON] 60s left
[CRON] 0s left
[CRON] Reached desired charge. Stopping
[PIN] Cancelling charging
[HW] Charger: OFF