Hive SmartPlug and XBee

Hive SmartPlug Power Meter

Since finding out that Hive have restricted their older ‘Smart’ plug from being remotely switched on/off, it become a mini obsession of mine to prove that the plug is still actually capable of being remotely controlled using ZigBee, because I was convinced that is is the same hardware as its predecessor from AlertMe.

Hive only advertised the older SmartPlug as a basic ZigBee booster device to help people improve signal strength in situations where the Hive hub struggles to communicate with its thermostat in larger houses. For this reason they claim they never intend make the SmartPlug remote controllable, because this may encourage people to move the plugs to different locations which may not be optimal to be a signal booster (and, possibly more pertinent, it would discourage people from buying the newer ActivePlug).

In this post I describe my efforts to remotely control a Hive SmartPlug via a XBee and Python code. Much of this post takes inspiration from the Desert Home’s blog in which he has done much of the groundwork controlling the Hives american cousin called ‘Lowe’s Iris’ (also manufactured by AlertMe). I also referred to the Tickett’s blog post for more specific research into the AlertMe ZigBee Profile.

Resetting SmartPlug

In order to pair the SmartPlug with the XBee the first thing we need to do is disassociate it from the Hive hub. Following instructions from the British Gas website:

To reset the SmartPlug, please hold down the button on its top until the light on the front has turned on and then off again (this can take up to 15 seconds). If the SmartPlug is brand new, it might need 30 mins of charge before you can do this. At this point you then need to press the button on the top four times repeatedly, doing this once a second. The logo on the front of the SmartPlug should light up solid for a few seconds, before starting to flash. It is now ready to be added and installed on your system.

The flashing light means it is searching for an open ZigBee network to associate with.

Configuring XBee

Next, we need to configure an XBee to be able to talk with the SmartPlug. Using the XCTU configuration software program a Series 2 XBee with the following settings:

  • XBee Modem XB24-ZB, ZIGBEE Coordinator API, Version 21A7 (or better)
  • ZigBee Stack Profile (ZS): 2
  • Encryption Enable (EE): 1
  • Encryption Options (EO): 1
  • Encryption Key (KY): 5a6967426565416c6c69616e63653039
  • API Enable (AP): 2
  • API Output Mode (AO): 3
Showing SmartPlug Frames
Showing SmartPlug Frames

The above settings place the XBee into a suitable mode to be able to act as a ZigBee HA (Home Automation) co-ordinator. The key, ‘5a6967426565416c6c69616e63653039’, is the standard key for all ZigBee HA profile devices and is hex for ‘ZigBeeAlliance09’.

Following setting the above, I then switched to console mode in XCTU and opened the XBee serial connection to see if I could see any messages, and while pressing the button on the top of the SmartPlug, I could see ZigBee frames in the XCTU console – yay! we have communication…

7E 00 17 91 00 0D 6F 00 03 BB B9 F8 D8 82 02 02 00 EE C2 16 01 09 00 80 04 00 D1
7E 00 22 91 00 0D 6F 00 03 BB B9 F8 D8 82 02 02 00 F0 C2 16 01 09 C0 FB 1F 2B 45 8B 01 32 10 3C 02 C3 FF 01 00 3A
7E 00 17 91 00 0D 6F 00 03 BB B9 F8 D8 82 02 02 00 EE C2 16 01 09 C0 80 05 01 0F
7E 00 17 91 00 0D 6F 00 03 BB B9 F8 D8 82 02 02 00 EF C2 16 01 09 01 81 00 00 D2
7E 00 1E 91 00 0D 6F 00 03 BB B9 F8 D8 82 02 02 00 EF C2 16 01 09 00 82 00 00 00 00 D6 62 00 00 00 9A

Python Code

Now that we have proved connectivity between the SmartPlug and the XBee, we next need to write some code in order to send ZigBee messages to the plug. After attempting to read the ZigBee HA specification I got very confused, but after immersing myself in further documentation and YouTube videos I eventually started to understand enough about ZigBee to be able to at least attempt to progress to the next part of this project (this is where Desert Home’s blog provided a huge leap in progress, as had done much of the groundwork already).

