Simple Python script that reads Google Calendar and updates OH items with event information

I red all the code but i haven’t understood the switch was for telegram! Now it’s perfectly clear! Thanks. I’ll wait to know if there is the possibility to add another calendar.

Thanks all!

I am not familiar with the google calendar api but I will check the python script and try to fund out if it’s possible to have a list of calendars instead of only one. Should be easy if google allows to retrieve more than one calender from one account with always the same api key.

thanks a lot! great man! we believe in you!

Ok, I checked out some details about google api. The solution for using multiple calendars is in my head now. I hope I can provide the modifications within the next 10 days.

Ok, this is my result. You can use multiple calendars. Instead of providing the single CalendarId within your CalSynHAB.ini you have to put all calendar IDs which you want to use into the .ini file in new section [CalendarIDs]. Your .ini file should look like this:

[General]
ApplicationName: OpenHAB

[Calendar]
Scope: https://www.googleapis.com/auth/calendar.readonly
MaxEvents: 10 
TimeZone: +01:00
ClientSecretFile: /etc/openhab2/scripts/CalSyncHAB/CalSyncHABSecret.json

[CalendarIDs]
id1 = abc@group.calendar.google.com
id2 = xyz@group.calendar.google.com

[OpenHAB]
HostName: 192.168.x.y
Port: 8080
ItemPrefix: gCal_

You may put as many calendar IDs into the file as you like. I tested with one and two.

Also the Settings.py file has to modified:

import configparser
import os

ApplicationDir = os.path.dirname(os.path.abspath(__file__))
HomeDir = os.path.expanduser('~')
CredentialDir = os.path.join(HomeDir, '.credentials')

if not os.path.exists(CredentialDir):
    os.makedirs(CredentialDir)

CredentialFilePath = os.path.join(CredentialDir, 'CalSyncHAB.json')
CalSyncHABSettings = os.path.join(ApplicationDir, 'CalSyncHAB.ini')

Settings = configparser.ConfigParser()
Settings.read(CalSyncHABSettings)

ApplicationName = Settings.get('General', 'ApplicationName')

CalendarScope = Settings.get('Calendar', 'Scope')
CalendarIdList = Settings.items('CalendarIDs')
CalendarMaxEvents = Settings.get('Calendar', 'MaxEvents')
CalendarTimeZone = Settings.get('Calendar', 'TimeZone')
CalendarClientSecretFile = Settings.get('Calendar', 'ClientSecretFile')

OpenHABHostName = Settings.get('OpenHAB', 'HostName')
OpenHABPort = Settings.get('OpenHAB', 'Port')
OpenHABItemPrefix = Settings.get('OpenHAB', 'ItemPrefix')

The only new thing in here is the line starting with ‘CalendarIdList =’ instead of 'CalendarId = ’

Last but not least the CalSyncHAB.py file has to modified.

import httplib2
import os
import datetime
import argparse as AP
import Settings as S
import warnings
import requests
import time
from operator import itemgetter
from apiclient import discovery
from oauth2client import client
from oauth2client import tools
from oauth2client.file import Storage

Flags = AP.ArgumentParser(parents=[tools.argparser]).parse_args()

def GetCredentials():
    with warnings.catch_warnings():
        warnings.simplefilter('ignore')
        CredentialStore = Storage(S.CredentialFilePath)
        Credentials = CredentialStore.get()

    if not Credentials or Credentials.invalid:
        AuthenticationFlow = client.flow_from_clientsecrets(S.CalendarClientSecretFile, S.CalendarScope)
        AuthenticationFlow.user_agent = S.ApplicationName
        Credentials = tools.run_flow(AuthenticationFlow, CredentialStore, Flags)

    return Credentials

