diff --git a/README.md b/README.md
index f4214ab..c7f1af0 100644
--- a/README.md
+++ b/README.md
@@ -1,50 +1,117 @@
 # pyenergenie
 A python interface to the Energenie line of products
 
-This is a placeholder repo, for the development of an open source set of Python Libraries
-to access the Energenie range of power control and monitoring products.
 
-The Energenie product line uses the HopeRF radio transciever, and the OpenHEMS protocol
-from Sentec. Energenie have built a RaspberryPi add-on board that interfaces to the HopeRF
-RFM69, and allows both control and monitoring of their products from a Raspberry Pi.
+This is the beginnings of an open source library to access the Energine range of
+power control and monitoring products from within Python.
 
-This code project aims to develop an open source python module, providing access to many or all
-of the features of the OpenHEMS, HopeRF and Energenie product line.
+The Energenie product line uses the HopeRF radio transciever, and the OpenHEMS 
+protocol from Sentec. Energenie have built a RaspberryPi add-on board that 
+interfaces to the HopeRF RFM69, and allows both control and monitoring of their 
+products from a Raspberry Pi.
 
-Plans
-====
+There are some existing Python libraries for some Energenie products, but they
+do not support the full radio interface and full product range.
 
-1. Write a minimal ctypes wrapper and build process for the existing bcm2835/HopeRF software distribution
-2. Create a proof of concept demo application using the Iotic-Labs IoT infrastructure
-3. Enhance the library design to support other features such as new message types, new product types
-4. Hopefully provide a platform that could be further innovated on using ScratchGPIO
+Energenie also have a modified set of C test code based on the HopeRF test harness,
+but this does not support all products, all variants of Raspberry Pi hardware,
+or all versions of the Raspbian OS.
+
+This project aims to develop an open source Python module, providing 
+access to many or all of the features of the OpenHEMS, HopeRF and Energenie 
+product line.
+
 
 Purpose
 ====
 
-I have a whole range of plans for building demo applications using the Energenie product line in the future,
-and want to help seed further innovations using this technology in schools and in industry. To do this, I believe
-that the code needs to be more flexible, accessible to all, and better structured and documented, 
-so I plan to develop via a series of progressive releases an open source python wrapper that others can fork and further innovate with.
+This release, as of 27/09/2015, is the beginnings of this work.
+It is not representative of the final API, but it is a starting point for me to
+start experimenting with ideas and testing out reliability, with a view to using
+these products to integrate into an Internet of Things solution provided by
+Iotic-Labs Ltd.
 
-Notes
+With it, you can receive monitor payloads from an Energenie MiHome Adaptor Plus,
+directly within Python programs. This type of plug can be used for energy monitoring
+and also for relay control of the socket.
+
+I've tried to make this a 'zero install' and 'zero configuration' experience.
+In theory (at least) you should be able to download the zip or git-clone,
+plug in your Energenie radio, plug in your MiHome Adapter Plus, and run the code
+to see data coming back.
+
+
+Getting Going
 ====
 
-Sharing of SPI/GPIO with this library will be an interesting challenge,
-often it is useful to also use other GPIO's and to use other SPI devices
-with an alternative chip select. The bcm2835 library doesn't seem to be
-provided or wrapped in a way conjucive to sharing. This might be addressed
-in the second pass of coding, it's too much to chew on in the first pass.
+1. Plug in your ENER314-RT-VER01 board from energine onto the 26 pin connector of
+your Raspberry Pi. At the moment I have only tested this with a Raspberry Pi B,
+although there is no reason why it should not work with any of the models currently
+available on the market. The underlying GPIO and SPI has been tested in other
+projects on a Pi2 for example.
 
-I'm hoping to publish a low level C API to the devices that others can code
-to via foreign function interfaces, and possibly a high level API that
-implements basic device classes, with higher level abstractions written
-in the python.
+2. Use the Download As Zip link to the right, and unzip the files onto your
+Raspberry Pi. 
 
-Getting perfect reuse for all on the first pass is going to be impossible.
+3. unzip the software
+
+    unzip pyenergenie-master.zip
+    cd pyenergenie-master
+    cd assets/src
+
+4. run the monitor test program
+
+    sudo python monitor.py
+
+After a few seconds, you should see some packet dumps appearing on the screen.
+The last few bytes will be 0x73 0x01 0x01 or 0x73 0x01 0x00 and these indicate
+the switch state of the plug. Press the button on the front of the plug to
+turn the switch on and off, and you should see the 0x01 change to 0x00 and
+back again.
+
+If it crashes, it sometimes leaves the radio in an indeterminite state, remove
+and replace the radio board and it should reset it (but see notes below about this).
+
+
+Plans
+====
+
+1. Add RESET support - the radio sometimes gets into an unknown and unrecoverable
+state and I have to remove the board to reset the radio. There might be a RESET
+line or a RESET command that can be sent at startup to solve this.
+
+2. Write an OpenHEMS decoder to decode the messages for friendly display. I will
+probably decode the hex buffer into a pydict, and then write a pydict to text
+formatter. This will expose the whole of OpenHEMS in a really nice Python structure
+to improve further innovation within Python.
+
+3. Write an OpenHEMS encoder to encode friendly messages. I will probably
+take a pydict with header and records in it and encode into a buffer that is
+then transmitted via the radio interface. As above, this will expose message
+creation in a really nice Python structure to improve further innovation within
+Python.
+
+4. Construct commands for switch-on and switch-off, and test sending these to a
+specific sensorid.
+
+5. Write a discovery service that sends a monitor command, then collects all
+the receive messages and builds an internal dictionary of devices that respond.
+I will probably at this point build a Python object for each device that responds,
+and this object will be a proxy that can be used to monitor and control that device,
+thus allowing any number of devices to be monitored and controlled in a 'Pythonic'
+way.
+
+6. Push a fair amount of the radio interface and some of OpenHEMS back down into
+a C library that implements the same interface as what we have at this point in the
+Python. Write a ctypes wrapper around this, so that the identical Python internal
+API is presented. The idea being that the first pass of Python coding defines the
+API we want to use, and the second pass turns this into a single library that
+does everything, exposed to Python via ctypes, but linkable to other applications
+and languages too.
+
 
 David Whale
 
 @whaleygeek
 
