Page 1 of 1

Coupling IFTTT to Domoticz by eMail

Posted: Tuesday 23 September 2014 22:29
by Jan
Past few weeks I have gained experience with IFTTT (If This Than That at https://ifttt.com/dashboard). I was interested to be able to connect IFTTT to Domoticz. My first experiment was to make use of the locations services of IFTTT (on my iPad). So when I coming home or leaving home, I want to toggle a switch to On or Off in Domoticz.
To be able to couple the location services of IFTTT to a Domoticz switch, I have make use of GMail messages.

So, when I am coming home, a recipe of IFTTT sent a message to my GMail address with in the subject the switch name ('Not at Home'). The body of the message does not matter but I start it with 'Off'. After that, a python script read all messages of the mailbox till the script find a subject with a corresponding switch name in Domoticz. In this case when the script find the message with the subject 'Not at Home' the script set my switch in Domoticz to 'Off'. Now the message will be deleted from the mail box. (When something was going wrong, the message will not be deleted!)

When I am leaving home, a recipe of IFTTT sent a message to my GMail address with in the subject the switch name ('Not at Home'). The body of the message must start it with 'On'. After that, a python script set my switch in Domoticz to 'On'. Now the message will be deleted from the mail box. (When something was going wrong, the message will not be deleted!)


When you want to implement the same, follow next steps:
1.
I have used the following two recipes:
- https://ifttt.com/recipes/133585-let-my ... cipeiseasy (For the area I used my home address; Recipe Title = Email my when I'am coming home!; To address = <mail address for homeautomation purposes>@gmail.com; Subject = Not at Home; Body = Off<br>{{OccurredAt}}<br>via iOS Location {{LocationMapUrl}})
- https://ifttt.com/recipes/132927-email- ... -from-work (For the area I used my home address; Recipe Title = Email my when I'am leaving home!; To address = <mail address for homeautomation purposes>@gmail.com; Subject = Not at Home; Body = On<br>{{OccurredAt}}<br>via iOS Location {{LocationMapUrl}})

2.
I made a bash file which runs every 5 minutes from a cron job (sudo crontab -e):
*/5 * * * * /home/pi/script/gmail.sh

The bash file looks like (gmail.sh):

Code: Select all

#!/bin/bash
#
#$HOME/script/gmail.py --verbose >> /tmp/gmail.log &

sudo python /home/pi/script/gmail.py >> /tmp/gmail.log &
3. In step 2 you see I make use of a python (v2.7) script. Hopefully, you can install python by your own.
In the script (gmail.py) you must change the constants IMAP_USERNAME and IMAP_PASSWORD with your own mail address and password! (For some people also the constant DOMOTICZ_HOST must be changed!)

Code: Select all

#!/usr/bin/python
import sys
import argparse
import json
import httplib
from datetime import datetime
import imaplib
import time
import email

__author__ = 'Jan N'
__version__ = '0.1'

DOMOTICZ_HOST = '127.0.0.1:8080'

IMAP_USERNAME = '<your mail address>@gmail.com'
IMAP_PASSWORD = '<your password>'

IMAP_SERVER = 'imap.gmail.com'
IMAP_PORT = '993'
IMAP_USE_SSL = True


def cli_options():
	cli_args = {}
	cli_args['verbose'] = True

	# Parse the CLI
	parser = argparse.ArgumentParser()
	parser.add_argument('--verbose', help='Verbose mode', action='store_true', default=False)

	# Parse arguments and die if error
	try:
		args = parser.parse_args()
	except Exception:
		sys.exit(2)

	if args.verbose:
		cli_args['verbose'] = args.verbose 

	return (cli_args)


def is_number(val):
	try:
		float(val)
		return True
	except ValueError:
		return False

def date_time():
	return datetime.now().strftime('%Y/%m/%d %H:%M:%S')


# This class is derived from Pymoticz, modified to use httplib
class Domoticz:
	def __init__(self, domoticz_host=DOMOTICZ_HOST): # Default on localhost
		self.host = domoticz_host


	def _request(self, url):
		(ip, port) = self.host.split(":")
		http = httplib.HTTPConnection(ip, port, timeout=2)
		http.request("GET", url)
		result = http.getresponse()

		if (result.status != 200):
			raise Exception

		http.close()
		return json.loads(result.read())

	def list(self):
		url='/json.htm?type=devices&used=true&order=Name'
		return self._request(url)

	def turn_on(self, _id):
		url='/json.htm?type=command&param=switchlight&idx=%s&switchcmd=On' % (_id)
		return self._request(url) 

	def turn_off(self, _id):
		url='/json.htm?type=command&param=switchlight&idx=%s&switchcmd=Off' % (_id)
		return self._request(url)

	def turn_on_if_off(self, _id):
		status=False
		if (self.get_switch_status(_id) == "Off"):
			self.turn_on(_id)
			status=True
		return status

	def turn_off_if_on(self, _id):
		status=False
		if (self.get_switch_status(_id) == "On"):
			self.turn_off(_id)
			status=True
		return status

	def turn_on_off(self, _id, _state):
		url='/json.htm?type=command&param=switchlight&idx=%s&switchcmd=%s' % (_id, _state)
		return self._request(url)

	def get_switch_status(self, _id):
		try:
			device = self.get_device_by_idx(_id)
		except:
			return None

		return device['Status']

	def get_device_by_idx(self, _id):
		url='/json.htm?type=devices&rid=%s' % (_id)
		try:
			device = self._request(url)
		except:
			return None

		return device['result'][0]

	def get_device_by_name(self, _name):
		devices = self.list()['result']
		for item in devices:
			if (item['Name'] == _name):
				return item

		return None


# This class is derived from cgoldberg
"""MailBox class for processing IMAP email.
 
(To use with Gmail: enable IMAP access in your Google account settings)
 
usage with GMail:
 
	import mailbox
 
	with mailbox.MailBox(gmail_username, gmail_password) as mbox:
		print mbox.get_count()
		print mbox.print_msgs()

for other IMAP servers, adjust settings as necessary.    
"""
class MailBox(object):

	def __init__(self, user, password):
		self.user = user
		self.password = password
		if IMAP_USE_SSL:
			self.imap = imaplib.IMAP4_SSL(IMAP_SERVER, IMAP_PORT)
		else:
			self.imap = imaplib.IMAP4(IMAP_SERVER, IMAP_PORT)

	def __enter__(self):
		self.imap.login(self.user, self.password)
		return self

	def __exit__(self, type, value, traceback):  # @ReservedAssignment
		self.imap.close()
		self.imap.logout()

	def get_count(self):
		self.imap.select('Inbox')
		status, data = self.imap.search(None, 'ALL')  # @UnusedVariable
		return sum(1 for num in data[0].split())  # @UnusedVariable

	def fetch_message(self, num):
		self.imap.select('Inbox')
		status, data = self.imap.fetch(str(num), '(RFC822)')  # @UnusedVariable
		email_msg = email.message_from_string(data[0][1])
		return email_msg

	def delete_message(self, num):
		self.imap.select('Inbox')
		self.imap.store(num, '+FLAGS', r'\Deleted')
		self.imap.expunge()
		
	def delete_all(self):
		self.imap.select('Inbox')
		status, data = self.imap.search(None, 'ALL')  # @UnusedVariable
		for num in data[0].split():
			self.imap.store(num, '+FLAGS', r'\Deleted')
		self.imap.expunge()

	def print_msgs(self):
		self.imap.select('Inbox')
		status, data = self.imap.search(None, 'ALL')  # @UnusedVariable
		for num in reversed(data[0].split()):
			status, data = self.imap.fetch(num, '(RFC822)')  # @UnusedVariable
			print 'Message %s\n%s\n' % (num, data[0][1])

	def get_latest_email_sent_to(self, email_address, timeout=300, poll=1):
		start_time = time.time()
		while ((time.time() - start_time) < timeout):
			# It's no use continuing until we've successfully selected
			# the inbox. And if we don't select it on each iteration
			# before searching, we get intermittent failures.
			status, data = self.imap.select('Inbox')
			if status != 'OK':
				time.sleep(poll)
				continue
			status, data = self.imap.search(None, 'TO', email_address)
			data = [d for d in data if d is not None]
			if status == 'OK' and data:
				for num in reversed(data[0].split()):
					status, data = self.imap.fetch(num, '(RFC822)')
					email_msg = email.message_from_string(data[0][1])
					return email_msg
				time.sleep(poll)
		raise AssertionError("No email sent to '%s' found in inbox "
			"after polling for %s seconds." % (email_address, timeout))

	def delete_msgs_sent_to(self, email_address):
		self.imap.select('Inbox')
		status, data = self.imap.search(None, 'TO', email_address)
		if status == 'OK':
			for num in reversed(data[0].split()):
				status, data = self.imap.fetch(num, '(RFC822)')
				self.imap.store(num, '+FLAGS', r'\Deleted')
		self.imap.expunge()


def main():
	# Parse the CLI options
	(cli_parms) = cli_options()

	# Print an empty line and date in verbose mode
	if cli_parms['verbose']:
		print ""

	# Get instance of Domoticz class
	d = Domoticz()

	imap_username = IMAP_USERNAME
	imap_password = IMAP_PASSWORD
	with MailBox(imap_username, imap_password) as mbox:
		messages = mbox.get_count()
		# if (messages > 0):
		# 	if cli_parms['verbose']:
		# 		print "{0} DEBUG: #Messages = {1}".format(date_time(), messages)
		# 		print "{0} DEBUG:".format(date_time())
		# 		print mbox.print_msgs()

		counter = 1
		while (counter <= messages):
			msg = mbox.fetch_message(counter)
			varSubject = msg['subject']
	
			# Set subject to Switch Name
			switch_name = varSubject
		
			# Get Switch Idx of switch from Domoticz
			switch_idx = 0
			if (switch_name != ""):
				# Print Switch Name (= subject)
				if cli_parms['verbose']:
					print "{0} DEBUG: Check Switch Name: {1}".format(date_time(), switch_name),
		
				# Get Switch Idx by Switch Name
				try:
					switch = d.get_device_by_name(switch_name)
					# Check if switch exists
					if (switch is not None):
						# Get Switch Idx
						switch_idx = switch['idx']
		
						# Print Switch Idx
						if cli_parms['verbose']:
							print "-> Switch Idx = {0}".format(switch_idx)

						# Check if message body starts with 'On'
						turn_on = False
						if isinstance(msg.get_payload(), list):
							for eachPayload in msg.get_payload():
								body = eachPayload.get_payload()
								# Checks body starts with 'On'
								if body.startswith('On'):
									turn_on = True
									break # Exists for loop
						else: # There is only a text/plain part
							body = msg.get_payload()
							if body.startswith('On'):
								turn_on = True

						if (turn_on):
							# Set switch to On
							if (switch_idx != 0) and d.turn_on_if_off(switch_idx) and cli_parms['verbose']: # Turn switch On only if Off
								print "{0} DEBUG: Switching {1}: {2}".format(date_time(), "On", switch_name)
						else:
							# Set switch to Off
							if (switch_idx != 0) and d.turn_off_if_on(switch_idx) and cli_parms['verbose']: # Turn switch Off only if On
								print "{0} DEBUG: Switching {1}: {2}".format(date_time(), "Off", switch_name)

						# Delete message if switch exists
						mbox.delete_message(counter)
						
					# Switch does not exists
					else:
						if cli_parms['verbose']:
							print "-> Switch does not exists"

				except:
					if cli_parms['verbose']:
						print "-> List raises an exception"

			counter += 1

	
if __name__ == "__main__":
	main()
PS. If you have suggestions to improve the source code above, don't hesitate!

4. Make a dummy switch in Domoticz with the name 'Not at Home'. (Must be the same name as the subjects in above recipes! So, when you choose another name, change also the subjects in your recipes.)

5. After this, you are ready to test above. I have used a new GMail address to separate my personal email from the home automation email. Hopefully it works! If you likes above, maybe you will sent me a personal message or response!

Re: Coupling IFTTT to Domoticz by eMail

Posted: Wednesday 24 September 2014 21:10
by gizmocuz
do you think it is possible to post it to a google drive spreadsheet, and that i can access the spreadsheet from native domoticz ?
Like with c++

i have found some web pages that say you can get the websheet as json, but when i tried it (very simple spreadsheet) i got a lot of garbage back,
and not my rows with all the columns

i now have

[date] [action] [id] [data]

like

today - switchlight - 1000 - on(entered/exitied etc)

when i publish the spreadsheet and try to open it as json, i have so many data back, and opening each node does not give me my data back, some rows are merged also

if this is working, we could make use of a spreadsheet for all kinds of fun

but polling a spreadsheet, or email server... is this fast enough ? i dont think google would like it to poll every 10 seconds

Re: Coupling IFTTT to Domoticz by eMail

Posted: Wednesday 24 September 2014 21:45
by gizmocuz
maybe write a native wordpress implementation ?

Re: Coupling IFTTT to Domoticz by eMail

Posted: Thursday 25 September 2014 22:29
by Jan
gizmocuz thanks for your suggestion.
In IFTTT it is not a problem to use a Google Drive Spreadsheet instead of a GMail channel. Your suggested columns ([date] [action] [id] [data]) must be possible too. But when you already have experience, that reading from a spreadsheet by native domoticz gives problems, it looks like not a good idea!
(With WordPress I have no experience.)

Re: Coupling IFTTT to Domoticz by eMail

Posted: Sunday 13 September 2015 20:30
by savage007
hi,

the script you provided doesn't work in my setup, i get an error message :


2015/09/13 17:42:29 DEBUG: Check Switch Name: is aangekomen op September 13, 201
5 at 01:36PM -> List raises an exception
2015/09/13 17:42:29 DEBUG: Check Switch Name: Inlogpoging geblokkeerd -> List ra
ises an exception
2015/09/13 17:42:30 DEBUG: Check Switch Name: Toegang voor minder veilige apps i
s ingeschakeld -> List raises an exception
2015/09/13 17:42:30 DEBUG: Check Switch Name: niet thuis -> List raises an excep
tion
2015/09/13 17:42:31 DEBUG: Check Switch Name: niet thuis -> List raises an excep
tion

domoticz version is 3004, i am currently running on windows.

i removed !/usr/bin/python from the script you provided.

Did something change in between versions of domoticz?

Re: Coupling IFTTT to Domoticz by eMail

Posted: Monday 29 August 2016 14:06
by renerene
any progress here? I'm getting the same error in the log '-> List raises an exception'

Re: Coupling IFTTT to Domoticz by eMail

Posted: Friday 11 August 2017 13:59
by kimhav
Interesting stuff this and would look into make use of this where external system (which can only communicate via email) would be able to trigger activities within Domoticz; So Cool Stuff! So as well, wondering if there has been any additional updates and improvements of the scripts used?

Re: Coupling IFTTT to Domoticz by eMail

Posted: Friday 18 August 2017 9:49
by kimhav
Well, guess I have to add myself to the wondering people whether something has changed in Domoticz (running with v3.8153) where I get an exception error as well from the gmail.py (running on Rpi2) script as well:

2017/08/18 09:42:27 DEBUG: Check Switch Name: Updated -> List raises an exception

Dummy device has been created using switch type On/Off and Type X10 and to the dummy device I've then added a sub/slave device to test the script whether it turns of a light or not based on what comes in via email.

Re: Coupling IFTTT to Domoticz by eMail

Posted: Wednesday 23 August 2017 10:48
by nizaga
hello, in my case i have a samsung tablet with imperihome, pushover and tasker. To interact via IFTTT, the recipe sends an email (a pushower email), so tasker plugin receives the push notificacion and triggers a imperihome scene via the plugin. it works pretty well, so i don't have to open my router to the outside world.

regards,