OWL Intuition (Electric and Heating and Hot Water) to Emoncms (success)

,

Hello.

I’m pleased to share that I now have my Owl “family” of sensors and controls feeding data into Emoncms (hosted on a PC within the house).

For those not familiar with Owl, the individual units talk wireless (proprietary protocol) to an Owl gateway box that plugs into your broadband router. This then uploads the stats to the Owl Web Portal, however one nice addition is that it also multicasts the data onto your home LAN network…where you can capture it and repurpose the data :slight_smile:

So, I now have a python script that listens to that multicast address, and then processes the data (which comes as XML datagrams), picks out the fields i’m actually interested in, and then sends that as a CSV encoded message to the Emoncms listener.

I’m thinking about writing up a short howto blog post of my setup in more detail, including sharing the scripts and which bits to modify for other owl households, but before I do, are there any specific questions (or answers) you would like such a blog post to include?

If I don’t hear anything i’ll create the blog anyway based on what I found to be the most challenging parts of this mini-project (one of which been that I had never used Python before!)

Cheers
Roger

Nice work, Roger!

I’d say create your blog. You can always deal with questions later. :wink:

I’d say that you should assume that your reader is intelligent, but has no knowledge of your specific set-up. So you must include the reason for doing something if it’s done in a way or for a reason that’s not obvious, or different from the way that’s usually recommended.

Okay, here we go.

It turned out to be a longer piece than anticipated when I wrote it all down, so i’ve split it across 3 posts to make it easier to read.

Parts 1 & 2 are written and online.
Part 3 is nearly complete and will be online tomorrow.

2 Likes

Great blog post. I’d started to write some python UDP code for my Owl myself last night but hoped someone else may have done this for EmonCMS. Did you implement the solar logging too as I have the full setup of Owl solar, elec, DHW & Ch installed. Shame this isn’t simply a configuration option in EmonCMS rather than having to setup a separate script to do the import. Ta.

Hi Andy. Thanks for the feedback.

Yes I use the emoncms Solar App and it works well with my script.

I also have a HW divert heater, but unfortunately Owl have a bug in their
later gateways (the bit with the antenna) which means multicast doesn’t
work, so I haven’t been able to try the ‘Solar App with Divert’.

I’ve done a few small changes to the script since i wrote those blog posts,
so I’ll do a follow-up blog later this week to cover the new bits (more
tweaks than anything major)

Hopefully my script is helping your project. It’s really good to know
someone has read the blog lol.

Regards

Can someone show step-by-step instructions hot to do it with Raspberry Pi, please? I have 3-phase OWL. Thanks!

The blog is offline, can you post it here?

Hi @rog99, Have these blog posts been moved?

Hi Paul. Yes, I’m afraid the website the blog was on is offline currently.

However the script itself is still accessible at http://rogerransome.com/rr-scripts/owl4.py which might be of some help.

2 Likes

Thanks @rog99, i will copy that code here (see below).

##################################################################
# v3 - electric + hw
# v4 - adds ground floor room sensor

from twisted.internet.protocol import DatagramProtocol
from lxml import objectify
from decimal import Decimal
import socket
import requests
import pdb

sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) # Internet, UDP

MCAST_ADDR = '224.192.32.19'
MCAST_PORT = 22600

class node(object):
# structure for storing data about channels
        def __init__(self, channel_id, current_w, daily_wh):
		self.channel_id = channel_id
                self.current_w = Decimal(current_w)
		self.daily_wh = Decimal(daily_wh)

        def __str__(self):
                return '%s' % (
		self.channel_id, #   self.channel_id
                self.current_w,  #   self.current_w
		self.daily_wh    #   self.daily_wh
                )

