diff --git a/README.md b/README.md
index e8c76c4..460d01c 100644
--- a/README.md
+++ b/README.md
@@ -106,6 +106,16 @@
 and encode and send those in to make the device do something in response.
 
 
+Experimental
+====
+
+You can try combined.py which is an example of how to switch both the
+purple MiHome plugs, and the green button legacy plugs in the same
+application (at the moment you have to use the unified_radio branch
+to do this, as I haven't merged this to master), but I tried it here
+and it works.
+
+
 Plans
 ====
 
diff --git a/src/Logger.py b/src/Logger.py
new file mode 100644
index 0000000..9157c64
--- /dev/null
+++ b/src/Logger.py
@@ -0,0 +1,85 @@
+# Logger.py  14/05/2016  D.J.Whale
+#
+# A simple logger - logs to file.
+
+
+from energenie import OpenThings
+import os, time
+
+LOG_FILENAME = "energenie.csv"
+HEADINGS = 'timestamp,mfrid,prodid,sensorid,flags,switch,voltage,freq,reactive,real'
+
+
+log_file = None
+
+def trace(msg):
+    print(str(msg))
+
+
+def logMessage(msg):
+    global log_file
+
+    if log_file == None:
+        if not os.path.isfile(LOG_FILENAME):
+            log_file = open(LOG_FILENAME, 'w')
+            log_file.write(HEADINGS + '\n')
+        else:
+            log_file = open(LOG_FILENAME, 'a') # append
+
+    # get the header
+    header    = msg['header']
+    timestamp = time.time()
+    mfrid     = header['mfrid']
+    productid = header['productid']
+    sensorid  = header['sensorid']
+
+    # set defaults for any data that doesn't appear in this message
+    # but build flags so we know which ones this contains
+    flags = [0 for i in range(7)]
+    switch = None
+    voltage = None
+    freq = None
+    reactive = None
+    real = None
+    apparent = None
+    current = None
+
+    # capture any data that we want
+    ##trace(msg)
+    for rec in msg['recs']:
+        paramid = rec['paramid']
+        try:
+            value = rec['value']
+        except:
+            value = None
+
+        if   paramid == OpenThings.PARAM_SWITCH_STATE:
+            switch = value
+            flags[0] = 1
+        elif paramid == OpenThings.PARAM_VOLTAGE:
+            flags[1] = 1
+            voltage = value
+        elif paramid == OpenThings.PARAM_FREQUENCY:
+            flags[2] = 1
+            freq = value
+        elif paramid == OpenThings.PARAM_REACTIVE_POWER:
+            flags[3] = 1
+            reactive = value
+        elif paramid == OpenThings.PARAM_REAL_POWER:
+            flags[4] = 1
+            real = value
+        elif paramid == OpenThings.PARAM_APPARENT_POWER:
+            flags[5] = 1
+            apparent = value
+        elif paramid == OpenThings.PARAM_CURRENT:
+            flags[6] = 1
+            current = value
+
+    # generate a line of CSV
+    flags = "".join([str(a) for a in flags])
+    csv = "%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s" % (timestamp, mfrid, productid, sensorid, flags, switch, voltage, freq, reactive, real, apparent, current)
+    log_file.write(csv + '\n')
+    log_file.flush()
+    ##trace(csv) # testing
+
+# END
diff --git a/src/combined.py b/src/combined.py
new file mode 100644
index 0000000..5467c22
--- /dev/null
+++ b/src/combined.py
@@ -0,0 +1,71 @@
+# combined.py  15/05/2016  D.J.Whale
+#
+# A simple demo of combining both FSK (MiHome) and OOK (green button legacy)
+#
+# NOTE: This is only a test harness.
+# If you really want a nice way to control these devices, wait for the 'device classes'
+# issues to be implemented and tested on top of the raw radio interface, as these
+# will be much nicer to use.
+
+import time
+from energenie import Messages, OpenThings, radio, encoder, Devices
+
+# build FSK messages for MiHome purple
+
+OpenThings.init(Devices.CRYPT_PID)
+
+PURPLE_ID = 0x68B # captured from a real device using Monitor.py
+m = OpenThings.alterMessage(
+    Messages.SWITCH,
+    header_sensorid=PURPLE_ID,
+    recs_0_value=1)
+purple_on = OpenThings.encode(m)
+
+m = OpenThings.alterMessage(
+    Messages.SWITCH,
+    header_sensorid=PURPLE_ID,
+    recs_0_value=0)
+purple_off = OpenThings.encode(m)
+
+# build OOK messages for legacy green button
+
+GREEN_ON  = encoder.build_switch_msg(True, device_address=1)
+GREEN_OFF = encoder.build_switch_msg(False, device_address=1)
+
+
+def switch_loop():
+    print("Turning green ON")
+    radio.modulation(ook=True)
+    radio.transmit(GREEN_ON)
+    time.sleep(0.5)
+
+    print("Turning purple ON")
+    radio.modulation(fsk=True)
+    radio.transmit(purple_on, inner_times=2)
+    time.sleep(2)
+
+    print("Turning green OFF")
+    radio.modulation(ook=True)
+    radio.transmit(GREEN_OFF)
+    time.sleep(0.5)
+
+    print("Turning purple OFF")
+    radio.modulation(fsk=True)
+    radio.transmit(purple_off, inner_times=2)
+    time.sleep(2)
+
+
+if __name__ == "__main__":
+
+    print("starting combined switch tester")
+    print("radio init")
+    radio.init()
+
+    try:
+        while True:
+            switch_loop()
+
+    finally:
+        radio.finished()
+
+# END
diff --git a/src/energenie/Devices.py b/src/energenie/Devices.py
index 2730f70..981acf9 100644
--- a/src/energenie/Devices.py
+++ b/src/energenie/Devices.py
@@ -1,79 +1,91 @@
 # Devices.py  30/09/2015  D.J.Whale
 #
 # Information about specific Energenie devices
+# This table is mostly reverse-engineered from various websites and web catalogues.
 
 MFRID                            = 0x04
 
+# Deprecated, these are old device names, do not use.
+#PRODUCTID_C1_MONITOR             = 0x01   # MIHO004 Monitor
+#PRODUCTID_R1_MONITOR_AND_CONTROL = 0x02   # MIHO005 Adaptor Plus
+
 #PRODUCTID_MIHO001               = 0x0?   #         Home Hub
 #PRODUCTID_MIHO002               = 0x0?   #         Control only
 #PRODUCTID_MIHO003               = 0x0?   #         Hand Controller
-PRODUCTID_C1_MONITOR             = 0x01   # MIHO004 Monitor
-PRODUCTID_R1_MONITOR_AND_CONTROL = 0x02   # MIHO005 Adaptor Plus
+PRODUCTID_MIHO004                = 0x01   #         Monitor only
+PRODUCTID_MIHO005                = 0x02   #         Adaptor Plus
 PRODUCTID_MIHO006                = 0x05   #         House Monitor
 #PRODUCTID_MIHO007               = 0x0?   #         Double Wall Socket White
 #PRODUCTID_MIHO008               = 0x0?   #         Single light switch
-#009 not used
-#010 not used
-#011 not used
-#012 not used
+#PRODUCTID_MIHO009 not used
+#PRODUCTID_MIHO010 not used
+#PRODUCTID_MIHO011 not used
+#PRODUCTID_MIHO012 not used
 PRODUCTID_MIHO013                = 0x03   #         eTRV
 #PRODUCTID_MIHO014               = 0x0?   #         In-line Relay
-#015 not used
-#016 not used
-#017
-#018
-#019
-#020
+#PRODUCTID_MIHO015 not used
+#PRODUCTID_MIHO016 not used
+#PRODUCTID_MIHO017
+#PRODUCTID_MIHO018
+#PRODUCTID_MIHO019
+#PRODUCTID_MIHO020
 #PRODUCTID_MIHO021               = 0x0?   #         Double Wall Socket Nickel
 #PRODUCTID_MIHO022               = 0x0?   #         Double Wall Socket Chrome
 #PRODUCTID_MIHO023               = 0x0?   #         Double Wall Socket Brushed Steel
 #PRODUCTID_MIHO024               = 0x0?   #         Style Light Nickel
 #PRODUCTID_MIHO025               = 0x0?   #         Style Light Chrome
 #PRODUCTID_MIHO026               = 0x0?   #         Style Light Steel
-#027 starter pack bundle
-#028 eco starter pack
-#029 heating bundle
-#030-036 not used
-#037 Adaptor Plus Bundle
-#038 2-gang socket Bundle
-#039 2-gang socket Bundle black nickel
-#040 2-gang socket Bundle chrome
-#041 2-gang socket Bundle stainless steel
+#PRODUCTID_MIHO027 starter pack bundle
+#PRODUCTID_MIHO028 eco starter pack
+#PRODUCTID_MIHO029 heating bundle
+#PRODUCTID_MIHO030 not used
+#PRODUCTID_MIHO031 not used
+#PRODUCTID_MIHO032 not used
+#PRODUCTID_MIHO033 not used
+#PRODUCTID_MIHO034 not used
+#PRODUCTID_MIHO035 not used
+#PRODUCTID_MIHO036 not used
+#PRODUCTID_MIHO037 Adaptor Plus Bundle
+#PRODUCTID_MIHO038 2-gang socket Bundle
+#PRODUCTID_MIHO039 2-gang socket Bundle black nickel
+#PRODUCTID_MIHO040 2-gang socket Bundle chrome
+#PRODUCTID_MIHO041 2-gang socket Bundle stainless steel
 
 
 CRYPT_PID                        = 242
 CRYPT_PIP                        = 0x0100
 
-# OpenHEMS does not support a broadcast id, but Energenie added one for their
-# MiHome Adaptors. This makes simple discovery possible.
-BROADCAST_ID                     = 0xFFFFFF # energenie broadcast
+# OpenThings does not support a broadcast id,
+# but Energenie added one for their MiHome Adaptors.
+# This makes simple discovery possible.
+BROADCAST_ID                     = 0xFFFFFF # Energenie broadcast
 
-# TODO put additional products in here from the Energenie directory
-# TODO make this table based
+#TODO: put additional products in here from the Energenie directory
+#TODO: make this table based
 
 def getDescription(mfrid, productid):
     if mfrid == MFRID:
         mfr = "Energenie"
-        if productid == PRODUCTID_C1_MONITOR:
-            product = "C1 MONITOR"
-        elif productid == PRODUCTID_R1_MONITOR_AND_CONTROL:
+        if productid == PRODUCTID_MIHO004:
+            product = "MIHO004 MONITOR"
+        elif productid == PRODUCTID_MIHO005:
             product = "MIHO005 ADAPTOR PLUS"
         elif productid == PRODUCTID_MIHO006:
             product = "MIHO006 HOUSE MONITOR"
         elif productid == PRODUCTID_MIHO013:
             product = "MIHO013 ETRV"
         else:
-            product = "UNKNOWN"
+            product = "UNKNOWN_%s" % str(hex(productid))
     else:
-        mfr     = "UNKNOWN"
-        product = "UNKNOWN"
+        mfr     = "UNKNOWN_%s" % str(hex(mfrid))
+        product = "UNKNOWN_%s" % str(hex(productid))
 
     return "Manufacturer:%s Product:%s" % (mfr, product)
 
 
 def hasSwitch(mfrid, productid):
-    if mfrid != MFRID:                                return False
-    if productid == PRODUCTID_R1_MONITOR_AND_CONTROL: return True
+    if mfrid != MFRID:                  return False
+    if productid == PRODUCTID_MIHO005:  return True
     return False
 
 
diff --git a/src/energenie/Messages.py b/src/energenie/Messages.py
index 7dddd3f..1ab2961 100644
--- a/src/energenie/Messages.py
+++ b/src/energenie/Messages.py
@@ -8,7 +8,7 @@
 SWITCH = {
     "header": {
         "mfrid":       Devices.MFRID,
-        "productid":   Devices.PRODUCTID_R1_MONITOR_AND_CONTROL,
+        "productid":   Devices.PRODUCTID_MIHO005,
         "encryptPIP":  Devices.CRYPT_PIP,
         "sensorid":    0 # FILL IN
     },
@@ -42,4 +42,26 @@
 }
 
 
+REGISTERED_SENSOR = {
+    "header": {
+        "mfrid":       0, # FILL IN
+        "productid":   0, # FILL IN
+        "encryptPIP":  Devices.CRYPT_PIP,
+        "sensorid":    0 # FILL IN
+    }
+}
+
+
+def send_join_ack(radio, mfrid, productid, sensorid):
+    # send back a JOIN ACK, so that join light stops flashing
+    response = OpenThings.alterMessage(JOIN_ACK,
+        header_mfrid=mfrid,
+        header_productid=productid,
+        header_sensorid=sensorid)
+    p = OpenThings.encode(response)
+    radio.transmitter()
+    radio.transmit(p, inner_times=2)
+    radio.receiver()
+
+
 # END
diff --git a/src/energenie/OpenThings.py b/src/energenie/OpenThings.py
index fbf935b..7dc3862 100644
--- a/src/energenie/OpenThings.py
+++ b/src/energenie/OpenThings.py
@@ -622,7 +622,7 @@
 
 
 def getFromMessage(message, keypath):
-	"""Get a field from a message, given a keypath to the item"""
+	"""Get a field from a message, given an underscored keypath to the item"""
 	path = keypath.split("_")
 
 	for p in path[:-1]:
@@ -630,8 +630,8 @@
 			p = int(p)
 		except:
 			pass
-		m = m[p]
-	return m[path[-1]]
+		message = message[p]
+	return message[path[-1]]
 
 
 #----- TEST HARNESS -----------------------------------------------------------
diff --git a/src/energenie/Registry.py b/src/energenie/Registry.py
new file mode 100644
index 0000000..f2316b0
--- /dev/null
+++ b/src/energenie/Registry.py
@@ -0,0 +1,50 @@
+# Registry.py  14/05/2016  D.J.Whale
+#
+# A simple registry of connected devices.
+#
+# NOTE: This is an initial, non persisted implementation only
+
+import time
+import Devices
+
+directory = {}
+
+def allkeys(d):
+    result = ""
+    for k in d:
+        if len(result) != 0:
+            result += ','
+        result += str(k)
+    return result
+
+
+def update(message):
+    """Update the local directory with information about this device"""
+    now      = time.time()
+    header   = message["header"]
+    sensorId = header["sensorid"]
+
+    if not directory.has_key(sensorId):
+        # new device discovered
+        desc = Devices.getDescription(header["mfrid"], header["productid"])
+        print("ADD device:%s %s" % (hex(sensorId), desc))
+        directory[sensorId] = {"header": message["header"]}
+        #trace(allkeys(directory))
+
+    directory[sensorId]["time"] = now
+    #TODO would be good to keep recs, but need to iterate through all and key by paramid,
+    #not as a list index, else merging will be hard.
+
+
+def size():
+    return len(directory)
+
+
+def get_sensorids():
+    return directory.keys()
+
+
+def get_info(sensor_id):
+    return directory[sensor_id]
+
+# END
diff --git a/src/energenie/drv/build_mac b/src/energenie/drv/build_mac
index e96ac74..379e8d4 100755
--- a/src/energenie/drv/build_mac
+++ b/src/energenie/drv/build_mac
@@ -10,9 +10,9 @@
 
 
 # build spis_test (soft SPI tester)
-##gcc spi_test.c spis.c gpio_sim.c delay_posix.c
-##mv a.out spis_test
-##chmod u+x spis_test
+gcc spi_test.c spis.c gpio_sim.c delay_posix.c
+mv a.out spis_test
+chmod u+x spis_test
 
 
 # build hrf69_test
@@ -40,13 +40,7 @@
 chmod u+x radio_test
 
 
-# build spi .so library on Raspberry Pi
-##gcc -Wall -shared -o spi_rpi.so -fPIC spis_rpi.c gpio_rpi.c
-##nm -D spi_rpi.so
-##cp spi_rpi.so ..
-
-
-# radio spi .so library on Mac
+# radio .so library on Mac
 gcc -Wall -shared -o radio_mac.so -fPIC radio.c hrfm69.c spis.c gpio_sim.c delay_posix.c
 ##nm -g radio_mac.so
 ##cp radio_mac.so ..
diff --git a/src/energenie/drv/build_rpi b/src/energenie/drv/build_rpi
index b7076c2..a10bfa0 100755
--- a/src/energenie/drv/build_rpi
+++ b/src/energenie/drv/build_rpi
@@ -31,15 +31,9 @@
 mv a.out radio_test
 chmod u+x radio_test
 
-# build spi .so library on Raspberry Pi
-##gcc -Wall -shared -o spi_rpi.so -fPIC spis_rpi.c gpio_rpi.c
-# nm -D spi_rpi.so
-##cp spi_rpi.so ..
 
-
-# radio spi .so library on Raspberry Pi
+# radio .so library on Raspberry Pi
 gcc -Wall -shared -o radio_rpi.so -fPIC radio.c hrfm69.c spis.c gpio_rpi.c delay_posix.c
 ##nm -D radio_rpi.so
-cp radio_rpi.so ..
 
 # END
diff --git a/src/energenie/drv/hrfm69.c b/src/energenie/drv/hrfm69.c
index 6960e45..cb15335 100644
--- a/src/energenie/drv/hrfm69.c
+++ b/src/energenie/drv/hrfm69.c
@@ -58,26 +58,79 @@
 
 
 /*---------------------------------------------------------------------------*/
+// Read bytes from FIFO in burst mode.
+// Never reads more than buflen bytes
+// First received byte is the count of remaining bytes
+// That byte is also returned in the user buffer.
+// Note the user buffer can be > FIFO_MAX, but there is no flow control
+// in the HRF driver yet, so you might get an underflow error if data is read
+// quicker than it comes in on-air. You might get an overflow error if
+// data comes in quicker than it is read.
 
-void HRF_readfifo_burst(uint8_t* buf, uint8_t len)
+
+HRF_RESULT HRF_readfifo_burst_cbp(uint8_t* buf, uint8_t buflen)
 {
-//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()
-//    return buf
+    uint8_t data;
+
+    spi_select();
+    spi_byte(HRF_ADDR_FIFO); /* prime the fifo burst reader */
+
+    /* Read the first byte, and then decide how many remaining bytes to receive */
+    data = spi_byte(HRF_ADDR_FIFO);
+    *(buf++) = data; /* the count byte is always returned as first byte of user buffer */
+
+    /* Validate the payload len against the supplied user buffer */
+    if (data > buflen)
+    {
+        spi_deselect();
+        TRACE_OUTS("buffer too small for payload len=");
+        TRACE_OUTN(data);
+        TRACE_NL();
+        return HRF_RESULT_ERR_BUFFER_TOO_SMALL;
+    }
+
+    buflen = data; /* now the expected payload length */
+
+    while (buflen != 0)
+    {
+        data = spi_byte(HRF_ADDR_FIFO);
+        *(buf++) = data;
+        buflen--;
+    }
+    spi_deselect();
+
+    //TODO: Read irqflags
+    //if underflow, this is an error (reading out too quick)
+    //if overflow, this is an error (not reading out quick enough)
+    //if not empty at end, this is a warning (might be ok, but user might want to clear_fifo after)
+    return HRF_RESULT_OK;
+}
+
+
+/*---------------------------------------------------------------------------*/
+// Read bytes from FIFO in burst mode.
+// Tries to read exactly buflen bytes
+
+HRF_RESULT HRF_readfifo_burst_len(uint8_t* buf, uint8_t buflen)
+{
+    uint8_t data;
+
+    spi_select();
+    spi_byte(HRF_ADDR_FIFO); /* prime the fifo burst reader */
+
+    while (buflen != 0)
+    {
+        data = spi_byte(HRF_ADDR_FIFO);
+        *(buf++) = data;
+        buflen--;
+    }
+    spi_deselect();
+
+    //TODO: Read irqflags
+    //if underflow, this is an error (reading out too quick)
+    //if overflow, this is an error (not reading out quick enough)
+    //if not empty at end, this is a warning (might be ok, but user might want to clear_fifo after)
+    return HRF_RESULT_OK;
 }
 
 
@@ -87,7 +140,11 @@
 HRF_RESULT HRF_checkreg(uint8_t addr, uint8_t mask, uint8_t value)
 {
     uint8_t regval = HRF_readreg(addr);
-    return (regval & mask) == value;
+    if ((regval & mask) == value)
+    {
+        return HRF_RESULT_OK_TRUE;
+    }
+    return HRF_RESULT_OK_FALSE;
 }
 
 
@@ -104,7 +161,8 @@
 
     while (! HRF_checkreg(addr, mask, value))
     {
-      // busy wait (TODO:with no timeout & error recovery?)
+      // busy wait
+      //TODO: No timeout or error recovery? Can cause permanent lockup
     }
 }
 
