EV car power management

Hej Mats.

Here is the code for the charging.
I just added the GOM range, but I cannot get the air temp, so I am using battery temp, I think it works ok, but not when the battery is warm from driving, the range is to long. I will try to get the air temp a bit more but if i cannot I will change that to a fixed value when the temp is above something like 22 degrees (then keep 22 when warmer), to be decided.

import logging
import requests
import pickle
import os
from enum import Enum

log = logging.getLogger(__name__)

# Telegram tokens - see https://www.mariansauter.de/2018/01/send-telegram-notifications-to-your-mobile-from-python-opensesame/
#
BOT_TOKEN = '888888888888888888xxxxxxxxxxxxxxxxxxx888888888888888'
BOT_CHATID = '888888888'



def checkChargeStatus():

    # load previous status
    #
    persistance = load()

    # check if we are driving or charging
    #
    charging = get_charging()
    if charging == 0 or charging == -1:
        if persistance['charging'] == True:
            bot_sendtext("Charging stopped. Last known State of charge "+format(persistance['SOC'],'.1f')+"% BMS")
            #bot_sendtext("Charging stopped. Last known State of charge "+format(persistance['SOC'],'.1f')+"% BMS - "+format(soc_display,'.1f')+ "% Display")
            persistance['charging'] = False
            save(persistance)
        return {"msg": "Not charging"} # Does nothing ?


    chargingPower = get_charging_power()
    soc = get_soc()
    soc_display = get_soc_display()
    temp = get_temp()

    # alert if just started to charge
    #
    if persistance['charging'] == False:
        bot_sendtext("Charging started at a rate of "+format(2*chargingPower,'.2f')+"kW. State of charge now "+format(soc,'.1f')+"% BMS - "+format(soc_display,'.1f')+ "% Display GOM now "+format(0.01*soc_display*2800/(16.08813 + (temp*(-0.13873))),'.0f')+"km")


    # 40% alert
    #
    if soc >= 40 and persistance['SOC'] < 40:
        bot_sendtext("Charging now at a rate of "+format(2*chargingPower,'.2f')+"kW. State of charge now "+format(soc,'.1f')+"% BMS - "+format(soc_display,'.1f')+ "% Display GOM now "+format(0.01*soc_display*2800/(16.08813 + (temp*(-0.13873))),'.0f')+"km")

    # 50% alert
    #
    if soc >= 50 and persistance['SOC'] < 50:
        bot_sendtext("Charging now at a rate of "+format(2*chargingPower,'.2f')+"kW. State of charge now "+format(soc,'.1f')+"% BMS - "+format(soc_display,'.1f')+ "% Display GOM now "+format(0.01*soc_display*2800/(16.08813 + (temp*(-0.13873))),'.0f')+"km")

    # 60% alert
    #
    if soc >= 60 and persistance['SOC'] < 60:
        bot_sendtext("Charging now at a rate of "+format(2*chargingPower,'.2f')+"kW. State of charge now "+format(soc,'.1f')+"% BMS - "+format(soc_display,'.1f')+ "% Display GOM now "+format(0.01*soc_display*2800/(16.08813 + (temp*(-0.13873))),'.0f')+"km")

    # 70% alert
    #
    if soc >= 70 and persistance['SOC'] < 70:
        bot_sendtext("Charging now at a rate of "+format(2*chargingPower,'.2f')+"kW. State of charge now "+format(soc,'.1f')+"% BMS - "+format(soc_display,'.1f')+ "% Display GOM now "+format(0.01*soc_display*2800/(16.08813 + (temp*(-0.13873))),'.0f')+"km")

    # 80% alert
    #
    if soc >= 80 and persistance['SOC'] < 80:
        bot_sendtext("Charging now at a rate of "+format(2*chargingPower,'.2f')+"kW. State of charge now "+format(soc,'.1f')+"% BMS - "+format(soc_display,'.1f')+ "% Display GOM now "+format(0.01*soc_display*2800/(16.08813 + (temp*(-0.13873))),'.0f')+"km")

   # 90% alert
    #
    if soc >= 90 and persistance['SOC'] < 90:
        bot_sendtext("Charging now at a rate of "+format(2*chargingPower,'.2f')+"kW. State of charge now "+format(soc,'.1f')+"% BMS - "+format(soc_display,'.1f')+ "% Display GOM now "+format(0.01*soc_display*2800/(16.08813 + (temp*(-0.13873))),'.0f')+"km")

    # 100% alert ... not sure if this can really happen
    #
    if soc >= 94 and persistance['SOC'] < 94:
        bot_sendtext("Charging now at a rate of "+format(2*chargingPower,'.2f')+"kW. State of charge now "+format(soc,'.1f')+"% BMS - "+format(soc_display,'.1f')+ "% Display GOM now "+format(0.01*soc_display*2800/(16.08813 + (temp*(-0.13873))),'.0f')+"km")

    # store status for next time
    #
    persistance['charging'] = True
    persistance['SOC'] = soc
    save(persistance)

    #return {"msg": "Charging at "+format(chargingPower,'.2f')+"kW, SOC now "+format(soc,'.1f')+"% "+format(temp,'.1f')+" temp"}
    #return {"msg": "Charging at "+format(chargingPower,'.2f')+"kW, SOC now "+format(soc,'.1f')+"% "+format(0.01*soc*2800/(16.08813 + (temp*(-0.13873))),'.1f')+" km"}
    return {"msg": "Charging at "+format(chargingPower,'.2f')+"kW, SOC (BMS) now "+format(soc,'.1f')+"% GOM now "+format(0.01*soc_display*2800/(16.08813 + (temp*(-0.13873))),'.0f')+"km"}

