EV car power management

#18

@jorgeli
Hi,

The code goes into custom code (just copy paste):


Like this:

Then you have to add a job that executes the code ([package/custom code name].[function], the function is poll):

The way my job is is set to every 6 minutes:

That together with setting the sleep times of the dongle to 5 minutes (300 seconds) works fine (I think the timings can be fine tuned a bit more). I am still working on that.
Times for sleep are set under settings:
41%20PM
Then go to Dongle-Adbvanced-Power. Mine looks at the moment like this:
.

To be noted the
BOT_TOKEN = ‘8888888888AAAAAAAAAA88888888’
BOT_CHATID = ‘888888888’
Are a telegram bot and a chat id to send info to my phone and not real, they need to be replaced with real values if used. Telegram (https://web.telegram.org).
Check out the following to create a bot: https://www.sohamkamani.com/blog/2016/09/21/making-a-telegram-bot/
The #bot_sendtext("… will send the text in the quotes if the # is removed, e.g. bot_sendtext(“sleep disabled”).
If left with the # then it will do nothing and you can skip the telegram part. I have used it for testing and then just turned it off.
However i use it when charging to get messages when passing 10% levels 40, 50…

Hope this helps :slight_smile:

#19

Thank you so much for your time to explain I am so grateful :smiley:
I will test today .
Thanks again :+1::+1::+1::grinning:

#20

Hmm It is not working for me so I think I have miss something?

#21

Did you set the power settings for the dongle?
You can execute the command from the prompt to test, run the same that you have in the job.
Try that, it should show you what is in the returns and if configured sends the telegram message.
In the command prompt it will either show “sleep disabled” or “sleep enabled” if it works (and th edonglr is on line)

    if charging_ccs == 1 or charging_normal == 1 or soc != -1:
        disable_sleep()
        #bot_sendtext("sleep disabled")
        #bot_sendtext(power.sleep_timer)
        return {"msg": "sleep disabled"}
    else:
        enable_sleep()
        #bot_sendtext("sleep enabled")
        #bot_sendtext(power.sleep_timer)
        return {"msg": "sleep enabled"}

I think the names of jobs and packaged (custom code) can not have upper case or start with a number, stick to the names that I put there to start.
And obviously if it is sleeping then nothing works.

#22

I have tested your code and settings on my IONIQ electric MY18 @AndersO, but i only got messages about AutoPi is going to sleep


No charging messages att all

#23

@el93mon
Hej Mats,
I’m afk today til Sunday, with only my phone.
The script only enables our disables sleep. I have another script that send messages about charging. I can post that when I get back.
You should get messages with sleep enables as well though from this one. If the # is removed.

#24

Not work for me at all.
I am novice and understand that i do something wrong but i don´t now what?
I test custom code direct in the prompt and get answer : ‘’‘import’’ is not available.’

#25

Hej Anders!

I thought something was missing, nice if you can share the code later. :slight_smile:

/Mats

#26

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

#27

@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.

#28

Thank you Anders, i will try it out!

#29

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:

#30

Now it works! Many thanks @AndersO

#31

@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?.

#32

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.

#33

@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.

#34

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

#35

@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

#36

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

#37

@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