Electric Car - Need help creating rules based on logs

Hi,
I have an IONIQ electric car that does not have any SIM inside, good and bad. So i managed to manipulate the charging cable to send voltage, current, kwh, and power via MQTT + if the charger is “online”. Looks like this:
image

I have two rules i try to achieve but i have no clue how to do it smart and not end up with multiple variables that maybe i do not need as M4 may have some rule-support inside that i do not know.

I would love to detect charging start…so when either LWT changes from “Connection lost” to “connected” OR Power changes from 0W to something bigger than 1000W. If this event happens, I need to store the current EnergyImport (as this never goes to 0 and increases with every charging procedure). When it stops it should build the difference of the energy at start and send me a notification (email,…whatevery).
The Energy-Difference should be shown in an additional item i guess…like EnergyImpLast.

The second rule should trigger if the charging is still ongoing (not yet stopped due to Power going back from >1000W to 0W…and “Connection Lost” which means somebody unplugged the car…

Sorry if it looks that i’m too lazy to do it myself, but to me it looks complex and i have no real clue how i could do it…Thanks for any helping hand to do it most efficient with less overhead in terms of lines/variables needed.

Norbert

Your plans look reasonable.

Another way to detect charging start / end would be to look for current changing from / to zero.

Does, a bit.

Make complex things by taking small steps. Making this efforts avoids the lazy look.

Try to make rules to detect charging start / stop. Then afterwards, can work on the energy calc part.

Hi Norbert,
This is not a simple rule you want to write.

For the trigger look at: https://www.openhab.org/docs/configuration/rules-dsl.html#rule-triggers

For the energy over time you could look at: Rule for Total energy consumed in kWh for day or Best practice for handling energy consumption in a single cummulative value?

Search the forum there are more…

Have a go at writing your rule, if you get stuck, post your code and we’ll help. We will not write your code for you. You ask for a helping hand.

sure, current translates into the same…

hm…going simple steps…i would have to check with every update if now POWER is >1000W. I would have a variable named ChargeState that goes True when this happens…if boolean exists?

And it goes False if it was true and power suddenly is Zero again.

For the delivered energy part i’m more unsure, do i really need a StartEnergy variable that is stored when the True is set…and somehow subtract from the EnergyImp-State when finished…I guess so/or it needs to be like that if not a more simple way is possible. I do not want to have too many variables that nobody needs except for these calculations…Is there any of these “since…” calculations possible that are done automatically?

Kind Regards

As already suggested, look at what rule triggers are available.
when X > NN is not.
You can simulate with
when X changed
then
if (X > NN)

All rather depends what data you get from your charger.
I’d be investigating if this kind of thing was practical
when chargeCurrent changed from 0

In usual openHAB terms, you might have a Switch type Item called “charging” and turn it on and off.

Sure, why not.

They cost you nothing. If you store important data in Items you can exploit other openHAB features like for example persistence, so that they can survive a system reboot.

Just doing the first detection part right now, most of my rules are done with “received update”. you suggest “changed”. e.g. Item EVchargePower received update vs. changed

From what i understand “received update” would trigger even if the value stays the same, and changed will only be triggered if the value itself really changed…so if the new value is the same as the old, the rule would not be triggered.

Is that true?
because i would guess that received update is more safe in that it would not take into consideration that the value itself needs to be different…

OnTOP: one more question:
if the energy_previous has to be stored…is this declared within the rule itself or on a global level…or does local/global not exist for rules…so wherever you define them, they are available everywhere?

Please have a short critical view on my first part, does it look OK/efficient/stable…thanks!

Switch Car_Charging "CarCharging"
Number EVchargeMeter "EVcharge Meter"

rule "ChargeStart Detection"
when
    Item EVchargePower received update
then
    if ((EVchargePower > 500) && Car_Charging.state == OFF &&) 
	   { Car_Charging.sendCommand(ON)
	     EVchargeMeter.postUpdate(EVchargeWhImp.state)
		}
end

This is what I came up with for gathering charging data for one of our EVs. This uses the new rule engine, Jython and the helper libraries. The outlet that the car plugs into (just 120V) is also used to charge batteries for the lawn mower, weed eater, chainsaw, etc., so there’s logic built in to detect what is charging.

We have Alexa enabled devices and the OH skill. To start a charge, we just say something like, ‘Echo, set Full Charge Mileage to 5.5’. If the car was unplugged but not completely charged, then ‘Echo, set Partial Charge Mileage to 55.5’. I have notifications setup, so after a full charge mileage is set, you will get the stats of the last drive and also when charging (car or battery) is complete.

Hope this helps!

ITEMS
I haven’t gotten everything migrated to UoM yet :blush:

Number:Length  Volt_Mileage                "Full Charge Mileage [%.1f mi]"                <text>              (gBattery_Charging)    {alexa="RangeController.rangeValue" [supportedRange="0:500:1"]}
Number:Length  Volt_Mileage_Partial        "Partial Charge Mileage [%.1f mi]"             <text>              (gBattery_Charging)    {alexa="RangeController.rangeValue" [supportedRange="0:500:1"]}
Number:Length  Volt_Mileage_Total          "Volt Mileage (total) [%.1f mi]"               <text>              (gBattery_Charging)
Number:Energy  Volt_Energy                 "Volt Energy (last charge) [%.0f kWh]"          <energy>            (gBattery_Charging)
Number:Energy  Volt_Energy_Total           "Volt Energy (total) [%.0f kWh]"                <energy>            (gBattery_Charging)
Number  Volt_Efficiency             "Volt Efficiency (last charge) [%.0f Wh/mi]"  <energy>            (gBattery_Charging)
Number  Volt_Efficiency_Average     "Volt Efficiency (average) [%.0f Wh/mi]"      <energy>            (gBattery_Charging)
Number  Volt_Cost                   "Volt Cost (last charge) [$%.2f]"               <text>              (gBattery_Charging)
Number  Volt_Cost_Total             "Volt Cost (total) [$%.2f]"                     <text>              (gBattery_Charging)
Number  Volt_Cost_PerMile           "Volt Cost/Mile (last charge) [$%.3f/mi]"     <text>              (gBattery_Charging)
Number  Volt_Cost_PerMile_Average   "Volt Cost/Mile (average) [$%.3f/mi]"         <text>              (gBattery_Charging)

Switch	Outlet9	                        "Car Charger [%s]"	                                        <poweroutlet-us>	(gGarageAttached,gOutlet,gBattery_Charging)                                             {channel="zwave:device:55555:node225:switch_binary", alexa="PowerController.powerState" [category="SMARTPLUG"]}
Number:Power	Outlet9_Power	                "Car Charger: Power [%.0f W]"	                            <energy>	        (gGarageAttached,gOutlet,gBattery_Charging)		                                        {channel="zwave:device:55555:node225:meter_watts"}
Number:Energy	Outlet9_Energy	                "Car Charger: Energy [%.0f kWh]"	                        <energy>	        (gGarageAttached,gOutlet,gBattery_Charging)		                                        {channel="zwave:device:55555:node225:meter_kwh"}

SCRIPT

from threading import Timer

from core.rules import rule
from core.triggers import when
from core.actions import PersistenceExtensions

from personal.utils import notification

from org.joda.time import DateTime

ENERGY_COST_PER_KWH = 0.1238
CHARGER_TIMER_ATTACHED = None# Outlet9

@rule("Power: Battery charging")
@when("Item Outlet9 changed to OFF")
def battery_charging(event):
    battery_charging.log.debug("Battery charging: {}: start".format(event.itemState))
    date_of_last_full_charge = DateTime(PersistenceExtensions.previousState(ir.getItem("Volt_Mileage"), True).timestamp.time)
    date_of_last_partial_charge = DateTime(PersistenceExtensions.previousState(ir.getItem("Volt_Mileage_Partial")).timestamp.time)
    last_charge_was_full = date_of_last_full_charge.isAfter(date_of_last_partial_charge)

    charge_start = date_of_last_full_charge if last_charge_was_full else date_of_last_partial_charge
    battery_charging.log.debug("Battery charging: charge_start=[{}]".format(charge_start))
    if PersistenceExtensions.maximumSince(ir.getItem("Outlet9_Power"), charge_start).state > DecimalType(900):
        message = "Car charging has completed"
    else:
        message = "Battery charging has completed"
    notification("Battery charging", message)

@rule("Power: Battery charging monitor")
@when("Item Outlet9_Power changed")
def battery_charging_monitor(event):
    #battery_charging_monitor.log.debug("Battery charging monitor: {}: start".format(event.itemState))
    global CHARGER_TIMER_ATTACHED
    if items["Outlet9"] == ON and event.itemState <= QuantityType("8 W") and event.oldItemState <= QuantityType("8 W"):
        if CHARGER_TIMER_ATTACHED is None or str(CHARGER_TIMER_ATTACHED.getState()) == "TERMINATED":
            CHARGER_TIMER_ATTACHED = Timer(600, lambda: events.sendCommand("Outlet9", "OFF"))
            CHARGER_TIMER_ATTACHED.start()
            battery_charging_monitor.log.debug("Battery charging monitor: Started battery charging turn off timer: Outlet9_Power=[{}], oldItemState=[{}]".format(event.itemState, event.oldItemState))
    elif CHARGER_TIMER_ATTACHED is not None and str(CHARGER_TIMER_ATTACHED.getState()) == "TIMED_WAITING":
        CHARGER_TIMER_ATTACHED.stop()
        battery_charging_monitor.log.debug("Battery charging monitor: Cancelled battery charging turn off timer: Outlet9_Power=[{}], oldItemState=[{}]".format(event.itemState, event.oldItemState))

@rule("Power: Volt mileage received update")
@when("Item Volt_Mileage received update")
def volt_mileage(event):
    volt_mileage.log.debug("Volt mileage change: {}: start".format(event.itemState))
    # these calculations are all for the last charge
    date_of_last_full_charge = DateTime(PersistenceExtensions.previousState(ir.getItem("Volt_Mileage"), True).timestamp.time)
    date_of_last_partial_charge = DateTime(PersistenceExtensions.previousState(ir.getItem("Volt_Mileage_Partial")).timestamp.time)
    last_charge_was_full = date_of_last_full_charge.isAfter(date_of_last_partial_charge)

    volt_mileage.log.debug("Volt mileage change: date_of_last_full_charge=[{}], date_of_last_partial_charge=[{}], last_charge_was_full=[{}]".format(date_of_last_full_charge, date_of_last_partial_charge, last_charge_was_full))
    previous_mileage = PersistenceExtensions.previousState(ir.getItem("Volt_Mileage" if last_charge_was_full else "Volt_Mileage_Partial"), True).state.floatValue()
    volt_mileage.log.debug("Volt mileage change: previous_mileage=[{}]".format(previous_mileage))
    total_mileage = items["Volt_Mileage_Total"].floatValue() + previous_mileage
    events.sendCommand("Volt_Mileage_Total", str(total_mileage))
    volt_mileage.log.debug("Volt mileage change: Volt_Mileage_Total=[{}]".format(total_mileage))

    energy_start = PersistenceExtensions.historicState(ir.getItem("Outlet9_Energy"), date_of_last_full_charge).state.floatValue()
    volt_mileage.log.debug("Volt mileage change: energy_start=[{}]".format(energy_start))
    energy_now = items["Outlet9_Energy"].floatValue()
    volt_mileage.log.debug("Volt mileage change: energy_now=[{}]".format(energy_now))
    energy_drive = energy_now - energy_start
    volt_mileage.log.debug("Volt mileage change: energy_drive=[{}]".format(energy_drive))
    events.sendCommand("Volt_Energy", str(energy_drive))
    events.sendCommand("Volt_Energy_Total", str(items["Volt_Energy_Total"].floatValue() + energy_drive))

    global ENERGY_COST_PER_KWH
    energy_drive_cost = ENERGY_COST_PER_KWH * energy_drive
    volt_mileage.log.debug("Volt mileage change: energy_drive_cost=[{:.3f}]".format(energy_drive_cost))
    events.sendCommand("Volt_Cost", str(energy_drive_cost))
    events.sendCommand("Volt_Cost_Total", str(items["Volt_Energy_Total"].floatValue() + energy_drive_cost))

    cost_per_mile = energy_drive_cost / previous_mileage
    volt_mileage.log.debug("Volt mileage change: cost_per_mile=[{:.3f}]".format(cost_per_mile))
    events.sendCommand("Volt_Cost_PerMile", str(cost_per_mile))

    cost_per_mile_ave = (items["Volt_Cost_PerMile_Average"].floatValue() + cost_per_mile) / 2
    volt_mileage.log.debug("Volt mileage change: cost_per_mile_ave=[{:.1f}]".format(cost_per_mile_ave))
    events.sendCommand("Volt_Cost_PerMile_Average", str(cost_per_mile_ave))

    efficiency = 1000 * energy_drive / previous_mileage
    volt_mileage.log.debug("Volt mileage change: efficiency=[{:.0f}]".format(efficiency))
    events.sendCommand("Volt_Efficiency", str(efficiency))

    efficiency_ave = (items["Volt_Efficiency_Average"].floatValue() + efficiency) / 2
    volt_mileage.log.debug("Volt mileage change: efficiency_ave=[{:.0f}]".format(efficiency_ave))
    events.sendCommand("Volt_Efficiency_Average", str(efficiency_ave))

    message = "Previous Volt charge report: {:.1f} cents per mile, {:.0f} Watt hours per mile".format(100 * cost_per_mile, efficiency)
    volt_mileage.log.debug("Volt mileage change: efficiency_ave=[{}]".format(efficiency_ave))
    notification("Volt mileage change", message)
    events.sendCommand("Outlet9", "ON")

Yes. This is in the docs.
I understood you wanted to capture the kWh reading at the start of a charge cycle, and then capture again at the completion.
If that is what you intend, choose triggers with care.