img_8070-1.jpg
Hive SmartPlug and XBee

I was guided towards the Python XBee library (or if you prefer there is an Arduino XBee library) which provides the appropriate interface to be able to send and receive ZigBee messages.

At this point I could go off on a tangent to try and explain what I have learnt about ZigBee Profiles, Clusters and End Points but this would be a blog post in itself (which one day I may write), for now I am going to try and keep the reminder of this blog fairly high level. However, one point to note is that AlertMe (Hive) have extended the standard HA profile with their own proprietary set of messages (also known as clusters).

So, armed with the above Python library we can start to exchange messages with the SmartPlug. The following is a summarised version of my code intended to aid discussion and highlight some of the basics. The code below demonstrates simply turning the SmartPlug switch on and off.

The full code for this project can be found on my GitHub site which contains a more featured version of this script which, as well as switch control, also reads power, signal strength values as well as software version information. I would very much welcome anyone who wants to contribute or improve on this code.

#! /usr/bin/python

from xbee import XBee, ZigBee
import serial
import time
import sys
import pprint

# Serial Configuration
XBEE_PORT = '/dev/ttyUSB0'
XBEE_BAUD = 9600
serialPort = serial.Serial(XBEE_PORT, XBEE_BAUD)

# ZigBee Profile IDs
ZDP_PROFILE_ID = '\x00\x00' # ZigBee Device Profile
ALERTME_PROFILE_ID = '\xc2\x16' # AlertMe Private Profile

# ZigBee Addressing
BROADCAST_LONG = '\x00\x00\x00\x00\x00\x00\xff\xff'
BROADCAST_SHORT = '\xff\xfe'
switchLongAddr = ''
switchShortAddr = ''

def receiveMessage(message):
    pp = pprint.PrettyPrinter(indent=4)
    pp.pprint(message)
    
    global switchLongAddr; switchLongAddr = message['source_addr_long']
    global switchShortAddr; switchShortAddr = message['source_addr']
    profileId = message['profile']
    clusterId = message['cluster']

    if (profileId == ZDP_PROFILE_ID):
        if (clusterId == '\x00\x06'):
            # Active Endpoint Request
            data = '\x00\x00'
            sendMessage(switchLongAddr, switchShortAddr, '\x00', '\x00', '\x00\x05', ZDP_PROFILE_ID, data)

            # Now the Match Descriptor Response
            data = '\x00\x00\x00\x00\x01\x02'
            sendMessage(switchLongAddr, switchShortAddr, '\x00', '\x00', '\x80\x06', ZDP_PROFILE_ID, data)

        elif (clusterId == '\x00\x06'):
            # Now there are two messages directed at the hardware code
            data = '\x11\x01\xfc'
            sendMessage(switchLongAddr, switchShortAddr, '\x00', '\x02', '\x00\xf6', ALERTME_PROFILE_ID, data)

            # The switch has to receive both of these to stay joined
            data = '\x19\x01\xfa\x00\x01'
            sendMessage(switchLongAddr, switchShortAddr, '\x00', '\x02', '\x00\xf0', ALERTME_PROFILE_ID, data)

    elif (profileId == ALERTME_PROFILE_ID):
        if (clusterId == '\x00\xee'):
            clusterCmd = message['rf_data'][2]
            if (clusterCmd == '\x80'):
                if (ord(message['rf_data'][3]) & 0x01):
                    state = "ON"
                else:
                    state = "OFF"
                print "Switch State:", state

def sendMessage(destLongAddr, destShortAddr, srcEndpoint, destEndpoint, clusterId, profileId, data):
    zb.send('tx_explicit',
        dest_addr_long = destLongAddr,
        dest_addr = destShortAddr,
        src_endpoint = srcEndpoint,
        dest_endpoint = destEndpoint,
        cluster = clusterId,
        profile = profileId,
        data = data
    )

