浏览代码

Merge pull request #40 from sidoh/PL1167

Add support for PL1167 / LT8900 radio module
Chris Mullins 8 年之前
父节点
当前提交
d95d0350ad

+ 20 - 6
README.md

@@ -15,14 +15,24 @@ This is a replacement for a Milight/LimitlessLED remote/gateway hosted on an ESP
 ## What you'll need
 ## What you'll need
 
 
 1. An ESP8266. I used a NodeMCU.
 1. An ESP8266. I used a NodeMCU.
-2. A NRF24L01+ module (~$3 on ebay).
+2. A NRF24L01+ module (~$3 on ebay). Alternatively, you can use a LT8900.
 3. Some way to connect the two (7 female/female dupont cables is probably easiest).
 3. Some way to connect the two (7 female/female dupont cables is probably easiest).
 
 
 ## Installing
 ## Installing
 
 
-#### Connect the NRF24L01+
+#### Connect the NRF24L01+ / LT8900
 
 
-This module is an SPI device. [This guide](https://www.mysensors.org/build/esp8266_gateway) details how to connect it. I used GPIO 16 for CE and GPIO 15 for CSN. These can be configured later.
+This project is compatible with both NRF24L01 and LT8900 radios. LT8900 is the same model used in the official MiLight devices. NRF24s are a very common 2.4 GHz radio device, but require software emulation of the LT8900's packet structure. As such, the LT8900 is more performant.
+
+Both modules are SPI devices and should be connected to the standard SPI pins on the ESP8266.
+
+##### NRF24L01+
+
+[This guide](https://www.mysensors.org/build/esp8266_gateway) details how to connect an NRF24 to an ESP8266. I used GPIO 16 for CE and GPIO 15 for CSN. These can be configured later.
+
+##### LT8900
+
+Connect SPI pins (CS, SCK, MOSI, MISO) to appropriate SPI pins on the ESP8266. With default settings, connect RST to GPIO 0, and PKT to GPIO 16.
 
 
 #### Setting up the ESP
 #### Setting up the ESP
 
 
@@ -61,7 +71,7 @@ The HTTP endpoints (shown below) will be fully functional at this point. You sho
 1. `PUT /settings`. Patches settings (e.g., doesn't overwrite keys that aren't present). Accepts a JSON blob in the body.
 1. `PUT /settings`. Patches settings (e.g., doesn't overwrite keys that aren't present). Accepts a JSON blob in the body.
 1. `GET /radio_configs`. Get a list of supported radio configs (aka `device_type`s).
 1. `GET /radio_configs`. Get a list of supported radio configs (aka `device_type`s).
 1. `GET /gateway_traffic/:device_type`. Starts an HTTP long poll. Returns any Milight traffic it hears. Useful if you need to know what your Milight gateway/remote ID is. Since protocols for RGBW/CCT are different, specify one of `rgbw`, `cct`, or `rgb_cct` as `:device_type. Accepts a JSON blob.
 1. `GET /gateway_traffic/:device_type`. Starts an HTTP long poll. Returns any Milight traffic it hears. Useful if you need to know what your Milight gateway/remote ID is. Since protocols for RGBW/CCT are different, specify one of `rgbw`, `cct`, or `rgb_cct` as `:device_type. Accepts a JSON blob.
-1. `PUT /gateways/:device_id/:device_type/:group_id`. Controls or sends commands to `:group_id` from `:device_id`. 
+1. `PUT /gateways/:device_id/:device_type/:group_id`. Controls or sends commands to `:group_id` from `:device_id`.
 1. `POST /raw_commands/:device_type`. Sends a raw RF packet with radio configs associated with `:device_type`. Example body:
 1. `POST /raw_commands/:device_type`. Sends a raw RF packet with radio configs associated with `:device_type`. Example body:
     ```
     ```
     {"packet": "01 02 03 04 05 06 07 08 09", "num_repeats": 10}
     {"packet": "01 02 03 04 05 06 07 08 09", "num_repeats": 10}
@@ -83,13 +93,13 @@ Route (5) supports these commands. Note that each bulb type has support for a di
    * `unpair`. Emulates the unpairing process. Send as you connect a paired bulb to have it disassociate with the device ID being used.
    * `unpair`. Emulates the unpairing process. Send as you connect a paired bulb to have it disassociate with the device ID being used.
    * `next_mode`. Cycles to the next "disco mode".
    * `next_mode`. Cycles to the next "disco mode".
    * `previous_mode`. Cycles to the previous disco mode.
    * `previous_mode`. Cycles to the previous disco mode.
-   * `mode_speed_up`. 
+   * `mode_speed_up`.
    * `mode_speed_down`.
    * `mode_speed_down`.
    * `level_down`. Turns down the brightness. Not all dimmable bulbs support this command.
    * `level_down`. Turns down the brightness. Not all dimmable bulbs support this command.
    * `level_up`. Turns down the brightness. Not all dimmable bulbs support this command.
    * `level_up`. Turns down the brightness. Not all dimmable bulbs support this command.
    * `temperature_down`. Turns down the white temperature. Not all bulbs with adjustable white temperature support this command.
    * `temperature_down`. Turns down the white temperature. Not all bulbs with adjustable white temperature support this command.
    * `temperature_up`. Turns up the white temperature. Not all bulbs with adjustable white temperature support this command.
    * `temperature_up`. Turns up the white temperature. Not all bulbs with adjustable white temperature support this command.
-   
+
 If you'd like to control bulbs in all groups paired with a particular device ID, set `:group_id` to 0.
 If you'd like to control bulbs in all groups paired with a particular device ID, set `:group_id` to 0.
 
 
 #### Examples
 #### Examples
@@ -113,3 +123,7 @@ true%
 You can add an arbitrary number of UDP gateways through the REST API or through the web UI. Each gateway server listens on a port and responds to the standard set of commands supported by the Milight protocol. This should allow you to use one of these with standard Milight integrations (SmartThings, Home Assistant, OpenHAB, etc.).
 You can add an arbitrary number of UDP gateways through the REST API or through the web UI. Each gateway server listens on a port and responds to the standard set of commands supported by the Milight protocol. This should allow you to use one of these with standard Milight integrations (SmartThings, Home Assistant, OpenHAB, etc.).
 
 
 You can select between versions 5 and 6 of the UDP protocol (documented [here](http://www.limitlessled.com/dev/)). Version 6 has support for the newer RGB+CCT bulbs and also includes response packets, which can theoretically improve reliability. Version 5 has much smaller packets and is probably lower latency.
 You can select between versions 5 and 6 of the UDP protocol (documented [here](http://www.limitlessled.com/dev/)). Version 6 has support for the newer RGB+CCT bulbs and also includes response packets, which can theoretically improve reliability. Version 5 has much smaller packets and is probably lower latency.
+
+## Acknowledgements
+
+* @WoodsterDK added support for LT8900 radios.

+ 183 - 156
data/web/index.html

@@ -16,7 +16,7 @@
   <!--[if lt IE 9]>
   <!--[if lt IE 9]>
     <script src="https://cdnjs.cloudflare.com/ajax/libs/html5shiv/3.7.3/html5shiv.js"></script>
     <script src="https://cdnjs.cloudflare.com/ajax/libs/html5shiv/3.7.3/html5shiv.js"></script>
   <![endif]-->
   <![endif]-->
-  
+
   <style>
   <style>
     .header-row { border-bottom: 1px solid #ccc; }
     .header-row { border-bottom: 1px solid #ccc; }
     label { display: block; }
     label { display: block; }
@@ -31,8 +31,8 @@
     .error-info:before { content: '('; }
     .error-info:before { content: '('; }
     .error-info:after { content: ')'; }
     .error-info:after { content: ')'; }
     .header-btn { margin: 20px; }
     .header-btn { margin: 20px; }
-    .btn-secondary { 
-      background-color: #fff; 
+    .btn-secondary {
+      background-color: #fff;
       border: 1px solid #ccc;
       border: 1px solid #ccc;
     }
     }
     .inline { display: inline-block; }
     .inline { display: inline-block; }
@@ -46,41 +46,41 @@
       width: calc(100% - 3em);
       width: calc(100% - 3em);
       display: inline-block;
       display: inline-block;
       cursor: pointer;
       cursor: pointer;
-      background: linear-gradient(to right, 
-        rgb(255, 0, 0) 0%, 
-        rgb(255, 255, 0) 16.6667%, 
-        rgb(0, 255, 0) 33.3333%, 
-        rgb(0, 255, 255) 50%, 
-        rgb(0, 0, 255) 66.6667%, 
-        rgb(255, 0, 255) 83.3333%, 
+      background: linear-gradient(to right,
+        rgb(255, 0, 0) 0%,
+        rgb(255, 255, 0) 16.6667%,
+        rgb(0, 255, 0) 33.3333%,
+        rgb(0, 255, 255) 50%,
+        rgb(0, 0, 255) 66.6667%,
+        rgb(255, 0, 255) 83.3333%,
         rgb(255, 0, 0) 100%
         rgb(255, 0, 0) 100%
       );
       );
     }
     }
-    .hue-value-display { 
+    .hue-value-display {
       border: 1px solid #000;
       border: 1px solid #000;
       margin-left: 0.5em;
       margin-left: 0.5em;
       width: 2em;
       width: 2em;
       height: 2em;
       height: 2em;
       display: inline-block;
       display: inline-block;
     }
     }
-    .plus-minus-group { 
+    .plus-minus-group {
       overflow: auto;
       overflow: auto;
       width: 100%;
       width: 100%;
       clear: both;
       clear: both;
       display: block;
       display: block;
     }
     }
-    .plus-minus-group button:first-of-type { 
+    .plus-minus-group button:first-of-type {
       border-bottom-right-radius: 0;
       border-bottom-right-radius: 0;
       border-top-right-radius: 0;
       border-top-right-radius: 0;
       float: left;
       float: left;
       display: block;
       display: block;
     }
     }
-    .plus-minus-group button:last-of-type { 
+    .plus-minus-group button:last-of-type {
       border-bottom-left-radius: 0;
       border-bottom-left-radius: 0;
       border-top-left-radius: 0;
       border-top-left-radius: 0;
       display: block;
       display: block;
     }
     }
-    .plus-minus-group .title { 
+    .plus-minus-group .title {
       border-width: 1px 0;
       border-width: 1px 0;
       border-color: #ccc;
       border-color: #ccc;
       border-style: solid;
       border-style: solid;
@@ -100,12 +100,12 @@
       animation: spin 1s infinite linear;
       animation: spin 1s infinite linear;
       -webkit-animation: spin2 1s infinite linear;
       -webkit-animation: spin2 1s infinite linear;
     }
     }
-    
+
     @keyframes spin {
     @keyframes spin {
       from { transform: scale(1) rotate(0deg); }
       from { transform: scale(1) rotate(0deg); }
       to { transform: scale(1) rotate(360deg); }
       to { transform: scale(1) rotate(360deg); }
     }
     }
-    
+
     @-webkit-keyframes spin2 {
     @-webkit-keyframes spin2 {
       from { -webkit-transform: rotate(0deg); }
       from { -webkit-transform: rotate(0deg); }
       to { -webkit-transform: rotate(360deg); }
       to { -webkit-transform: rotate(360deg); }
@@ -120,52 +120,55 @@
   <script src="https://cdnjs.cloudflare.com/ajax/libs/underscore.js/1.8.3/underscore-min.js"></script>
   <script src="https://cdnjs.cloudflare.com/ajax/libs/underscore.js/1.8.3/underscore-min.js"></script>
   <script src="https://cdnjs.cloudflare.com/ajax/libs/bootstrap-slider/9.7.0/bootstrap-slider.min.js"></script>
   <script src="https://cdnjs.cloudflare.com/ajax/libs/bootstrap-slider/9.7.0/bootstrap-slider.min.js"></script>
   <script src="https://cdnjs.cloudflare.com/ajax/libs/selectize.js/0.12.4/js/standalone/selectize.min.js"></script>
   <script src="https://cdnjs.cloudflare.com/ajax/libs/selectize.js/0.12.4/js/standalone/selectize.min.js"></script>
-  
+
   <script lang="text/javascript">
   <script lang="text/javascript">
     var FORM_SETTINGS = [
     var FORM_SETTINGS = [
-      "admin_username", "admin_password", "ce_pin", "csn_pin", "packet_repeats",
-      "http_repeat_factor", "auto_restart_period"
+      "admin_username", "admin_password", "ce_pin", "csn_pin", "reset_pin","packet_repeats",
+      "http_repeat_factor", "auto_restart_period", "radio_interface_type"
     ];
     ];
-    
+
     var FORM_SETTINGS_HELP = {
     var FORM_SETTINGS_HELP = {
+      ce_pin : "'CE' for NRF24L01 interface, and 'PKT' for 'PL1167/LT8900' interface",
       packet_repeats : "The number of times to repeat RF packets sent to bulbs",
       packet_repeats : "The number of times to repeat RF packets sent to bulbs",
       http_repeat_factor : "Multiplicative factor on packet_repeats for " +
       http_repeat_factor : "Multiplicative factor on packet_repeats for " +
         "requests initiated by the HTTP API. UDP API typically receives " +
         "requests initiated by the HTTP API. UDP API typically receives " +
         "duplicate packets, so more repeats should be used for HTTP.",
         "duplicate packets, so more repeats should be used for HTTP.",
       auto_restart_period : "Automatically restart the device every number of " +
       auto_restart_period : "Automatically restart the device every number of " +
-        "minutes specified. Use 0 for disabled."
+        "minutes specified. Use 0 for disabled.",
+      radio_interface_type : "2.4 GHz radio model. Only change this if you know " +
+        "You're not using an NRF24L01!"
     }
     }
-    
+
     var UDP_PROTOCOL_VERSIONS = [ 5, 6 ];
     var UDP_PROTOCOL_VERSIONS = [ 5, 6 ];
     var DEFAULT_UDP_PROTOCL_VERSION = 5;
     var DEFAULT_UDP_PROTOCL_VERSION = 5;
-    
+
     var selectize;
     var selectize;
-    
+
     var toHex = function(v) {
     var toHex = function(v) {
       return "0x" + (v).toString(16).toUpperCase();
       return "0x" + (v).toString(16).toUpperCase();
     }
     }
-  
+
     var activeUrl = function() {
     var activeUrl = function() {
       var deviceId = $('#deviceId option:selected').val()
       var deviceId = $('#deviceId option:selected').val()
         , groupId = $('#groupId .active input').data('value')
         , groupId = $('#groupId .active input').data('value')
         , mode = getCurrentMode();
         , mode = getCurrentMode();
-        
+
       if (deviceId == "") {
       if (deviceId == "") {
         alert("Please enter a device ID.");
         alert("Please enter a device ID.");
         throw "Must enter device ID";
         throw "Must enter device ID";
       }
       }
-      
+
       if (! $('#group-option').data('for').split(',').includes(mode)) {
       if (! $('#group-option').data('for').split(',').includes(mode)) {
         groupId = 0;
         groupId = 0;
       }
       }
-        
+
       return "/gateways/" + deviceId + "/" + mode + "/" + groupId;
       return "/gateways/" + deviceId + "/" + mode + "/" + groupId;
     }
     }
-    
+
     var getCurrentMode = function() {
     var getCurrentMode = function() {
       return $('input[name="mode"]:checked').data('value');
       return $('input[name="mode"]:checked').data('value');
     };
     };
-    
+
     var updateGroup = _.throttle(
     var updateGroup = _.throttle(
       function(params) {
       function(params) {
         $.ajax(
         $.ajax(
@@ -179,7 +182,7 @@
       },
       },
       1000
       1000
     );
     );
-    
+
     var sendCommand = _.throttle(
     var sendCommand = _.throttle(
       function(params) {
       function(params) {
         $.ajax(
         $.ajax(
@@ -193,18 +196,18 @@
       },
       },
       1000
       1000
     )
     )
-    
+
     var sniffRequest;
     var sniffRequest;
     var sniffing = false;
     var sniffing = false;
     var getTraffic = function() {
     var getTraffic = function() {
       var sniffType = $('#sniff-type input:checked').data('value');
       var sniffType = $('#sniff-type input:checked').data('value');
-      
+
       sniffRequest = $.get('/gateway_traffic/' + sniffType, function(data) {
       sniffRequest = $.get('/gateway_traffic/' + sniffType, function(data) {
         $('#sniffed-traffic').html(data + $('#sniffed-traffic').html());
         $('#sniffed-traffic').html(data + $('#sniffed-traffic').html());
         getTraffic();
         getTraffic();
       });
       });
     };
     };
-    
+
     var gatewayServerRow = function(deviceId, port, version) {
     var gatewayServerRow = function(deviceId, port, version) {
       var elmt = '<tr>';
       var elmt = '<tr>';
       elmt += '<td>';
       elmt += '<td>';
@@ -215,17 +218,17 @@
       elmt += '</td>';
       elmt += '</td>';
       elmt += '<td>';
       elmt += '<td>';
       elmt += '<div class="btn-group" data-toggle="buttons">';
       elmt += '<div class="btn-group" data-toggle="buttons">';
-      
+
       for (var i = 0; i < UDP_PROTOCOL_VERSIONS.length; i++) {
       for (var i = 0; i < UDP_PROTOCOL_VERSIONS.length; i++) {
         var val = UDP_PROTOCOL_VERSIONS[i]
         var val = UDP_PROTOCOL_VERSIONS[i]
           , selected = (version == val || (val == DEFAULT_UDP_PROTOCL_VERSION && !UDP_PROTOCOL_VERSIONS.includes(version)));
           , selected = (version == val || (val == DEFAULT_UDP_PROTOCL_VERSION && !UDP_PROTOCOL_VERSIONS.includes(version)));
-        
+
         elmt += '<label class="btn btn-secondary' + (selected ? ' active' : '') + '">';
         elmt += '<label class="btn btn-secondary' + (selected ? ' active' : '') + '">';
-        elmt += '<input type="radio" name="versions[]" autocomplete="off" data-value="' + val + '" ' 
+        elmt += '<input type="radio" name="versions[]" autocomplete="off" data-value="' + val + '" '
           + (selected ? 'checked' : '') +'> ' + val;
           + (selected ? 'checked' : '') +'> ' + val;
         elmt += '</label>';
         elmt += '</label>';
       }
       }
-      
+
       elmt += '</div></td>';
       elmt += '</div></td>';
       elmt += '<td>';
       elmt += '<td>';
       elmt += '<button class="btn btn-danger remove-gateway-server">';
       elmt += '<button class="btn btn-danger remove-gateway-server">';
@@ -235,17 +238,21 @@
       elmt += '</tr>';
       elmt += '</tr>';
       return elmt;
       return elmt;
     }
     }
-    
+
     var loadSettings = function() {
     var loadSettings = function() {
       $.getJSON('/settings', function(val) {
       $.getJSON('/settings', function(val) {
         Object.keys(val).forEach(function(k) {
         Object.keys(val).forEach(function(k) {
           var field = $('#settings input[name="' + k + '"]');
           var field = $('#settings input[name="' + k + '"]');
-          
+
           if (field.length > 0) {
           if (field.length > 0) {
-            field.val(val[k]);
+            if (field.attr('type') === 'radio') {
+              field.filter('[value="' + val[k] + '"]').click();
+            } else {
+              field.val(val[k]);
+            }
           }
           }
         });
         });
-        
+
         if (val.device_ids) {
         if (val.device_ids) {
           selectize.clearOptions();
           selectize.clearOptions();
           val.device_ids.forEach(function(v) {
           val.device_ids.forEach(function(v) {
@@ -253,7 +260,7 @@
           });
           });
           selectize.refreshOptions();
           selectize.refreshOptions();
         }
         }
-        
+
         var gatewayForm = $('#gateway-server-configs').html('');
         var gatewayForm = $('#gateway-server-configs').html('');
         if (val.gateway_configs) {
         if (val.gateway_configs) {
           val.gateway_configs.forEach(function(v) {
           val.gateway_configs.forEach(function(v) {
@@ -262,16 +269,16 @@
         }
         }
       });
       });
     };
     };
-    
+
     var saveGatewayConfigs = function() {
     var saveGatewayConfigs = function() {
       var form = $('#gateway-server-form')
       var form = $('#gateway-server-form')
         , errors = false;
         , errors = false;
-        
+
       $('input', form).removeClass('error');
       $('input', form).removeClass('error');
-      
+
       var deviceIds = $('input[name="deviceIds[]"]', form).map(function(i, v) {
       var deviceIds = $('input[name="deviceIds[]"]', form).map(function(i, v) {
         var val = $(v).val();
         var val = $(v).val();
-        
+
         if (isNaN(val)) {
         if (isNaN(val)) {
           errors = true;
           errors = true;
           $(v).addClass('error');
           $(v).addClass('error');
@@ -280,10 +287,10 @@
           return val;
           return val;
         }
         }
       });
       });
-      
+
       var ports = $('input[name="ports[]"]', form).map(function(i, v) {
       var ports = $('input[name="ports[]"]', form).map(function(i, v) {
         var val = $(v).val();
         var val = $(v).val();
-        
+
         if (isNaN(val)) {
         if (isNaN(val)) {
           errors = true;
           errors = true;
           $(v).addClass('error');
           $(v).addClass('error');
@@ -292,11 +299,11 @@
           return val;
           return val;
         }
         }
       });
       });
-      
+
       var versions = $('.active input[name="versions[]"]', form).map(function(i, v) {
       var versions = $('.active input[name="versions[]"]', form).map(function(i, v) {
         return $(v).data('value');
         return $(v).data('value');
       });
       });
-        
+
       if (!errors) {
       if (!errors) {
         var data = [];
         var data = [];
         for (var i = 0; i < deviceIds.length; i++) {
         for (var i = 0; i < deviceIds.length; i++) {
@@ -312,7 +319,7 @@
         )
         )
       }
       }
     };
     };
-    
+
     var deviceIdError = function(v) {
     var deviceIdError = function(v) {
       if (!v) {
       if (!v) {
         $('#device-id-label').removeClass('error');
         $('#device-id-label').removeClass('error');
@@ -321,10 +328,10 @@
         $('#device-id-label .error-info').html(v);
         $('#device-id-label .error-info').html(v);
       }
       }
     };
     };
-    
+
     var updateModeOptions = function() {
     var updateModeOptions = function() {
       var currentMode = getCurrentMode();
       var currentMode = getCurrentMode();
-      
+
       $('.mode-option').map(function() {
       $('.mode-option').map(function() {
         if ($(this).data('for').split(',').includes(currentMode)) {
         if ($(this).data('for').split(',').includes(currentMode)) {
           $(this).show();
           $(this).show();
@@ -333,10 +340,10 @@
         }
         }
       });
       });
     };
     };
-    
+
     var parseVersion = function(v) {
     var parseVersion = function(v) {
       var matches = v.match(/(\d+)\.(\d+)\.(\d+)(-(.*))?/);
       var matches = v.match(/(\d+)\.(\d+)\.(\d+)(-(.*))?/);
-      
+
       return {
       return {
         major: matches[1],
         major: matches[1],
         minor: matches[2],
         minor: matches[2],
@@ -345,28 +352,28 @@
         parts: [matches[1], matches[2], matches[3], matches[5]]
         parts: [matches[1], matches[2], matches[3], matches[5]]
       };
       };
     };
     };
-    
+
     var isNewerVersion = function(a, b) {
     var isNewerVersion = function(a, b) {
       var va = parseVersion(a)
       var va = parseVersion(a)
         , vb = parseVersion(b);
         , vb = parseVersion(b);
-        
+
       return va.parts > vb.parts;
       return va.parts > vb.parts;
     };
     };
-    
+
     var handleCheckForUpdates = function() {
     var handleCheckForUpdates = function() {
       var currentVersion = null
       var currentVersion = null
         , latestRelease = null;
         , latestRelease = null;
-        
+
       var handleReceiveData = function() {
       var handleReceiveData = function() {
         if (currentVersion != null) {
         if (currentVersion != null) {
           $('#current-version').html(currentVersion.version + " (" + currentVersion.variant + ")");
           $('#current-version').html(currentVersion.version + " (" + currentVersion.variant + ")");
         }
         }
-        
+
         if (latestRelease != null) {
         if (latestRelease != null) {
           $('#latest-version .info-key').each(function() {
           $('#latest-version .info-key').each(function() {
             var value = latestRelease[$(this).data('key')];
             var value = latestRelease[$(this).data('key')];
             var prop = $(this).data('prop');
             var prop = $(this).data('prop');
-            
+
             if (prop) {
             if (prop) {
               $(this).prop(prop, value);
               $(this).prop(prop, value);
             } else {
             } else {
@@ -374,11 +381,11 @@
             }
             }
           });
           });
         }
         }
-        
+
         if (currentVersion != null && latestRelease != null) {
         if (currentVersion != null && latestRelease != null) {
           $('#latest-version .status').html('');
           $('#latest-version .status').html('');
           $('#latest-version-info').show();
           $('#latest-version-info').show();
-          
+
           var summary;
           var summary;
           if (isNewerVersion(latestRelease.tag_name, currentVersion.version)) {
           if (isNewerVersion(latestRelease.tag_name, currentVersion.version)) {
             summary = "New version available!";
             summary = "New version available!";
@@ -386,30 +393,30 @@
             summary = "You're on the most recent version.";
             summary = "You're on the most recent version.";
           }
           }
           $('#version-summary').html(summary);
           $('#version-summary').html(summary);
-          
+
           var releaseAsset = latestRelease.assets.filter(function(x) {
           var releaseAsset = latestRelease.assets.filter(function(x) {
             return x.name.indexOf(currentVersion.variant) != -1;
             return x.name.indexOf(currentVersion.variant) != -1;
           });
           });
-          
+
           if (releaseAsset.length > 0) {
           if (releaseAsset.length > 0) {
             console.log(releaseAsset[0].url);
             console.log(releaseAsset[0].url);
             $('#firmware-link').prop('href', releaseAsset[0].browser_download_url);
             $('#firmware-link').prop('href', releaseAsset[0].browser_download_url);
           }
           }
         }
         }
-        
+
         console.log(latestRelease);
         console.log(latestRelease);
       }
       }
-      
+
       var handleError = function(e, d) {
       var handleError = function(e, d) {
         console.log(e);
         console.log(e);
         console.log(d);
         console.log(d);
       }
       }
-      
+
       $('#current-version,#latest-version .status').html('<i class="spinning glyphicon glyphicon-refresh"></i>');
       $('#current-version,#latest-version .status').html('<i class="spinning glyphicon glyphicon-refresh"></i>');
       $('#version-summary').html('');
       $('#version-summary').html('');
       $('#latest-version-info').hide();
       $('#latest-version-info').hide();
       $('#updates-modal').modal();
       $('#updates-modal').modal();
-      
+
       $.ajax(
       $.ajax(
         '/about',
         '/about',
         {
         {
@@ -420,7 +427,7 @@
           failure: handleError
           failure: handleError
         }
         }
       );
       );
-      
+
       $.ajax(
       $.ajax(
         '/latest_release',
         '/latest_release',
         {
         {
@@ -432,26 +439,26 @@
         }
         }
       );
       );
     };
     };
-    
+
     $(function() {
     $(function() {
       $('.radio-option').click(function() {
       $('.radio-option').click(function() {
         $(this).prev().prop('checked', true);
         $(this).prev().prop('checked', true);
       });
       });
-      
+
       var hueDragging = false;
       var hueDragging = false;
       var colorUpdated = function(e) {
       var colorUpdated = function(e) {
         var x = e.pageX - $(this).offset().left
         var x = e.pageX - $(this).offset().left
           , pct = x/(1.0*$(this).width())
           , pct = x/(1.0*$(this).width())
           , hue = Math.round(360*pct)
           , hue = Math.round(360*pct)
           ;
           ;
-          
+
         $('.hue-value-display').css({
         $('.hue-value-display').css({
           backgroundColor: "hsl(" + hue + ",100%,50%)"
           backgroundColor: "hsl(" + hue + ",100%,50%)"
         });
         });
-        
+
         updateGroup({hue: hue});
         updateGroup({hue: hue});
       };
       };
-      
+
       $('.hue-picker-inner')
       $('.hue-picker-inner')
         .mousedown(function(e) {
         .mousedown(function(e) {
           hueDragging = true;
           hueDragging = true;
@@ -468,26 +475,26 @@
             colorUpdated.call(this, e);
             colorUpdated.call(this, e);
           }
           }
         });
         });
-        
+
       $('.slider').slider();
       $('.slider').slider();
-      
+
       $('.raw-update').change(function() {
       $('.raw-update').change(function() {
         var data = {}
         var data = {}
           , val = $(this).attr('type') == 'checkbox' ? $(this).is(':checked') : $(this).val()
           , val = $(this).attr('type') == 'checkbox' ? $(this).is(':checked') : $(this).val()
           ;
           ;
-          
+
         data[$(this).attr('name')] = val;
         data[$(this).attr('name')] = val;
         updateGroup(data);
         updateGroup(data);
       });
       });
-      
+
       $('.command-btn').click(function() {
       $('.command-btn').click(function() {
         updateGroup({command: $(this).data('command')});
         updateGroup({command: $(this).data('command')});
       });
       });
-      
+
       $('.system-btn').click(function() {
       $('.system-btn').click(function() {
         sendCommand({command: $(this).data('command')});
         sendCommand({command: $(this).data('command')});
       });
       });
-      
+
       $('#sniff').click(function() {
       $('#sniff').click(function() {
         if (sniffing) {
         if (sniffing) {
           sniffRequest.abort();
           sniffRequest.abort();
@@ -499,17 +506,17 @@
           $(this).html('Stop Sniffing');
           $(this).html('Stop Sniffing');
         }
         }
       });
       });
-      
+
       $('#add-server-btn').click(function() {
       $('#add-server-btn').click(function() {
         $('#gateway-server-configs').append(gatewayServerRow('', ''));
         $('#gateway-server-configs').append(gatewayServerRow('', ''));
       });
       });
-      
+
       $('#mode').change(updateModeOptions);
       $('#mode').change(updateModeOptions);
-      
+
       $('body').on('click', '.remove-gateway-server', function() {
       $('body').on('click', '.remove-gateway-server', function() {
         $(this).closest('tr').remove();
         $(this).closest('tr').remove();
       });
       });
-      
+
       selectize = $('#deviceId').selectize({
       selectize = $('#deviceId').selectize({
         create: true,
         create: true,
         sortField: 'text',
         sortField: 'text',
@@ -521,53 +528,73 @@
             deviceIdError("Must be an integer between 0x0000 and 0xFFFF");
             deviceIdError("Must be an integer between 0x0000 and 0xFFFF");
             return false;
             return false;
           }
           }
-          
+
           var value = parseInt(v);
           var value = parseInt(v);
-          
+
           if (! (0 <= v && v <= 0xFFFF)) {
           if (! (0 <= v && v <= 0xFFFF)) {
             deviceIdError("Must be an integer between 0x0000 and 0xFFFF");
             deviceIdError("Must be an integer between 0x0000 and 0xFFFF");
             return false;
             return false;
-          } 
-          
+          }
+
           deviceIdError(false);
           deviceIdError(false);
-          
+
           return true;
           return true;
         }
         }
       });
       });
       selectize = selectize[0].selectize;
       selectize = selectize[0].selectize;
-      
+
       var settings = "";
       var settings = "";
-      
+
       FORM_SETTINGS.forEach(function(k) {
       FORM_SETTINGS.forEach(function(k) {
         var elmt = '<div class="form-entry">';
         var elmt = '<div class="form-entry">';
+        elmt += '<div>';
         elmt += '<label for="' + k + '">' + k + '</label>';
         elmt += '<label for="' + k + '">' + k + '</label>';
-        
+
         if (FORM_SETTINGS_HELP[k]) {
         if (FORM_SETTINGS_HELP[k]) {
           elmt += '<div class="field-help" data-help-text="' + FORM_SETTINGS_HELP[k] + '"></div>';
           elmt += '<div class="field-help" data-help-text="' + FORM_SETTINGS_HELP[k] + '"></div>';
         }
         }
-        
-        elmt += '<input type="text" class="form-control" name="' + k + '"/>';
+
         elmt += '</div>';
         elmt += '</div>';
-        
+
+        if(k === "radio_interface_type") {
+          elmt += '<div class="btn-group" id="radio_interface_type" data-toggle="buttons">' +
+            '<label class="btn btn-secondary active">' +
+              '<input type="radio" id="nrf24" name="radio_interface_type" autocomplete="off" value="nRF24" /> nRF24' +
+            '</label>'+
+            '<label class="btn btn-secondary">' +
+              '<input type="radio" id="lt8900" name="radio_interface_type" autocomplete="off" value="LT8900" /> PL1167/LT8900' +
+            '</label>' +
+          '</div>';
+        } else {
+          elmt += '<input type="text" class="form-control" name="' + k + '"/>';
+          elmt += '</div>';
+        }
+
         settings += elmt;
         settings += elmt;
       });
       });
-        
+
       $('#settings').prepend(settings);
       $('#settings').prepend(settings);
       $('#settings').submit(function(e) {
       $('#settings').submit(function(e) {
         var obj = {};
         var obj = {};
-        
+
         FORM_SETTINGS.forEach(function(k) {
         FORM_SETTINGS.forEach(function(k) {
-          obj[k] = $('#settings input[name="' + k + '"]').val();
+          var elmt = $('#settings input[name="' + k + '"]');
+
+          if (elmt.attr('type') === 'radio') {
+            obj[k] = elmt.filter(':checked').val();
+          } else {
+            obj[k] = elmt.val();
+          }
         });
         });
-        
+
         // pretty hacky. whatever.
         // pretty hacky. whatever.
         obj.device_ids = _.map(
         obj.device_ids = _.map(
           $('.selectize-control .option'),
           $('.selectize-control .option'),
-          function(x) { 
+          function(x) {
             return $(x).data('value')
             return $(x).data('value')
           }
           }
         );
         );
-        
+
         $.ajax(
         $.ajax(
           "/settings",
           "/settings",
           {
           {
@@ -576,17 +603,17 @@
             data: JSON.stringify(obj)
             data: JSON.stringify(obj)
           }
           }
         );
         );
-        
+
         e.preventDefault();
         e.preventDefault();
         return false;
         return false;
       });
       });
-      
+
       $('#gateway-server-form').submit(function(e) {
       $('#gateway-server-form').submit(function(e) {
         saveGatewayConfigs();
         saveGatewayConfigs();
         e.preventDefault();
         e.preventDefault();
         return false;
         return false;
       });
       });
-      
+
       $('.field-help').each(function() {
       $('.field-help').each(function() {
         var elmt = $('<i></i>')
         var elmt = $('<i></i>')
           .addClass('glyphicon glyphicon-question-sign')
           .addClass('glyphicon glyphicon-question-sign')
@@ -597,14 +624,14 @@
           });
           });
         $(this).append(elmt);
         $(this).append(elmt);
       });
       });
-      
+
       $('#updates-btn').click(handleCheckForUpdates);
       $('#updates-btn').click(handleCheckForUpdates);
-      
+
       loadSettings();
       loadSettings();
       updateModeOptions();
       updateModeOptions();
     });
     });
   </script>
   </script>
-  
+
   <div id="update-firmware-modal" class="modal fade" role="dialog">
   <div id="update-firmware-modal" class="modal fade" role="dialog">
     <div class="modal-dialog">
     <div class="modal-dialog">
       <!-- Modal content-->
       <!-- Modal content-->
@@ -620,10 +647,10 @@
             <a href="https://github.com/sidoh/esp8266_milight_hub/releases">GitHub releases page</a>.
             <a href="https://github.com/sidoh/esp8266_milight_hub/releases">GitHub releases page</a>.
             Check for a new version by clicking on the "Check for Updates" button.
             Check for a new version by clicking on the "Check for Updates" button.
             </p>
             </p>
-            
+
             <p>
             <p>
               <b>Make sure the binary you're uploading was compiled for your board!</b>
               <b>Make sure the binary you're uploading was compiled for your board!</b>
-              Firmware with incompatible settings could prevent boots. If this happens, 
+              Firmware with incompatible settings could prevent boots. If this happens,
               reflash the board with USB.
               reflash the board with USB.
             </p>
             </p>
           </div>
           </div>
@@ -637,10 +664,10 @@
           <button type="button" class="btn btn-default" data-dismiss="modal">Close</button>
           <button type="button" class="btn btn-default" data-dismiss="modal">Close</button>
         </div>
         </div>
       </div>
       </div>
-      
+
     </div>
     </div>
   </div>
   </div>
-  
+
   <div id="updates-modal" class="modal fade" role="dialog">
   <div id="updates-modal" class="modal fade" role="dialog">
     <div class="modal-dialog">
     <div class="modal-dialog">
       <!-- Modal content-->
       <!-- Modal content-->
@@ -651,29 +678,29 @@
         </div>
         </div>
         <div class="modal-body">
         <div class="modal-body">
           <div id="version-summary"></div>
           <div id="version-summary"></div>
-          
+
           <hr />
           <hr />
-          
+
           <h4>Current Version</h4>
           <h4>Current Version</h4>
           <div id="current-version"></div>
           <div id="current-version"></div>
-          
+
           <div id="latest-version">
           <div id="latest-version">
             <h4>Latest Version</h4>
             <h4>Latest Version</h4>
             <div class="status"></div>
             <div class="status"></div>
             <div id="latest-version-info">
             <div id="latest-version-info">
               <label>Version</label>
               <label>Version</label>
               <div class="info-key" data-key="tag_name"></div>
               <div class="info-key" data-key="tag_name"></div>
-              
+
               <label>Release Date</label>
               <label>Release Date</label>
               <div class="info-key" data-key="published_at"></div>
               <div class="info-key" data-key="published_at"></div>
-              
+
               <label>Release Notes</label>
               <label>Release Notes</label>
               <pre class="info-key" data-key="body"></pre>
               <pre class="info-key" data-key="body"></pre>
-              
+
               <div>
               <div>
                 <a class="info-key" data-prop="href" data-key="html_url">View on GitHub</a>
                 <a class="info-key" data-prop="href" data-key="html_url">View on GitHub</a>
               </div>
               </div>
-              
+
               <div>
               <div>
                 <a class="info-key" id="firmware-link">Download Firmware</a>
                 <a class="info-key" id="firmware-link">Download Firmware</a>
               </div>
               </div>
@@ -685,10 +712,10 @@
           <button type="button" class="btn btn-default" data-dismiss="modal">Close</button>
           <button type="button" class="btn btn-default" data-dismiss="modal">Close</button>
         </div>
         </div>
       </div>
       </div>
-      
+
     </div>
     </div>
   </div>
   </div>
-  
+
   <div class="container">
   <div class="container">
     <div class="row header-row">
     <div class="row header-row">
       <div class="col-sm-12">
       <div class="col-sm-12">
@@ -697,9 +724,9 @@
         </h1>
         </h1>
       </div>
       </div>
     </div>
     </div>
-    
+
     <div>&nbsp;</div>
     <div>&nbsp;</div>
-    
+
     <div class="row">
     <div class="row">
       <div class="col-sm-4">
       <div class="col-sm-4">
         <label for="deviceId" id="device-id-label">
         <label for="deviceId" id="device-id-label">
@@ -709,11 +736,11 @@
         <select id="deviceId" placeholder="Enter hub ID">
         <select id="deviceId" placeholder="Enter hub ID">
 				</select>
 				</select>
       </div>
       </div>
-      
+
       <div class="col-sm-3">
       <div class="col-sm-3">
         <div class="mode-option" id="group-option" data-for="cct,rgbw,rgb_cct">
         <div class="mode-option" id="group-option" data-for="cct,rgbw,rgb_cct">
           <label for="groupId">Group</label>
           <label for="groupId">Group</label>
-        
+
           <div class="btn-group" id="groupId" data-toggle="buttons">
           <div class="btn-group" id="groupId" data-toggle="buttons">
             <label class="btn btn-secondary active">
             <label class="btn btn-secondary active">
               <input type="radio" name="options" autocomplete="off" data-value="1" checked> 1
               <input type="radio" name="options" autocomplete="off" data-value="1" checked> 1
@@ -733,10 +760,10 @@
           </div>
           </div>
         </div>
         </div>
       </div>
       </div>
-      
+
       <div class="col-sm-4">
       <div class="col-sm-4">
         <label for="groupId">Mode</label>
         <label for="groupId">Mode</label>
-        
+
         <div class="btn-group" id="mode" data-toggle="buttons">
         <div class="btn-group" id="mode" data-toggle="buttons">
           <label class="btn btn-secondary active">
           <label class="btn btn-secondary active">
             <input type="radio" name="mode" autocomplete="off" data-value="rgbw" checked> RGBW
             <input type="radio" name="mode" autocomplete="off" data-value="rgbw" checked> RGBW
@@ -753,7 +780,7 @@
         </div>
         </div>
       </div>
       </div>
     </div>
     </div>
-    
+
     <div class="row"><div class="col-sm-12">
     <div class="row"><div class="col-sm-12">
     <div class="mode-option" data-for="rgbw,rgb_cct,rgb">
     <div class="mode-option" data-for="rgbw,rgb_cct,rgb">
       <div class="row">
       <div class="row">
@@ -761,7 +788,7 @@
           <h5>Hue</h5>
           <h5>Hue</h5>
         </div>
         </div>
       </div>
       </div>
-      
+
       <div class="row">
       <div class="row">
         <div class="col-sm-6">
         <div class="col-sm-6">
           <span class="hue-picker">
           <span class="hue-picker">
@@ -772,7 +799,7 @@
       </div>
       </div>
     </div>
     </div>
     </div></div>
     </div></div>
-    
+
     <div class="mode-option" data-for="rgb_cct">
     <div class="mode-option" data-for="rgb_cct">
       <div class="row">
       <div class="row">
         <div class="col-sm-12">
         <div class="col-sm-12">
@@ -789,7 +816,7 @@
         </div>
         </div>
       </div>
       </div>
     </div>
     </div>
-          
+
     <div class="mode-option" data-for="cct,rgb_cct">
     <div class="mode-option" data-for="cct,rgb_cct">
       <div class="row">
       <div class="row">
         <div class="col-sm-12">
         <div class="col-sm-12">
@@ -806,13 +833,13 @@
         </div>
         </div>
       </div>
       </div>
     </div>
     </div>
-    
+
     <div class="row">
     <div class="row">
       <div class="col-sm-12">
       <div class="col-sm-12">
         <h5>Brightness</h5>
         <h5>Brightness</h5>
       </div>
       </div>
     </div>
     </div>
-      
+
     <div class="row">
     <div class="row">
       <div class="col-sm-12">
       <div class="col-sm-12">
         <input class="slider raw-update" name="level"
         <input class="slider raw-update" name="level"
@@ -822,13 +849,13 @@
         />
         />
       </div>
       </div>
     </div>
     </div>
-    
+
     <div class="row">
     <div class="row">
       <div class="col-sm-12">
       <div class="col-sm-12">
         <h5>Commands</h5>
         <h5>Commands</h5>
       </div>
       </div>
     </div>
     </div>
-    
+
     <div class="row">
     <div class="row">
       <div class="col-sm-12">
       <div class="col-sm-12">
         <ul class="command-buttons">
         <ul class="command-buttons">
@@ -886,12 +913,12 @@
         </ul>
         </ul>
       </div>
       </div>
     </div>
     </div>
-    
+
     <div class="row header-row">
     <div class="row header-row">
       <div class="col col-sm-10">
       <div class="col col-sm-10">
         <h1>Gateway Servers</h1>
         <h1>Gateway Servers</h1>
       </div>
       </div>
-      
+
       <div class="col col-sm-2">
       <div class="col col-sm-2">
         <button class="btn btn-success header-btn" id="add-server-btn">
         <button class="btn btn-success header-btn" id="add-server-btn">
           <i class="glyphicon glyphicon-plus"></i>
           <i class="glyphicon glyphicon-plus"></i>
@@ -899,7 +926,7 @@
         </button>
         </button>
       </div>
       </div>
     </div>
     </div>
-    
+
     <div class="row">
     <div class="row">
       <div class="col col-sm-12">
       <div class="col col-sm-12">
         <form id="gateway-server-form">
         <form id="gateway-server-form">
@@ -918,17 +945,17 @@
         </form>
         </form>
       </div>
       </div>
     </div>
     </div>
-    
+
     <div>&nbsp;</div>
     <div>&nbsp;</div>
-    
+
     <div class="row header-row">
     <div class="row header-row">
       <div class="col-sm-12">
       <div class="col-sm-12">
         <h1>Settings</h1>
         <h1>Settings</h1>
       </div>
       </div>
     </div>
     </div>
-    
+
     <div>&nbsp;</div>
     <div>&nbsp;</div>
-    
+
     <div class="row">
     <div class="row">
       <div class="col-sm-12">
       <div class="col-sm-12">
         <form action="#" id="settings">
         <form action="#" id="settings">
@@ -936,19 +963,19 @@
         </form>
         </form>
       </div>
       </div>
     </div>
     </div>
-    
+
     <div class="row header-row">
     <div class="row header-row">
       <div class="col-sm-12">
       <div class="col-sm-12">
         <h1>Sniff Traffic</h1>
         <h1>Sniff Traffic</h1>
       </div>
       </div>
     </div>
     </div>
-    
+
     <div>&nbsp;</div>
     <div>&nbsp;</div>
-    
+
     <div class="row">
     <div class="row">
       <div class="col-sm-12">
       <div class="col-sm-12">
         <button type="button" id="sniff" class="btn btn-primary">Start Sniffing</button>
         <button type="button" id="sniff" class="btn btn-primary">Start Sniffing</button>
-        
+
         <div class="btn-group" id="sniff-type" data-toggle="buttons">
         <div class="btn-group" id="sniff-type" data-toggle="buttons">
           <label class="btn btn-secondary active">
           <label class="btn btn-secondary active">
             <input type="radio" name="options" autocomplete="off" data-value="rgbw" checked> RGBW
             <input type="radio" name="options" autocomplete="off" data-value="rgbw" checked> RGBW
@@ -963,31 +990,31 @@
             <input type="radio" name="options" autocomplete="off" data-value="rgb"> RGB
             <input type="radio" name="options" autocomplete="off" data-value="rgb"> RGB
           </label>
           </label>
         </div>
         </div>
-        
+
         <div> &nbsp; </div>
         <div> &nbsp; </div>
-        
+
         <pre id="sniffed-traffic"></pre>
         <pre id="sniffed-traffic"></pre>
       </div>
       </div>
     </div>
     </div>
-    
+
     <div class="row header-row">
     <div class="row header-row">
       <div class="col-sm-12">
       <div class="col-sm-12">
         <h1>Admin</h1>
         <h1>Admin</h1>
       </div>
       </div>
     </div>
     </div>
-    
+
     <div>&nbsp;</div>
     <div>&nbsp;</div>
-    
+
     <div class="row">
     <div class="row">
       <div class="col-sm-12">
       <div class="col-sm-12">
         <button type="button" class="btn btn-danger system-btn" data-command="restart">
         <button type="button" class="btn btn-danger system-btn" data-command="restart">
           Restart
           Restart
         </button>
         </button>
-        
+
         <button type="button" id="updates-btn" class="btn btn-primary">
         <button type="button" id="updates-btn" class="btn btn-primary">
           Check for Updates
           Check for Updates
         </button>
         </button>
-        
+
         <button type="button" class="btn btn-primary" data-toggle="modal" data-target="#update-firmware-modal">
         <button type="button" class="btn btn-primary" data-toggle="modal" data-target="#update-firmware-modal">
           Update Firmware
           Update Firmware
         </button>
         </button>
@@ -995,4 +1022,4 @@
     </div>
     </div>
   </div>
   </div>
 </body>
 </body>
-</html>
+</html>

+ 0 - 36
lib/MiLight/AbstractPL1167.h

@@ -1,36 +0,0 @@
-/*
- * AbstractPL1167.h
- *
- *  Created on: 29 May 2015
- *      Author: henryk
- */
-
-#ifdef ARDUINO
-#include "Arduino.h"
-#else
-#include <stdint.h>
-#include <stdlib.h>
-#endif
-
-#ifndef ABSTRACTPL1167_H_
-#define ABSTRACTPL1167_H_
-
-class AbstractPL1167 {
-  public:
-    virtual int open() = 0;
-
-    virtual int setPreambleLength(uint8_t preambleLength) = 0;
-    virtual int setSyncword(uint16_t syncword0, uint16_t syncword3) = 0;
-    virtual int setTrailerLength(uint8_t trailerLength) = 0;
-    virtual int setMaxPacketLength(uint8_t maxPacketLength) = 0;
-    virtual int setCRC(bool crc) = 0;
-    virtual int writeFIFO(const uint8_t data[], size_t data_length) = 0;
-    virtual int transmit(uint8_t channel) = 0;
-    virtual int receive(uint8_t channel) = 0;
-    virtual int readFIFO(uint8_t data[], size_t &data_length) = 0;
-};
-
-
-
-
-#endif /* ABSTRACTPL1167_H_ */

+ 633 - 0
lib/MiLight/LT8900MiLightRadio.cpp

@@ -0,0 +1,633 @@
+/*
+ * MiLightRadioPL1167_LT89000.cpp
+ *
+ *  Created on: 31 March 2017
+ *      Author: WoodsterDK
+ *
+ *  Very inspired by:
+ *  https://github.com/pmoscetta/authometion-milight/tree/master/Authometion-MiLight
+ *  https://bitbucket.org/robvanderveer/lt8900lib
+ */
+
+#include "LT8900MiLightRadio.h"
+#include <SPI.h>
+
+/**************************************************************************/
+// Constructor
+/**************************************************************************/
+LT8900MiLightRadio::LT8900MiLightRadio(byte byCSPin, byte byResetPin, byte byPktFlag, const MiLightRadioConfig& config)
+  : _config(config),
+    _channel(0)
+{
+  _csPin = byCSPin;
+	_pin_pktflag = byPktFlag;
+
+  pinMode(_pin_pktflag, INPUT);
+
+	if (byResetPin > 0)					// If zero then bypass hardware reset
+	{
+		pinMode(byResetPin, OUTPUT);
+		digitalWrite(byResetPin, LOW);
+		delay(200);
+		digitalWrite(byResetPin, HIGH);
+		delay(200);
+	}
+
+  pinMode(_csPin, OUTPUT);
+  digitalWrite(_csPin, HIGH);
+
+  SPI.begin();
+
+  SPI.setDataMode(SPI_MODE1);
+  // The following speed settings depends upon the wiring and PCB
+  //SPI.setFrequency(8000000);
+  SPI.setFrequency(4000000);
+  SPI.setBitOrder(MSBFIRST);
+
+  //Initialize transceiver with correct settings
+  vInitRadioModule(config.type);
+  delay(50);
+
+  // Check if HW is connected
+  _bConnected = bCheckRadioConnection();
+
+  //Reset SPI MODE to default
+  SPI.setDataMode(SPI_MODE0);
+  _waiting = false;
+}
+
+
+
+
+/**************************************************************************/
+// Checks the connection to the radio module by verifying a register setting
+/**************************************************************************/
+bool LT8900MiLightRadio::bCheckRadioConnection(void)
+{
+	bool bRetValue = false;
+	uint16_t value_0 = uiReadRegister(0);
+	uint16_t value_1 = uiReadRegister(1);
+
+	if ((value_0 == 0x6fe0) && (value_1 == 0x5681))
+	{
+    #ifdef DEBUG_PRINTF
+		  Serial.println(F("Radio module running correctly..."));
+    #endif
+		bRetValue = true;
+	}
+	else
+	{
+    #ifdef DEBUG_PRINTF
+		  Serial.println(F("Failed initializing the radio module..."));
+    #endif
+	}
+
+	return bRetValue;
+}
+
+/**************************************************************************/
+// Initialize radio module
+/**************************************************************************/
+void LT8900MiLightRadio::vInitRadioModule(MiLightRadioType type)
+{
+	if (type == RGB_CCT)
+	{
+		bool bWriteDefaultDefault = true;  // Is it okay to use the default power up values, without setting them
+
+		regWrite16(0x00, 0x6F, 0xE0, 7);  // Recommended value by PMmicro
+		regWrite16(0x02, 0x66, 0x17, 7);  // Recommended value by PMmicro
+		regWrite16(0x04, 0x9C, 0xC9, 7);  // Recommended value by PMmicro
+
+		regWrite16(0x05, 0x66, 0x37, 7);  // Recommended value by PMmicro
+		regWrite16(0x07, 0x00, 0x4C, 7);  // PL1167's TX/RX Enable and Channel Register, Default channel 76
+		regWrite16(0x08, 0x6C, 0x90, 7);  // Recommended value by PMmicro
+		regWrite16(0x09, 0x48, 0x00, 7);  // PA Control register
+
+		regWrite16(0x0B, 0x00, 0x08, 7);  // Recommended value by PMmicro
+		regWrite16(0x0D, 0x48, 0xBD, 7);  // Recommended value by PMmicro
+		regWrite16(0x16, 0x00, 0xFF, 7);  // Recommended value by PMmicro
+		regWrite16(0x18, 0x00, 0x67, 7);  // Recommended value by PMmicro
+
+		regWrite16(0x1A, 0x19, 0xE0, 7);  // Recommended value by PMmicro
+		regWrite16(0x1B, 0x13, 0x00, 7);  // Recommended value by PMmicro
+
+		regWrite16(0x20, 0x48, 0x00, 7);  // Recommended value by PMmicro
+		regWrite16(0x21, 0x3F, 0xC7, 7);  // Recommended value by PMmicro
+		regWrite16(0x22, 0x20, 0x00, 7);  // Recommended value by PMmicro
+		regWrite16(0x23, 0x03, 0x00, 7);  // Recommended value by PMmicro
+
+		regWrite16(0x24, 0x72, 0x36, 7);  // Sync R0
+		regWrite16(0x27, 0x18, 0x09, 7);  // Sync R3
+		regWrite16(0x28, 0x44, 0x02, 7);  // Recommended value by PMmicro
+		regWrite16(0x29, 0xB0, 0x00, 7);  // Recommended value by PMmicro
+		regWrite16(0x2A, 0xFD, 0xB0, 7);  // Recommended value by PMmicro
+
+		if (bWriteDefaultDefault == true)
+		{
+			regWrite16(0x01, 0x56, 0x81, 7);  // Recommended value by PMmicro
+			regWrite16(0x0A, 0x7F, 0xFD, 7);  // Recommended value by PMmicro
+			regWrite16(0x0C, 0x00, 0x00, 7);  // Recommended value by PMmicro
+			regWrite16(0x17, 0x80, 0x05, 7);  // Recommended value by PMmicro
+			regWrite16(0x19, 0x16, 0x59, 7);  // Recommended value by PMmicro
+			regWrite16(0x1C, 0x18, 0x00, 7);  // Recommended value by PMmicro
+
+			regWrite16(0x25, 0x00, 0x00, 7);  // Recommended value by PMmicro
+			regWrite16(0x26, 0x00, 0x00, 7);  // Recommended value by PMmicro
+			regWrite16(0x2B, 0x00, 0x0F, 7);  // Recommended value by PMmicro
+		}
+	}
+	else if((type == RGBW) || (type == CCT) || (type == RGB) )
+	{
+		regWrite16(0, 111, 224, 7);  // Recommended value by PMmicro
+		regWrite16(1, 86, 129, 7);   // Recommended value by PMmicro
+		regWrite16(2, 102, 23, 7);   // Recommended value by PMmicro
+		regWrite16(4, 156, 201, 7);  // Recommended value by PMmicro
+		regWrite16(5, 102, 55, 7);   // Recommended value by PMmicro
+		regWrite16(7, 0, 76, 7);     // PL1167's TX/RX Enable and Channel Register
+		regWrite16(8, 108, 144, 7);  // Recommended value by PMmicro
+		regWrite16(9, 72, 0, 7);     // PL1167's PA Control Register
+		regWrite16(10, 127, 253, 7); // Recommended value by PMmicro
+		regWrite16(11, 0, 8, 7);     // PL1167's RSSI OFF Control Register -- ???
+		regWrite16(12, 0, 0, 7);     // Recommended value by PMmicro
+		regWrite16(13, 72, 189, 7);  // Recommended value by PMmicro
+		regWrite16(22, 0, 255, 7);   // Recommended value by PMmicro
+		regWrite16(23, 128, 5, 7);   // PL1167's VCO Calibration Enable Register
+		regWrite16(24, 0, 103, 7);   // Recommended value by PMmicro
+		regWrite16(25, 22, 89, 7);   // Recommended value by PMmicro
+		regWrite16(26, 25, 224, 7);  // Recommended value by PMmicro
+		regWrite16(27, 19, 0, 7);    // Recommended value by PMmicro
+		regWrite16(28, 24, 0, 7);    // Recommended value by PMmicro
+		regWrite16(32, 72, 0, 7);    // PL1167's Data Configure Register: LEN_PREAMBLE = 010 -> (0xAAAAAA) 3 bytes, LEN_SYNCWORD = 01 -> 32 bits, LEN_TRAILER = 000 -> (0x05) 4 bits, TYPE_PKT_DAT = 00 -> NRZ law data, TYPE_FEC = 00 -> No FEC
+		regWrite16(33, 63, 199, 7);  // PL1167's Delay Time Control Register 0
+		regWrite16(34, 32, 0, 7);    // PL1167's Delay Time Control Register 1
+		regWrite16(35, 3, 0, 7);     // PL1167's Power Management and Miscellaneous Register
+		regWrite16(40, 68, 2, 7);    // PL1167's FIFO and SYNCWORD Threshold Register
+		regWrite16(41, 176, 0, 7);   // PL1167's Miscellaneous Register: CRC_ON = 1 -> ON, SCR_ON = 0 -> OFF, EN_PACK_LEN = 1 -> ON, FW_TERM_TX = 1 -> ON, AUTO_ACK = 0 -> OFF, PKT_LEVEL = 0 -> PKT active high, CRC_INIT_DAT = 0
+		regWrite16(42, 253, 176, 7); // PL1167's SCAN RSSI Register 0
+		regWrite16(43, 0, 15, 7);    // PL1167's SCAN RSSI Register 1
+		delay(200);
+		regWrite16(128, 0, 0, 7);
+		regWrite16(129, 255, 255, 7);
+		regWrite16(130, 0, 0, 7);
+		regWrite16(132, 0, 0, 7);
+		regWrite16(133, 255, 255, 7);
+		regWrite16(135, 255, 255, 7);
+		regWrite16(136, 0, 0, 7);
+		regWrite16(137, 255, 255, 7);
+		regWrite16(138, 0, 0, 7);
+		regWrite16(139, 255, 255, 7);
+		regWrite16(140, 0, 0, 7);
+		regWrite16(141, 255, 255, 7);
+		regWrite16(150, 0, 0, 7);
+		regWrite16(151, 255, 255, 7);
+		regWrite16(152, 0, 0, 7);
+		regWrite16(153, 255, 255, 7);
+		regWrite16(154, 0, 0, 7);
+		regWrite16(155, 255, 255, 7);
+		regWrite16(156, 0, 0, 7);
+		regWrite16(160, 0, 0, 7);
+		regWrite16(161, 255, 255, 7);
+		regWrite16(162, 0, 0, 7);
+		regWrite16(163, 255, 255, 7);
+		regWrite16(168, 0, 0, 7);
+		regWrite16(169, 255, 255, 7);
+		regWrite16(170, 0, 0, 7);
+		regWrite16(171, 255, 255, 7);
+		regWrite16(7, 0, 0, 7);       // Disable TX/RX and set radio channel to 0
+	}
+}
+
+/**************************************************************************/
+// Set sync word
+/**************************************************************************/
+void LT8900MiLightRadio::vSetSyncWord(uint16_t syncWord3, uint16_t syncWord2, uint16_t syncWord1, uint16_t syncWord0)
+{
+	uiWriteRegister(R_SYNCWORD1, syncWord0);
+	uiWriteRegister(R_SYNCWORD2, syncWord1);
+	uiWriteRegister(R_SYNCWORD3, syncWord1);
+	uiWriteRegister(R_SYNCWORD4, syncWord3);
+}
+
+/**************************************************************************/
+// Low level register write with delay
+/**************************************************************************/
+void LT8900MiLightRadio::regWrite16(byte ADDR, byte V1, byte V2, byte WAIT)
+{
+	digitalWrite(_csPin, LOW);
+	SPI.transfer(ADDR);
+	SPI.transfer(V1);
+	SPI.transfer(V2);
+	digitalWrite(_csPin, HIGH);
+	delayMicroseconds(WAIT);
+}
+
+
+/**************************************************************************/
+// Low level register read
+/**************************************************************************/
+uint16_t LT8900MiLightRadio::uiReadRegister(uint8_t reg)
+{
+	SPI.setDataMode(SPI_MODE1);
+	digitalWrite(_csPin, LOW);
+	SPI.transfer(REGISTER_READ | (REGISTER_MASK & reg));
+	uint8_t high = SPI.transfer(0x00);
+	uint8_t low = SPI.transfer(0x00);
+
+	digitalWrite(_csPin, HIGH);
+
+	SPI.setDataMode(SPI_MODE0);
+	return (high << 8 | low);
+}
+
+
+/**************************************************************************/
+// Low level 16bit register write
+/**************************************************************************/
+uint8_t LT8900MiLightRadio::uiWriteRegister(uint8_t reg, uint16_t data)
+{
+	uint8_t high = data >> 8;
+	uint8_t low = data & 0xFF;
+
+	digitalWrite(_csPin, LOW);
+
+	uint8_t result = SPI.transfer(REGISTER_WRITE | (REGISTER_MASK & reg));
+	SPI.transfer(high);
+	SPI.transfer(low);
+
+	digitalWrite(_csPin, HIGH);
+
+	return result;
+}
+
+/**************************************************************************/
+// Start listening on specified channel and syncword
+/**************************************************************************/
+void LT8900MiLightRadio::vStartListening(uint uiChannelToListenTo)
+{
+  _dupes_received = 0;
+  vSetSyncWord(_config.syncword3, 0,0,_config.syncword0);
+	//vSetChannel(uiChannelToListenTo);
+
+  _channel = uiChannelToListenTo;
+
+	uiWriteRegister(R_CHANNEL, _channel & CHANNEL_MASK);   //turn off rx/tx
+	delay(3);
+	uiWriteRegister(R_FIFO_CONTROL, 0x0080);  //flush rx
+	uiWriteRegister(R_CHANNEL, (_channel & CHANNEL_MASK) | _BV(CHANNEL_RX_BIT));   //enable RX
+	delay(5);
+}
+
+/**************************************************************************/
+// Resume listening - without changing the channel and syncword
+/**************************************************************************/
+void LT8900MiLightRadio::vResumeRX(void)
+{
+  _dupes_received = 0;
+	uiWriteRegister(R_CHANNEL, _channel & CHANNEL_MASK);   //turn off rx/tx
+	delay(3);
+	uiWriteRegister(R_FIFO_CONTROL, 0x0080);  //flush rx
+	uiWriteRegister(R_CHANNEL, (_channel & CHANNEL_MASK) | _BV(CHANNEL_RX_BIT));   //enable RX
+}
+
+/**************************************************************************/
+// Check if data is available using the hardware pin PKT_FLAG
+/**************************************************************************/
+bool LT8900MiLightRadio::bAvailablePin()
+{
+	//read the PKT_FLAG pin.
+	if (digitalRead(_pin_pktflag) != 0)
+	{
+		return true;
+	}
+
+	return false;
+}
+
+/**************************************************************************/
+// Check if data is available using the PKT_FLAG state in the status register
+/**************************************************************************/
+bool LT8900MiLightRadio::bAvailableRegister()
+{
+	//read the PKT_FLAG state; this can also be done with a hard wire.
+	uint16_t value = uiReadRegister(R_STATUS);
+
+	if (value & STATUS_PKT_BIT_MASK != 0)
+	{
+		return true;
+	}
+
+	return false;
+}
+
+/**************************************************************************/
+// Read the RX buffer
+/**************************************************************************/
+int LT8900MiLightRadio::iReadRXBuffer(uint8_t *buffer, size_t maxBuffer)
+{
+	uint16_t value = uiReadRegister(R_STATUS);
+	if (bitRead(value, STATUS_CRC_BIT) == 0)
+	{
+		uint16_t data = uiReadRegister(R_FIFO);
+		uint8_t packetSize = data >> 8;
+		if (maxBuffer < packetSize + 1)
+		{
+			//BUFFER TOO SMALL
+			return -2;
+		}
+
+		uint8_t pos = 0;;
+		buffer[pos++] = (data & 0xFF);
+		while (pos < packetSize)
+		{
+			data = uiReadRegister(R_FIFO);
+			buffer[pos++] = data >> 8;
+			buffer[pos++] = data & 0xFF;
+		}
+
+		return packetSize;
+	}
+	else
+	{
+		//CRC error
+		return -1;
+	}
+}
+
+
+/**************************************************************************/
+// Set the active channel for the radio module
+/**************************************************************************/
+void LT8900MiLightRadio::vSetChannel(uint8_t channel)
+{
+	_channel = channel;
+	uiWriteRegister(R_CHANNEL, (_channel & CHANNEL_MASK));
+}
+
+/**************************************************************************/
+// Startup
+/**************************************************************************/
+int LT8900MiLightRadio::begin()
+{
+  vSetChannel(_config.channels[0]);
+  configure();
+  available();
+  return 0;
+}
+
+/**************************************************************************/
+// Configure the module according to type, and start listening
+/**************************************************************************/
+int LT8900MiLightRadio::configure()
+{
+  vInitRadioModule(_config.type);
+  vSetSyncWord(_config.syncword3, 0,0,_config.syncword0);
+  vStartListening(_config.channels[0]);
+  return 0;
+}
+
+/**************************************************************************/
+// Check if data is available
+/**************************************************************************/
+bool LT8900MiLightRadio::available()
+{
+  _waiting = false;
+
+  if(bAvailablePin())
+  {
+    int iStartTime = millis();
+    int iUpdateStamp = 0;
+    static byte byLastReceived[32];
+
+    byte byaFramesSizes[20];
+    byte byaFramesReceivedCount[20];
+    byte byaFramesReceived[20][32];
+    byte byFrameCounter = 0;
+
+    bool bDifference = false;
+
+    bool bRetVal = false;
+
+    if (bAvailablePin())
+    {
+      unsigned long ulTimeStamp = millis();
+      unsigned long ulElapsedTime = 0;
+
+      do
+      {
+        if (bAvailablePin())
+        {
+          iUpdateStamp = millis();
+          uint8_t buf[32];
+
+          int packetSize = iReadRXBuffer(buf, 32);
+          if (packetSize > 0)
+          {
+            bRetVal = true;
+            bDifference = false;
+
+            for (int iCounter = 0; iCounter < packetSize && bDifference == false; iCounter++)
+            {
+              if (buf[iCounter] != byLastReceived[iCounter])
+              {
+                bDifference = true;
+              }
+            }
+
+            if (bDifference == true)
+            {
+              for (int i = 0; i < packetSize; i++)
+              {
+                byaFramesReceived[byFrameCounter][i] = buf[i];
+                byLastReceived[i] = buf[i];
+                byaFramesSizes[i] = packetSize;
+              }
+
+              byaFramesReceivedCount[byFrameCounter] = 1;
+              byFrameCounter++;
+            }
+            else
+            {
+              byaFramesReceivedCount[byFrameCounter-1]++;
+            }
+          }
+          else
+          {
+            if (packetSize < 0)
+            {
+              Serial.println(F("LT8900: Packet less than zero, buffer to small"));
+            }
+            else
+            {
+              Serial.println(F("LT8900: Packet read fail"));
+            }
+          }
+
+          vResumeRX();
+        }
+
+        ulElapsedTime = millis();
+        yield();
+        ulElapsedTime = ulElapsedTime - ulTimeStamp;
+      }while (ulElapsedTime < 1000 );
+
+      #ifdef DEBUG_PRINTF
+      printf_P(PSTR("ElapsedRX: %d\n"), iUpdateStamp - iStartTime);
+      #endif
+
+      if (byFrameCounter != 0)
+      {
+        #ifdef DEBUG_PRINTF
+        printf_P(PSTR("Packets received: %d\n"), byFrameCounter);
+        #endif
+
+        for (byte byCounterFrame = 0; byCounterFrame < byFrameCounter; byCounterFrame++)
+        {
+          _dupes_received = byaFramesReceivedCount[byCounterFrame];
+
+          #ifdef DEBUG_PRINTF
+          printf_P(PSTR("Packet read OK, rec: %d Frame:\n"), byaFramesReceivedCount[byCounterFrame]);
+          #endif
+          //dump the packet.
+
+          _packet[0]=byaFramesSizes[byCounterFrame];
+          for (int iByteCounter = 0; iByteCounter < byaFramesSizes[byCounterFrame]; iByteCounter++)
+          {
+            byte byReceivedvalue = byaFramesReceived[byCounterFrame][iByteCounter];
+            _packet[iByteCounter+1] = byReceivedvalue;
+
+            #ifdef DEBUG_PRINTF
+            printf_P(PSTR("%02X "), byReceivedvalue);
+            #endif
+          }
+
+          _waiting = true;
+        }
+
+        #ifdef DEBUG_PRINTF
+        Serial.println();
+        #endif
+      }
+    }
+    yield();
+  }
+
+  return _waiting;
+}
+
+int LT8900MiLightRadio::dupesReceived()
+{
+  return _dupes_received;
+}
+
+/**************************************************************************/
+// Read received data from buffer to upper layer
+/**************************************************************************/
+int LT8900MiLightRadio::read(uint8_t frame[], size_t &frame_length)
+{
+  if (!_waiting)
+  {
+    frame_length = 0;
+    return -1;
+  }
+
+  if (frame_length > sizeof(_packet) - 1)
+  {
+    frame_length = sizeof(_packet) - 1;
+  }
+
+  if (frame_length > _packet[0])
+  {
+    frame_length = _packet[0];
+  }
+
+  memcpy(frame, _packet +1 , frame_length);
+  _waiting = false;
+
+  // Start RX mode again
+  vResumeRX();
+
+  return _packet[0];
+}
+
+/**************************************************************************/
+// Write data
+/**************************************************************************/
+int LT8900MiLightRadio::write(uint8_t frame[], size_t frame_length)
+{
+  if (frame_length > sizeof(_out_packet) - 1) {
+    return -1;
+  }
+
+  memcpy(_out_packet + 1, frame, frame_length);
+  _out_packet[0] = frame_length;
+
+  SPI.setDataMode(SPI_MODE1);
+
+  int retval = resend();
+  yield();
+
+  SPI.setDataMode(SPI_MODE0);
+
+  if (retval < 0) {
+    return retval;
+  }
+  return frame_length;
+}
+
+/**************************************************************************/
+// Handle the transmission to regarding to freq diversity and repeats
+/**************************************************************************/
+int LT8900MiLightRadio::resend()
+{
+  byte Length =  _out_packet[0];
+
+  for (size_t i = 0; i < MiLightRadioConfig::NUM_CHANNELS; i++)
+  {
+    sendPacket(_out_packet, Length, _config.channels[i]);
+    delayMicroseconds(DEFAULT_TIME_BETWEEN_RETRANSMISSIONS_uS);
+  }
+
+  return 0;
+}
+
+/**************************************************************************/
+// The actual transmit happens here
+/**************************************************************************/
+bool LT8900MiLightRadio::sendPacket(uint8_t *data, size_t packetSize, byte byChannel)
+{
+  if(_bConnected) // Must be connected to module otherwise it might lookup waiting for _pin_pktflag
+  {
+    if (packetSize < 1 || packetSize > 255)
+    {
+      return false;
+    }
+
+    uiWriteRegister(R_CHANNEL, 0x0000);
+    uiWriteRegister(R_FIFO_CONTROL, 0x8080);  //flush tx and RX
+
+    digitalWrite(_csPin, LOW);        // Enable PL1167 SPI transmission
+    SPI.transfer(R_FIFO);             // Start writing PL1167's FIFO Data register
+    SPI.transfer(packetSize);         // Length of data buffer: x bytes
+
+    for (byte iCounter = 0; iCounter < packetSize; iCounter++)
+    {
+      SPI.transfer((data[1+iCounter]));
+    }
+    digitalWrite(_csPin, HIGH);  // Disable PL1167 SPI transmission
+    delayMicroseconds(10);
+
+    uiWriteRegister(R_CHANNEL,  (byChannel & CHANNEL_MASK) | _BV(CHANNEL_TX_BIT));   //enable RX
+
+    //Wait until the packet is sent.
+    while (digitalRead(_pin_pktflag) == 0)
+    {
+        //do nothing.
+    }
+
+    return true;
+  }
+}
+
+const MiLightRadioConfig& LT8900MiLightRadio::config() {
+  return _config;
+}

+ 90 - 0
lib/MiLight/LT8900MiLightRadio.h

@@ -0,0 +1,90 @@
+#ifdef ARDUINO
+#include "Arduino.h"
+#else
+#include <stdint.h>
+#include <stdlib.h>
+#include <string.h>
+#endif
+
+#include <MiLightRadioConfig.h>
+#include <MiLightButtons.h>
+#include <MiLightRadio.h>
+
+//#define DEBUG_PRINTF
+
+// Register defines
+#define REGISTER_READ       0b10000000  //bin
+#define REGISTER_WRITE      0b00000000  //bin
+#define REGISTER_MASK       0b01111111  //bin
+
+#define R_CHANNEL           7
+#define CHANNEL_RX_BIT      7
+#define CHANNEL_TX_BIT      8
+#define CHANNEL_MASK        0b01111111  ///bin
+
+#define STATUS_PKT_BIT_MASK	0x40
+
+#define R_STATUS            48
+#define STATUS_CRC_BIT      15
+
+#define R_FIFO              50
+#define R_FIFO_CONTROL      52
+
+#define R_SYNCWORD1         36
+#define R_SYNCWORD2         37
+#define R_SYNCWORD3         38
+#define R_SYNCWORD4         39
+
+//#define DEFAULT_TIME_BETWEEN_RETRANSMISSIONS_uS	350
+#define DEFAULT_TIME_BETWEEN_RETRANSMISSIONS_uS	0
+
+#ifndef MILIGHTRADIOPL1167_LT8900_H_
+#define MILIGHTRADIOPL1167_LT8900_H_
+
+class LT8900MiLightRadio : public MiLightRadio {
+  public:
+    LT8900MiLightRadio(byte byCSPin, byte byResetPin, byte byPktFlag, const MiLightRadioConfig& config);
+
+    virtual int begin();
+    virtual bool available();
+    virtual int read(uint8_t frame[], size_t &frame_length);
+    virtual int dupesReceived();
+    virtual int write(uint8_t frame[], size_t frame_length);
+    virtual int resend();
+    virtual int configure();
+    virtual const MiLightRadioConfig& config();
+
+  private:
+
+    void vInitRadioModule(MiLightRadioType type);
+    void vSetSyncWord(uint16_t syncWord3, uint16_t syncWord2, uint16_t syncWord1, uint16_t syncWord0);
+    uint16_t uiReadRegister(uint8_t reg);
+    void regWrite16(byte ADDR, byte V1, byte V2, byte WAIT);
+    uint8_t uiWriteRegister(uint8_t reg, uint16_t data);
+
+    bool bAvailablePin(void);
+    bool bAvailableRegister(void);
+    void vStartListening(uint uiChannelToListenTo);
+    void vResumeRX(void);
+    int iReadRXBuffer(uint8_t *buffer, size_t maxBuffer);
+    void vSetChannel(uint8_t channel);
+    void vGenericSendPacket(int iMode, int iLength, byte *pbyFrame, byte byChannel );
+    bool bCheckRadioConnection(void);
+    bool sendPacket(uint8_t *data, size_t packetSize,byte byChannel);
+
+    byte _pin_pktflag;
+    byte _csPin;
+    bool _bConnected;
+
+    const MiLightRadioConfig& _config;
+
+    uint8_t _channel;
+    uint8_t _packet[10];
+    uint8_t _out_packet[10];
+    bool _waiting;
+    int _dupes_received;
+};
+
+
+
+#endif

+ 65 - 36
lib/MiLight/MiLightClient.cpp

@@ -2,40 +2,61 @@
 #include <MiLightRadioConfig.h>
 #include <MiLightRadioConfig.h>
 #include <Arduino.h>
 #include <Arduino.h>
 
 
+MiLightClient::MiLightClient(MiLightRadioFactory* radioFactory)
+  : resendCount(MILIGHT_DEFAULT_RESEND_COUNT),
+    currentRadio(NULL),
+    numRadios(MiLightRadioConfig::NUM_CONFIGS)
+{
+  radios = new MiLightRadio*[numRadios];
+
+  for (size_t i = 0; i < numRadios; i++) {
+    radios[i] = radioFactory->create(*MiLightRadioConfig::ALL_CONFIGS[i]);
+  }
+
+  this->currentRadio = radios[0];
+  this->currentRadio->configure();
+}
+
+void MiLightClient::begin() {
+  for (size_t i = 0; i < numRadios; i++) {
+    radios[i]->begin();
+  }
+}
+
 MiLightRadio* MiLightClient::switchRadio(const MiLightRadioType type) {
 MiLightRadio* MiLightClient::switchRadio(const MiLightRadioType type) {
-  RadioStack* stack = NULL;
-  
+  MiLightRadio* radio = NULL;
+
   for (int i = 0; i < numRadios; i++) {
   for (int i = 0; i < numRadios; i++) {
-    if (radios[i]->config.type == type) {
-      stack = radios[i];
+    if (this->radios[i]->config().type == type) {
+      radio = radios[i];
       break;
       break;
     }
     }
   }
   }
-  
-  if (stack != NULL) {
-    MiLightRadio *radio = stack->getRadio();
-    
-    if (currentRadio->config.type != stack->config.type) {
+
+  if (radio != NULL) {
+    if (currentRadio != radio) {
       radio->configure();
       radio->configure();
     }
     }
-    
-    currentRadio = stack;
-    formatter = stack->config.packetFormatter;
+
+    this->currentRadio = radio;
+    this->formatter = radio->config().packetFormatter;
+
     return radio;
     return radio;
   } else {
   } else {
     Serial.print(F("MiLightClient - tried to get radio for unknown type: "));
     Serial.print(F("MiLightClient - tried to get radio for unknown type: "));
     Serial.println(type);
     Serial.println(type);
   }
   }
-  
+
   return NULL;
   return NULL;
 }
 }
 
 
-void MiLightClient::prepare(MiLightRadioConfig& config, 
-  const uint16_t deviceId, 
+
+void MiLightClient::prepare(MiLightRadioConfig& config,
+  const uint16_t deviceId,
   const uint8_t groupId) {
   const uint8_t groupId) {
-  
+
   switchRadio(config.type);
   switchRadio(config.type);
-  
+
   if (deviceId >= 0 && groupId >= 0) {
   if (deviceId >= 0 && groupId >= 0) {
     formatter->prepare(deviceId, groupId);
     formatter->prepare(deviceId, groupId);
   }
   }
@@ -45,41 +66,49 @@ void MiLightClient::setResendCount(const unsigned int resendCount) {
   this->resendCount = resendCount;
   this->resendCount = resendCount;
 }
 }
 
 
+
 bool MiLightClient::available() {
 bool MiLightClient::available() {
   if (currentRadio == NULL) {
   if (currentRadio == NULL) {
     return false;
     return false;
   }
   }
-  
-  return currentRadio->getRadio()->available();
-}
 
 
+  return currentRadio->available();
+}
 void MiLightClient::read(uint8_t packet[]) {
 void MiLightClient::read(uint8_t packet[]) {
   if (currentRadio == NULL) {
   if (currentRadio == NULL) {
     return;
     return;
   }
   }
-  
-  size_t length;
-  currentRadio->getRadio()->read(packet, length);
+
+  size_t length = currentRadio->config().getPacketLength();
+
+  currentRadio->read(packet, length);
 }
 }
 
 
 void MiLightClient::write(uint8_t packet[]) {
 void MiLightClient::write(uint8_t packet[]) {
   if (currentRadio == NULL) {
   if (currentRadio == NULL) {
     return;
     return;
   }
   }
-  
+
 #ifdef DEBUG_PRINTF
 #ifdef DEBUG_PRINTF
   printf("Sending packet: ");
   printf("Sending packet: ");
-  for (int i = 0; i < currentRadio->config.getPacketLength(); i++) {
+  for (int i = 0; i < currentRadio->config().getPacketLength(); i++) {
     printf("%02X", packet[i]);
     printf("%02X", packet[i]);
   }
   }
   printf("\n");
   printf("\n");
+  int iStart = millis();
 #endif
 #endif
-  
+
   for (int i = 0; i < this->resendCount; i++) {
   for (int i = 0; i < this->resendCount; i++) {
-    currentRadio->getRadio()->write(packet, currentRadio->config.getPacketLength());
+    currentRadio->write(packet, currentRadio->config().getPacketLength());
   }
   }
+
+#ifdef DEBUG_PRINTF
+  int iElapsed = millis() - iStart;
+  Serial.print("Elapsed: ");
+  Serial.println(iElapsed);
+#endif
 }
 }
-    
+
 void MiLightClient::updateColorRaw(const uint8_t color) {
 void MiLightClient::updateColorRaw(const uint8_t color) {
   formatter->updateColorRaw(color);
   formatter->updateColorRaw(color);
   flushPacket();
   flushPacket();
@@ -94,7 +123,7 @@ void MiLightClient::updateBrightness(const uint8_t brightness) {
   formatter->updateBrightness(brightness);
   formatter->updateBrightness(brightness);
   flushPacket();
   flushPacket();
 }
 }
-    
+
 void MiLightClient::updateMode(uint8_t mode) {
 void MiLightClient::updateMode(uint8_t mode) {
   formatter->updateMode(mode);
   formatter->updateMode(mode);
   flushPacket();
   flushPacket();
@@ -118,7 +147,7 @@ void MiLightClient::modeSpeedUp() {
   formatter->modeSpeedUp();
   formatter->modeSpeedUp();
   flushPacket();
   flushPacket();
 }
 }
-    
+
 void MiLightClient::updateStatus(MiLightStatus status, uint8_t groupId) {
 void MiLightClient::updateStatus(MiLightStatus status, uint8_t groupId) {
   formatter->updateStatus(status, groupId);
   formatter->updateStatus(status, groupId);
   flushPacket();
   flushPacket();
@@ -148,7 +177,7 @@ void MiLightClient::unpair() {
   formatter->unpair();
   formatter->unpair();
   flushPacket();
   flushPacket();
 }
 }
-    
+
 void MiLightClient::increaseBrightness() {
 void MiLightClient::increaseBrightness() {
   formatter->increaseBrightness();
   formatter->increaseBrightness();
   flushPacket();
   flushPacket();
@@ -182,24 +211,24 @@ void MiLightClient::command(uint8_t command, uint8_t arg) {
 void MiLightClient::formatPacket(uint8_t* packet, char* buffer) {
 void MiLightClient::formatPacket(uint8_t* packet, char* buffer) {
   formatter->format(packet, buffer);
   formatter->format(packet, buffer);
 }
 }
-    
+
 void MiLightClient::flushPacket() {
 void MiLightClient::flushPacket() {
   PacketStream& stream = formatter->buildPackets();
   PacketStream& stream = formatter->buildPackets();
   const size_t prevNumRepeats = this->resendCount;
   const size_t prevNumRepeats = this->resendCount;
-  
+
   // When sending multiple packets, normalize the number of repeats
   // When sending multiple packets, normalize the number of repeats
   if (stream.numPackets > 1) {
   if (stream.numPackets > 1) {
     setResendCount(MILIGHT_DEFAULT_RESEND_COUNT);
     setResendCount(MILIGHT_DEFAULT_RESEND_COUNT);
   }
   }
-  
+
   while (stream.hasNext()) {
   while (stream.hasNext()) {
     write(stream.next());
     write(stream.next());
-    
+
     if (stream.hasNext()) {
     if (stream.hasNext()) {
       delay(10);
       delay(10);
     }
     }
   }
   }
-  
+
   setResendCount(prevNumRepeats);
   setResendCount(prevNumRepeats);
   formatter->reset();
   formatter->reset();
 }
 }

+ 61 - 79
lib/MiLight/MiLightClient.h

@@ -1,92 +1,74 @@
 #include <Arduino.h>
 #include <Arduino.h>
 #include <MiLightRadio.h>
 #include <MiLightRadio.h>
-#include <PL1167_nRF24.h>
-#include <RF24.h>
+#include <MiLightRadioFactory.h>
 #include <MiLightButtons.h>
 #include <MiLightButtons.h>
-#include <RadioStack.h>
+#include <Settings.h>
 
 
 #ifndef _MILIGHTCLIENT_H
 #ifndef _MILIGHTCLIENT_H
 #define _MILIGHTCLIENT_H
 #define _MILIGHTCLIENT_H
 
 
-// #define DEBUG_PRINTF
+//#define DEBUG_PRINTF
 
 
 #define MILIGHT_DEFAULT_RESEND_COUNT 10
 #define MILIGHT_DEFAULT_RESEND_COUNT 10
 
 
 class MiLightClient {
 class MiLightClient {
-  public:
-    MiLightClient(uint8_t cePin, uint8_t csnPin)
-      : rf(RF24(cePin, csnPin)),
-      resendCount(MILIGHT_DEFAULT_RESEND_COUNT),
-      currentRadio(NULL),
-      numRadios(MiLightRadioConfig::NUM_CONFIGS)
-    {
-      radios = new RadioStack*[numRadios];
-      
-      for (size_t i = 0; i < numRadios; i++) {
-        radios[i] = new RadioStack(rf, *MiLightRadioConfig::ALL_CONFIGS[i]);
-      }
-      
-      currentRadio = radios[0];
-      currentRadio->getRadio()->configure();
-    }
-    
-    ~MiLightClient() {
-      delete[] radios;
-    }
-    
-    void begin() {
-      for (size_t i = 0; i < numRadios; i++) {
-        radios[i]->getRadio()->begin();
-      }
-    }
-    
-    void prepare(MiLightRadioConfig& config, const uint16_t deviceId = -1, const uint8_t groupId = -1);
-    void setResendCount(const unsigned int resendCount);
-    bool available();
-    void read(uint8_t packet[]);
-    void write(uint8_t packet[]);
-    
-    // Common methods
-    void updateStatus(MiLightStatus status);
-    void updateStatus(MiLightStatus status, uint8_t groupId);
-    void pair();
-    void unpair();
-    void command(uint8_t command, uint8_t arg);
-    void updateMode(uint8_t mode);
-    void nextMode();
-    void previousMode();
-    void modeSpeedDown();
-    void modeSpeedUp();
-    
-    // RGBW methods
-    void updateHue(const uint16_t hue);
-    void updateBrightness(const uint8_t brightness);
-    void updateColorWhite();
-    void updateColorRaw(const uint8_t color);
-
-    // CCT methods
-    void updateTemperature(const uint8_t colorTemperature);
-    void decreaseTemperature();
-    void increaseTemperature();
-    void increaseBrightness();
-    void decreaseBrightness();
-    
-    void updateSaturation(const uint8_t saturation);
-    
-    void formatPacket(uint8_t* packet, char* buffer);
-    
-    
-  protected:
-    RF24 rf;
-    RadioStack** radios;
-    RadioStack* currentRadio;
-    PacketFormatter* formatter;
-    const size_t numRadios;
-    
-    unsigned int resendCount;
-    
-    MiLightRadio* switchRadio(const MiLightRadioType type);
-    void flushPacket();
+public:
+  MiLightClient(MiLightRadioFactory* radioFactory);
+
+  ~MiLightClient() {
+    delete[] radios;
+  }
+
+  void begin();
+  void prepare(MiLightRadioConfig& config, const uint16_t deviceId = -1, const uint8_t groupId = -1);
+
+  void setResendCount(const unsigned int resendCount);
+  bool available();
+  void read(uint8_t packet[]);
+  void write(uint8_t packet[]);
+
+  // Common methods
+  void updateStatus(MiLightStatus status);
+  void updateStatus(MiLightStatus status, uint8_t groupId);
+  void pair();
+  void unpair();
+  void command(uint8_t command, uint8_t arg);
+  void updateMode(uint8_t mode);
+  void nextMode();
+  void previousMode();
+  void modeSpeedDown();
+  void modeSpeedUp();
+
+  // RGBW methods
+  void updateHue(const uint16_t hue);
+  void updateBrightness(const uint8_t brightness);
+  void updateColorWhite();
+  void updateColorRaw(const uint8_t color);
+
+  // CCT methods
+  void updateTemperature(const uint8_t colorTemperature);
+  void decreaseTemperature();
+  void increaseTemperature();
+  void increaseBrightness();
+  void decreaseBrightness();
+
+  void updateSaturation(const uint8_t saturation);
+
+  void formatPacket(uint8_t* packet, char* buffer);
+
+
+protected:
+
+  MiLightRadio** radios;
+  MiLightRadio* currentRadio;
+  PacketFormatter* formatter;
+  const size_t numRadios;
+
+  unsigned int resendCount;
+
+  MiLightRadio* switchRadio(const MiLightRadioType type);
+
+  void flushPacket();
 };
 };
 
 
-#endif
+#endif

+ 14 - 32
lib/MiLight/MiLightRadio.h

@@ -1,49 +1,31 @@
-/*
- * MiLightRadio.h
- *
- *  Created on: 29 May 2015
- *      Author: henryk
- */
 
 
 #ifdef ARDUINO
 #ifdef ARDUINO
 #include "Arduino.h"
 #include "Arduino.h"
 #else
 #else
 #include <stdint.h>
 #include <stdint.h>
 #include <stdlib.h>
 #include <stdlib.h>
-#include <string.h>
 #endif
 #endif
 
 
-#include "AbstractPL1167.h"
 #include <MiLightRadioConfig.h>
 #include <MiLightRadioConfig.h>
 
 
-// #define DEBUG_PRINTF
-
-#ifndef MILIGHTRADIO_H_
-#define MILIGHTRADIO_H_
+#ifndef _MILIGHT_RADIO_H_
+#define _MILIGHT_RADIO_H_
 
 
 class MiLightRadio {
 class MiLightRadio {
   public:
   public:
-    MiLightRadio(AbstractPL1167 &pl1167, const MiLightRadioConfig& config);
-    
-    int begin();
-    bool available();
-    int read(uint8_t frame[], size_t &frame_length);
-    int dupesReceived();
-    int write(uint8_t frame[], size_t frame_length);
-    int resend();
-    int configure();
-    
-  private:
-    AbstractPL1167 &_pl1167;
-    const MiLightRadioConfig& config;
-    uint32_t _prev_packet_id;
-
-    uint8_t _packet[10];
-    uint8_t _out_packet[10];
-    bool _waiting;
-    int _dupes_received;
+
+    virtual int begin();
+    virtual bool available();
+    virtual int read(uint8_t frame[], size_t &frame_length);
+    virtual int dupesReceived();
+    virtual int write(uint8_t frame[], size_t frame_length);
+    virtual int resend();
+    virtual int configure();
+    virtual const MiLightRadioConfig& config();
+
 };
 };
 
 
 
 
 
 
-#endif /* MILIGHTRADIO_H_ */
+
+#endif

+ 32 - 0
lib/MiLight/MiLightRadioFactory.cpp

@@ -0,0 +1,32 @@
+#include <MiLightRadioFactory.h>
+
+MiLightRadioFactory* MiLightRadioFactory::fromSettings(const Settings& settings) {
+  switch (settings.radioInterfaceType) {
+    case nRF24:
+      return new NRF24Factory(settings.csnPin, settings.cePin);
+
+    case LT8900:
+      return new LT8900Factory(settings.csnPin, settings.resetPin, settings.cePin);
+
+    default:
+      return NULL;
+  }
+}
+
+NRF24Factory::NRF24Factory(uint8_t csnPin, uint8_t cePin)
+  : rf24(RF24(cePin, csnPin))
+{ }
+
+MiLightRadio* NRF24Factory::create(const MiLightRadioConfig &config) {
+  return new NRF24MiLightRadio(rf24, config);
+}
+
+LT8900Factory::LT8900Factory(uint8_t csPin, uint8_t resetPin, uint8_t pktFlag)
+  : _csPin(csPin),
+    _resetPin(resetPin),
+    _pktFlag(pktFlag)
+{ }
+
+MiLightRadio* LT8900Factory::create(const MiLightRadioConfig& config) {
+  return new LT8900MiLightRadio(_csPin, _resetPin, _pktFlag, config);
+}

+ 49 - 0
lib/MiLight/MiLightRadioFactory.h

@@ -0,0 +1,49 @@
+#include <RF24.h>
+#include <PL1167_nRF24.h>
+#include <MiLightRadioConfig.h>
+#include <MiLightRadio.h>
+#include <NRF24MiLightRadio.h>
+#include <LT8900MiLightRadio.h>
+#include <Settings.h>
+
+#ifndef _MILIGHT_RADIO_FACTORY_H
+#define _MILIGHT_RADIO_FACTORY_H
+
+class MiLightRadioFactory {
+public:
+
+  virtual MiLightRadio* create(const MiLightRadioConfig& config) = 0;
+
+  static MiLightRadioFactory* fromSettings(const Settings& settings);
+  
+};
+
+class NRF24Factory : public MiLightRadioFactory {
+public:
+
+  NRF24Factory(uint8_t cePin, uint8_t csnPin);
+
+  virtual MiLightRadio* create(const MiLightRadioConfig& config);
+
+protected:
+
+  RF24 rf24;
+
+};
+
+class LT8900Factory : public MiLightRadioFactory {
+public:
+
+  LT8900Factory(uint8_t csPin, uint8_t resetPin, uint8_t pktFlag);
+
+  virtual MiLightRadio* create(const MiLightRadioConfig& config);
+
+protected:
+
+  uint8_t _csPin;
+  uint8_t _resetPin;
+  uint8_t _pktFlag;
+
+};
+
+#endif

+ 29 - 31
lib/MiLight/MiLightRadio.cpp

@@ -1,26 +1,22 @@
-/*
- * MiLightRadio.cpp
- *
- *  Created on: 29 May 2015
- *      Author: henryk
- */
+// Adapated from code from henryk
 
 
-#include "MiLightRadio.h"
+#include <PL1167_nRF24.h>
+#include <NRF24MiLightRadio.h>
 
 
 #define PACKET_ID(packet, packet_length) ( (packet[1] << 8) | packet[packet_length - 1] )
 #define PACKET_ID(packet, packet_length) ( (packet[1] << 8) | packet[packet_length - 1] )
 
 
-MiLightRadio::MiLightRadio(AbstractPL1167 &pl1167, const MiLightRadioConfig& config)
-  : _pl1167(pl1167), config(config) {
-  _waiting = false;
-}
+NRF24MiLightRadio::NRF24MiLightRadio(RF24& rf24, const MiLightRadioConfig& config)
+  : _pl1167(PL1167_nRF24(rf24)),
+    _waiting(false),
+    _config(config)
+{ }
 
 
-int MiLightRadio::begin()
-{
+int NRF24MiLightRadio::begin() {
   int retval = _pl1167.open();
   int retval = _pl1167.open();
   if (retval < 0) {
   if (retval < 0) {
     return retval;
     return retval;
   }
   }
-  
+
   retval = configure();
   retval = configure();
   if (retval < 0) {
   if (retval < 0) {
     return retval;
     return retval;
@@ -31,7 +27,7 @@ int MiLightRadio::begin()
   return 0;
   return 0;
 }
 }
 
 
-int MiLightRadio::configure() {
+int NRF24MiLightRadio::configure() {
   int retval = _pl1167.setCRC(true);
   int retval = _pl1167.setCRC(true);
   if (retval < 0) {
   if (retval < 0) {
     return retval;
     return retval;
@@ -47,38 +43,38 @@ int MiLightRadio::configure() {
     return retval;
     return retval;
   }
   }
 
 
-  retval = _pl1167.setSyncword(config.syncword0, config.syncword3);
+  retval = _pl1167.setSyncword(_config.syncword0, _config.syncword3);
   if (retval < 0) {
   if (retval < 0) {
     return retval;
     return retval;
   }
   }
 
 
-  // +1 to be able to buffer the length 
-  retval = _pl1167.setMaxPacketLength(config.getPacketLength() + 1);
+  // +1 to be able to buffer the length
+  retval = _pl1167.setMaxPacketLength(_config.getPacketLength() + 1);
   if (retval < 0) {
   if (retval < 0) {
     return retval;
     return retval;
   }
   }
-  
+
   return 0;
   return 0;
 }
 }
 
 
-bool MiLightRadio::available() {
+bool NRF24MiLightRadio::available() {
   if (_waiting) {
   if (_waiting) {
 #ifdef DEBUG_PRINTF
 #ifdef DEBUG_PRINTF
   printf("_waiting\n");
   printf("_waiting\n");
 #endif
 #endif
     return true;
     return true;
   }
   }
-  
-  if (_pl1167.receive(config.channels[0]) > 0) {
+
+  if (_pl1167.receive(_config.channels[0]) > 0) {
 #ifdef DEBUG_PRINTF
 #ifdef DEBUG_PRINTF
-  printf("MiLightRadio - received packet!\n");
+  printf("NRF24MiLightRadio - received packet!\n");
 #endif
 #endif
     size_t packet_length = sizeof(_packet);
     size_t packet_length = sizeof(_packet);
     if (_pl1167.readFIFO(_packet, packet_length) < 0) {
     if (_pl1167.readFIFO(_packet, packet_length) < 0) {
       return false;
       return false;
     }
     }
 #ifdef DEBUG_PRINTF
 #ifdef DEBUG_PRINTF
-  printf("MiLightRadio - Checking packet length (expecting %d, is %d)\n", _packet[0] + 1U, packet_length);
+  printf("NRF24MiLightRadio - Checking packet length (expecting %d, is %d)\n", _packet[0] + 1U, packet_length);
 #endif
 #endif
     if (packet_length == 0 || packet_length != _packet[0] + 1U) {
     if (packet_length == 0 || packet_length != _packet[0] + 1U) {
       return false;
       return false;
@@ -98,13 +94,13 @@ bool MiLightRadio::available() {
   return _waiting;
   return _waiting;
 }
 }
 
 
-int MiLightRadio::dupesReceived()
+int NRF24MiLightRadio::dupesReceived()
 {
 {
   return _dupes_received;
   return _dupes_received;
 }
 }
 
 
 
 
-int MiLightRadio::read(uint8_t frame[], size_t &frame_length)
+int NRF24MiLightRadio::read(uint8_t frame[], size_t &frame_length)
 {
 {
   if (!_waiting) {
   if (!_waiting) {
     frame_length = 0;
     frame_length = 0;
@@ -125,8 +121,7 @@ int MiLightRadio::read(uint8_t frame[], size_t &frame_length)
   return _packet[0];
   return _packet[0];
 }
 }
 
 
-int MiLightRadio::write(uint8_t frame[], size_t frame_length)
-{
+int NRF24MiLightRadio::write(uint8_t frame[], size_t frame_length) {
   if (frame_length > sizeof(_out_packet) - 1) {
   if (frame_length > sizeof(_out_packet) - 1) {
     return -1;
     return -1;
   }
   }
@@ -141,11 +136,14 @@ int MiLightRadio::write(uint8_t frame[], size_t frame_length)
   return frame_length;
   return frame_length;
 }
 }
 
 
-int MiLightRadio::resend()
-{
+int NRF24MiLightRadio::resend() {
   for (size_t i = 0; i < MiLightRadioConfig::NUM_CHANNELS; i++) {
   for (size_t i = 0; i < MiLightRadioConfig::NUM_CHANNELS; i++) {
     _pl1167.writeFIFO(_out_packet, _out_packet[0] + 1);
     _pl1167.writeFIFO(_out_packet, _out_packet[0] + 1);
-    _pl1167.transmit(config.channels[i]);
+    _pl1167.transmit(_config.channels[i]);
   }
   }
   return 0;
   return 0;
 }
 }
+
+const MiLightRadioConfig& NRF24MiLightRadio::config() {
+  return _config;
+}

+ 43 - 0
lib/MiLight/NRF24MiLightRadio.h

@@ -0,0 +1,43 @@
+#ifdef ARDUINO
+#include "Arduino.h"
+#else
+#include <stdint.h>
+#include <stdlib.h>
+#include <string.h>
+#endif
+
+#include <RF24.h>
+#include <PL1167_nRF24.h>
+#include <MiLightRadioConfig.h>
+#include <MiLightRadio.h>
+
+#ifndef _NRF24_MILIGHT_RADIO_H_
+#define _NRF24_MILIGHT_RADIO_H_
+
+class NRF24MiLightRadio : public MiLightRadio {
+  public:
+    NRF24MiLightRadio(RF24& rf, const MiLightRadioConfig& config);
+
+    int begin();
+    bool available();
+    int read(uint8_t frame[], size_t &frame_length);
+    int dupesReceived();
+    int write(uint8_t frame[], size_t frame_length);
+    int resend();
+    int configure();
+    const MiLightRadioConfig& config();
+
+  private:
+    PL1167_nRF24 _pl1167;
+    const MiLightRadioConfig& _config;
+    uint32_t _prev_packet_id;
+
+    uint8_t _packet[10];
+    uint8_t _out_packet[10];
+    bool _waiting;
+    int _dupes_received;
+};
+
+
+
+#endif

+ 6 - 5
lib/MiLight/PL1167_nRF24.cpp

@@ -12,7 +12,8 @@ static uint8_t reverse_bits(uint8_t data);
 static void demangle_packet(uint8_t *in, uint8_t *out) ;
 static void demangle_packet(uint8_t *in, uint8_t *out) ;
 
 
 PL1167_nRF24::PL1167_nRF24(RF24 &radio)
 PL1167_nRF24::PL1167_nRF24(RF24 &radio)
-  : _radio(radio) { }
+  : _radio(radio)
+{ }
 
 
 static const uint8_t pipe[] = {0xd1, 0x28, 0x5e, 0x55, 0x55};
 static const uint8_t pipe[] = {0xd1, 0x28, 0x5e, 0x55, 0x55};
 
 
@@ -266,7 +267,7 @@ int PL1167_nRF24::transmit(uint8_t channel)
       buffer_fill -= 8;
       buffer_fill -= 8;
     }
     }
   }
   }
-  
+
   yield();
   yield();
 
 
   _radio.write(tmp, outp);
   _radio.write(tmp, outp);
@@ -374,14 +375,14 @@ int PL1167_nRF24::internal_receive()
   }
   }
 
 
   memcpy(_packet, tmp, outp);
   memcpy(_packet, tmp, outp);
-  
+
   _packet_length = outp;
   _packet_length = outp;
   _received = true;
   _received = true;
-  
+
 #ifdef DEBUG_PRINTF
 #ifdef DEBUG_PRINTF
   printf("Successfully parsed packet of length %d\n", _packet_length);
   printf("Successfully parsed packet of length %d\n", _packet_length);
 #endif
 #endif
-  
+
   return outp;
   return outp;
 }
 }
 
 

+ 2 - 3
lib/MiLight/PL1167_nRF24.h

@@ -9,7 +9,6 @@
 #include "Arduino.h"
 #include "Arduino.h"
 #endif
 #endif
 
 
-#include "AbstractPL1167.h"
 #include "RF24.h"
 #include "RF24.h"
 
 
 // #define DEBUG_PRINTF
 // #define DEBUG_PRINTF
@@ -17,9 +16,9 @@
 #ifndef PL1167_NRF24_H_
 #ifndef PL1167_NRF24_H_
 #define PL1167_NRF24_H_
 #define PL1167_NRF24_H_
 
 
-class PL1167_nRF24 : public AbstractPL1167 {
+class PL1167_nRF24 {
   public:
   public:
-    PL1167_nRF24(RF24 &radio);
+    PL1167_nRF24(RF24& radio);
     int open();
     int open();
     int setPreambleLength(uint8_t preambleLength);
     int setPreambleLength(uint8_t preambleLength);
     int setSyncword(uint16_t syncword0, uint16_t syncword3);
     int setSyncword(uint16_t syncword0, uint16_t syncword3);

+ 0 - 34
lib/MiLight/RadioStack.h

@@ -1,34 +0,0 @@
-#include <RF24.h>
-#include <PL1167_nRF24.h>
-#include <MiLightRadioConfig.h>
-#include <MiLightRadio.h>
-
-#ifndef _RADIO_STACK_H
-#define _RADIO_STACK_H 
-
-class RadioStack {
-public:
-  RadioStack(RF24& rf, const MiLightRadioConfig& config) 
-    : config(config)
-  {
-    nrf = new PL1167_nRF24(rf);
-    radio = new MiLightRadio(*nrf, config);
-  }
-  
-  ~RadioStack() {
-    delete radio;
-    delete nrf;
-  }
-  
-  inline MiLightRadio* getRadio() {
-    return this->radio;
-  }
-  
-  const MiLightRadioConfig& config;
-  
-private:
-  PL1167_nRF24 *nrf;
-  MiLightRadio *radio;
-};
-
-#endif

+ 57 - 22
lib/Settings/Settings.cpp

@@ -3,7 +3,7 @@
 #include <FS.h>
 #include <FS.h>
 #include <IntParsing.h>
 #include <IntParsing.h>
 #include <algorithm>
 #include <algorithm>
-  
+
 bool Settings::hasAuthSettings() {
 bool Settings::hasAuthSettings() {
   return adminUsername.length() > 0 && adminPassword.length() > 0;
   return adminUsername.length() > 0 && adminPassword.length() > 0;
 }
 }
@@ -16,7 +16,7 @@ size_t Settings::getAutoRestartPeriod() {
   if (_autoRestartPeriod == 0) {
   if (_autoRestartPeriod == 0) {
     return 0;
     return 0;
   }
   }
-  
+
   return std::max(_autoRestartPeriod, static_cast<size_t>(MINIMUM_RESTART_PERIOD));
   return std::max(_autoRestartPeriod, static_cast<size_t>(MINIMUM_RESTART_PERIOD));
 }
 }
 
 
@@ -31,34 +31,42 @@ void Settings::deserialize(Settings& settings, JsonObject& parsedSettings) {
     if (parsedSettings.containsKey("admin_username")) {
     if (parsedSettings.containsKey("admin_username")) {
       settings.adminUsername = parsedSettings.get<String>("admin_username");
       settings.adminUsername = parsedSettings.get<String>("admin_username");
     }
     }
-    
+
     if (parsedSettings.containsKey("admin_password")) {
     if (parsedSettings.containsKey("admin_password")) {
       settings.adminPassword = parsedSettings.get<String>("admin_password");
       settings.adminPassword = parsedSettings.get<String>("admin_password");
     }
     }
-    
+
     if (parsedSettings.containsKey("ce_pin")) {
     if (parsedSettings.containsKey("ce_pin")) {
       settings.cePin = parsedSettings["ce_pin"];
       settings.cePin = parsedSettings["ce_pin"];
     }
     }
-    
+
     if (parsedSettings.containsKey("csn_pin")) {
     if (parsedSettings.containsKey("csn_pin")) {
       settings.csnPin = parsedSettings["csn_pin"];
       settings.csnPin = parsedSettings["csn_pin"];
     }
     }
-    
+
+    if (parsedSettings.containsKey("reset_pin")) {
+      settings.resetPin = parsedSettings["reset_pin"];
+    }
+
+    if (parsedSettings.containsKey("radio_interface_type")) {
+      settings.radioInterfaceType = typeFromString(parsedSettings["radio_interface_type"]);
+    }
+
     if (parsedSettings.containsKey("packet_repeats")) {
     if (parsedSettings.containsKey("packet_repeats")) {
       settings.packetRepeats = parsedSettings["packet_repeats"];
       settings.packetRepeats = parsedSettings["packet_repeats"];
     }
     }
-    
+
     if (parsedSettings.containsKey("http_repeat_factor")) {
     if (parsedSettings.containsKey("http_repeat_factor")) {
       settings.httpRepeatFactor = parsedSettings["http_repeat_factor"];
       settings.httpRepeatFactor = parsedSettings["http_repeat_factor"];
     }
     }
-    
+
     if (parsedSettings.containsKey("auto_restart_period")) {
     if (parsedSettings.containsKey("auto_restart_period")) {
       settings._autoRestartPeriod = parsedSettings["auto_restart_period"];
       settings._autoRestartPeriod = parsedSettings["auto_restart_period"];
     }
     }
-    
+
     JsonArray& arr = parsedSettings["device_ids"];
     JsonArray& arr = parsedSettings["device_ids"];
     settings.updateDeviceIds(arr);
     settings.updateDeviceIds(arr);
-    
+
     JsonArray& gatewayArr = parsedSettings["gateway_configs"];
     JsonArray& gatewayArr = parsedSettings["gateway_configs"];
     settings.updateGatewayConfigs(gatewayArr);
     settings.updateGatewayConfigs(gatewayArr);
   }
   }
@@ -69,7 +77,7 @@ void Settings::updateDeviceIds(JsonArray& arr) {
     if (this->deviceIds) {
     if (this->deviceIds) {
       delete this->deviceIds;
       delete this->deviceIds;
     }
     }
-    
+
     this->deviceIds = new uint16_t[arr.size()];
     this->deviceIds = new uint16_t[arr.size()];
     this->numDeviceIds = arr.size();
     this->numDeviceIds = arr.size();
     arr.copyTo(this->deviceIds, arr.size());
     arr.copyTo(this->deviceIds, arr.size());
@@ -81,13 +89,13 @@ void Settings::updateGatewayConfigs(JsonArray& arr) {
     if (this->gatewayConfigs) {
     if (this->gatewayConfigs) {
       delete[] this->gatewayConfigs;
       delete[] this->gatewayConfigs;
     }
     }
-    
+
     this->gatewayConfigs = new GatewayConfig*[arr.size()];
     this->gatewayConfigs = new GatewayConfig*[arr.size()];
     this->numGatewayConfigs = arr.size();
     this->numGatewayConfigs = arr.size();
-    
+
     for (size_t i = 0; i < arr.size(); i++) {
     for (size_t i = 0; i < arr.size(); i++) {
       JsonArray& params = arr[i];
       JsonArray& params = arr[i];
-      
+
       if (params.success() && params.size() == 3) {
       if (params.success() && params.size() == 3) {
         this->gatewayConfigs[i] = new GatewayConfig(parseInt<uint16_t>(params[0]), params[1], params[2]);
         this->gatewayConfigs[i] = new GatewayConfig(parseInt<uint16_t>(params[0]), params[1], params[2]);
       } else {
       } else {
@@ -112,6 +120,12 @@ void Settings::patch(JsonObject& parsedSettings) {
     if (parsedSettings.containsKey("csn_pin")) {
     if (parsedSettings.containsKey("csn_pin")) {
       this->csnPin = parsedSettings["csn_pin"];
       this->csnPin = parsedSettings["csn_pin"];
     }
     }
+    if (parsedSettings.containsKey("reset_pin")) {
+      this->resetPin = parsedSettings["reset_pin"];
+    }
+    if (parsedSettings.containsKey("radio_interface_type")) {
+      this->radioInterfaceType = typeFromString(parsedSettings["radio_interface_type"]);
+    }
     if (parsedSettings.containsKey("packet_repeats")) {
     if (parsedSettings.containsKey("packet_repeats")) {
       this->packetRepeats = parsedSettings["packet_repeats"];
       this->packetRepeats = parsedSettings["packet_repeats"];
     }
     }
@@ -137,7 +151,7 @@ void Settings::load(Settings& settings) {
     File f = SPIFFS.open(SETTINGS_FILE, "r");
     File f = SPIFFS.open(SETTINGS_FILE, "r");
     String settingsContents = f.readStringUntil(SETTINGS_TERMINATOR);
     String settingsContents = f.readStringUntil(SETTINGS_TERMINATOR);
     f.close();
     f.close();
-    
+
     deserialize(settings, settingsContents);
     deserialize(settings, settingsContents);
   } else {
   } else {
     settings.save();
     settings.save();
@@ -153,7 +167,7 @@ String Settings::toJson(const bool prettyPrint) {
 
 
 void Settings::save() {
 void Settings::save() {
   File f = SPIFFS.open(SETTINGS_FILE, "w");
   File f = SPIFFS.open(SETTINGS_FILE, "w");
-  
+
   if (!f) {
   if (!f) {
     Serial.println(F("Opening settings file failed"));
     Serial.println(F("Opening settings file failed"));
   } else {
   } else {
@@ -165,21 +179,23 @@ void Settings::save() {
 void Settings::serialize(Stream& stream, const bool prettyPrint) {
 void Settings::serialize(Stream& stream, const bool prettyPrint) {
   DynamicJsonBuffer jsonBuffer;
   DynamicJsonBuffer jsonBuffer;
   JsonObject& root = jsonBuffer.createObject();
   JsonObject& root = jsonBuffer.createObject();
-  
+
   root["admin_username"] = this->adminUsername;
   root["admin_username"] = this->adminUsername;
   root["admin_password"] = this->adminPassword;
   root["admin_password"] = this->adminPassword;
   root["ce_pin"] = this->cePin;
   root["ce_pin"] = this->cePin;
   root["csn_pin"] = this->csnPin;
   root["csn_pin"] = this->csnPin;
+  root["reset_pin"] = this->resetPin;
+  root["radio_interface_type"] = typeToString(this->radioInterfaceType);
   root["packet_repeats"] = this->packetRepeats;
   root["packet_repeats"] = this->packetRepeats;
   root["http_repeat_factor"] = this->httpRepeatFactor;
   root["http_repeat_factor"] = this->httpRepeatFactor;
   root["auto_restart_period"] = this->_autoRestartPeriod;
   root["auto_restart_period"] = this->_autoRestartPeriod;
-  
+
   if (this->deviceIds) {
   if (this->deviceIds) {
     JsonArray& arr = jsonBuffer.createArray();
     JsonArray& arr = jsonBuffer.createArray();
     arr.copyFrom(this->deviceIds, this->numDeviceIds);
     arr.copyFrom(this->deviceIds, this->numDeviceIds);
     root["device_ids"] = arr;
     root["device_ids"] = arr;
   }
   }
-  
+
   if (this->gatewayConfigs) {
   if (this->gatewayConfigs) {
     JsonArray& arr = jsonBuffer.createArray();
     JsonArray& arr = jsonBuffer.createArray();
     for (size_t i = 0; i < this->numGatewayConfigs; i++) {
     for (size_t i = 0; i < this->numGatewayConfigs; i++) {
@@ -189,13 +205,32 @@ void Settings::serialize(Stream& stream, const bool prettyPrint) {
       elmt.add(this->gatewayConfigs[i]->protocolVersion);
       elmt.add(this->gatewayConfigs[i]->protocolVersion);
       arr.add(elmt);
       arr.add(elmt);
     }
     }
-    
+
     root["gateway_configs"] = arr;
     root["gateway_configs"] = arr;
   }
   }
-  
+
   if (prettyPrint) {
   if (prettyPrint) {
     root.prettyPrintTo(stream);
     root.prettyPrintTo(stream);
   } else {
   } else {
     root.printTo(stream);
     root.printTo(stream);
   }
   }
-}
+}
+
+RadioInterfaceType Settings::typeFromString(const String& s) {
+  if (s.equalsIgnoreCase("lt8900")) {
+    return LT8900;
+  } else {
+    return nRF24;
+  }
+}
+
+String Settings::typeToString(RadioInterfaceType type) {
+  switch (type) {
+    case LT8900:
+      return "LT8900";
+
+    case nRF24:
+    default:
+      return "nRF24";
+  }
+}

+ 12 - 0
lib/Settings/Settings.h

@@ -27,6 +27,11 @@
 
 
 #define MINIMUM_RESTART_PERIOD 1
 #define MINIMUM_RESTART_PERIOD 1
 
 
+enum RadioInterfaceType {
+  nRF24 = 0,
+  LT8900 = 1,
+};
+
 class GatewayConfig {
 class GatewayConfig {
 public:
 public:
   GatewayConfig(uint16_t deviceId, uint16_t port, uint8_t protocolVersion)
   GatewayConfig(uint16_t deviceId, uint16_t port, uint8_t protocolVersion)
@@ -48,6 +53,8 @@ public:
     // CE and CSN pins from nrf24l01
     // CE and CSN pins from nrf24l01
     cePin(D0),
     cePin(D0),
     csnPin(D8),
     csnPin(D8),
+    resetPin(0),
+    radioInterfaceType(nRF24),
     deviceIds(NULL),
     deviceIds(NULL),
     gatewayConfigs(NULL),
     gatewayConfigs(NULL),
     numDeviceIds(0),
     numDeviceIds(0),
@@ -71,6 +78,9 @@ public:
   static void deserialize(Settings& settings, JsonObject& json);
   static void deserialize(Settings& settings, JsonObject& json);
   static void load(Settings& settings);
   static void load(Settings& settings);
 
 
+  static RadioInterfaceType typeFromString(const String& s);
+  static String typeToString(RadioInterfaceType type);
+
   void save();
   void save();
   String toJson(const bool prettyPrint = true);
   String toJson(const bool prettyPrint = true);
   void serialize(Stream& stream, const bool prettyPrint = false);
   void serialize(Stream& stream, const bool prettyPrint = false);
@@ -82,6 +92,8 @@ public:
   String adminPassword;
   String adminPassword;
   uint8_t cePin;
   uint8_t cePin;
   uint8_t csnPin;
   uint8_t csnPin;
+  uint8_t resetPin;
+  RadioInterfaceType radioInterfaceType;
   uint16_t *deviceIds;
   uint16_t *deviceIds;
   GatewayConfig **gatewayConfigs;
   GatewayConfig **gatewayConfigs;
   size_t numGatewayConfigs;
   size_t numGatewayConfigs;

+ 3 - 2
platformio.ini

@@ -18,24 +18,25 @@ lib_deps_external =
   ArduinoJson
   ArduinoJson
 build_flags = !python .get_version.py
 build_flags = !python .get_version.py
 # -D MILIGHT_UDP_DEBUG
 # -D MILIGHT_UDP_DEBUG
+# -D DEBUG_PRINTF
 
 
 [env:nodemcuv2]
 [env:nodemcuv2]
 platform = espressif8266
 platform = espressif8266
 framework = arduino
 framework = arduino
 board = nodemcuv2
 board = nodemcuv2
+build_flags = ${common.build_flags} -Wl,-Tesp8266.flash.4m1m.ld -D FIRMWARE_VARIANT=nodemcuv2
 lib_deps =
 lib_deps =
   ${common.lib_deps_builtin}
   ${common.lib_deps_builtin}
   ${common.lib_deps_external}
   ${common.lib_deps_external}
-build_flags = ${common.build_flags} -D FIRMWARE_VARIANT=nodemcuv2
 
 
 [env:d1_mini]
 [env:d1_mini]
 platform = espressif8266
 platform = espressif8266
 framework = arduino
 framework = arduino
 board = d1_mini
 board = d1_mini
+build_flags = ${common.build_flags} -Wl,-Tesp8266.flash.4m1m.ld -D FIRMWARE_VARIANT=d1_mini
 lib_deps =
 lib_deps =
   ${common.lib_deps_builtin}
   ${common.lib_deps_builtin}
   ${common.lib_deps_external}
   ${common.lib_deps_external}
-build_flags = ${common.build_flags} -D FIRMWARE_VARIANT=d1_mini
 
 
 [env:esp12]
 [env:esp12]
 platform = espressif8266
 platform = espressif8266

+ 22 - 14
src/main.cpp

@@ -17,6 +17,7 @@ WiFiManager wifiManager;
 Settings settings;
 Settings settings;
 
 
 MiLightClient* milightClient;
 MiLightClient* milightClient;
+MiLightRadioFactory* radioFactory;
 MiLightHttpServer *httpServer;
 MiLightHttpServer *httpServer;
 
 
 int numUdpServers = 0;
 int numUdpServers = 0;
@@ -29,13 +30,13 @@ void initMilightUdpServers() {
         delete udpServers[i];
         delete udpServers[i];
       }
       }
     }
     }
-    
+
     delete udpServers;
     delete udpServers;
   }
   }
-  
+
   udpServers = new MiLightUdpServer*[settings.numGatewayConfigs];
   udpServers = new MiLightUdpServer*[settings.numGatewayConfigs];
   numUdpServers = settings.numGatewayConfigs;
   numUdpServers = settings.numGatewayConfigs;
-  
+
   for (size_t i = 0; i < settings.numGatewayConfigs; i++) {
   for (size_t i = 0; i < settings.numGatewayConfigs; i++) {
     GatewayConfig* config = settings.gatewayConfigs[i];
     GatewayConfig* config = settings.gatewayConfigs[i];
     MiLightUdpServer* server = MiLightUdpServer::fromVersion(
     MiLightUdpServer* server = MiLightUdpServer::fromVersion(
@@ -44,7 +45,7 @@ void initMilightUdpServers() {
       config->port,
       config->port,
       config->deviceId
       config->deviceId
     );
     );
-    
+
     if (server == NULL) {
     if (server == NULL) {
       Serial.print(F("Error creating UDP server with protocol version: "));
       Serial.print(F("Error creating UDP server with protocol version: "));
       Serial.println(config->protocolVersion);
       Serial.println(config->protocolVersion);
@@ -55,17 +56,24 @@ void initMilightUdpServers() {
   }
   }
 }
 }
 
 
-void initMilightClient() {
+
+void applySettings() {
   if (milightClient) {
   if (milightClient) {
     delete milightClient;
     delete milightClient;
   }
   }
-  
-  milightClient = new MiLightClient(settings.cePin, settings.csnPin);
+  if (radioFactory) {
+    delete radioFactory;
+  }
+
+  radioFactory = MiLightRadioFactory::fromSettings(settings);
+
+  if (radioFactory == NULL) {
+    Serial.println(F("ERROR: unable to construct radio factory"));
+  }
+
+  milightClient = new MiLightClient(radioFactory);
   milightClient->begin();
   milightClient->begin();
-}
 
 
-void applySettings() {
-  initMilightClient();
   initMilightUdpServers();
   initMilightUdpServers();
 }
 }
 
 
@@ -73,7 +81,7 @@ bool shouldRestart() {
   if (! settings.isAutoRestartEnabled()) {
   if (! settings.isAutoRestartEnabled()) {
     return false;
     return false;
   }
   }
-  
+
   return settings.getAutoRestartPeriod()*60*1000 < millis();
   return settings.getAutoRestartPeriod()*60*1000 < millis();
 }
 }
 
 
@@ -83,7 +91,7 @@ void setup() {
   SPIFFS.begin();
   SPIFFS.begin();
   Settings::load(settings);
   Settings::load(settings);
   applySettings();
   applySettings();
-  
+
   httpServer = new MiLightHttpServer(settings, milightClient);
   httpServer = new MiLightHttpServer(settings, milightClient);
   httpServer->onSettingsSaved(applySettings);
   httpServer->onSettingsSaved(applySettings);
   httpServer->begin();
   httpServer->begin();
@@ -91,13 +99,13 @@ void setup() {
 
 
 void loop() {
 void loop() {
   httpServer->handleClient();
   httpServer->handleClient();
-  
+
   if (udpServers) {
   if (udpServers) {
     for (size_t i = 0; i < settings.numGatewayConfigs; i++) {
     for (size_t i = 0; i < settings.numGatewayConfigs; i++) {
       udpServers[i]->handleClient();
       udpServers[i]->handleClient();
     }
     }
   }
   }
-  
+
   if (shouldRestart()) {
   if (shouldRestart()) {
     Serial.println(F("Auto-restart triggered. Restarting..."));
     Serial.println(F("Auto-restart triggered. Restarting..."));
     ESP.restart();
     ESP.restart();