Receive charging alerts on your phone ( and smart watch )


Sure, I’ll open a new topic tomorrow. Too late for today :slight_smile:

Warning in advance: it’s not pretty, but it works :stuck_out_tongue:


I’ve added some improvements to the script including -

  • efficiency since last charge
  • CO2 saved
  • Charging point details
  • Estimated range

I’ll post script in a few days when back home.


Wow big work ! Thanks


Wow. I am interest it too !!
I hope you update the sharing code :wink: in the first post :slight_smile:

1 Like

no is better @plord update with new at the end , it’s confuse people (I see on bug fix corrections )

1 Like

This is what I’m currently using -

import logging
import requests
import pickle
import os
import json
import urllib
from math import sin, cos, sqrt, atan2, radians

log = logging.getLogger(__name__)

# Telegram tokens - see
bot_token = add'
bot_chatID = 'add'

# Constants
# average ICE g/km
ice_emissions = 120.1 

# Average UK g/kWh
# electric_emissions = 225.0 

# Octopus g/kWh
electric_emissions = 0.0 

# max range
wltp = 279

# FIX THIS - add in SOH to calcs

# list of non-stanard chargers
chargers = [ 
        {'latitude':0, 'longitude':0, 'msg':'At home, '+'5p/KWh overnight'}

Poll to see if car is being charged.  If so :

1. Disable auto sleep whilst charging
2. Send Telegram message when charging starts
    - Estimated miles per kWh ( based on miles travelled and delta Accumulative Charge Power )
    - Estimaed CO2 saved on fuel since last charged ( based on ICE emissions and electricity supplier )
    - Estimated total CO2 saved since purchase ( based on total milage and ICE emissions )
    - Details of nearest charger ( ie probabally what is being used to charge car )
    - Estimated range ( based on WLTP )
3. Send Telegram message when charging reaches each 10%
5. Send Telegram message when charging stops
def poll():

    # enable sleep in case anything goes wrong below

    # load previous status
    persistance = load()

    # check if we are driving or charging
    driving = get_driving()
    if driving == 1 or driving == -1:
        if persistance['charging'] == True:
            if persistance['soc'] >= 99:
                persistance['soc'] = 100
            bot_sendtext('Charging *stopped*. Last known State of charge *'+format(persistance['soc'],'.1f')+'%* ('+format(wltp*persistance['soc']/100, '.1f')+' miles) charged '+format(persistance['cec']-persistance['start_cec'],'.1f')+'kWh')
            persistance['charging'] = False
        return {'msg': 'Not charging'}

    batt_power = get_charging_power()

    # avoid fake charging
    if batt_power <= 0:
        return {'msg': 'Not charging - power less than zero'}

    # now we are charging
    soc = get_soc()
    cec = get_cec()

    # alert if just started to charge
    if persistance['charging'] == False:
        last_charge_odo = persistance['odo']
        last_charge_soc = persistance['soc']
        odo = get_odometer()
        persistance['odo'] = odo
        persistance['start_cec'] = cec
        if last_charge_soc != soc:
            mperkwh = (odo-last_charge_odo)/(last_charge_soc*64.0/100.0-soc*64.0/100.0)
            mperkwh = 0.0
        co2saved = (ice_emissions*(odo-last_charge_odo)*1.609) - electric_emissions*(last_charge_soc*64.0/100.0-soc*64.0/100.0)
        bot_sendtext('Estmated *'+format(mperkwh,'.2f')+'mi/kWh* since last charge')
        bot_sendtext('*'+format(co2saved/1000,'.2f')+'Kg* CO2 saved since last charge')
        bot_sendtext('*'+format(odo*ice_emissions/1000000,'.2f')+'tonnes* CO2 saved in total')  
        bot_sendtext('Charging *started* at a rate of '+format(batt_power,'.2f')+'kW. State of charge now *'+format(soc,'.1f')+'%* ('+format(wltp*soc/100, '.1f')+' miles)')

    # each 10% alaert
    for level in xrange(0, 100, 10):
        if soc >= level and persistance['soc'] < level:
            bot_sendtext('Charging *now* at a rate of '+format(batt_power,'.2f')+'kW. State of charge now *'+format(soc,'.1f')+'%* ('+format(wltp*soc/100, '.1f')+' miles)')

    # store status for next time
    persistance['charging'] = True
    persistance['soc'] = soc
    persistance['cec'] = cec

    return {'msg': 'Charging at '+format(batt_power,'.2f')+'kW, SOC now *'+format(soc,'.1f')+'%*'}

# send message to telegram
def bot_sendtext(bot_message):
	send_text = '' + bot_token + '/sendMessage?' + urllib.urlencode({'chat_id': bot_chatID, 'parse_mode': 'Markdown', 'text': unicode(bot_message).encode('utf-8')})
# load persistance
def load():
        p = pickle.load( open( 'charge_status.p', 'rb' ) )
        p = { 'charging': False, 'soc': 0.0, 'odo': 0, 'cec': 0.0, 'start_cec': 0.0 }

    return p

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

# delete persistance
def delete():

# dump persistance
def dump():
    return load()

# check if we are driving.  Returns :
#   0 - charging
#   1 - driving
#   -1 - can't read data
def get_driving():
        args = ['driving']
        kwargs = {
            'mode': '220',
            'pid': '101',
            'header': '7E4',
            'baudrate': 500000,
            'formula': 'bytes_to_int([53:54])',
            'protocol': '6',
            'verify': False,
            'force': True,
        # note - sums are done outside of the forumla due to autopi failing
        # with 0
        return (int(__salt__['obd.query'](*args, **kwargs)['value'])&4)/4
        return -1

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

# get display state of charge
def get_soc():
    args = ['soc']
    kwargs = {
        'mode': '220',
        'pid': '105',
        'header': '7E4',
        'baudrate': 500000,
        'formula': 'bytes_to_int([34:35])/2.0',
        'protocol': '6',
        'verify': False,
        'force': True,
    return __salt__['obd.query'](*args, **kwargs)['value']

# get odometer
def get_odometer():
    args = ['odometer']
    kwargs = {
        'mode': '22',
        'pid': 'B002',
        'header': '7C6',
        'baudrate': 500000,
        'formula': 'bytes_to_int([11:12])*16777216+bytes_to_int([12:13])*65536+bytes_to_int([13:14])*256+bytes_to_int([14:15])',
        'protocol': '6',
        'verify': False,
        'force': True,
    return __salt__['obd.query'](*args, **kwargs)['value']

# get Accumulative Charge Power
def get_cec():
    args = ['odometer']
    kwargs = {
        'mode': '220',
        'pid': '101',
        'header': '7E4',
        'baudrate': 500000,
        'formula': '(bytes_to_int([41:42])*16777216+bytes_to_int([42:43])*65536+bytes_to_int([43:44])*256+bytes_to_int([44:45]))/10.0',
        'protocol': '6',
        'verify': False,
        'force': True,
    return __salt__['obd.query'](*args, **kwargs)['value']

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

# disable autopi sleep
def disable_sleep():
    args = ['sleep']
    kwargs = {
        'enable': False,

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

# get nearest charger
def nearest_charger():
    location = get_location()

    for charger in chargers:
        lat1 = radians(charger['latitude'])
        lon1 = radians(charger['longitude'])
        lat2 = radians(location['latitude'])
        lon2 = radians(location['longitude'])
        dlon = lon2 - lon1
        dlat = lat2 - lat1
        a = sin(dlat / 2)**2 + cos(lat1) * cos(lat2) * sin(dlon / 2)**2
        c = 2 * atan2(sqrt(a), sqrt(1 - a))
        dist = 6373.0 * c

        if dist < 0.02:
  'found local charger '+charger['msg'])
            return charger['msg']''+str(location['latitude'])+'&longitude='+str(location['longitude']))
    result = requests.get(''+str(location['latitude'])+'&longitude='+str(location['longitude']))
    for i in result.json():
        return i['OperatorInfo']['Title']+', '+i['AddressInfo']['Title']+', '+i['UsageCost']

    return 'No local charger found'

Obviously comments / fixes / improvements more than welcome.

Right now I’m not convinced that it handles errors well, so more tweaks will be needed.

1 Like
Hyundai Kona / Ionic EV

@plord errr you might want to redact the home charger coordinates? :slight_smile:


Feel free to call in for a charge … free cup of tea thrown in :slight_smile:

1 Like

That’s literally 1616 km away from me.
Maybe, someday :wink: :smile:


Hey @plord

Nice work I was wondering, would it make sense to have it in a github repo or similar? It get’s a bit invisible and not very easy to build upon and reshare when it is hidden in a forum post.


Yes, I was thinking the same :slight_smile:

IMHO It would make most sense if we have a area under and have an option to clone/download a script in the custom code editor.




@plord Awesome, I will have a look tonight!


Hi, I will try it on an Ioniq EV, hopefullt it should work.
Can’t it be used to it keep the autopi alive when driving as well (or does it already do that)?
I’m planning to learn some python, but i am not there yet. PL/SQL doesn’t really help :slight_smile:


My autopi stays awake when driving … I believe the piece thats missing is detecting when trips start and stop.


Hello @plord
How do you that ? By custom code ?
I already have your custom code for send message to telegram. Who work well.
Also this custom code lets the autopi awake, so it is usefull.
But when I drive , my autopi shut down.
So, how do you do to let’s the car awake when driving ?


I don’t think I do anything … its just always worked that way. From today -


I tried by copying your code (from the starting post above) into custom code (changing the Telegram part only):
bot_token = ‘xx523xxxxxxxT6IU’

bot_chatID = ‘12345’

And then adding the job as you did.
That is all, am i missing some part?

As it is now the autopi sleeps after a while both driving and charging (so far only charging from 230V) and sends no messages via telegram.
I had the wrong chatt id yesterday, i changed it now and tested sending messages via a browser that works. I have not yet tried it today but even if the message fails shouldn’t the dongle stay awake when charging?

Could it be that it is not compatible with Ioniq? It should be the same as fo Kona i would think.

What do you mean by; the piece detecting start and stop? Is that in the code in the first post or somewhere else?

Sorry about all the questions but i am completly new to this.


Thanks. For me, my car when turn on is ok.
Autopi detect 0,20 voltage more, so turn autaumatiquely ON the dongle. This is ok

I try a lots of timer different. But autopi shut down at 40 minute after the car turned ON.

I try everything, but it is the maximum I can.

I don’t know why.

So for short trip 40 minute < it is ok. For more, autopi go to sleep. It maybe my python code crash.

You did update today ?
What is you maximum drive with autopi ok ?

I hope we will have update soon.



Actually you are right, I see the same ( just didn’t pay much attention I guess ) - I do have two triggers installed - one to send me an email when the system powers on and one when it goes to sleep - so I see when this happens.

Yesterday I set off at 8am and the autopi when to sleep at 8:41 am due to inactivity fallback and woke up again at 9:24.

1 Like