-July 2015
+September 2015
diff --git a/src/energenie/OpenHEMS.py b/src/energenie/OpenHEMS.py
new file mode 100644
index 0000000..6143d0d
--- /dev/null
+++ b/src/energenie/OpenHEMS.py
@@ -0,0 +1,229 @@
+# OpenHEMS.py  27/09/2015  D.J.Whale
+#
+# Implement OpenHEMS message encoding and decoding
+
+import crypto
+
+# report has bit 7 clear
+# command has bit 7 set
+
+PARAM_ALARM           = 0x21
+PARAM_DEBUG_OUTPUT    = 0x2D
+PARAM_IDENTIFY        = 0x3F
+PARAM_SOURCE_SELECTOR = 0x40 # command only
+PARAM_WATER_DETECTOR  = 0x41
+PARAM_GLASS_BREAKAGE  = 0x42
+PARAM_CLOSURES        = 0x43
+PARAM_DOOR_BELL       = 0x44
+PARAM_ENERGY          = 0x45
+PARAM_FALL_SENSOR     = 0x46
+PARAM_GAS_VOLUME      = 0x47
+PARAM_AIR_PRESSURE    = 0x48
+PARAM_ILLUMINANCE     = 0x49
+PARAM_LEVEL           = 0x4C
+PARAM_RAINFALL        = 0x4D
+PARAM_APPARENT_POWER  = 0x50
+PARAM_POWER_FACTOR    = 0x51
+PARAM_REPORT_PERIOD   = 0x52
+PARAM_SMOKE_DETECTOR  = 0x53
+PARAM_TIME_AND_DATE   = 0x54
+PARAM_VIBRATION       = 0x56
+PARAM_WATER_VOLUME    = 0x57
+PARAM_WIND_SPEED      = 0x58
+PARAM_GAS_PRESSURE    = 0x61
+PARAM_BATTERY_LEVEL   = 0x62
+PARAM_CO_DETECTOR     = 0x63
+PARAM_DOOR_SENSOR     = 0x64
+PARAM_EMERGENCY       = 0x65
+PARAM_FREQUENCY       = 0x66
+PARAM_GAS_FLOW_RATE   = 0x67
+PARAM_CURRENT         = 0x69
+PARAM_JOIN            = 0x6A
+PARAM_LIGHT_LEVEL     = 0x6C
+PARAM_MOTION_DETECTOR = 0x6D
+PARAM_OCCUPANCY       = 0x6F
+PARAM_REAL_POWER      = 0x70
+PARAM_REACTIVE_POWER  = 0x71
+PARAM_ROTATION_SPEED  = 0x72
+PARAM_SWITCH_STATE    = 0x73
+PARAM_TEMPERATURE     = 0x74
+PARAM_VOLTAGE         = 0x76
+PARAM_WATER_FLOW_RATE = 0x77
+PARAM_WATER_PRESSURE  = 0x78
+
+PARAM_TEST            = 0xAA
+
+
+
+crypt_pid = None
+crypt_pip = None
+
+def init(pid, pip):
+    global crypt_pid, crypt_pip
+    crypt_pid = pid
+    crypt_pip = pip
+
+
+def warning(msg):
+    print("warning:" + str(msg))
+
+
+#TODO decode OpenHEMS message payload structure
+#TODO decrypt OpenHEMS message payload
+#TODO check the CRC is correct
+
+"""
+		case S_MSGLEN:							// Read message length
+		case S_MANUFACT_ID:						// Read manufacturer identifier
+		case S_PRODUCT_ID:						// Read product identifier
+		case S_ENCRYPTPIP:						// Read encryption pip
+		case S_SENSORID:						// Read sensor ID
+	    /******************* start reading RECORDS  ********************/
+		case S_DATA_PARAMID:					// Read record parameter identifier
+			msgPtr->paramId = msgPtr->value & 0x7F;
+			temp = getIdName(msgPtr->paramId);
+			printf(" %s=", temp);
+			if (msgPtr->paramId == 0)			// Parameter identifier CRC. Go to CRC
+			{
+				msgPtr->state = S_CRC;
+				msgPtr->recordBytesToRead = SIZE_CRC;
+			}
+			else
+			{
+				msgPtr->state = S_DATA_TYPEDESC;
+				msgPtr->recordBytesToRead = SIZE_DATA_TYPEDESC;
+			}
+			if (strcmp(temp, "Unknown") == 0)	// Unknown parameter, finish fetching message
+				msgPtr->state = S_FINISH;
+			break;
+		case S_DATA_TYPEDESC:					// Read record type description
+			if ((msgPtr->value & 0x0F) == 0)	// No more data to read in that record
+			{
+				msgPtr->state = S_DATA_PARAMID;
+				msgPtr->recordBytesToRead = SIZE_DATA_PARAMID;
+			}
+			else
+			{
+				msgPtr->state = S_DATA_VAL;
+				msgPtr->recordBytesToRead = msgPtr->value & 0x0F;
+			}
+			msgPtr->type = msgPtr->value;
+			break;
+		case S_DATA_VAL:						// Read record data
+			temp = getValString(msgPtr->value, msgPtr->type >> 4, msgPtr->recordBytesToRead);
+			printf("%s", temp);
+			msgPtr->state = S_DATA_PARAMID;
+			msgPtr->recordBytesToRead = SIZE_DATA_PARAMID;
+			if (strcmp(temp, "Reserved") == 0)
+				msgPtr->state = S_FINISH;
+			break;
+	    /******************* finish reading RECORDS  ********************/
+		case S_CRC:								// Check CRC
+			msgPtr->state = S_FINISH;
+			if ((int16_t)msgPtr->value == crc(msgPtr->buf + NON_CRC, msgPtr->bufCnt - NON_CRC - SIZE_CRC))
+			{
+				printf("OK\n");
+			}
+			else
+			{
+				printf("FAIL expVal=%04x, pip=%04x, val=%04x\n", (int16_t)msgPtr->value, msgPtr->pip, crc(msgPtr->buf + NON_CRC, msgPtr->bufCnt - NON_CRC - SIZE_CRC));
+			}
+			break;
+"""
+
+#TODO if can't decode message throw an exception
+def decode(payload):
+    buffer = ""
+    length = payload[0]
+    if length+1 != len(payload):
+        warning("rx payload length mismatch")
+
+    mfrId = payload[1]
+    productId = payload[2]
+
+    buffer += "len:" + str(length) + " "
+    buffer += "mfr:" + hex(mfrId) + " "
+    buffer += "prod:" + hex(productId) + " "
+
+    pip = (payload[3]<<8) + payload[4]
+    crypto.init(crypt_pid, pip)
+    crypto.cryptPayload(payload, 5, len(payload)-5)
+
+    for n in payload[5:]:
+        buffer += hex(n) + " "
+
+    #TODO check CRC matches
+    return buffer
+
+
+#----- MESSAGE ENCODER --------------------------------------------------------
+#
+# Encodes a message using the OpenHEMS message payload structure
+
+# R1 message product id 0x02 monitor and control (in switching program?)
+# C1 message product id 0x01 monitor only (in listening program)
+
+
+#TODO change this so it returns a pydict
+#write an encoder that turns the pydict into a buffer for the radio
+
+def make_monitor():
+    payload = [
+        7 + 3 + 3,                  # payload remaining length (header+records+footer)
+        0x04,                       # manufacturer id = Energenie
+        0x01,                       # product id = 0x01=C1(monitor)  0x02=R1(monitor+control)
+        0x01,                       # reserved1 (cryptSeedMSB)
+        0x00,                       # reserved2 (cryptSeedLSB)
+        # from here up until the NUL is crc'd
+        # from here up to and including the CRC is crypted
+        0xFF,                       # sensorIdHigh broadcast
+        0xFF,                       # sensorIdMid broadcast
+        0xFF,                       # sensorIdLow broadcast
+        # RECORDS
+        PARAM_SWITCH_STATE | 0x80,  # set switch state
+        0x01,                       # type/length
+        0x00,                       # value off
+        0x00                        # NUL
+    ]
+    # Calculate and append the CRC bytes
+    crc = calcCRC(payload, 5, len(payload)-5)
+    payload.append((crc >> 8) & 0xFF) # MSB, big-endian
+    payload.append(crc & 0xFF)        # LSB
+
+    crypto.init(crypt_pid, crypt_pip)
+    crypto.cryptPayload(payload, 5, len(payload)-5) # including CRC
+    return payload
+
+
+#----- CRC CALCULATION --------------------------------------------------------
+
+#int16_t crc(uint8_t const mes[], unsigned char siz)
+#{
+#	uint16_t rem = 0;
+#	unsigned char byte, bit;
+#
+#	for (byte = 0; byte < siz; ++byte)
+#	{
+#		rem ^= (mes[byte] << 8);
+#		for (bit = 8; bit > 0; --bit)
+#		{
+#			rem = ((rem & (1 << 15)) ? ((rem << 1) ^ 0x1021) : (rem << 1));
+#		}
+#	}
+#	return rem;
+#}
+
+def calcCRC(payload, start, length):
+    rem = 0
+    for b in payload[start:start+length]:
+        rem ^= (b<<8)
+        for bit in range(8):
+            if rem & (1<<15) != 0:
+                # bit is set
+                rem = ((rem<<1) ^ 0x1021) & 0xFFFF # always maintain U16
+            else:
+                # bit is clear
+                rem = (rem<<1) & 0xFFFF # always maintain U16
+    return rem
+
+# END
diff --git a/src/energenie/__init__.py b/src/energenie/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/src/energenie/__init__.py
diff --git a/src/energenie/build b/src/energenie/build
new file mode 100755
index 0000000..a476cf0
--- /dev/null
+++ b/src/energenie/build
@@ -0,0 +1,21 @@
+#! /bin/bash
+
+
+# build gpio_test
+gcc gpio_test.c gpio.c
+mv a.out gpio_test
+chmod u+x gpio_test
+
+
+# build spi_test
+gcc spi_test.c spi.c gpio.c
+mv a.out spi_test
+chmod u+x spi_test
+
+
+# build spi .so library on Raspberry Pi
+gcc -Wall -shared -o spi_rpi.so -fPIC spi.c gpio.c
+#nm -D spi.so
+
+
+
diff --git a/src/energenie/crypto.py b/src/energenie/crypto.py
new file mode 100644
index 0000000..0c1fa04
--- /dev/null
+++ b/src/energenie/crypto.py
@@ -0,0 +1,53 @@
+# crypto.py  27/09/2015  D.J.Whale
+#
+# Crypto engine for OpenHEMS, including crc calculation
+
+
+
+ran = None
+
+#static uint16_t ran;
+
+#void seed(uint8_t pid, uint16_t pip)
+#{
+#	ran = ((((uint16_t) pid) << 8) ^ pip);
+#}
+
+
+def init(pid, pip):
+    """Initialise the crypto engine state variables"""
+    global ran
+    ran = (((pid&0xFF)<<8) ^ pip) & 0xFFFF # maintain U16
+
+
+#uint8_t crypt(uint8_t dat)
+#{
+#	unsigned char i; //(u8)
+
+#	for (i = 0; i < 5; ++i)
+#	{
+#		ran = (ran & 1) ? ((ran >> 1) ^ 62965U) : (ran >> 1);
+#	}
+#	return (uint8_t)(ran ^ dat ^ 90U);
+#}
+
+def cryptByte(data):
+    """crypt a byte of data and update the crypto engine state variable"""
+    global ran
+    for i in range(5):
+        if (ran&0x01) != 0: # bit0
+            # bit0 set
+            ran = ((ran>>1) ^ 62965) & 0xFFFF # maintain U16
+        else:
+            # bit0 clear
+            ran = ran >> 1
+
+    return (ran ^ data ^ 90) & 0xFF
+
+
+def cryptPayload(payload, start, length):
+    """Encrypt a range of bytes in place by modifying those payload bytes"""
+    for i in range(start, start+length):
+        payload[i] = cryptByte(payload[i])
+
+# END
diff --git a/src/energenie/gpio.c b/src/energenie/gpio.c
new file mode 100644
index 0000000..7d9a144
--- /dev/null
+++ b/src/energenie/gpio.c
@@ -0,0 +1,182 @@
+/* gpio.c  D.J.Whale  8/07/2014
+ * 
+ * A very simple interface to the GPIO port on the Raspberry Pi.
+ */
+
+/***** INCLUDES *****/
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdint.h>
+#include <fcntl.h>
+#include <sys/mman.h>
+#include <unistd.h>
+#include <time.h>
+
+#include "gpio.h"
+
+
+/***** CONFIGURATION *****/
+
+/* uncomment to make this a simulated driver */
+//#define GPIO_SIMULATED
+
+
+/***** CONSTANTS *****/
+
+#define BCM2708_PERI_BASE        0x20000000
+//#define GPIO_BASE                (BCM2708_PERI_BASE + 0x200000) /* GPIO controller */
+#define GPIO_BASE_OFFSET           0x200000
+
+#define PAGE_SIZE  (4*1024)
+#define BLOCK_SIZE (4*1024)
+
+
+/***** VARIABLES *****/
+
+static int  mem_fd;
+static void *gpio_map;
+
+static volatile unsigned *gpio;
+
+
+/****** MACROS *****/
+
+#define INP_GPIO(g) *(gpio+((g)/10)) &= ~(7<<(((g)%10)*3))
+#define OUT_GPIO(g) *(gpio+((g)/10)) |=  (1<<(((g)%10)*3))
+#define SET_GPIO_ALT(g,a) *(gpio+(((g)/10))) |= (((a)<=3?(a)+4:(a)==4?3:2)<<(((g)%10)*3))
+
+#define GPIO_SET *(gpio+7)  // sets   bits which are 1 ignores bits which are 0
+#define GPIO_CLR *(gpio+10) // clears bits which are 1 ignores bits which are 0
+
+#define GPIO_READ(g) ((*(gpio+13)&(1<<g)) != 0)
+
+#define GPIO_HIGH(g) GPIO_SET = (1<<(g))
+#define GPIO_LOW(g)  GPIO_CLR = (1<<(g))
+
+
+void gpio_init()
+{
+#ifndef GPIO_SIMULATED
+
+   uint32_t peri_base = BCM2708_PERI_BASE; /* default if device tree not found */
+   uint32_t gpio_base;
+   FILE* fp;
+
+   /* for RPi2, get peri-base from device tree */
+   if ((fp = fopen("/proc/device-tree/soc/ranges", "rb")) != NULL)
+   {
+      unsigned char buf[4];
+
+      fseek(fp, 4, SEEK_SET);
+      if (fread(buf, 1, sizeof(buf), fp) == sizeof(buf))
+      {
+         peri_base = buf[0]<<24 | buf[1]<<16 | buf[2]<<8 | buf[3];
+      }
+      fclose(fp);
+   }
+
+   gpio_base = peri_base + GPIO_BASE_OFFSET;
+
+
+   /* open /dev/mem */
+   if ((mem_fd = open("/dev/mem", O_RDWR|O_SYNC) ) < 0) 
+   {
+      printf("can't open /dev/mem \n");
+      exit(-1); //TODO return a result code
+   }
+
+   /* mmap GPIO */
+   gpio_map = mmap(
+      NULL,             //Any adddress in our space will do
+      BLOCK_SIZE,       //Map length
+      PROT_READ|PROT_WRITE,// Enable reading & writting to mapped memory
+      MAP_SHARED,       //Shared with other processes
+      mem_fd,           //File to map
+      gpio_base         //Offset to GPIO peripheral
+   );
+
+   close(mem_fd); //No need to keep mem_fd open after mmap
+
+   if (gpio_map == MAP_FAILED) 
+   {
+      printf("mmap error %d\n", (int)gpio_map);//errno also set!
+      exit(-1); //TODO return a result code
+   }
+
+   // Always use volatile pointer!
+   gpio = (volatile unsigned *)gpio_map;
+#endif
+}
+
+
+void gpio_setin(int g)
+{
+#ifndef GPIO_SIMULATED
+  INP_GPIO(g);
+#else
+  printf("gpio:in:%d\n", g);
+#endif
+}
+
+
+void gpio_setout(int g)
+{
+#ifndef GPIO_SIMULATED
+  /* always INP_GPIO before OUT_GPIO */
+  //INP_GPIO(g); #### this causes glitching
+  OUT_GPIO(g);
+#else
+  printf("gpio:out:%d\n", g);
+#endif
+}
+
+
+void gpio_high(int g)
+{
+#ifndef GPIO_SIMULATED
+  GPIO_HIGH(g);
+#else
+  printf("gpio:high:%d\n", g);
+#endif
+}
+
+
+void gpio_low(int g)
+{
+#ifndef GPIO_SIMULATED
+  GPIO_LOW(g);
+#else
+  printf("gpio:low:%d\n", g);
+#endif
+}
+
+
+void gpio_write(int g, int v)
+{
+#ifndef GPIO_SIMULATED
+  if (v != 0)
+  {
+    GPIO_HIGH(g);
+  }
+  else
+  {
+    GPIO_LOW(g);
+  }
+#else
+  printf("gpio:write:%d=%d\n", g, v);
+#endif
+}
+
+
+int  gpio_read(int g)
+{
+#ifndef GPIO_SIMULATED
+  return GPIO_READ(g);
+#else
+  return 0; /* always low in simulation */
+#endif
+}
+
+
+/***** END OF FILE *****/
diff --git a/src/energenie/gpio.h b/src/energenie/gpio.h
new file mode 100644
index 0000000..7437e9a
--- /dev/null
+++ b/src/energenie/gpio.h
@@ -0,0 +1,23 @@
+/* gpio.h  D.J.Whale  8/07/2014
+ * 
+ * A very simple interface to the GPIO port on the Raspberry Pi.
+ */
+
+#ifndef GPIO_H
+#define GPIO_H
+
+
+/***** FUNCTION PROTOTYPES *****/
+
+void gpio_init(void);
+void gpio_setin(int g);
+void gpio_setout(int g);
+void gpio_high(int g);
+void gpio_low(int g);
+void gpio_write(int g, int v);
+int  gpio_read(int g);
+//TODO probably need gpio_finished() to unmmap() the memory region and clean up the peripheral?
+#endif
+
+/***** END OF FILE *****/
+
diff --git a/src/energenie/gpio_test.c b/src/energenie/gpio_test.c
new file mode 100644
index 0000000..a10a71b
--- /dev/null
+++ b/src/energenie/gpio_test.c
@@ -0,0 +1,40 @@
+/* gpio_test.c  30/07/2015  D.J.Whale
+ * Simple test that GPIO 2/3 work
+ */
+
+#include <stdio.h>
+#include <time.h>
+
+#include "gpio.h"
+
+static void delay(struct timespec time)
+{
+  nanosleep(&time, NULL);
+}
+
+static struct timespec delay_1sec = {1, 0};
+
+
+void main(void)
+{
+  int i;
+
+  gpio_init();
+  gpio_setout(2);
+  gpio_setout(3);
+
+  for (i=0; i<10; i++)
+  {
+    puts("GPIO 2");
+    gpio_write(2, 1);
+    delay(delay_1sec);
+    gpio_write(2, 0);
+    delay(delay_1sec);
+
+    puts("GPIO 3");
+    gpio_write(3, 1);
+    delay(delay_1sec);
+    gpio_write(3, 0);
+    delay(delay_1sec);
+  }
+}
diff --git a/src/energenie/radio.py b/src/energenie/radio.py
new file mode 100644
index 0000000..9cfa544
--- /dev/null
+++ b/src/energenie/radio.py
@@ -0,0 +1,317 @@
+# test1.py  26/09/2015  D.J.Whale
+#
+# Simple low level test of the HopeRF interface
+# Uses direct SPI commands to exercise the interface.
+#
+# Receives and dumps payload buffers.
+#
+# Eventually a lot of this will be pushed into a separate module,
+# and then pushed back into C once it is proved working.
+
+import spi
+
+def warning(msg):
+    print("warning:" + str(msg))
+
+def trace(msg):
+    print(str(msg))
+
+
+#----- REGISTER ACCESS --------------------------------------------------------
+
+def HRF_writereg(addr, data):
+    """Write an 8 bit value to a register"""
+    buf = [addr | MASK_WRITE_DATA, data]
+    spi.select()
+    spi.frame(buf)
+    spi.deselect()
+
+
+def HRF_readreg(addr):
+    """Read an 8 bit value from a register"""
+    buf = [addr, 0x00]
+    spi.select()
+    res = spi.frame(buf)
+    spi.deselect()
+    #print(hex(res[1]))
+    return res[1] # all registers are 8 bit
+
+
+def HRF_writefifo_burst(buf):
+    """Write all bytes in buf to the payload FIFO, in a single burst"""
+    spi.select()
+    buf.insert(0, ADDR_FIFO | MASK_WRITE_DATA)
+    spi.frame(buf)
+    spi.deselect()
+
+
+def HRF_readfifo_burst():
+    """Read bytes from the payload FIFO using burst read"""
+    #first byte read is the length in remaining bytes
+    buf = []
+    spi.select()
+    spi.frame([ADDR_FIFO])
+    count = 1 # read at least the length byte
+    while count > 0:
+        rx = spi.frame([ADDR_FIFO])
+        data = rx[0]
+        if len(buf) == 0:
+            count = data
+        else:
+            count -= 1
+        buf.append(data)
+    spi.deselect()
+    trace("readfifo:" + str(buf))
+    return buf
+
+
+def HRF_checkreg(addr, mask, value):
+    """Check to see if a register matches a specific value or not"""
+    regval = HRF_readreg(addr)
+    #print("addr %d mask %d wanted %d actual %d" % (addr,mask,value,regval))
+    return (regval & mask) == value
+
+
+def HRF_pollreg(addr, mask, value):
+    """Poll a register until it meet some criteria"""
+    while not HRF_checkreg(addr, mask, value):
+        pass
+
+
+#----- HRF REGISTER PROTOCOL --------------------------------------------------
+
+# HopeRF register addresses
+# Precise register description can be found on: 
+# www.hoperf.com/upload/rf/RFM69W-V1.3.pdf
+# on page 63 - 74
+
+ADDR_FIFO			= 0x00
+ADDR_OPMODE			= 0x01
+ADDR_REGDATAMODUL	= 0x02
+ADDR_BITRATEMSB		= 0x03
+ADDR_BITRATELSB		= 0x04
+ADDR_FDEVMSB		= 0x05
+ADDR_FDEVLSB		= 0x06
+ADDR_FRMSB			= 0x07
+ADDR_FRMID			= 0x08
+ADDR_FRLSB			= 0x09
+ADDR_AFCCTRL		= 0x0B
+ADDR_LNA			= 0x18
+ADDR_RXBW			= 0x19
+ADDR_AFCFEI			= 0x1E
+ADDR_IRQFLAGS1		= 0x27
+ADDR_IRQFLAGS2		= 0x28
+ADDR_RSSITHRESH		= 0x29
+ADDR_PREAMBLELSB	= 0x2D
+ADDR_SYNCCONFIG		= 0x2E
+ADDR_SYNCVALUE1		= 0x2F
+ADDR_SYNCVALUE2		= 0x30
+ADDR_SYNCVALUE3		= 0x31
+ADDR_SYNCVALUE4		= 0x32
+ADDR_PACKETCONFIG1	= 0x37
+ADDR_PAYLOADLEN		= 0x38
+ADDR_NODEADDRESS	= 0x39
+ADDR_FIFOTHRESH		= 0x3C
+
+# HopeRF masks to set and clear bits
+MASK_REGDATAMODUL_OOK	= 0x08
+MASK_REGDATAMODUL_FSK	= 0x00
+MASK_WRITE_DATA		    = 0x80
+MASK_MODEREADY		    = 0x80
+MASK_FIFONOTEMPTY	    = 0x40
+MASK_FIFOLEVEL		    = 0x20
+MASK_FIFOOVERRUN	    = 0x10
+MASK_PACKETSENT		    = 0x08
+MASK_TXREADY		    = 0x20
+MASK_PACKETMODE		    = 0x60
+MASK_MODULATION		    = 0x18
+MASK_PAYLOADRDY		    = 0x04
+
+MODE_STANDBY 			= 0x04	# Standby
+MODE_TRANSMITER 		= 0x0C	# Transmiter
+MODE_RECEIVER 			= 0x10	# Receiver
+VAL_REGDATAMODUL_FSK	= 0x00	# Modulation scheme FSK
+VAL_REGDATAMODUL_OOK	= 0x08	# Modulation scheme OOK
+VAL_FDEVMSB30			= 0x01	# frequency deviation 5kHz 0x0052 -> 30kHz 0x01EC
+VAL_FDEVLSB30			= 0xEC	# frequency deviation 5kHz 0x0052 -> 30kHz 0x01EC
+VAL_FRMSB434			= 0x6C	# carrier freq -> 434.3MHz 0x6C9333
+VAL_FRMID434			= 0x93	# carrier freq -> 434.3MHz 0x6C9333
+VAL_FRLSB434			= 0x33	# carrier freq -> 434.3MHz 0x6C9333
+VAL_FRMSB433			= 0x6C	# carrier freq -> 433.92MHz 0x6C7AE1
+VAL_FRMID433			= 0x7A	# carrier freq -> 433.92MHz 0x6C7AE1
+VAL_FRLSB433			= 0xE1	# carrier freq -> 433.92MHz 0x6C7AE1
+VAL_AFCCTRLS			= 0x00	# standard AFC routine
+VAL_AFCCTRLI			= 0x20	# improved AFC routine
+VAL_LNA50				= 0x08	# LNA input impedance 50 ohms
+VAL_LNA50G				= 0x0E	# LNA input impedance 50 ohms, LNA gain -> 48db
+VAL_LNA200				= 0x88	# LNA input impedance 200 ohms
+VAL_RXBW60				= 0x43	# channel filter bandwidth 10kHz -> 60kHz  page:26
+VAL_RXBW120				= 0x41	# channel filter bandwidth 120kHz
+VAL_AFCFEIRX			= 0x04	# AFC is performed each time RX mode is entered
+VAL_RSSITHRESH220		= 0xDC	# RSSI threshold 0xE4 -> 0xDC (220)
+VAL_PREAMBLELSB3		= 0x03	# preamble size LSB 3
+VAL_PREAMBLELSB5		= 0x05	# preamble size LSB 5
+VAL_SYNCCONFIG2			= 0x88	# Size of the Synch word = 2 (SyncSize + 1)
+VAL_SYNCCONFIG4			= 0x98	# Size of the Synch word = 4 (SyncSize + 1)
+VAL_SYNCVALUE1FSK		= 0x2D	# 1st byte of Sync word
+VAL_SYNCVALUE2FSK		= 0xD4	# 2nd byte of Sync word
+VAL_SYNCVALUE1OOK		= 0x80	# 1nd byte of Sync word
+VAL_PACKETCONFIG1FSK	= 0xA2	# Variable length, Manchester coding, Addr must match NodeAddress
+VAL_PACKETCONFIG1FSKNO	= 0xA0	# Variable length, Manchester coding
+VAL_PACKETCONFIG1OOK	= 0		# Fixed length, no Manchester coding
+VAL_PAYLOADLEN255		= 0xFF	# max Length in RX, not used in Tx
+VAL_PAYLOADLEN66		= 66	# max Length in RX, not used in Tx
+VAL_PAYLOADLEN_OOK		= (13 + 8 * 17)	# Payload Length
+VAL_NODEADDRESS01		= 0x01	# Node address used in address filtering
+VAL_NODEADDRESS04		= 0x04	# Node address used in address filtering
+VAL_FIFOTHRESH1			= 0x81	# Condition to start packet transmission: at least one byte in FIFO
+VAL_FIFOTHRESH30		= 0x1E	# Condition to start packet transmission: wait for 30 bytes in FIFO
+
+config_FSK = [
+    [ADDR_REGDATAMODUL,     VAL_REGDATAMODUL_FSK],	    # modulation scheme FSK
+    [ADDR_FDEVMSB, 		    VAL_FDEVMSB30],  			# frequency deviation 5kHz 0x0052 -> 30kHz 0x01EC
+    [ADDR_FDEVLSB, 		    VAL_FDEVLSB30],			    # frequency deviation 5kHz 0x0052 -> 30kHz 0x01EC
+    [ADDR_FRMSB, 		    VAL_FRMSB434],			    # carrier freq -> 434.3MHz 0x6C9333
+    [ADDR_FRMID, 		    VAL_FRMID434],			    # carrier freq -> 434.3MHz 0x6C9333
+    [ADDR_FRLSB, 		    VAL_FRLSB434],			    # carrier freq -> 434.3MHz 0x6C9333
+    [ADDR_AFCCTRL, 		    VAL_AFCCTRLS],			    # standard AFC routine
+    [ADDR_LNA, 			    VAL_LNA50],				    # 200ohms, gain by AGC loop -> 50ohms
+    [ADDR_RXBW, 		    VAL_RXBW60],				# channel filter bandwidth 10kHz -> 60kHz  page:26
+    [ADDR_BITRATEMSB, 	    0x1A],					    # 4800b/s
+    [ADDR_BITRATELSB, 	    0x0B],					    # 4800b/s
+    #[ADDR_AFCFEI, 		    VAL_AFCFEIRX],		        # AFC is performed each time rx mode is entered
+    #[ADDR_RSSITHRESH, 	    VAL_RSSITHRESH220],	        # RSSI threshold 0xE4 -> 0xDC (220)
+    #[ADDR_PREAMBLELSB, 	VAL_PREAMBLELSB5],	        # preamble size LSB set to 5
+    [ADDR_SYNCCONFIG, 	    VAL_SYNCCONFIG2],		    # Size of the Synch word = 2 (SyncSize + 1)
+    [ADDR_SYNCVALUE1, 	    VAL_SYNCVALUE1FSK],		    # 1st byte of Sync word
+    [ADDR_SYNCVALUE2, 	    VAL_SYNCVALUE2FSK],		    # 2nd byte of Sync word
+    #[ADDR_PACKETCONFIG1,   VAL_PACKETCONFIG1FSK],	    # Variable length, Manchester coding, Addr must match NodeAddress
+    [ADDR_PACKETCONFIG1,    VAL_PACKETCONFIG1FSKNO],	# Variable length, Manchester coding
+    [ADDR_PAYLOADLEN, 	    VAL_PAYLOADLEN66],		    # max Length in RX, not used in Tx
+    #[ADDR_NODEADDRESS, 	VAL_NODEADDRESS01],		    # Node address used in address filtering
+    [ADDR_NODEADDRESS, 	    0x06],		                # Node address used in address filtering
+    [ADDR_FIFOTHRESH, 	    VAL_FIFOTHRESH1],		    # Condition to start packet transmission: at least one byte in FIFO
+    [ADDR_OPMODE, 		    MODE_RECEIVER]			    # Operating mode to Receiver
+]
+
+
+def HRF_wait_ready():
+    """Wait for HRF to be ready after last command"""
+    HRF_pollreg(ADDR_IRQFLAGS1, MASK_MODEREADY, MASK_MODEREADY)
+
+
+def HRF_wait_txready():
+    """Wait for HRF to be ready and ready for tx, after last command"""
+    trace("waiting for transmit ready...")
+    HRF_pollreg(ADDR_IRQFLAGS1, MASK_MODEREADY|MASK_TXREADY, MASK_MODEREADY|MASK_TXREADY)
+    trace("transmit ready")
+
+
+def HRF_config_FSK():
+    """Configure HRF for FSK modulation"""
+    for cmd in config_FSK:
+        HRF_writereg(cmd[0], cmd[1])
+        HRF_wait_ready()
+
+
+def HRF_change_mode(mode):
+    HRF_writereg(ADDR_OPMODE, mode)
+
+
+def HRF_clear_fifo():
+    """Clear any data in the HRF payload FIFO by reading until empty"""
+    while (HRF_readreg(ADDR_IRQFLAGS2) & MASK_FIFONOTEMPTY) == MASK_FIFONOTEMPTY:
+        HRF_readreg(ADDR_FIFO)
+
+
+def HRF_check_payload():
+    """Check if there is a payload in the FIFO waiting to be processed"""
+    irqflags1 = HRF_readreg(ADDR_IRQFLAGS1)
+    irqflags2 = HRF_readreg(ADDR_IRQFLAGS2)
+    print("irq1 %s   irq2 %s" % (hex(irqflags1), hex(irqflags2)))
+
+    return (irqflags2 & MASK_PAYLOADRDY) == MASK_PAYLOADRDY
+
+
+def HRF_receive_payload():
+    """Receive the whole payload"""
+    return HRF_readfifo_burst()
+
+
+def HRF_send_payload(payload):
+    trace("send_payload")
+    dumpPayloadAsHex(payload)
+    HRF_writefifo_burst(payload)
+    trace("  waiting for sent...")
+    HRF_pollreg(ADDR_IRQFLAGS2, MASK_PACKETSENT, MASK_PACKETSENT)
+    trace("  sent")
+    reg = HRF_readreg(ADDR_IRQFLAGS2)
+    trace("  irqflags2=%s" % hex(reg))
+    if ((reg & MASK_FIFONOTEMPTY) != 0) or ((reg & MASK_FIFOOVERRUN) != 0):
+        warning("Failed to send payload to HRF")
+
+def dumpPayloadAsHex(payload):
+    length = payload[0]
+    print(hex(length))
+    if length+1 != len(payload):
+        print("warning length byte mismatch actual:%d inbuf:%d" % (len(payload), length))
+
+    for i in range(1,length+1):
+        print("[%d] = %s" % (i, hex(payload[i])))
+
+
+
+
+
+#----- USER API ---------------------------------------------------------------
+#
+# This is only a first-pass at a user API.
+# it might change quite a bit in the second pass.
+
+mode = None
+
+def init():
+    spi.init_defaults()
+    trace("config FSK")
+    HRF_config_FSK()
+    HRF_clear_fifo()
+    receiver()
+
+
+def transmitter():
+    """Change into transmitter mode"""
+    global mode
+    trace("transmitter mode")
+    HRF_change_mode(MODE_TRANSMITER)
+    mode = "TRANSMITTER"
+    HRF_wait_txready()
+
+
+def transmit(payload):
+    HRF_send_payload(payload)
+
+
+def receiver():
+    """Change into receiver mode"""
+    global mode
+    trace("receiver mode")
+    HRF_change_mode(MODE_RECEIVER)
+    HRF_wait_ready()
+    mode = "RECEIVER"
+
+
+def isReceiveWaiting():
+    return HRF_check_payload()
+
+
+def receive():
+    return HRF_receive_payload()
+
+
+def finished():
+    """Close the library down cleanly when finished"""
+    spi.finished()
+
+
+
+# END
diff --git a/src/energenie/spi.c b/src/energenie/spi.c
new file mode 100644
index 0000000..0f60a10
--- /dev/null
+++ b/src/energenie/spi.c
@@ -0,0 +1,172 @@
+/* spi.c  D.J.Whale  19/07/2014
+ */
+
+
+/***** INCLUDES *****/
+
+#include <stdio.h>
+#include <stdlib.h>
+//#include <time.h>
+#include <sys/time.h>
+#include <string.h>
+
+#include "spi.h"
+#include "gpio.h"
+
+
+/***** MACROS *****/
+
+#define CLOCK_ACTIVE() gpio_write(config.sclk, config.cpol?0:1)
+#define CLOCK_IDLE()   gpio_write(config.sclk, config.cpol?1:0)
+
+#define SELECTED()     gpio_write(config.cs, config.spol?1:0)
+#define NOT_SELECTED() gpio_write(config.cs, config.spol?0:1)
+
+
+/***** VARIABLES *****/
+
+static SPI_CONFIG config;
+
+
+/* Based on code suggested by Gordon Henderson:
+ * https://github.com/WiringPi/WiringPi/blob/master/wiringPi/wiringPi.c
+ * 
+ * Note that his trick of using the hardware timer just didn't work,
+ * and this is the best of a bad bunch. nanosleep() delays at least
+ * 100uS in some cases.
+ */
+ 
+static void delayus(unsigned int us)
+{
+  struct timeval tNow, tLong, tEnd;
+
+  gettimeofday(&tNow, NULL);
+  tLong.tv_sec  = us / 1000000;
+  tLong.tv_usec = us % 1000000;
+  timeradd(&tNow, &tLong, &tEnd);
+
+  while (timercmp(&tNow, &tEnd, <))
+  {
+    gettimeofday(&tNow, NULL);
+  }
+}  
+
+
+void spi_init_defaults(void)
+{
+#define CS    7    //CE1
+#define SCLK  11
+#define MOSI  10
+#define MISO  9
+
+/* ms */
+#define TSETTLE (1)     /* us settle */
+#define THOLD   (1)     /* us hold */
+#define TFREQ   (1)     /* us half clock */
+
+  SPI_CONFIG defaultConfig = {CS, SCLK, MOSI, MISO, SPI_SPOL0, SPI_CPOL0, SPI_CPHA0,
+                          TSETTLE, THOLD, TFREQ};
+
+  spi_init(&defaultConfig);
+}
+
+
+void spi_init(SPI_CONFIG* pConfig)
+{
+  /* It's a standalone library, so init GPIO also */
+  gpio_init();
+  memcpy(&config, pConfig, sizeof(SPI_CONFIG));
+
+  //TODO: Implement CPHA1
+  if (config.cpha != 0)
+  {
+    fprintf(stderr, "error: CPHA 1 not yet supported");
+    exit(-1);
+  }
+
+  gpio_setout(config.sclk);
+  CLOCK_IDLE();
+
+  gpio_setout(config.mosi);
+  gpio_low(config.mosi);
+  gpio_setin(config.miso);
+
+  gpio_setout(config.cs);
+  NOT_SELECTED();
+}
+
+
+void spi_finished(void)
+{
+  gpio_setin(config.mosi);
+  gpio_setin(config.sclk);
+  gpio_setin(config.cs);
+}
+
+
+void spi_select(void)
+{
+  SELECTED();
+  delayus(config.tSettle);
+}
+
+
+void spi_deselect(void)
+{
+  NOT_SELECTED();
+  delayus(config.tSettle);
+}
+
+
+int spi_byte(int txbyte)
+{
+  int rxbyte = 0;
+  int bitno;
+  int bit ;
+
+  //TODO: Implement CPHA1
+
+  for (bitno=0; bitno<8; bitno++)
+  {
+    /* Transmit MSB first */
+    bit = ((txbyte & 0x80) != 0x00);
+    txbyte <<= 1;
+    gpio_write(config.mosi, bit);
+    delayus(config.tSettle);
+    CLOCK_ACTIVE();
+    delayus(config.tHold);
+    delayus(config.tFreq);
+
+    /* Read MSB first */
+    bit = gpio_read(config.miso);
+    rxbyte = (rxbyte<<1) | bit;
+
+    CLOCK_IDLE();
+    delayus(config.tFreq);
+  }
+  return rxbyte;
+}
+
+
+void spi_frame(unsigned char* pTx, unsigned char* pRx, unsigned char count)
+{
+  unsigned char tx = 0;
+  unsigned char rx;
+
+  while (count > 0)
+  {
+    if (pTx != NULL)
+    {
+      tx = *(pTx++);
+    }
+    rx = spi_byte(tx);
+    if (pRx != NULL)
+    {
+      *(pRx++) = rx;
+    }
+    count--;
+  }
+}
+
+
+/***** END OF FILE *****/
diff --git a/src/energenie/spi.h b/src/energenie/spi.h
new file mode 100644
index 0000000..7534264
--- /dev/null
+++ b/src/energenie/spi.h
@@ -0,0 +1,64 @@
+/* spi.h  D.J.Whale  19/07/2014 */
+
+
+#ifndef SPI_H
+#define SPI_H
+
+
+/***** INCLUDES *****/
+
+//#include <time.h>
+
+
+/***** CONSTANTS *****/
+
+#define SPI_CPOL0 0
+#define SPI_CPOL1 1
+#define SPI_SPOL0 0
+#define SPI_SPOL1 1
+#define SPI_CPHA0 0
+#define SPI_CPHA1 1
+
+
+/***** STRUCTURES *****/
+
+typedef struct
+{
+  unsigned char cs;
+  unsigned char sclk;
+  unsigned char mosi;
+  unsigned char miso;
+
+  unsigned char spol;
+  unsigned char cpol;
+  unsigned char cpha;
+
+  //struct timespec tSettle;
+  //struct timespec tHold;
+  //struct timespec tFreq;
+  unsigned int tSettle;
+  unsigned int tHold;
+  unsigned int tFreq;
+} SPI_CONFIG;
+
+
+
+/***** FUNCTION PROTOTYPES *****/
+
+void spi_init_defaults(void);
+
+void spi_init(SPI_CONFIG* pConfig);
+
+void spi_select(void);
+
+void spi_deselect(void);
+
+int spi_byte(int txbyte);
+
+void spi_frame(unsigned char* pTx, unsigned char* pRx, unsigned char count);
+
+void spi_finished(void);
+
+#endif
+
+/***** END OF FILE *****/
diff --git a/src/energenie/spi.py b/src/energenie/spi.py
new file mode 100644
index 0000000..1ce3f42
--- /dev/null
+++ b/src/energenie/spi.py
@@ -0,0 +1,66 @@
+# spi.py  19/07/2014  D.J.Whale
+# 
+# a C based SPI driver, with a python wrapper
+
+LIBNAME = "spi_rpi.so"
+
+import ctypes
+
+from os import path
+mydir = path.dirname(path.abspath(__file__))
+
+libspi               = ctypes.cdll.LoadLibrary(mydir + "/" + LIBNAME)
+spi_init_defaults_fn = libspi["spi_init_defaults"]
+spi_init_fn          = libspi["spi_init"]
+spi_select_fn        = libspi["spi_select"]
+spi_deselect_fn      = libspi["spi_deselect"]
+spi_byte_fn          = libspi["spi_byte"]
+spi_frame_fn         = libspi["spi_frame"]
+spi_finished_fn      = libspi["spi_finished"]
+
+def trace(msg):
+  pass #print("spi:" + msg)
+  
+def init_defaults():
+  trace("calling init_defaults")
+  spi_init_defaults_fn()
+  
+def init():
+  trace("calling init")
+  #TODO build a config structure
+  #TODO pass in pointer to config structure
+  #spi_init_fn()
+  
+def select():
+  trace("calling select")
+  spi_select_fn()
+  
+def deselect():
+  trace("calling deselect")
+  spi_deselect_fn()
+  
+def byte(tx):
+  txbyte = ctypes.c_ubyte(tx)
+  #trace("calling byte")
+  rxbyte = spi_byte_fn(txbyte)
+  return rxbyte
+
+  
+def frame(txlist):
+  trace("calling frame ")
+  framelen = len(txlist)
+  #print("len:" + str(framelen))
+  Frame = ctypes.c_ubyte * framelen
+  txframe = Frame(*txlist)
+  rxframe = Frame()
+  
+  spi_frame_fn(ctypes.byref(txframe), ctypes.byref(rxframe), framelen)
+  rxlist = []
+  for i in range(framelen):
+    rxlist.append(rxframe[i])
+  return rxlist
+  
+def finished():
+  trace("calling finished")
+  spi_finished_fn()
+
diff --git a/src/energenie/spi_rpi.so b/src/energenie/spi_rpi.so
new file mode 100755
index 0000000..6f81455
--- /dev/null
+++ b/src/energenie/spi_rpi.so
Binary files differ
diff --git a/src/energenie/spi_test.c b/src/energenie/spi_test.c
new file mode 100644
index 0000000..a7ca0f7
--- /dev/null
+++ b/src/energenie/spi_test.c
@@ -0,0 +1,79 @@
+/* spi_test.c  D.J.Whale  18/07/2014 
+ *
+ * A simple SPI port exerciser.
+ */
+
+
+/***** INCLUDES *****/
+#include <stdio.h>
+#include <stdlib.h>
+#include "gpio.h"
+#include "spi.h"
+
+
+/***** CONSTANTS *****/
+
+/* GPIO numbers on Raspberry Pi */
+#define CS    8
+#define SCLK  11
+#define MOSI  10
+#define MISO  9
+
+/* ms */
+#define TSETTLE (1UL * 1000UL)     /* us */
+#define THOLD   (1UL * 1000UL)     /* us */
+#define TFREQ   (1UL * 1000UL)     /* 1us = 1MHz */
+
+int main(int argc, char **argv)
+{
+  unsigned char cmd_prog[4] = {0xAC, 0x53, 0x00, 0x00};
+  unsigned char cmd_id0[4]  = {0x30, 0x00, 0x00, 0x00};
+  unsigned char cmd_id1[4]  = {0x30, 0x00, 0x01, 0x00};
+  unsigned char cmd_id2[4]  = {0x30, 0x00, 0x02, 0x00};
+
+  unsigned char rx[4];
+  SPI_CONFIG spiConfig = {CS, SCLK, MOSI, MISO, SPI_SPOL0, SPI_CPOL0, SPI_CPHA0,
+                          {0,TSETTLE},{0,THOLD},{0,TFREQ}};
+  int i;
+  unsigned char id[3];
+
+
+  /* Init */
+
+  printf("init\n");
+  //gpio_init();
+  spi_init(&spiConfig);
+
+
+  /* Enter programming mode */
+
+  printf("select\n");
+  spi_select();
+  spi_frame(cmd_prog, NULL, 4);
+
+
+  /* Get ID bytes */
+
+  printf("read ID bytes\n");
+  spi_frame(cmd_id0, rx, 4);
+  id[0] = rx[3];
+
+  spi_frame(cmd_id1, rx, 4);
+  id[1] = rx[3];
+
+  spi_frame(cmd_id2, rx, 4);
+  id[2] = rx[3];
+
+  spi_deselect();
+
+
+  /* Show ID bytes */
+
+  printf("ID: %02X %02X %02X\n", id[0], id[1], id[2]);
+
+  spi_finished();
+  return 0;
+}
+
+
+/***** END OF FILE *****/
diff --git a/src/monitor.py b/src/monitor.py
new file mode 100644
index 0000000..72dbec4
--- /dev/null
+++ b/src/monitor.py
@@ -0,0 +1,77 @@
+# monitor.py  27/09/2015  D.J.Whale
+#
+# Monitor settings of Energine MiHome plugs
+
+import time
+from energenie import radio, OpenHEMS
+
+CRYPT_PID = 242
+CRYPT_PIP = 0x0100
+
+def trace(msg):
+    print(str(msg))
+
+
+#----- TIMER ------------------------------------------------------------------
+
+class Timer():
+    def __init__(self, ratesec=1):
+        self.rate = ratesec
+        self.nexttick = time.time()
+
+
+    def check(self):
+        """Maintain the timer and see if it is time for the next tick"""
+        now = time.time()
+
+        if now >= self.nexttick:
+            # asynchronous tick, might drift, but won't stack up if late
+            self.nexttick = now + self.rate
+            return True
+
+        return False
+
+
+#----- TEST APPLICATION -------------------------------------------------------
+
+def monitor():
+    """Send monitor poke messages and capture any responses"""
+
+    sendMonitorTimer = Timer(3)
+    pollReceiveTimer = Timer(1)
+
+    while True:
+        # Keep in receiver mode as much as possible
+        # but don't keep trying to switch into receiver if already there
+        if radio.mode != "RECEIVER":
+            radio.receiver()
+
+        # See if there is a payload, and if there is, process it
+        if pollReceiveTimer.check():
+            if radio.isReceiveWaiting():
+                trace("receiving payload")
+                payload = radio.receive()
+                trace("decoding payload")
+                print(OpenHEMS.decode(payload))  # TODO decode from buffer to pydict
+
+        # If it is time to send a monitor message, send it
+        if sendMonitorTimer.check():
+            trace("time for monitor")
+            payload = OpenHEMS.make_monitor() # TODO encode from pydict to buffer
+            radio.transmitter()
+            trace("sending monitor message")
+            radio.transmit(payload)
+
+
+if __name__ == "__main__":
+
+    radio.init()
+    OpenHEMS.init(CRYPT_PID, CRYPT_PIP)
+
+    try:
+        monitor()
+
+    finally:
+        radio.finished()
+
+# END