Electric Car - Need help creating rules based on logs

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")