Ioniq BEV 28 kWh; staying awake, charging messages, CO2 usage

Custom code that will work with the Ioniq BEV.
Anyone that has ideas on what to add in addition? :slight_smile:

The below keeps the Autopi awake and sends telegrams with charging, CO2 usage, milage etc.

Jobs for the below scripts:

Note: Telegram IDs have to be changed to real ones

my_sleep_reset:
Resets the sleep timer to five minutes every one or two minutes (job setting), if the car is running or charging, until it stops charging or running then the sleep timer runs its course and autopi goes to sleep
The initial sleep timer set in the config should be set to 5 minutes as well

import os
import requests

BOT_TOKEN = '8888888888888888888999999999999999999999999888888'
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)

my_co2save:
Initially calcualted the CO2 savings/usage but is now only saving the milage and used for CO2 saving calculation when charging

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

BOT_TOKEN = '88888888888888888888888888888888888888888'
BOT_CHATID = '888888888'

def poll():
    # Store CO2 Savings changed to km, saved and used in formula for CO2 savings in charging messages
    #

    co2persistance = loadco2()
    co2savings = get_co2savings()	
    #milage = get_milage()
    #co2usage = get_co2usage()	
	

    if co2savings != -1:
        co2persistance['charging'] = True
        co2persistance['CO2'] = co2savings
        saveco2(co2persistance)
        #bot_sendtext("sleep disabled")
        #bot_sendtext(power.sleep_timer)
        #return {"msg": "Driving and CO2 savings are: "}
        return {"msg": "Driving and milage is: "+format(co2persistance['CO2'],'.0f')+" km"}
    else:
        #return {"msg": "not driving no CO2 saving, however "}
        return {"msg": "NOT driving no CO2 data saving, milage "+format(co2persistance['CO2'],'.0f')+" km"}
    # store CO2 for next time
    #
    #co2persistance['charging'] = True
    #co2persistance['CO2'] = co2savings
    #saveco2(co2persistance)

    return {"msg": "milage for CO2 calc: "+format(co2persistance['CO2'],'.0f')+" km"}

# Environment stuff -----------------------------------------------------------------------------

# load CO2 persistance
#
def loadco2():
    try:
        co2persistance = pickle.load( open( 'co2fixed.p', 'rb' ) )
    except:
        co2persistance = { 'charging': False, 'CO2': 0 }

    return co2persistance

# save co2persistance
#
def saveco2(co2persistance):
	if co2persistance != -1:
		pickle.dump( co2persistance, open( "co2fixed.p", "wb" ) )

# delete co2persistance
#
def deleteco2():
    os.remove("co2fixed.p")


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

# Odometer
# CO2
def get_co2savings():
    try:
        args = ['co2savings']
        kwargs = {
        'mode': '22',
        'pid': 'B002',
        'header': '7C6',
        'baudrate': 500000,
        'formula': 'bytes_to_int(message.data[10:12])',
        'protocol': '6',
        'verify': False,
        'force': True,
        }
        #return __salt__['obd.query'](*args, **kwargs)['value']*0.1534/1000
        return __salt__['obd.query'](*args, **kwargs)['value']
    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)

my_status;
Sends Telegram messages charging; at start, during and when finished.
A Swedish mile is 10 km, hence the 10 km in the 1.44 etc.
The CO2 savings is based on Swedish electric power (factor 0.047 CO2 cost per kWh) and the Ioniq power consumption in average (1.44)
ICE car CO2 emmision (factor 2.67 CO2 well to wheel per liter gas)
It probably needs some testing to get the right decimal places if changing the factor, milage size and ICE gas usage

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 = '8888888888888888888888888888888888U'
BOT_CHATID = '8888888888'