# Create ZigBee library API object, which spawns a new thread
zb = ZigBee(serialPort, callback = receiveMessage)

switchState = True
while True:
    try:
        time.sleep(1.00)

        if(switchLongAddr != ''):
            # Toggle On and Off
            switchState = not switchState
            data = '\x11\x00\x02\x00\x01' if switchState else '\x11\x00\x02\x01\x01' 
            sendMessage(switchLongAddr, switchShortAddr, '\x00', '\x02', '\x00\xee', ALERTME_PROFILE_ID, data)

        else:
            # Send out initial broadcast to provoke a response,
            # So we can then ascertain the switch address
            data = '\x12' + '\x01'
            sendMessage(BROADCAST_LONG, BROADCAST_SHORT, '\x00', '\x00', '\x00\x32', ZDP_PROFILE_ID, data)

    except KeyboardInterrupt:
        print "Keyboard Interrupt"
        break

    except:
        print "Unexpected Error:", sys.exc_info()[0], sys.exc_info()[1]
        break

# Close up shop
print "Closing Serial Port"
zb.halt()
serialPort.close()

The code above will initiate communication with the SmartPlug and then toggle the switch on and off once per second. The code also prints the data content of the received ZigBee packets to the screen. Once again I really must give credit to Desert Home as the origin of much of the above code comes from his blog. The following is a breakdown of the code along with explanations.

Setup serial connectivity with the XBee, amend the XBEE_PORT accordingly. Baud rate 9600.

# Serial Configuration
XBEE_PORT = '/dev/ttyUSB0'
XBEE_BAUD = 9600
serialPort = serial.Serial(XBEE_PORT, XBEE_BAUD)

Declare a couple of standard ZigBee Profile ID constants. Every data request in ZigBee is sent with an Application Profile ID which are 16-bit numbers and range from 0x0000 to 0x7fff for public profiles and 0xbf00 to 0xffff for manufacturer-specific profiles.

Also declared are the broadcast, switch short and long addresses. Note at this point the switchLongAddr and switchShortAddr are unknown. These are learnt later and populated following receiving a broadcast response.

# ZigBee Profile IDs
ZDP_PROFILE_ID = '\x00\x00' # ZigBee Device Profile
ALERTME_PROFILE_ID = '\xc2\x16' # AlertMe Private Profile

# ZigBee Addressing
BROADCAST_LONG = '\x00\x00\x00\x00\x00\x00\xff\xff'
BROADCAST_SHORT = '\xff\xfe'
switchLongAddr = ''
switchShortAddr = ''

Initiate ZigBee object using the Python XBee library. Take particular note of callback function, receiveMessage(), which is called each time a ZigBee packet is received.

# Create ZigBee library API object, which spawns a new thread
zb = ZigBee(serialPort, callback = receiveMessage)

The following receiveMessage() function is called each time a packet is received by the XBee. It is the job of this function to read, interpret and possibly respond to the packet.

All ZigBee devices have 2 addresses; a long 8-byte MAC address and a short 2-byte address used to reduce transmission time. One of the first tasks of the receiveMessage() function is to take note of the incoming packets long and short addresses and populate the global variables switchLongAddr & switchShortAddr accordingly.

The function then reads the Profile ID, Cluster ID and Cluster Command to determine the type of packet it is.

During initial negotiation, after transmission of certain packets, the SmartPlug expects certain appropriate packets in response. There are a couple of cases of this below, on receipt of a packet with ZDP_PROFILE_ID and clusterId 0x0006 Active Endpoint Request, the function responds back to the switch with a couple of acknowledgment packets.

We also use pp to print the packet contents to the screen for debugging.