class OwlMessage(object):
        def __init__(self, datagram):
  #print "datagram: %r" % (datagram,)
                self.root = objectify.fromstring(datagram)

		if self.root.tag == 'electricity':
	
			# there are also weather events -- we don't care about these (yet)
	                assert (self.root.tag == 'electricity'), ('OwlMessage XML must have `electricity` root node (got %r).' % self.root.tag)

			# note that the MAC address is given by the message, not the packet.
			# this can be spoofed
	                self.mac = self.root.attrib['id']
	
			# read signal information for the sensor's 433MHz link
	                self.rssi = Decimal(self.root.signal[0].attrib['rssi'])
	                self.lqi = Decimal(self.root.signal[0].attrib['lqi'])
		
			# read timestamp from message
			self.msgtime = self.root.timestamp
	
			# read battery information from the sensor.
	                self.battery = Decimal(self.root.battery[0].attrib['level'][:-1])
	
			import xml.etree.ElementTree as ET
			tree = ET.ElementTree(ET.fromstring(datagram))
			root = tree.getroot()
	
			#self.otimestamp = root[0].text # messages timestamp
			self.och1curr = root[3][0].text # chan1 current usage
			self.och1day = root[3][1].text # chan1 daily usage

		elif self.root.tag == 'hot_water':
			assert (self.root.tag == 'hot_water'), ('OwlMessage XML must have `hot_water` root node (got %r).' % self.root.tag)

			#gather relevant data from datagram
			self.mac = self.root.attrib['id']
			self.msgtime = self.root.timestamp
			self.battery = Decimal(self.root.zones.zone.battery.attrib['level'][:-1])
			self.tcurr = self.root.zones.zone.temperature.current.text # current HW temperature according to probe sensor
			self.treq = self.root.zones.zone.temperature.required.text # required/target HW temperature according to time schedule
			self.tambi = self.root.zones.zone.temperature.ambient.text # ambient temperature at wall control box

			# HW heater state; 0=off,1=heat on,4=comfort(at temp),5=warmingup,6=comfort(cooling down), 7=standby (on, freeze protection)
			self.hwstatus = self.root.zones.zone.temperature.attrib['state']

		elif self.root.tag == 'heating':
			assert (self.root.tag == 'heating'), ('OwlMessage XML must have `heating` root node (got %r).' % self.root.tag)
			# gather heating data from datagram (1 room sensor)
			# self.msgtime = self.root.timestamp
			# self.battery = Decimal(self.root.zones.zone.battery.attrib['level'][:-1])
			# self.tcurr = self.root.zones.zone.temperature.current.text
			# self.treq = self.root.zones.zone.temperature.required.text
			# self.chstatus = self.root.zones.zone.temperature.attrib['state']

			# ground floor room sensor [zone 0]
                        self.gbattery = Decimal(self.root.zones.zone[0].battery.attrib['level'][:-1])
                        self.gtcurr = self.root.zones.zone[0].temperature.current.text
                        self.gtreq = self.root.zones.zone[0].temperature.required.text
                        self.gchstatus = self.root.zones.zone[0].temperature.attrib['state']

			# upstairs room sensor [zone 1]
                        self.ubattery = Decimal(self.root.zones.zone[1].battery.attrib['level'][:-1])
                        self.utcurr = self.root.zones.zone[1].temperature.current.text
                        self.utreq = self.root.zones.zone[1].temperature.required.text
                        self.uchstatus = self.root.zones.zone[1].temperature.attrib['state']

        def __str__(self):

		if self.root.tag == 'electricity': #prepare electricity message
			self.nodemsg = '1&' # we want to submit electricity messages as node 1
                	return (self.nodemsg + 'csv=%s' % (
			','.join((str(x) for x in (self.mac,self.msgtime,self.battery,self.och1curr,self.och1day)))
	                ))

		elif self.root.tag == 'hot_water': #prepare hot water message
			self.nodemsg = '2&' # we want to submit hot water messages as node 2
			return (self.nodemsg + 'csv=%s' % (
			','.join((str(x) for x in (self.mac,self.msgtime,self.battery,self.tcurr,self.treq,self.tambi,self.hwstatus)))
                        ))

		elif self.root.tag == 'heating': #prepare heating message
                        self.nodemsg = '3&' # we want to submit heating messages as node 3
                        return (self.nodemsg + 'csv=0,%s' % (
                        ','.join((str(x) for x in (self.gbattery,self.gtcurr,self.gtreq,self.gchstatus,self.ubattery,self.utcurr,self.utreq,self.uchstatus)))
                        ))


class OwlIntuitionProtocol(DatagramProtocol):
        def __init__(self, iface=''):
                """
  Protocol for Owl Intution (Network Owl) multicast UDP.

  :param iface: Name of the interface to use to communicate with the Network Owl.  If not specified, uses the default network connection on the cost.
  :type iface: str
                """
                self.iface = iface

        def startProtocol(self):
                self.transport.joinGroup(MCAST_ADDR, self.iface)

        def datagramReceived(self, datagram, address):
                msg = OwlMessage(datagram)
		self.owlReceived(address, msg)

	def owlReceived(self, address, msg):
       	        print '%s' % (msg)
       	        curlmessage = '%s' % (msg)
		myurl = "http://localhost/emoncms/input/post.json?node="+curlmessage+"&apikey=46e787d42926d0caf0bd647747d3ac1c"
		r = requests.post(myurl)

if __name__ == '__main__':
        from twisted.internet import reactor
        from argparse import ArgumentParser
        parser = ArgumentParser()
        parser.add_argument('-i', '--iface', dest='iface', default='', help='Network interface to use for getting data.')
        
        options = parser.parse_args()
        
        protocol = OwlIntuitionProtocol(iface=options.iface)
        reactor.listenMulticast(MCAST_PORT, protocol, listenMultiple=True)
        reactor.run()

#########################################################################

Do you intend to have the blog back up and running oneday?

Perhaps you content might be better place on GitHub or maybe it could be found a home here somewhere, subject to some peer review and a nod from the powers that be, or indeed just as a forum topic that we can close to comment?