def checkChargeStatus():

    # load previous status
    #
    persistance = load()
    
    co2persistance = loadco2()

    # 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). Tons of CO2 saved compared to ICE car: "+format(co2persistance['CO2']*0.1801/1000,'.3f')+" tons."+" CO2 usage (Sweden): "+format(co2persistance['CO2']*0.006768/1000,'.3f')+" tons Total Milage: "+format(co2persistance['CO2'],'.0f')+" km. Cost ICE car gas at 16 SEK/l and 0.7 l/10km: "+format(co2persistance['CO2']*16*0.7/10,'.0f')+" SEK")
            persistance['charging'] = False
            save(persistance)
        return {"msg": "Not charging. Charging stopped. Last known State of charge "+format(persistance['SOC'],'.1f')+"% (BMS). Tons of CO2 saved compared to ICE car: "+format(co2persistance['CO2']*0.1801/1000,'.3f')+" tons"}
    chargingPower = get_charging_power()
    soc = get_soc()
    soc_display = get_soc_display()
    temp = get_temp()
    temp_h = get_temp_h()    
    air_temp = get_air_temp()


    # if air temp is more or equal to 25 then set to 25
    #
    #if air_temp >= 25:
        #temp = 25

    # alert if just started to charge
    #
    if persistance['charging'] == False:
        bot_sendtext("Charging started at a rate of "+format(2*chargingPower,'.2f')+" kW. SOC BMS = "+format(soc,'.1f')+"%, SOC Display = "+format(soc_display,'.1f')+ "%, GOM "+format(0.01*soc_display*2800/(16.08813 + (air_temp*(-0.13873)))/0.89,'.0f')+"km, batt. temp. = "+format(temp,'.0f')+ " - "+format(temp_h,'.0f')+  " Degrees, air temp. = "+format(air_temp,'.0f')+" Degrees")
        # )/0.92 testing bigger range for GOM formula shows less at 15-20 Centigrade

    # Alerts at 40, 50, 60, 70, 80, 90 and 94% SOC BMS
    #
    if (soc >= 40 and persistance['SOC'] < 40) or (soc >= 50 and persistance['SOC'] < 50) or (soc >= 60 and persistance['SOC'] < 60) or (soc >= 70 and persistance['SOC'] < 70) or (soc >= 80 and persistance['SOC'] < 80) or (soc >= 90 and persistance['SOC'] < 90) or (soc >= 94 and persistance['SOC'] < 94):
        bot_sendtext("Charging at a rate of "+format(2*chargingPower,'.2f')+" kW. SOC BMS = "+format(soc,'.1f')+"%, SOC Display = "+format(soc_display,'.1f')+ "%, GOM = "+format(0.01*soc_display*2800/(16.08813 + (air_temp*(-0.13873)))/0.89,'.0f')+" km, batt. temp. = "+format(temp,'.0f')+ " - "+format(temp_h,'.0f')+  " Degrees, air temp. = "+format(air_temp,'.0f')+" Degrees")
        # )/0.89 testing bigger range

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

    return {"msg": "Charging at a rate of "+format(2*chargingPower,'.2f') + " kW. SOC BMS = "+format(soc,'.1f')+"%, SOC Display = "+format(soc_display,'.1f')+ "%, GOM = "+format(0.01*soc_display*2800/(16.08813 + (air_temp*(-0.13873)))/0.89,'.0f')+" km, batt. temp. = "+format(temp,'.0f')+ " - "+format(temp_h,'.0f')+  " Degrees, air temp. = "+format(air_temp,'.0f')+" Degrees. Saved CO2 compared to ICE car: "+format(co2persistance['CO2']*0.1801/1000,'.3f')+" tons."+" CO2 usage (Sweden): "+format(co2persistance['CO2']*0.006768/1000,'.3f')+" tons Total Milage: "+format(co2persistance['CO2'],'.0f')+" km. Cost ICE car gas at 16 SEK/l and 0.7 l/10km: "+format(co2persistance['CO2']*16*0.7/10,'.0f')+" SEK"}
    # )/0.92 testing bigger range

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


# Environment stuff -----------------------------------------------------------------------------

# load CO2 persistance
#
def loadco2():
    try:
        co2persistance = pickle.load( open( 'co2fixed.p', 'rb' ) )
    except:
        co2persistance = { 'charging': False, 'CO2': 0 }

    return co2persistance



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

# get Battery Temprature low
#
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']

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


# get Air Temperature, used to calcualte GOM range
#
def get_air_temp():
    args = ['air_temp']
    kwargs = {
        'mode': '21',
        'pid': '80',
        'header': '7E6',
        'baudrate': 500000,
        'formula': '(bytes_to_int(message.data[14:15])-80)/2',
        '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[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

# Charging CCS
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

# 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[12:13])',
        'protocol': '6',
        'verify': False,
        'force': True,
        }
        return (int(__salt__['obd.query'](*args, **kwargs)['value'])&32)/32
1 Like

Hi @AndersO

This is really awesome. I got the sleep resetter up and running and am now getting messages on telegram :slight_smile:
Now trying to get all the above up and running.

I have not been succesfull in getting any data into widgets on the dashboard yet. Have you been succesfull with that? (PID > Logger > Widget)
I tested the PID’s for SOH and SOC in the terminal and they work fine, buy no data on the dashboard widgets.

Hi @Mikael_Madsen
yes for me they work fine, I have SOH, SOC display and BMS, charging power, battery upper and lower temperature, 12V battery voltage and RPI temperature plus the map.
However I had to redo the loggers after upgrading the software.
I just made the loggers and then the widgets. The widgets appear in the list values as both PID and ODB, I picked ODB (I think for all) and set the timer for them to 15 minutes and it works fine.

@AndersO could you share some screen shots?

I noticed that SOC BMS was in the list as both PID and ODB.
The PID is a float and does not show the “preview” line chart, the OBD is a long and does show the “preview” line chart. At one point I saw some data for the ODB SOC BMS, but it disappeared again.

I do not know why the SOC BMS shows up on the list as OBD and SOC Display and SOH does not, they only appear as PID. Not sure if there is anything that I did wrong in the PID’s or the Loggers, which is why screen shots of PID, Logger and Widget would be awesome.

Thx.

@Mikael_Madsen
Hope this helps:

28%20PM

@AndersO awesome :slight_smile:
I can see that you have created your own can bus, could you share a screen shot of that one?
That might be the issue…

Thx. again :slight_smile:

@Mikael_Madsen
Hi, I dont think I did, i think it was automatically generated for me somehow when I made my profile.
But in any case here is the screenshot:

Hi @AndersO

I deleted all Loggers, synced, and added them again.
Also deleted and recrated after crating the loggers again.
Then the odb showed up in the list and it all works now :slight_smile:

Thx a lot!