Index: /trunk/D_OpenThermGateway.json
===================================================================
--- /trunk/D_OpenThermGateway.json	(revision 1)
+++ /trunk/D_OpenThermGateway.json	(revision 3)
@@ -35,5 +35,5 @@
             "FanMode": {
                 "Style": "numeric",
-                "Service": "urn: upnp-org: serviceId: HVAC_FanOperatingMode1",
+                "Service": "urn:upnp-org:serviceId:HVAC_FanOperatingMode1",
                 "Variable": "ModeStatus"
             }
@@ -170,4 +170,88 @@
                     },
                     "ControlCode": "heater_setpoint"
+                },
+                {
+                    "ControlType": "label",
+                    "Label": {
+                        "lang_tag": "firmware",
+                        "text": "Gateway firmware version:"
+                    },
+                    "Display": {
+                        "Top": 140,
+                        "Left": 50,
+                        "Width": 75,
+                        "Height": 20
+                    }
+                },
+                {
+                    "ControlType": "variable",
+                    "Display": {
+                        "Service": "urn:otgw-tclcode-com:serviceId:OpenThermGateway1",
+                        "Variable": "FirmwareVersion",
+                        "Top": 140,
+                        "Left": 200,
+                        "Width": 175,
+                        "Height": 20
+                    }
+                },
+                {
+                    "ControlType": "label",
+                    "Label": {
+                        "lang_tag": "cmd",
+                        "text": "Free format command:"
+                    },
+                    "Display": {
+                        "Top": 220,
+                        "Left": 50,
+                        "Width": 75,
+                        "Height": 20
+                    }
+                },
+                {
+                    "ControlType":"input",
+                    "ID": "CustomCommand",
+                    "Style":"text",
+                    "Display":{
+                        "Top": 217,
+                        "Left": 200,
+                        "Width": 100,
+                        "Height": 20
+                    }
+                },
+                {
+                    "ControlType": "button",
+                    "Label": {
+                        "lang_tag": "cmd_send",
+                        "text": "Send"
+                    },
+                    "Display": {
+                        "Top": 217,
+                        "Left": 325,
+                        "Width": 75,
+                        "Height": 20,
+                        "Service": "urn:otgw-tclcode-com:serviceId:OpenThermGateway1",
+                    },
+                    "Command": {
+                        "Service": "urn:otgw-tclcode-com:serviceId:OpenThermGateway1",
+                        "Action": "SendCommand",
+                        "Parameters": [
+                            {
+                                "Name": "Command",
+                                "ID": "CustomCommand"
+                            }
+                        ]
+                    },
+                    "ControlCode": "heater_cmd"
+                },
+                {
+                    "ControlType": "variable",
+                    "Display": {
+                        "Service": "urn:otgw-tclcode-com:serviceId:OpenThermGateway1",
+                        "Variable": "CommandResponse",
+                        "Top": 220,
+                        "Left": 425,
+                        "Width": 175,
+                        "Height": 20
+                    }
                 }
             ]
@@ -363,4 +447,4 @@
         }
     ],
-    "DeviceType": "urn:schemas-upnp-org:device:Heater:1"
+    "DeviceType": "urn:schemas-upnp-org:device:HVAC_ZoneThermostat:1"
 }
Index: /trunk/S_OpenThermGateway.xml
===================================================================
--- /trunk/S_OpenThermGateway.xml	(revision 1)
+++ /trunk/S_OpenThermGateway.xml	(revision 3)
@@ -3,5 +3,5 @@
   <specVersion>
     <major>1</major>
-    <minor>0</minor>
+    <minor>1</minor>
   </specVersion>
   <serviceStateTable>
@@ -17,5 +17,23 @@
     </stateVariable>
   </serviceStateTable>
- <actionList>
-  </actionList>
+  <actionList>
+    <action>
+      <name>SetOutsideTemperature</name>
+      <argumentList>
+        <argument>
+          <name>NewTemperature</name>
+          <direction>in</direction>
+        </argument>
+      </argumentList>
+    </action>
+     <action>
+      <name>SendCommand</name>
+      <argumentList>
+        <argument>
+          <name>Command</name>
+          <direction>in</direction>
+        </argument>
+      </argumentList>
+    </action>
+ </actionList>
 </scpd>
Index: /trunk/D_OpenThermGateway.xml
===================================================================
--- /trunk/D_OpenThermGateway.xml	(revision 1)
+++ /trunk/D_OpenThermGateway.xml	(revision 3)
@@ -7,6 +7,4 @@
   <device>
     <deviceType>urn:schemas-upnp-org:device:HVAC_ZoneThermostat:1</deviceType>
-    <Category_Num>5</Category_Num>
-    <SubCategory_Num>2</SubCategory_Num>
     <staticJson>D_OpenThermGateway.json</staticJson>
     <serviceList>
@@ -17,8 +15,49 @@
       </service>
       <service>
-      <Optional/>
-         <serviceType>urn:schemas-upnp-org:service:Heater:1</serviceType>
-         <serviceId>urn:schemas-upnp-org:serviceId:Heater1</serviceId>
-         <SCPDURL>D_Heater1.xml</SCPDURL>
+        <Optional/>
+        <serviceType>urn:schemas-upnp-org:service:HVAC_FanOperatingMode:1</serviceType>
+        <serviceId>urn:upnp-org:serviceId:HVAC_FanOperatingMode1</serviceId>
+        <SCPDURL>S_HVAC_FanOperatingMode1.xml</SCPDURL>
+      </service>
+      <service>
+        <serviceType>urn:micasaverde-com:serviceId:HVAC_OperatingState:1</serviceType>
+        <serviceId>urn:micasaverde-com:serviceId:HVAC_OperatingState1</serviceId>
+        <SCPDURL>S_HVAC_OperatingState1.xml</SCPDURL>
+      </service>
+      <service>
+        <serviceType>urn:schemas-upnp-org:service:HVAC_UserOperatingMode:1</serviceType>
+        <serviceId>urn:upnp-org:serviceId:HVAC_UserOperatingMode1</serviceId>
+        <SCPDURL>S_HVAC_UserOperatingMode1.xml</SCPDURL>
+      </service>
+      <service>
+        <Optional/>
+        <serviceType>urn:schemas-upnp-org:service:FanSpeed:1</serviceType>
+        <serviceId>urn:upnp-org:serviceId:FanSpeed1</serviceId>
+        <SCPDURL>S_FanSpeed1.xml</SCPDURL>
+      </service>
+      <service>
+        <serviceType>urn:schemas-upnp-org:service:TemperatureSensor:1</serviceType>
+        <serviceId>urn:upnp-org:serviceId:TemperatureSensor1</serviceId>
+        <SCPDURL>S_TemperatureSensor1.xml</SCPDURL>
+      </service>
+      <service>
+        <serviceType>urn:schemas-upnp-org:service:TemperatureSetpoint:1</serviceType>
+        <serviceId>urn:upnp-org:serviceId:TemperatureSetpoint1_Heat</serviceId>
+        <SCPDURL>S_TemperatureSetpoint1.xml</SCPDURL>
+      </service>
+      <service>
+        <serviceType>urn:schemas-upnp-org:service:SwitchPower:1</serviceType>
+        <serviceId>urn:upnp-org:serviceId:SwitchPower1</serviceId>
+        <SCPDURL>S_SwitchPower1.xml</SCPDURL>
+      </service>
+      <service>
+        <serviceType>urn:schemas-micasaverde-com:service:EnergyMetering:1</serviceType>
+        <serviceId>urn:micasaverde-com:serviceId:EnergyMetering1</serviceId>
+        <SCPDURL>S_EnergyMetering1.xml</SCPDURL>
+      </service>
+      <service>
+        <serviceType>urn:schemas-micasaverde-com:service:HaDevice:1</serviceType>
+        <serviceId>urn:micasaverde-com:serviceId:HaDevice1</serviceId>
+        <SCPDURL>S_HaDevice1.xml</SCPDURL>
       </service>
     </serviceList>