# send message to telegram
#
def bot_sendtext(message):
    send_text = 'https://api.telegram.org/bot' + BOT_TOKEN + '/sendMessage?chat_id=' + BOT_CHATID + '&parse_mode=Markdown&text=' + message
    requests.get(send_text)

# load persistance
#
def load():
    try:
        persistance = pickle.load( open( 'charge_status.p', 'rb' ) )
    except:
        persistance = { 'charging': False, 'SOC': 0 }

    return persistance

# save persistance
#
def save(persistance):
    pickle.dump( persistance, open( "charge_status.p", "wb" ) )

# delete persistance
#
def delete():
    os.remove("charge_status.p")



# OBD Queries -----------------------------------------------------------------------------

# get Battery Temprature
#
def get_temp():
    args = ['temp']
    kwargs = {
        'mode': '21',
        'pid': '01',
        'header': '7E4',
        'baudrate': 500000,
        'formula': 'twos_comp(bytes_to_int(message.data[17:18]),8)',
        'protocol': '6',
        'verify': False,
        'force': True,
        }
    return __salt__['obd.query'](*args, **kwargs)['value']

def get_charging_power():
        args = ['charging_power']
        kwargs = {
        'mode': '21',
        'pid': '01',
        'header': '7E4',
        'baudrate': 500000,
        #'formula': '(twos_comp(bytes_to_int(message.data[13:14])*256+bytes_to_int(message.data[14:15]),16)/10.0)*((bytes_to_int(message.data[15:16])+bytes_to_int(message.data[16:17]))/10.0)/1000.0',
        'formula': '(twos_comp(bytes_to_int(message.data[12:13])*256+bytes_to_int(message.data[13:14]),16)/10.0)*((bytes_to_int(message.data[14:15])+bytes_to_int(message.data[15:16]))/10.0)/100.0',
        'protocol': '6',
        'verify': False,
        'force': True,
        }
        return __salt__['obd.query'](*args, **kwargs)['value']*-1.0

# get BMS state of charge
#
def get_soc():
    args = ['soc']
    kwargs = {
        'mode': '21',
        'pid': '01',
        'header': '7E4',
        'baudrate': 500000,
        'formula': 'bytes_to_int(message.data[6:7])',
        'protocol': '6',
        'verify': False,
        'force': True,
        }
    return __salt__['obd.query'](*args, **kwargs)['value']/2.0

# get display state of charge
#
def get_soc_display():
    try:
        args = ['soc']
        kwargs = {
            'mode': '21',
            'pid': '05',
            'header': '7E4',
            'baudrate': 500000,
            'formula': 'bytes_to_int(message.data[33:34])',
            'protocol': '6',
            'verify': False,
            'force': True,
            }
        return __salt__['obd.query'](*args, **kwargs)['value']/2.0
    except:
        return -1

## LOCATION
#
def get_location():
    args = []
    kwargs = {}
    return __salt__['ec2x.gnss_nmea_gga'](*args, **kwargs)

