Receive charging alerts on your phone ( and smart watch )

#36

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:

2 Likes
#37

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.

4 Likes
#38

Wow big work ! Thanks

#39

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

1 Like
#40

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

1 Like
#41

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 https://www.mariansauter.de/2018/01/send-telegram-notifications-to-your-mobile-from-python-opensesame/
#
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
    #
    enable_sleep()

    # 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
            save(persistance)
        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
    #
    disable_sleep()
    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)
        else:
            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(nearest_charger()) 
        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)')
            break

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

    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 = 'https://api.telegram.org/bot' + bot_token + '/sendMessage?' + urllib.urlencode({'chat_id': bot_chatID, 'parse_mode': 'Markdown', 'text': unicode(bot_message).encode('utf-8')})
	requests.get(send_text)
    
# load persistance
#
def load():
    try:
        p = pickle.load( open( 'charge_status.p', 'rb' ) )
    except:
        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():
    os.remove('charge_status.p')

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

# check if we are driving.  Returns :
#   0 - charging
#   1 - driving
#   -1 - can't read data
def get_driving():
    try:
        args = ['driving']
        kwargs = {
            'mode': '220',
            'pid': '101',
            'header': '7E4',
            'baudrate': 500000,
            'formula': 'bytes_to_int(message.data[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
    except:
        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(message.data[13:14])*256+bytes_to_int(message.data[14:15]),16)/10.0)*((bytes_to_int(message.data[15:16])*256+bytes_to_int(message.data[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(message.data[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(message.data[11:12])*16777216+bytes_to_int(message.data[12:13])*65536+bytes_to_int(message.data[13:14])*256+bytes_to_int(message.data[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(message.data[41:42])*16777216+bytes_to_int(message.data[42:43])*65536+bytes_to_int(message.data[43:44])*256+bytes_to_int(message.data[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,
    }
    __salt__['power.sleep_timer'](**kwargs)

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

# 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:
            log.info('found local charger '+charger['msg'])
            return charger['msg']

    log.info('https://api.openchargemap.io/v3/poi/?output=json&distance=0.1&maxresults=1&latitude='+str(location['latitude'])+'&longitude='+str(location['longitude']))
    result = requests.get('https://api.openchargemap.io/v3/poi/?output=json&distance=0.1&maxresults=1&latitude='+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
#42

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

#43

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

1 Like
#44

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

#45

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.

#46

Yes, I was thinking the same :slight_smile:

IMHO It would make most sense if we have a area under https://github.com/autopi-io and have an option to clone/download a script in the custom code editor.

3 Likes
#47

See https://github.com/plord12/autopi-tools#user-content-charge-status

2 Likes
#48

@plord Awesome, I will have a look tonight!

#49

@plord
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:

#50

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

#51

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 ?
Thank’s.

#52

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

#53

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

#54

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.

Thanks.

#55

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