Index: /trunk/J_OpenThermGateway.js
===================================================================
--- /trunk/J_OpenThermGateway.js	(revision 1)
+++ /trunk/J_OpenThermGateway.js	(revision 3)
@@ -1,44 +1,49 @@
 var browserIE = false;
 var otgjsButtons = new Array();
+var otgjsMessage;
 var otgjsConfig;
 
 // Flag ID, y, x1, x2
 var otgjsInfoFlagLayout = [
-   [0x0100,  20,  30, 190],
-   [0x0200,  40,  30, 190],
-   [0x0400,  60,  30, 190],
-   [0x0800,  80,  30, 190],
-   [0x1000, 100,  30, 190],
-   [0x0001,  20, 230, 385],
-   [0x0002,  40, 230, 385],
-   [0x0004,  60, 230, 385],
-   [0x0008,  80, 230, 385],
-   [0x0010, 100, 230, 385],
-   [0x0020, 120,  30, 190],
-   [0x0040, 120, 230, 385]
+   [0x0100,  20,  30, 190], // Central Heating enable
+   [0x0200,  40,  30, 190], // Domestic Hot Water enable
+   [0x0400,  60,  30, 190], // Cooling enable
+   [0x0800,  80,  30, 190], // OTC active
+   [0x1000, 100,  30, 190], // Central Heating 2 enable
+   [0x0001, 120,  30, 190], // Fault indication
+   [0x0002,  20, 235, 390], // Central Heating mode
+   [0x0004,  40, 235, 390], // Domestic Hot Water mode
+   [0x0008,  80, 235, 390], // Flame status
+   [0x0010,  60, 235, 390], // Cooling status
+   [0x0020, 100, 235, 390], // Central Heating 2 mode
+   [0x0040, 120, 235, 390]  // Diagnostic indication
 ]
 
 // Fault ID, y, x1, x2
 var otgjsInfoFaultLayout = [
-   [0x0100,  20, 425, 570],
-   [0x0200,  40, 425, 570],
-   [0x0400,  60, 425, 570],
-   [0x0800,  80, 425, 570],
-   [0x1000, 100, 425, 570],
-   [0x2000, 120, 425, 570]
+   [0x0100,  20, 435, 580],
+   [0x0200,  40, 435, 580],
+   [0x0400,  60, 435, 580],
+   [0x0800,  80, 435, 580],
+   [0x1000, 100, 435, 580],
+   [0x2000, 120, 435, 580]
 ]
 
-// Message ID, y, x1, x2
+// Message ID, y, x1, x2, optional: index hb/lb
 var otgjsInfoMsgLayout = [
-   [24, 160,  30, 215],
-   [16, 160, 330, 535],
-   [25, 180,  30, 215],
-   [28, 200,  30, 215],
-   [17, 180, 330, 535],
-   [14, 200, 330, 535],
-   [27, 220,  30, 215],
-   [ 1, 220, 330, 535],
-   [26, 240,  30, 215],
-   [56, 240, 330, 535]
+   [ 1, 215, 340, 540], // Control setpoint
+   [ 5, 275, 340, 540, 'lb'], // Fault flags & OEM fault code
+   [ 9, 175, 340, 540], // Remote override room setpoint
+   [14, 255, 340, 540], // Maximum relative modulation level
+   [16, 155, 340, 540], // Room setpoint
+   [17, 255,  30, 220], // Relative modulation level
+   [18, 275,  30, 220], // Central heating water pressure
+   [24, 155,  30, 220], // Room temperature
+   [25, 175,  30, 220], // Boiler water temperature
+   [26, 235,  30, 220], // DHW temperature
+   [27, 215,  30, 220], // Outside temperature
+   [28, 195,  30, 220], // Return water temperature
+   [56, 235, 340, 540], // DHW setpoint
+   [57, 195, 340, 540]  // Max CH water setpoint
 ];
 
@@ -47,6 +52,10 @@
 
 function otgjsInfoTab(deviceID) {
-   var html = '<style type="text/css">input {margin:0px;} .skinned-form-controls input[type="checkbox"]:disabled + span, .skinned-form-controls input[type="checkbox"]:disabled + span:before {opacity: 1.0;}</style>';
-
+   var html = '<style type="text/css">input {margin:0px;} .skinned-form-controls input[type="checkbox"]:disabled + span, .skinned-form-controls input[type="checkbox"]:disabled + span:before {opacity: 1.0;} '
+   html += 'div.hr {height: 1px; background-image: -webkit-linear-gradient(left, rgba(0,0,0,0), rgba(200,200,200,0.9), rgba(0,0,0,0)); '; 
+   html += 'background-image:    -moz-linear-gradient(left, rgba(0,0,0,0), rgba(200,200,200,0.75), rgba(0,0,0,0)); '; 
+   html += 'background-image:     -ms-linear-gradient(left, rgba(0,0,0,0), rgba(200,200,200,0.9), rgba(0,0,0,0)); '; 
+   html += 'background-image:      -o-linear-gradient(left, rgba(0,0,0,0), rgba(200,200,200,0.9), rgba(0,0,0,0));} ';
+   html += 'div.err {color: #ABABAB}</style>';
    for (var i=0; i<otgjsInfoFlagLayout.length; i++) {
       html += '<div class="label" id="flag'+otgjsInfoFlagLayout[i][0]+'" style="position: absolute; top: '+otgjsInfoFlagLayout[i][1]+'px; left: '+otgjsInfoFlagLayout[i][2]+'px;"></div>';
@@ -59,12 +68,34 @@
       html += '<input type="checkbox" id="faultcheck'+otgjsInfoFaultLayout[i][0]+'" disabled><span></span></div>';
    }
+   html += '<div class="hr" style="position: absolute; top: 145px; left: 28px; width: 570px;"></div>';
    for (var i=0; i<otgjsInfoMsgLayout.length; i++) {
-      html += '<div class="label" id="msg'+otgjsInfoMsgLayout[i][0]+'" style="position: absolute; top: '+otgjsInfoMsgLayout[i][1]+'px; left: '+otgjsInfoMsgLayout[i][2]+'px;">'
-      html += '</div><div class="variable" id="msgval'+otgjsInfoMsgLayout[i][0]+'" style="position: absolute; top: '+otgjsInfoMsgLayout[i][1]+'px; left: '+otgjsInfoMsgLayout[i][3]+'px; width: 50px; text-align: right"></div>'
-   }
-
+      html += '<div class="label" id="msg'+otgjsInfoMsgLayout[i][0]+'" style="position: absolute; top: '+otgjsInfoMsgLayout[i][1]+'px; left: '+otgjsInfoMsgLayout[i][2]+'px;">';
+      html += '</div><div class="variable" id="msgval'+otgjsInfoMsgLayout[i][0]+'" style="position: absolute; top: '+otgjsInfoMsgLayout[i][1]+'px; left: '+otgjsInfoMsgLayout[i][3]+'px; width: 55px; text-align: right"></div>';
+   }
+   html += '<div class="hr" style="position: absolute; top: 300px; left: 28px; width: 570px;"></div>';
+   for (var i=0; i<4; i++) {
+      html += '<div class="err" id="err'+i+'" style="position: absolute; top: 310px; left: '+(50+145*i)+'px;">';
+      html += '</div><div class="err" id="errval'+i+'" style="position: absolute; top: 310px; left: '+(100+145*i)+'px; width: 30px; text-align: right"></div>';
+   }
    otgjsDetectBrowser();
    set_panel_html(html);