# Retuns exception
def get_carState():
  #  try:
        args = ['driving']
        kwargs = {
        'mode': '21',
        'pid': '01',
        'header': '7E4',
        'baudrate': 500000,
        'formula': 'bytes_to_int(message.data[53:54])',  # Ignition
        'protocol': '6',
        'verify': False,
        'force': True,
        }
        return (int(__salt__['obd.query'](*args, **kwargs)['value'])&4)/4

def get_charging():
    try:
        args = ['driving']
        kwargs = {
        'mode': '21',
        'pid': '01',
        'header': '7E4',
        'baudrate': 500000,
        'formula': 'bytes_to_int(message.data[11:12])',
        'protocol': '6',
        'verify': False,
        'force': True,
        }
        return (int(__salt__['obd.query'](*args, **kwargs)['value'])&128)/128
    except:
        return -1

# Weird response, always seem to be true
def get_charging_chademo():
#  try:
        args = ['CCS Plug']
        kwargs = {
        'mode': '21',
        'pid': '01',
        'header': '7E4',
        'baudrate': 500000,
        'formula': 'bytes_to_int(message.data[12:13])',
        'protocol': '6',
        'verify': False,
        'force': True,
        }
        return (int(__salt__['obd.query'](*args, **kwargs)['value'])&64)/64

# Weird response, always seem to be true
def get_charging_normal():
#  try:
        args = ['J1772 Plug']
        kwargs = {
        'mode': '21',
        'pid': '01',
        'header': '7E4',
        'baudrate': 500000,
        'formula': 'bytes_to_int(message.data[12:13])',
        'protocol': '6',
        'verify': False,
        'force': True,
        }
        return (int(__salt__['obd.query'](*args, **kwargs)['value'])&32)/32


23%20PM

@jorgeli

If the code is installed you should be able to run it from the command prompt, not the complete code (I think that is what you tried to do) just the command to execute it (same as in the job), see screenshot:

At the moment i am charging and the return is “sleep disabled”

Hope this helps.

Thank you Anders, i will try it out!

Thanks for you patience and help for me :slight_smile: -)
Yes it works when write in prompt. It says sleep disabled
But the dongle are going to sleep anyway from power settings time.
So the script not working for me :frowning:

Now it works! Many thanks @AndersO

@jorgeli
Hi,
The script makes it stay awake when charging and when drivieng.
In all other cases sleep should be enabled and the dongle should go to sleep after 5 minutes (with the sleep timer set to 300).
Does your dongle sleep when the car is on, when charging or both?.

The dongle is awake Only the 5 min when i drive or charge and then go to sleepmode.
So just follow the dongle power management.

@jorgeli, weird.
How often is the job running that executes the script?
I’ve set it to 6 minutes + run on start, the script sets the sleep to 5 minutes (300 sec) and the setting for the dongle is set to 5 minutes as well.
Does the job have the command properly written?
My guess would be that there is something wrong with the job.

If all is ok I think you need to ask support in that case, mine never goes to sleep while driving or charging. When turned off it does.

Hi Anders
Thanks again for feedback :slight_smile:
Yes something i must have done wrong.
My config is like this:

@jorgeli
Hi, i think you are missing a space, for the Cron schedule I have a space after the 6 minutes: ‘*/6 * * * *’
Try that and see what happens

Thank you very much I thing it work now :slight_smile:

@jorgeli
Good to hear.
FYI there are spaces between all the stars, not sure you are aware of the and not sure it support it without stars.
the star are (* * * * *):

  • minute per hour
  • hour of the day
  • day of the month
  • month of the year
  • day of the week

I’ve followed all your tutorial but I only received: car unlocked.
Do you know why? Thanks

@ricard_ferre_jornet
Hi, I am not sure i understand, car unlocked would be no return or message from what I posted.
The only things i have posted is to keep the autopi awake and to report via telegram on charging, both only hor Ioniq Electric.

New code for staying awake for the Ioniq BEV that works better and is smaller, it has to run at shorter intervals than the sleep timer.
I have it running every 2 minutes and the sleep timer is 300 sec (5 min).
The old code had to run at start and then at a longer interval than the sleep timer.
The new one can/should run several times within the sleep timer period.
What it does it just resets the sleep timer everytime it runs if charging or driving.
I think that the disable_sleep() can also be removed, but not 100% sure:

import os
import requests

BOT_TOKEN = '8888888888888888xxxxxxxxxxxx8888888888'
BOT_CHATID = '88888888888'