def receiveMessage(message):
    pp = pprint.PrettyPrinter(indent=4)
    pp.pprint(message)
    
    global switchLongAddr; switchLongAddr = message['source_addr_long']
    global switchShortAddr; switchShortAddr = message['source_addr']
    profileId = message['profile']
    clusterId = message['cluster']

    if (profileId == ZDP_PROFILE_ID):
        if (clusterId == '\x00\x06'):
            # Active Endpoint Request
            data = '\x00\x00'
            sendMessage(switchLongAddr, switchShortAddr, '\x00', '\x00', '\x00\x05', ZDP_PROFILE_ID, data)

            # Now the Match Descriptor Response
            data = '\x00\x00\x00\x00\x01\x02'
            sendMessage(switchLongAddr, switchShortAddr, '\x00', '\x00', '\x80\x06', ZDP_PROFILE_ID, data)

        elif (clusterId == '\x00\x06'):
            # Now there are two messages directed at the hardware code
            data = '\x11\x01\xfc'
            sendMessage(switchLongAddr, switchShortAddr, '\x00', '\x02', '\x00\xf6', ALERTME_PROFILE_ID, data)

            # The switch has to receive both of these to stay joined
            data = '\x19\x01\xfa\x00\x01'
            sendMessage(switchLongAddr, switchShortAddr, '\x00', '\x02', '\x00\xf0', ALERTME_PROFILE_ID, data)

    elif (profileId == ALERTME_PROFILE_ID):
        if (clusterId == '\x00\xee'):
            clusterCmd = message['rf_data'][2]
            if (clusterCmd == '\x80'):
                if (ord(message['rf_data'][3]) & 0x01):
                    state = "ON"
                else:
                    state = "OFF"
                print "Switch State:", state

The sendMessage() function is nothing special, other than acting as a basic stub to the XBee libraries zb.send function.

def sendMessage(destLongAddr, destShortAddr, srcEndpoint, destEndpoint, clusterId, profileId, data):
    zb.send('tx_explicit',
        dest_addr_long = destLongAddr,
        dest_addr = destShortAddr,
        src_endpoint = srcEndpoint,
        dest_endpoint = destEndpoint,
        cluster = clusterId,
        profile = profileId,
        data = data
    )

Finally, this is where we send a message to the SmartPlug to toggle the switch relay on and off each second. We setup an infinite loop in which we first transmits a broadcast packet and waits for the switchLongAddress to be set. Once the long address is learnt we continue to loop sending a packet to the SmartPlug requesting the switch relay is turned on and off.

switchState = True
while True:
    try:
        time.sleep(1.00)

        if(switchLongAddr != ''):
            # Toggle On and Off
            switchState = not switchState
            data = '\x11\x00\x02\x00\x01' if switchState else '\x11\x00\x02\x01\x01' 
            sendMessage(switchLongAddr, switchShortAddr, '\x00', '\x02', '\x00\xee', ALERTME_PROFILE_ID, data)

        else:
            # Send out initial broadcast to provoke a response,
            # So we can then ascertain the switch address
            data = '\x12' + '\x01'
            sendMessage(BROADCAST_LONG, BROADCAST_SHORT, '\x00', '\x00', '\x00\x32', ZDP_PROFILE_ID, data)

We now know the Hive SmartPlug IS capable of being remotely switched on and off as well as power usage values read, however I don’t think Hive will oblige any time soon to enable this, especially now they have launched their newer ActivePlug.

What would be interesting to explore next is whether the same approach could be taken to control other Hive devices such as the thermostat or newer ActivePlug. Another avenue to explore is whether this could be used as a base to write a SmartThings device plugin.

Note: On occasion, usually following having had the SmartPlug associated back with the Hive hub, the SmartPlug would refuse to communicate with the XBee (or at least with the Python code) even after running through the disassociation procedure. Although the RX, TX and RST LEDs would flicker on the XBee no messages could be read. I discovered that I needed to leave both the XBee and SmartPlug on for 24-hours after which they would behave and communicate again. I do not know the reason for this, but assume there is an association time-out which needs to run its course. If anyone knows the reason for this I would love to know!

Also See:

Hacking Iris
GitHub
Desert Home Blog
Tickett’s Blog
Pypi
Stack Overflow
Matteo Blog

Add a Comment

Your email address will not be published. Required fields are marked *