-   otgjsGetInfo(deviceID, "urn:otgw-tclcode-com:serviceId:OpenThermGateway1", 'GetConfiguration', otgjsInfoConfig);
+   if (otgjsMessage == undefined) {
+      otgjsGetInfo(deviceID, "urn:otgw-tclcode-com:serviceId:OpenThermGateway1", 'GetMessages', otgjsInfoConfig);
+   } else {
+      otgjsInfoConfig(deviceID, otgjsMessage);
+   }
+}
+
+function otgjsGetVariableState(deviceObj, service, variable) {
+   if (deviceObj && deviceObj.states) {
+      var statesNo = deviceObj.states.length;
+      for (var i=0; i<statesNo; i++) {
+         var stateObj = deviceObj.states[i];
+         if(stateObj && stateObj.service == service && stateObj.variable == variable) {
+            return stateObj.value;
+         }
+      }
+   }
+   return undefined
 }
 
@@ -72,9 +103,11 @@
    // Keep updating while we are on this tab
    if (document.getElementById('msg'+otgjsInfoMsgLayout[0][0]) != null) {
+      // Get the state of the device
+      var deviceObj = get_lu_status_device_obj(deviceID);
       // Update flag values
       for (var i=0; i<otgjsInfoFlagLayout.length; i++) {
          var elem = document.getElementById('flagcheck'+otgjsInfoFlagLayout[i][0]);
-         var msg = otgjsConfig[0].flags[otgjsInfoFlagLayout[i][0]];
-         var val = get_device_state(deviceID, "urn:otgw-tclcode-com:serviceId:OpenThermGateway1", msg.var, 1);
+         var msg = otgjsMessage[0].flags[otgjsInfoFlagLayout[i][0]];
+         var val = otgjsGetVariableState(deviceObj, "urn:otgw-tclcode-com:serviceId:OpenThermGateway1", msg.var);
          elem.checked = (val == "1");
       }
@@ -82,6 +115,6 @@
       for (var i=0; i<otgjsInfoFaultLayout.length; i++) {
          var elem = document.getElementById('faultcheck'+otgjsInfoFaultLayout[i][0]);
-         var msg = otgjsConfig[5].flags[otgjsInfoFaultLayout[i][0]];
-         var val = get_device_state(deviceID, "urn:otgw-tclcode-com:serviceId:OpenThermGateway1", msg.var, 1);
+         var msg = otgjsMessage[5].flags[otgjsInfoFaultLayout[i][0]];
+         var val = otgjsGetVariableState(deviceObj, "urn:otgw-tclcode-com:serviceId:OpenThermGateway1", msg.var);
          elem.checked = (val == "1");
       }
@@ -89,18 +122,29 @@
       for (var i=0; i<otgjsInfoMsgLayout.length; i++) {
          var elem = document.getElementById('msgval'+otgjsInfoMsgLayout[i][0]);
-         var msg = otgjsConfig[otgjsInfoMsgLayout[i][0]];
-         var val = get_device_state(deviceID, "urn:otgw-tclcode-com:serviceId:OpenThermGateway1", msg.var, 1);
+         var msg = otgjsMessage[otgjsInfoMsgLayout[i][0]];
+         var msgVar = msg.var
+         if (typeof(msgVar) == 'object') {
+            msgVar = msgVar[otgjsInfoMsgLayout[i][4]]
+         }
+         var val = otgjsGetVariableState(deviceObj, "urn:otgw-tclcode-com:serviceId:OpenThermGateway1", msgVar);
          elem.innerHTML = (val == null ? "???" : val);
       }
-      setTimeout("otgjsDisplayInfo("+deviceID+")", 500);
+      // Update error count
+      var val = otgjsGetVariableState(deviceObj, "urn:otgw-tclcode-com:serviceId:OpenThermGateway1", "Errors");
+      var val_a = val.split(',');
+      for (var i=0; i<val_a.length; i++) {
+         var elem = document.getElementById('errval'+i);
+         elem.innerHTML = val_a[i];
+      }
+      setTimeout("otgjsDisplayInfo("+deviceID+")", 1000);
    }
 };
 
 function otgjsInfoConfig(deviceID, result) {
-   otgjsConfig = result;
+   otgjsMessage = result;
    // Add flag labels
    for (var i=0; i<otgjsInfoFlagLayout.length; i++) {
       var elem = document.getElementById('flag'+otgjsInfoFlagLayout[i][0]);
-      var msg = otgjsConfig[0].flags[otgjsInfoFlagLayout[i][0]];
+      var msg = otgjsMessage[0].flags[otgjsInfoFlagLayout[i][0]];
       elem.innerHTML = msg.txt;
    }
@@ -108,5 +152,5 @@
    for (var i=0; i<otgjsInfoFaultLayout.length; i++) {
       var elem = document.getElementById('fault'+otgjsInfoFaultLayout[i][0]);
-      var msg = otgjsConfig[5].flags[otgjsInfoFaultLayout[i][0]];
+      var msg = otgjsMessage[5].flags[otgjsInfoFaultLayout[i][0]];
       elem.innerHTML = msg.txt;
    }
@@ -114,6 +158,17 @@
    for (var i=0; i<otgjsInfoMsgLayout.length; i++) {
       var elem = document.getElementById('msg'+otgjsInfoMsgLayout[i][0]);
-      var msg = otgjsConfig[otgjsInfoMsgLayout[i][0]];
-      elem.innerHTML = msg.txt;
+      var msg = otgjsMessage[otgjsInfoMsgLayout[i][0]];
+      var txt = msg.txt;
+      var sub = otgjsInfoMsgLayout[i][4];
+      if (sub != null) {
+         var n = txt.search("&");
+         if (sub == 'lb') { txt = txt.substr(n+1); } else { txt = txt.substr(0, n-1); }
+      }
+      elem.innerHTML = txt;
+   }
+   // Add error labels
+   for (var i=0; i<4; i++) {
+      var elem = document.getElementById('err'+i);
+      elem.innerHTML = "Error 0"+(i+1)+":";
    }
    otgjsDisplayInfo(deviceID);
@@ -124,9 +179,12 @@
 
 function otgjsSettingsTab(deviceID) {
+   if (otgjsConfig == undefined) {
+      otgjsConfig = otgjsGetInfo(deviceID, "urn:otgw-tclcode-com:serviceId:OpenThermGateway1", 'GetConfiguration', 0, false);
+   }
    var deviceObj = get_device_obj(deviceID);
    var devicePos = get_device_index(deviceID)
-   var html = '';
-   html += '<table width="70%" border="0" cellspacing="3" cellpadding="0">';
-   html += '<tr><td colspan="3"><div class="label"><b>General options</b></div></td></tr>';
+   var html = '<style>span.customStyleSelectBox {border:0}</style>';
+   html += '<table width="100%" border="0" cellspacing="3" cellpadding="0">';
+   html += '<tr><td colspan="3"><div class="label"><b>Plugin options</b></div></td></tr>';
    
    if (deviceObj.commUse) {
@@ -141,38 +199,61 @@
       html += '</tr>';
    }
-   var modes = [{'value':'Gateway','label':'Gateway'}, {'value':'Monitor','label':'Monitor'}];
-   html += otgjsAddButtons(deviceID, 'Generate debug logging & files', 'PluginDebug');
-   html += '<tr><td colspan="3"><div class="label"><b>Gateway options</b></div></td></tr>';
-   html += otgjsAddPulldown(deviceID, 'Gateway mode', 'GatewayMode', modes);
+   // Make list of temerature sensors
+   var sensors = [{'value':'','label':'None'}];
+   var devicesCount = jsonp.ud.devices.length;
+   for (var i=0; i<devicesCount; i++){
+      if (jsonp.ud.devices[i].category_num == 17) {
+         sensors.push({ 'value':jsonp.ud.devices[i].id,'label':jsonp.ud.devices[i].name });
+      }
+   }
+   var debug = [{'value':'0','label':'Off'},{'value':'1','label':'On'}];
+   html += otgjsAddPulldown(deviceID, 'Generate debug logging & files', 'PluginDebug', debug);
+   html += otgjsAddPulldown(deviceID, 'Outside temperature sensor', 'OutsideSensorDevice', sensors);
+   html += '<tr><td colspan="3"><div class="label"><b>Gateway configuration</b></div></td></tr>';
+   for (var elem in otgjsConfig) {
+      if (otgjsConfig[elem].cmd != undefined) {
+         var list = [];
+         for (var prop in otgjsConfig[elem].tab) {
+            list.push({ 'value':prop,'label':otgjsConfig[elem].tab[prop].txt });
+         }
+         if (elem != "LED") {
+            html += otgjsAddPulldown(deviceID, otgjsConfig[elem].txt, otgjsConfig[elem].var, list);
+         } else {
+            for (var i=0; i<4; i++){
+               html += otgjsAddPulldown(deviceID, 'LED '+String.fromCharCode(65+i)+' function', 'LED'+i, list);
+            }
+         }
+      }
+   }
    html += '</table>';
 
    set_panel_html(html);
-   otgjsSettingsButtons(deviceID);
-}
-
-function otgjsSettingsButtons(deviceID) {
-   var dataLen = otgjsButtons.length;
-   for (var i = 0; i < dataLen; i++) {
-      var state = get_device_state(deviceID, "urn:otgw-tclcode-com:serviceId:OpenThermGateway1", otgjsButtons[i], 0)
-      if (state == "1") {
-         document.getElementById(otgjsButtons[i] + 'On').className = "button selected";
-         document.getElementById(otgjsButtons[i] + 'Off').className = "button";
-      } else {
-         document.getElementById(otgjsButtons[i] + 'Off').className = "button selected";
-         document.getElementById(otgjsButtons[i] + 'On').className = "button";
-      }
-   }
 }
 
 function otgjsUpdate(deviceID, variable, value) {
-   set_device_state(deviceID, "urn:otgw-tclcode-com:serviceId:OpenThermGateway1", variable, value, 0);
-   otgjsSettingsButtons(deviceID);
+   if (variable.search("LED") >= 0) {
+      var idx = Number(variable.charAt(3));
+      variable = "LEDFunctions";
+      var state = get_device_state(deviceID, "urn:otgw-tclcode-com:serviceId:OpenThermGateway1", variable, 0);
+      value = state.substr(0, idx) + value + state.substr(idx+1);
+      set_device_state(deviceID, "urn:otgw-tclcode-com:serviceId:OpenThermGateway1", variable, value, 0);
+   } else {
+      set_device_state(deviceID, "urn:otgw-tclcode-com:serviceId:OpenThermGateway1", variable, value, 0);
+      otgjsSettingsButtons(deviceID);
+   }
 }
 
 function otgjsAddPulldown(deviceID, label, variable, values, extra) {
    extra = (extra == null) ? '' : extra;
-   var selected = get_device_state(deviceID, "urn:otgw-tclcode-com:serviceId:OpenThermGateway1", variable, 0);
+   var selected;
+   if (variable.search("LED") >= 0) {
+      var idx = Number(variable.charAt(3));
+      var state = get_device_state(deviceID, "urn:otgw-tclcode-com:serviceId:OpenThermGateway1", "LEDFunctions", 0);
+      selected = (state == null ? '' : state.substr(idx, 1));
+   } else {
+      selected = get_device_state(deviceID, "urn:otgw-tclcode-com:serviceId:OpenThermGateway1", variable, 0);
+   }
    var pulldown = pulldown_from_array(values, variable + 'Select', selected, 'onChange="otgjsUpdate(' + deviceID + ', \'' + variable + '\', this.value)"');
-   var html = '<tr><td><div class="label">' + label + '</div></td><td colspan="2">' + pulldown + '</td><td>' + extra + '</td></tr>';
+   var html = '<tr><td><div class="label" id="+variable+">' + label + '</div></td><td colspan="2">' + pulldown + '</td><td>' + extra + '</td></tr>';
 
    return html;
@@ -231,8 +312,9 @@
 }
 
-function otgjsGetInfo(device, sid, what, func) {
+function otgjsGetInfo(device, sid, what, func, async) {
+   var result = '';
    new Ajax.Request("../port_3480/data_request", { 
      method: "get", 
-     asynchronous: true,
+     asynchronous: (async == undefined ? true : async),
      parameters: { 
          id: 'lr_' + what + device,
@@ -246,7 +328,10 @@
      }, 
      onComplete: function (response) { 
-         var result = response.responseText.evalJSON();
-         func(device, result)
+         result = response.responseText.evalJSON();
+         if (async != false) {
+            func(device, result);
+         }
      }
    });
-}
+   return result;
+}
Index: /trunk/I_OpenThermGateway.xml
===================================================================
--- /trunk/I_OpenThermGateway.xml	(revision 1)
+++ /trunk/I_OpenThermGateway.xml	(revision 3)
@@ -26,4 +26,5 @@
       otgMessage = pluginLib.otgMessage
       otgCallbackHandler = pluginLib.otgCallbackHandler
+      otgSetOutsideTempCallback = pluginLib.otgSetOutsideTempCallback
 
 		luup.log("OTG: library L_OpenThermGateway loaded")
@@ -60,4 +61,20 @@
       </run>
    </action>
-   </actionList>
+
+   <action>
+      <serviceId>urn:otgw-tclcode-com:serviceId:OpenThermGateway1</serviceId>
+      <name>SetOutsideTemperature</name>
+      <run>
+         if (pluginLib ~= nil) then pluginLib.otgSetOutsideTemperature(lul_settings.NewTemperature) end
+      </run>
+   </action>
+
+   <action>
+      <serviceId>urn:otgw-tclcode-com:serviceId:OpenThermGateway1</serviceId>
+      <name>SendCommand</name>
+      <run>
+         if (pluginLib ~= nil) then pluginLib.otgSendCommand(lul_settings.Command) end
+      </run>
+   </action>
+  </actionList>
 </implementation>
Index: /trunk/L_OpenThermGateway.lua
===================================================================
--- /trunk/L_OpenThermGateway.lua	(revision 1)
+++ /trunk/L_OpenThermGateway.lua	(revision 3)
@@ -4,9 +4,34 @@
 local socket = require("socket") -- needed for accurate timing
 
--------------------------------------------------
--- Control the OpenTherm Gateway via Mios Vera --
--------------------------------------------------
+-------------------------------------------------------------
+-- Monitor and control the OpenTherm Gateway via Mios Vera --
+-------------------------------------------------------------
  
-local OTG_PLUGIN_VERSION = "0.1"
+local OTG_PLUGIN_VERSION = "0.3"
+
+--- QUEUE STRCUTURE ---
+local Queue = {}
+function Queue.new()
+   return {first = 0, last = -1}
+end
+
+function Queue.push(list, value)
+   local last = list.last + 1
+   list.last = last
+   list[last] = value
+end
+    
+function Queue.pop(list)
+   local first = list.first
+   if first > list.last then return nil end
+   local value = list[first]
+   list[first] = nil -- to allow garbage collection
+   list.first = first + 1
+   return value
+end
+
+function Queue.len(list)
+   return list.last - list.first + 1
+end
 
 --- SERVICES ---
@@ -21,8 +46,12 @@
 --- DEVICES ---
 local otgDevice
+local otgOutsideSensor
 
 --- DYNAMIC VALUES ---
-local otgErrorCnt_t = { 0, 0, 0, 0 }
+local otgErrorCnt_t = {}
 local otgLastStatus = 0
+local otgGatewayMode = false
+local otgExpectingResponse = false
+local otgResponseQueue = Queue.new()
 
 --- DEBUGGING ---
@@ -34,13 +63,65 @@
 local otgPluginInit_t = { -- false: update; true: create only
    { "PluginVersion", OTG_PLUGIN_VERSION, false },
-   { "PluginDebug", "1", true },
+   { "PluginDebug", "0", true },
    { "LogPath", otgLogPath, true },
-   { "GatewayMode", "Monitor", true }
-}
-
+   { "GatewayMode", "", true },
+   { "CommandResponse", "", false },
+   { "Errors", "0,0,0,0", true }
+}
+
+-- OpenTherm Gateway operating modes
+local otgGatewayMode_t = {
+   [0] = { txt = "Monitor" },
+   [1] = { txt = "Gateway" }
+}
+
+-- OpenTherm Gateway LED functions
+local otgLedFunction_t = {
+   R = { txt = "Receiving an OpenTherm message from the thermostat or boiler" },
+   X = { txt = "Transmitting an OpenTherm message to the thermostat or boiler" },
+   T = { txt = "Transmitting or receiving a message on the master interface" },
+   B = { txt = "Transmitting or receiving a message on the slave interface" },
+   O = { txt = "Remote setpoint override is active" },
+   F = { txt = "Flame is on", var = "FlameStatus" },
+   H = { txt = "Central heating is on", var = "CHMode" },
+   W = { txt = "Hot water is on", var = "DHWMode" },
+   C = { txt = "Comfort mode (Domestic Hot Water Enable) is on", var = "DHWEnabled" },
+   E = { txt = "Transmission error has been detected", var = "Errors" },
+   M = { txt = "Boiler requires maintenance", var = "DiagnosticEvent" }
+}
+
+-- OpenTherm Gateway signal transition checking
+local otgIgnoreTrans_t = {
+   [0] = { txt = "Check" },
+   [1] = { txt = "Ignore" }
+}
+
+-- OpenTherm Gateway reference voltage
+local otgRefVoltage_t = {
+   [0] = { txt = "0.625V" },
+   [1] = { txt = "0.833V" },
+   [2] = { txt = "1.042V" },
+   [3] = { txt = "1.250V" },
+   [4] = { txt = "1.458V" },
+   [5] = { txt = "1.667V" },
+   [6] = { txt = "1.875V" },
+   [7] = { txt = "2.083V" },
+   [8] = { txt = "2.292V" },
+   [9] = { txt = "2.500V" },
+}
+
+-- OpenTherm Gateway domestic hot water setting
+local otgDHWsetting_t = {
+   [0] = { txt = "Off" },
+   [1] = { txt = "On (comfort mode)" },
+    A  = { txt = "Thermostat controlled" }
+}
+
+-- OpenTherm Gateway message types
 local otgMsgInitiator_t = {
    A = "Answer", B = "Boiler", R = "Request", T = "Thermostat"
 }
 