def Main():
    Credentials = GetCredentials()

    HTTPAuthorization = Credentials.authorize(httplib2.Http())
    CalendarService = discovery.build('calendar', 'v3', http = HTTPAuthorization)
    CurrentTime = datetime.datetime.utcnow().isoformat() + 'Z'

    EventList = []
    for key, CalendarId in S.CalendarIdList:
        CalendarEvents = CalendarService.events().list(
            calendarId = CalendarId,
            timeMin = CurrentTime,
            maxResults = S.CalendarMaxEvents,
            singleEvents = True,
            orderBy = 'startTime').execute()
        RetrievedEvents = CalendarEvents.get('items', [])
        for SingleEvent in RetrievedEvents:
            EventStartTime = None
            EventEndTime = None
            event = []
            if 'summary' in SingleEvent:
                event.append(SingleEvent['summary'])
            else:
                event.append(' ')    
            
            if 'location' in SingleEvent:
                event.append(SingleEvent['location'])
            else:
                event.append(' ')
            
            if 'description' in SingleEvent:
                event.append(SingleEvent['description'])
            else:
                event.append(' ')
            
            if 'start' in SingleEvent:
                EventStartTime = SingleEvent['start'].get('dateTime', SingleEvent['start'].get('date'))
                try:
                    datetime.datetime.strptime(EventStartTime, '%Y-%m-%dT%H:%M:%S' + S.CalendarTimeZone)
                except ValueError:
                    if "T" not in EventStartTime:
                        EventStartTime = EventStartTime + 'T00:00:00' + S.CalendarTimeZone
                    else:
                        EventStartTime = EventStartTime
                event.append(EventStartTime)
            else:
                event.append(' ')
            
            if 'end' in SingleEvent:
                EventEndTime = SingleEvent['end'].get('dateTime', SingleEvent['end'].get('date'))
                try:
                    datetime.datetime.strptime(EventEndTime, '%Y-%m-%dT%H:%M:%S' + S.CalendarTimeZone)
                except ValueError:
                    if "T" not in EventEndTime:
                        EventEndTime = EventEndTime + 'T00:00:00' + S.CalendarTimeZone
                    else:
                        EventEndTime = EventEndTime
                event.append(EventEndTime)
            else:
                event.append(' ')
            
            EventList.append(event)
    
    SortedEvents = sorted(EventList, key=itemgetter(3)) 

    if S.OpenHABPort.strip() != '':
        TrimmedHostAndPort = S.OpenHABHostName.strip() + ':' + S.OpenHABPort.strip()
    else:
        TrimmedHostAndPort = S.OpenHABHostName.strip()

    MaxEvents = int(S.CalendarMaxEvents) + 1
    for EventCounter in range(1, MaxEvents):

        CalendarEventSummaryItemURL = 'http://' + TrimmedHostAndPort + '/rest/items/' + S.OpenHABItemPrefix + 'Event' + str(EventCounter) + '_Summary'
        OpenHABResponse = requests.post(CalendarEventSummaryItemURL, data = '', allow_redirects = True)
       
        CalendarEventLocationItemURL = 'http://' + TrimmedHostAndPort + '/rest/items/' + S.OpenHABItemPrefix + 'Event' + str(EventCounter) + '_Location'
        OpenHABResponse = requests.post(CalendarEventLocationItemURL, data = '', allow_redirects = True)

        CalendarEventDescriptionItemURL = 'http://' + TrimmedHostAndPort + '/rest/items/' + S.OpenHABItemPrefix + 'Event' + str(EventCounter) + '_Description'
        OpenHABResponse = requests.post(CalendarEventDescriptionItemURL, data = '', allow_redirects = True)
        
        CalendarEventStartTimeItemURL = 'http://' + TrimmedHostAndPort + '/rest/items/' + S.OpenHABItemPrefix + 'Event' + str(EventCounter) + '_StartTime'
        OpenHABResponse = requests.post(CalendarEventStartTimeItemURL, data = '1970-01-01T00:00:00', allow_redirects = True)

        CalendarEventEndTimeItemURL = 'http://' + TrimmedHostAndPort + '/rest/items/' + S.OpenHABItemPrefix + 'Event' + str(EventCounter) + '_EndTime'
        OpenHABResponse = requests.post(CalendarEventEndTimeItemURL, data = '1970-01-01T00:00:00', allow_redirects = True)

    time.sleep(2)

    EventCounter = 0

    for SingleEvent in SortedEvents:
        EventSummary = ''
        EventLocation = ''
        EventDescription = ''
        EventStartTime = None
        EventEndTime = None

        EventCounter += 1

        if SingleEvent[0] != ' ':
            EventSummary = SingleEvent[0]

        if SingleEvent[1] != ' ':
            EventLocation = SingleEvent[1]

        if SingleEvent[2] != ' ':
            EventDescription = SingleEvent[2]

        if SingleEvent[3] != ' ':
            EventStartTime = SingleEvent[3]

        if SingleEvent[4] != ' ':
            EventEndTime = SingleEvent[4]

        CalendarEventSummaryItemURL = 'http://' + TrimmedHostAndPort + '/rest/items/' + S.OpenHABItemPrefix + 'Event' + str(EventCounter) + '_Summary'
        OpenHABResponse = requests.post(CalendarEventSummaryItemURL, data = EventSummary.encode('utf-8'), allow_redirects = True)

        CalendarEventLocationItemURL = 'http://' + TrimmedHostAndPort + '/rest/items/' + S.OpenHABItemPrefix + 'Event' + str(EventCounter) + '_Location'
        OpenHABResponse = requests.post(CalendarEventLocationItemURL, data = EventLocation.encode('utf-8'), allow_redirects = True)

        CalendarEventDescriptionItemURL = 'http://' + TrimmedHostAndPort + '/rest/items/' + S.OpenHABItemPrefix + 'Event' + str(EventCounter) + '_Description'
        OpenHABResponse = requests.post(CalendarEventDescriptionItemURL, data = EventDescription.encode('utf-8'), allow_redirects = True)

        CalendarEventStartTimeItemURL = 'http://' + TrimmedHostAndPort + '/rest/items/' + S.OpenHABItemPrefix + 'Event' + str(EventCounter) + '_StartTime'
        OpenHABResponse = requests.post(CalendarEventStartTimeItemURL, data = EventStartTime, allow_redirects = True)
    
        CalendarEventEndTimeItemURL = 'http://' + TrimmedHostAndPort + '/rest/items/' + S.OpenHABItemPrefix + 'Event' + str(EventCounter) + '_EndTime'
        OpenHABResponse = requests.post(CalendarEventEndTimeItemURL, data = EventEndTime, allow_redirects = True)

        if EventCounter == MaxEvents:
            break