@@ -115,6 +173,7 @@
 void HRF_clear_fifo(void)
 {
     //TODO: max fifolen is 66, should bail after that to prevent lockup
+    //especially if radio crashed and SPI always returns stuck flag bit
     while ((HRF_readreg(HRF_ADDR_IRQFLAGS2) & HRF_MASK_FIFONOTEMPTY) == HRF_MASK_FIFONOTEMPTY)
     {
         HRF_readreg(HRF_ADDR_FIFO);
diff --git a/src/energenie/drv/hrfm69.h b/src/energenie/drv/hrfm69.h
index 3223fe8..aab23a0 100644
--- a/src/energenie/drv/hrfm69.h
+++ b/src/energenie/drv/hrfm69.h
@@ -13,6 +13,11 @@
 #include "system.h"
 
 typedef uint8_t HRF_RESULT;
+//consider these, so we can easily pass back a boolean too
+#define HRF_RESULT_OK                   0x00
+#define HRF_RESULT_OK_FALSE             0x00
+#define HRF_RESULT_OK_TRUE              0x01
+#define HRF_RESULT_ERR_BUFFER_TOO_SMALL 0x81
 
 typedef struct
 {
@@ -113,7 +118,9 @@
 
 extern void HRF_writefifo_burst(uint8_t* buf, uint8_t len);
 
-extern void HRF_readfifo_burst(uint8_t* buf, uint8_t len);
+extern HRF_RESULT HRF_readfifo_burst_cbp(uint8_t* buf, uint8_t buflen);
+
+extern HRF_RESULT HRF_readfifo_burst_len(uint8_t* buf, uint8_t buflen);
 
 extern HRF_RESULT HRF_checkreg(uint8_t addr, uint8_t mask, uint8_t value);
 
diff --git a/src/energenie/drv/radio.c b/src/energenie/drv/radio.c
index b8ea7ec..81a408f 100644
--- a/src/energenie/drv/radio.c
+++ b/src/energenie/drv/radio.c
@@ -6,6 +6,67 @@
  */
 
 
+/* TODO (rx)
+DONE: decide interface for: HRF_readfifo_burst(uint8_t* buf, uint8_t len)
+DONE: Knit in the payload check
+DONE: Knit in the get_payload
+
+TODO: test with monitor.py (receive only mode, FSK)
+*/
+
+
+/* TODO (OOK rx)
+TODO: Write a tester legacy_rx.py to exercise 16 byte OOK receive
+TODO: See if we can receive from RF hand transmiter
+TODO: See how much 'noise bytes' we get
+TODO: Might need to set the sync bytes on OOK receive to prevent false trigger
+  This will mean loading a different configuration for OOK receive to transmit
+  But we don't want to be manually looking for sync bits, and we don't want
+  to use the preamble generator on tx as it doesn't work too well.
+*/
+
+
+/*
+TODO: Harden up the payload receiver
+  1. If there is a corrupted packet in the fifo, the first byte
+  might not be the length byte, and the read might fail or leave
+  bytes in the buffer. So, the receiver needs to be hardended up
+  to cope with these cases. HRF_readfifo_burst returns an error
+  if the length byte is longer than buflen, but need to check this
+  error code.
+
+  2. if we are in fixed length, or count byte, and more data
+  is in the fifo than we expect, this will be read next time.
+  Perhaps we need to clear the fifo at the end of each read operation,
+  and also report if there is junk in the buffer (i.e. would always
+  expect a read to leave an empty fifo).
+
+  3. We should check the fifo underrun bit at the end of a read,
+  this would mean that some of the bytes we read are not real bytes
+  and should not be interpreted, that packet should be junked.
+*/
+
+
+/* TODO: DUTY CYCLE PROTECTION REQUIREMENT
+ *
+ * See page 3 of this app note: http://www.ti.com/lit/an/swra090/swra090.pdf
+ *
+ * At OOK 4800bps, 1 bit is 20uS, 1 byte is 1.6ms, 16 bytes is 26.6ms
+ * 15 times (old design limit) is 400ms
+ * 255 times (new design limit) is 6.8s
+ *
+ * Transmitter duty cycle
+ * The transmitter duty cycle is defined as the ratio of the maximum ”on” time,
+ * relative to a onehour period. If message acknowledgement is required, the
+ * additional ”on” time shall be included. Advisory limits are:
+ *
+ * Duty cycle  Maximum “on” time [sec]   Minimum “off” time [sec]
+ * 0.1 %       0.72                      0.72
+ * 1 %         3.6                       1.8
+ * 10 %        36                        3.6
+ */
+
+
 /***** INCLUDES *****/
 
 #include "system.h"
@@ -20,16 +81,15 @@
 /***** CONFIGURATION *****/
 
 #define EXPECTED_RADIOVER 36
+#define MAX_FIFO_BUFFER   66
 
 
 // Energenie specific radio config values
-//#define RADIO_VAL_SYNCVALUE1FSK          0x2D	// 1st byte of Sync word
-//#define RADIO_VAL_SYNCVALUE2FSK          0xD4	// 2nd byte of Sync word
-//#define RADIO_VAL_SYNCVALUE1OOK          0x80	// 1nd byte of Sync word
+#define RADIO_VAL_SYNCVALUE1FSK          0x2D	// 1st byte of Sync word
+#define RADIO_VAL_SYNCVALUE2FSK          0xD4	// 2nd byte of Sync word
+#define RADIO_VAL_SYNCVALUE1OOK          0x80	// 1nd byte of Sync word
 //#define RADIO_VAL_PACKETCONFIG1FSK       0xA2	// Variable length, Manchester coding, Addr must match NodeAddress
-//#define RADIO_VAL_PACKETCONFIG1FSKNO     0xA0	// Variable length, Manchester coding
-//#define RADIO_VAL_PACKETCONFIG1OOK       0		// Fixed length, no Manchester coding
-//#define RADIO_VAL_PAYLOADLEN_OOK         (13 + 8 * 17)	// Payload Length (WRONG!)
+#define RADIO_VAL_PACKETCONFIG1FSKNO     0xA0	// Variable length, Manchester coding
 
 //TODO: Not sure, might pass this in? What about on Arduino?
 //What about if we have multiple chip selects on same SPI?
@@ -56,32 +116,31 @@
 static void _wait_ready(void);
 static void _wait_txready(void);
 static void _config(HRF_CONFIG_REC* config, uint8_t len);
-//static int _payload_waiting(void);
+static int _payload_waiting(void);
 
 
 //----- ENERGENIE SPECIFIC CONFIGURATIONS --------------------------------------
-// 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_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_PACKETCONFIG1FSKNO],       # Variable length, Manchester coding
-//     [ADDR_PAYLOADLEN,         VAL_PAYLOADLEN66],             # max Length in RX, not used in Tx
-//     [ADDR_NODEADDRESS,        0x06],                         # Node address used in address filtering TODO???
-//     [ADDR_FIFOTHRESH,         VAL_FIFOTHRESH1],              # Condition to start packet transmission: at least one byte in FIFO
-//     [ADDR_OPMODE,             MODE_RECEIVER]                 # Operating mode to Receiver
-// ]
-//#define CONFIG_FSK_COUNT (sizeof(config_FSK)/sizeof(HRF_CONFIG_REC))
+
+static HRF_CONFIG_REC config_FSK[] = {
+     {HRF_ADDR_REGDATAMODUL,       HRF_VAL_REGDATAMODUL_FSK},         // modulation scheme FSK
+     {HRF_ADDR_FDEVMSB,            HRF_VAL_FDEVMSB30},                // frequency deviation 5kHz 0x0052 -> 30kHz 0x01EC
+     {HRF_ADDR_FDEVLSB,            HRF_VAL_FDEVLSB30},                // frequency deviation 5kHz 0x0052 -> 30kHz 0x01EC
+     {HRF_ADDR_FRMSB,              HRF_VAL_FRMSB434},                 // carrier freq -> 434.3MHz 0x6C9333
+     {HRF_ADDR_FRMID,              HRF_VAL_FRMID434},                 // carrier freq -> 434.3MHz 0x6C9333
+     {HRF_ADDR_FRLSB,              HRF_VAL_FRLSB434},                 // carrier freq -> 434.3MHz 0x6C9333
+     {HRF_ADDR_AFCCTRL,            HRF_VAL_AFCCTRLS},                 // standard AFC routine
+     {HRF_ADDR_LNA,                HRF_VAL_LNA50},                    // 200ohms, gain by AGC loop -> 50ohms
+     {HRF_ADDR_RXBW,               HRF_VAL_RXBW60},                   // channel filter bandwidth 10kHz -> 60kHz  page:26
+     {HRF_ADDR_BITRATEMSB,         0x1A},                             // 4800b/s
+     {HRF_ADDR_BITRATELSB,         0x0B},                             // 4800b/s
+     {HRF_ADDR_SYNCCONFIG,         HRF_VAL_SYNCCONFIG2},              // Size of the Synch word = 2 (SyncSize + 1)
+     {HRF_ADDR_SYNCVALUE1,         RADIO_VAL_SYNCVALUE1FSK},          // 1st byte of Sync word
+     {HRF_ADDR_SYNCVALUE2,         RADIO_VAL_SYNCVALUE2FSK},          // 2nd byte of Sync word
+     {HRF_ADDR_PACKETCONFIG1,      RADIO_VAL_PACKETCONFIG1FSKNO},     // Variable length, Manchester coding
+     {HRF_ADDR_PAYLOADLEN,         HRF_VAL_PAYLOADLEN66},             // max Length in RX, not used in Tx
+     {HRF_ADDR_NODEADDRESS,        0x06},                             // Node address used in address filtering (not used)
+};
+#define CONFIG_FSK_COUNT (sizeof(config_FSK)/sizeof(HRF_CONFIG_REC))
 
 
 static HRF_CONFIG_REC config_OOK[] = {
@@ -177,15 +236,11 @@
 /*---------------------------------------------------------------------------*/
 // Check if there is a payload in the FIFO waiting to be processed
 
-//static int _payload_waiting(void)
-//{
-//    //TODO: First read might be superflous, but left in just in case
-//    //uint8_t irqflags1 =
-//    HRF_readreg(HRF_ADDR_IRQFLAGS1);
-//
-//    uint8_t irqflags2 = HRF_readreg(HRF_ADDR_IRQFLAGS2);
-//    return (irqflags2 & HRF_MASK_PAYLOADRDY) == HRF_MASK_PAYLOADRDY;
-//}
+static int _payload_waiting(void)
+{
+    uint8_t irqflags2 = HRF_readreg(HRF_ADDR_IRQFLAGS2);
+    return (irqflags2 & HRF_MASK_PAYLOADRDY) == HRF_MASK_PAYLOADRDY;
+}
 
 
 /***** PUBLIC ****************************************************************/
@@ -256,11 +311,11 @@
         _config(config_OOK, CONFIG_OOK_COUNT);
         radio_data.modu = mod;
     }
-    //else if (mod == RADIO_MODULATION_FSK)
-    //{
-    //    _config(config_FSK, CONFIG_FSK_COUNT);
-    //    radio_data.modu = mod;
-    //}
+    else if (mod == RADIO_MODULATION_FSK)
+    {
+        _config(config_FSK, CONFIG_FSK_COUNT);
+        radio_data.modu = mod;
+    }
     else //TODO: make this ASSERT()
     {
         TRACE_FAIL("Unknown modulation\n");
@@ -269,6 +324,7 @@
 
 
 /*---------------------------------------------------------------------------*/
+// Put radio into transmit mode for chosen modulation scheme
 
 void radio_transmitter(RADIO_MODULATION mod)
 {
@@ -280,6 +336,9 @@
 
 
 /*---------------------------------------------------------------------------*/
+// Put radio into receive mode for chosen modulation scheme
+// This will open up the receive window, so that a packet can come in
+// and be detected later by radio_isReceiveWaiting
 
 void radio_receiver(RADIO_MODULATION mod)
 {
@@ -323,38 +382,6 @@
 /*---------------------------------------------------------------------------*/
 // Send a payload of data
 
-/* DESIGN FOR DUTY CYCLE PROTECTION REQUIREMENT (write this later)
- *
- * At OOK 4800bps, 1 bit is 20uS, 1 byte is 1.6ms, 16 bytes is 26.6ms
- * 15 times (old design limit) is 400ms
- * 255 times (new design limit) is 6.8s
-
- * See page 3 of this app note: http://www.ti.com/lit/an/swra090/swra090.pdf
- *
- * Transmitter duty cycle
- * The transmitter duty cycle is defined as the ratio of the maximum ”on” time, relative to a onehour period.
- * If message acknowledgement is required, the additional ”on” time shall be included. Advisory limits are:
- *
- * Duty cycle  Maximum “on” time [sec]   Minimum “off” time [sec]
- * 0.1 %       0.72                      0.72
- * 1 %         3.6                       1.8
- * 10 %        36                        3.6
- */
-
-/* DESIGN FOR >255 payload len (write this later)
-
-   will need to set fifolevel as a proportion of payload len
-   and load that proportion.
-   i.e. inside the payload repeat loop
-     load the fifo up in non integral payload portions
-   also, txstart condition would need to start before whole payload loaded in FIFO
-   that is probably ok, but fifolev is more to do with fill rate and transmit rate,
-   and less to do with the actual payload length.
-   Note that FIFO empties at a rate proportional to the bitrate,
-   and also adding on manchester coding will slow the emptying rate.
- */
-
-
 void radio_send_payload(uint8_t* payload, uint8_t len, uint8_t times)
 {
     TRACE_OUTS("radio_send_payload\n");
@@ -417,7 +444,7 @@
     TRACE_OUTN(irqflags2);
     TRACE_NL();
 
-    //TODO: make this ASSERT()??
+    //TODO: make this TRACE_ASSERT()
     if (((irqflags2 & HRF_MASK_FIFONOTEMPTY) != 0) || ((irqflags2 & HRF_MASK_FIFOOVERRUN) != 0))
     {
         TRACE_FAIL("FIFO not empty or overrun at end of burst");
@@ -427,39 +454,62 @@
 
 
 /*---------------------------------------------------------------------------*/
+// Check to see if a payload is waiting in the receive buffer
 
-//RADIO_RESULT radio_isReceiveWaiting(void)
-//{
-// def isReceiveWaiting():
-//     """Check to see if a payload is waiting in the receive buffer"""
-//     return check_payload()
-//    return RADIO_RESULT_ERR_UNIMPLEMENTED;
-//}
+RADIO_RESULT radio_is_receive_waiting(void)
+{
+    if (_payload_waiting())
+    {
+        return RADIO_RESULT_OK_TRUE;
+    }
+    return RADIO_RESULT_OK_FALSE;
+}
 
 
 /*---------------------------------------------------------------------------*/
-//TODO: high level receive, put into receive, receive a payload, back to standby
+// read a single payload from the payload buffer
+// this reads a fixed length payload
 
-//RADIO_RESULT radio_receive(uint8_t* buf, uint8_t len)
-//{
-// def receive():
-//     """Receive a single payload from the buffer using the present modulation scheme"""
-// change into receive mode if not already there
-// receive payload
-// if was not in receive mode, change back to previous mode
-//}
-
+RADIO_RESULT radio_get_payload_len(uint8_t* buf, uint8_t buflen)
+{
+    if (buflen > MAX_FIFO_BUFFER)
+    {  /* At the moment, the receiver cannot reliably cope with payloads > 1 FIFO buffer.
+        * It *might* be able to in the future.
+        */
+        return RADIO_RESULT_ERR_LONG_PAYLOAD;
+    }
+    HRF_RESULT r = HRF_readfifo_burst_len(buf, buflen);
+    if (r != HRF_RESULT_OK)
+    {
+        return RADIO_RESULT_ERR_READ_FAILED;
+    }
+    return RADIO_RESULT_OK;
+}
 
 /*---------------------------------------------------------------------------*/
-//TODO: low level receive, just receive a payload
-//
-//RADIO_RESULT radio_receive_payload(uint8_t* buf, uint8_t len)
-//{
-// def receive():
-//     """Receive a single payload from the buffer using the present modulation scheme"""
-//     return HRF_receive_payload()
-//    return RADIO_RESULT_ERR_UNIMPLEMENTED;
-//}
+// read a single payload from the payload buffer
+// this reads count byte preceeded payloads.
+// The CBP payload always has the count byte in the first byte
+// and this is returned in the user buffer too.
+
+RADIO_RESULT radio_get_payload_cbp(uint8_t* buf, uint8_t buflen)
+{
+    ////if (buflen > MAX_FIFO_BUFFER)
+    ////{  /* At the moment, the receiver cannot reliably cope with payloads > 1 FIFO buffer.
+    ////    * It *might* be able to in the future.
+    ////    */
+    ////    return RADIO_RESULT_ERR_LONG_PAYLOAD;
+    ////}
+    HRF_RESULT r = HRF_readfifo_burst_cbp(buf, buflen);
+    if (r != HRF_RESULT_OK)
+    {
+        TRACE_OUTS("radio_get_payload_cbp failed, error=");
+        TRACE_OUTN(r);
+        TRACE_NL();
+        return RADIO_RESULT_ERR_READ_FAILED;
+    }
+    return RADIO_RESULT_OK;
+}
 
 
 /*---------------------------------------------------------------------------*/
diff --git a/src/energenie/drv/radio.h b/src/energenie/drv/radio.h
index deb0168..7097075 100644
--- a/src/energenie/drv/radio.h
+++ b/src/energenie/drv/radio.h
@@ -8,7 +8,13 @@
 #define _RADIO_H
 
 typedef uint8_t RADIO_RESULT;
-#define RADIO_RESULT_ERR_UNIMPLEMENTED 0x81
+#define RADIO_RESULT_IS_ERR(R)         (((R) & 0x80) != 0)
+#define RADIO_RESULT_OK                0x00
+#define RADIO_RESULT_OK_FALSE          0x00
+#define RADIO_RESULT_OK_TRUE           0x01
+#define RADIO_RESULT_ERR_UNIMPLEMENTED 0x80
+#define RADIO_RESULT_ERR_LONG_PAYLOAD  0x81
+#define RADIO_RESULT_ERR_READ_FAILED   0x82
 
 typedef uint8_t RADIO_MODULATION;
 #define RADIO_MODULATION_OOK 0
@@ -29,13 +35,13 @@
 
 extern void radio_send_payload(uint8_t* payload, uint8_t len, uint8_t times);
 
-//extern void radio_receiver(RADIO_MODULATION mod);
+extern void radio_receiver(RADIO_MODULATION mod);
 
-//extern RADIO_RESULT radio_isReceiveWaiting(void);
+extern RADIO_RESULT radio_is_receive_waiting(void);
 
-//extern RADIO_RESULT radio_receive(uint8_t* buf, uint8_t len);
+extern RADIO_RESULT radio_get_payload_len(uint8_t* buf, uint8_t buflen);
 
-//extern RADIO_RESULT radio_receive_payload(uint8_t* buf, uint8_t len);
+extern RADIO_RESULT radio_get_payload_cbp(uint8_t* buf, uint8_t buflen);
 
 extern void radio_standby(void);
 
diff --git a/src/energenie/drv/radio_rpi.so b/src/energenie/drv/radio_rpi.so
index 0129587..b18de90 100755
--- a/src/energenie/drv/radio_rpi.so
+++ b/src/energenie/drv/radio_rpi.so
Binary files differ
diff --git a/src/energenie/drv/spi_test.c b/src/energenie/drv/spi_test.c
index cef2dc1..a371957 100644
--- a/src/energenie/drv/spi_test.c
+++ b/src/energenie/drv/spi_test.c
@@ -14,6 +14,7 @@
 #include "gpio.h"
 #include "spi.h"
 #include "trace.h"
+#include "delay.h"
 
 
 /***** CONSTANTS *****/
diff --git a/src/energenie/drv/spih_arduino.c b/src/energenie/drv/spih_arduino.c
deleted file mode 100644
index 74643ce..0000000
--- a/src/energenie/drv/spih_arduino.c
+++ /dev/null
@@ -1,6 +0,0 @@
-// placeholder for arduino SPI hardware driver
-//
-// This will be used to aid porting to the arduino.
-// The arduino has more predictable timing than the Raspberry Pi,
-// and thus it is a much better test suite platform.
-
diff --git a/src/energenie/radio.py b/src/energenie/radio.py
index 83c6f6a..6b6f294 100644
--- a/src/energenie/radio.py
+++ b/src/energenie/radio.py
@@ -1,493 +1,271 @@
-# test1.py  26/09/2015  D.J.Whale
+# radio2.py  15/04/2015  D.J.Whale
 #
-# Simple low level test of the HopeRF interface
-# Uses direct SPI commands to exercise the interface.
+# New version of the radio driver, with most of the fast stuff pushed into C.
 #
-# 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.
+# NOTE 1: This is only used for OOK transmit & FSK transmit at the moment.
+# FSK receive is currently being re-implemented in radio.c
 
-import spi
+# NOTE 2: Also there is an idea to do a python wrapper, build the C code
+# for an Arduino and wrap it with a simple serial message handler.
+# This would then make it possible to use the Energenie Radio on a Mac/PC/Linux
+# machine but by still using the same higher level Python code.
+# All you would need is a different radio.py that marshalled data to and from
+# the Arduino via pyserial.
 
-def warning(msg):
-    print("warning:" + str(msg))
+#TODO: Should really add parameter validation here, so that C code doesn't have to.
+#although it will be faster in C (C could be made optional, like an assert?)
+
+LIBNAME = "drv/radio_rpi.so"
+##LIBNAME = "drv/radio_mac.so" # testing
+
+import time
+import ctypes
+from os import path
+mydir = path.dirname(path.abspath(__file__))
+
+libradio                     = ctypes.cdll.LoadLibrary(mydir + "/" + LIBNAME)
+radio_init_fn                = libradio["radio_init"]
+radio_reset_fn               = libradio["radio_reset"]
+radio_get_ver_fn             = libradio["radio_get_ver"]
+radio_modulation_fn          = libradio["radio_modulation"]
+radio_transmitter_fn         = libradio["radio_transmitter"]
+radio_transmit_fn            = libradio["radio_transmit"]
+radio_send_payload_fn        = libradio["radio_send_payload"]
+radio_receiver_fn            = libradio["radio_receiver"]
+radio_is_receive_waiting_fn  = libradio["radio_is_receive_waiting"]
+radio_get_payload_len_fn     = libradio["radio_get_payload_len"]
+radio_get_payload_cbp_fn     = libradio["radio_get_payload_cbp"]
+radio_standby_fn             = libradio["radio_standby"]
+radio_finished_fn            = libradio["radio_finished"]
+
+RADIO_MODULATION_OOK = 0
+RADIO_MODULATION_FSK = 1
+
+# A temporary limit, the receiver will only receive 1 FIFO worth of data maximul
+# This includes the length byte at the start of an OpenThings message
+MAX_RX_SIZE = 66
+
+
+#TODO RADIO_RESULT_XX
 
 def trace(msg):
     print(str(msg))
 
 
-def ashex(p):
+def tohex(l):
     line = ""
-    for b in p:
-        line += str(hex(b)) + " "
+    for item in l:
+        line += hex(item) + " "
     return line
 
 
-#----- HOPERF REGISTER INTERFACE ----------------------------------------------
-# Precise register descriptions can be found in:
-# 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_VERSION               =  0x10
-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
-##TODO: This calculation looks wrong, inherited from the original C code.
-#It accounts for the 'magic' byte that C used for the SPI address.
-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 two? bytes in FIFO
-VAL_FIFOTHRESH30            = 0x1E	# Condition to start packet transmission: wait for >30 bytes in FIFO
+def unimplemented(m):
+    print("warning: method is not implemented:%s" % m)
+    return m
 
 
-#----- HOPERF RADIO INTERFACE -------------------------------------------------
-
-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 deprecated(m):
+    """Load-time warning about deprecated method"""
+    print("warning: method is deprecated:%s" % m)
+    return m
 
 
-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 untested(m):
+    """Load-time warning about untested function"""
+    print("warning: method is untested:%s" % m)
+    return m
 
 
-def HRF_writefifo_burst(buf):
-    """Write all bytes in buf to the payload FIFO, in a single burst"""
-    # Don't modify buf, in case caller reuses it
-    txbuf = [ADDR_FIFO | MASK_WRITE_DATA]
-    for b in buf:
-      txbuf.append(b)
-    #print("write FIFO %s" % ashex(txbuf))
+def disabled(m):
+    """Load-time waring about disabled function"""
+    print("warning: method is disabled:%s" % m)
+    def nothing(*args, **kwargs):pass
+    return nothing
 
-    spi.select()
-    spi.frame(txbuf)
-    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(ashex(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
-
-
-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_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)
-    #trace("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")
-    #trace("payload:%s" % ashex(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")
-
-
-
-#----- ENERGENIE SPECIFIC CONFIGURATIONS --------------------------------------
-
-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
-]
-
-config_OOK = [
-    [ADDR_REGDATAMODUL,       VAL_REGDATAMODUL_OOK],	     # modulation scheme OOK
-    [ADDR_FDEVMSB,            0],                            # frequency deviation -> 0kHz
-    [ADDR_FDEVLSB,            0],                            # frequency deviation -> 0kHz
-    [ADDR_FRMSB,              VAL_FRMSB433],                 # carrier freq -> 433.92MHz 0x6C7AE1
-    [ADDR_FRMID,              VAL_FRMID433],                 # carrier freq -> 433.92MHz 0x6C7AE1
-    [ADDR_FRLSB,              VAL_FRLSB433],                 # carrier freq -> 433.92MHz 0x6C7AE1
-    [ADDR_RXBW,               VAL_RXBW120],                  # channel filter bandwidth 120kHz
-    [ADDR_BITRATEMSB, 	      0x1A],                         # 4800b/s
-    [ADDR_BITRATELSB,         0x0B],                         # 4800b/s
-    [ADDR_PREAMBLELSB, 	      0],                            # preamble size LSB 3
-    [ADDR_SYNCCONFIG, 	      VAL_SYNCCONFIG4],		         # Size of the Sync word = 4 (SyncSize + 1)
-    [ADDR_SYNCVALUE1, 	      VAL_SYNCVALUE1OOK],            # sync value 1
-    [ADDR_SYNCVALUE2, 	      0],                            # sync value 2
-    [ADDR_SYNCVALUE3, 	      0],                            # sync value 3
-    [ADDR_SYNCVALUE4, 	      0],                            # sync value 4
-    [ADDR_PACKETCONFIG1,      VAL_PACKETCONFIG1OOK],	     # Fixed length, no Manchester coding, OOK
-    [ADDR_PAYLOADLEN, 	      VAL_PAYLOADLEN_OOK],	         # Payload Length
-    [ADDR_FIFOTHRESH, 	      VAL_FIFOTHRESH30],             # Condition to start packet transmission: wait for 30 bytes in FIFO
-]
-
-
-def HRF_config(config):
-    """Load a table of configuration values into HRF registers"""
-    for cmd in config:
-        HRF_writereg(cmd[0], cmd[1])
-        HRF_wait_ready()
-
-
-#ORIGINAL C CODE
-#void HRF_send_OOK_msg(uint8_t relayState)
-#{
-#	uint8_t buf[17];
-#	uint8_t i;
-#
-#	HRF_config_OOK();
-#
-#	buf[1] = 0x80;										// Preambule 32b enclosed in sync words
-#	buf[2] = 0x00;
-#	buf[3] = 0x00;
-#	buf[4] = 0x00;
-#
-#	for (i = 5; i <= 14; ++i){
-#		buf[i] = 8 + (i&1) * 6 + 128 + (i&2) * 48;		// address 20b * 4 = 10 Bytes
-#	}
-#
-#	if (relayState == 1)
-#	{
-#		printf("relay ON\n\n");
-#		buf[15] = 0xEE;		// D0-high, D1-h		// S1 on
-#		buf[16] = 0xEE;		// D2-h, D3-h
-#	}
-#	else
-#	{
-#		printf("relay OFF\n\n");
-#		buf[15] = 0xEE;		// D0-high, D1-h		// S1 off
-#		buf[16] = 0xE8;		// D2-h, D3-l
-#	}
-#
-#	HRF_wait_for (ADDR_IRQFLAGS1, MASK_MODEREADY | MASK_TXREADY, true);		// wait for ModeReady + TX ready
-#	HRF_reg_Wn(buf + 4, 0, 12);						// don't include sync word (4 bytes) into data buffer
-#
-#	for (i = 0; i < 8; ++i)							// Send the same message few more times
-#	{
-#		HRF_wait_for(ADDR_IRQFLAGS2, MASK_FIFOLEVEL, false);
-#		HRF_reg_Wn(buf, 0, 16);						// with sync word
-#	}
-#
-#	HRF_wait_for (ADDR_IRQFLAGS2, MASK_PACKETSENT, true);		// wait for Packet sent
-#	HRF_assert_reg_val(ADDR_IRQFLAGS2, MASK_FIFONOTEMPTY | MASK_FIFOOVERRUN, false, "are all bytes sent?");
-#	HRF_config_FSK();
-#	HRF_wait_for (ADDR_IRQFLAGS1, MASK_MODEREADY, true);		// wait for ModeReady
-#}
-
-
-# first payload
-# (radio sync 4 bytes, not counted)
-# address 10 bytes
-# command 2 bytes
-# i.e. 12 bytes
-# so, >30 is 2 and a bit payloads loaded.
-# 66 byte FIFO size
-# so that means FIFO roughly half full before starts tx,
-# FIFO at or below half full before another payload will be added
-
-# packetsent is based on the fixed payload length
-# which is (13 + 8 * 17) = 149
-# This maths looks wrong.
-# 10 bytes of address, two bytes of command = 12
-# 13 includes the dummy byte at the start, but that is for the SPI address and not counted
-
-def HRF_send_OOK_payload(payload):
-    """Send a payload multiple times"""
-
-    #TODO: note the zero at the start was the C method of reserving space for address byte
-    p1 = [0x00] + payload
-    # This sync pattern does not match C code, but it works.
-    # The sync pattern from the C code does not work here
-    # Note that buf[0] in the C is undefined due to being uninitialised
-    # but it is a space for the address byte in the C fifo burst routine
-    pn = [0x00,0x80,0x00,0x00,0x00] + payload # from the C
-    #TODO: note the zero at the start was the C method of reserving space for address byte
-    # which is not needed here in this python.
-    # This is old test data due to wrong baud rate - deprecated
-    #pn = [0x80,0x80,0x80,0x80,0x80] + payload
-
-    HRF_pollreg(ADDR_IRQFLAGS1, MASK_MODEREADY|MASK_TXREADY, MASK_MODEREADY|MASK_TXREADY)
-    HRF_writefifo_burst(p1)
-    
-    for i in range(8):
-        # waits for <31 bytes in FIFO
-        HRF_pollreg(ADDR_IRQFLAGS2, MASK_FIFOLEVEL, 0)
-        HRF_writefifo_burst(pn)
-
-    HRF_pollreg(ADDR_IRQFLAGS2, MASK_PACKETSENT, MASK_PACKETSENT) # wait for Packet 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 repeated payload to HRF")
-
-    # Note: packetsent is only cleared on exit from TX (i.e to STANDBY or RECEIVE)
-
-
-
-#----- RADIO API --------------------------------------------------------------
-
-mode = None
-modulation_fsk = None
 
 def init():
     """Initialise the module ready for use"""
-    spi.init_defaults()
-    trace("RESET")
-
-    # Note that if another program left GPIO pins in a different state
-    # and did a dirty exit, the reset fails to work and the clear fifo hangs.
-    # Might have to make the spi.init() set everything to inputs first,
-    # then set to outputs, to make sure that the
-    # GPIO registers are in a deterministic start state.
-    spi.reset() # send a hardware reset to ensure radio in clean state
-
-    HRF_clear_fifo()
+    #extern void radio_init(void);
+    radio_init_fn()
 
 
 def reset():
-    """Reset the radio chip"""
-    spi.reset()
+    """Reset the radio device"""
+    #extern void radio_reset(void);
+    radio_reset_fn()
 
 
 def get_ver():
-    """Get the version number of the radio chip"""
-    return HRF_readreg(ADDR_VERSION)
+    """Read out the version number of the radio"""
+    return radio_get_ver_fn()
 
 
 def modulation(fsk=None, ook=None):
     """Switch modulation, if needed"""
-    global modulation_fsk
-
-    # Handle sensible module defaults for earlier versions of user code
-    if fsk == None and ook == None:
-        # Force FSK mode
-        fsk = True
-
-    if fsk != None and fsk:
-        if modulation_fsk == None or modulation_fsk == False:
-            trace("switch to FSK")
-            HRF_config(config_FSK)
-            modulation_fsk = True
-
-    elif ook != None and ook:
-        if modulation_fsk == None or modulation_fsk == True:
-            trace("switch to OOK")
-            HRF_config(config_OOK)
-            modulation_fsk = False
+    #extern void radio_modulation(RADIO_MODULATION mod);
+    if ook:
+        m = ctypes.c_int(RADIO_MODULATION_OOK)
+    elif fsk:
+        m = ctypes.c_int(RADIO_MODULATION_FSK)
+    else:
+        raise RuntimeError("Must choose fsk or ook mode")
+    radio_modulation_fn(m)
 
 
 def transmitter(fsk=None, ook=None):
     """Change into transmitter mode"""
-    global mode
-
-    trace("transmitter mode")
-    modulation(fsk, ook)
-    HRF_change_mode(MODE_TRANSMITER)
-    mode = "TRANSMITTER"
-    HRF_wait_txready()
+    #extern void radio_transmitter(RADIO_MODULATION mod);
+    if ook:
+        m = ctypes.c_int(RADIO_MODULATION_OOK)
+    elif fsk:
+        m = ctypes.c_int(RADIO_MODULATION_FSK)
+    else: # defaults to FSK
+        m = ctypes.c_int(RADIO_MODULATION_FSK)
+    radio_transmitter_fn(m)
 
 
-def transmit(payload):
+def transmit(payload, outer_times=1, inner_times=8, outer_delay=0):
     """Transmit a single payload using the present modulation scheme"""
-    spi.start_transaction()
-    if not modulation_fsk:
-        HRF_send_OOK_payload(payload)
-    else:
-        HRF_send_payload(payload)
-    spi.end_transaction()
+    #Note, this optionally does a mode change before and after
+    #extern void radio_transmit(uint8_t* payload, uint8_t len, uint8_t repeats);
+
+    framelen = len(payload)
+    if framelen < 1 or framelen > 255:
+        raise ValueError("frame len must be 1..255")
+    if outer_times < 1:
+        raise ValueError("outer_times must be >0")
+    if inner_times < 1 or inner_times > 255:
+        raise ValueError("tx times must be 0..255")
+
+    framelen     = len(payload)
+    Frame        = ctypes.c_ubyte * framelen
+    txframe      = Frame(*payload)
+    inner_times  = ctypes.c_ubyte(inner_times)
+    
+    for i in range(outer_times):
+        #TODO: transmit() will mode change if required
+        #this means that outer_times will keep popping and pushing the mode
+        #that might be ok, as it will force all the flags to clear?
+        radio_transmit_fn(txframe, framelen, inner_times)
+        if outer_delay != 0:
+            time.sleep(outer_delay)
+
+
+def send_payload(payload, outer_times=1, inner_times=8, outer_delay=0):
+    """Transmit a payload in present modulation scheme, repeated"""
+    #Note, this does not do a mode change before or after,
+    #and assumes the mode is already transmit
+    #extern void radio_send_payload(uint8_t* payload, uint8_t len, uint8_t times);
+
+    framelen = len(payload)
+    if framelen < 1 or framelen > 255:
+        raise ValueError("frame len must be 1..255")
+    if outer_times < 1:
+        raise ValueError("outer_times must be >0")
+    if inner_times < 1 or inner_times > 255:
+        raise ValueError("tx times must be 0..255")
+    Frame          = ctypes.c_ubyte * framelen
+    txframe        = Frame(*payload)
+    inner_times    = ctypes.c_ubyte(inner_times)
+
+    for i in range(outer_times):
+        radio_send_payload_fn(txframe, framelen, inner_times)
+        if outer_delay != 0:
+            time.sleep(outer_delay)
 
 
 def receiver(fsk=None, ook=None):
     """Change into receiver mode"""
-    global mode
+    #extern void radio_receiver(RADIO_MODULATION mod);
+    if ook:
+        m = ctypes.c_int(RADIO_MODULATION_OOK)
+    elif fsk:
+        m = ctypes.c_int(RADIO_MODULATION_FSK)
+    else: # defaults to FSK
+        m = ctypes.c_int(RADIO_MODULATION_FSK)
 
-    trace("receiver mode")
-    modulation(fsk, ook)
-    HRF_change_mode(MODE_RECEIVER)
-    HRF_wait_ready()
-    mode = "RECEIVER"
+    radio_receiver_fn(m)
 
 
-def isReceiveWaiting():
+def is_receive_waiting():
     """Check to see if a payload is waiting in the receive buffer"""
-    spi.start_transaction()
-    waiting = HRF_check_payload()
-    spi.end_transaction()
-    return waiting
+    #extern RADIO_RESULT radio_is_receive_waiting(void);
+    res = radio_is_receive_waiting_fn()
+    # this is RADIO_RESULT_OK_TRUE or RADIO_RESULT_OK_FALSE
+    # so it is safe to evaluate it as a boolean number.
+    return (res != 0)
 
 
-def receive():
-    """Receive a single payload from the buffer using the present modulation scheme"""
-    spi.start_transaction()
-    payload = HRF_receive_payload()
-    spi.end_transaction()
-    return payload
+def receive(size=None):
+    """Receive a single payload"""
+
+    if size == None:
+        return receive_cbp()
+    else:
+        return receive_len(size)
+
+
+def receive_cbp():
+    """Receive a count byte preceded payload"""
+    ##trace("receive_cbp")
+
+    ##bufsize = MAX_RX_SIZE
+    bufsize = 255 # testing
+    Buffer = ctypes.c_ubyte * bufsize
+    rxbuf  = Buffer()
+    buflen = ctypes.c_ubyte(bufsize)
+    #RADIO_RESULT radio_get_payload_cbp(uint8_t* buf, uint8_t buflen)
+
+    result = radio_get_payload_cbp_fn(rxbuf, buflen)
+
+    if result != 0: # RADIO_RESULT_OK
+        raise RuntimeError("Receive failed, radio.c error code %s" % hex(result))
+
+    size = 1+rxbuf[0] # The count byte in the payload
+
+    # turn buffer into a list of bytes, using 'size' as the counter
+    rxlist = []
+    for i in range(size):
+        rxlist.append(rxbuf[i])
+
+    ##trace("receive_cbp returhs %s" % tohex(rxlist))
+    return rxlist # Python len(rxlist) tells us how many bytes including length byte if present
+
+
+@untested
+def receive_len(size):
+    """Receive a fixed payload size"""
+
+    bufsize = size
+
+    Buffer = ctypes.c_ubyte * bufsize
+    rxbuf  = Buffer()
+    buflen = ctypes.c_ubyte(bufsize)
+    #RADIO_RESULT radio_get_payload_len(uint8_t* buf, uint8_t buflen)
+
+    result = radio_get_payload_len_fn(rxbuf, buflen)
+
+    if result != 0: # RADIO_RESULT_OK
+        raise RuntimeError("Receive failed, error code %s" % hex(result))
+
+    # turn buffer into a list of bytes, using 'size' as the counter
+    rxlist = []
+    for i in range(size):
+        rxlist.append(rxbuf[i])
+
+    return rxlist # Python len(rxlist) tells us how many bytes including length byte if present
+
+
+def standby():
+    """Put radio into standby mode"""
+    #extern void radio_standby(void);
+    radio_standby_fn()
 
 
 def finished():
     """Close the library down cleanly when finished"""
-    spi.finished()
+    #extern void radio_finished(void);
+    radio_finished_fn()
 
 
 # END
diff --git a/src/energenie/radio2.py b/src/energenie/radio2.py
deleted file mode 100644
index 7e2439c..0000000
--- a/src/energenie/radio2.py
+++ /dev/null
@@ -1,177 +0,0 @@
-# radio2.py  15/04/2015  D.J.Whale
-#
-# New version of the radio driver, with most of the fast stuff pushed into C.
-#
-# NOTE 1: This is partially tested, and only used for OOK transmit at the moment.
-# FSK transmit and receive is inside radio.py, as the underlying radio.c code
-# does not yet support FSK mode.
-
-# NOTE 2: Also there is an idea to do a python wrapper, build the C code
-# for an Arduino and wrap it with a simple serial message handler.
-# This would then make it possible to use the Energenie Radio on a Mac/PC/Linux
-# machine but by still using the same higher level Python code.
-# All you would need is a different radio.py that marshalled data to and from
-# the Arduino via pyserial.
-
-#TODO: Should really add parameter validation here, so that C code doesn't have to.
-#although it will be faster in C (C could be made optional, like an assert?)
-
-LIBNAME = "radio_rpi.so"
-#LIBNAME = "drv/radio_mac.so" # testing
-
-import time
-import ctypes
-from os import path
-mydir = path.dirname(path.abspath(__file__))
-
-libradio                   = ctypes.cdll.LoadLibrary(mydir + "/" + LIBNAME)
-radio_init_fn              = libradio["radio_init"]
-radio_reset_fn             = libradio["radio_reset"]
-radio_get_ver_fn           = libradio["radio_get_ver"]
-radio_modulation_fn        = libradio["radio_modulation"]
-radio_transmitter_fn       = libradio["radio_transmitter"]
-radio_transmit_fn          = libradio["radio_transmit"]
-radio_send_payload_fn      = libradio["radio_send_payload"]
-#radio_receiver_fn          = libradio["radio_receiver"]
-#radio_isReceiveWaiting_fn  = libradio["radio_isReceiveWaiting"]
-#radio_receive_fn           = libradio["radio_receive"]
-#radio_receive_payload_fn   = libradio["radio_receive_payload"]
-radio_standby_fn           = libradio["radio_standby"]
-radio_finished_fn          = libradio["radio_finished"]
-
-RADIO_MODULATION_OOK = 0
-RADIO_MODULATION_FSK = 1
-
-
-def init():
-    """Initialise the module ready for use"""
-    #extern void radio_init(void);
-    radio_init_fn()
-
-
-def reset():
-    """Reset the radio device"""
-    #extern void radio_reset(void);
-    radio_reset_fn()
-
-
-def get_ver():
-    """Read out the version number of the radio"""
-    return radio_get_ver_fn()
-
-
-def modulation(fsk=None, ook=None):
-    """Switch modulation, if needed"""
-    #extern void radio_modulation(RADIO_MODULATION mod);
-    if ook:
-        m = ctypes.c_int(RADIO_MODULATION_OOK)
-    elif fsk:
-        m = ctypes.c_int(RADIO_MODULATION_FSK)
-    radio_modulation_fn(m)
-
-
-def transmitter(fsk=None, ook=None):
-    """Change into transmitter mode"""
-    #extern void radio_transmitter(RADIO_MODULATION mod);
-    if ook:
-        m = ctypes.c_int(RADIO_MODULATION_OOK)
-    elif fsk:
-        m = ctypes.c_int(RADIO_MODULATION_FSK)
-    radio_transmitter_fn(m)
-
-
-def transmit(payload, outer_times=1, inner_times=8, outer_delay=0):
-    """Transmit a single payload using the present modulation scheme"""
-    #Note, this optionally does a mode change before and after
-    #extern void radio_transmit(uint8_t* payload, uint8_t len, uint8_t repeats);
-
-    framelen = len(payload)
-    if framelen < 1 or framelen > 255:
-        raise ValueError("frame len must be 1..255")
-    if outer_times < 1:
-        raise ValueError("outer_times must be >0")
-    if inner_times < 1 or inner_times > 255:
-        raise ValueError("tx times must be 0..255")
-
-    framelen     = len(payload)
-    Frame        = ctypes.c_ubyte * framelen
-    txframe      = Frame(*payload)
-    inner_times  = ctypes.c_ubyte(inner_times)
-    
-    for i in range(outer_times):
-        radio_transmit_fn(txframe, framelen, inner_times)
-        if outer_delay != 0:
-            time.sleep(outer_delay)
-
-
-def send_payload(payload, outer_times=1, inner_times=8, outer_delay=0):
-    """Transmit a payload in present modulation scheme, repeated"""
-    #Note, this does not do a mode change before or after,
-    #and assumes the mode is already transmit
-    #extern void radio_send_payload(uint8_t* payload, uint8_t len, uint8_t times);
-
-    framelen = len(payload)
-    if framelen < 1 or framelen > 255:
-        raise ValueError("frame len must be 1..255")
-    if outer_times < 1:
-        raise ValueError("outer_times must be >0")
-    if inner_times < 1 or inner_times > 255:
-        raise ValueError("tx times must be 0..255")
-    Frame          = ctypes.c_ubyte * framelen
-    txframe        = Frame(*payload)
-    inner_times    = ctypes.c_ubyte(inner_times)
-
-    for i in range(outer_times):
-        radio_send_payload_fn(txframe, framelen, inner_times)
-        if outer_delay != 0:
-            time.sleep(outer_delay)
-
-
-#def receiver(fsk=None, ook=None):
-#    """Change into receiver mode"""
-#    #extern void radio_receiver(RADIO_MODULATION mod);
-#    if ook:
-#        m = ctypes.c_int(RADIO_MODULATION_OOK)
-#    elif fsk:
-#        m = ctypes.c_int(RADIO_MODULATION_FSK)
-#
-#    radio_receiver_fn(m)
-
-
-#def isReceiveWaiting():
-#    """Check to see if a payload is waiting in the receive buffer"""
-#    #extern RADIO_RESULT radio_isReceiveWaiting(void);
-#    pass # TODO
-#    # returns bool
-#    ##res = radio_isReceiveWaitingFn()
-
-
-#def receive():
-#    """Put radio into receive mode and receive"""
-#    #extern RADIO_RESULT radio_receive(uint8_t* buf, uint8_t len)
-#    pass # TODO
-#    ##radio_receive_fn(buf, len)
-#    # returns list of bytes
-
-
-#def radio_receive_payload():
-#    """Receive a single payload"""
-#    #extern RADIO_RESULT radio_receive_payload(uint8_t* buf, uint8_t len);
-#    pass # TODO
-#    ##radio_receive_payload_fn(buf, len)
-#    # returns list of bytes
-
-
-def standby():
-    """Put radio into standby mode"""
-    #extern void radio_standby(void);
-    radio_standby_fn()
-
-
-def finished():
-    """Close the library down cleanly when finished"""
-    #extern void radio_finished(void);
-    radio_finished_fn()
-
-
-# END
diff --git a/src/energenie/radio2_test.py b/src/energenie/radio2_test.py
deleted file mode 100644
index 1a13ceb..0000000
--- a/src/energenie/radio2_test.py
+++ /dev/null
@@ -1,71 +0,0 @@
-# radio2_test.py  15/04/2016  D.J.Whale
-#
-# A simple Energenie radio exerciser
-#
-# Repeatedly transmits OOK packets to turn switch 1 on and off.
-#
-
-import radio2 as radio
-import time
-
-# How many times to repeat the OOK payload.
-# 4800bps*8*16=26ms per payload
-# 75 payloads is 2 seconds
-# 255 payloads is 6.8 seconds
-TIMES = 75
-DELAY = 0.5
-
-# The 'radio' module knows nothing about the Energenie (HS1527) bit encoding,
-# so this test code manually encodes the bits.
-# For the full Python stack, there is an encoder module that can generate
-# specific payloads. Repeats are done in radio_transmitter.
-# The HRF preamble feature is no longer used, it's more predictable to
-# put the preamble in the payload.
-
-
-# preamble pulse with timing violation gap
-PREAMBLE = [0x80, 0x00, 0x00, 0x00]
-
-# Energenie 'random' 20 bit address is 0x6C6C6
-# 0110 1100 0110 1100 0110
-# 0 encoded as 8 (1000)
-# 1 encoded as E (1110)
-ADDR     = [0x8E, 0xE8, 0xEE, 0x88, 0x8E, 0xE8, 0xEE, 0x88, 0x8E, 0xE8]
-
-# Energenie 'switch 1 ON' command  F 1111  (0xEE, 0xEE)
-SW1_ON  = [0xEE, 0xEE]
-
-# Energenie 'switch 1 OFF' command E 1110  (0xEE, 0xE8)
-SW1_OFF = [0xEE, 0xE8]
-
-# manual preamble, 20 bit encoded address, 4 encoded data bits
-enc_1on  = PREAMBLE + ADDR + SW1_ON
-enc_1off = PREAMBLE + ADDR + SW1_OFF
-
-
-def radio_test_ook():
-    """Repeatedly test switch 1 ON then OFF"""
-
-    radio.init()
-    # init() defaults to standby()
-    try:
-        radio.modulation(ook=True)
-        while True:
-            print("Switch 1 ON")
-            radio.transmit(enc_1on, TIMES)
-            # auto returns to standby
-            if DELAY!=0: time.sleep(DELAY)
-
-            print("Switch 1 OFF")
-            radio.transmit(enc_1off, TIMES)
-            # auto returns to standby
-            if DELAY!=0: time.sleep(DELAY)
-
-    finally:
-        radio.finished()
-
-
-if __name__ == "__main__":
-    radio_test_ook()
-
-# END
diff --git a/src/energenie/radio_rpi.so b/src/energenie/radio_rpi.so
deleted file mode 100755
index 0129587..0000000
--- a/src/energenie/radio_rpi.so
+++ /dev/null
Binary files differ
diff --git a/src/energenie/radio_test.py b/src/energenie/radio_test.py
new file mode 100644
index 0000000..b02f117
--- /dev/null
+++ b/src/energenie/radio_test.py
@@ -0,0 +1,71 @@
+# radio_test.py  15/04/2016  D.J.Whale
+#
+# A simple Energenie radio exerciser
+#
+# Repeatedly transmits OOK packets to turn switch 1 on and off.
+#
+
+import radio
+import time
+
+# How many times to repeat the OOK payload.
+# 4800bps*8*16=26ms per payload
+# 75 payloads is 2 seconds
+# 255 payloads is 6.8 seconds
+TIMES = 75
+DELAY = 0.5
+
+# The 'radio' module knows nothing about the Energenie (HS1527) bit encoding,
+# so this test code manually encodes the bits.
+# For the full Python stack, there is an encoder module that can generate
+# specific payloads. Repeats are done in radio_transmitter.
+# The HRF preamble feature is no longer used, it's more predictable to
+# put the preamble in the payload.
+
+
+# preamble pulse with timing violation gap
+PREAMBLE = [0x80, 0x00, 0x00, 0x00]
+
+# Energenie 'random' 20 bit address is 0x6C6C6
+# 0110 1100 0110 1100 0110
+# 0 encoded as 8 (1000)
+# 1 encoded as E (1110)
+ADDR     = [0x8E, 0xE8, 0xEE, 0x88, 0x8E, 0xE8, 0xEE, 0x88, 0x8E, 0xE8]
+
+# Energenie 'switch 1 ON' command  F 1111  (0xEE, 0xEE)
+SW1_ON  = [0xEE, 0xEE]
+
+# Energenie 'switch 1 OFF' command E 1110  (0xEE, 0xE8)
+SW1_OFF = [0xEE, 0xE8]
+
+# manual preamble, 20 bit encoded address, 4 encoded data bits
+enc_1on  = PREAMBLE + ADDR + SW1_ON
+enc_1off = PREAMBLE + ADDR + SW1_OFF
+
+
+def radio_test_ook():
+    """Repeatedly test switch 1 ON then OFF"""
+
+    radio.init()
+    # init() defaults to standby()
+    try:
+        radio.modulation(ook=True)
+        while True:
+            print("Switch 1 ON")
+            radio.transmit(enc_1on, TIMES)
+            # auto returns to standby
+            if DELAY!=0: time.sleep(DELAY)
+
+            print("Switch 1 OFF")
+            radio.transmit(enc_1off, TIMES)
+            # auto returns to standby
+            if DELAY!=0: time.sleep(DELAY)
+
+    finally:
+        radio.finished()
+
+
+if __name__ == "__main__":
+    radio_test_ook()
+
+# END
diff --git a/src/energenie/spi.py b/src/energenie/spi.py
deleted file mode 100644
index 74f3a10..0000000
--- a/src/energenie/spi.py
+++ /dev/null
@@ -1,140 +0,0 @@
-# spi.py  19/07/2014  D.J.Whale
-# 
-# a C based SPI driver, with a python wrapper
-
-LIBNAME = "spi_rpi.so"
-
-import ctypes
-import time
-
-from os import path
-mydir = path.dirname(path.abspath(__file__))
-
-RESET     = 25 # BCM GPIO
-LED_GREEN = 27 # BCM GPIO (not B rev1)
-LED_RED   = 22 # BCM GPIO
-
-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"]
-
-# Might put this in a separate rpi_gpio.so and dynamically link both
-# from rpi_spi and from python, but have to be careful with sharing
-# memmap. For now, we only use this to control the radio RESET,
-# so let's leave this embedded inside the rpi_spi.so until we
-# need any more visibility
-#gpio_init_fn            = libspi["gpio_init"]
-#gpio_setin_fn           = libspi["gpio_setin"]
-gpio_setout_fn          = libspi["gpio_setout"]
-gpio_high_fn            = libspi["gpio_high"]
-gpio_low_fn             = libspi["gpio_low"]
-#gpio_write_fn           = libspi["gpio_write"]
-#gpio_read_fn            = libspi["gpio_read"]
-
-
-def trace(msg):
-  pass #print("spi:" + msg)
-
-
-# Note, this is radio specific, not SPI specific. hmm.
-# fine for now to test the theory, but architecturally this is wrong.
-# might have an optional reset line in spi_config, and an spi_reset()
-# in spi.c that excercises it if required, as a more generic way
-# to do this, as most SPI devices have a reset line. If value not
-# defined, nothing happens. If value defined, it must have a
-# polarity too, and spi.c will set it to an output inactive
-# until reset is required. Also need to set up a reset active
-# time and a 'after reset' guard time. That will cover most
-# cases generically without having to surface the whole inner GPIO
-# out to the python.
-
-def reset():
-  trace("reset")
-
-  reset = ctypes.c_int(RESET)
-  gpio_setout_fn(reset)
-  gpio_high_fn(reset)
-  time.sleep(0.1)
-  gpio_low_fn(reset)
-  time.sleep(0.1)
-
-  # Put LEDs into known off state
-  led_red = ctypes.c_int(LED_RED)
-  led_green = ctypes.c_int(LED_GREEN)
-  gpio_setout_fn(led_red)
-  gpio_low_fn(led_red)
-  gpio_setout_fn(led_green)
-  gpio_low_fn(led_green)
-
-
-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 start_transaction():
-  """Start a transmit or receive, perhaps multiple bursts"""
-  # turn the GREEN LED on
-  led_green = ctypes.c_int(LED_GREEN)
-  gpio_high_fn(led_green)
-
-
-def end_transaction():
-  """End a transmit or receive, perhaps multiple listens"""
-  # turn the GREEN LED off
-  led_green = ctypes.c_int(LED_GREEN)
-  gpio_low_fn(led_green)
-
-
-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()
-
-
-# END
-
-
diff --git a/src/energenie/spi_rpi.so b/src/energenie/spi_rpi.so
deleted file mode 100755
index 6f81455..0000000
--- a/src/energenie/spi_rpi.so
+++ /dev/null
Binary files differ
diff --git a/src/legacy.py b/src/legacy.py
index 0e7b7b2..a665bd7 100644
--- a/src/legacy.py
+++ b/src/legacy.py
@@ -8,9 +8,7 @@
 
 import time
 
-from energenie import encoder
-# moving over to the new, faster, C radio driver
-from energenie import radio2 as radio
+from energenie import encoder, radio
 
 # How many times to send messages in the driver fast loop
 # Present version of driver limits to 15
@@ -34,7 +32,7 @@
 # Prebuild all possible message up front, to make switching code faster
 
 HOUSE_ADDRESS = None # Use default energenie quasi-random address 0x6C6C6
-#HOUSE_ADDRESS = 0xA0170 # Captured address of David's RF hand controller
+##HOUSE_ADDRESS = 0xA0170 # Captured address of David's RF hand controller
 
 ALL_ON     = encoder.build_switch_msg(True,                    house_address=HOUSE_ADDRESS)
 ONE_ON     = encoder.build_switch_msg(True,  device_address=1, house_address=HOUSE_ADDRESS)
@@ -138,13 +136,12 @@
     radio.modulation(ook=True)
 
     try:
-        #pattern_test()
+        ##pattern_test()
         legacy_learn_mode()
         legacy_switch_loop()
-        #switch1_loop()
+        ##switch1_loop()
     finally:
         radio.finished()
 
-
 # END
 
diff --git a/src/monitor.py b/src/monitor.py
index 43ed0ee..9dbf3ce 100644
--- a/src/monitor.py
+++ b/src/monitor.py
@@ -9,133 +9,20 @@
 # However, it will log all messages from MiHome monitor, adaptor plus and house monitor
 # to a CSV log file, so could be the basis for a non-controlling energy logging app.
 
-import time
+from energenie import Registry, Devices, Messages, OpenThings, radio
 
-from energenie import OpenThings
-from energenie import Devices, Messages, radio
-import os
-
-LOG_FILENAME = "energenie.csv"
+import Logger
 
 def warning(msg):
     print("warning:%s" % str(msg))
 
+
 def trace(msg):
     print("monitor:%s" % str(msg))
 
-log_file = None
-
-def logMessage(msg):
-    HEADINGS = 'timestamp,mfrid,prodid,sensorid,flags,switch,voltage,freq,reactive,real'
-
-    global log_file
-    if log_file == None:
-        if not os.path.isfile(LOG_FILENAME):
-            log_file = open(LOG_FILENAME, 'w')
-            log_file.write(HEADINGS + '\n')
-        else:
-            log_file = open(LOG_FILENAME, 'a') # append
-
-    # get the header
-    header    = msg['header']
-    timestamp = time.time()
-    mfrid     = header['mfrid']
-    productid = header['productid']
-    sensorid  = header['sensorid']
-
-    # set defaults for any data that doesn't appear in this message
-    # but build flags so we know which ones this contains
-    flags = [0 for i in range(7)]
-    switch = None
-    voltage = None
-    freq = None
-    reactive = None
-    real = None
-    apparent = None
-    current = None
-
-    # capture any data that we want
-    #print(msg)
-    for rec in msg['recs']:
-        paramid = rec['paramid']
-        try:
-            value = rec['value']
-        except:
-            value = None
-            
-        if   paramid == OpenThings.PARAM_SWITCH_STATE:
-            switch = value
-            flags[0] = 1
-        elif paramid == OpenThings.PARAM_VOLTAGE:
-            flags[1] = 1
-            voltage = value
-        elif paramid == OpenThings.PARAM_FREQUENCY:
-            flags[2] = 1
-            freq = value
-        elif paramid == OpenThings.PARAM_REACTIVE_POWER:
-            flags[3] = 1
-            reactive = value
-        elif paramid == OpenThings.PARAM_REAL_POWER:
-            flags[4] = 1
-            real = value
-        elif paramid == OpenThings.PARAM_APPARENT_POWER:
-            flags[5] = 1
-            apparent = value
-        elif paramid == OpenThings.PARAM_CURRENT:
-            flags[6] = 1
-            current = value
-
-    # generate a line of CSV
-    flags = "".join([str(a) for a in flags])
-    csv = "%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s" % (timestamp, mfrid, productid, sensorid, flags, switch, voltage, freq, reactive, real, apparent, current)
-    log_file.write(csv + '\n')
-    log_file.flush()
-    trace(csv) # testing
-
 
 #----- TEST APPLICATION -------------------------------------------------------
 
-directory = {}
-
-def allkeys(d):
-    result = ""
-    for k in d:
-        if len(result) != 0:
-            result += ','
-        result += str(k)
-    return result
-
-        
-def updateDirectory(message):
-    """Update the local directory with information about this device"""
-    now      = time.time()
-    header   = message["header"]
-    sensorId = header["sensorid"]
-
-    if not directory.has_key(sensorId):
-        # new device discovered
-        desc = Devices.getDescription(header["mfrid"], header["productid"])
-        print("ADD device:%s %s" % (hex(sensorId), desc))
-        directory[sensorId] = {"header": message["header"]}
-        #trace(allkeys(directory))
-
-    directory[sensorId]["time"] = now
-    #TODO would be good to keep recs, but need to iterate through all and key by paramid,
-    #not as a list index, else merging will be hard.
-
-
-def send_join_ack(mfrid, productid, sensorid):
-    # send back a JOIN ACK, so that join light stops flashing
-    response = OpenThings.alterMessage(Messages.JOIN_ACK,
-        header_mfrid=mfrid,
-        header_productid=productid,
-        header_sensorid=sensorid)
-    p = OpenThings.encode(response)
-    radio.transmitter()
-    radio.transmit(p)
-    radio.receiver()
-
-
 def monitor_loop():
     """Capture any incoming messages and log to CSV file"""
 
@@ -143,8 +30,8 @@
 
     while True:
         # See if there is a payload, and if there is, process it
-        if radio.isReceiveWaiting():
-            #trace("receiving payload")
+        if radio.is_receive_waiting():
+            trace("receiving payload")
             payload = radio.receive()
             try:
                 decoded = OpenThings.decode(payload)
@@ -154,9 +41,9 @@
                       
             OpenThings.showMessage(decoded)
             # Any device that reports will be added to the non-persistent directory
-            updateDirectory(decoded)
-            #trace(decoded)
-            logMessage(decoded)
+            Registry.update(decoded)
+            ##trace(decoded)
+            Logger.logMessage(decoded)
 
             # Process any JOIN messages by sending back a JOIN-ACK to turn the LED off
             if len(decoded["recs"]) == 0:
@@ -164,14 +51,11 @@
                 print("Empty record:%s" % decoded)
             else:
                 # assume only 1 rec in a join, for now
-                #TODO: use OpenThings.getFromMessage("header_mfrid")
                 if decoded["recs"][0]["paramid"] == OpenThings.PARAM_JOIN:
-                    header    = decoded["header"]
-                    mfrid     = header["mfrid"]
-                    productid = header["productid"]
-                    sensorid  = header["sensorid"]
-                    send_join_ack(mfrid, productid, sensorid)
-
+                    mfrid     = OpenThings.getFromMessage(decoded, "header_mfrid")
+                    productid = OpenThings.getFromMessage(decoded, "header_productid")
+                    sensorid  = OpenThings.getFromMessage(decoded, "header_sensorid")
+                    Messages.send_join_ack(radio, mfrid, productid, sensorid)
 
 
 if __name__ == "__main__":
diff --git a/src/switch.py b/src/switch.py
index f283936..2c6c2ef 100644
--- a/src/switch.py
+++ b/src/switch.py
@@ -10,130 +10,83 @@
 # Don't expect this to be a good starting point for an application.
 # Consider waiting for me to finish developing the device object interface first.
 
-import time
-
-from energenie import OpenThings
-from energenie import Devices, Messages, radio
+from energenie import Devices, Messages, Registry, OpenThings, radio
 from Timer import Timer
 
+
 # Increase this if you have lots of switches, so that the receiver has enough
 # time to receive update messages, otherwise your devices won't make it into
 # the device directory.
-TX_RATE = 10 # seconds between each switch change cycle
+TX_RATE = 2 # seconds between each switch change cycle
+
 
 def warning(msg):
     print("warning:%s" % str(msg))
 
+
 def trace(msg):
     print("monitor:%s" % str(msg))
 
 
 #----- TEST APPLICATION -------------------------------------------------------
 
-directory = {}
+def switch_sniff_loop():
+    """Listen to sensor messages and add them to the Registry"""
 
-def allkeys(d):
-    result = ""
-    for k in d:
-        if len(result) != 0:
-            result += ','
-        result += str(k)
-    return result
+    # See if there is a payload, and if there is, process it
+    if radio.is_receive_waiting():
+        ##trace("receiving payload")
+        payload = radio.receive()
+        try:
+            decoded = OpenThings.decode(payload)
+        except OpenThings.OpenThingsException as e:
+            warning("Can't decode payload:" + str(e))
+            return
 
-        
-def updateDirectory(message):
-    """Update the local directory with information about this device"""
-    now      = time.time()
-    header   = message["header"]
-    sensorId = header["sensorid"]
+        OpenThings.showMessage(decoded)
+        # Any device that reports will be added to the non-persistent directory
+        Registry.update(decoded)
+        ##trace(decoded)
 
-    if not directory.has_key(sensorId):
-        # new device discovered
-        desc = Devices.getDescription(header["mfrid"], header["productid"])
-        print("ADD device:%s %s" % (hex(sensorId), desc))
-        directory[sensorId] = {"header": message["header"]}
-        #trace(allkeys(directory))
-
-    directory[sensorId]["time"] = now
-    #TODO would be good to keep recs, but need to iterate through all and key by paramid,
-    #not as a list index, else merging will be hard.
+        # Process any JOIN messages by sending back a JOIN-ACK to turn the LED off
+        if len(decoded["recs"]) == 0:
+            # handle messages with zero recs in them silently
+            print("Empty record:%s" % decoded)
+        else:
+            # assume only 1 rec in a join, for now
+            if decoded["recs"][0]["paramid"] == OpenThings.PARAM_JOIN:
+                mfrid     = OpenThings.getFromMessage(decoded, "header_mfrid")
+                productid = OpenThings.getFromMessage(decoded, "header_productid")
+                sensorid  = OpenThings.getFromMessage(decoded, "header_sensorid")
+                Messages.send_join_ack(radio, mfrid, productid, sensorid)
 
 
-def send_join_ack(mfrid, productid, sensorid):
-    # send back a JOIN ACK, so that join light stops flashing
-    response = OpenThings.alterMessage(Messages.JOIN_ACK,
-        header_mfrid=mfrid,
-        header_productid=productid,
-        header_sensorid=sensorid)
-    p = OpenThings.encode(response)
-    radio.transmitter()
-    radio.transmit(p)
-    radio.receiver()
+def switch_toggle_loop():
+    """Toggle the switch on all devices in the directory"""
 
+    global switch_state
 
-def switch_loop():
-    """Listen to sensor messages, and turn switches on and off every few seconds"""
+    if Registry.size() > 0 and sendSwitchTimer.check():
+        print("transmit")
+        radio.transmitter()
 
-    # Define the schedule of message polling
-    sendSwitchTimer    = Timer(TX_RATE, 1)   # every n seconds offset by initial 1
-    switch_state       = 0             # OFF
-    radio.receiver()
+        for sensorid in Registry.get_sensorids():
+            # Only try to toggle the switch for devices that actually have a switch
+            header = Registry.get_info(sensorid)["header"]
+            mfrid = header["mfrid"]
+            productid = header["productid"]
 
-    while True:
-        # See if there is a payload, and if there is, process it
-        if radio.isReceiveWaiting():
-            #trace("receiving payload")
-            payload = radio.receive()
-            try:
-                decoded = OpenThings.decode(payload)
-            except OpenThings.OpenThingsException as e:
-                warning("Can't decode payload:" + str(e))
-                continue
-                      
-            OpenThings.showMessage(decoded)
-            # Any device that reports will be added to the non-persistent directory
-            updateDirectory(decoded)
-            #trace(decoded)
+            if Devices.hasSwitch(mfrid, productid):
+                request = OpenThings.alterMessage(Messages.SWITCH,
+                    header_sensorid=sensorid,
+                    recs_0_value=switch_state)
+                p = OpenThings.encode(request)
+                print("Sending switch message to %s %s" % (hex(productid), hex(sensorid)))
+                # Transmit multiple times, hope one of them gets through
+                radio.transmit(p, inner_times=2)
 
-            # Process any JOIN messages by sending back a JOIN-ACK to turn the LED off
-            if len(decoded["recs"]) == 0:
-                # handle messages with zero recs in them silently
-                print("Empty record:%s" % decoded)
-            else:
-                # assume only 1 rec in a join, for now
-                #TODO: use OpenThings.getFromMessage("header_mfrid")
-                if decoded["recs"][0]["paramid"] == OpenThings.PARAM_JOIN:
-                    header    = decoded["header"]
-                    mfrid     = header["mfrid"]
-                    productid = header["productid"]
-                    sensorid  = header["sensorid"]
-                    send_join_ack(mfrid, productid, sensorid)
-
-
-        # Toggle the switch on all devices in the directory
-        if len(directory) > 0 and sendSwitchTimer.check():
-            print("transmit")
-            radio.transmitter()
-
-            for sensorid in directory.keys():
-                # Only try to toggle the switch for devices that actually have a switch
-                header = directory[sensorid]["header"]
-                mfrid = header["mfrid"]
-                productid = header["productid"]
-
-                if Devices.hasSwitch(mfrid, productid):
-                    request = OpenThings.alterMessage(Messages.SWITCH,
-                        header_sensorid=sensorid,
-                        recs_0_value=switch_state)
-                    p = OpenThings.encode(request)
-                    print("Sending switch message to %s %s" % (hex(productid), hex(sensorid)))
-                    # Transmit multiple times, hope one of them gets through
-                    for i in range(4):
-                        radio.transmit(p)
-
-            radio.receiver()
-            print("receive")
-            switch_state = (switch_state+1) % 2 # toggle
+        radio.receiver()
+        switch_state = (switch_state+1) % 2 # toggle
         
 
 if __name__ == "__main__":
@@ -142,8 +95,23 @@
     radio.init()
     OpenThings.init(Devices.CRYPT_PID)
 
+    # Seed the registry with a known device, to simplify tx-only testing
+    SENSOR_ID = 0x68B # captured from a real device
+    device_header = OpenThings.alterMessage(Messages.REGISTERED_SENSOR,
+        header_mfrid     = Devices.MFRID,
+        header_productid = Devices.PRODUCTID_MIHO005, # adaptor plus
+        header_sensorid  = SENSOR_ID)
+    Registry.update(device_header)
+
+
+    sendSwitchTimer    = Timer(TX_RATE, 1)   # every n seconds offset by initial 1
+    switch_state       = 0 # OFF
+    radio.receiver()
+
     try:
-        switch_loop()
+        while True:
+            switch_sniff_loop()
+            switch_toggle_loop()
 
     finally:
         radio.finished()