def poll():
    charging_ccs = get_charging_ccs()
    charging_normal = get_charging_normal()
    soc = get_soc()
    #if charging_ccs == 1 or (charging_normal != 1 and soc != -1):
    if charging_ccs == 1 or charging_normal == 1 or soc != -1:
        disable_sleep()
        enable_sleep()
        #bot_sendtext("sleep timer reset")
        return {"msg": "sleep timer reset"}
    #else:
        #bot_sendtext("sleep timer running")
        #return {"msg": "sleep timer running"}

# enable autopi sleep
def enable_sleep():
    args = ['sleep']
    kwargs = {
        'enable': True,
        'period': 300,
        'reason': 'charge status',
    }
    __salt__['power.sleep_timer'](**kwargs)

# disable autopi sleep
def disable_sleep():
    args = ['sleep']
    kwargs = {
        'enable': False,
    }
    __salt__['power.sleep_timer'](**kwargs)

def get_charging():
    try:
        args = ['charging']
        kwargs = {
        'mode': '21',
        'pid': '01',
        'header': '7E4',
        'baudrate': 500000,
        'formula': 'bytes_to_int(message.data[11:12])',
        'protocol': '6',
        'verify': False,
        'force': True,
        }
        return (int(__salt__['obd.query'](*args, **kwargs)['value'])&128)/128
    except:
        return -1

# get display state of charge
#
def get_soc():
    try:
        args = ['soc']
        kwargs = {
            'mode': '21',
            'pid': '05',
            'header': '7E4',
            'baudrate': 500000,
            'formula': 'bytes_to_int(message.data[33:34])',
            'protocol': '6',
            'verify': False,
            'force': True,
            }
        return __salt__['obd.query'](*args, **kwargs)['value']/2.0
    except:
        return -1

# Get Chrging CCS
def get_charging_ccs():
    try:
        args = ['CCS Plug']
        kwargs = {
        'mode': '21',
        'pid': '01',
        'header': '7E4',
        'baudrate': 500000,
        'formula': 'bytes_to_int(message.data[11:12])',
        'protocol': '6',
        'verify': False,
        'force': True,
        }
        return (int(__salt__['obd.query'](*args, **kwargs)['value'])&64)/64
    except:
        return -1

# Get charging 230V
def get_charging_normal():
    try:
        args = ['J1772 Plug']
        kwargs = {
        'mode': '21',
        'pid': '01',
        'header': '7E4',
        'baudrate': 500000,
        'formula': 'bytes_to_int(message.data[11:12])',
        'protocol': '6',
        'verify': False,
        'force': True,
        }
        return (int(__salt__['obd.query'](*args, **kwargs)['value'])&32)/32
    except:
        return -1

def bot_sendtext(message):
    send_text = 'https://api.telegram.org/bot' + BOT_TOKEN + '/sendMessage?chat_id=' + BOT_CHATID + '&parse_mode=Markdown&text=' + message
    requests.get(send_text)
2 Likes

I’ve copyed your new code and I’ve put my bot token and bot chadid but I can’t save the code when I put the create button in my autopi.

@ricard_ferre_jornet
I have no idea, I have never had any issues with create, save etc. Buttons they open windows and save etc.
Terry with another browser? I have inky used chrome.
You can always mail support, they are good at answering.

Is it the cloud that send out the custom code every time to reset sleep timer ?
or is the code on Autopi after update?
For me it works sometime and sometime it going to sleep anyway.
So i guess that i have could have bad conection when it going to sleep ?
I try now to increase the sleep timer and see if it workd better.

@jorgeli
I think it is all in the dongle, the code is saved to the dongle and executed there with local jobs.
With the new code the job needs to run at a much shorter interval than the sleep timer, the sleep timer is set in the code to 300 (5 min), what the code does is just resetting the sleep timer as defined in the code, see below.
If resetting the sleep timer without having the period defined it sets it to default which is I believe is 30 minutes
I have the job running every 2 minutes to make sure that it resets it at least twice in the sleep timer time span.

def enable_sleep():
    args = ['sleep']
    kwargs = {
        'enable': True,
        'period': 300,

The setting in the configuration of the dongle for the sleep timer is overwritten when the script is running.

Well is it local on the dongle ti don´t now what is wrong :roll_eyes:
I have the job for 2 min to ,but it’s like it doesn’t update every time and then it is power down?

Same when i test charge with only type 2 it is powering down ( have not test with CCS yet)