if __name__ == '__main__':
    Main()

The scripts retrieves ‘MaxEvents’ upcoming events from each of your given calendars. In the next setp all events are sorted by their starttime. And in the end ‘MaxEvents’ events from the sorted list are placed into your openhab items.

1 Like

should the apiclient be the google apiclient as I am getting errors

Traceback (most recent call last):
  File "/etc/openhab2/scripts/CalSyncHAB/CalSyncHAB.py", line 9, in <module>
    from apiclient import discovery
ImportError: No module named apiclient

https://stackoverflow.com/questions/18267749/importerror-no-module-named-apiclient-discovery

@Chod: My 3 files do not work out of the box. I only posted the changes I made in @davorf‘s code. The google-api-python-client has to be installed (besides other things).
Did you follow the install process described in post #46 Simple Python script that reads Google Calendar and updates OH items with event information ?

my error is well before the google attempt its on the initial loading of modules, i am guessing my python install in incorrect

That‘s what I said. You have to:

maybe as sudo.

I have done that previously, it has not affected the error, there is a error about

Cannot uninstall 'httplib2'. It is a distutils installed project and thus we cannot accurately determine which files belong to it which would lead to only a partial uninstall.

which happens with the google api client api upgrade

python -m pip install --upgrade google-api-python-client

I‘m not a linux expert. Maybe different python versions are mixed on your machine. You can find out with

whereis python
which python

Maybe you should delete all versions and install a fresh one. I am using python 2.7.13.

Also a

sudo apt-get update
sudo apt-get upgrade

could be helpful.

Is it supposed to create the item file, rules and sitemap for you or do I generate those?

I’m not quite sure if understand your question correctly. you have to create all items, rules, sitemaps etc. by yourself. Nothing will be generated automatically.
You should follow then instructions in posts 1 and 47.
Um, just as writing this I realise that everything has become a bit messy. We have different versions of the python script and of the openhab rules as well. Give me some days and I will setup a fresh git with the latest software and instructions.

@davorf: if its ok for you I can put the latest versions of the scripts and rules into a new git under my name. Just asking because I don’t wanna adorn myself with borrowed plumes.

Hello!

Since I don’t have much time for any kind of hobby, home automation included, I really appreciate your help to script users. And since this is all open source code, feel free to do anything you like with it.

Best regards,
Davor

Thx, very much. I will of course link to your original scripts

Thanks Frank;

I followed the instructions and got the script running (with you’re latest script that has support for multiple calendars) but I was unsure if the script was going to auto generate it’s own thing file and maintain it or if I had to make it and it was just updating the data on the back end.

I am still unsure about what items to put into my things file; here is an example

gCal_calID_Event1_Summary (String)
gCal_CALID2_Event1_Summary (String)

Also as a noob tip for future readers; you have to chmod the .sh script to make it executable

The last is correct. The items (not things!) are just updated by the script via REST api.

Items have to be put into .items files and things into .things files. I guess it won’t be good mixing these types. We are using items for everything in here.

You need: n sets of items which will be filled when calling the python script and will contain your event data like:

String     gCal_Event1_Summary "Event1 Sum.: [%s]" <calendar> (gCalEvent)
String     gCal_Event1_Location "Event1 Loc.: [%s]" <calendar> (gCalEvent) 
String     gCal_Event1_Description "Event1 Desc. [%s]" <calendar> (gCalEvent) 
DateTime   gCal_Event1_StartTime  "Event1 Start [%1$td.%1$tm.%1$tY %1$tH:%1$tM]" <calendar> (gCalEvent)
DateTime   gCal_Event1_EndTime "Event1 End [%1$td.%1$tm.%1$tY %1$tH:%1$tM]" <calendar> (gCalEvent)

where n is the number of events you specified in the .ini file under

MaxEvents: 10

I am using 10. This number depends on how many events you expect to be starting at the same time respectively between two calls of

rule "GetCalEvents"

CalSyncHAB.py expects the names of the items like:

<ItemPrefix>Event<EventCounter>_Summary
<ItemPrefix>Event<EventCounter>_Location
<ItemPrefix>Event<EventCounter>_Description
<ItemPrefix>Event<EventCounter>_StartTime
<ItemPrefix>Event<EventCounter>_EndTime

where “ItemPrefix” is defined in your .ini file and “EventCounter” is a number starting from 1. If you set MaxEvents to 10 and ItemPrefix to “gcal_” your last item has to be
“gcal_Event10_EndTime”.

Yo do not need to put the name of your calendar into the name of the items.
BTW: you shuould delete the id of your calendar from the post to prevent others from trying to hack it.

that is absolutely correct. Thanks, I will add this to the instructions list.

1 Like

Thanks for the catch; I’ve edited my post.

Worked like a charm. Appreciate the help!!!

You‘re welcome. Let me know if you need further help.

1 Like