瀏覽代碼

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
 
 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).
 
 ## 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
 
@@ -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. `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. `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:
     ```
     {"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.
    * `next_mode`. Cycles to the next "disco mode".
    * `previous_mode`. Cycles to the previous disco mode.
-   * `mode_speed_up`. 
+   * `mode_speed_up`.
    * `mode_speed_down`.
    * `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.
    * `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.
-   
+
 If you'd like to control bulbs in all groups paired with a particular device ID, set `:group_id` to 0.
 
 #### 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 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]>
     <script src="https://cdnjs.cloudflare.com/ajax/libs/html5shiv/3.7.3/html5shiv.js"></script>
   <![endif]-->
-  
+
   <style>
     .header-row { border-bottom: 1px solid #ccc; }
     label { display: block; }
@@ -31,8 +31,8 @@
     .error-info:before { content: '('; }
     .error-info:after { content: ')'; }
     .header-btn { margin: 20px; }
-    .btn-secondary { 
-      background-color: #fff; 
+    .btn-secondary {
+      background-color: #fff;
       border: 1px solid #ccc;
     }
     .inline { display: inline-block; }
@@ -46,41 +46,41 @@
       width: calc(100% - 3em);
       display: inline-block;
       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%
       );
     }
-    .hue-value-display { 
+    .hue-value-display {
       border: 1px solid #000;
       margin-left: 0.5em;
       width: 2em;
       height: 2em;
       display: inline-block;
     }
-    .plus-minus-group { 
+    .plus-minus-group {
       overflow: auto;
       width: 100%;
       clear: both;
       display: block;
     }
-    .plus-minus-group button:first-of-type { 
+    .plus-minus-group button:first-of-type {
       border-bottom-right-radius: 0;
       border-top-right-radius: 0;
       float: left;
       display: block;
     }
-    .plus-minus-group button:last-of-type { 
+    .plus-minus-group button:last-of-type {
       border-bottom-left-radius: 0;
       border-top-left-radius: 0;
       display: block;
     }
-    .plus-minus-group .title { 
+    .plus-minus-group .title {
       border-width: 1px 0;
       border-color: #ccc;
       border-style: solid;
@@ -100,12 +100,12 @@
       animation: spin 1s infinite linear;
       -webkit-animation: spin2 1s infinite linear;
     }
-    
+
     @keyframes spin {
       from { transform: scale(1) rotate(0deg); }
       to { transform: scale(1) rotate(360deg); }
     }
-    
+
     @-webkit-keyframes spin2 {
       from { -webkit-transform: rotate(0deg); }
       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/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 lang="text/javascript">
     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 = {
+      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",
       http_repeat_factor : "Multiplicative factor on packet_repeats for " +
         "requests initiated by the HTTP API. UDP API typically receives " +
         "duplicate packets, so more repeats should be used for HTTP.",
       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 DEFAULT_UDP_PROTOCL_VERSION = 5;
-    
+
     var selectize;
-    
+
     var toHex = function(v) {
       return "0x" + (v).toString(16).toUpperCase();
     }
-  
+
     var activeUrl = function() {
       var deviceId = $('#deviceId option:selected').val()
         , groupId = $('#groupId .active input').data('value')
         , mode = getCurrentMode();
-        
+
       if (deviceId == "") {
         alert("Please enter a device ID.");
         throw "Must enter device ID";
       }
-      
+
       if (! $('#group-option').data('for').split(',').includes(mode)) {
         groupId = 0;
       }
-        
+
       return "/gateways/" + deviceId + "/" + mode + "/" + groupId;
     }
-    
+
     var getCurrentMode = function() {
       return $('input[name="mode"]:checked').data('value');
     };
-    
+
     var updateGroup = _.throttle(
       function(params) {
         $.ajax(
@@ -179,7 +182,7 @@
       },
       1000
     );
-    
+
     var sendCommand = _.throttle(
       function(params) {
         $.ajax(
@@ -193,18 +196,18 @@
       },
       1000
     )
-    
+
     var sniffRequest;
     var sniffing = false;
     var getTraffic = function() {
       var sniffType = $('#sniff-type input:checked').data('value');
-      
+
       sniffRequest = $.get('/gateway_traffic/' + sniffType, function(data) {
         $('#sniffed-traffic').html(data + $('#sniffed-traffic').html());
         getTraffic();
       });
     };
-    
+
     var gatewayServerRow = function(deviceId, port, version) {
       var elmt = '<tr>';
       elmt += '<td>';
@@ -215,17 +218,17 @@
       elmt += '</td>';
       elmt += '<td>';
       elmt += '<div class="btn-group" data-toggle="buttons">';
-      
+
       for (var i = 0; i < UDP_PROTOCOL_VERSIONS.length; i++) {
         var val = UDP_PROTOCOL_VERSIONS[i]
           , selected = (version == val || (val == DEFAULT_UDP_PROTOCL_VERSION && !UDP_PROTOCOL_VERSIONS.includes(version)));
-        
+
         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;
         elmt += '</label>';
       }
-      
+
       elmt += '</div></td>';
       elmt += '<td>';
       elmt += '<button class="btn btn-danger remove-gateway-server">';
@@ -235,17 +238,21 @@
       elmt += '</tr>';
       return elmt;
     }
-    
+
     var loadSettings = function() {
       $.getJSON('/settings', function(val) {
         Object.keys(val).forEach(function(k) {
           var field = $('#settings input[name="' + k + '"]');
-          
+
           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) {
           selectize.clearOptions();
           val.device_ids.forEach(function(v) {
@@ -253,7 +260,7 @@
           });
           selectize.refreshOptions();
         }
-        
+
         var gatewayForm = $('#gateway-server-configs').html('');
         if (val.gateway_configs) {
           val.gateway_configs.forEach(function(v) {
@@ -262,16 +269,16 @@
         }
       });
     };
-    
+
     var saveGatewayConfigs = function() {
       var form = $('#gateway-server-form')
         , errors = false;
-        
+
       $('input', form).removeClass('error');
-      
+
       var deviceIds = $('input[name="deviceIds[]"]', form).map(function(i, v) {
         var val = $(v).val();
-        
+
         if (isNaN(val)) {
           errors = true;
           $(v).addClass('error');
@@ -280,10 +287,10 @@
           return val;
         }
       });
-      
+
       var ports = $('input[name="ports[]"]', form).map(function(i, v) {
         var val = $(v).val();
-        
+
         if (isNaN(val)) {
           errors = true;
           $(v).addClass('error');
@@ -292,11 +299,11 @@
           return val;
         }
       });
-      
+
       var versions = $('.active input[name="versions[]"]', form).map(function(i, v) {
         return $(v).data('value');
       });
-        
+
       if (!errors) {
         var data = [];
         for (var i = 0; i < deviceIds.length; i++) {
@@ -312,7 +319,7 @@
         )
       }
     };
-    
+
     var deviceIdError = function(v) {
       if (!v) {
         $('#device-id-label').removeClass('error');
@@ -321,10 +328,10 @@
         $('#device-id-label .error-info').html(v);
       }
     };
-    
+
     var updateModeOptions = function() {
       var currentMode = getCurrentMode();
-      
+
       $('.mode-option').map(function() {
         if ($(this).data('for').split(',').includes(currentMode)) {
           $(this).show();
@@ -333,10 +340,10 @@
         }
       });
     };
-    
+
     var parseVersion = function(v) {
       var matches = v.match(/(\d+)\.(\d+)\.(\d+)(-(.*))?/);
-      
+
       return {
         major: matches[1],
         minor: matches[2],
@@ -345,28 +352,28 @@
         parts: [matches[1], matches[2], matches[3], matches[5]]
       };
     };
-    
+
     var isNewerVersion = function(a, b) {
       var va = parseVersion(a)
         , vb = parseVersion(b);
-        
+
       return va.parts > vb.parts;
     };
-    
+
     var handleCheckForUpdates = function() {
       var currentVersion = null
         , latestRelease = null;
-        
+
       var handleReceiveData = function() {
         if (currentVersion != null) {
           $('#current-version').html(currentVersion.version + " (" + currentVersion.variant + ")");
         }
-        
+
         if (latestRelease != null) {
           $('#latest-version .info-key').each(function() {
             var value = latestRelease[$(this).data('key')];
             var prop = $(this).data('prop');
-            
+
             if (prop) {
               $(this).prop(prop, value);
             } else {
@@ -374,11 +381,11 @@
             }
           });
         }
-        
+
         if (currentVersion != null && latestRelease != null) {
           $('#latest-version .status').html('');
           $('#latest-version-info').show();
-          
+
           var summary;
           if (isNewerVersion(latestRelease.tag_name, currentVersion.version)) {
             summary = "New version available!";
@@ -386,30 +393,30 @@
             summary = "You're on the most recent version.";
           }
           $('#version-summary').html(summary);
-          
+
           var releaseAsset = latestRelease.assets.filter(function(x) {
             return x.name.indexOf(currentVersion.variant) != -1;
           });
-          
+
           if (releaseAsset.length > 0) {
             console.log(releaseAsset[0].url);
             $('#firmware-link').prop('href', releaseAsset[0].browser_download_url);
           }
         }
-        
+
         console.log(latestRelease);
       }
-      
+
       var handleError = function(e, d) {
         console.log(e);
         console.log(d);
       }
-      
+
       $('#current-version,#latest-version .status').html('<i class="spinning glyphicon glyphicon-refresh"></i>');
       $('#version-summary').html('');
       $('#latest-version-info').hide();
       $('#updates-modal').modal();
-      
+
       $.ajax(
         '/about',
         {
@@ -420,7 +427,7 @@
           failure: handleError
         }
       );
-      
+
       $.ajax(
         '/latest_release',
         {
@@ -432,26 +439,26 @@
         }
       );
     };
-    
+
     $(function() {
       $('.radio-option').click(function() {
         $(this).prev().prop('checked', true);
       });
-      
+
       var hueDragging = false;
       var colorUpdated = function(e) {
         var x = e.pageX - $(this).offset().left
           , pct = x/(1.0*$(this).width())
           , hue = Math.round(360*pct)
           ;
-          
+
         $('.hue-value-display').css({
           backgroundColor: "hsl(" + hue + ",100%,50%)"
         });
-        
+
         updateGroup({hue: hue});
       };
-      
+
       $('.hue-picker-inner')
         .mousedown(function(e) {
           hueDragging = true;
@@ -468,26 +475,26 @@
             colorUpdated.call(this, e);
           }
         });
-        
+
       $('.slider').slider();
-      
+
       $('.raw-update').change(function() {
         var data = {}
           , val = $(this).attr('type') == 'checkbox' ? $(this).is(':checked') : $(this).val()
           ;
-          
+
         data[$(this).attr('name')] = val;
         updateGroup(data);
       });
-      
+
       $('.command-btn').click(function() {
         updateGroup({command: $(this).data('command')});
       });
-      
+
       $('.system-btn').click(function() {
         sendCommand({command: $(this).data('command')});
       });
-      
+
       $('#sniff').click(function() {
         if (sniffing) {
           sniffRequest.abort();
@@ -499,17 +506,17 @@
           $(this).html('Stop Sniffing');
         }
       });
-      
+
       $('#add-server-btn').click(function() {
         $('#gateway-server-configs').append(gatewayServerRow('', ''));
       });
-      
+
       $('#mode').change(updateModeOptions);
-      
+
       $('body').on('click', '.remove-gateway-server', function() {
         $(this).closest('tr').remove();
       });
-      
+
       selectize = $('#deviceId').selectize({
         create: true,
         sortField: 'text',
@@ -521,53 +528,73 @@
             deviceIdError("Must be an integer between 0x0000 and 0xFFFF");
             return false;
           }
-          
+
           var value = parseInt(v);
-          
+
           if (! (0 <= v && v <= 0xFFFF)) {
             deviceIdError("Must be an integer between 0x0000 and 0xFFFF");
             return false;
-          } 
-          
+          }
+
           deviceIdError(false);
-          
+
           return true;
         }
       });
       selectize = selectize[0].selectize;
-      
+
       var settings = "";
-      
+
       FORM_SETTINGS.forEach(function(k) {
         var elmt = '<div class="form-entry">';
+        elmt += '<div>';
         elmt += '<label for="' + k + '">' + k + '</label>';
-        
+
         if (FORM_SETTINGS_HELP[k]) {
           elmt += '<div class="field-help" data-help-text="' + FORM_SETTINGS_HELP[k] + '"></div>';
         }
-        
-        elmt += '<input type="text" class="form-control" name="' + k + '"/>';
+
         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').prepend(settings);
       $('#settings').submit(function(e) {
         var obj = {};
-        
+
         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.
         obj.device_ids = _.map(
           $('.selectize-control .option'),
-          function(x) { 
+          function(x) {
             return $(x).data('value')
           }
         );
-        
+
         $.ajax(
           "/settings",
           {
@@ -576,17 +603,17 @@
             data: JSON.stringify(obj)
           }
         );
-        
+
         e.preventDefault();
         return false;
       });
-      
+
       $('#gateway-server-form').submit(function(e) {
         saveGatewayConfigs();
         e.preventDefault();
         return false;
       });
-      
+
       $('.field-help').each(function() {
         var elmt = $('<i></i>')
           .addClass('glyphicon glyphicon-question-sign')
@@ -597,14 +624,14 @@
           });
         $(this).append(elmt);
       });
-      
+
       $('#updates-btn').click(handleCheckForUpdates);
-      
+
       loadSettings();
       updateModeOptions();
     });
   </script>
-  
+
   <div id="update-firmware-modal" class="modal fade" role="dialog">
     <div class="modal-dialog">
       <!-- Modal content-->
@@ -620,10 +647,10 @@
             <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.
             </p>
-            
+
             <p>
               <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.
             </p>
           </div>
@@ -637,10 +664,10 @@
           <button type="button" class="btn btn-default" data-dismiss="modal">Close</button>
         </div>
       </div>
-      
+
     </div>
   </div>
-  
+
   <div id="updates-modal" class="modal fade" role="dialog">
     <div class="modal-dialog">
       <!-- Modal content-->
@@ -651,29 +678,29 @@
         </div>
         <div class="modal-body">
           <div id="version-summary"></div>
-          
+
           <hr />
-          
+
           <h4>Current Version</h4>
           <div id="current-version"></div>
-          
+
           <div id="latest-version">
             <h4>Latest Version</h4>
             <div class="status"></div>
             <div id="latest-version-info">
               <label>Version</label>
               <div class="info-key" data-key="tag_name"></div>
-              
+
               <label>Release Date</label>
               <div class="info-key" data-key="published_at"></div>
-              
+
               <label>Release Notes</label>
               <pre class="info-key" data-key="body"></pre>
-              
+
               <div>
                 <a class="info-key" data-prop="href" data-key="html_url">View on GitHub</a>
               </div>
-              
+
               <div>
                 <a class="info-key" id="firmware-link">Download Firmware</a>
               </div>
@@ -685,10 +712,10 @@
           <button type="button" class="btn btn-default" data-dismiss="modal">Close</button>
         </div>
       </div>
-      
+
     </div>
   </div>
-  
+
   <div class="container">
     <div class="row header-row">
       <div class="col-sm-12">
@@ -697,9 +724,9 @@
         </h1>
       </div>
     </div>
-    
+
     <div>&nbsp;</div>
-    
+
     <div class="row">
       <div class="col-sm-4">
         <label for="deviceId" id="device-id-label">
@@ -709,11 +736,11 @@
         <select id="deviceId" placeholder="Enter hub ID">
 				</select>
       </div>
-      
+
       <div class="col-sm-3">
         <div class="mode-option" id="group-option" data-for="cct,rgbw,rgb_cct">
           <label for="groupId">Group</label>
-        
+
           <div class="btn-group" id="groupId" data-toggle="buttons">
             <label class="btn btn-secondary active">
               <input type="radio" name="options" autocomplete="off" data-value="1" checked> 1
@@ -733,10 +760,10 @@
           </div>
         </div>
       </div>
-      
+
       <div class="col-sm-4">
         <label for="groupId">Mode</label>
-        
+
         <div class="btn-group" id="mode" data-toggle="buttons">
           <label class="btn btn-secondary active">
             <input type="radio" name="mode" autocomplete="off" data-value="rgbw" checked> RGBW
@@ -753,7 +780,7 @@
         </div>
       </div>
     </div>
-    
+
     <div class="row"><div class="col-sm-12">
     <div class="mode-option" data-for="rgbw,rgb_cct,rgb">
       <div class="row">
@@ -761,7 +788,7 @@
           <h5>Hue</h5>
         </div>
       </div>
-      
+
       <div class="row">
         <div class="col-sm-6">
           <span class="hue-picker">
@@ -772,7 +799,7 @@
       </div>
     </div>
     </div></div>
-    
+
     <div class="mode-option" data-for="rgb_cct">
       <div class="row">
         <div class="col-sm-12">
@@ -789,7 +816,7 @@
         </div>
       </div>
     </div>
-          
+
     <div class="mode-option" data-for="cct,rgb_cct">
       <div class="row">
         <div class="col-sm-12">
@@ -806,13 +833,13 @@
         </div>
       </div>
     </div>
-    
+
     <div class="row">
       <div class="col-sm-12">
         <h5>Brightness</h5>
       </div>
     </div>
-      
+
     <div class="row">
       <div class="col-sm-12">
         <input class="slider raw-update" name="level"
@@ -822,13 +849,13 @@
         />
       </div>
     </div>
-    
+
     <div class="row">
       <div class="col-sm-12">
         <h5>Commands</h5>
       </div>
     </div>
-    
+
     <div class="row">
       <div class="col-sm-12">
         <ul class="command-buttons">
@@ -886,12 +913,12 @@
         </ul>
       </div>
     </div>
-    
+
     <div class="row header-row">
       <div class="col col-sm-10">
         <h1>Gateway Servers</h1>
       </div>
-      
+
       <div class="col col-sm-2">
         <button class="btn btn-success header-btn" id="add-server-btn">
           <i class="glyphicon glyphicon-plus"></i>
@@ -899,7 +926,7 @@
         </button>
       </div>
     </div>
-    
+
     <div class="row">
       <div class="col col-sm-12">
         <form id="gateway-server-form">
@@ -918,17 +945,17 @@
         </form>
       </div>
     </div>
-    
+
     <div>&nbsp;</div>
-    
+
     <div class="row header-row">
       <div class="col-sm-12">
         <h1>Settings</h1>
       </div>
     </div>
-    
+
     <div>&nbsp;</div>
-    
+
     <div class="row">
       <div class="col-sm-12">
         <form action="#" id="settings">
@@ -936,19 +963,19 @@
         </form>
       </div>
     </div>
-    
+
     <div class="row header-row">
       <div class="col-sm-12">
         <h1>Sniff Traffic</h1>
       </div>
     </div>
-    
+
     <div>&nbsp;</div>
-    
+
     <div class="row">
       <div class="col-sm-12">
         <button type="button" id="sniff" class="btn btn-primary">Start Sniffing</button>
-        
+
         <div class="btn-group" id="sniff-type" data-toggle="buttons">
           <label class="btn btn-secondary active">
             <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
           </label>
         </div>
-        
+
         <div> &nbsp; </div>
-        
+
         <pre id="sniffed-traffic"></pre>
       </div>
     </div>
-    
+
     <div class="row header-row">
       <div class="col-sm-12">
         <h1>Admin</h1>
       </div>
     </div>
-    
+
     <div>&nbsp;</div>
-    
+
     <div class="row">
       <div class="col-sm-12">
         <button type="button" class="btn btn-danger system-btn" data-command="restart">
           Restart
         </button>
-        
+
         <button type="button" id="updates-btn" class="btn btn-primary">
           Check for Updates
         </button>
-        
+
         <button type="button" class="btn btn-primary" data-toggle="modal" data-target="#update-firmware-modal">
           Update Firmware
         </button>
@@ -995,4 +1022,4 @@
     </div>
   </div>
 </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 <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) {
-  RadioStack* stack = NULL;
-  
+  MiLightRadio* radio = NULL;
+
   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;
     }
   }
-  
-  if (stack != NULL) {
-    MiLightRadio *radio = stack->getRadio();
-    
-    if (currentRadio->config.type != stack->config.type) {
+
+  if (radio != NULL) {
+    if (currentRadio != radio) {
       radio->configure();
     }
-    
-    currentRadio = stack;
-    formatter = stack->config.packetFormatter;
+
+    this->currentRadio = radio;
+    this->formatter = radio->config().packetFormatter;
+
     return radio;
   } else {
     Serial.print(F("MiLightClient - tried to get radio for unknown type: "));
     Serial.println(type);
   }
-  
+
   return NULL;
 }
 
-void MiLightClient::prepare(MiLightRadioConfig& config, 
-  const uint16_t deviceId, 
+
+void MiLightClient::prepare(MiLightRadioConfig& config,
+  const uint16_t deviceId,
   const uint8_t groupId) {
-  
+
   switchRadio(config.type);
-  
+
   if (deviceId >= 0 && groupId >= 0) {
     formatter->prepare(deviceId, groupId);
   }
@@ -45,41 +66,49 @@ void MiLightClient::setResendCount(const unsigned int resendCount) {
   this->resendCount = resendCount;
 }
 
+
 bool MiLightClient::available() {
   if (currentRadio == NULL) {
     return false;
   }
-  
-  return currentRadio->getRadio()->available();
-}
 
+  return currentRadio->available();
+}
 void MiLightClient::read(uint8_t packet[]) {
   if (currentRadio == NULL) {
     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[]) {
   if (currentRadio == NULL) {
     return;
   }
-  
+
 #ifdef DEBUG_PRINTF
   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("\n");
+  int iStart = millis();
 #endif
-  
+
   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) {
   formatter->updateColorRaw(color);
   flushPacket();
@@ -94,7 +123,7 @@ void MiLightClient::updateBrightness(const uint8_t brightness) {
   formatter->updateBrightness(brightness);
   flushPacket();
 }
-    
+
 void MiLightClient::updateMode(uint8_t mode) {
   formatter->updateMode(mode);
   flushPacket();
@@ -118,7 +147,7 @@ void MiLightClient::modeSpeedUp() {
   formatter->modeSpeedUp();
   flushPacket();
 }
-    
+
 void MiLightClient::updateStatus(MiLightStatus status, uint8_t groupId) {
   formatter->updateStatus(status, groupId);
   flushPacket();
@@ -148,7 +177,7 @@ void MiLightClient::unpair() {
   formatter->unpair();
   flushPacket();
 }
-    
+
 void MiLightClient::increaseBrightness() {
   formatter->increaseBrightness();
   flushPacket();
@@ -182,24 +211,24 @@ void MiLightClient::command(uint8_t command, uint8_t arg) {
 void MiLightClient::formatPacket(uint8_t* packet, char* buffer) {
   formatter->format(packet, buffer);
 }
-    
+
 void MiLightClient::flushPacket() {
   PacketStream& stream = formatter->buildPackets();
   const size_t prevNumRepeats = this->resendCount;
-  
+
   // When sending multiple packets, normalize the number of repeats
   if (stream.numPackets > 1) {
     setResendCount(MILIGHT_DEFAULT_RESEND_COUNT);
   }
-  
+
   while (stream.hasNext()) {
     write(stream.next());
-    
+
     if (stream.hasNext()) {
       delay(10);
     }
   }
-  
+
   setResendCount(prevNumRepeats);
   formatter->reset();
 }

+ 61 - 79
lib/MiLight/MiLightClient.h

@@ -1,92 +1,74 @@
 #include <Arduino.h>
 #include <MiLightRadio.h>
-#include <PL1167_nRF24.h>
-#include <RF24.h>
+#include <MiLightRadioFactory.h>
 #include <MiLightButtons.h>
-#include <RadioStack.h>
+#include <Settings.h>
 
 #ifndef _MILIGHTCLIENT_H
 #define _MILIGHTCLIENT_H
 
-// #define DEBUG_PRINTF
+//#define DEBUG_PRINTF
 
 #define MILIGHT_DEFAULT_RESEND_COUNT 10
 
 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
 #include "Arduino.h"
 #else
 #include <stdint.h>
 #include <stdlib.h>
-#include <string.h>
 #endif
 
-#include "AbstractPL1167.h"
 #include <MiLightRadioConfig.h>
 
-// #define DEBUG_PRINTF
-
-#ifndef MILIGHTRADIO_H_
-#define MILIGHTRADIO_H_
+#ifndef _MILIGHT_RADIO_H_
+#define _MILIGHT_RADIO_H_
 
 class MiLightRadio {
   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] )
 
-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();
   if (retval < 0) {
     return retval;
   }
-  
+
   retval = configure();
   if (retval < 0) {
     return retval;
@@ -31,7 +27,7 @@ int MiLightRadio::begin()
   return 0;
 }
 
-int MiLightRadio::configure() {
+int NRF24MiLightRadio::configure() {
   int retval = _pl1167.setCRC(true);
   if (retval < 0) {
     return retval;
@@ -47,38 +43,38 @@ int MiLightRadio::configure() {
     return retval;
   }
 
-  retval = _pl1167.setSyncword(config.syncword0, config.syncword3);
+  retval = _pl1167.setSyncword(_config.syncword0, _config.syncword3);
   if (retval < 0) {
     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) {
     return retval;
   }
-  
+
   return 0;
 }
 
-bool MiLightRadio::available() {
+bool NRF24MiLightRadio::available() {
   if (_waiting) {
 #ifdef DEBUG_PRINTF
   printf("_waiting\n");
 #endif
     return true;
   }
-  
-  if (_pl1167.receive(config.channels[0]) > 0) {
+
+  if (_pl1167.receive(_config.channels[0]) > 0) {
 #ifdef DEBUG_PRINTF
-  printf("MiLightRadio - received packet!\n");
+  printf("NRF24MiLightRadio - received packet!\n");
 #endif
     size_t packet_length = sizeof(_packet);
     if (_pl1167.readFIFO(_packet, packet_length) < 0) {
       return false;
     }
 #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
     if (packet_length == 0 || packet_length != _packet[0] + 1U) {
       return false;
@@ -98,13 +94,13 @@ bool MiLightRadio::available() {
   return _waiting;
 }
 
-int MiLightRadio::dupesReceived()
+int NRF24MiLightRadio::dupesReceived()
 {
   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) {
     frame_length = 0;
@@ -125,8 +121,7 @@ int MiLightRadio::read(uint8_t frame[], size_t &frame_length)
   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) {
     return -1;
   }
@@ -141,11 +136,14 @@ int MiLightRadio::write(uint8_t frame[], size_t frame_length)
   return frame_length;
 }
 
-int MiLightRadio::resend()
-{
+int NRF24MiLightRadio::resend() {
   for (size_t i = 0; i < MiLightRadioConfig::NUM_CHANNELS; i++) {
     _pl1167.writeFIFO(_out_packet, _out_packet[0] + 1);
-    _pl1167.transmit(config.channels[i]);
+    _pl1167.transmit(_config.channels[i]);
   }
   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) ;
 
 PL1167_nRF24::PL1167_nRF24(RF24 &radio)
-  : _radio(radio) { }
+  : _radio(radio)
+{ }
 
 static const uint8_t pipe[] = {0xd1, 0x28, 0x5e, 0x55, 0x55};
 
@@ -266,7 +267,7 @@ int PL1167_nRF24::transmit(uint8_t channel)
       buffer_fill -= 8;
     }
   }
-  
+
   yield();
 
   _radio.write(tmp, outp);
@@ -374,14 +375,14 @@ int PL1167_nRF24::internal_receive()
   }
 
   memcpy(_packet, tmp, outp);
-  
+
   _packet_length = outp;
   _received = true;
-  
+
 #ifdef DEBUG_PRINTF
   printf("Successfully parsed packet of length %d\n", _packet_length);
 #endif
-  
+
   return outp;
 }
 

+ 2 - 3
lib/MiLight/PL1167_nRF24.h

@@ -9,7 +9,6 @@
 #include "Arduino.h"
 #endif
 
-#include "AbstractPL1167.h"
 #include "RF24.h"
 
 // #define DEBUG_PRINTF
@@ -17,9 +16,9 @@
 #ifndef PL1167_NRF24_H_
 #define PL1167_NRF24_H_
 
-class PL1167_nRF24 : public AbstractPL1167 {
+class PL1167_nRF24 {
   public:
-    PL1167_nRF24(RF24 &radio);
+    PL1167_nRF24(RF24& radio);
     int open();
     int setPreambleLength(uint8_t preambleLength);
     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 <IntParsing.h>
 #include <algorithm>
-  
+
 bool Settings::hasAuthSettings() {
   return adminUsername.length() > 0 && adminPassword.length() > 0;
 }
@@ -16,7 +16,7 @@ size_t Settings::getAutoRestartPeriod() {
   if (_autoRestartPeriod == 0) {
     return 0;
   }
-  
+
   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")) {
       settings.adminUsername = parsedSettings.get<String>("admin_username");
     }
-    
+
     if (parsedSettings.containsKey("admin_password")) {
       settings.adminPassword = parsedSettings.get<String>("admin_password");
     }
-    
+
     if (parsedSettings.containsKey("ce_pin")) {
       settings.cePin = parsedSettings["ce_pin"];
     }
-    
+
     if (parsedSettings.containsKey("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")) {
       settings.packetRepeats = parsedSettings["packet_repeats"];
     }
-    
+
     if (parsedSettings.containsKey("http_repeat_factor")) {
       settings.httpRepeatFactor = parsedSettings["http_repeat_factor"];
     }
-    
+
     if (parsedSettings.containsKey("auto_restart_period")) {
       settings._autoRestartPeriod = parsedSettings["auto_restart_period"];
     }
-    
+
     JsonArray& arr = parsedSettings["device_ids"];
     settings.updateDeviceIds(arr);
-    
+
     JsonArray& gatewayArr = parsedSettings["gateway_configs"];
     settings.updateGatewayConfigs(gatewayArr);
   }
@@ -69,7 +77,7 @@ void Settings::updateDeviceIds(JsonArray& arr) {
     if (this->deviceIds) {
       delete this->deviceIds;
     }
-    
+
     this->deviceIds = new uint16_t[arr.size()];
     this->numDeviceIds = arr.size();
     arr.copyTo(this->deviceIds, arr.size());
@@ -81,13 +89,13 @@ void Settings::updateGatewayConfigs(JsonArray& arr) {
     if (this->gatewayConfigs) {
       delete[] this->gatewayConfigs;
     }
-    
+
     this->gatewayConfigs = new GatewayConfig*[arr.size()];
     this->numGatewayConfigs = arr.size();
-    
+
     for (size_t i = 0; i < arr.size(); i++) {
       JsonArray& params = arr[i];
-      
+
       if (params.success() && params.size() == 3) {
         this->gatewayConfigs[i] = new GatewayConfig(parseInt<uint16_t>(params[0]), params[1], params[2]);
       } else {
@@ -112,6 +120,12 @@ void Settings::patch(JsonObject& parsedSettings) {
     if (parsedSettings.containsKey("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")) {
       this->packetRepeats = parsedSettings["packet_repeats"];
     }
@@ -137,7 +151,7 @@ void Settings::load(Settings& settings) {
     File f = SPIFFS.open(SETTINGS_FILE, "r");
     String settingsContents = f.readStringUntil(SETTINGS_TERMINATOR);
     f.close();
-    
+
     deserialize(settings, settingsContents);
   } else {
     settings.save();
@@ -153,7 +167,7 @@ String Settings::toJson(const bool prettyPrint) {
 
 void Settings::save() {
   File f = SPIFFS.open(SETTINGS_FILE, "w");
-  
+
   if (!f) {
     Serial.println(F("Opening settings file failed"));
   } else {
@@ -165,21 +179,23 @@ void Settings::save() {
 void Settings::serialize(Stream& stream, const bool prettyPrint) {
   DynamicJsonBuffer jsonBuffer;
   JsonObject& root = jsonBuffer.createObject();
-  
+
   root["admin_username"] = this->adminUsername;
   root["admin_password"] = this->adminPassword;
   root["ce_pin"] = this->cePin;
   root["csn_pin"] = this->csnPin;
+  root["reset_pin"] = this->resetPin;
+  root["radio_interface_type"] = typeToString(this->radioInterfaceType);
   root["packet_repeats"] = this->packetRepeats;
   root["http_repeat_factor"] = this->httpRepeatFactor;
   root["auto_restart_period"] = this->_autoRestartPeriod;
-  
+
   if (this->deviceIds) {
     JsonArray& arr = jsonBuffer.createArray();
     arr.copyFrom(this->deviceIds, this->numDeviceIds);
     root["device_ids"] = arr;
   }
-  
+
   if (this->gatewayConfigs) {
     JsonArray& arr = jsonBuffer.createArray();
     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);
       arr.add(elmt);
     }
-    
+
     root["gateway_configs"] = arr;
   }
-  
+
   if (prettyPrint) {
     root.prettyPrintTo(stream);
   } else {
     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
 
+enum RadioInterfaceType {
+  nRF24 = 0,
+  LT8900 = 1,
+};
+
 class GatewayConfig {
 public:
   GatewayConfig(uint16_t deviceId, uint16_t port, uint8_t protocolVersion)
@@ -48,6 +53,8 @@ public:
     // CE and CSN pins from nrf24l01
     cePin(D0),
     csnPin(D8),
+    resetPin(0),
+    radioInterfaceType(nRF24),
     deviceIds(NULL),
     gatewayConfigs(NULL),
     numDeviceIds(0),
@@ -71,6 +78,9 @@ public:
   static void deserialize(Settings& settings, JsonObject& json);
   static void load(Settings& settings);
 
+  static RadioInterfaceType typeFromString(const String& s);
+  static String typeToString(RadioInterfaceType type);
+
   void save();
   String toJson(const bool prettyPrint = true);
   void serialize(Stream& stream, const bool prettyPrint = false);
@@ -82,6 +92,8 @@ public:
   String adminPassword;
   uint8_t cePin;
   uint8_t csnPin;
+  uint8_t resetPin;
+  RadioInterfaceType radioInterfaceType;
   uint16_t *deviceIds;
   GatewayConfig **gatewayConfigs;
   size_t numGatewayConfigs;

+ 3 - 2
platformio.ini

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

+ 22 - 14
src/main.cpp

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