+-- OpenTherm message type
 local otgMsgType_t = {
    [0] = "Read-Data", [1] = "Write-Data", [2] = "Invalid-Data", [3] = "-reserved-", 
@@ -48,22 +129,32 @@
 }
 
--- OpenTherm message flags
-
-local otgStatusFlag_t = { -- ID 0: Master status (HB) & Slave status (LB)
-   [0x0100] = { txt = "Central Heating enable", var = "CHEnabled" },
-   [0x0200] = { txt = "Domestic Hot Water enable", var = "DHWEnabled" },
+-- OpenTherm status flags [ID 0: Master status (HB) & Slave status (LB)]
+local otgStatusFlag_t = {
+   [0x0100] = { txt = "Central heating enable", var = "CHEnabled" },
+   [0x0200] = { txt = "Domestic hot water enable", var = "DHWEnabled" },
    [0x0400] = { txt = "Cooling enable", var = "CoolEnabled" },
-   [0x0800] = { txt = "OTC active", var = "OTCActive" },
-   [0x1000] = { txt = "Central Heating 2 enable", var = "CH2Enabled" },
+   [0x0800] = { txt = "Outside temp. comp. active", var = "OTCActive" },
+   [0x1000] = { txt = "Central heating 2 enable", var = "CH2Enabled" },
    [0x0001] = { txt = "Fault indication", var = "Fault" }, -- no fault/fault
-   [0x0002] = { txt = "Central Heating mode", var = "CHMode" }, -- not active/active
-   [0x0004] = { txt = "Domestic Hot Water mode", var = "DHWMode" }, -- not active/active
+   [0x0002] = { txt = "Central heating mode", var = "CHMode" }, -- not active/active
+   [0x0004] = { txt = "Domestic hot water mode", var = "DHWMode" }, -- not active/active
    [0x0008] = { txt = "Flame status", var = "FlameStatus" }, -- flame off/on
    [0x0010] = { txt = "Cooling status", var = "CoolingStatus" }, -- not active/active
-   [0x0020] = { txt = "Central Heating 2 mode", var = "CH2Mode" }, -- not active/active
+   [0x0020] = { txt = "Central heating 2 mode", var = "CH2Mode" }, -- not active/active
    [0x0040] = { txt = "Diagnostic indication", var = "DiagnosticEvent" } -- no diagnostics/diagnostics event
 }
 
-local otgFaultFlag_t = { -- ID 5: Application-specific fault flags (HB)
+-- OpenTherm configuration flags [ID 3: master/slave config flags (HB)]
+local otgConfigFlag_t = {
+   [0x0100] = { txt = "Domestic hot water present", var = "ConfigDHWpresent" },
+   [0x0200] = { txt = "Control type (modulating on/off)", var = "ConfigControlType" },
+   [0x0400] = { txt = "Cooling supported", var = "ConfigCooling" },
+   [0x0800] = { txt = "Domestic hot water storage tank", var = "ConfigDHW" },
+   [0x1000] = { txt = "Master low-off & pump control allowed", var = "ConfigMasterPump" },
+   [0x2000] = { txt = "Central heating 2 present", var = "ConfigCH2" }
+}
+
+-- OpenTherm fault flags [ID 5: Application-specific fault flags (HB)]
+local otgFaultFlag_t = {
    [0x0100] = { txt = "Service request", var = "FaultServiceRequest" },
    [0x0200] = { txt = "Lockout-reset", var = "FaultLockoutReset" },
@@ -75,53 +166,68 @@
 
 -- OpenTherm messages
-
 local otgMessage_t = {
-   [0] = { txt = "Status", val = "flag8", var = nil, flags = otgStatusFlag_t },
-   [1] = { txt = "Control setpoint", val = "f8.8", var = "ControlSetpoint" },
-   [2] = { txt = "Master configuration" },
-   [3] = { txt = "Slave configuration" },
-   [4] = { txt = "Remote command" },
-   [5] = { txt = "Application-specific flags", val = "flag8", flags = otgFaultFlag_t },
-   [6] = { txt = "Remote parameter flags", val = "flag8", var = nil },
-   [8] = { txt = "Control setpoint 2" },
-   [9] = { txt = "Remote override room setpoint" },
-   [14] = { txt = "Maximum relative modulation level", val = "f8.8", var = "MaxRelModLevel" },
-   [15] = { txt = "Boiler capacity and modulation limits", val = "u8", var = nil },
-   [16] = { txt = "Room Setpoint", val = "f8.8", sid = OTG_TEMP_SETP_SID, var = "CurrentSetpoint" },
-   [17] = { txt = "Relative modulation level", val = "f8.8", var = "RelModLevel" },
-   [18] = { txt = "CH water pressure", val = "f8.8", var = nil },
-   [19] = { txt = "DHW flow rate", val = "f8.8" },
-   [20] = { txt = "Day of week & time of day" },
-   [21] = { txt = "Date" },
-   [22] = { txt = "Year", val = "u16" },
-   [23] = { txt = "Room setpoint CH2", val = "f8.8" },
-   [24] = { txt = "Room temperature", val = "f8.8", sid = OTG_TEMP_SENS_SID, var = "CurrentTemperature" },
-   [25] = { txt = "Boiler water temperature", val = "f8.8", var = "BoilerWaterTemp" },
-   [26] = { txt = "DHW temperature", val = "f8.8", var = "DHWTemp" },
-   [27] = { txt = "Outside temperature", val = "f8.8", var = "OutsideTemp" },
-   [28] = { txt = "Return water temperature", val = "f8.8", var = "ReturnWaterTemp" },
-   [29] = { txt = "Solar storage temperature", val = "f8.8" },
-   [30] = { txt = "Solar collector temperature", val = "s16" },
-   [31] = { txt = "Flow temperature CH2", val = "f8.8" },
-   [32] = { txt = "DHW2 temperature", val = "f8.8" },
-   [33] = { txt = "Exhaust temperature", val = "s16" },
-   [48] = { txt = "DHW setpoint boundaries", val = "u8", var = nil },
-   [49] = { txt = "Max CH setpoint boundaries", val = "u8", var = nil },
-   [56] = { txt = "DHW setpoint", val = "f8.8", var = "DHWSetpoint" },
-   [57] = { txt = "Max CH water setpoint", val = "f8.8", var = nil },
-   [100] = { txt = "Remote override function" },
-   [115] = { txt = "OEM diagnostic code" },
-   [116] = { txt = "Burner starts", val = "u16", var = nil },
-   [117] = { txt = "CH pump starts", val = "u16", var = nil },
-   [118] = { txt = "DHW pump/valve starts", val = "u16", var = nil },
-   [119] = { txt = "DHW burner starts", val = "u16", var = nil },
-   [120] = { txt = "Burner operation hours", val = "u16", var = nil },
-   [121] = { txt = "CH pump operation hours", val = "u16", var = nil },
-   [122] = { txt = "DHW pump/valve operation hours", val = "u16", var = nil },
-   [123] = { txt = "DHW burner operation hours", val = "u16", var = nil },
-   [124] = { txt = "Opentherm version Master", val = "f8.8" },
-   [125] = { txt = "Opentherm version Slave", val = "f8.8" },
-   [126] = { txt = "Master product version", val = "u8" },
-   [127] = { txt = "Slave product version", val = "u8" },
+   [ 0] = { dir = "R-", txt = "Status", val = "flag8", flags = otgStatusFlag_t },
+   [ 1] = { dir = "-W", txt = "Control setpoint (&deg;C)", val = "f8.8", var = "ControlSetpoint" },
+   [ 2] = { dir = "-W", txt = "Master configuration", val = { hb = "flag8", lb = "u8" } },
+   [ 3] = { dir = "R-", txt = "Slave configuration", val = { hb = "flag8", lb = "u8" }, flags = otgConfigFlag_t },
+   [ 4] = { dir = "-W", txt = "Remote command", val = "u8" },
+   [ 5] = { dir = "R-", txt = "Fault flags & OEM fault code", val = { hb = "flag8", lb = "u8" }, var = { lb = "FaultCode" }, flags = otgFaultFlag_t },
+   [ 6] = { dir = "R-", txt = "Remote parameter flags", val = "flag8" },
+   [ 7] = { dir = "-W", txt = "Cooling control signal (%)", var = "f8.8" },
+   [ 8] = { dir = "-W", txt = "Control setpoint 2 (&deg;C)", var = "f8.8" },
+   [ 9] = { dir = "R-", txt = "Remote override room setpoint (&deg;C)", val = "f8.8", var ="RemoteOverrideRoomSetpoint" },
+   [10] = { dir = "R-", txt = "Number of transparent slave parameters (TSP) supported by slave", val = "u8" },
+   [11] = { dir = "RW", txt = "Index number/value of referred-to transparent slave parameter (TSP)", val = "u8" },
+   [12] = { dir = "R-", txt = "Size of fault history buffer (FHB) supported by slave", val = "u8" },
+   [13] = { dir = "R-", txt = "Index number/value of referred-to fault history buffer (FHB) entry", val = "u8" },
+   [14] = { dir = "-W", txt = "Max. relative modulation level (%)", val = "f8.8", var = "MaxRelativeModulationLevel" },
+   [15] = { dir = "R-", txt = "Max. boiler capacity (kW) and modulation level setting (%)", val = "u8" },
+   [16] = { dir = "-W", txt = "Room setpoint (&deg;C)", val = "f8.8", sid = OTG_TEMP_SETP_SID, var = "CurrentSetpoint" },
+   [17] = { dir = "R-", txt = "Relative modulation level (%)", val = "f8.8", var = "RelativeModulationLevel" },
+   [18] = { dir = "R-", txt = "Central heating water pressure (bar)", val = "f8.8", var = "CHWaterPressure" },
+   [19] = { dir = "R-", txt = "Domestic hot water flow rate (litres/minute)", val = "f8.8" },
+   [20] = { dir = "RW", txt = "Day of week & time of day" },
+   [21] = { dir = "RW", txt = "Date", val = "u8" },
+   [22] = { dir = "RW", txt = "Year", val = "u16" },
+   [23] = { dir = "-W", txt = "Room setpoint CH2 (&deg;C)", val = "f8.8" },
+   [24] = { dir = "-W", txt = "Room temperature (&deg;C)", val = "f8.8", sid = OTG_TEMP_SENS_SID, var = "CurrentTemperature" },
+   [25] = { dir = "R-", txt = "Boiler water temperature (&deg;C)", val = "f8.8", var = "BoilerWaterTemperature" },
+   [26] = { dir = "R-", txt = "Domestic hot water temperature (&deg;C)", val = "f8.8", var = "DHWTemperature" },
+   [27] = { dir = "R-", txt = "Outside temperature (&deg;C)", val = "f8.8", var = "OutsideTemperature" },
+   [28] = { dir = "R-", txt = "Return water temperature (&deg;C)", val = "f8.8", var = "ReturnWaterTemperature" },
+   [29] = { dir = "R-", txt = "Solar storage temperature (&deg;C)", val = "f8.8" },
+   [30] = { dir = "R-", txt = "Solar collector temperature (&deg;C)", val = "s16" },
+   [31] = { dir = "R-", txt = "Flow temperature central heating 2 (&deg;C)", val = "f8.8" },
+   [32] = { dir = "R-", txt = "Domestic hot water 2 temperature (&deg;C)", val = "f8.8", var = "DHW2Temperature" },
+   [33] = { dir = "R-", txt = "Boiler exhaust temperature (&deg;C)", val = "s16" },
+   [48] = { dir = "R-", txt = "Domestic hot water setpoint boundaries (&deg;C)", val = "s8" },
+   [49] = { dir = "R-", txt = "Max. central heating setpoint boundaries (&deg;C)", val = "s8" },
+   [50] = { dir = "R-", txt = "OTC heat curve ratio upper & lower bounds", val = "s8" },
+   [56] = { dir = "RW", txt = "Domestic hot water setpoint (&deg;C)", val = "f8.8", var = "DHWSetpoint" },
+   [57] = { dir = "RW", txt = "Max. central heating water setpoint (&deg;C)", val = "f8.8", var = "MaxCHWaterSetpoint" },
+   [58] = { dir = "RW", txt = "OTC heat curve ratio (&deg;C)", val = "f8.8" },
+   [100] = { dir = "R-", txt = "Remote override function", val = { hb = "flag8", lb = "u8" }, var = { hb = "RemoteOverrideFunction" } },
+   [115] = { dir = "R-", txt = "OEM diagnostic code", val = "u16" },
+   [116] = { dir = "RW", txt = "Number of starts burner", val = "u16" },
+   [117] = { dir = "RW", txt = "Number of starts central heating pump", val = "u16" },
+   [118] = { dir = "RW", txt = "Number of starts domestic hot water pump/valve", val = "u16" },
+   [119] = { dir = "RW", txt = "Number of starts burner during domestic hot water mode", val = "u16" },
+   [120] = { dir = "RW", txt = "Number of hours that burner is in operation (i.e. flame on)", val = "u16" },
+   [121] = { dir = "RW", txt = "Number of hours that central heating pump has been running", val = "u16" },
+   [122] = { dir = "RW", txt = "Number of hours that domestic hot water pump has been running/valve has been opened", val = "u16" },
+   [123] = { dir = "RW", txt = "Number of hours that domestic hot water burner is in operation during DHW mode", val = "u16" },
+   [124] = { dir = "-W", txt = "Opentherm version Master", val = "f8.8" },
+   [125] = { dir = "R-", txt = "Opentherm version Slave", val = "f8.8" },
+   [126] = { dir = "-W", txt = "Master product version and type", val = "u8" },
+   [127] = { dir = "R-", txt = "Slave product version and type", val = "u8" }
+}
+
+local otgConfig_t = {
+   VER = { txt = "Gateway firmware version", var = "FirmwareVersion", rep = "A", ret = "OpenTherm Gateway (.*)" },
+   GW  = { txt = "Operating mode", tab = otgGatewayMode_t, var = "GatewayMode", cmd = "GW", rep = "G", ret = "[0|1]"},
+   LED = { txt = "LED <> function", tab = otgLedFunction_t, var = "LEDFunctions", cmd = "LA", rep = "L", ret = "[R|X|T|B|O|F|H|W|C|E|M]+" },
+   ITR = { txt = "Non-significant transitions", tab = otgIgnoreTrans_t, var = "IgnoreTransitions", cmd = "IT", rep = "T", ret = "[0|1]" },
+   REF  = { txt = "Reference voltage", tab = otgRefVoltage_t, var = "ReferenceVoltage", cmd = "VR", rep = "V", ret = "%d" },
+   HOT  = { txt = "Domestic hot water enable ", tab = otgDHWsetting_t, var = "DHWSetting", cmd = "HW", rep = "W", ret = "[0|1|A]" }
 }
 
@@ -132,9 +238,11 @@
 -- Update a system variable only if the value will change
 function updateIfNeeded(sid, var, newVal, id, createOnly)
-   local curVal = luup.variable_get(sid, var, id)
-   local valUpdate = (curVal == nil) or ((createOnly ~= true) and (curVal ~= tostring(newVal)) or false)
-   if (valUpdate == true) then
-      luup.variable_set(sid, var, newVal, id)
-      return true
+   if (sid ~= nil and var ~= nil and newVal ~= nil and id ~= nil) then
+      local curVal = luup.variable_get(sid, var, id)
+      local valUpdate = (curVal == nil) or ((createOnly ~= true) and (curVal ~= tostring(newVal)) or false)
+      if (valUpdate == true) then
+         luup.variable_set(sid, var, newVal, id)
+         return true
+      end
    end
    return false
@@ -213,10 +321,47 @@
 end
 
+-- otgDecodeMessage
+function otgDecodeMessage(val1, msgVal, val2)
+   local val = ""
+   if (msgVal == "u16") then -- unsigned 16
+      val = val1 * 256 + val2
+   elseif (msgVal == "s16") then -- signed 16
+      val = -32767 + val1 * 256 + val2
+   elseif (msgVal == "f8.8") then -- floating point
+      val1 = val1 + 1/256 * val2
+      val = string.format("%.2f", val1) -- high accuracy
+   else -- byte only types
+      if (msgVal == "flag8") then -- flag 8
+         local i
+         for i = 7, 0, -1 do
+            val = val .. ((bitw.band(val1, 2^i) == 0) and "0" or "1")
+         end
+      elseif (msgVal == "u8") then -- unsigned 8
+         val = string.format("%d", val1)
+      elseif (msgVal == "s8") then -- signed 8
+         val = string.format("%d", -127 + val1)
+      end
+      if (val2 ~= nil) then
+         val = val .. " " .. otgDecodeMessage(val2, msgVal)
+      end
+   end
+   return val
+end
+
 -- otgWriteCommand: write a command to the OTG
-function otgWriteCommand(cmd)
+function otgWriteCommand(cmd, response)
    local success = luup.io.write(cmd, otgDevice)
+   debug("Sent command " .. cmd)
    if (success == false) then
       otgMessage("Cannot send message to the OpenTherm Gateway", 2)
       return false
+   end
+   if (response ~= nil) and (response == true) then
+      updateIfNeeded(OTG_GATEWAY_SID, "CommandResponse", "", otgDevice)
+      otgExpectingResponse = true
+   elseif (response == nil) then
+      Queue.push(otgResponseQueue, "OK")
+   elseif (type(response) == "string") then
+      Queue.push(otgResponseQueue, response)
    end
 end
@@ -265,13 +410,38 @@
    end
    
-   -- Request firmware version
-   local success = otgWriteCommand("PR=A")
-   if (success == false) then
-      return false
+   -- Update commands for firmware 4 (note: only happens on 2nd run)
+   local version = luup.variable_get(OTG_GATEWAY_SID, "FirmwareVersion", otgDevice)
+   if (version ~= nil) then
+      local major = tonumber(string.match(version, "(%d+)\."))
+      if (major ~= nil and major > 3) then
+         otgConfig_t.GW.rep = "M"
+      end
    end
 
    -- Register luup web handlers
+   luup.register_handler("otgCallbackHandler", "GetMessages" .. otgDevice)
    luup.register_handler("otgCallbackHandler", "GetConfiguration" .. otgDevice)
    luup.register_handler("otgCallbackHandler", "GetMessageFile" .. otgDevice)
+   
+   -- Optional: Register outside sensor device
+   otgOutsideSensor = luup.variable_get(OTG_GATEWAY_SID, "OutsideSensorDevice", otgDevice)
+   if (otgOutsideSensor ~= nil and otgOutsideSensor ~= "") then
+      otgOutsideSensor = tonumber(otgOutsideSensor)
+      debug("Registering outside temperature sensor " .. otgOutsideSensor)
+      luup.variable_watch("otgSetOutsideTempCallback", OTG_TEMP_SENS_SID, "CurrentTemperature", otgOutsideSensor)
+   end
+   
+   -- Get error count locally
+   local errors = luup.variable_get(OTG_GATEWAY_SID, "Errors", otgDevice)
+   string.gsub(errors, "([^,]+)", function(c) otgErrorCnt_t[#otgErrorCnt_t + 1] = tonumber(c) end)
+
+   -- Send report commands to Gateway to receive settings
+   for key, elem in pairs(otgConfig_t) do
+      otgWriteCommand("PR=" .. elem.rep, key) -- get OTG value
+   end
+
+   -- Store gateway mode locally
+   otgGatewayMode = (luup.variable_get(OTG_GATEWAY_SID, otgConfig_t.GW.var, otgDevice) == "1")
+   otgMessage("Started in " .. otgConfig_t.GW.tab[(otgGatewayMode and 1 or 0)].txt .. " mode.", 2)
 end
 
@@ -289,8 +459,13 @@
       local msgVal = "flag8"
       local msgVar = ""
+      local msgFlags = nil
       if (otgMessage_t[msg] ~= nil) then
          msgTxt = otgMessage_t[msg].txt
          msgVal = otgMessage_t[msg].val or "flag8"
          msgVar = otgMessage_t[msg].var or ""
+         msgFlags = otgMessage_t[msg].flags
+         if (msgVar == "" and msg > 0) then
+            debug("*** No variable for message " .. msg .. " (" .. msgTxt .. ") ***")
+         end
       end
       local val1 = tonumber("0x" .. string.sub(data, 6, 7))
@@ -298,22 +473,8 @@
       local val = 0
       -- Format value
-      if (msgVal == "flag8") then
-         val = ""
-         local i, item
-         for i = 7, 0, -1 do
-            val = val .. ((bitw.band(val1, 2^i) == 0) and "0" or "1")
-         end
-         val = val .. " "
-         for i = 7, 0, -1 do
-            val = val .. ((bitw.band(val2, 2^i) == 0) and "0" or "1")
-         end
-      elseif (msgVal == "u8") then
-         val = string.format("%02x %02x", val1, val2)
-      elseif (msgVal == "u16") then
-         val = val1 * 256 + val2
-      elseif (msgVal == "f8.8") then
-         val1 = val1 + 1/256 * val2
-         val = string.format("%.2f", val1) -- high accuracy
-         val2 = string.format("%.1f", val1) -- low accuracy for variables
+      if (type(msgVal) == "table") then
+         val = otgDecodeMessage(val1, msgVal.hb) .. "," .. otgDecodeMessage(val2, msgVal.lb)
+      else
+         val = otgDecodeMessage(val1, msgVal, val2)
       end
       local s = string.format("%10s: %-14s %s = ", sender, ctype, msgTxt) .. val
@@ -321,8 +482,7 @@
       debug(s)
       -- Optional: decode flags
-      local flags = otgMessage_t[msg].flags
       val1 = val1 * 256 + val2
-      if (flags ~= nil) then
-         for i, item in pairs(flags) do
+      if (msgFlags ~= nil) then
+         for i, item in pairs(msgFlags) do
             local flagVal = ((bitw.band(val1, i) == 0) and "0" or "1")
             debug(">> " .. item.txt .. ": " .. flagVal)
@@ -336,8 +496,15 @@
          local sid = otgMessage_t[msg].sid or OTG_GATEWAY_SID
          if (msgVal == "f8.8" and sid ~= OTG_GATEWAY_SID) then
+            val2 = string.format("%.1f", tonumber(val))
             updateIfNeeded(sid, msgVar, val2, otgDevice) -- use lower accuracy for non-gateway variables
             sid = OTG_GATEWAY_SID -- set high accuray value in gateway variable as well
          end
-         updateIfNeeded(sid, msgVar, val, otgDevice)
+         if (type(msgVar) == "table") then
+            local hb, lb = string.match(val, "([^,]*),(.*)")
+            updateIfNeeded(sid, msgVar.hb, hb, otgDevice)
+            updateIfNeeded(sid, msgVar.lb, lb, otgDevice)
+         else
+            updateIfNeeded(sid, msgVar, val, otgDevice)
+         end
       end
       -- Optional: Update MCV specific vars
@@ -350,13 +517,13 @@
          local modeState = "Idle"
          local modeStatus = "Off"
-         if (chEnabled == true and chMode == true) then
+         if (chEnabled == "1" and chMode == "1") then
             modeState = "Heating"
             modeStatus = "HeatOn"
-         elseif (chEnabled == true and chMode == false) then
+         elseif (chEnabled == "1" and chMode == "0") then
             modeState = "PendingHeat"
-         elseif (coolEnabled == true and coolMode == true) then
+         elseif (coolEnabled == "1" and coolMode == "1") then
             modeState = "Cooling"
             modeStatus = "CoolOn"
-         elseif (coolEnabled == true and coolMode == false) then
+         elseif (coolEnabled == "1" and coolMode == "0") then
             modeState = "PendingCool"
          end
@@ -364,11 +531,43 @@
          updateIfNeeded(OTG_HVAC_USER_SID, "ModeStatus", modeStatus, otgDevice)
       end
-   elseif (string.find(data, "OpenTherm Gateway") ~= nil) then -- PR=A
-      local ver = string.match(data, "%a+ %a+ (.*)")
-      debug("Firmware version " .. ver)
-      updateIfNeeded(OTG_GATEWAY_SID, "FirmwareVersion", ver, otgDevice)
+   elseif (otgExpectingResponse == true) then
+      otgExpectingResponse = false
+      updateIfNeeded(OTG_GATEWAY_SID, "CommandResponse", data, otgDevice)
+   elseif (Queue.len(otgResponseQueue) > 0) then
+      local elem = Queue.pop(otgResponseQueue)
+      local elem_t = otgConfig_t[elem]
+      if (elem_t ~= nil) then -- a known message
+         local val = string.match(data, elem_t.ret) -- check expected response
+         if (val ~= nil) then -- valid response
+            local valOld = luup.variable_get(OTG_GATEWAY_SID, elem_t.var, otgDevice)
+            if (valOld ~= nil and valOld ~= "" and valOld ~= val and elem_t.cmd ~= nil) then
+               val = valOld -- current setting takes precedence
+               if (elem == "LED") then
+                  otgWriteCommand("LA=" .. string.sub(val, 1, 1))
+                  otgWriteCommand("LB=" .. string.sub(val, 2, 2))
+                  otgWriteCommand("LC=" .. string.sub(val, 3, 3))
+                  otgWriteCommand("LD=" .. string.sub(val, 4, 4))
+               else
+                  otgWriteCommand(elem_t.cmd .. "=" .. val)
+               end
+            end
+            if (elem == "GW") then
+               otgGatewayMode = (val == "1")
+            end
+            updateIfNeeded(OTG_GATEWAY_SID, elem_t.var, val, otgDevice)
+         else
+            debug("Error for cmd: " .. elem .. "; expected " .. elem_t.ret .. " but got " .. data)
+         end
+      elseif (elem ~= data) then
+         debug("Error: expected " .. elem .. " but got " .. data)
+      end
    elseif (string.find(data, "Error") ~= nil) then -- Error received
-      local errNr = string.match(data, "(%d*)")
+      local errNr = tonumber(string.match(data, "(%d+)"))
       otgErrorCnt_t[errNr] = otgErrorCnt_t[errNr] + 1
+      local errors = ""
+      for i = 1, 4 do
+         errors = errors .. "," .. otgErrorCnt_t[i]
+      end
+      luup.variable_set(OTG_GATEWAY_SID, "Errors", string.sub(errors, 2), otgDevice)
    else
       debug("Unknown message")
@@ -377,14 +576,13 @@
 end
 
--- Action functions
+--- ACTION FUNCTIONS ---
 
 -- otgSetCurrentSetpoint
 function otgSetCurrentSetpoint(NewCurrentSetpoint)
    debug("otgSetCurrentSetpoint " .. (NewCurrentSetpoint or ""))
-   local mode = luup.variable_get(OTG_GATEWAY_SID, "GatewayMode", otgDevice)
-   if (mode == "Gateway") then
+   if (otgGatewayMode == true) then
       return otgWriteCommand("TT=" .. NewCurrentSetpoint) -- Temperature temporary
    else
-      otgMessage("SetModeTarget only possible in Gateway mode", 2)
+      otgMessage("SetCurrentSetpoint only possible in Gateway mode", 2)
    end
 end
@@ -393,6 +591,5 @@
 function otgSetModeTarget(NewModeTarget)
    debug("otgSetModeTarget " .. (NewModeTarget or ""))
-   local mode = luup.variable_get(OTG_GATEWAY_SID, "GatewayMode", otgDevice)
-   if (mode == "Gateway") then
+   if (otgGatewayMode == true) then
       if (NewModeTarget == "Off") then
          return otgWriteCommand("TT=0") -- Schedule
@@ -406,4 +603,27 @@
       otgMessage("SetModeTarget only possible in Gateway mode", 2)
    end
+end
+
+function otgSetOutsideTempCallback(lul_device, lul_service, lul_variable, lul_value_old, lul_value_new)
+   debug("otgSetOutsideTempCallback " .. (lul_device or "") .. ", " .. (lul_value_new or ""))
+   if (lul_device == otgOutsideSensor) then
+      otgSetOutsideTemperature(lul_value_new)
+   end
+end
+
+-- otgSetOutsideTemperature
+function otgSetOutsideTemperature(NewTemperature)
+   debug("otgSetOutsideTemperature " .. (NewTemperature or ""))
+   if (otgGatewayMode == true) then
+      return otgWriteCommand("OT=" .. NewTemperature) -- Outside temperature
+   else
+      otgMessage("SetOutsideTemperature only possible in Gateway mode", 2)
+   end
+end
+
+-- otgSendCommand
+function otgSendCommand(Command)
+   debug("otgSendCommand " .. (Command or ""))
+   return otgWriteCommand(Command, true) -- no checking on input
 end
 
@@ -481,6 +701,8 @@
    debug("otgCallbackHandler: request " .. lul_request)
    if (lul_outputformat ~= "xml") then
-      if (lul_request == "GetConfiguration" .. otgDevice) then
+      if (lul_request == "GetMessages" .. otgDevice) then
          return table2Json(otgMessage_t)
+      elseif (lul_request == "GetConfiguration" .. otgDevice) then
+         return table2Json(otgConfig_t)
       elseif (lul_request == "GetMessageFile" .. otgDevice) then
          return dumpFile(otgLogFilename)
