Index: /branches/v1.x/S_TCPLightingGateway1.xml
===================================================================
--- /branches/v1.x/S_TCPLightingGateway1.xml	(revision 30)
+++ /branches/v1.x/S_TCPLightingGateway1.xml	(revision 30)
@@ -0,0 +1,27 @@
+<?xml version="1.0"?>
+<scpd xmlns="urn:schemas-upnp-org:service-1-0">
+  <specVersion>
+    <major>1</major>
+    <minor>0</minor>
+  </specVersion>
+  <serviceStateTable>
+    <stateVariable sendEvents="no">
+      <name>AutoDiscovered</name>
+      <dataType>boolean</dataType>
+      <defaultValue>0</defaultValue>
+    </stateVariable>
+    <stateVariable sendEvents="yes">
+      <name>ErrorText</name>
+      <dataType>string</dataType>
+    </stateVariable>
+    <stateVariable sendEvents="no">
+      <name>OfflineDeviceNameFormat</name>
+      <dataType>string</dataType>
+    </stateVariable>
+  </serviceStateTable>
+  <actionList>
+    <action>
+      <name>Synchronize</name>
+    </action>
+  </actionList>
+</scpd>
Index: /branches/v1.x/D_TCPLightingGateway1.xml
===================================================================
--- /branches/v1.x/D_TCPLightingGateway1.xml	(revision 30)
+++ /branches/v1.x/D_TCPLightingGateway1.xml	(revision 30)
@@ -0,0 +1,26 @@
+<?xml version="1.0"?>
+<root xmlns="urn:schemas-upnp-org:device-1-0">
+  <specVersion>
+    <major>1</major>
+    <minor>0</minor>
+  </specVersion>
+  <device>
+    <deviceType>urn:schemas-pmcode-org:service:TCPLightingGateway:1</deviceType>
+    <staticJson>D_TCPLightingGateway1.json</staticJson>
+    <serviceList>
+      <service>
+        <serviceType>urn:schemas-pmcode-org:service:TCPLightingGateway:1</serviceType>
+        <serviceId>urn:pmcode-org:serviceId:TCPLightingGateway1</serviceId>
+        <SCPDURL>S_TCPLightingGateway1.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>
+    <implementationList>
+      <implementationFile>I_TCPLighting1.xml</implementationFile>
+    </implementationList>
+  </device>
+</root>
Index: /branches/v1.x/D_TCPLighting1.xml
===================================================================
--- /branches/v1.x/D_TCPLighting1.xml	(revision 30)
+++ /branches/v1.x/D_TCPLighting1.xml	(revision 30)
@@ -0,0 +1,31 @@
+<?xml version="1.0"?>
+<root xmlns="urn:schemas-upnp-org:device-1-0">
+  <specVersion>
+    <major>1</major>
+    <minor>0</minor>
+  </specVersion>
+  <device>
+    <deviceType>urn:schemas-upnp-org:device:DimmableLight:1</deviceType>
+    <staticJson>D_DimmableLight1.json</staticJson>
+    <serviceList>
+      <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-upnp-org:service:Dimming:1</serviceType>
+        <serviceId>urn:upnp-org:serviceId:Dimming1</serviceId>
+        <SCPDURL>S_Dimming1.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>
+    <implementationList>
+      <implementationFile>I_TCPLighting1.xml</implementationFile>
+    </implementationList>
+  </device>
+</root>
Index: /branches/v1.x/test.lua
===================================================================
--- /branches/v1.x/test.lua	(revision 30)
+++ /branches/v1.x/test.lua	(revision 30)
@@ -0,0 +1,25 @@
+local tcplighting = require("L_TCPLighting1")
+local json = require ("json")
+
+
+-- initialize gateway
+gateway = tcplighting_gateway_init(null) --
+print (json.encode(gateway))
+
+
+-- modify a light's intensity
+if (gateway.initialized) then
+    -- generate a random intensity
+    math.randomseed(os.time())
+    math.random(); math.random(); math.random()
+    local load_level_target = math.random(100)
+    --  call the gateway
+    tcplighting_set_load_level_target_by_name(gateway, "Front Entry Light", "Front Entry", load_level_target)
+    tcplighting_set_target_by_name(gateway, "Front Entry Light", "Front Entry", 1)
+    -- poll to get latest status
+    tcplighting_gateway_poll(gateway)
+    print (json.encode(gateway.devices))
+else
+    print ("--- ERROR: " .. gateway.error_text)
+end
+
Index: /branches/v1.x/I_TCPLighting1.xml
===================================================================
--- /branches/v1.x/I_TCPLighting1.xml	(revision 30)
+++ /branches/v1.x/I_TCPLighting1.xml	(revision 30)
@@ -0,0 +1,460 @@
+<?xml version="1.0"?>
+<implementation>
+  <functions>
+
+       -- ---------------------------------------------------------------------
+       -- Constants
+       -- ---------------------------------------------------------------------
+       local GATEWAY_DEVICE_TYPE = "urn:schemas-pmcode-org:service:TCPLightingGateway:1"
+       local GATEWAY_SERVICE_ID  = "urn:pmcode-org:serviceId:TCPLightingGateway1"
+       local HA_DEVICE_TYPE      = "urn:schemas-micasaverde-com:service:HaDevice:1"
+       local HA_SERVICE_ID       = "urn:micasaverde-com:serviceId:HaDevice1"
+       local DIMMER_DEVICE_TYPE  = "urn:schemas-upnp-org:device:DimmableLight:1"
+       local DIMMER_SERVICE_ID   = "urn:upnp-org:serviceId:Dimming1"
+       local SWITCH_DEVICE_TYPE  = "urn:schemas-upnp-org:device:SwitchPower:1"
+       local SWITCH_SERVICE_ID   = "urn:upnp-org:serviceId:SwitchPower1"
+       local LIGHT_DEVICE_FILE   = "D_TCPLighting1.xml"
+       
+       
+       -- ---------------------------------------------------------------------
+       -- Pseudo-Constants: Can be overridden by parent device variables
+       -- ---------------------------------------------------------------------
+       local OFFLINE_DEVICE_NAME_FORMAT = "[Offline] %s"
+
+
+       -- ---------------------------------------------------------------------
+       -- table representing the gateway and its devices
+       -- ---------------------------------------------------------------------
+       local tcplighting_gateway = nil
+
+
+       -- ---------------------------------------------------------------------
+       -- Get a reference to the gateway (will init if necessary)
+       -- ---------------------------------------------------------------------
+	   local function get_gateway (gateway_ip)
+		  if (not tcplighting_gateway) or (not gateway_ip) or (gateway_ip == "") then
+             luup.log ("TCPLighting:  get_gateway - calling tcplighting_gateway_init,  IP=" .. tostring(gateway_ip))
+			 tcplighting_gateway = tcplighting_gateway_init (gateway_ip)
+			 if tcplighting_gateway.error_text then
+				luup.task(tcplighting_gateway.error_text, 2, "TCPLighting", -1)
+			 end
+		  end
+		  return tcplighting_gateway
+	   end
+
+
+       -- ---------------------------------------------------------------------
+       -- Clear the gateway object
+       -- ---------------------------------------------------------------------
+	   local function reset_gateway ()
+		  tcplighting_gateway = nil
+		  return
+	   end
+
+
+       -- ---------------------------------------------------------------------
+       -- Returns true if the device is offline
+       -- ---------------------------------------------------------------------
+       local function is_offline (device)
+           return ((device == nil) or (device.offline and device.offline == "1"))
+       end
+
+
+       -- ---------------------------------------------------------------------
+       -- Returns true if the device has dimming capabilities
+       -- ---------------------------------------------------------------------
+       local function is_dimmer_switch (device)
+           return ((device and device.capability and string.find(device.capability,"switch_multilevel")) or
+                   (device and device.type and string.find(device.type,"multilevel")))
+       end
+
+
+       -- ---------------------------------------------------------------------
+       -- Returns true if the device has on/off capabilities
+       -- ---------------------------------------------------------------------
+       local function is_binary_switch (device)
+           return (device and device.capability and string.find(device.capability,"switch_binary"))
+       end
+
+
+       -- ---------------------------------------------------------------------
+       -- See if we have a matching room number
+       -- ---------------------------------------------------------------------
+       local function room_number (input_room_name)
+          local room_num = 0
+          if input_room_name then
+              for i, room_name in ipairs(luup.rooms) do
+                  if room_name == input_room_name then
+                      room_num = i
+                      break
+                  end
+              end
+          end
+          return room_num
+       end
+
+
+       -- ---------------------------------------------------------------------
+       -- Sets a LUUP device variable, but only if it has no current value
+       -- ---------------------------------------------------------------------
+       local function initialize_variable (service_id, variable_name, new_value, device_number)
+          local current_value = luup.variable_get (service_id, variable_name, device_number)
+          if (not current_value) or (current_value == "") then
+              luup.variable_set (service_id, variable_name, new_value, device_number)
+          end
+       end
+
+
+       -- ---------------------------------------------------------------------
+       -- Sets a LUUP device attribute, but only if it has no current value
+       -- ---------------------------------------------------------------------
+       local function initialize_attr (attr_name, new_value, device_number)
+          local current_value = luup.attr_get (attr_name, device_number)
+          if (not current_value) or (current_value == "") then
+              luup.attr_set (attr_name, new_value, device_number)
+          end
+       end
+
+
+       -- ---------------------------------------------------------------------
+       -- Updates a LUUP device variable, but only if its value has changed
+       -- ---------------------------------------------------------------------
+       local function update_variable (service_id, variable_name, new_value, device_number)
+          local current_value = luup.variable_get (service_id, variable_name, device_number)
+          if (not current_value) or (new_value ~= current_value) then
+              luup.variable_set (service_id, variable_name, new_value, device_number)
+          end
+       end
+
+
+       -- ---------------------------------------------------------------------
+       -- Updates a LUUP device attribute, but only if its value has changed
+       -- ---------------------------------------------------------------------
+       local function update_attr (attr_name, new_value, device_number)
+          local current_value = luup.attr_get (attr_name, device_number)
+          if (not current_value) or (new_value ~= current_value) then
+              luup.attr_set (attr_name, new_value, device_number)
+          end
+       end
+
+
+       -- ---------------------------------------------------------------------
+       -- Update a device to match info from gateway
+       -- ---------------------------------------------------------------------
+       local function update_device_settings (gateway, did, lul_device)
+
+           if not (gateway and did and lul_device) then
+              return
+           end
+
+           local device_number = tonumber(lul_device)
+           local device = gateway.devices[did]
+           
+		   -- indicate device is offline if we don't have data for it 
+           update_attr ("invisible", "", device_number) -- legacy code: prior versions made offline devices invisible
+		   if is_offline(device) then
+		       luup.log ("TCPLighting:  update_device_settings - device with id=" .. lul_device .. " is offline or has no data from gateway")
+		       update_attr ("name", string.format(OFFLINE_DEVICE_NAME_FORMAT, device.name), device_number)
+			   return
+		   else
+		       update_attr ("name", device.name, device_number)
+		   end
+
+           -- update the device
+		   update_attr ("manufacturer", device.manufacturer, device_number)
+		   update_attr ("model",        device.prodmodel,    device_number)
+
+           if device and device.room and device.room.name then
+               local room_num = room_number(device.room.name)
+               if room_num and room_num ~= 0 then
+                  local room_num_string = tostring(room_num)
+                  update_attr ("room_num", room_num_string, device_number)
+                  update_attr ("room",     room_num_string, device_number)
+               else
+                  luup.log("TCPLighting:  update_device_settings - unknown room " .. device.room.name)
+               end
+           end
+
+           if device and is_binary_switch(device) then
+		       update_variable (SWITCH_SERVICE_ID, "Status", device.state, device_number)
+		       update_variable (SWITCH_SERVICE_ID, "Target", device.state, device_number)
+           end
+
+           if device and is_dimmer_switch(device) then
+		      update_variable (DIMMER_SERVICE_ID, "LoadLevelStatus", device.level, device_number)
+		      update_variable (DIMMER_SERVICE_ID, "LoadLevelTarget", device.level, device_number)
+           end
+
+           return
+
+       end
+
+
+       -- ---------------------------------------------------------------------
+       -- Update all of the children of the specified device
+       -- ---------------------------------------------------------------------
+       local function sync_child_devices (gateway, parent_lul_device)
+
+           if not (gateway and parent_lul_device) then
+              return
+           end
+
+           -- poll the gateway for most current device data
+           tcplighting_gateway_poll(gateway)
+
+           -- find matching child devices
+           local parent_device_number = tonumber (parent_lul_device)
+           for k, luup_device in pairs(luup.devices) do
+               if (luup_device.device_num_parent == parent_device_number) and (luup_device.id) then
+                    luup.log ("TCPLighting:  sync_child_devices for " .. parent_lul_device .. " - updating child " .. k .. " " .. luup_device.description)
+                    update_device_settings (gateway, luup_device.id, k)
+               end
+           end
+
+           return
+
+       end
+
+
+       -- ---------------------------------------------------------------------
+       -- Create child devices of gateway if necessary
+       -- ---------------------------------------------------------------------
+       local function create_child_devices (gateway, parent_lul_device)
+
+           if gateway and gateway.initialized then
+
+              luup.log ("TCPLighting:  create_child_devices - gateway is initialized")
+
+			  local parent_device_number = tonumber (parent_lul_device)
+
+              -- Build up a table of child devices, indexed by did, with value being the device type
+			  local child_device_map = {}
+
+			  -- find existing children
+              for k, luup_device in pairs(luup.devices) do
+                  if (luup_device.device_num_parent == parent_device_number) and (luup_device.id) and (luup_device.device_type) then
+					  local did = luup.attr_get ("altid", k)
+					  if (did) then
+				         child_device_map[did] = {name=luup_device.description, device_type = luup_device.device_type}
+					  end
+                  end
+			  end
+
+              -- see if we have new children
+			  local new_devices_found = false
+              for did,gateway_device in pairs(gateway.devices) do
+				  if child_device_map[gateway_device.did] == nil then
+                     if is_dimmer_switch (gateway_device) then
+				        child_device_map[gateway_device.did] = {name=gateway_device.name, device_type=DIMMER_DEVICE_TYPE}
+						new_devices_found = true
+                     elseif is_binary_switch (gateway_device) then
+				        child_device_map[gateway_device.did] = {name=gateway_device.name, device_type=SWITCH_DEVICE_TYPE}
+						new_devices_found = true
+                     end
+				  end
+              end
+
+			  -- resync the combined list of child devices if new ones were found
+              if new_devices_found then
+			     local chdev_context = luup.chdev.start (parent_device_number)
+				 for did,device in pairs(child_device_map) do
+                     luup.log ("TCPLighting:  create_child_devices - creating child with did=" .. did .. ", name=" .. device.name)
+                     luup.chdev.append (parent_device_number, chdev_context, did, device.name, device.device_type, LIGHT_DEVICE_FILE,"","",false)
+                 end
+                 luup.chdev.sync (parent_device_number, chdev_context)
+			  else
+		         luup.log ("TCPLighting:  create_child_devices - no new child devices")
+              end
+
+           end
+
+       end
+
+
+       -- ---------------------------------------------------------------------
+       -- Schedule the next poll
+       -- ---------------------------------------------------------------------
+	   local function schedule_next_poll (lul_device)
+
+           local device_number = tonumber(lul_device)
+
+		   -- make sure the polling variables have values
+		   initialize_variable (HA_SERVICE_ID, "PollingEnabled", "1",  device_number)
+		   initialize_variable (HA_SERVICE_ID, "PollMinDelay",   "60", device_number)
+
+           -- get current polling settings (may be different from above if overriden by user)
+		   local polling_enabled  = luup.variable_get (HA_SERVICE_ID, "PollingEnabled", device_number)
+		   local polling_interval = luup.variable_get (HA_SERVICE_ID, "PollMinDelay",   device_number)
+
+		   -- make a delayed call to the synchronization functions
+		   if polling_enabled == "true" or polling_enabled == "1" then
+		      luup.call_timer("synchronize_gateway", 1, tonumber(polling_interval), "", lul_device)
+			  luup.log ("TCPLighting:  schedule_next_poll - timer set")
+		   end
+
+	   end
+
+
+       -- ---------------------------------------------------------------------
+       -- Contact the gateway and initialize the gateway object
+       -- ---------------------------------------------------------------------
+	   function synchronize_gateway (lul_device)
+
+		   luup.log ("TCPLighting: synchronize_gateway - start")
+
+           local device_number = tonumber(lul_device)
+           local gateway_ip = luup.attr_get ("ip", device_number)
+
+           -- will create and populate a new gateway configuration object if necessary
+           local gateway = get_gateway (gateway_ip)
+
+		   if (gateway.error_text == nil) or (gateway.error_text == "") then
+		   
+               -- Sync up gateway properties
+			   initialize_attr ("name",  "TCP Lighting Gateway", device_number)
+			   if gateway.gateway_uuid then
+			      initialize_attr ("altid", gateway.gateway_uuid,  device_number)
+			   end
+               update_variable (GATEWAY_SERVICE_ID, "ErrorText", "", device_number)
+			   if gateway.autodiscovered then
+				  -- Use IP address from autodiscovery
+		          update_attr     ("ip", gateway.gateway_ip, device_number)
+				  update_variable (GATEWAY_SERVICE_ID, "AutoDiscovered", "1", device_number)
+			   end
+
+			   -- check to see if prefix for offline devices has been overridden
+			   --   some of this code looks redundant, but is required  
+			   --   in case previous versions of plugin were installed
+			   initialize_variable (GATEWAY_SERVICE_ID, "OfflineDeviceNameFormat", OFFLINE_DEVICE_NAME_FORMAT, device_number) 
+			   local offline_device_name_format_override = luup.variable_get (GATEWAY_SERVICE_ID, "OfflineDeviceNameFormat", device_number)
+			   if offline_device_name_format_override then
+			       OFFLINE_DEVICE_NAME_FORMAT = offline_device_name_format_override
+			   end
+
+               -- create and synchronize all children
+		       create_child_devices (gateway, lul_device)
+               sync_child_devices (gateway, lul_device)
+
+		   else
+
+		       -- flag that there was an error
+		       luup.log ("TCPLighting:  synchronize_gateway - error: " .. gateway.error_text)
+               update_variable (GATEWAY_SERVICE_ID, "ErrorText", gateway.error_text, device_number)
+
+               -- clear autodiscovered IP on failures
+               local previously_autodiscovered = luup.variable_get  (GATEWAY_SERVICE_ID, "AutoDiscovered", device_number)
+			   if previously_autodiscovered and previously_autodiscovered == "1" then
+				  -- Clear IP address from autodiscovery
+		          update_attr     ("ip", "", device_number)
+				  update_variable (GATEWAY_SERVICE_ID, "AutoDiscovered", "0", device_number)
+		          luup.log ("TCPLighting:  synchronize_gateway - cleared previously autodiscovered IP = " .. tostring(gateway_ip))
+			   end
+
+		       -- reset the gateway object
+			   reset_gateway ()
+
+           end
+
+		   -- set up polling so that changes made externally to the plugin show up in the UI
+		   schedule_next_poll (lul_device)
+
+		   luup.log ("TCPLighting: synchronize_gateway - end")
+
+           return true
+
+	   end
+
+
+       -- ---------------------------------------------------------------------
+       -- Set the dimming level on a light
+       -- ---------------------------------------------------------------------
+       local function set_load_level_target (lul_device, lul_settings)
+	       local device_number = tonumber(lul_device)
+           local gateway_ip = luup.devices[device_number].ip
+           local did = luup.devices[device_number].id
+		   local gateway = get_gateway (gateway_ip)
+		   if gateway.initialized and did then
+               tcplighting_set_load_level_target (gateway, did, tonumber(lul_settings.newLoadlevelTarget))
+               tcplighting_gateway_poll (gateway)
+			   update_device_settings (gateway, did, lul_device)
+           end
+           return true
+	   end
+
+
+       -- ---------------------------------------------------------------------
+       -- Flips switch on/off
+       -- ---------------------------------------------------------------------
+       local function set_target (lul_device, lul_settings)
+	       local device_number = tonumber(lul_device)
+           local gateway_ip = luup.devices[device_number].ip
+           local did = luup.devices[device_number].id
+		   local gateway = get_gateway (gateway_ip)
+		   if gateway.initialized and did then
+               tcplighting_set_target (gateway, did, tonumber(lul_settings.newTargetValue))
+               tcplighting_gateway_poll (gateway)
+			   update_device_settings (gateway, did, lul_device)
+           end
+           return true
+	   end
+
+
+       -- ---------------------------------------------------------------------
+       -- Plugin startup
+       -- Will initialize the gateway, which in turn creates child devices
+       -- for each light as necessary
+       -- ---------------------------------------------------------------------
+      function tcplighting_plugin_startup (lul_device)
+
+		  luup.log ("TCPLighting: tcplighting_plugin_startup - start")
+
+	      local device_number = tonumber(lul_device)
+          local device_type = luup.devices[device_number].device_type
+
+	      if device_type == GATEWAY_DEVICE_TYPE then
+		     synchronize_gateway (lul_device)
+          else
+             luup.log ("TCPLighting: tcplighting_plugin_startup - not gateway")
+		  end
+
+		  luup.log ("TCPLighting: tcplighting_plugin_startup - end")
+
+	  end
+
+
+  </functions>
+
+  <startup>tcplighting_plugin_startup</startup>
+
+  <files>L_TCPLighting1.lua</files>
+
+  <actionList>
+
+    <action>
+      <serviceId>urn:upnp-org:serviceId:SwitchPower1</serviceId>
+      <name>SetTarget</name>
+      <run>return set_target (lul_device, lul_settings)</run>
+    </action>
+
+    <action>
+      <serviceId>urn:upnp-org:serviceId:Dimming1</serviceId>
+      <name>SetLoadLevelTarget</name>
+      <run>return set_load_level_target (lul_device, lul_settings)</run>
+    </action>
+
+    <action>
+      <serviceId>urn:pmcode-org:serviceId:TCPLightingGateway1</serviceId>
+      <name>Synchronize</name>
+      <run>return synchronize_gateway (lul_device)</run>
+    </action>
+
+	<action>
+      <serviceId>urn:micasaverde-com:serviceId:HaDevice1</serviceId>
+      <name>Poll</name>
+      <run>return synchronize_gateway (lul_device)</run>
+    </action>
+
+   </actionList>
+
+</implementation>
+
Index: /branches/v1.x/L_TCPLighting1.lua
===================================================================
--- /branches/v1.x/L_TCPLighting1.lua	(revision 30)
+++ /branches/v1.x/L_TCPLighting1.lua	(revision 30)
@@ -0,0 +1,303 @@
+-- ----------------------------------------------------------------------------
+-- Search for a Connected by TCP gateway via SSDP
+-- ----------------------------------------------------------------------------
+function tcplighting_gateway_ssdp_search ()
+
+    -- Create an SSDP request - note that the Connected By TCP gateways
+    -- do not appear to respond to ssdp:all requests, so we'll directly
+    -- target the Greenwave Reality gateways that TCPi uses
+    -- See: http://upnp.org/specs/arch/UPnP-arch-DeviceArchitecture-v1.1.pdf
+    local ssdp_headers =
+        {
+        [1] = "M-SEARCH * HTTP/1.1\r\n",
+        [2] = "HOST: 239.255.255.250:1900\r\n",
+        [3] = "MAN: \"ssdp:discover\"\r\n",
+        [4] = "MX: 3\r\n",
+        [5] = "ST: urn:greenwavereality-com:service:gop:1\r\n\r\n"
+        }
+    local ssdp_request = table.concat (ssdp_headers )
+
+    -- Get a UDP socket
+    local http = require("socket.http")
+    local udp = socket.udp()
+
+     -- Send out the SSDP request.  Don't wait forever, they normally respond quickly or not at all.
+    udp:sendto (ssdp_request, "239.255.255.250", 1900)
+    udp:settimeout (3)
+
+    -- Loop over response packets until we get and IP or there is no more data
+    local gateway_ip   = nil
+	local gateway_uuid = nil
+
+    repeat
+        local received_datagram = udp:receive()
+        if received_datagram then
+           -- pull the gateway IP out of the LOCATION header in the response
+           gateway_ip   = string.match (received_datagram, "LOCATION:[%s+]http://([%d%.]+)")
+           -- pull the gateway UID out of the USN header
+           gateway_uuid = string.match (received_datagram, "USN:[%s+]uuid:(%d+)")
+        else
+           break
+        end
+    until (gateway_ip)
+
+    -- return the IP address & UUID of the discovered gateway (or nil if we are SOL)
+    return gateway_ip, gateway_uuid
+
+end
+
+
+-- ----------------------------------------------------------------------------
+-- Implements urn:upnp-org:serviceId:SwitchPower1.SetTarget
+-- ----------------------------------------------------------------------------
+function tcplighting_set_target (gateway, did, target_value)
+
+    local http = require("socket.http")
+
+    http.TIMEOUT = gateway.HTTP_TIMEOUT
+
+    return http.request("http://" .. gateway.devices[did].gateway_ip
+                        .. "/gwr/gop.php",
+                        "cmd=DeviceSendCommand&data="
+                        .. "<gip>"
+                        .. "<version>1</version>"
+                        .. "<token>1234567890</token>"
+                        .. "<did>" .. did .. "</did>"
+                        .. "<value>" .. target_value .. "</value>"
+                        .. "</gip>"
+                        .. "&fmt=xml")
+end
+
+-- ----------------------------------------------------------------------------
+-- Implements urn:upnp-org:serviceId:Dimming1.SetLoadLevelTarget
+-- ----------------------------------------------------------------------------
+function tcplighting_set_load_level_target (gateway, did, load_percent)
+
+    local http = require("socket.http")
+
+    http.TIMEOUT = gateway.HTTP_TIMEOUT
+
+    -- make sure light is on/off to match request
+    if load_percent > 0 then
+        tcplighting_set_target (gateway, did, 1)
+    else
+        tcplighting_set_target (gateway, did, 0)
+    end
+
+    -- change the dimming level
+    return http.request("http://" .. gateway.devices[did].gateway_ip
+                        .. "/gwr/gop.php",
+                        "cmd=DeviceSendCommand&data="
+                        .. "<gip>"
+                        .. "<version>1</version>"
+                        .. "<token>1234567890</token>"
+                        .. "<did>" .. did .. "</did>"
+                        .. "<value>" .. load_percent .. "</value>"
+                        .. "<type>level</type>"
+                        .. "</gip>"
+                        .. "&fmt=xml")
+end
+
+
+-- ----------------------------------------------------------------------------
+-- Loads the gateway configuration
+-- ----------------------------------------------------------------------------
+function tcplighting_gateway_init (gateway_ip)
+
+    local MAX_SSDP_RETRIES = 5
+
+    -- create an empty gateway object
+    local gateway = {}
+    gateway.initialized    = false
+	gateway.autodiscovered = false
+    gateway.error_text     = "TCP Lighting Gateway not yet initialized"  -- error text for last failed operation
+    gateway.gateway_ip     = nil
+    gateway.gateway_uuid   = nil
+    gateway.devices        = {}    -- a cache of device info indexed by unique did
+    gateway.HTTP_TIMEOUT   = 5     -- 5 seconds should be fine on a local network
+
+	-- Attempt auto-discovery via SSDP if IP not specified
+    if (gateway_ip == nil) or (string.len(gateway_ip) == 0) then
+        -- since UDP doesn't provide a reliable transport, try the SSDP request few times if necessary
+        for i = 1, MAX_SSDP_RETRIES do
+           gateway.gateway_ip, gateway.gateway_uuid = tcplighting_gateway_ssdp_search ()
+           if gateway.gateway_ip then
+		      gateway.autodiscovered = true
+		      break
+		   end
+        end
+	else
+	    -- used specified IP
+	    gateway.gateway_ip = gateway_ip
+	end
+
+    -- Bail if we still don't have an IP
+    if not gateway.gateway_ip then
+       gateway.error_text = "No gateway IP address specified, autodiscovery failed"
+       return gateway
+    end
+
+    -- Get the gateway's configuration data
+    tcplighting_gateway_poll (gateway)
+
+    return gateway
+
+end
+
+
+-- ----------------------------------------------------------------------------
+-- Get current values from the gateway
+-- ----------------------------------------------------------------------------
+function tcplighting_gateway_poll (gateway)
+
+    -- Get the gateway's configuration data
+    local result, status = tcplighting_get_gateway_config (gateway)
+
+    -- Check the HTTP status
+    if status ~= 200 then
+       gateway.error_text = "HTTP gateway query request to " .. gateway.gateway_ip .. " failed"
+       if status then gateway.error_text = gateway.error_text .. " - " .. status     end
+       if result then gateway.error_text = gateway.error_text .. " - " .. result     end
+       return
+    end
+
+    -- Make sure something came back
+    if not result then
+       gateway.error_text = "HTTP gateway query request to " .. gateway.gateway_ip .. " did not return any data"
+       return
+    end
+
+    -- Only parse if the HTTP request was successful
+    tcplighting_parse_gateway_config(gateway, result)
+    return
+
+end
+
+
+-- ----------------------------------------------------------------------------
+-- Queries a Connected by TCP Gateway to determine which lights are available
+-- ----------------------------------------------------------------------------
+function tcplighting_get_gateway_config (gateway)
+
+    local http = require("socket.http")
+
+    http.TIMEOUT = gateway.HTTP_TIMEOUT
+
+    return http.request("http://" .. gateway.gateway_ip
+                        .. "/gwr/gop.php",
+                        "cmd=GWRBatch&data="
+                        .. "<gwrcmds>"
+                        .. "<gwrcmd>"
+                        .. "<gcmd>RoomGetCarousel</gcmd>"
+                        .. "<gdata>"
+                        .. "<gip>"
+                        .. "<version>1</version>"
+                        .. "<token>1234567890</token>"
+                        .. "<fields>name,image,imageurl,control,power,product,class,realtype,status</fields>"
+                        .. "</gip>"
+                        .. "</gdata>"
+                        .. "</gwrcmd>"
+                        .. "</gwrcmds>"
+                        .. "&fmt=xml")
+end
+
+
+-- ----------------------------------------------------------------------------
+-- Parse the gateway configuration
+-- ----------------------------------------------------------------------------
+function tcplighting_parse_gateway_config (gateway, config_xml)
+
+    local lxp = require("lxp")
+
+    -- Local collection devices - will be added to master table after parsing
+    local new_devices = {}
+
+    -- enumerate the XML elements that we want to load into our tables
+    local room_elements_of_interest = "|rid|name|desc|"
+    local device_elements_of_interest = "|did|name|desc|state|level|offline|type|rangemin|rangemax|power|poweravg|energy|manufacturer|prodbrand|prodmodel|prodtype|capability|"
+
+    -- used to keep track of the current context during the parsing process
+    local current_room_object = nil
+    local current_device_object = nil
+    local current_element_name = nil
+
+    -- XML parsing handler for the start of an element
+    local function start_element_handler (parser, name)
+        current_element_name = name
+        if name == "room" then
+            current_room_object = {}
+        elseif name == "device" then
+            current_device_object = {}
+            current_device_object["gateway_ip"] = gateway.gateway_ip
+            current_device_object["room"] = current_room_object
+        end
+    end
+
+    -- XML parsing handler for the end of an element
+    local function end_element_handler (parser, name)
+        current_element_name = name
+        if name == "room" then
+            current_room_object = nil
+        elseif name == "device" then
+             new_devices[#new_devices+1] = current_device_object
+            current_device_object = nil
+        end
+    end
+
+    -- XML parsing handler for the text of an element
+    local function character_handler (parser, value)
+        if current_device_object and string.find(device_elements_of_interest,"|"..current_element_name.."|") then
+            current_device_object[current_element_name] = value
+        elseif current_room_object and (not current_device_object) and string.find(room_elements_of_interest,"|"..current_element_name.."|") then
+            current_room_object[current_element_name] = value
+        end
+    end
+
+    -- Create a parser
+    local callbacks = {StartElement = start_element_handler, EndElement = end_element_handler, CharacterData = character_handler}
+    local parser = lxp.new(callbacks)
+
+    -- Parse the XML
+    parser:parse(config_xml)
+    parser:parse() -- finishes the document
+    parser:close()
+
+    -- Save the devices, uniquely indexed by did
+    if #new_devices > 0 then
+        for i,device in ipairs(new_devices) do
+            gateway.devices[device.did] = device
+            -- tweak for interpretation of load level for dimmers - always set to zero if device is off
+            if device.state == "0" then
+               device.level = "0"
+            end
+        end
+        gateway.initialized = true
+        gateway.error_text = nil
+    else
+        gateway.error_text = "No devices found at gateway " .. gateway.gateway_ip .. " while parsing " .. config_xml
+    end
+
+    return
+
+end
+
+-- ----------------------------------------------------------------------------
+-- Helper functions for testing - call devices by name/room
+-- ----------------------------------------------------------------------------
+function tcplighting_get_did (gateway, name, roomname)
+   local did = nil
+   for key,device in pairs(gateway.devices) do
+      if device.name == name and device.room.name == roomname then
+         did = device.did
+      end
+   end
+   return did
+end
+
+function tcplighting_set_target_by_name  (gateway, name, roomname, target_value)
+    return tcplighting_set_target(gateway, tcplighting_get_did (gateway, name, roomname), target_value)
+end
+
+function tcplighting_set_load_level_target_by_name (gateway, name, roomname, load_percent)
+    return tcplighting_set_load_level_target(gateway, tcplighting_get_did (gateway, name, roomname), load_percent)
+end
Index: /branches/v1.x/test-ssdp.lua
===================================================================
--- /branches/v1.x/test-ssdp.lua	(revision 30)
+++ /branches/v1.x/test-ssdp.lua	(revision 30)
@@ -0,0 +1,43 @@
+-- Event handling sequence:
+--
+-- 1. Is  IP specified?
+--     1a. If No IP: trigger auto-discovery
+--     1b. If auto discovery failed set error text and bail
+--     1c. Set IP and set auto-discovered=true
+--
+--
+--
+--
+--
+--
+--
+--
+--
+--
+--
+
+
+
+require ("L_TCPLighting1")
+
+
+    local MAX_SSDP_RETRIES = 5
+
+
+    function discover_gateway ()
+        local gateway_ip   = null
+        local gateway_uuid = null
+        -- since UDP doesn't provide a reliable transport, try the SSDP request few times if necessary
+        for i = 1, MAX_SSDP_RETRIES do
+           gateway_ip, gateway_uuid = tcplighting_gateway_ssdp_search ()
+           if gateway_ip then break end
+        end
+        return gateway_ip, gateway_uuid
+    end
+
+
+    local start_clock = os.clock()
+    print (discover_gateway ())
+    local end_clock = os.clock()
+
+    print ((end_clock-start_clock)*1000, " ms")
Index: /branches/v1.x/D_TCPLightingGateway1.json
===================================================================
--- /branches/v1.x/D_TCPLightingGateway1.json	(revision 30)
+++ /branches/v1.x/D_TCPLightingGateway1.json	(revision 30)
@@ -0,0 +1,95 @@
+{
+    "flashicon":      "icons/generic_sensor.swf",
+    "imgIconBody":     "",
+    "imgIconDimmable": "",
+    "imgIconTurnable": "",
+    "imgIconMin":      "",
+    "imgIconMax":      "",
+    "halloIconsDir":   "pics/hallo",
+    "inScene":         "0",
+    "DisplayStatus":   {},
+    "doc_url":
+       {
+       "doc_language": 1,
+	   "doc_manual":   1,
+       "doc_version":  1,
+       "doc_platform": 0,
+       "doc_page":     "lighting_devices"
+       },
+    "Tabs":
+	   [
+
+       {
+       "Label":    {"lang_tag": "tcp_lighting_gateway", "text": "Gateway"},
+       "Position": "0",
+       "TabType":  "flash",
+	   "SceneGroup":
+	        [
+		    {
+			"id": "1",
+			"top": "1",
+			"left": "0",
+			"x": "1",
+			"y": "1"
+		    },
+		    {
+			"id": "2",
+			"top": "2",
+			"left": "0",
+			"x": "1",
+			"y": "2"
+		    }
+		    ],
+		"ControlGroup": [
+			{
+			"id": "1",
+			"scenegroup": "1",
+			"isSingle": "1"
+			},
+			{
+			"id": "2",
+			"scenegroup": "2",
+			"type": "info"
+			}
+		],
+       "Control":
+		    [
+            {
+            "ControlType": "label",
+            "Label":       {"lang_tag": "synchronize_desc","text": "Sync to the gateway and configure lights - Gateway ip must be specified in Advanced tab"},
+            "Display":     {"Top": 20,"Left": 50,"Width": 200,"Height": 20}
+            },
+            {
+            "ControlGroup": "1",
+            "ControlType":  "button",
+            "Label":       {"lang_tag": "synchronize_command","text": "Sync"},
+			"Display":     {"Top": 37,"Left": 50, "Width": 100, "Height": 20 },
+            "Command":     {"Service": "urn:pmcode-org:serviceId:TCPLightingGateway1", "Action": "Synchronize", "Parameters": []}
+			},
+            {
+            "ControlType": "label",
+            "Label":       {"lang_tag": "error_text_label","text": "Gateway Errors:"},
+			"Display":     {"Top": 80,"Left": 50,"Width": 200, "Height": 20}
+			},
+            {
+            "ControlGroup": "2",
+            "ControlType": "variable",
+            "Display":     {"Top": 95, "Left": 70, "Width": 200, "Height": 20,
+			                "Service": "urn:pmcode-org:serviceId:TCPLightingGateway1", "Variable": "ErrorText"}
+			}
+            ]
+        },
+
+        {
+        "Label":      {"lang_tag": "advanced","text": "Advanced"},
+        "Position":   "1",
+        "TabType":    "javascript",
+        "ScriptName": "shared.js",
+        "Function":   "advanced_device"
+        }
+    ],
+    "eventList":   {},
+    "eventList2":  [],
+    "DeviceType":  "urn:schemas-pmcode-org:service:TCPLightingGateway:1",
+    "device_type": "urn:schemas-pmcode-org:service:TCPLightingGateway:1"
+}
Index: /tags/v1.0.1/S_TCPLightingGateway1.xml
===================================================================
--- /tags/v1.0.1/S_TCPLightingGateway1.xml	(revision 30)
+++ /tags/v1.0.1/S_TCPLightingGateway1.xml	(revision 30)
@@ -0,0 +1,27 @@
+<?xml version="1.0"?>
+<scpd xmlns="urn:schemas-upnp-org:service-1-0">
+  <specVersion>
+    <major>1</major>
+    <minor>0</minor>
+  </specVersion>
+  <serviceStateTable>
+    <stateVariable sendEvents="no">
+      <name>AutoDiscovered</name>
+      <dataType>boolean</dataType>
+      <defaultValue>0</defaultValue>
+    </stateVariable>
+    <stateVariable sendEvents="yes">
+      <name>ErrorText</name>
+      <dataType>string</dataType>
+    </stateVariable>
+    <stateVariable sendEvents="no">
+      <name>OfflineDeviceNameFormat</name>
+      <dataType>string</dataType>
+    </stateVariable>
+  </serviceStateTable>
+  <actionList>
+    <action>
+      <name>Synchronize</name>
+    </action>
+  </actionList>
+</scpd>
Index: /tags/v1.0.1/D_TCPLightingGateway1.xml
===================================================================
--- /tags/v1.0.1/D_TCPLightingGateway1.xml	(revision 30)
+++ /tags/v1.0.1/D_TCPLightingGateway1.xml	(revision 30)
@@ -0,0 +1,26 @@
+<?xml version="1.0"?>
+<root xmlns="urn:schemas-upnp-org:device-1-0">
+  <specVersion>
+    <major>1</major>
+    <minor>0</minor>
+  </specVersion>
+  <device>
+    <deviceType>urn:schemas-pmcode-org:service:TCPLightingGateway:1</deviceType>
+    <staticJson>D_TCPLightingGateway1.json</staticJson>
+    <serviceList>
+      <service>
+        <serviceType>urn:schemas-pmcode-org:service:TCPLightingGateway:1</serviceType>
+        <serviceId>urn:pmcode-org:serviceId:TCPLightingGateway1</serviceId>
+        <SCPDURL>S_TCPLightingGateway1.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>
+    <implementationList>
+      <implementationFile>I_TCPLighting1.xml</implementationFile>
+    </implementationList>
+  </device>
+</root>
Index: /tags/v1.0.1/D_TCPLighting1.xml
===================================================================
--- /tags/v1.0.1/D_TCPLighting1.xml	(revision 30)
+++ /tags/v1.0.1/D_TCPLighting1.xml	(revision 30)
@@ -0,0 +1,31 @@
+<?xml version="1.0"?>
+<root xmlns="urn:schemas-upnp-org:device-1-0">
+  <specVersion>
+    <major>1</major>
+    <minor>0</minor>
+  </specVersion>
+  <device>
+    <deviceType>urn:schemas-upnp-org:device:DimmableLight:1</deviceType>
+    <staticJson>D_DimmableLight1.json</staticJson>
+    <serviceList>
+      <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-upnp-org:service:Dimming:1</serviceType>
+        <serviceId>urn:upnp-org:serviceId:Dimming1</serviceId>
+        <SCPDURL>S_Dimming1.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>
+    <implementationList>
+      <implementationFile>I_TCPLighting1.xml</implementationFile>
+    </implementationList>
+  </device>
+</root>
Index: /tags/v1.0.1/test.lua
===================================================================
--- /tags/v1.0.1/test.lua	(revision 30)
+++ /tags/v1.0.1/test.lua	(revision 30)
@@ -0,0 +1,25 @@
+local tcplighting = require("L_TCPLighting1")
+local json = require ("json")
+
+
+-- initialize gateway
+gateway = tcplighting_gateway_init(null) --
+print (json.encode(gateway))
+
+
+-- modify a light's intensity
+if (gateway.initialized) then
+    -- generate a random intensity
+    math.randomseed(os.time())
+    math.random(); math.random(); math.random()
+    local load_level_target = math.random(100)
+    --  call the gateway
+    tcplighting_set_load_level_target_by_name(gateway, "Front Entry Light", "Front Entry", load_level_target)
+    tcplighting_set_target_by_name(gateway, "Front Entry Light", "Front Entry", 1)
+    -- poll to get latest status
+    tcplighting_gateway_poll(gateway)
+    print (json.encode(gateway.devices))
+else
+    print ("--- ERROR: " .. gateway.error_text)
+end
+
Index: /tags/v1.0.1/I_TCPLighting1.xml
===================================================================
--- /tags/v1.0.1/I_TCPLighting1.xml	(revision 30)
+++ /tags/v1.0.1/I_TCPLighting1.xml	(revision 30)
@@ -0,0 +1,459 @@
+<?xml version="1.0"?>
+<implementation>
+  <functions>
+
+       -- ---------------------------------------------------------------------
+       -- Constants
+       -- ---------------------------------------------------------------------
+       local GATEWAY_DEVICE_TYPE = "urn:schemas-pmcode-org:service:TCPLightingGateway:1"
+       local GATEWAY_SERVICE_ID  = "urn:pmcode-org:serviceId:TCPLightingGateway1"
+       local HA_DEVICE_TYPE      = "urn:schemas-micasaverde-com:service:HaDevice:1"
+       local HA_SERVICE_ID       = "urn:micasaverde-com:serviceId:HaDevice1"
+       local DIMMER_DEVICE_TYPE  = "urn:schemas-upnp-org:device:DimmableLight:1"
+       local DIMMER_SERVICE_ID   = "urn:upnp-org:serviceId:Dimming1"
+       local SWITCH_DEVICE_TYPE  = "urn:schemas-upnp-org:device:SwitchPower:1"
+       local SWITCH_SERVICE_ID   = "urn:upnp-org:serviceId:SwitchPower1"
+       local LIGHT_DEVICE_FILE   = "D_TCPLighting1.xml"
+       
+       
+       -- ---------------------------------------------------------------------
+       -- Pseudo-Constants: Can be overridden by parent device variables
+       -- ---------------------------------------------------------------------
+       local OFFLINE_DEVICE_NAME_FORMAT = "[Offline] %s"
+
+
+       -- ---------------------------------------------------------------------
+       -- table representing the gateway and its devices
+       -- ---------------------------------------------------------------------
+       local tcplighting_gateway = nil
+
+
+       -- ---------------------------------------------------------------------
+       -- Get a reference to the gateway (will init if necessary)
+       -- ---------------------------------------------------------------------
+	   local function get_gateway (gateway_ip)
+		  if (not tcplighting_gateway) or (not gateway_ip) or (gateway_ip == "") then
+             luup.log ("TCPLighting:  get_gateway - calling tcplighting_gateway_init,  IP=" .. tostring(gateway_ip))
+			 tcplighting_gateway = tcplighting_gateway_init (gateway_ip)
+			 if tcplighting_gateway.error_text then
+				luup.task(tcplighting_gateway.error_text, 2, "TCPLighting", -1)
+			 end
+		  end
+		  return tcplighting_gateway
+	   end
+
+
+       -- ---------------------------------------------------------------------
+       -- Clear the gateway object
+       -- ---------------------------------------------------------------------
+	   local function reset_gateway ()
+		  tcplighting_gateway = nil
+		  return
+	   end
+
+
+       -- ---------------------------------------------------------------------
+       -- Returns true if the device is offline
+       -- ---------------------------------------------------------------------
+       local function is_offline (device)
+           return ((device == nil) or (device.offline and device.offline == "1"))
+       end
+
+
+       -- ---------------------------------------------------------------------
+       -- Returns true if the device has dimming capabilities
+       -- ---------------------------------------------------------------------
+       local function is_dimmer_switch (device)
+           return (device and device.capability and string.find(device.capability,"switch_multilevel"))
+       end
+
+
+       -- ---------------------------------------------------------------------
+       -- Returns true if the device has on/off capabilities
+       -- ---------------------------------------------------------------------
+       local function is_binary_switch (device)
+           return (device and device.capability and string.find(device.capability,"switch_binary"))
+       end
+
+
+       -- ---------------------------------------------------------------------
+       -- See if we have a matching room number
+       -- ---------------------------------------------------------------------
+       local function room_number (input_room_name)
+          local room_num = 0
+          if input_room_name then
+              for i, room_name in ipairs(luup.rooms) do
+                  if room_name == input_room_name then
+                      room_num = i
+                      break
+                  end
+              end
+          end
+          return room_num
+       end
+
+
+       -- ---------------------------------------------------------------------
+       -- Sets a LUUP device variable, but only if it has no current value
+       -- ---------------------------------------------------------------------
+       local function initialize_variable (service_id, variable_name, new_value, device_number)
+          local current_value = luup.variable_get (service_id, variable_name, device_number)
+          if (not current_value) or (current_value == "") then
+              luup.variable_set (service_id, variable_name, new_value, device_number)
+          end
+       end
+
+
+       -- ---------------------------------------------------------------------
+       -- Sets a LUUP device attribute, but only if it has no current value
+       -- ---------------------------------------------------------------------
+       local function initialize_attr (attr_name, new_value, device_number)
+          local current_value = luup.attr_get (attr_name, device_number)
+          if (not current_value) or (current_value == "") then
+              luup.attr_set (attr_name, new_value, device_number)
+          end
+       end
+
+
+       -- ---------------------------------------------------------------------
+       -- Updates a LUUP device variable, but only if its value has changed
+       -- ---------------------------------------------------------------------
+       local function update_variable (service_id, variable_name, new_value, device_number)
+          local current_value = luup.variable_get (service_id, variable_name, device_number)
+          if (not current_value) or (new_value ~= current_value) then
+              luup.variable_set (service_id, variable_name, new_value, device_number)
+          end
+       end
+
+
+       -- ---------------------------------------------------------------------
+       -- Updates a LUUP device attribute, but only if its value has changed
+       -- ---------------------------------------------------------------------
+       local function update_attr (attr_name, new_value, device_number)
+          local current_value = luup.attr_get (attr_name, device_number)
+          if (not current_value) or (new_value ~= current_value) then
+              luup.attr_set (attr_name, new_value, device_number)
+          end
+       end
+
+
+       -- ---------------------------------------------------------------------
+       -- Update a device to match info from gateway
+       -- ---------------------------------------------------------------------
+       local function update_device_settings (gateway, did, lul_device)
+
+           if not (gateway and did and lul_device) then
+              return
+           end
+
+           local device_number = tonumber(lul_device)
+           local device = gateway.devices[did]
+           
+		   -- indicate device is offline if we don't have data for it 
+           update_attr ("invisible", "", device_number) -- legacy code: prior versions made offline devices invisible
+		   if is_offline(device) then
+		       luup.log ("TCPLighting:  update_device_settings - device with id=" .. lul_device .. " is offline or has no data from gateway")
+		       update_attr ("name", string.format(OFFLINE_DEVICE_NAME_FORMAT, device.name), device_number)
+			   return
+		   else
+		       update_attr ("name", device.name, device_number)
+		   end
+
+           -- update the device
+		   update_attr ("manufacturer", device.manufacturer, device_number)
+		   update_attr ("model",        device.prodmodel,    device_number)
+
+           if device and device.room and device.room.name then
+               local room_num = room_number(device.room.name)
+               if room_num and room_num ~= 0 then
+                  local room_num_string = tostring(room_num)
+                  update_attr ("room_num", room_num_string, device_number)
+                  update_attr ("room",     room_num_string, device_number)
+               else
+                  luup.log("TCPLighting:  update_device_settings - unknown room " .. device.room.name)
+               end
+           end
+
+           if device and is_binary_switch(device) then
+		       update_variable (SWITCH_SERVICE_ID, "Status", device.state, device_number)
+		       update_variable (SWITCH_SERVICE_ID, "Target", device.state, device_number)
+           end
+
+           if device and is_dimmer_switch(device) then
+		      update_variable (DIMMER_SERVICE_ID, "LoadLevelStatus", device.level, device_number)
+		      update_variable (DIMMER_SERVICE_ID, "LoadLevelTarget", device.level, device_number)
+           end
+
+           return
+
+       end
+
+
+       -- ---------------------------------------------------------------------
+       -- Update all of the children of the specified device
+       -- ---------------------------------------------------------------------
+       local function sync_child_devices (gateway, parent_lul_device)
+
+           if not (gateway and parent_lul_device) then
+              return
+           end
+
+           -- poll the gateway for most current device data
+           tcplighting_gateway_poll(gateway)
+
+           -- find matching child devices
+           local parent_device_number = tonumber (parent_lul_device)
+           for k, luup_device in pairs(luup.devices) do
+               if (luup_device.device_num_parent == parent_device_number) and (luup_device.id) then
+                    luup.log ("TCPLighting:  sync_child_devices for " .. parent_lul_device .. " - updating child " .. k .. " " .. luup_device.description)
+                    update_device_settings (gateway, luup_device.id, k)
+               end
+           end
+
+           return
+
+       end
+
+
+       -- ---------------------------------------------------------------------
+       -- Create child devices of gateway if necessary
+       -- ---------------------------------------------------------------------
+       local function create_child_devices (gateway, parent_lul_device)
+
+           if gateway and gateway.initialized then
+
+              luup.log ("TCPLighting:  create_child_devices - gateway is initialized")
+
+			  local parent_device_number = tonumber (parent_lul_device)
+
+              -- Build up a table of child devices, indexed by did, with value being the device type
+			  local child_device_map = {}
+
+			  -- find existing children
+              for k, luup_device in pairs(luup.devices) do
+                  if (luup_device.device_num_parent == parent_device_number) and (luup_device.id) and (luup_device.device_type) then
+					  local did = luup.attr_get ("altid", k)
+					  if (did) then
+				         child_device_map[did] = {name=luup_device.description, device_type = luup_device.device_type}
+					  end
+                  end
+			  end
+
+              -- see if we have new children
+			  local new_devices_found = false
+              for did,gateway_device in pairs(gateway.devices) do
+				  if child_device_map[gateway_device.did] == nil then
+                     if is_dimmer_switch (gateway_device) then
+				        child_device_map[gateway_device.did] = {name=gateway_device.name, device_type=DIMMER_DEVICE_TYPE}
+						new_devices_found = true
+                     elseif is_binary_switch (gateway_device) then
+				        child_device_map[gateway_device.did] = {name=gateway_device.name, device_type=SWITCH_DEVICE_TYPE}
+						new_devices_found = true
+                     end
+				  end
+              end
+
+			  -- resync the combined list of child devices if new ones were found
+              if new_devices_found then
+			     local chdev_context = luup.chdev.start (parent_device_number)
+				 for did,device in pairs(child_device_map) do
+                     luup.log ("TCPLighting:  create_child_devices - creating child with did=" .. did .. ", name=" .. device.name)
+                     luup.chdev.append (parent_device_number, chdev_context, did, device.name, device.device_type, LIGHT_DEVICE_FILE,"","",false)
+                 end
+                 luup.chdev.sync (parent_device_number, chdev_context)
+			  else
+		         luup.log ("TCPLighting:  create_child_devices - no new child devices")
+              end
+
+           end
+
+       end
+
+
+       -- ---------------------------------------------------------------------
+       -- Schedule the next poll
+       -- ---------------------------------------------------------------------
+	   local function schedule_next_poll (lul_device)
+
+           local device_number = tonumber(lul_device)
+
+		   -- make sure the polling variables have values
+		   initialize_variable (HA_SERVICE_ID, "PollingEnabled", "1",  device_number)
+		   initialize_variable (HA_SERVICE_ID, "PollMinDelay",   "60", device_number)
+
+           -- get current polling settings (may be different from above if overriden by user)
+		   local polling_enabled  = luup.variable_get (HA_SERVICE_ID, "PollingEnabled", device_number)
+		   local polling_interval = luup.variable_get (HA_SERVICE_ID, "PollMinDelay",   device_number)
+
+		   -- make a delayed call to the synchronization functions
+		   if polling_enabled == "true" or polling_enabled == "1" then
+		      luup.call_timer("synchronize_gateway", 1, tonumber(polling_interval), "", lul_device)
+			  luup.log ("TCPLighting:  schedule_next_poll - timer set")
+		   end
+
+	   end
+
+
+       -- ---------------------------------------------------------------------
+       -- Contact the gateway and initialize the gateway object
+       -- ---------------------------------------------------------------------
+	   function synchronize_gateway (lul_device)
+
+		   luup.log ("TCPLighting: synchronize_gateway - start")
+
+           local device_number = tonumber(lul_device)
+           local gateway_ip = luup.attr_get ("ip", device_number)
+
+           -- will create and populate a new gateway configuration object if necessary
+           local gateway = get_gateway (gateway_ip)
+
+		   if (gateway.error_text == nil) or (gateway.error_text == "") then
+		   
+               -- Sync up gateway properties
+			   initialize_attr ("name",  "TCP Lighting Gateway", device_number)
+			   if gateway.gateway_uuid then
+			      initialize_attr ("altid", gateway.gateway_uuid,  device_number)
+			   end
+               update_variable (GATEWAY_SERVICE_ID, "ErrorText", "", device_number)
+			   if gateway.autodiscovered then
+				  -- Use IP address from autodiscovery
+		          update_attr     ("ip", gateway.gateway_ip, device_number)
+				  update_variable (GATEWAY_SERVICE_ID, "AutoDiscovered", "1", device_number)
+			   end
+
+			   -- check to see if prefix for offline devices has been overridden
+			   --   some of this code looks redundant, but is required  
+			   --   in case previous versions of plugin were installed
+			   initialize_variable (GATEWAY_SERVICE_ID, "OfflineDeviceNameFormat", OFFLINE_DEVICE_NAME_FORMAT, device_number) 
+			   local offline_device_name_format_override = luup.variable_get (GATEWAY_SERVICE_ID, "OfflineDeviceNameFormat", device_number)
+			   if offline_device_name_format_override then
+			       OFFLINE_DEVICE_NAME_FORMAT = offline_device_name_format_override
+			   end
+
+               -- create and synchronize all children
+		       create_child_devices (gateway, lul_device)
+               sync_child_devices (gateway, lul_device)
+
+		   else
+
+		       -- flag that there was an error
+		       luup.log ("TCPLighting:  synchronize_gateway - error: " .. gateway.error_text)
+               update_variable (GATEWAY_SERVICE_ID, "ErrorText", gateway.error_text, device_number)
+
+               -- clear autodiscovered IP on failures
+               local previously_autodiscovered = luup.variable_get  (GATEWAY_SERVICE_ID, "AutoDiscovered", device_number)
+			   if previously_autodiscovered and previously_autodiscovered == "1" then
+				  -- Clear IP address from autodiscovery
+		          update_attr     ("ip", "", device_number)
+				  update_variable (GATEWAY_SERVICE_ID, "AutoDiscovered", "0", device_number)
+		          luup.log ("TCPLighting:  synchronize_gateway - cleared previously autodiscovered IP = " .. tostring(gateway_ip))
+			   end
+
+		       -- reset the gateway object
+			   reset_gateway ()
+
+           end
+
+		   -- set up polling so that changes made externally to the plugin show up in the UI
+		   schedule_next_poll (lul_device)
+
+		   luup.log ("TCPLighting: synchronize_gateway - end")
+
+           return true
+
+	   end
+
+
+       -- ---------------------------------------------------------------------
+       -- Set the dimming level on a light
+       -- ---------------------------------------------------------------------
+       local function set_load_level_target (lul_device, lul_settings)
+	       local device_number = tonumber(lul_device)
+           local gateway_ip = luup.devices[device_number].ip
+           local did = luup.devices[device_number].id
+		   local gateway = get_gateway (gateway_ip)
+		   if gateway.initialized and did then
+               tcplighting_set_load_level_target (gateway, did, tonumber(lul_settings.newLoadlevelTarget))
+               tcplighting_gateway_poll (gateway)
+			   update_device_settings (gateway, did, lul_device)
+           end
+           return true
+	   end
+
+
+       -- ---------------------------------------------------------------------
+       -- Flips switch on/off
+       -- ---------------------------------------------------------------------
+       local function set_target (lul_device, lul_settings)
+	       local device_number = tonumber(lul_device)
+           local gateway_ip = luup.devices[device_number].ip
+           local did = luup.devices[device_number].id
+		   local gateway = get_gateway (gateway_ip)
+		   if gateway.initialized and did then
+               tcplighting_set_target (gateway, did, tonumber(lul_settings.newTargetValue))
+               tcplighting_gateway_poll (gateway)
+			   update_device_settings (gateway, did, lul_device)
+           end
+           return true
+	   end
+
+
+       -- ---------------------------------------------------------------------
+       -- Plugin startup
+       -- Will initialize the gateway, which in turn creates child devices
+       -- for each light as necessary
+       -- ---------------------------------------------------------------------
+      function tcplighting_plugin_startup (lul_device)
+
+		  luup.log ("TCPLighting: tcplighting_plugin_startup - start")
+
+	      local device_number = tonumber(lul_device)
+          local device_type = luup.devices[device_number].device_type
+
+	      if device_type == GATEWAY_DEVICE_TYPE then
+		     synchronize_gateway (lul_device)
+          else
+             luup.log ("TCPLighting: tcplighting_plugin_startup - not gateway")
+		  end
+
+		  luup.log ("TCPLighting: tcplighting_plugin_startup - end")
+
+	  end
+
+
+  </functions>
+
+  <startup>tcplighting_plugin_startup</startup>
+
+  <files>L_TCPLighting1.lua</files>
+
+  <actionList>
+
+    <action>
+      <serviceId>urn:upnp-org:serviceId:SwitchPower1</serviceId>
+      <name>SetTarget</name>
+      <run>return set_target (lul_device, lul_settings)</run>
+    </action>
+
+    <action>
+      <serviceId>urn:upnp-org:serviceId:Dimming1</serviceId>
+      <name>SetLoadLevelTarget</name>
+      <run>return set_load_level_target (lul_device, lul_settings)</run>
+    </action>
+
+    <action>
+      <serviceId>urn:pmcode-org:serviceId:TCPLightingGateway1</serviceId>
+      <name>Synchronize</name>
+      <run>return synchronize_gateway (lul_device)</run>
+    </action>
+
+	<action>
+      <serviceId>urn:micasaverde-com:serviceId:HaDevice1</serviceId>
+      <name>Poll</name>
+      <run>return synchronize_gateway (lul_device)</run>
+    </action>
+
+   </actionList>
+
+</implementation>
+
Index: /tags/v1.0.1/L_TCPLighting1.lua
===================================================================
--- /tags/v1.0.1/L_TCPLighting1.lua	(revision 30)
+++ /tags/v1.0.1/L_TCPLighting1.lua	(revision 30)
@@ -0,0 +1,303 @@
+-- ----------------------------------------------------------------------------
+-- Search for a Connected by TCP gateway via SSDP
+-- ----------------------------------------------------------------------------
+function tcplighting_gateway_ssdp_search ()
+
+    -- Create an SSDP request - note that the Connected By TCP gateways
+    -- do not appear to respond to ssdp:all requests, so we'll directly
+    -- target the Greenwave Reality gateways that TCPi uses
+    -- See: http://upnp.org/specs/arch/UPnP-arch-DeviceArchitecture-v1.1.pdf
+    local ssdp_headers =
+        {
+        [1] = "M-SEARCH * HTTP/1.1\r\n",
+        [2] = "HOST: 239.255.255.250:1900\r\n",
+        [3] = "MAN: \"ssdp:discover\"\r\n",
+        [4] = "MX: 3\r\n",
+        [5] = "ST: urn:greenwavereality-com:service:gop:1\r\n\r\n"
+        }
+    local ssdp_request = table.concat (ssdp_headers )
+
+    -- Get a UDP socket
+    local http = require("socket.http")
+    local udp = socket.udp()
+
+     -- Send out the SSDP request.  Don't wait forever, they normally respond quickly or not at all.
+    udp:sendto (ssdp_request, "239.255.255.250", 1900)
+    udp:settimeout (3)
+
+    -- Loop over response packets until we get and IP or there is no more data
+    local gateway_ip   = nil
+	local gateway_uuid = nil
+
+    repeat
+        local received_datagram = udp:receive()
+        if received_datagram then
+           -- pull the gateway IP out of the LOCATION header in the response
+           gateway_ip   = string.match (received_datagram, "LOCATION:[%s+]http://([%d%.]+)")
+           -- pull the gateway UID out of the USN header
+           gateway_uuid = string.match (received_datagram, "USN:[%s+]uuid:(%d+)")
+        else
+           break
+        end
+    until (gateway_ip)
+
+    -- return the IP address & UUID of the discovered gateway (or nil if we are SOL)
+    return gateway_ip, gateway_uuid
+
+end
+
+
+-- ----------------------------------------------------------------------------
+-- Implements urn:upnp-org:serviceId:SwitchPower1.SetTarget
+-- ----------------------------------------------------------------------------
+function tcplighting_set_target (gateway, did, target_value)
+
+    local http = require("socket.http")
+
+    http.TIMEOUT = gateway.HTTP_TIMEOUT
+
+    return http.request("http://" .. gateway.devices[did].gateway_ip
+                        .. "/gwr/gop.php",
+                        "cmd=DeviceSendCommand&data="
+                        .. "<gip>"
+                        .. "<version>1</version>"
+                        .. "<token>1234567890</token>"
+                        .. "<did>" .. did .. "</did>"
+                        .. "<value>" .. target_value .. "</value>"
+                        .. "</gip>"
+                        .. "&fmt=xml")
+end
+
+-- ----------------------------------------------------------------------------
+-- Implements urn:upnp-org:serviceId:Dimming1.SetLoadLevelTarget
+-- ----------------------------------------------------------------------------
+function tcplighting_set_load_level_target (gateway, did, load_percent)
+
+    local http = require("socket.http")
+
+    http.TIMEOUT = gateway.HTTP_TIMEOUT
+
+    -- make sure light is on/off to match request
+    if load_percent > 0 then
+        tcplighting_set_target (gateway, did, 1)
+    else
+        tcplighting_set_target (gateway, did, 0)
+    end
+
+    -- change the dimming level
+    return http.request("http://" .. gateway.devices[did].gateway_ip
+                        .. "/gwr/gop.php",
+                        "cmd=DeviceSendCommand&data="
+                        .. "<gip>"
+                        .. "<version>1</version>"
+                        .. "<token>1234567890</token>"
+                        .. "<did>" .. did .. "</did>"
+                        .. "<value>" .. load_percent .. "</value>"
+                        .. "<type>level</type>"
+                        .. "</gip>"
+                        .. "&fmt=xml")
+end
+
+
+-- ----------------------------------------------------------------------------
+-- Loads the gateway configuration
+-- ----------------------------------------------------------------------------
+function tcplighting_gateway_init (gateway_ip)
+
+    local MAX_SSDP_RETRIES = 5
+
+    -- create an empty gateway object
+    local gateway = {}
+    gateway.initialized    = false
+	gateway.autodiscovered = false
+    gateway.error_text     = "TCP Lighting Gateway not yet initialized"  -- error text for last failed operation
+    gateway.gateway_ip     = nil
+    gateway.gateway_uuid   = nil
+    gateway.devices        = {}    -- a cache of device info indexed by unique did
+    gateway.HTTP_TIMEOUT   = 5     -- 5 seconds should be fine on a local network
+
+	-- Attempt auto-discovery via SSDP if IP not specified
+    if (gateway_ip == nil) or (string.len(gateway_ip) == 0) then
+        -- since UDP doesn't provide a reliable transport, try the SSDP request few times if necessary
+        for i = 1, MAX_SSDP_RETRIES do
+           gateway.gateway_ip, gateway.gateway_uuid = tcplighting_gateway_ssdp_search ()
+           if gateway.gateway_ip then
+		      gateway.autodiscovered = true
+		      break
+		   end
+        end
+	else
+	    -- used specified IP
+	    gateway.gateway_ip = gateway_ip
+	end
+
+    -- Bail if we still don't have an IP
+    if not gateway.gateway_ip then
+       gateway.error_text = "No gateway IP address specified, autodiscovery failed"
+       return gateway
+    end
+
+    -- Get the gateway's configuration data
+    tcplighting_gateway_poll (gateway)
+
+    return gateway
+
+end
+
+
+-- ----------------------------------------------------------------------------
+-- Get current values from the gateway
+-- ----------------------------------------------------------------------------
+function tcplighting_gateway_poll (gateway)
+
+    -- Get the gateway's configuration data
+    local result, status = tcplighting_get_gateway_config (gateway)
+
+    -- Check the HTTP status
+    if status ~= 200 then
+       gateway.error_text = "HTTP gateway query request to " .. gateway.gateway_ip .. " failed"
+       if status then gateway.error_text = gateway.error_text .. " - " .. status     end
+       if result then gateway.error_text = gateway.error_text .. " - " .. result     end
+       return
+    end
+
+    -- Make sure something came back
+    if not result then
+       gateway.error_text = "HTTP gateway query request to " .. gateway.gateway_ip .. " did not return any data"
+       return
+    end
+
+    -- Only parse if the HTTP request was successful
+    tcplighting_parse_gateway_config(gateway, result)
+    return
+
+end
+
+
+-- ----------------------------------------------------------------------------
+-- Queries a Connected by TCP Gateway to determine which lights are available
+-- ----------------------------------------------------------------------------
+function tcplighting_get_gateway_config (gateway)
+
+    local http = require("socket.http")
+
+    http.TIMEOUT = gateway.HTTP_TIMEOUT
+
+    return http.request("http://" .. gateway.gateway_ip
+                        .. "/gwr/gop.php",
+                        "cmd=GWRBatch&data="
+                        .. "<gwrcmds>"
+                        .. "<gwrcmd>"
+                        .. "<gcmd>RoomGetCarousel</gcmd>"
+                        .. "<gdata>"
+                        .. "<gip>"
+                        .. "<version>1</version>"
+                        .. "<token>1234567890</token>"
+                        .. "<fields>name,image,imageurl,control,power,product,class,realtype,status</fields>"
+                        .. "</gip>"
+                        .. "</gdata>"
+                        .. "</gwrcmd>"
+                        .. "</gwrcmds>"
+                        .. "&fmt=xml")
+end
+
+
+-- ----------------------------------------------------------------------------
+-- Parse the gateway configuration
+-- ----------------------------------------------------------------------------
+function tcplighting_parse_gateway_config (gateway, config_xml)
+
+    local lxp = require("lxp")
+
+    -- Local collection devices - will be added to master table after parsing
+    local new_devices = {}
+
+    -- enumerate the XML elements that we want to load into our tables
+    local room_elements_of_interest = "|rid|name|desc|"
+    local device_elements_of_interest = "|did|name|desc|state|level|offline|type|rangemin|rangemax|power|poweravg|energy|manufacturer|prodbrand|prodmodel|prodtype|capability|"
+
+    -- used to keep track of the current context during the parsing process
+    local current_room_object = nil
+    local current_device_object = nil
+    local current_element_name = nil
+
+    -- XML parsing handler for the start of an element
+    local function start_element_handler (parser, name)
+        current_element_name = name
+        if name == "room" then
+            current_room_object = {}
+        elseif name == "device" then
+            current_device_object = {}
+            current_device_object["gateway_ip"] = gateway.gateway_ip
+            current_device_object["room"] = current_room_object
+        end
+    end
+
+    -- XML parsing handler for the end of an element
+    local function end_element_handler (parser, name)
+        current_element_name = name
+        if name == "room" then
+            current_room_object = nil
+        elseif name == "device" then
+             new_devices[#new_devices+1] = current_device_object
+            current_device_object = nil
+        end
+    end
+
+    -- XML parsing handler for the text of an element
+    local function character_handler (parser, value)
+        if current_device_object and string.find(device_elements_of_interest,"|"..current_element_name.."|") then
+            current_device_object[current_element_name] = value
+        elseif current_room_object and (not current_device_object) and string.find(room_elements_of_interest,"|"..current_element_name.."|") then
+            current_room_object[current_element_name] = value
+        end
+    end
+
+    -- Create a parser
+    local callbacks = {StartElement = start_element_handler, EndElement = end_element_handler, CharacterData = character_handler}
+    local parser = lxp.new(callbacks)
+
+    -- Parse the XML
+    parser:parse(config_xml)
+    parser:parse() -- finishes the document
+    parser:close()
+
+    -- Save the devices, uniquely indexed by did
+    if #new_devices > 0 then
+        for i,device in ipairs(new_devices) do
+            gateway.devices[device.did] = device
+            -- tweak for interpretation of load level for dimmers - always set to zero if device is off
+            if device.state == "0" then
+               device.level = "0"
+            end
+        end
+        gateway.initialized = true
+        gateway.error_text = nil
+    else
+        gateway.error_text = "No devices found at gateway " .. gateway.gateway_ip .. " while parsing " .. config_xml
+    end
+
+    return
+
+end
+
+-- ----------------------------------------------------------------------------
+-- Helper functions for testing - call devices by name/room
+-- ----------------------------------------------------------------------------
+function tcplighting_get_did (gateway, name, roomname)
+   local did = nil
+   for key,device in pairs(gateway.devices) do
+      if device.name == name and device.room.name == roomname then
+         did = device.did
+      end
+   end
+   return did
+end
+
+function tcplighting_set_target_by_name  (gateway, name, roomname, target_value)
+    return tcplighting_set_target(gateway, tcplighting_get_did (gateway, name, roomname), target_value)
+end
+
+function tcplighting_set_load_level_target_by_name (gateway, name, roomname, load_percent)
+    return tcplighting_set_load_level_target(gateway, tcplighting_get_did (gateway, name, roomname), load_percent)
+end
Index: /tags/v1.0.1/test-ssdp.lua
===================================================================
--- /tags/v1.0.1/test-ssdp.lua	(revision 30)
+++ /tags/v1.0.1/test-ssdp.lua	(revision 30)
@@ -0,0 +1,43 @@
+-- Event handling sequence:
+--
+-- 1. Is  IP specified?
+--     1a. If No IP: trigger auto-discovery
+--     1b. If auto discovery failed set error text and bail
+--     1c. Set IP and set auto-discovered=true
+--
+--
+--
+--
+--
+--
+--
+--
+--
+--
+--
+
+
+
+require ("L_TCPLighting1")
+
+
+    local MAX_SSDP_RETRIES = 5
+
+
+    function discover_gateway ()
+        local gateway_ip   = null
+        local gateway_uuid = null
+        -- since UDP doesn't provide a reliable transport, try the SSDP request few times if necessary
+        for i = 1, MAX_SSDP_RETRIES do
+           gateway_ip, gateway_uuid = tcplighting_gateway_ssdp_search ()
+           if gateway_ip then break end
+        end
+        return gateway_ip, gateway_uuid
+    end
+
+
+    local start_clock = os.clock()
+    print (discover_gateway ())
+    local end_clock = os.clock()
+
+    print ((end_clock-start_clock)*1000, " ms")
Index: /tags/v1.0.1/D_TCPLightingGateway1.json
===================================================================
--- /tags/v1.0.1/D_TCPLightingGateway1.json	(revision 30)
+++ /tags/v1.0.1/D_TCPLightingGateway1.json	(revision 30)
@@ -0,0 +1,95 @@
+{
+    "flashicon":      "icons/generic_sensor.swf",
+    "imgIconBody":     "",
+    "imgIconDimmable": "",
+    "imgIconTurnable": "",
+    "imgIconMin":      "",
+    "imgIconMax":      "",
+    "halloIconsDir":   "pics/hallo",
+    "inScene":         "0",
+    "DisplayStatus":   {},
+    "doc_url":
+       {
+       "doc_language": 1,
+	   "doc_manual":   1,
+       "doc_version":  1,
+       "doc_platform": 0,
+       "doc_page":     "lighting_devices"
+       },
+    "Tabs":
+	   [
+
+       {
+       "Label":    {"lang_tag": "tcp_lighting_gateway", "text": "Gateway"},
+       "Position": "0",
+       "TabType":  "flash",
+	   "SceneGroup":
+	        [
+		    {
+			"id": "1",
+			"top": "1",
+			"left": "0",
+			"x": "1",
+			"y": "1"
+		    },
+		    {
+			"id": "2",
+			"top": "2",
+			"left": "0",
+			"x": "1",
+			"y": "2"
+		    }
+		    ],
+		"ControlGroup": [
+			{
+			"id": "1",
+			"scenegroup": "1",
+			"isSingle": "1"
+			},
+			{
+			"id": "2",
+			"scenegroup": "2",
+			"type": "info"
+			}
+		],
+       "Control":
+		    [
+            {
+            "ControlType": "label",
+            "Label":       {"lang_tag": "synchronize_desc","text": "Sync to the gateway and configure lights - Gateway ip must be specified in Advanced tab"},
+            "Display":     {"Top": 20,"Left": 50,"Width": 200,"Height": 20}
+            },
+            {
+            "ControlGroup": "1",
+            "ControlType":  "button",
+            "Label":       {"lang_tag": "synchronize_command","text": "Sync"},
+			"Display":     {"Top": 37,"Left": 50, "Width": 100, "Height": 20 },
+            "Command":     {"Service": "urn:pmcode-org:serviceId:TCPLightingGateway1", "Action": "Synchronize", "Parameters": []}
+			},
+            {
+            "ControlType": "label",
+            "Label":       {"lang_tag": "error_text_label","text": "Gateway Errors:"},
+			"Display":     {"Top": 80,"Left": 50,"Width": 200, "Height": 20}
+			},
+            {
+            "ControlGroup": "2",
+            "ControlType": "variable",
+            "Display":     {"Top": 95, "Left": 70, "Width": 200, "Height": 20,
+			                "Service": "urn:pmcode-org:serviceId:TCPLightingGateway1", "Variable": "ErrorText"}
+			}
+            ]
+        },
+
+        {
+        "Label":      {"lang_tag": "advanced","text": "Advanced"},
+        "Position":   "1",
+        "TabType":    "javascript",
+        "ScriptName": "shared.js",
+        "Function":   "advanced_device"
+        }
+    ],
+    "eventList":   {},
+    "eventList2":  [],
+    "DeviceType":  "urn:schemas-pmcode-org:service:TCPLightingGateway:1",
+    "device_type": "urn:schemas-pmcode-org:service:TCPLightingGateway:1"
+}
Index: /tags/v2.0.1/S_TCPLightingGateway1.xml
===================================================================
--- /tags/v2.0.1/S_TCPLightingGateway1.xml	(revision 30)
+++ /tags/v2.0.1/S_TCPLightingGateway1.xml	(revision 30)
@@ -0,0 +1,30 @@
+<?xml version="1.0"?>
+<scpd xmlns="urn:schemas-upnp-org:service-1-0">
+  <specVersion>
+    <major>1</major>
+    <minor>0</minor>
+  </specVersion>
+  <serviceStateTable>
+    <stateVariable sendEvents="no">
+      <name>AutoDiscovered</name>
+      <dataType>boolean</dataType>
+      <defaultValue>0</defaultValue>
+    </stateVariable>
+    <stateVariable sendEvents="yes">
+      <name>ErrorText</name>
+      <dataType>string</dataType>
+    </stateVariable>
+    <stateVariable sendEvents="no">
+      <name>OfflineDeviceNameFormat</name>
+      <dataType>string</dataType>
+    </stateVariable>
+  </serviceStateTable>
+  <actionList>
+    <action>
+      <name>Pair</name>
+    </action>
+    <action>
+      <name>Synchronize</name>
+    </action>
+  </actionList>
+</scpd>
Index: /tags/v2.0.1/D_TCPLightingGateway1.xml
===================================================================
--- /tags/v2.0.1/D_TCPLightingGateway1.xml	(revision 30)
+++ /tags/v2.0.1/D_TCPLightingGateway1.xml	(revision 30)
@@ -0,0 +1,26 @@
+<?xml version="1.0"?>
+<root xmlns="urn:schemas-upnp-org:device-1-0">
+  <specVersion>
+    <major>1</major>
+    <minor>0</minor>
+  </specVersion>
+  <device>
+    <deviceType>urn:schemas-pmcode-org:service:TCPLightingGateway:1</deviceType>
+    <staticJson>D_TCPLightingGateway1.json</staticJson>
+    <serviceList>
+      <service>
+        <serviceType>urn:schemas-pmcode-org:service:TCPLightingGateway:1</serviceType>
+        <serviceId>urn:pmcode-org:serviceId:TCPLightingGateway1</serviceId>
+        <SCPDURL>S_TCPLightingGateway1.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>
+    <implementationList>
+      <implementationFile>I_TCPLighting1.xml</implementationFile>
+    </implementationList>
+  </device>
+</root>
Index: /tags/v2.0.1/D_TCPLighting1.xml
===================================================================
--- /tags/v2.0.1/D_TCPLighting1.xml	(revision 30)
+++ /tags/v2.0.1/D_TCPLighting1.xml	(revision 30)
@@ -0,0 +1,31 @@
+<?xml version="1.0"?>
+<root xmlns="urn:schemas-upnp-org:device-1-0">
+  <specVersion>
+    <major>1</major>
+    <minor>0</minor>
+  </specVersion>
+  <device>
+    <deviceType>urn:schemas-upnp-org:device:DimmableLight:1</deviceType>
+    <staticJson>D_DimmableLight1.json</staticJson>
+    <serviceList>
+      <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-upnp-org:service:Dimming:1</serviceType>
+        <serviceId>urn:upnp-org:serviceId:Dimming1</serviceId>
+        <SCPDURL>S_Dimming1.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>
+    <implementationList>
+      <implementationFile>I_TCPLighting1.xml</implementationFile>
+    </implementationList>
+  </device>
+</root>
Index: /tags/v2.0.1/test.lua
===================================================================
--- /tags/v2.0.1/test.lua	(revision 30)
+++ /tags/v2.0.1/test.lua	(revision 30)
@@ -0,0 +1,25 @@
+local tcplighting = require("L_TCPLighting1")
+local json = require ("json")
+
+
+-- initialize gateway
+gateway = tcplighting_gateway_init(null) --
+print (json.encode(gateway))
+
+
+-- modify a light's intensity
+if (gateway.initialized) then
+    -- generate a random intensity
+    math.randomseed(os.time())
+    math.random(); math.random(); math.random()
+    local load_level_target = math.random(100)
+    --  call the gateway
+    tcplighting_set_load_level_target_by_name(gateway, "Front Entry Light", "Front Entry", load_level_target)
+    tcplighting_set_target_by_name(gateway, "Front Entry Light", "Front Entry", 1)
+    -- poll to get latest status
+    tcplighting_gateway_poll(gateway)
+    print (json.encode(gateway.devices))
+else
+    print ("--- ERROR: " .. gateway.error_text)
+end
+
Index: /tags/v2.0.1/I_TCPLighting1.xml
===================================================================
--- /tags/v2.0.1/I_TCPLighting1.xml	(revision 30)
+++ /tags/v2.0.1/I_TCPLighting1.xml	(revision 30)
@@ -0,0 +1,549 @@
+<?xml version="1.0"?>
+<implementation>
+  <functions>
+
+       -- ---------------------------------------------------------------------
+       -- Constants
+       -- ---------------------------------------------------------------------
+       local GATEWAY_DEVICE_TYPE = "urn:schemas-pmcode-org:service:TCPLightingGateway:1"
+       local GATEWAY_SERVICE_ID  = "urn:pmcode-org:serviceId:TCPLightingGateway1"
+       local HA_DEVICE_TYPE      = "urn:schemas-micasaverde-com:service:HaDevice:1"
+       local HA_SERVICE_ID       = "urn:micasaverde-com:serviceId:HaDevice1"
+       local DIMMER_DEVICE_TYPE  = "urn:schemas-upnp-org:device:DimmableLight:1"
+       local DIMMER_SERVICE_ID   = "urn:upnp-org:serviceId:Dimming1"
+       local SWITCH_DEVICE_TYPE  = "urn:schemas-upnp-org:device:SwitchPower:1"
+       local SWITCH_SERVICE_ID   = "urn:upnp-org:serviceId:SwitchPower1"
+       local LIGHT_DEVICE_FILE   = "D_TCPLighting1.xml"
+       
+       
+       -- ---------------------------------------------------------------------
+       -- Pseudo-Constants: Can be overridden by parent device variables
+       -- ---------------------------------------------------------------------
+       local OFFLINE_DEVICE_NAME_FORMAT = "[Offline] %s"
+
+
+       -- ---------------------------------------------------------------------
+       -- table representing the gateway and its devices
+       -- ---------------------------------------------------------------------
+       local tcplighting_gateway = nil
+
+
+       -- ---------------------------------------------------------------------
+       -- Get a reference to the gateway (will init if necessary)
+       -- ---------------------------------------------------------------------
+	   local function get_gateway (gateway_ip)
+		  if (not tcplighting_gateway) or (not gateway_ip) or (gateway_ip == "") then
+             luup.log ("TCPLighting:  get_gateway - calling tcplighting_gateway_init,  IP=" .. tostring(gateway_ip))
+			 tcplighting_gateway = tcplighting_gateway_init (gateway_ip)
+			 if tcplighting_gateway.error_text then
+				luup.task(tcplighting_gateway.error_text, 2, "TCPLighting", -1)
+			 end
+		  end
+		  return tcplighting_gateway
+	   end
+
+
+       -- ---------------------------------------------------------------------
+       -- Clear the gateway object
+       -- ---------------------------------------------------------------------
+	   local function reset_gateway ()
+		  tcplighting_gateway = nil
+		  return
+	   end
+
+
+       -- ---------------------------------------------------------------------
+       -- Returns true if the device is offline
+       -- ---------------------------------------------------------------------
+       local function is_offline (device)
+           return ((not device) or (device.offline and device.offline == "1"))
+       end
+
+
+       -- ---------------------------------------------------------------------
+       -- Returns true if the device has dimming capabilities
+       -- ---------------------------------------------------------------------
+       local function is_dimmer_switch (device)
+           return (device and device.rangemax and device.rangemax == "99")
+       end
+
+
+       -- ---------------------------------------------------------------------
+       -- Returns true if the device has on/off capabilities
+       -- ---------------------------------------------------------------------
+       local function is_binary_switch (device)
+           return (device and device.capability and string.find(device.capability,"switch_binary"))
+       end
+
+
+       -- ---------------------------------------------------------------------
+       -- See if we have a matching room number
+       -- ---------------------------------------------------------------------
+       local function room_number (input_room_name)
+          local room_num = 0
+          if input_room_name then
+              for i, room_name in ipairs(luup.rooms) do
+                  if room_name == input_room_name then
+                      room_num = i
+                      break
+                  end
+              end
+          end
+          return room_num
+       end
+
+
+       -- ---------------------------------------------------------------------
+       -- Sets a LUUP device variable, but only if it has no current value
+       -- ---------------------------------------------------------------------
+       local function initialize_variable (service_id, variable_name, new_value, device_number)
+          local current_value = luup.variable_get (service_id, variable_name, device_number)
+          if (not current_value) or (current_value == "") then
+              luup.variable_set (service_id, variable_name, new_value, device_number)
+          end
+       end
+
+
+       -- ---------------------------------------------------------------------
+       -- Sets a LUUP device attribute, but only if it has no current value
+       -- ---------------------------------------------------------------------
+       local function initialize_attr (attr_name, new_value, device_number)
+          local current_value = luup.attr_get (attr_name, device_number)
+          if (not current_value) or (current_value == "") then
+              luup.attr_set (attr_name, new_value, device_number)
+          end
+       end
+
+
+       -- ---------------------------------------------------------------------
+       -- Updates a LUUP device variable, but only if its value has changed
+       -- ---------------------------------------------------------------------
+       local function update_variable (service_id, variable_name, new_value, device_number)
+          local current_value = luup.variable_get (service_id, variable_name, device_number)
+          if (not current_value) or (new_value ~= current_value) then
+              luup.variable_set (service_id, variable_name, new_value, device_number)
+          end
+       end
+
+
+       -- ---------------------------------------------------------------------
+       -- Updates a LUUP device attribute, but only if its value has changed
+       -- ---------------------------------------------------------------------
+       local function update_attr (attr_name, new_value, device_number)
+          local current_value = luup.attr_get (attr_name, device_number)
+          if (not current_value) or (new_value ~= current_value) then
+		      luup.log ("TCPLighting:  update_attr - device_number=" .. tostring(device_number))
+		      luup.log ("TCPLighting:  update_attr - attr_name=" .. tostring(attr_name))
+		      luup.log ("TCPLighting:  update_attr - new_value=" .. tostring(new_value))
+              luup.attr_set (attr_name, new_value, device_number)
+          end
+       end
+
+
+       -- ---------------------------------------------------------------------
+       -- Update a device to match info from gateway
+       -- ---------------------------------------------------------------------
+       local function update_device_settings (gateway, did, lul_device)
+
+           if not (gateway and did and lul_device) then
+              return
+           end
+
+           local device_number = tonumber(lul_device)
+           local device = gateway.devices[did]
+           
+		   -- indicate if we don't have data for device
+		   if (not device) then
+		       luup.log ("TCPLighting:  update_device_settings - device with id=" .. lul_device .. " has no data from gateway")
+			   return
+		   end
+           
+           
+		   -- indicate device is offline if we don't have data for it 
+           update_attr ("invisible", "", device_number) -- legacy code: prior versions made offline devices invisible
+		   if is_offline(device) then
+		       luup.log ("TCPLighting:  update_device_settings - device with id=" .. lul_device .. " is offline")
+		       update_attr ("name", string.format(OFFLINE_DEVICE_NAME_FORMAT, device.name), device_number)
+			   return
+		   else
+		       update_attr ("name", device.name, device_number)
+		   end
+
+           -- update the device
+		   update_attr ("manufacturer", device.manufacturer, device_number)
+		   update_attr ("model",        device.prodmodel,    device_number)
+
+           if device and device.room and device.room.name then
+               local room_num = room_number(device.room.name)
+               if room_num and room_num ~= 0 then
+                  local room_num_string = tostring(room_num)
+                  update_attr ("room_num", room_num_string, device_number)
+                  update_attr ("room",     room_num_string, device_number)
+               else
+                  luup.log("TCPLighting:  update_device_settings - unknown room " .. device.room.name)
+               end
+           end
+
+           if device and is_binary_switch(device) then
+		       update_variable (SWITCH_SERVICE_ID, "Status", device.state, device_number)
+		       update_variable (SWITCH_SERVICE_ID, "Target", device.state, device_number)
+           end
+
+           if device and is_dimmer_switch(device) then
+		      update_variable (DIMMER_SERVICE_ID, "LoadLevelStatus", device.level, device_number)
+		      update_variable (DIMMER_SERVICE_ID, "LoadLevelTarget", device.level, device_number)
+           end
+
+           return
+
+       end
+
+
+       -- ---------------------------------------------------------------------
+       -- Update all of the children of the specified device
+       -- ---------------------------------------------------------------------
+       local function sync_child_devices (gateway, parent_lul_device)
+
+           if not (gateway and parent_lul_device) then
+              return
+           end
+
+           -- poll the gateway for most current device data
+           tcplighting_gateway_poll(gateway)
+
+           -- find matching child devices
+           local parent_device_number = tonumber (parent_lul_device)
+           for k, luup_device in pairs(luup.devices) do
+               if (luup_device.device_num_parent == parent_device_number) and (luup_device.id) then
+                    luup.log ("TCPLighting:  sync_child_devices for " .. parent_lul_device .. " - updating child " .. k .. " " .. luup_device.description)
+                    update_device_settings (gateway, luup_device.id, k)
+               end
+           end
+
+           return
+
+       end
+
+
+       -- ---------------------------------------------------------------------
+       -- Create child devices of gateway if necessary
+       -- ---------------------------------------------------------------------
+       local function create_child_devices (gateway, parent_lul_device)
+
+           if gateway and gateway.initialized then
+
+              luup.log ("TCPLighting:  create_child_devices - gateway is initialized")
+
+			  local parent_device_number = tonumber (parent_lul_device)
+
+              -- Build up a table of child devices, indexed by did, with value being the device type
+			  local child_device_map = {}
+
+			  -- find existing children
+              for k, luup_device in pairs(luup.devices) do
+                  if (luup_device.device_num_parent == parent_device_number) and (luup_device.id) and (luup_device.device_type) then
+					  local did = luup.attr_get ("altid", k)
+					  if (did) then
+				         child_device_map[did] = {name=luup_device.description, device_type = luup_device.device_type}
+					  end
+                  end
+			  end
+
+              -- see if we have new children
+			  local new_devices_found = false
+              for did,gateway_device in pairs(gateway.devices) do
+				  if child_device_map[gateway_device.did] == nil then
+                     if is_dimmer_switch (gateway_device) then
+				        child_device_map[gateway_device.did] = {name=gateway_device.name, device_type=DIMMER_DEVICE_TYPE}
+						new_devices_found = true
+                     elseif is_binary_switch (gateway_device) then
+				        child_device_map[gateway_device.did] = {name=gateway_device.name, device_type=SWITCH_DEVICE_TYPE}
+						new_devices_found = true
+                     end
+				  end
+              end
+
+			  -- resync the combined list of child devices if new ones were found
+              if new_devices_found then
+			     local chdev_context = luup.chdev.start (parent_device_number)
+				 for did,device in pairs(child_device_map) do
+                     luup.log ("TCPLighting:  create_child_devices - creating child with did=" .. did .. ", name=" .. device.name)
+                     luup.chdev.append (parent_device_number, chdev_context, did, device.name, device.device_type, LIGHT_DEVICE_FILE,"","",false)
+                 end
+                 luup.chdev.sync (parent_device_number, chdev_context)
+			  else
+		         luup.log ("TCPLighting:  create_child_devices - no new child devices")
+              end
+
+           end
+
+       end
+
+
+       -- ---------------------------------------------------------------------
+       -- Schedule the next poll
+       -- ---------------------------------------------------------------------
+	   local function schedule_next_poll (lul_device)
+
+           local device_number = tonumber(lul_device)
+
+		   -- make sure the polling variables have values
+		   initialize_variable (HA_SERVICE_ID, "PollingEnabled", "1",  device_number)
+		   initialize_variable (HA_SERVICE_ID, "PollMinDelay",   "60", device_number)
+
+           -- get current polling settings (may be different from above if overriden by user)
+		   local polling_enabled  = luup.variable_get (HA_SERVICE_ID, "PollingEnabled", device_number)
+		   local polling_interval = luup.variable_get (HA_SERVICE_ID, "PollMinDelay",   device_number)
+
+		   -- make a delayed call to the synchronization functions
+		   if polling_enabled == "true" or polling_enabled == "1" then
+		      luup.call_timer("synchronize_gateway", 1, tonumber(polling_interval), "", lul_device)
+			  luup.log ("TCPLighting:  schedule_next_poll - timer set")
+		   end
+
+	   end
+
+
+       -- ---------------------------------------------------------------------
+       -- Authenticate to the gateway and obtain a token that can be used
+       -- for subsequent calls
+       -- ---------------------------------------------------------------------
+	   function pair_with_gateway (lul_device)
+
+		   luup.log ("TCPLighting: pair_with_gateway - start")
+
+           local device_number = tonumber(lul_device)
+
+           -- will create and populate a new gateway configuration object if necessary
+           local gateway_ip = luup.attr_get ("ip", device_number)
+           local gateway    = get_gateway (gateway_ip)
+
+           -- Assign gateway UUID if necessary
+           if (not gateway.gateway_uuid) then
+               local gateway_uuid = luup.variable_get  (GATEWAY_SERVICE_ID, "GatewayUUID", device_number)
+               if (gateway_uuid) then
+                   gateway.gateway_uuid = gateway_uuid
+               else
+                   -- Assign gateway UUID from the device UDN 
+                   local device_udn = luup.attr_get ("local_udn", device_number)
+                   if (device_udn) and (device_udn ~= "") and (string.len(device_udn) > 5) then
+                       gateway.gateway_uuid = string.sub(device_udn,6) -- take everything after "uuid:" prefix
+                   end
+               end
+           end
+
+           -- clear the current token
+           update_variable (GATEWAY_SERVICE_ID, "GatewayToken", "", device_number)
+
+           -- login to the gateway
+           luup.log ("TCPLighting:  pair_with_gateway - IP=" .. tostring(gateway.gateway_ip))
+           luup.log ("TCPLighting:  pair_with_gateway - UUID=" .. tostring(gateway.gateway_uuid))
+           tcplighting_gateway_pair (gateway)
+           luup.log ("TCPLighting:  pair_with_gateway - Token=" .. tostring(gateway.gateway_token))
+
+		   if (gateway.gateway_token) then
+		   
+		       -- update device variables
+               update_variable (GATEWAY_SERVICE_ID, "GatewayToken", gateway.gateway_token, device_number)
+               update_variable (GATEWAY_SERVICE_ID, "GatewayUUID",  gateway.gateway_uuid,  device_number)
+               update_variable (GATEWAY_SERVICE_ID, "ErrorText",    gateway.error_text,    device_number)
+               
+               -- initiate a sync
+		       luup.log ("TCPLighting: pair_with_gateway - synchronizing")
+               return synchronize_gateway (lul_device)
+
+		   else
+
+		       -- flag that there was an error
+		       luup.log ("TCPLighting:  pair_with_gateway - error: " .. gateway.error_text)
+               update_variable (GATEWAY_SERVICE_ID, "ErrorText", gateway.error_text, device_number)
+
+           end
+
+		   luup.log ("TCPLighting: pair_with_gateway - end")
+
+           return true
+
+	   end
+
+
+       -- ---------------------------------------------------------------------
+       -- Contact the gateway and initialize the gateway object
+       -- ---------------------------------------------------------------------
+	   function synchronize_gateway (lul_device)
+
+		   luup.log ("TCPLighting: synchronize_gateway - start")
+
+           local device_number = tonumber(lul_device)
+           local gateway_ip = luup.attr_get ("ip", device_number)
+
+           -- will create and populate a new gateway configuration object if necessary
+           local gateway = get_gateway (gateway_ip)
+
+           -- Make sure we have a gateway token
+           local gateway_token = luup.variable_get  (GATEWAY_SERVICE_ID, "GatewayToken", device_number)
+           if (not gateway_token) or (gateway_token == "") then
+               gateway.error_text = "No gateway token assigned, use device configuration UI to pair first"
+               update_variable (GATEWAY_SERVICE_ID, "ErrorText", gateway.error_text, device_number)
+               return true
+           end
+           if (gateway_token) then
+               gateway.gateway_token = gateway_token
+           end
+
+		   if (gateway.error_text == nil) or (gateway.error_text == "") then
+		   
+               -- Sync up gateway properties
+			   initialize_attr ("name",  "TCP Lighting Gateway", device_number)
+			   if gateway.ssdp_uuid then
+			      initialize_attr ("altid", gateway.ssdp_uuid,  device_number)
+			   end
+               update_variable (GATEWAY_SERVICE_ID, "ErrorText", "", device_number)
+			   if gateway.autodiscovered then
+				  -- Use IP address from autodiscovery
+		          update_attr     ("ip", gateway.gateway_ip, device_number)
+				  update_variable (GATEWAY_SERVICE_ID, "AutoDiscovered", "1", device_number)
+			   end
+
+			   -- check to see if prefix for offline devices has been overridden
+			   --   some of this code looks redundant, but is required  
+			   --   in case previous versions of plugin were installed
+			   initialize_variable (GATEWAY_SERVICE_ID, "OfflineDeviceNameFormat", OFFLINE_DEVICE_NAME_FORMAT, device_number) 
+			   local offline_device_name_format_override = luup.variable_get (GATEWAY_SERVICE_ID, "OfflineDeviceNameFormat", device_number)
+			   if offline_device_name_format_override then
+			       OFFLINE_DEVICE_NAME_FORMAT = offline_device_name_format_override
+			   end
+
+               -- create and synchronize all children
+		       create_child_devices (gateway, lul_device)
+               sync_child_devices (gateway, lul_device)
+
+		   else
+
+		       -- flag that there was an error
+		       luup.log ("TCPLighting:  synchronize_gateway - error: " .. gateway.error_text)
+               update_variable (GATEWAY_SERVICE_ID, "ErrorText", gateway.error_text, device_number)
+
+               -- clear autodiscovered IP on failures
+               local previously_autodiscovered = luup.variable_get  (GATEWAY_SERVICE_ID, "AutoDiscovered", device_number)
+			   if previously_autodiscovered and previously_autodiscovered == "1" then
+				  -- Clear IP address from autodiscovery
+		          update_attr     ("ip", "", device_number)
+				  update_variable (GATEWAY_SERVICE_ID, "AutoDiscovered", "0", device_number)
+		          luup.log ("TCPLighting:  synchronize_gateway - cleared previously autodiscovered IP = " .. tostring(gateway_ip))
+			   end
+
+		       -- reset the gateway object
+			   reset_gateway ()
+
+           end
+
+		   -- set up polling so that changes made externally to the plugin show up in the UI
+		   schedule_next_poll (lul_device)
+
+		   luup.log ("TCPLighting: synchronize_gateway - end")
+
+           return true
+
+	   end
+
+
+       -- ---------------------------------------------------------------------
+       -- Set the dimming level on a light
+       -- ---------------------------------------------------------------------
+       local function set_load_level_target (lul_device, lul_settings)
+	       local device_number = tonumber(lul_device)
+           local gateway_ip = luup.devices[device_number].ip
+           local did = luup.devices[device_number].id
+		   local gateway = get_gateway (gateway_ip)
+		   if gateway.initialized and did then
+               tcplighting_set_load_level_target (gateway, did, tonumber(lul_settings.newLoadlevelTarget))
+               tcplighting_gateway_poll (gateway)
+			   update_device_settings (gateway, did, lul_device)
+           end
+           return true
+	   end
+
+
+       -- ---------------------------------------------------------------------
+       -- Flips switch on/off
+       -- ---------------------------------------------------------------------
+       local function set_target (lul_device, lul_settings)
+	       local device_number = tonumber(lul_device)
+           local gateway_ip = luup.devices[device_number].ip
+           local did = luup.devices[device_number].id
+		   local gateway = get_gateway (gateway_ip)
+		   if gateway.initialized and did then
+               tcplighting_set_target (gateway, did, tonumber(lul_settings.newTargetValue))
+               tcplighting_gateway_poll (gateway)
+			   update_device_settings (gateway, did, lul_device)
+           end
+           return true
+	   end
+
+
+       -- ---------------------------------------------------------------------
+       -- Plugin startup
+       -- Will initialize the gateway, which in turn creates child devices
+       -- for each light as necessary
+       -- ---------------------------------------------------------------------
+      function tcplighting_plugin_startup (lul_device)
+
+		  luup.log ("TCPLighting: tcplighting_plugin_startup - start")
+
+	      local device_number = tonumber(lul_device)
+          local device_type = luup.devices[device_number].device_type
+
+	      if device_type == GATEWAY_DEVICE_TYPE then
+		     synchronize_gateway (lul_device)
+          else
+             luup.log ("TCPLighting: tcplighting_plugin_startup - not gateway")
+		  end
+
+		  luup.log ("TCPLighting: tcplighting_plugin_startup - end")
+
+	  end
+
+
+  </functions>
+
+  <startup>tcplighting_plugin_startup</startup>
+
+  <files>L_TCPLighting1.lua</files>
+
+  <actionList>
+
+    <action>
+      <serviceId>urn:upnp-org:serviceId:SwitchPower1</serviceId>
+      <name>SetTarget</name>
+      <run>return set_target (lul_device, lul_settings)</run>
+    </action>
+
+    <action>
+      <serviceId>urn:upnp-org:serviceId:Dimming1</serviceId>
+      <name>SetLoadLevelTarget</name>
+      <run>return set_load_level_target (lul_device, lul_settings)</run>
+    </action>
+
+    <action>
+      <serviceId>urn:pmcode-org:serviceId:TCPLightingGateway1</serviceId>
+      <name>Pair</name>
+      <run>return pair_with_gateway (lul_device)</run>
+    </action>
+
+    <action>
+      <serviceId>urn:pmcode-org:serviceId:TCPLightingGateway1</serviceId>
+      <name>Synchronize</name>
+      <run>return synchronize_gateway (lul_device)</run>
+    </action>
+
+	<action>
+      <serviceId>urn:micasaverde-com:serviceId:HaDevice1</serviceId>
+      <name>Poll</name>
+      <run>return synchronize_gateway (lul_device)</run>
+    </action>
+
+   </actionList>
+
+</implementation>
+
Index: /tags/v2.0.1/L_TCPLighting1.lua
===================================================================
--- /tags/v2.0.1/L_TCPLighting1.lua	(revision 30)
+++ /tags/v2.0.1/L_TCPLighting1.lua	(revision 30)
@@ -0,0 +1,402 @@
+-- ----------------------------------------------------------------------------
+-- Search for a Connected by TCP gateway via SSDP
+-- ----------------------------------------------------------------------------
+function tcplighting_gateway_ssdp_search ()
+
+    -- Create an SSDP request - note that the Connected By TCP gateways
+    -- do not appear to respond to ssdp:all requests, so we'll directly
+    -- target the Greenwave Reality gateways that TCPi uses
+    -- See: http://upnp.org/specs/arch/UPnP-arch-DeviceArchitecture-v1.1.pdf
+    local ssdp_headers =
+        {
+        [1] = "M-SEARCH * HTTP/1.1\r\n",
+        [2] = "HOST: 239.255.255.250:1900\r\n",
+        [3] = "MAN: \"ssdp:discover\"\r\n",
+        [4] = "MX: 3\r\n",
+        [5] = "ST: urn:greenwavereality-com:service:gop:1\r\n\r\n"
+        }
+    local ssdp_request = table.concat (ssdp_headers )
+
+    -- Get a UDP socket
+    local http = require("socket.http")
+    local udp = socket.udp()
+
+     -- Send out the SSDP request.  Don't wait forever, they normally respond quickly or not at all.
+    udp:sendto (ssdp_request, "239.255.255.250", 1900)
+    udp:settimeout (3)
+
+    -- Loop over response packets until we get and IP or there is no more data
+    local gateway_ip   = nil
+	local ssdp_uuid    = nil
+
+    repeat
+        local received_datagram = udp:receive()
+        if received_datagram then
+           -- pull the gateway IP out of the LOCATION header in the response
+           gateway_ip = string.match (received_datagram, "LOCATION:[%s+]http://([%d%.]+)")
+           -- pull the gateway UID out of the USN header
+           ssdp_uuid  = string.match (received_datagram, "USN:[%s+]uuid:(%d+)")
+        else
+           break
+        end
+    until (gateway_ip)
+
+    -- return the IP address & UUID of the discovered gateway (or nil if we are SOL)
+    return gateway_ip, ssdp_uuid
+end
+
+-- ----------------------------------------------------------------------------
+-- Pair with a Connected by TCP gateway
+--
+-- Note: User must put gateway into "pairing" mode by pressing 
+--       green sync button (green on the model I have anyway)
+-- ----------------------------------------------------------------------------
+function tcplighting_gateway_pair (gateway)
+
+    -- Bail if we don't a gateway
+    if (not gateway) then
+       gateway.error_text = "No gateway object provided, pairing failed"
+       return gateway
+    end
+
+    -- Bail if we don't a gateway IP
+    if (not gateway.gateway_ip) then
+       gateway.error_text = "No gateway IP address specified, pairing failed"
+       return gateway
+    end
+
+    -- Bail if we  don't have a gateway UUID
+    if (not gateway.gateway_uuid) then
+       gateway.error_text = "No gateway UUID address derived, pairing failed"
+       return 
+    end
+
+    local http  = require("socket.http")
+    local https = require("ssl.https")
+
+    http.TIMEOUT = 5
+    
+    local result, status  = https.request("https://" .. gateway.gateway_ip
+                                          .. "/gwr/gop.php",
+                                         "cmd=GWRLogin&data="
+                                          .. "<gip>"
+                                          .. "<version>1</version>"
+                                          .. "<email>" .. gateway.gateway_uuid .. "</email>"
+                                          .. "<password>" .. gateway.gateway_uuid .. "</password>"
+                                          .. "</gip>"
+                                          .. "&fmt=xml")
+    
+    local rc_value    = nil
+    if (status == 200 and result) then
+       -- The result is a small bit of XML that can easily be parsed through string manipulation
+       rc_value    = extract_xml_element_value (result, "rc")
+       gateway.gateway_token = extract_xml_element_value (result, "token")
+       gateway.error_text = ""
+    end
+
+    if (not gateway.gateway_token) then
+       gateway.error_text = "No gateway token extracted, pairing failed"
+       if status   then gateway.error_text = gateway.error_text .. " - HTTPS status=" .. status  end
+       if rc_value then gateway.error_text = gateway.error_text .. " - Gateway RC=" .. rc_value  end
+    end
+
+    return 
+
+end
+
+
+-- ----------------------------------------------------------------------------
+-- Implements urn:upnp-org:serviceId:SwitchPower1.SetTarget
+-- ----------------------------------------------------------------------------
+function tcplighting_set_target (gateway, did, target_value)
+
+    local http  = require("socket.http")
+    local https = require("ssl.https")
+
+    http.TIMEOUT = gateway.HTTP_TIMEOUT
+
+    return https.request("https://" .. gateway.devices[did].gateway_ip
+                        .. "/gwr/gop.php",
+                        "cmd=DeviceSendCommand&data="
+                        .. "<gip>"
+                        .. "<version>1</version>"
+                        .. "<token>" .. gateway.gateway_token .. "</token>"
+                        .. "<did>" .. did .. "</did>"
+                        .. "<value>" .. target_value .. "</value>"
+                        .. "</gip>"
+                        .. "&fmt=xml")
+end
+
+-- ----------------------------------------------------------------------------
+-- Implements urn:upnp-org:serviceId:Dimming1.SetLoadLevelTarget
+-- ----------------------------------------------------------------------------
+function tcplighting_set_load_level_target (gateway, did, load_percent)
+
+    local http  = require("socket.http")
+    local https = require("ssl.https")
+
+    http.TIMEOUT = gateway.HTTP_TIMEOUT
+
+    -- make sure light is on/off to match request
+    if load_percent > 0 then
+        tcplighting_set_target (gateway, did, 1)
+    else
+        tcplighting_set_target (gateway, did, 0)
+    end
+
+    -- change the dimming level
+    return https.request("https://" .. gateway.devices[did].gateway_ip
+                        .. "/gwr/gop.php",
+                        "cmd=DeviceSendCommand&data="
+                        .. "<gip>"
+                        .. "<version>1</version>"
+                        .. "<token>" .. gateway.gateway_token .. "</token>"
+                        .. "<did>" .. did .. "</did>"
+                        .. "<value>" .. load_percent .. "</value>"
+                        .. "<type>level</type>"
+                        .. "</gip>"
+                        .. "&fmt=xml")
+end
+
+
+-- ----------------------------------------------------------------------------
+-- Create a gateway object
+-- ----------------------------------------------------------------------------
+function tcplighting_gateway_create (gateway_ip)
+
+    -- create an empty gateway object
+    local gateway = {}
+    gateway.authenticated  = false
+    gateway.initialized    = false
+    gateway.autodiscovered = false
+    gateway.error_text     = nil    -- error text for last failed operation
+    gateway.gateway_ip     = gateway_ip
+    gateway.gateway_uuid   = nil
+    gateway.gateway_token  = nil
+    gateway.ssdp_uuid      = nil
+    gateway.devices        = {}    -- a cache of device info indexed by unique did
+    gateway.HTTP_TIMEOUT   = 5     -- 5 seconds should be fine on a local network
+
+    return gateway
+
+end
+
+
+-- ----------------------------------------------------------------------------
+-- Loads the gateway configuration
+-- ----------------------------------------------------------------------------
+function tcplighting_gateway_init (gateway_ip)
+
+    local MAX_SSDP_RETRIES = 5
+
+    -- create a gateway object
+    local gateway = tcplighting_gateway_create (gateway_ip)
+
+	-- Attempt auto-discovery via SSDP if IP not specified (not likely to work with new "secure" gateways)
+    if (gateway_ip == nil) or (string.len(gateway_ip) == 0) then
+        -- since UDP doesn't provide a reliable transport, try the SSDP request few times if necessary
+        for i = 1, MAX_SSDP_RETRIES do
+           gateway.gateway_ip, gateway.ssdp_uuid = tcplighting_gateway_ssdp_search ()
+           if gateway.gateway_ip then
+		      gateway.autodiscovered = true
+		      break
+		   end
+        end
+	else
+	    -- used specified IP
+	    gateway.gateway_ip = gateway_ip
+	end
+
+    -- Bail if we still don't have an IP
+    if not gateway.gateway_ip then
+       gateway.error_text = "No gateway IP address specified, autodiscovery failed"
+       return gateway
+    end
+
+    return gateway
+
+end
+
+
+-- ----------------------------------------------------------------------------
+-- Get current values from the gateway
+-- ----------------------------------------------------------------------------
+function tcplighting_gateway_poll (gateway)
+
+    -- Bail if we don't a gateway IP
+    if (not gateway.gateway_ip) then
+       gateway.error_text = "No gateway IP address specified, polling failed"
+       return gateway
+    end
+
+    -- Get the gateway's configuration data
+    local result, status = tcplighting_get_gateway_config (gateway)
+
+    -- Check the HTTP status
+    if status ~= 200 then
+       gateway.error_text = "HTTP gateway query request to " .. gateway.gateway_ip .. " failed"
+       if status then gateway.error_text = gateway.error_text .. " - " .. status     end
+       if result then gateway.error_text = gateway.error_text .. " - " .. result     end
+       return
+    end
+
+    -- Make sure something came back
+    if not result then
+       gateway.error_text = "HTTP gateway query request to " .. gateway.gateway_ip .. " did not return any data"
+       return
+    end
+
+    -- Only parse if the HTTP request was successful
+    tcplighting_parse_gateway_config(gateway, result)
+    return
+
+end
+
+
+-- ----------------------------------------------------------------------------
+-- Queries a Connected by TCP Gateway to determine which lights are available
+-- ----------------------------------------------------------------------------
+function tcplighting_get_gateway_config (gateway)
+
+    local http  = require("socket.http")
+    local https = require("ssl.https")
+
+
+    http.TIMEOUT = gateway.HTTP_TIMEOUT
+
+    return https.request("https://" .. gateway.gateway_ip
+                        .. "/gwr/gop.php",
+                        "cmd=GWRBatch&data="
+                        .. "<gwrcmds>"
+                        .. "<gwrcmd>"
+                        .. "<gcmd>RoomGetCarousel</gcmd>"
+                        .. "<gdata>"
+                        .. "<gip>"
+                        .. "<version>1</version>"
+                        .. "<token>" .. gateway.gateway_token .. "</token>"
+                        .. "<fields>name,image,imageurl,control,power,product,class,realtype,status</fields>"
+                        .. "</gip>"
+                        .. "</gdata>"
+                        .. "</gwrcmd>"
+                        .. "</gwrcmds>"
+                        .. "&fmt=xml")
+end
+
+
+-- ----------------------------------------------------------------------------
+-- Parse the gateway configuration
+-- ----------------------------------------------------------------------------
+function tcplighting_parse_gateway_config (gateway, config_xml)
+
+    local lxp = require("lxp")
+
+    -- Local collection devices - will be added to master table after parsing
+    local new_devices = {}
+
+    -- enumerate the XML elements that we want to load into our tables
+    local room_elements_of_interest = "|rid|name|desc|"
+    local device_elements_of_interest = "|did|name|desc|state|level|offline|type|rangemin|rangemax|power|poweravg|energy|manufacturer|prodbrand|prodmodel|prodtype|capability|"
+
+    -- used to keep track of the current context during the parsing process
+    local current_room_object = nil
+    local current_device_object = nil
+    local current_element_name = nil
+
+    -- XML parsing handler for the start of an element
+    local function start_element_handler (parser, name)
+        current_element_name = name
+        if name == "room" then
+            current_room_object = {}
+        elseif name == "device" then
+            current_device_object = {}
+            current_device_object["gateway_ip"] = gateway.gateway_ip
+            current_device_object["room"] = current_room_object
+        end
+    end
+
+    -- XML parsing handler for the end of an element
+    local function end_element_handler (parser, name)
+        current_element_name = name
+        if name == "room" then
+            current_room_object = nil
+        elseif name == "device" then
+             new_devices[#new_devices+1] = current_device_object
+            current_device_object = nil
+        end
+    end
+
+    -- XML parsing handler for the text of an element
+    local function character_handler (parser, value)
+        if current_device_object and string.find(device_elements_of_interest,"|"..current_element_name.."|") then
+            current_device_object[current_element_name] = value
+        elseif current_room_object and (not current_device_object) and string.find(room_elements_of_interest,"|"..current_element_name.."|") then
+            current_room_object[current_element_name] = value
+        end
+    end
+
+    -- Create a parser
+    local callbacks = {StartElement = start_element_handler, EndElement = end_element_handler, CharacterData = character_handler}
+    local parser = lxp.new(callbacks)
+
+    -- Parse the XML
+    parser:parse(config_xml)
+    parser:parse() -- finishes the document
+    parser:close()
+
+    -- Save the devices, uniquely indexed by did
+    if #new_devices > 0 then
+        for i,device in ipairs(new_devices) do
+            gateway.devices[device.did] = device
+            -- tweak for interpretation of load level for dimmers - always set to zero if device is off
+            if device.state == "0" then
+               device.level = "0"
+            end
+        end
+        gateway.initialized = true
+        gateway.error_text = nil
+    else
+        gateway.error_text = "No devices found at gateway " .. gateway.gateway_ip .. " while parsing " .. config_xml
+    end
+
+    return
+
+end
+
+
+-- ----------------------------------------------------------------------------
+-- Extracts the contents of the first occurrence of the given element 
+-- Note: element_name should not contain angle brackets (i.e. "xxx", not "<xxx>")
+-- ----------------------------------------------------------------------------
+function extract_xml_element_value (xml_string, element_name)
+    local element_value = nil
+    if (xml_string and element_name) then
+       local element_start_pos = string.find (xml_string, "<" .. element_name .. ">")
+       local element_end_pos   = string.find (xml_string, "</" .. element_name .. ">")
+       if (element_start_pos and element_end_pos) then
+           element_value = string.sub (xml_string, element_start_pos+string.len(element_name)+2, element_end_pos-1)
+       end
+    end
+    return element_value
+end
+
+
+-- ----------------------------------------------------------------------------
+-- Helper functions for testing - call devices by name/room
+-- ----------------------------------------------------------------------------
+function tcplighting_get_did (gateway, name, roomname)
+   local did = nil
+   for key,device in pairs(gateway.devices) do
+      if device.name == name and device.room.name == roomname then
+         did = device.did
+      end
+   end
+   return did
+end
+
+function tcplighting_set_target_by_name  (gateway, name, roomname, target_value)
+    return tcplighting_set_target(gateway, tcplighting_get_did (gateway, name, roomname), target_value)
+end
+
+function tcplighting_set_load_level_target_by_name (gateway, name, roomname, load_percent)
+    return tcplighting_set_load_level_target(gateway, tcplighting_get_did (gateway, name, roomname), load_percent)
+end
Index: /tags/v2.0.1/test-ssdp.lua
===================================================================
--- /tags/v2.0.1/test-ssdp.lua	(revision 30)
+++ /tags/v2.0.1/test-ssdp.lua	(revision 30)
@@ -0,0 +1,43 @@
+-- Event handling sequence:
+--
+-- 1. Is  IP specified?
+--     1a. If No IP: trigger auto-discovery
+--     1b. If auto discovery failed set error text and bail
+--     1c. Set IP and set auto-discovered=true
+--
+--
+--
+--
+--
+--
+--
+--
+--
+--
+--
+
+
+
+require ("L_TCPLighting1")
+
+
+    local MAX_SSDP_RETRIES = 5
+
+
+    function discover_gateway ()
+        local gateway_ip   = null
+        local gateway_uuid = null
+        -- since UDP doesn't provide a reliable transport, try the SSDP request few times if necessary
+        for i = 1, MAX_SSDP_RETRIES do
+           gateway_ip, gateway_uuid = tcplighting_gateway_ssdp_search ()
+           if gateway_ip then break end
+        end
+        return gateway_ip, gateway_uuid
+    end
+
+
+    local start_clock = os.clock()
+    print (discover_gateway ())
+    local end_clock = os.clock()
+
+    print ((end_clock-start_clock)*1000, " ms")
Index: /tags/v2.0.1/D_TCPLightingGateway1.json
===================================================================
--- /tags/v2.0.1/D_TCPLightingGateway1.json	(revision 30)
+++ /tags/v2.0.1/D_TCPLightingGateway1.json	(revision 30)
@@ -0,0 +1,106 @@
+{
+    "flashicon":      "icons/generic_sensor.swf",
+    "imgIconBody":     "",
+    "imgIconDimmable": "",
+    "imgIconTurnable": "",
+    "imgIconMin":      "",
+    "imgIconMax":      "",
+    "halloIconsDir":   "pics/hallo",
+    "inScene":         "0",
+    "DisplayStatus":   {},
+    "doc_url":
+       {
+       "doc_language": 1,
+	   "doc_manual":   1,
+       "doc_version":  1,
+       "doc_platform": 0,
+       "doc_page":     "lighting_devices"
+       },
+    "Tabs":
+	   [
+
+       {
+       "Label":    {"lang_tag": "tcp_lighting_gateway", "text": "Gateway"},
+       "Position": "0",
+       "TabType":  "flash",
+	   "SceneGroup":
+	        [
+		    {
+			"id": "1",
+			"top": "1",
+			"left": "0",
+			"x": "1",
+			"y": "1"
+		    },
+		    {
+			"id": "2",
+			"top": "2",
+			"left": "0",
+			"x": "1",
+			"y": "2"
+		    }
+		    ],
+		"ControlGroup": [
+			{
+			"id": "1",
+			"scenegroup": "1",
+			"isSingle": "1"
+			},
+			{
+			"id": "2",
+			"scenegroup": "2",
+			"type": "info"
+			}
+		],
+       "Control":
+		    [
+            {
+            "ControlType": "label",
+            "Label":       {"lang_tag": "pair_desc","text": "Securely Login/Pair with the gateway - Press the green sync button on the gateway first "},
+            "Display":     {"Top": 20,"Left": 50,"Width": 200,"Height": 20}
+            },
+            {
+            "ControlType":  "button",
+            "Label":       {"lang_tag": "pair_command","text": "Pair"},
+			"Display":     {"Top": 37,"Left": 50, "Width": 100, "Height": 20 },
+            "Command":     {"Service": "urn:pmcode-org:serviceId:TCPLightingGateway1", "Action": "Pair", "Parameters": []}
+			},
+            {
+            "ControlType": "label",
+            "Label":       {"lang_tag": "synchronize_desc","text": "Sync to the gateway and configure lights - Gateway ip must be specified in Advanced tab"},
+            "Display":     {"Top": 80,"Left": 50,"Width": 200,"Height": 20}
+            },
+            {
+            "ControlGroup": "1",
+            "ControlType":  "button",
+            "Label":       {"lang_tag": "synchronize_command","text": "Sync"},
+			"Display":     {"Top": 97,"Left": 50, "Width": 100, "Height": 20 },
+            "Command":     {"Service": "urn:pmcode-org:serviceId:TCPLightingGateway1", "Action": "Synchronize", "Parameters": []}
+			},
+            {
+            "ControlType": "label",
+            "Label":       {"lang_tag": "error_text_label","text": "Gateway Errors:"},
+			"Display":     {"Top": 140,"Left": 50,"Width": 200, "Height": 20}
+			},
+            {
+            "ControlGroup": "2",
+            "ControlType": "variable",
+            "Display":     {"Top": 155, "Left": 70, "Width": 200, "Height": 20,
+			                "Service": "urn:pmcode-org:serviceId:TCPLightingGateway1", "Variable": "ErrorText"}
+			}
+            ]
+        },
+
+        {
+        "Label":      {"lang_tag": "advanced","text": "Advanced"},
+        "Position":   "1",
+        "TabType":    "javascript",
+        "ScriptName": "shared.js",
+        "Function":   "advanced_device"
+        }
+    ],
+    "eventList":   {},
+    "eventList2":  [],
+    "DeviceType":  "urn:schemas-pmcode-org:service:TCPLightingGateway:1",
+    "device_type": "urn:schemas-pmcode-org:service:TCPLightingGateway:1"
+}
Index: /tags/v1.0.2/S_TCPLightingGateway1.xml
===================================================================
--- /tags/v1.0.2/S_TCPLightingGateway1.xml	(revision 30)
+++ /tags/v1.0.2/S_TCPLightingGateway1.xml	(revision 30)
@@ -0,0 +1,27 @@
+<?xml version="1.0"?>
+<scpd xmlns="urn:schemas-upnp-org:service-1-0">
+  <specVersion>
+    <major>1</major>
+    <minor>0</minor>
+  </specVersion>
+  <serviceStateTable>
+    <stateVariable sendEvents="no">
+      <name>AutoDiscovered</name>
+      <dataType>boolean</dataType>
+      <defaultValue>0</defaultValue>
+    </stateVariable>
+    <stateVariable sendEvents="yes">
+      <name>ErrorText</name>
+      <dataType>string</dataType>
+    </stateVariable>
+    <stateVariable sendEvents="no">
+      <name>OfflineDeviceNameFormat</name>
+      <dataType>string</dataType>
+    </stateVariable>
+  </serviceStateTable>
+  <actionList>
+    <action>
+      <name>Synchronize</name>
+    </action>
+  </actionList>
+</scpd>
Index: /tags/v1.0.2/D_TCPLightingGateway1.xml
===================================================================
--- /tags/v1.0.2/D_TCPLightingGateway1.xml	(revision 30)
+++ /tags/v1.0.2/D_TCPLightingGateway1.xml	(revision 30)
@@ -0,0 +1,26 @@
+<?xml version="1.0"?>
+<root xmlns="urn:schemas-upnp-org:device-1-0">
+  <specVersion>
+    <major>1</major>
+    <minor>0</minor>
+  </specVersion>
+  <device>
+    <deviceType>urn:schemas-pmcode-org:service:TCPLightingGateway:1</deviceType>
+    <staticJson>D_TCPLightingGateway1.json</staticJson>
+    <serviceList>
+      <service>
+        <serviceType>urn:schemas-pmcode-org:service:TCPLightingGateway:1</serviceType>
+        <serviceId>urn:pmcode-org:serviceId:TCPLightingGateway1</serviceId>
+        <SCPDURL>S_TCPLightingGateway1.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>
+    <implementationList>
+      <implementationFile>I_TCPLighting1.xml</implementationFile>
+    </implementationList>
+  </device>
+</root>
Index: /tags/v1.0.2/D_TCPLighting1.xml
===================================================================
--- /tags/v1.0.2/D_TCPLighting1.xml	(revision 30)
+++ /tags/v1.0.2/D_TCPLighting1.xml	(revision 30)
@@ -0,0 +1,31 @@
+<?xml version="1.0"?>
+<root xmlns="urn:schemas-upnp-org:device-1-0">
+  <specVersion>
+    <major>1</major>
+    <minor>0</minor>
+  </specVersion>
+  <device>
+    <deviceType>urn:schemas-upnp-org:device:DimmableLight:1</deviceType>
+    <staticJson>D_DimmableLight1.json</staticJson>
+    <serviceList>
+      <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-upnp-org:service:Dimming:1</serviceType>
+        <serviceId>urn:upnp-org:serviceId:Dimming1</serviceId>
+        <SCPDURL>S_Dimming1.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>
+    <implementationList>
+      <implementationFile>I_TCPLighting1.xml</implementationFile>
+    </implementationList>
+  </device>
+</root>
Index: /tags/v1.0.2/test.lua
===================================================================
--- /tags/v1.0.2/test.lua	(revision 30)
+++ /tags/v1.0.2/test.lua	(revision 30)
@@ -0,0 +1,25 @@
+local tcplighting = require("L_TCPLighting1")
+local json = require ("json")
+
+
+-- initialize gateway
+gateway = tcplighting_gateway_init(null) --
+print (json.encode(gateway))
+
+
+-- modify a light's intensity
+if (gateway.initialized) then
+    -- generate a random intensity
+    math.randomseed(os.time())
+    math.random(); math.random(); math.random()
+    local load_level_target = math.random(100)
+    --  call the gateway
+    tcplighting_set_load_level_target_by_name(gateway, "Front Entry Light", "Front Entry", load_level_target)
+    tcplighting_set_target_by_name(gateway, "Front Entry Light", "Front Entry", 1)
+    -- poll to get latest status
+    tcplighting_gateway_poll(gateway)
+    print (json.encode(gateway.devices))
+else
+    print ("--- ERROR: " .. gateway.error_text)
+end
+
Index: /tags/v1.0.2/I_TCPLighting1.xml
===================================================================
--- /tags/v1.0.2/I_TCPLighting1.xml	(revision 30)
+++ /tags/v1.0.2/I_TCPLighting1.xml	(revision 30)
@@ -0,0 +1,460 @@
+<?xml version="1.0"?>
+<implementation>
+  <functions>
+
+       -- ---------------------------------------------------------------------
+       -- Constants
+       -- ---------------------------------------------------------------------
+       local GATEWAY_DEVICE_TYPE = "urn:schemas-pmcode-org:service:TCPLightingGateway:1"
+       local GATEWAY_SERVICE_ID  = "urn:pmcode-org:serviceId:TCPLightingGateway1"
+       local HA_DEVICE_TYPE      = "urn:schemas-micasaverde-com:service:HaDevice:1"
+       local HA_SERVICE_ID       = "urn:micasaverde-com:serviceId:HaDevice1"
+       local DIMMER_DEVICE_TYPE  = "urn:schemas-upnp-org:device:DimmableLight:1"
+       local DIMMER_SERVICE_ID   = "urn:upnp-org:serviceId:Dimming1"
+       local SWITCH_DEVICE_TYPE  = "urn:schemas-upnp-org:device:SwitchPower:1"
+       local SWITCH_SERVICE_ID   = "urn:upnp-org:serviceId:SwitchPower1"
+       local LIGHT_DEVICE_FILE   = "D_TCPLighting1.xml"
+       
+       
+       -- ---------------------------------------------------------------------
+       -- Pseudo-Constants: Can be overridden by parent device variables
+       -- ---------------------------------------------------------------------
+       local OFFLINE_DEVICE_NAME_FORMAT = "[Offline] %s"
+
+
+       -- ---------------------------------------------------------------------
+       -- table representing the gateway and its devices
+       -- ---------------------------------------------------------------------
+       local tcplighting_gateway = nil
+
+
+       -- ---------------------------------------------------------------------
+       -- Get a reference to the gateway (will init if necessary)
+       -- ---------------------------------------------------------------------
+	   local function get_gateway (gateway_ip)
+		  if (not tcplighting_gateway) or (not gateway_ip) or (gateway_ip == "") then
+             luup.log ("TCPLighting:  get_gateway - calling tcplighting_gateway_init,  IP=" .. tostring(gateway_ip))
+			 tcplighting_gateway = tcplighting_gateway_init (gateway_ip)
+			 if tcplighting_gateway.error_text then
+				luup.task(tcplighting_gateway.error_text, 2, "TCPLighting", -1)
+			 end
+		  end
+		  return tcplighting_gateway
+	   end
+
+
+       -- ---------------------------------------------------------------------
+       -- Clear the gateway object
+       -- ---------------------------------------------------------------------
+	   local function reset_gateway ()
+		  tcplighting_gateway = nil
+		  return
+	   end
+
+
+       -- ---------------------------------------------------------------------
+       -- Returns true if the device is offline
+       -- ---------------------------------------------------------------------
+       local function is_offline (device)
+           return ((device == nil) or (device.offline and device.offline == "1"))
+       end
+
+
+       -- ---------------------------------------------------------------------
+       -- Returns true if the device has dimming capabilities
+       -- ---------------------------------------------------------------------
+       local function is_dimmer_switch (device)
+           return ((device and device.capability and string.find(device.capability,"switch_multilevel")) or
+                   (device and device.type and string.find(device.type,"multilevel")))
+       end
+
+
+       -- ---------------------------------------------------------------------
+       -- Returns true if the device has on/off capabilities
+       -- ---------------------------------------------------------------------
+       local function is_binary_switch (device)
+           return (device and device.capability and string.find(device.capability,"switch_binary"))
+       end
+
+
+       -- ---------------------------------------------------------------------
+       -- See if we have a matching room number
+       -- ---------------------------------------------------------------------
+       local function room_number (input_room_name)
+          local room_num = 0
+          if input_room_name then
+              for i, room_name in ipairs(luup.rooms) do
+                  if room_name == input_room_name then
+                      room_num = i
+                      break
+                  end
+              end
+          end
+          return room_num
+       end
+
+
+       -- ---------------------------------------------------------------------
+       -- Sets a LUUP device variable, but only if it has no current value
+       -- ---------------------------------------------------------------------
+       local function initialize_variable (service_id, variable_name, new_value, device_number)
+          local current_value = luup.variable_get (service_id, variable_name, device_number)
+          if (not current_value) or (current_value == "") then
+              luup.variable_set (service_id, variable_name, new_value, device_number)
+          end
+       end
+
+
+       -- ---------------------------------------------------------------------
+       -- Sets a LUUP device attribute, but only if it has no current value
+       -- ---------------------------------------------------------------------
+       local function initialize_attr (attr_name, new_value, device_number)
+          local current_value = luup.attr_get (attr_name, device_number)
+          if (not current_value) or (current_value == "") then
+              luup.attr_set (attr_name, new_value, device_number)
+          end
+       end
+
+
+       -- ---------------------------------------------------------------------
+       -- Updates a LUUP device variable, but only if its value has changed
+       -- ---------------------------------------------------------------------
+       local function update_variable (service_id, variable_name, new_value, device_number)
+          local current_value = luup.variable_get (service_id, variable_name, device_number)
+          if (not current_value) or (new_value ~= current_value) then
+              luup.variable_set (service_id, variable_name, new_value, device_number)
+          end
+       end
+
+
+       -- ---------------------------------------------------------------------
+       -- Updates a LUUP device attribute, but only if its value has changed
+       -- ---------------------------------------------------------------------
+       local function update_attr (attr_name, new_value, device_number)
+          local current_value = luup.attr_get (attr_name, device_number)
+          if (not current_value) or (new_value ~= current_value) then
+              luup.attr_set (attr_name, new_value, device_number)
+          end
+       end
+
+
+       -- ---------------------------------------------------------------------
+       -- Update a device to match info from gateway
+       -- ---------------------------------------------------------------------
+       local function update_device_settings (gateway, did, lul_device)
+
+           if not (gateway and did and lul_device) then
+              return
+           end
+
+           local device_number = tonumber(lul_device)
+           local device = gateway.devices[did]
+           
+		   -- indicate device is offline if we don't have data for it 
+           update_attr ("invisible", "", device_number) -- legacy code: prior versions made offline devices invisible
+		   if is_offline(device) then
+		       luup.log ("TCPLighting:  update_device_settings - device with id=" .. lul_device .. " is offline or has no data from gateway")
+		       update_attr ("name", string.format(OFFLINE_DEVICE_NAME_FORMAT, device.name), device_number)
+			   return
+		   else
+		       update_attr ("name", device.name, device_number)
+		   end
+
+           -- update the device
+		   update_attr ("manufacturer", device.manufacturer, device_number)
+		   update_attr ("model",        device.prodmodel,    device_number)
+
+           if device and device.room and device.room.name then
+               local room_num = room_number(device.room.name)
+               if room_num and room_num ~= 0 then
+                  local room_num_string = tostring(room_num)
+                  update_attr ("room_num", room_num_string, device_number)
+                  update_attr ("room",     room_num_string, device_number)
+               else
+                  luup.log("TCPLighting:  update_device_settings - unknown room " .. device.room.name)
+               end
+           end
+
+           if device and is_binary_switch(device) then
+		       update_variable (SWITCH_SERVICE_ID, "Status", device.state, device_number)
+		       update_variable (SWITCH_SERVICE_ID, "Target", device.state, device_number)
+           end
+
+           if device and is_dimmer_switch(device) then
+		      update_variable (DIMMER_SERVICE_ID, "LoadLevelStatus", device.level, device_number)
+		      update_variable (DIMMER_SERVICE_ID, "LoadLevelTarget", device.level, device_number)
+           end
+
+           return
+
+       end
+
+
+       -- ---------------------------------------------------------------------
+       -- Update all of the children of the specified device
+       -- ---------------------------------------------------------------------
+       local function sync_child_devices (gateway, parent_lul_device)
+
+           if not (gateway and parent_lul_device) then
+              return
+           end
+
+           -- poll the gateway for most current device data
+           tcplighting_gateway_poll(gateway)
+
+           -- find matching child devices
+           local parent_device_number = tonumber (parent_lul_device)
+           for k, luup_device in pairs(luup.devices) do
+               if (luup_device.device_num_parent == parent_device_number) and (luup_device.id) then
+                    luup.log ("TCPLighting:  sync_child_devices for " .. parent_lul_device .. " - updating child " .. k .. " " .. luup_device.description)
+                    update_device_settings (gateway, luup_device.id, k)
+               end
+           end
+
+           return
+
+       end
+
+
+       -- ---------------------------------------------------------------------
+       -- Create child devices of gateway if necessary
+       -- ---------------------------------------------------------------------
+       local function create_child_devices (gateway, parent_lul_device)
+
+           if gateway and gateway.initialized then
+
+              luup.log ("TCPLighting:  create_child_devices - gateway is initialized")
+
+			  local parent_device_number = tonumber (parent_lul_device)
+
+              -- Build up a table of child devices, indexed by did, with value being the device type
+			  local child_device_map = {}
+
+			  -- find existing children
+              for k, luup_device in pairs(luup.devices) do
+                  if (luup_device.device_num_parent == parent_device_number) and (luup_device.id) and (luup_device.device_type) then
+					  local did = luup.attr_get ("altid", k)
+					  if (did) then
+				         child_device_map[did] = {name=luup_device.description, device_type = luup_device.device_type}
+					  end
+                  end
+			  end
+
+              -- see if we have new children
+			  local new_devices_found = false
+              for did,gateway_device in pairs(gateway.devices) do
+				  if child_device_map[gateway_device.did] == nil then
+                     if is_dimmer_switch (gateway_device) then
+				        child_device_map[gateway_device.did] = {name=gateway_device.name, device_type=DIMMER_DEVICE_TYPE}
+						new_devices_found = true
+                     elseif is_binary_switch (gateway_device) then
+				        child_device_map[gateway_device.did] = {name=gateway_device.name, device_type=SWITCH_DEVICE_TYPE}
+						new_devices_found = true
+                     end
+				  end
+              end
+
+			  -- resync the combined list of child devices if new ones were found
+              if new_devices_found then
+			     local chdev_context = luup.chdev.start (parent_device_number)
+				 for did,device in pairs(child_device_map) do
+                     luup.log ("TCPLighting:  create_child_devices - creating child with did=" .. did .. ", name=" .. device.name)
+                     luup.chdev.append (parent_device_number, chdev_context, did, device.name, device.device_type, LIGHT_DEVICE_FILE,"","",false)
+                 end
+                 luup.chdev.sync (parent_device_number, chdev_context)
+			  else
+		         luup.log ("TCPLighting:  create_child_devices - no new child devices")
+              end
+
+           end
+
+       end
+
+
+       -- ---------------------------------------------------------------------
+       -- Schedule the next poll
+       -- ---------------------------------------------------------------------
+	   local function schedule_next_poll (lul_device)
+
+           local device_number = tonumber(lul_device)
+
+		   -- make sure the polling variables have values
+		   initialize_variable (HA_SERVICE_ID, "PollingEnabled", "1",  device_number)
+		   initialize_variable (HA_SERVICE_ID, "PollMinDelay",   "60", device_number)
+
+           -- get current polling settings (may be different from above if overriden by user)
+		   local polling_enabled  = luup.variable_get (HA_SERVICE_ID, "PollingEnabled", device_number)
+		   local polling_interval = luup.variable_get (HA_SERVICE_ID, "PollMinDelay",   device_number)
+
+		   -- make a delayed call to the synchronization functions
+		   if polling_enabled == "true" or polling_enabled == "1" then
+		      luup.call_timer("synchronize_gateway", 1, tonumber(polling_interval), "", lul_device)
+			  luup.log ("TCPLighting:  schedule_next_poll - timer set")
+		   end
+
+	   end
+
+
+       -- ---------------------------------------------------------------------
+       -- Contact the gateway and initialize the gateway object
+       -- ---------------------------------------------------------------------
+	   function synchronize_gateway (lul_device)
+
+		   luup.log ("TCPLighting: synchronize_gateway - start")
+
+           local device_number = tonumber(lul_device)
+           local gateway_ip = luup.attr_get ("ip", device_number)
+
+           -- will create and populate a new gateway configuration object if necessary
+           local gateway = get_gateway (gateway_ip)
+
+		   if (gateway.error_text == nil) or (gateway.error_text == "") then
+		   
+               -- Sync up gateway properties
+			   initialize_attr ("name",  "TCP Lighting Gateway", device_number)
+			   if gateway.gateway_uuid then
+			      initialize_attr ("altid", gateway.gateway_uuid,  device_number)
+			   end
+               update_variable (GATEWAY_SERVICE_ID, "ErrorText", "", device_number)
+			   if gateway.autodiscovered then
+				  -- Use IP address from autodiscovery
+		          update_attr     ("ip", gateway.gateway_ip, device_number)
+				  update_variable (GATEWAY_SERVICE_ID, "AutoDiscovered", "1", device_number)
+			   end
+
+			   -- check to see if prefix for offline devices has been overridden
+			   --   some of this code looks redundant, but is required  
+			   --   in case previous versions of plugin were installed
+			   initialize_variable (GATEWAY_SERVICE_ID, "OfflineDeviceNameFormat", OFFLINE_DEVICE_NAME_FORMAT, device_number) 
+			   local offline_device_name_format_override = luup.variable_get (GATEWAY_SERVICE_ID, "OfflineDeviceNameFormat", device_number)
+			   if offline_device_name_format_override then
+			       OFFLINE_DEVICE_NAME_FORMAT = offline_device_name_format_override
+			   end
+
+               -- create and synchronize all children
+		       create_child_devices (gateway, lul_device)
+               sync_child_devices (gateway, lul_device)
+
+		   else
+
+		       -- flag that there was an error
+		       luup.log ("TCPLighting:  synchronize_gateway - error: " .. gateway.error_text)
+               update_variable (GATEWAY_SERVICE_ID, "ErrorText", gateway.error_text, device_number)
+
+               -- clear autodiscovered IP on failures
+               local previously_autodiscovered = luup.variable_get  (GATEWAY_SERVICE_ID, "AutoDiscovered", device_number)
+			   if previously_autodiscovered and previously_autodiscovered == "1" then
+				  -- Clear IP address from autodiscovery
+		          update_attr     ("ip", "", device_number)
+				  update_variable (GATEWAY_SERVICE_ID, "AutoDiscovered", "0", device_number)
+		          luup.log ("TCPLighting:  synchronize_gateway - cleared previously autodiscovered IP = " .. tostring(gateway_ip))
+			   end
+
+		       -- reset the gateway object
+			   reset_gateway ()
+
+           end
+
+		   -- set up polling so that changes made externally to the plugin show up in the UI
+		   schedule_next_poll (lul_device)
+
+		   luup.log ("TCPLighting: synchronize_gateway - end")
+
+           return true
+
+	   end
+
+
+       -- ---------------------------------------------------------------------
+       -- Set the dimming level on a light
+       -- ---------------------------------------------------------------------
+       local function set_load_level_target (lul_device, lul_settings)
+	       local device_number = tonumber(lul_device)
+           local gateway_ip = luup.devices[device_number].ip
+           local did = luup.devices[device_number].id
+		   local gateway = get_gateway (gateway_ip)
+		   if gateway.initialized and did then
+               tcplighting_set_load_level_target (gateway, did, tonumber(lul_settings.newLoadlevelTarget))
+               tcplighting_gateway_poll (gateway)
+			   update_device_settings (gateway, did, lul_device)
+           end
+           return true
+	   end
+
+
+       -- ---------------------------------------------------------------------
+       -- Flips switch on/off
+       -- ---------------------------------------------------------------------
+       local function set_target (lul_device, lul_settings)
+	       local device_number = tonumber(lul_device)
+           local gateway_ip = luup.devices[device_number].ip
+           local did = luup.devices[device_number].id
+		   local gateway = get_gateway (gateway_ip)
+		   if gateway.initialized and did then
+               tcplighting_set_target (gateway, did, tonumber(lul_settings.newTargetValue))
+               tcplighting_gateway_poll (gateway)
+			   update_device_settings (gateway, did, lul_device)
+           end
+           return true
+	   end
+
+
+       -- ---------------------------------------------------------------------
+       -- Plugin startup
+       -- Will initialize the gateway, which in turn creates child devices
+       -- for each light as necessary
+       -- ---------------------------------------------------------------------
+      function tcplighting_plugin_startup (lul_device)
+
+		  luup.log ("TCPLighting: tcplighting_plugin_startup - start")
+
+	      local device_number = tonumber(lul_device)
+          local device_type = luup.devices[device_number].device_type
+
+	      if device_type == GATEWAY_DEVICE_TYPE then
+		     synchronize_gateway (lul_device)
+          else
+             luup.log ("TCPLighting: tcplighting_plugin_startup - not gateway")
+		  end
+
+		  luup.log ("TCPLighting: tcplighting_plugin_startup - end")
+
+	  end
+
+
+  </functions>
+
+  <startup>tcplighting_plugin_startup</startup>
+
+  <files>L_TCPLighting1.lua</files>
+
+  <actionList>
+
+    <action>
+      <serviceId>urn:upnp-org:serviceId:SwitchPower1</serviceId>
+      <name>SetTarget</name>
+      <run>return set_target (lul_device, lul_settings)</run>
+    </action>
+
+    <action>
+      <serviceId>urn:upnp-org:serviceId:Dimming1</serviceId>
+      <name>SetLoadLevelTarget</name>
+      <run>return set_load_level_target (lul_device, lul_settings)</run>
+    </action>
+
+    <action>
+      <serviceId>urn:pmcode-org:serviceId:TCPLightingGateway1</serviceId>
+      <name>Synchronize</name>
+      <run>return synchronize_gateway (lul_device)</run>
+    </action>
+
+	<action>
+      <serviceId>urn:micasaverde-com:serviceId:HaDevice1</serviceId>
+      <name>Poll</name>
+      <run>return synchronize_gateway (lul_device)</run>
+    </action>
+
+   </actionList>
+
+</implementation>
+
Index: /tags/v1.0.2/L_TCPLighting1.lua
===================================================================
--- /tags/v1.0.2/L_TCPLighting1.lua	(revision 30)
+++ /tags/v1.0.2/L_TCPLighting1.lua	(revision 30)
@@ -0,0 +1,303 @@
+-- ----------------------------------------------------------------------------
+-- Search for a Connected by TCP gateway via SSDP
+-- ----------------------------------------------------------------------------
+function tcplighting_gateway_ssdp_search ()
+
+    -- Create an SSDP request - note that the Connected By TCP gateways
+    -- do not appear to respond to ssdp:all requests, so we'll directly
+    -- target the Greenwave Reality gateways that TCPi uses
+    -- See: http://upnp.org/specs/arch/UPnP-arch-DeviceArchitecture-v1.1.pdf
+    local ssdp_headers =
+        {
+        [1] = "M-SEARCH * HTTP/1.1\r\n",
+        [2] = "HOST: 239.255.255.250:1900\r\n",
+        [3] = "MAN: \"ssdp:discover\"\r\n",
+        [4] = "MX: 3\r\n",
+        [5] = "ST: urn:greenwavereality-com:service:gop:1\r\n\r\n"
+        }
+    local ssdp_request = table.concat (ssdp_headers )
+
+    -- Get a UDP socket
+    local http = require("socket.http")
+    local udp = socket.udp()
+
+     -- Send out the SSDP request.  Don't wait forever, they normally respond quickly or not at all.
+    udp:sendto (ssdp_request, "239.255.255.250", 1900)
+    udp:settimeout (3)
+
+    -- Loop over response packets until we get and IP or there is no more data
+    local gateway_ip   = nil
+	local gateway_uuid = nil
+
+    repeat
+        local received_datagram = udp:receive()
+        if received_datagram then
+           -- pull the gateway IP out of the LOCATION header in the response
+           gateway_ip   = string.match (received_datagram, "LOCATION:[%s+]http://([%d%.]+)")
+           -- pull the gateway UID out of the USN header
+           gateway_uuid = string.match (received_datagram, "USN:[%s+]uuid:(%d+)")
+        else
+           break
+        end
+    until (gateway_ip)
+
+    -- return the IP address & UUID of the discovered gateway (or nil if we are SOL)
+    return gateway_ip, gateway_uuid
+
+end
+
+
+-- ----------------------------------------------------------------------------
+-- Implements urn:upnp-org:serviceId:SwitchPower1.SetTarget
+-- ----------------------------------------------------------------------------
+function tcplighting_set_target (gateway, did, target_value)
+
+    local http = require("socket.http")
+
+    http.TIMEOUT = gateway.HTTP_TIMEOUT
+
+    return http.request("http://" .. gateway.devices[did].gateway_ip
+                        .. "/gwr/gop.php",
+                        "cmd=DeviceSendCommand&data="
+                        .. "<gip>"
+                        .. "<version>1</version>"
+                        .. "<token>1234567890</token>"
+                        .. "<did>" .. did .. "</did>"
+                        .. "<value>" .. target_value .. "</value>"
+                        .. "</gip>"
+                        .. "&fmt=xml")
+end
+
+-- ----------------------------------------------------------------------------
+-- Implements urn:upnp-org:serviceId:Dimming1.SetLoadLevelTarget
+-- ----------------------------------------------------------------------------
+function tcplighting_set_load_level_target (gateway, did, load_percent)
+
+    local http = require("socket.http")
+
+    http.TIMEOUT = gateway.HTTP_TIMEOUT
+
+    -- make sure light is on/off to match request
+    if load_percent > 0 then
+        tcplighting_set_target (gateway, did, 1)
+    else
+        tcplighting_set_target (gateway, did, 0)
+    end
+
+    -- change the dimming level
+    return http.request("http://" .. gateway.devices[did].gateway_ip
+                        .. "/gwr/gop.php",
+                        "cmd=DeviceSendCommand&data="
+                        .. "<gip>"
+                        .. "<version>1</version>"
+                        .. "<token>1234567890</token>"
+                        .. "<did>" .. did .. "</did>"
+                        .. "<value>" .. load_percent .. "</value>"
+                        .. "<type>level</type>"
+                        .. "</gip>"
+                        .. "&fmt=xml")
+end
+
+
+-- ----------------------------------------------------------------------------
+-- Loads the gateway configuration
+-- ----------------------------------------------------------------------------
+function tcplighting_gateway_init (gateway_ip)
+
+    local MAX_SSDP_RETRIES = 5
+
+    -- create an empty gateway object
+    local gateway = {}
+    gateway.initialized    = false
+	gateway.autodiscovered = false
+    gateway.error_text     = "TCP Lighting Gateway not yet initialized"  -- error text for last failed operation
+    gateway.gateway_ip     = nil
+    gateway.gateway_uuid   = nil
+    gateway.devices        = {}    -- a cache of device info indexed by unique did
+    gateway.HTTP_TIMEOUT   = 5     -- 5 seconds should be fine on a local network
+
+	-- Attempt auto-discovery via SSDP if IP not specified
+    if (gateway_ip == nil) or (string.len(gateway_ip) == 0) then
+        -- since UDP doesn't provide a reliable transport, try the SSDP request few times if necessary
+        for i = 1, MAX_SSDP_RETRIES do
+           gateway.gateway_ip, gateway.gateway_uuid = tcplighting_gateway_ssdp_search ()
+           if gateway.gateway_ip then
+		      gateway.autodiscovered = true
+		      break
+		   end
+        end
+	else
+	    -- used specified IP
+	    gateway.gateway_ip = gateway_ip
+	end
+
+    -- Bail if we still don't have an IP
+    if not gateway.gateway_ip then
+       gateway.error_text = "No gateway IP address specified, autodiscovery failed"
+       return gateway
+    end
+
+    -- Get the gateway's configuration data
+    tcplighting_gateway_poll (gateway)
+
+    return gateway
+
+end
+
+
+-- ----------------------------------------------------------------------------
+-- Get current values from the gateway
+-- ----------------------------------------------------------------------------
+function tcplighting_gateway_poll (gateway)
+
+    -- Get the gateway's configuration data
+    local result, status = tcplighting_get_gateway_config (gateway)
+
+    -- Check the HTTP status
+    if status ~= 200 then
+       gateway.error_text = "HTTP gateway query request to " .. gateway.gateway_ip .. " failed"
+       if status then gateway.error_text = gateway.error_text .. " - " .. status     end
+       if result then gateway.error_text = gateway.error_text .. " - " .. result     end
+       return
+    end
+
+    -- Make sure something came back
+    if not result then
+       gateway.error_text = "HTTP gateway query request to " .. gateway.gateway_ip .. " did not return any data"
+       return
+    end
+
+    -- Only parse if the HTTP request was successful
+    tcplighting_parse_gateway_config(gateway, result)
+    return
+
+end
+
+
+-- ----------------------------------------------------------------------------
+-- Queries a Connected by TCP Gateway to determine which lights are available
+-- ----------------------------------------------------------------------------
+function tcplighting_get_gateway_config (gateway)
+
+    local http = require("socket.http")
+
+    http.TIMEOUT = gateway.HTTP_TIMEOUT
+
+    return http.request("http://" .. gateway.gateway_ip
+                        .. "/gwr/gop.php",
+                        "cmd=GWRBatch&data="
+                        .. "<gwrcmds>"
+                        .. "<gwrcmd>"
+                        .. "<gcmd>RoomGetCarousel</gcmd>"
+                        .. "<gdata>"
+                        .. "<gip>"
+                        .. "<version>1</version>"
+                        .. "<token>1234567890</token>"
+                        .. "<fields>name,image,imageurl,control,power,product,class,realtype,status</fields>"
+                        .. "</gip>"
+                        .. "</gdata>"
+                        .. "</gwrcmd>"
+                        .. "</gwrcmds>"
+                        .. "&fmt=xml")
+end
+
+
+-- ----------------------------------------------------------------------------
+-- Parse the gateway configuration
+-- ----------------------------------------------------------------------------
+function tcplighting_parse_gateway_config (gateway, config_xml)
+
+    local lxp = require("lxp")
+
+    -- Local collection devices - will be added to master table after parsing
+    local new_devices = {}
+
+    -- enumerate the XML elements that we want to load into our tables
+    local room_elements_of_interest = "|rid|name|desc|"
+    local device_elements_of_interest = "|did|name|desc|state|level|offline|type|rangemin|rangemax|power|poweravg|energy|manufacturer|prodbrand|prodmodel|prodtype|capability|"
+
+    -- used to keep track of the current context during the parsing process
+    local current_room_object = nil
+    local current_device_object = nil
+    local current_element_name = nil
+
+    -- XML parsing handler for the start of an element
+    local function start_element_handler (parser, name)
+        current_element_name = name
+        if name == "room" then
+            current_room_object = {}
+        elseif name == "device" then
+            current_device_object = {}
+            current_device_object["gateway_ip"] = gateway.gateway_ip
+            current_device_object["room"] = current_room_object
+        end
+    end
+
+    -- XML parsing handler for the end of an element
+    local function end_element_handler (parser, name)
+        current_element_name = name
+        if name == "room" then
+            current_room_object = nil
+        elseif name == "device" then
+             new_devices[#new_devices+1] = current_device_object
+            current_device_object = nil
+        end
+    end
+
+    -- XML parsing handler for the text of an element
+    local function character_handler (parser, value)
+        if current_device_object and string.find(device_elements_of_interest,"|"..current_element_name.."|") then
+            current_device_object[current_element_name] = value
+        elseif current_room_object and (not current_device_object) and string.find(room_elements_of_interest,"|"..current_element_name.."|") then
+            current_room_object[current_element_name] = value
+        end
+    end
+
+    -- Create a parser
+    local callbacks = {StartElement = start_element_handler, EndElement = end_element_handler, CharacterData = character_handler}
+    local parser = lxp.new(callbacks)
+
+    -- Parse the XML
+    parser:parse(config_xml)
+    parser:parse() -- finishes the document
+    parser:close()
+
+    -- Save the devices, uniquely indexed by did
+    if #new_devices > 0 then
+        for i,device in ipairs(new_devices) do
+            gateway.devices[device.did] = device
+            -- tweak for interpretation of load level for dimmers - always set to zero if device is off
+            if device.state == "0" then
+               device.level = "0"
+            end
+        end
+        gateway.initialized = true
+        gateway.error_text = nil
+    else
+        gateway.error_text = "No devices found at gateway " .. gateway.gateway_ip .. " while parsing " .. config_xml
+    end
+
+    return
+
+end
+
+-- ----------------------------------------------------------------------------
+-- Helper functions for testing - call devices by name/room
+-- ----------------------------------------------------------------------------
+function tcplighting_get_did (gateway, name, roomname)
+   local did = nil
+   for key,device in pairs(gateway.devices) do
+      if device.name == name and device.room.name == roomname then
+         did = device.did
+      end
+   end
+   return did
+end
+
+function tcplighting_set_target_by_name  (gateway, name, roomname, target_value)
+    return tcplighting_set_target(gateway, tcplighting_get_did (gateway, name, roomname), target_value)
+end
+
+function tcplighting_set_load_level_target_by_name (gateway, name, roomname, load_percent)
+    return tcplighting_set_load_level_target(gateway, tcplighting_get_did (gateway, name, roomname), load_percent)
+end
Index: /tags/v1.0.2/test-ssdp.lua
===================================================================
--- /tags/v1.0.2/test-ssdp.lua	(revision 30)
+++ /tags/v1.0.2/test-ssdp.lua	(revision 30)
@@ -0,0 +1,43 @@
+-- Event handling sequence:
+--
+-- 1. Is  IP specified?
+--     1a. If No IP: trigger auto-discovery
+--     1b. If auto discovery failed set error text and bail
+--     1c. Set IP and set auto-discovered=true
+--
+--
+--
+--
+--
+--
+--
+--
+--
+--
+--
+
+
+
+require ("L_TCPLighting1")
+
+
+    local MAX_SSDP_RETRIES = 5
+
+
+    function discover_gateway ()
+        local gateway_ip   = null
+        local gateway_uuid = null
+        -- since UDP doesn't provide a reliable transport, try the SSDP request few times if necessary
+        for i = 1, MAX_SSDP_RETRIES do
+           gateway_ip, gateway_uuid = tcplighting_gateway_ssdp_search ()
+           if gateway_ip then break end
+        end
+        return gateway_ip, gateway_uuid
+    end
+
+
+    local start_clock = os.clock()
+    print (discover_gateway ())
+    local end_clock = os.clock()
+
+    print ((end_clock-start_clock)*1000, " ms")
Index: /tags/v1.0.2/D_TCPLightingGateway1.json
===================================================================
--- /tags/v1.0.2/D_TCPLightingGateway1.json	(revision 30)
+++ /tags/v1.0.2/D_TCPLightingGateway1.json	(revision 30)
@@ -0,0 +1,95 @@
+{
+    "flashicon":      "icons/generic_sensor.swf",
+    "imgIconBody":     "",
+    "imgIconDimmable": "",
+    "imgIconTurnable": "",
+    "imgIconMin":      "",
+    "imgIconMax":      "",
+    "halloIconsDir":   "pics/hallo",
+    "inScene":         "0",
+    "DisplayStatus":   {},
+    "doc_url":
+       {
+       "doc_language": 1,
+	   "doc_manual":   1,
+       "doc_version":  1,
+       "doc_platform": 0,
+       "doc_page":     "lighting_devices"
+       },
+    "Tabs":
+	   [
+
+       {
+       "Label":    {"lang_tag": "tcp_lighting_gateway", "text": "Gateway"},
+       "Position": "0",
+       "TabType":  "flash",
+	   "SceneGroup":
+	        [
+		    {
+			"id": "1",
+			"top": "1",
+			"left": "0",
+			"x": "1",
+			"y": "1"
+		    },
+		    {
+			"id": "2",
+			"top": "2",
+			"left": "0",
+			"x": "1",
+			"y": "2"
+		    }
+		    ],
+		"ControlGroup": [
+			{
+			"id": "1",
+			"scenegroup": "1",
+			"isSingle": "1"
+			},
+			{
+			"id": "2",
+			"scenegroup": "2",
+			"type": "info"
+			}
+		],
+       "Control":
+		    [
+            {
+            "ControlType": "label",
+            "Label":       {"lang_tag": "synchronize_desc","text": "Sync to the gateway and configure lights - Gateway ip must be specified in Advanced tab"},
+            "Display":     {"Top": 20,"Left": 50,"Width": 200,"Height": 20}
+            },
+            {
+            "ControlGroup": "1",
+            "ControlType":  "button",
+            "Label":       {"lang_tag": "synchronize_command","text": "Sync"},
+			"Display":     {"Top": 37,"Left": 50, "Width": 100, "Height": 20 },
+            "Command":     {"Service": "urn:pmcode-org:serviceId:TCPLightingGateway1", "Action": "Synchronize", "Parameters": []}
+			},
+            {
+            "ControlType": "label",
+            "Label":       {"lang_tag": "error_text_label","text": "Gateway Errors:"},
+			"Display":     {"Top": 80,"Left": 50,"Width": 200, "Height": 20}
+			},
+            {
+            "ControlGroup": "2",
+            "ControlType": "variable",
+            "Display":     {"Top": 95, "Left": 70, "Width": 200, "Height": 20,
+			                "Service": "urn:pmcode-org:serviceId:TCPLightingGateway1", "Variable": "ErrorText"}
+			}
+            ]
+        },
+
+        {
+        "Label":      {"lang_tag": "advanced","text": "Advanced"},
+        "Position":   "1",
+        "TabType":    "javascript",
+        "ScriptName": "shared.js",
+        "Function":   "advanced_device"
+        }
+    ],
+    "eventList":   {},
+    "eventList2":  [],
+    "DeviceType":  "urn:schemas-pmcode-org:service:TCPLightingGateway:1",
+    "device_type": "urn:schemas-pmcode-org:service:TCPLightingGateway:1"
+}
Index: /tags/v2.0/S_TCPLightingGateway1.xml
===================================================================
--- /tags/v2.0/S_TCPLightingGateway1.xml	(revision 30)
+++ /tags/v2.0/S_TCPLightingGateway1.xml	(revision 30)
@@ -0,0 +1,30 @@
+<?xml version="1.0"?>
+<scpd xmlns="urn:schemas-upnp-org:service-1-0">
+  <specVersion>
+    <major>1</major>
+    <minor>0</minor>
+  </specVersion>
+  <serviceStateTable>
+    <stateVariable sendEvents="no">
+      <name>AutoDiscovered</name>
+      <dataType>boolean</dataType>
+      <defaultValue>0</defaultValue>
+    </stateVariable>
+    <stateVariable sendEvents="yes">
+      <name>ErrorText</name>
+      <dataType>string</dataType>
+    </stateVariable>
+    <stateVariable sendEvents="no">
+      <name>OfflineDeviceNameFormat</name>
+      <dataType>string</dataType>
+    </stateVariable>
+  </serviceStateTable>
+  <actionList>
+    <action>
+      <name>Pair</name>
+    </action>
+    <action>
+      <name>Synchronize</name>
+    </action>
+  </actionList>
+</scpd>
Index: /tags/v2.0/D_TCPLightingGateway1.xml
===================================================================
--- /tags/v2.0/D_TCPLightingGateway1.xml	(revision 30)
+++ /tags/v2.0/D_TCPLightingGateway1.xml	(revision 30)
@@ -0,0 +1,26 @@
+<?xml version="1.0"?>
+<root xmlns="urn:schemas-upnp-org:device-1-0">
+  <specVersion>
+    <major>1</major>
+    <minor>0</minor>
+  </specVersion>
+  <device>
+    <deviceType>urn:schemas-pmcode-org:service:TCPLightingGateway:1</deviceType>
+    <staticJson>D_TCPLightingGateway1.json</staticJson>
+    <serviceList>
+      <service>
+        <serviceType>urn:schemas-pmcode-org:service:TCPLightingGateway:1</serviceType>
+        <serviceId>urn:pmcode-org:serviceId:TCPLightingGateway1</serviceId>
+        <SCPDURL>S_TCPLightingGateway1.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>
+    <implementationList>
+      <implementationFile>I_TCPLighting1.xml</implementationFile>
+    </implementationList>
+  </device>
+</root>
Index: /tags/v2.0/D_TCPLighting1.xml
===================================================================
--- /tags/v2.0/D_TCPLighting1.xml	(revision 30)
+++ /tags/v2.0/D_TCPLighting1.xml	(revision 30)
@@ -0,0 +1,31 @@
+<?xml version="1.0"?>
+<root xmlns="urn:schemas-upnp-org:device-1-0">
+  <specVersion>
+    <major>1</major>
+    <minor>0</minor>
+  </specVersion>
+  <device>
+    <deviceType>urn:schemas-upnp-org:device:DimmableLight:1</deviceType>
+    <staticJson>D_DimmableLight1.json</staticJson>
+    <serviceList>
+      <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-upnp-org:service:Dimming:1</serviceType>
+        <serviceId>urn:upnp-org:serviceId:Dimming1</serviceId>
+        <SCPDURL>S_Dimming1.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>
+    <implementationList>
+      <implementationFile>I_TCPLighting1.xml</implementationFile>
+    </implementationList>
+  </device>
+</root>
Index: /tags/v2.0/test.lua
===================================================================
--- /tags/v2.0/test.lua	(revision 30)
+++ /tags/v2.0/test.lua	(revision 30)
@@ -0,0 +1,25 @@
+local tcplighting = require("L_TCPLighting1")
+local json = require ("json")
+
+
+-- initialize gateway
+gateway = tcplighting_gateway_init(null) --
+print (json.encode(gateway))
+
+
+-- modify a light's intensity
+if (gateway.initialized) then
+    -- generate a random intensity
+    math.randomseed(os.time())
+    math.random(); math.random(); math.random()
+    local load_level_target = math.random(100)
+    --  call the gateway
+    tcplighting_set_load_level_target_by_name(gateway, "Front Entry Light", "Front Entry", load_level_target)
+    tcplighting_set_target_by_name(gateway, "Front Entry Light", "Front Entry", 1)
+    -- poll to get latest status
+    tcplighting_gateway_poll(gateway)
+    print (json.encode(gateway.devices))
+else
+    print ("--- ERROR: " .. gateway.error_text)
+end
+
Index: /tags/v2.0/I_TCPLighting1.xml
===================================================================
--- /tags/v2.0/I_TCPLighting1.xml	(revision 30)
+++ /tags/v2.0/I_TCPLighting1.xml	(revision 30)
@@ -0,0 +1,549 @@
+<?xml version="1.0"?>
+<implementation>
+  <functions>
+
+       -- ---------------------------------------------------------------------
+       -- Constants
+       -- ---------------------------------------------------------------------
+       local GATEWAY_DEVICE_TYPE = "urn:schemas-pmcode-org:service:TCPLightingGateway:1"
+       local GATEWAY_SERVICE_ID  = "urn:pmcode-org:serviceId:TCPLightingGateway1"
+       local HA_DEVICE_TYPE      = "urn:schemas-micasaverde-com:service:HaDevice:1"
+       local HA_SERVICE_ID       = "urn:micasaverde-com:serviceId:HaDevice1"
+       local DIMMER_DEVICE_TYPE  = "urn:schemas-upnp-org:device:DimmableLight:1"
+       local DIMMER_SERVICE_ID   = "urn:upnp-org:serviceId:Dimming1"
+       local SWITCH_DEVICE_TYPE  = "urn:schemas-upnp-org:device:SwitchPower:1"
+       local SWITCH_SERVICE_ID   = "urn:upnp-org:serviceId:SwitchPower1"
+       local LIGHT_DEVICE_FILE   = "D_TCPLighting1.xml"
+       
+       
+       -- ---------------------------------------------------------------------
+       -- Pseudo-Constants: Can be overridden by parent device variables
+       -- ---------------------------------------------------------------------
+       local OFFLINE_DEVICE_NAME_FORMAT = "[Offline] %s"
+
+
+       -- ---------------------------------------------------------------------
+       -- table representing the gateway and its devices
+       -- ---------------------------------------------------------------------
+       local tcplighting_gateway = nil
+
+
+       -- ---------------------------------------------------------------------
+       -- Get a reference to the gateway (will init if necessary)
+       -- ---------------------------------------------------------------------
+	   local function get_gateway (gateway_ip)
+		  if (not tcplighting_gateway) or (not gateway_ip) or (gateway_ip == "") then
+             luup.log ("TCPLighting:  get_gateway - calling tcplighting_gateway_init,  IP=" .. tostring(gateway_ip))
+			 tcplighting_gateway = tcplighting_gateway_init (gateway_ip)
+			 if tcplighting_gateway.error_text then
+				luup.task(tcplighting_gateway.error_text, 2, "TCPLighting", -1)
+			 end
+		  end
+		  return tcplighting_gateway
+	   end
+
+
+       -- ---------------------------------------------------------------------
+       -- Clear the gateway object
+       -- ---------------------------------------------------------------------
+	   local function reset_gateway ()
+		  tcplighting_gateway = nil
+		  return
+	   end
+
+
+       -- ---------------------------------------------------------------------
+       -- Returns true if the device is offline
+       -- ---------------------------------------------------------------------
+       local function is_offline (device)
+           return ((not device) or (device.offline and device.offline == "1"))
+       end
+
+
+       -- ---------------------------------------------------------------------
+       -- Returns true if the device has dimming capabilities
+       -- ---------------------------------------------------------------------
+       local function is_dimmer_switch (device)
+           return (device and device.rangemax and device.rangemax == "99")
+       end
+
+
+       -- ---------------------------------------------------------------------
+       -- Returns true if the device has on/off capabilities
+       -- ---------------------------------------------------------------------
+       local function is_binary_switch (device)
+           return (device and device.capability and string.find(device.capability,"switch_binary"))
+       end
+
+
+       -- ---------------------------------------------------------------------
+       -- See if we have a matching room number
+       -- ---------------------------------------------------------------------
+       local function room_number (input_room_name)
+          local room_num = 0
+          if input_room_name then
+              for i, room_name in ipairs(luup.rooms) do
+                  if room_name == input_room_name then
+                      room_num = i
+                      break
+                  end
+              end
+          end
+          return room_num
+       end
+
+
+       -- ---------------------------------------------------------------------
+       -- Sets a LUUP device variable, but only if it has no current value
+       -- ---------------------------------------------------------------------
+       local function initialize_variable (service_id, variable_name, new_value, device_number)
+          local current_value = luup.variable_get (service_id, variable_name, device_number)
+          if (not current_value) or (current_value == "") then
+              luup.variable_set (service_id, variable_name, new_value, device_number)
+          end
+       end
+
+
+       -- ---------------------------------------------------------------------
+       -- Sets a LUUP device attribute, but only if it has no current value
+       -- ---------------------------------------------------------------------
+       local function initialize_attr (attr_name, new_value, device_number)
+          local current_value = luup.attr_get (attr_name, device_number)
+          if (not current_value) or (current_value == "") then
+              luup.attr_set (attr_name, new_value, device_number)
+          end
+       end
+
+
+       -- ---------------------------------------------------------------------
+       -- Updates a LUUP device variable, but only if its value has changed
+       -- ---------------------------------------------------------------------
+       local function update_variable (service_id, variable_name, new_value, device_number)
+          local current_value = luup.variable_get (service_id, variable_name, device_number)
+          if (not current_value) or (new_value ~= current_value) then
+              luup.variable_set (service_id, variable_name, new_value, device_number)
+          end
+       end
+
+
+       -- ---------------------------------------------------------------------
+       -- Updates a LUUP device attribute, but only if its value has changed
+       -- ---------------------------------------------------------------------
+       local function update_attr (attr_name, new_value, device_number)
+          local current_value = luup.attr_get (attr_name, device_number)
+          if (not current_value) or (new_value ~= current_value) then
+		      luup.log ("TCPLighting:  update_attr - device_number=" .. device_number)
+		      luup.log ("TCPLighting:  update_attr - attr_name=" .. attr_name)
+		      luup.log ("TCPLighting:  update_attr - new_value=" .. new_value)
+              luup.attr_set (attr_name, new_value, device_number)
+          end
+       end
+
+
+       -- ---------------------------------------------------------------------
+       -- Update a device to match info from gateway
+       -- ---------------------------------------------------------------------
+       local function update_device_settings (gateway, did, lul_device)
+
+           if not (gateway and did and lul_device) then
+              return
+           end
+
+           local device_number = tonumber(lul_device)
+           local device = gateway.devices[did]
+           
+		   -- indicate if we don't have data for device
+		   if (not device) then
+		       luup.log ("TCPLighting:  update_device_settings - device with id=" .. lul_device .. " has no data from gateway")
+			   return
+		   end
+           
+           
+		   -- indicate device is offline if we don't have data for it 
+           update_attr ("invisible", "", device_number) -- legacy code: prior versions made offline devices invisible
+		   if is_offline(device) then
+		       luup.log ("TCPLighting:  update_device_settings - device with id=" .. lul_device .. " is offline")
+		       update_attr ("name", string.format(OFFLINE_DEVICE_NAME_FORMAT, device.name), device_number)
+			   return
+		   else
+		       update_attr ("name", device.name, device_number)
+		   end
+
+           -- update the device
+		   update_attr ("manufacturer", device.manufacturer, device_number)
+		   update_attr ("model",        device.prodmodel,    device_number)
+
+           if device and device.room and device.room.name then
+               local room_num = room_number(device.room.name)
+               if room_num and room_num ~= 0 then
+                  local room_num_string = tostring(room_num)
+                  update_attr ("room_num", room_num_string, device_number)
+                  update_attr ("room",     room_num_string, device_number)
+               else
+                  luup.log("TCPLighting:  update_device_settings - unknown room " .. device.room.name)
+               end
+           end
+
+           if device and is_binary_switch(device) then
+		       update_variable (SWITCH_SERVICE_ID, "Status", device.state, device_number)
+		       update_variable (SWITCH_SERVICE_ID, "Target", device.state, device_number)
+           end
+
+           if device and is_dimmer_switch(device) then
+		      update_variable (DIMMER_SERVICE_ID, "LoadLevelStatus", device.level, device_number)
+		      update_variable (DIMMER_SERVICE_ID, "LoadLevelTarget", device.level, device_number)
+           end
+
+           return
+
+       end
+
+
+       -- ---------------------------------------------------------------------
+       -- Update all of the children of the specified device
+       -- ---------------------------------------------------------------------
+       local function sync_child_devices (gateway, parent_lul_device)
+
+           if not (gateway and parent_lul_device) then
+              return
+           end
+
+           -- poll the gateway for most current device data
+           tcplighting_gateway_poll(gateway)
+
+           -- find matching child devices
+           local parent_device_number = tonumber (parent_lul_device)
+           for k, luup_device in pairs(luup.devices) do
+               if (luup_device.device_num_parent == parent_device_number) and (luup_device.id) then
+                    luup.log ("TCPLighting:  sync_child_devices for " .. parent_lul_device .. " - updating child " .. k .. " " .. luup_device.description)
+                    update_device_settings (gateway, luup_device.id, k)
+               end
+           end
+
+           return
+
+       end
+
+
+       -- ---------------------------------------------------------------------
+       -- Create child devices of gateway if necessary
+       -- ---------------------------------------------------------------------
+       local function create_child_devices (gateway, parent_lul_device)
+
+           if gateway and gateway.initialized then
+
+              luup.log ("TCPLighting:  create_child_devices - gateway is initialized")
+
+			  local parent_device_number = tonumber (parent_lul_device)
+
+              -- Build up a table of child devices, indexed by did, with value being the device type
+			  local child_device_map = {}
+
+			  -- find existing children
+              for k, luup_device in pairs(luup.devices) do
+                  if (luup_device.device_num_parent == parent_device_number) and (luup_device.id) and (luup_device.device_type) then
+					  local did = luup.attr_get ("altid", k)
+					  if (did) then
+				         child_device_map[did] = {name=luup_device.description, device_type = luup_device.device_type}
+					  end
+                  end
+			  end
+
+              -- see if we have new children
+			  local new_devices_found = false
+              for did,gateway_device in pairs(gateway.devices) do
+				  if child_device_map[gateway_device.did] == nil then
+                     if is_dimmer_switch (gateway_device) then
+				        child_device_map[gateway_device.did] = {name=gateway_device.name, device_type=DIMMER_DEVICE_TYPE}
+						new_devices_found = true
+                     elseif is_binary_switch (gateway_device) then
+				        child_device_map[gateway_device.did] = {name=gateway_device.name, device_type=SWITCH_DEVICE_TYPE}
+						new_devices_found = true
+                     end
+				  end
+              end
+
+			  -- resync the combined list of child devices if new ones were found
+              if new_devices_found then
+			     local chdev_context = luup.chdev.start (parent_device_number)
+				 for did,device in pairs(child_device_map) do
+                     luup.log ("TCPLighting:  create_child_devices - creating child with did=" .. did .. ", name=" .. device.name)
+                     luup.chdev.append (parent_device_number, chdev_context, did, device.name, device.device_type, LIGHT_DEVICE_FILE,"","",false)
+                 end
+                 luup.chdev.sync (parent_device_number, chdev_context)
+			  else
+		         luup.log ("TCPLighting:  create_child_devices - no new child devices")
+              end
+
+           end
+
+       end
+
+
+       -- ---------------------------------------------------------------------
+       -- Schedule the next poll
+       -- ---------------------------------------------------------------------
+	   local function schedule_next_poll (lul_device)
+
+           local device_number = tonumber(lul_device)
+
+		   -- make sure the polling variables have values
+		   initialize_variable (HA_SERVICE_ID, "PollingEnabled", "1",  device_number)
+		   initialize_variable (HA_SERVICE_ID, "PollMinDelay",   "60", device_number)
+
+           -- get current polling settings (may be different from above if overriden by user)
+		   local polling_enabled  = luup.variable_get (HA_SERVICE_ID, "PollingEnabled", device_number)
+		   local polling_interval = luup.variable_get (HA_SERVICE_ID, "PollMinDelay",   device_number)
+
+		   -- make a delayed call to the synchronization functions
+		   if polling_enabled == "true" or polling_enabled == "1" then
+		      luup.call_timer("synchronize_gateway", 1, tonumber(polling_interval), "", lul_device)
+			  luup.log ("TCPLighting:  schedule_next_poll - timer set")
+		   end
+
+	   end
+
+
+       -- ---------------------------------------------------------------------
+       -- Authenticate to the gateway and obtain a token that can be used
+       -- for subsequent calls
+       -- ---------------------------------------------------------------------
+	   function pair_with_gateway (lul_device)
+
+		   luup.log ("TCPLighting: pair_with_gateway - start")
+
+           local device_number = tonumber(lul_device)
+
+           -- will create and populate a new gateway configuration object if necessary
+           local gateway_ip = luup.attr_get ("ip", device_number)
+           local gateway    = get_gateway (gateway_ip)
+
+           -- Assign gateway UUID if necessary
+           if (not gateway.gateway_uuid) then
+               local gateway_uuid = luup.variable_get  (GATEWAY_SERVICE_ID, "GatewayUUID", device_number)
+               if (gateway_uuid) then
+                   gateway.gateway_uuid = gateway_uuid
+               else
+                   -- Assign gateway UUID from the device UDN 
+                   local device_udn = luup.attr_get ("local_udn", device_number)
+                   if (device_udn) and (device_udn ~= "") and (string.len(device_udn) > 5) then
+                       gateway.gateway_uuid = string.sub(device_udn,6) -- take everything after "uuid:" prefix
+                   end
+               end
+           end
+
+           -- clear the current token
+           update_variable (GATEWAY_SERVICE_ID, "GatewayToken", "", device_number)
+
+           -- login to the gateway
+           luup.log ("TCPLighting:  pair_with_gateway - IP=" .. tostring(gateway.gateway_ip))
+           luup.log ("TCPLighting:  pair_with_gateway - UUID=" .. tostring(gateway.gateway_uuid))
+           tcplighting_gateway_pair (gateway)
+           luup.log ("TCPLighting:  pair_with_gateway - Token=" .. tostring(gateway.gateway_token))
+
+		   if (gateway.gateway_token) then
+		   
+		       -- update device variables
+               update_variable (GATEWAY_SERVICE_ID, "GatewayToken", gateway.gateway_token, device_number)
+               update_variable (GATEWAY_SERVICE_ID, "GatewayUUID",  gateway.gateway_uuid,  device_number)
+               update_variable (GATEWAY_SERVICE_ID, "ErrorText",    gateway.error_text,    device_number)
+               
+               -- initiate a sync
+		       luup.log ("TCPLighting: pair_with_gateway - synchronizing")
+               return synchronize_gateway (lul_device)
+
+		   else
+
+		       -- flag that there was an error
+		       luup.log ("TCPLighting:  pair_with_gateway - error: " .. gateway.error_text)
+               update_variable (GATEWAY_SERVICE_ID, "ErrorText", gateway.error_text, device_number)
+
+           end
+
+		   luup.log ("TCPLighting: pair_with_gateway - end")
+
+           return true
+
+	   end
+
+
+       -- ---------------------------------------------------------------------
+       -- Contact the gateway and initialize the gateway object
+       -- ---------------------------------------------------------------------
+	   function synchronize_gateway (lul_device)
+
+		   luup.log ("TCPLighting: synchronize_gateway - start")
+
+           local device_number = tonumber(lul_device)
+           local gateway_ip = luup.attr_get ("ip", device_number)
+
+           -- will create and populate a new gateway configuration object if necessary
+           local gateway = get_gateway (gateway_ip)
+
+           -- Make sure we have a gateway token
+           local gateway_token = luup.variable_get  (GATEWAY_SERVICE_ID, "GatewayToken", device_number)
+           if (not gateway_token) or (gateway_token == "") then
+               gateway.error_text = "No gateway token assigned, use device configuration UI to pair first"
+               update_variable (GATEWAY_SERVICE_ID, "ErrorText", gateway.error_text, device_number)
+               return true
+           end
+           if (gateway_token) then
+               gateway.gateway_token = gateway_token
+           end
+
+		   if (gateway.error_text == nil) or (gateway.error_text == "") then
+		   
+               -- Sync up gateway properties
+			   initialize_attr ("name",  "TCP Lighting Gateway", device_number)
+			   if gateway.ssdp_uuid then
+			      initialize_attr ("altid", gateway.ssdp_uuid,  device_number)
+			   end
+               update_variable (GATEWAY_SERVICE_ID, "ErrorText", "", device_number)
+			   if gateway.autodiscovered then
+				  -- Use IP address from autodiscovery
+		          update_attr     ("ip", gateway.gateway_ip, device_number)
+				  update_variable (GATEWAY_SERVICE_ID, "AutoDiscovered", "1", device_number)
+			   end
+
+			   -- check to see if prefix for offline devices has been overridden
+			   --   some of this code looks redundant, but is required  
+			   --   in case previous versions of plugin were installed
+			   initialize_variable (GATEWAY_SERVICE_ID, "OfflineDeviceNameFormat", OFFLINE_DEVICE_NAME_FORMAT, device_number) 
+			   local offline_device_name_format_override = luup.variable_get (GATEWAY_SERVICE_ID, "OfflineDeviceNameFormat", device_number)
+			   if offline_device_name_format_override then
+			       OFFLINE_DEVICE_NAME_FORMAT = offline_device_name_format_override
+			   end
+
+               -- create and synchronize all children
+		       create_child_devices (gateway, lul_device)
+               sync_child_devices (gateway, lul_device)
+
+		   else
+
+		       -- flag that there was an error
+		       luup.log ("TCPLighting:  synchronize_gateway - error: " .. gateway.error_text)
+               update_variable (GATEWAY_SERVICE_ID, "ErrorText", gateway.error_text, device_number)
+
+               -- clear autodiscovered IP on failures
+               local previously_autodiscovered = luup.variable_get  (GATEWAY_SERVICE_ID, "AutoDiscovered", device_number)
+			   if previously_autodiscovered and previously_autodiscovered == "1" then
+				  -- Clear IP address from autodiscovery
+		          update_attr     ("ip", "", device_number)
+				  update_variable (GATEWAY_SERVICE_ID, "AutoDiscovered", "0", device_number)
+		          luup.log ("TCPLighting:  synchronize_gateway - cleared previously autodiscovered IP = " .. tostring(gateway_ip))
+			   end
+
+		       -- reset the gateway object
+			   reset_gateway ()
+
+           end
+
+		   -- set up polling so that changes made externally to the plugin show up in the UI
+		   schedule_next_poll (lul_device)
+
+		   luup.log ("TCPLighting: synchronize_gateway - end")
+
+           return true
+
+	   end
+
+
+       -- ---------------------------------------------------------------------
+       -- Set the dimming level on a light
+       -- ---------------------------------------------------------------------
+       local function set_load_level_target (lul_device, lul_settings)
+	       local device_number = tonumber(lul_device)
+           local gateway_ip = luup.devices[device_number].ip
+           local did = luup.devices[device_number].id
+		   local gateway = get_gateway (gateway_ip)
+		   if gateway.initialized and did then
+               tcplighting_set_load_level_target (gateway, did, tonumber(lul_settings.newLoadlevelTarget))
+               tcplighting_gateway_poll (gateway)
+			   update_device_settings (gateway, did, lul_device)
+           end
+           return true
+	   end
+
+
+       -- ---------------------------------------------------------------------
+       -- Flips switch on/off
+       -- ---------------------------------------------------------------------
+       local function set_target (lul_device, lul_settings)
+	       local device_number = tonumber(lul_device)
+           local gateway_ip = luup.devices[device_number].ip
+           local did = luup.devices[device_number].id
+		   local gateway = get_gateway (gateway_ip)
+		   if gateway.initialized and did then
+               tcplighting_set_target (gateway, did, tonumber(lul_settings.newTargetValue))
+               tcplighting_gateway_poll (gateway)
+			   update_device_settings (gateway, did, lul_device)
+           end
+           return true
+	   end
+
+
+       -- ---------------------------------------------------------------------
+       -- Plugin startup
+       -- Will initialize the gateway, which in turn creates child devices
+       -- for each light as necessary
+       -- ---------------------------------------------------------------------
+      function tcplighting_plugin_startup (lul_device)
+
+		  luup.log ("TCPLighting: tcplighting_plugin_startup - start")
+
+	      local device_number = tonumber(lul_device)
+          local device_type = luup.devices[device_number].device_type
+
+	      if device_type == GATEWAY_DEVICE_TYPE then
+		     synchronize_gateway (lul_device)
+          else
+             luup.log ("TCPLighting: tcplighting_plugin_startup - not gateway")
+		  end
+
+		  luup.log ("TCPLighting: tcplighting_plugin_startup - end")
+
+	  end
+
+
+  </functions>
+
+  <startup>tcplighting_plugin_startup</startup>
+
+  <files>L_TCPLighting1.lua</files>
+
+  <actionList>
+
+    <action>
+      <serviceId>urn:upnp-org:serviceId:SwitchPower1</serviceId>
+      <name>SetTarget</name>
+      <run>return set_target (lul_device, lul_settings)</run>
+    </action>
+
+    <action>
+      <serviceId>urn:upnp-org:serviceId:Dimming1</serviceId>
+      <name>SetLoadLevelTarget</name>
+      <run>return set_load_level_target (lul_device, lul_settings)</run>
+    </action>
+
+    <action>
+      <serviceId>urn:pmcode-org:serviceId:TCPLightingGateway1</serviceId>
+      <name>Pair</name>
+      <run>return pair_with_gateway (lul_device)</run>
+    </action>
+
+    <action>
+      <serviceId>urn:pmcode-org:serviceId:TCPLightingGateway1</serviceId>
+      <name>Synchronize</name>
+      <run>return synchronize_gateway (lul_device)</run>
+    </action>
+
+	<action>
+      <serviceId>urn:micasaverde-com:serviceId:HaDevice1</serviceId>
+      <name>Poll</name>
+      <run>return synchronize_gateway (lul_device)</run>
+    </action>
+
+   </actionList>
+
+</implementation>
+
Index: /tags/v2.0/L_TCPLighting1.lua
===================================================================
--- /tags/v2.0/L_TCPLighting1.lua	(revision 30)
+++ /tags/v2.0/L_TCPLighting1.lua	(revision 30)
@@ -0,0 +1,402 @@
+-- ----------------------------------------------------------------------------
+-- Search for a Connected by TCP gateway via SSDP
+-- ----------------------------------------------------------------------------
+function tcplighting_gateway_ssdp_search ()
+
+    -- Create an SSDP request - note that the Connected By TCP gateways
+    -- do not appear to respond to ssdp:all requests, so we'll directly
+    -- target the Greenwave Reality gateways that TCPi uses
+    -- See: http://upnp.org/specs/arch/UPnP-arch-DeviceArchitecture-v1.1.pdf
+    local ssdp_headers =
+        {
+        [1] = "M-SEARCH * HTTP/1.1\r\n",
+        [2] = "HOST: 239.255.255.250:1900\r\n",
+        [3] = "MAN: \"ssdp:discover\"\r\n",
+        [4] = "MX: 3\r\n",
+        [5] = "ST: urn:greenwavereality-com:service:gop:1\r\n\r\n"
+        }
+    local ssdp_request = table.concat (ssdp_headers )
+
+    -- Get a UDP socket
+    local http = require("socket.http")
+    local udp = socket.udp()
+
+     -- Send out the SSDP request.  Don't wait forever, they normally respond quickly or not at all.
+    udp:sendto (ssdp_request, "239.255.255.250", 1900)
+    udp:settimeout (3)
+
+    -- Loop over response packets until we get and IP or there is no more data
+    local gateway_ip   = nil
+	local ssdp_uuid    = nil
+
+    repeat
+        local received_datagram = udp:receive()
+        if received_datagram then
+           -- pull the gateway IP out of the LOCATION header in the response
+           gateway_ip = string.match (received_datagram, "LOCATION:[%s+]http://([%d%.]+)")
+           -- pull the gateway UID out of the USN header
+           ssdp_uuid  = string.match (received_datagram, "USN:[%s+]uuid:(%d+)")
+        else
+           break
+        end
+    until (gateway_ip)
+
+    -- return the IP address & UUID of the discovered gateway (or nil if we are SOL)
+    return gateway_ip, ssdp_uuid
+end
+
+-- ----------------------------------------------------------------------------
+-- Pair with a Connected by TCP gateway
+--
+-- Note: User must put gateway into "pairing" mode by pressing 
+--       green sync button (green on the model I have anyway)
+-- ----------------------------------------------------------------------------
+function tcplighting_gateway_pair (gateway)
+
+    -- Bail if we don't a gateway
+    if (not gateway) then
+       gateway.error_text = "No gateway object provided, pairing failed"
+       return gateway
+    end
+
+    -- Bail if we don't a gateway IP
+    if (not gateway.gateway_ip) then
+       gateway.error_text = "No gateway IP address specified, pairing failed"
+       return gateway
+    end
+
+    -- Bail if we  don't have a gateway UUID
+    if (not gateway.gateway_uuid) then
+       gateway.error_text = "No gateway UUID address derived, pairing failed"
+       return 
+    end
+
+    local http  = require("socket.http")
+    local https = require("ssl.https")
+
+    http.TIMEOUT = 5
+    
+    local result, status  = https.request("https://" .. gateway.gateway_ip
+                                          .. "/gwr/gop.php",
+                                         "cmd=GWRLogin&data="
+                                          .. "<gip>"
+                                          .. "<version>1</version>"
+                                          .. "<email>" .. gateway.gateway_uuid .. "</email>"
+                                          .. "<password>" .. gateway.gateway_uuid .. "</password>"
+                                          .. "</gip>"
+                                          .. "&fmt=xml")
+    
+    local rc_value    = nil
+    if (status == 200 and result) then
+       -- The result is a small bit of XML that can easily be parsed through string manipulation
+       rc_value    = extract_xml_element_value (result, "rc")
+       gateway.gateway_token = extract_xml_element_value (result, "token")
+       gateway.error_text = ""
+    end
+
+    if (not gateway.gateway_token) then
+       gateway.error_text = "No gateway token extracted, pairing failed"
+       if status   then gateway.error_text = gateway.error_text .. " - HTTPS status=" .. status  end
+       if rc_value then gateway.error_text = gateway.error_text .. " - Gateway RC=" .. rc_value  end
+    end
+
+    return 
+
+end
+
+
+-- ----------------------------------------------------------------------------
+-- Implements urn:upnp-org:serviceId:SwitchPower1.SetTarget
+-- ----------------------------------------------------------------------------
+function tcplighting_set_target (gateway, did, target_value)
+
+    local http  = require("socket.http")
+    local https = require("ssl.https")
+
+    http.TIMEOUT = gateway.HTTP_TIMEOUT
+
+    return https.request("https://" .. gateway.devices[did].gateway_ip
+                        .. "/gwr/gop.php",
+                        "cmd=DeviceSendCommand&data="
+                        .. "<gip>"
+                        .. "<version>1</version>"
+                        .. "<token>" .. gateway.gateway_token .. "</token>"
+                        .. "<did>" .. did .. "</did>"
+                        .. "<value>" .. target_value .. "</value>"
+                        .. "</gip>"
+                        .. "&fmt=xml")
+end
+
+-- ----------------------------------------------------------------------------
+-- Implements urn:upnp-org:serviceId:Dimming1.SetLoadLevelTarget
+-- ----------------------------------------------------------------------------
+function tcplighting_set_load_level_target (gateway, did, load_percent)
+
+    local http  = require("socket.http")
+    local https = require("ssl.https")
+
+    http.TIMEOUT = gateway.HTTP_TIMEOUT
+
+    -- make sure light is on/off to match request
+    if load_percent > 0 then
+        tcplighting_set_target (gateway, did, 1)
+    else
+        tcplighting_set_target (gateway, did, 0)
+    end
+
+    -- change the dimming level
+    return https.request("https://" .. gateway.devices[did].gateway_ip
+                        .. "/gwr/gop.php",
+                        "cmd=DeviceSendCommand&data="
+                        .. "<gip>"
+                        .. "<version>1</version>"
+                        .. "<token>" .. gateway.gateway_token .. "</token>"
+                        .. "<did>" .. did .. "</did>"
+                        .. "<value>" .. load_percent .. "</value>"
+                        .. "<type>level</type>"
+                        .. "</gip>"
+                        .. "&fmt=xml")
+end
+
+
+-- ----------------------------------------------------------------------------
+-- Create a gateway object
+-- ----------------------------------------------------------------------------
+function tcplighting_gateway_create (gateway_ip)
+
+    -- create an empty gateway object
+    local gateway = {}
+    gateway.authenticated  = false
+    gateway.initialized    = false
+    gateway.autodiscovered = false
+    gateway.error_text     = nil    -- error text for last failed operation
+    gateway.gateway_ip     = gateway_ip
+    gateway.gateway_uuid   = nil
+    gateway.gateway_token  = nil
+    gateway.ssdp_uuid      = nil
+    gateway.devices        = {}    -- a cache of device info indexed by unique did
+    gateway.HTTP_TIMEOUT   = 5     -- 5 seconds should be fine on a local network
+
+    return gateway
+
+end
+
+
+-- ----------------------------------------------------------------------------
+-- Loads the gateway configuration
+-- ----------------------------------------------------------------------------
+function tcplighting_gateway_init (gateway_ip)
+
+    local MAX_SSDP_RETRIES = 5
+
+    -- create a gateway object
+    local gateway = tcplighting_gateway_create (gateway_ip)
+
+	-- Attempt auto-discovery via SSDP if IP not specified (not likely to work with new "secure" gateways)
+    if (gateway_ip == nil) or (string.len(gateway_ip) == 0) then
+        -- since UDP doesn't provide a reliable transport, try the SSDP request few times if necessary
+        for i = 1, MAX_SSDP_RETRIES do
+           gateway.gateway_ip, gateway.ssdp_uuid = tcplighting_gateway_ssdp_search ()
+           if gateway.gateway_ip then
+		      gateway.autodiscovered = true
+		      break
+		   end
+        end
+	else
+	    -- used specified IP
+	    gateway.gateway_ip = gateway_ip
+	end
+
+    -- Bail if we still don't have an IP
+    if not gateway.gateway_ip then
+       gateway.error_text = "No gateway IP address specified, autodiscovery failed"
+       return gateway
+    end
+
+    return gateway
+
+end
+
+
+-- ----------------------------------------------------------------------------
+-- Get current values from the gateway
+-- ----------------------------------------------------------------------------
+function tcplighting_gateway_poll (gateway)
+
+    -- Bail if we don't a gateway IP
+    if (not gateway.gateway_ip) then
+       gateway.error_text = "No gateway IP address specified, polling failed"
+       return gateway
+    end
+
+    -- Get the gateway's configuration data
+    local result, status = tcplighting_get_gateway_config (gateway)
+
+    -- Check the HTTP status
+    if status ~= 200 then
+       gateway.error_text = "HTTP gateway query request to " .. gateway.gateway_ip .. " failed"
+       if status then gateway.error_text = gateway.error_text .. " - " .. status     end
+       if result then gateway.error_text = gateway.error_text .. " - " .. result     end
+       return
+    end
+
+    -- Make sure something came back
+    if not result then
+       gateway.error_text = "HTTP gateway query request to " .. gateway.gateway_ip .. " did not return any data"
+       return
+    end
+
+    -- Only parse if the HTTP request was successful
+    tcplighting_parse_gateway_config(gateway, result)
+    return
+
+end
+
+
+-- ----------------------------------------------------------------------------
+-- Queries a Connected by TCP Gateway to determine which lights are available
+-- ----------------------------------------------------------------------------
+function tcplighting_get_gateway_config (gateway)
+
+    local http  = require("socket.http")
+    local https = require("ssl.https")
+
+
+    http.TIMEOUT = gateway.HTTP_TIMEOUT
+
+    return https.request("https://" .. gateway.gateway_ip
+                        .. "/gwr/gop.php",
+                        "cmd=GWRBatch&data="
+                        .. "<gwrcmds>"
+                        .. "<gwrcmd>"
+                        .. "<gcmd>RoomGetCarousel</gcmd>"
+                        .. "<gdata>"
+                        .. "<gip>"
+                        .. "<version>1</version>"
+                        .. "<token>" .. gateway.gateway_token .. "</token>"
+                        .. "<fields>name,image,imageurl,control,power,product,class,realtype,status</fields>"
+                        .. "</gip>"
+                        .. "</gdata>"
+                        .. "</gwrcmd>"
+                        .. "</gwrcmds>"
+                        .. "&fmt=xml")
+end
+
+
+-- ----------------------------------------------------------------------------
+-- Parse the gateway configuration
+-- ----------------------------------------------------------------------------
+function tcplighting_parse_gateway_config (gateway, config_xml)
+
+    local lxp = require("lxp")
+
+    -- Local collection devices - will be added to master table after parsing
+    local new_devices = {}
+
+    -- enumerate the XML elements that we want to load into our tables
+    local room_elements_of_interest = "|rid|name|desc|"
+    local device_elements_of_interest = "|did|name|desc|state|level|offline|type|rangemin|rangemax|power|poweravg|energy|manufacturer|prodbrand|prodmodel|prodtype|capability|"
+
+    -- used to keep track of the current context during the parsing process
+    local current_room_object = nil
+    local current_device_object = nil
+    local current_element_name = nil
+
+    -- XML parsing handler for the start of an element
+    local function start_element_handler (parser, name)
+        current_element_name = name
+        if name == "room" then
+            current_room_object = {}
+        elseif name == "device" then
+            current_device_object = {}
+            current_device_object["gateway_ip"] = gateway.gateway_ip
+            current_device_object["room"] = current_room_object
+        end
+    end
+
+    -- XML parsing handler for the end of an element
+    local function end_element_handler (parser, name)
+        current_element_name = name
+        if name == "room" then
+            current_room_object = nil
+        elseif name == "device" then
+             new_devices[#new_devices+1] = current_device_object
+            current_device_object = nil
+        end
+    end
+
+    -- XML parsing handler for the text of an element
+    local function character_handler (parser, value)
+        if current_device_object and string.find(device_elements_of_interest,"|"..current_element_name.."|") then
+            current_device_object[current_element_name] = value
+        elseif current_room_object and (not current_device_object) and string.find(room_elements_of_interest,"|"..current_element_name.."|") then
+            current_room_object[current_element_name] = value
+        end
+    end
+
+    -- Create a parser
+    local callbacks = {StartElement = start_element_handler, EndElement = end_element_handler, CharacterData = character_handler}
+    local parser = lxp.new(callbacks)
+
+    -- Parse the XML
+    parser:parse(config_xml)
+    parser:parse() -- finishes the document
+    parser:close()
+
+    -- Save the devices, uniquely indexed by did
+    if #new_devices > 0 then
+        for i,device in ipairs(new_devices) do
+            gateway.devices[device.did] = device
+            -- tweak for interpretation of load level for dimmers - always set to zero if device is off
+            if device.state == "0" then
+               device.level = "0"
+            end
+        end
+        gateway.initialized = true
+        gateway.error_text = nil
+    else
+        gateway.error_text = "No devices found at gateway " .. gateway.gateway_ip .. " while parsing " .. config_xml
+    end
+
+    return
+
+end
+
+
+-- ----------------------------------------------------------------------------
+-- Extracts the contents of the first occurrence of the given element 
+-- Note: element_name should not contain angle brackets (i.e. "xxx", not "<xxx>")
+-- ----------------------------------------------------------------------------
+function extract_xml_element_value (xml_string, element_name)
+    local element_value = nil
+    if (xml_string and element_name) then
+       local element_start_pos = string.find (xml_string, "<" .. element_name .. ">")
+       local element_end_pos   = string.find (xml_string, "</" .. element_name .. ">")
+       if (element_start_pos and element_end_pos) then
+           element_value = string.sub (xml_string, element_start_pos+string.len(element_name)+2, element_end_pos-1)
+       end
+    end
+    return element_value
+end
+
+
+-- ----------------------------------------------------------------------------
+-- Helper functions for testing - call devices by name/room
+-- ----------------------------------------------------------------------------
+function tcplighting_get_did (gateway, name, roomname)
+   local did = nil
+   for key,device in pairs(gateway.devices) do
+      if device.name == name and device.room.name == roomname then
+         did = device.did
+      end
+   end
+   return did
+end
+
+function tcplighting_set_target_by_name  (gateway, name, roomname, target_value)
+    return tcplighting_set_target(gateway, tcplighting_get_did (gateway, name, roomname), target_value)
+end
+
+function tcplighting_set_load_level_target_by_name (gateway, name, roomname, load_percent)
+    return tcplighting_set_load_level_target(gateway, tcplighting_get_did (gateway, name, roomname), load_percent)
+end
Index: /tags/v2.0/test-ssdp.lua
===================================================================
--- /tags/v2.0/test-ssdp.lua	(revision 30)
+++ /tags/v2.0/test-ssdp.lua	(revision 30)
@@ -0,0 +1,43 @@
+-- Event handling sequence:
+--
+-- 1. Is  IP specified?
+--     1a. If No IP: trigger auto-discovery
+--     1b. If auto discovery failed set error text and bail
+--     1c. Set IP and set auto-discovered=true
+--
+--
+--
+--
+--
+--
+--
+--
+--
+--
+--
+
+
+
+require ("L_TCPLighting1")
+
+
+    local MAX_SSDP_RETRIES = 5
+
+
+    function discover_gateway ()
+        local gateway_ip   = null
+        local gateway_uuid = null
+        -- since UDP doesn't provide a reliable transport, try the SSDP request few times if necessary
+        for i = 1, MAX_SSDP_RETRIES do
+           gateway_ip, gateway_uuid = tcplighting_gateway_ssdp_search ()
+           if gateway_ip then break end
+        end
+        return gateway_ip, gateway_uuid
+    end
+
+
+    local start_clock = os.clock()
+    print (discover_gateway ())
+    local end_clock = os.clock()
+
+    print ((end_clock-start_clock)*1000, " ms")
Index: /tags/v2.0/D_TCPLightingGateway1.json
===================================================================
--- /tags/v2.0/D_TCPLightingGateway1.json	(revision 30)
+++ /tags/v2.0/D_TCPLightingGateway1.json	(revision 30)
@@ -0,0 +1,106 @@
+{
+    "flashicon":      "icons/generic_sensor.swf",
+    "imgIconBody":     "",
+    "imgIconDimmable": "",
+    "imgIconTurnable": "",
+    "imgIconMin":      "",
+    "imgIconMax":      "",
+    "halloIconsDir":   "pics/hallo",
+    "inScene":         "0",
+    "DisplayStatus":   {},
+    "doc_url":
+       {
+       "doc_language": 1,
+	   "doc_manual":   1,
+       "doc_version":  1,
+       "doc_platform": 0,
+       "doc_page":     "lighting_devices"
+       },
+    "Tabs":
+	   [
+
+       {
+       "Label":    {"lang_tag": "tcp_lighting_gateway", "text": "Gateway"},
+       "Position": "0",
+       "TabType":  "flash",
+	   "SceneGroup":
+	        [
+		    {
+			"id": "1",
+			"top": "1",
+			"left": "0",
+			"x": "1",
+			"y": "1"
+		    },
+		    {
+			"id": "2",
+			"top": "2",
+			"left": "0",
+			"x": "1",
+			"y": "2"
+		    }
+		    ],
+		"ControlGroup": [
+			{
+			"id": "1",
+			"scenegroup": "1",
+			"isSingle": "1"
+			},
+			{
+			"id": "2",
+			"scenegroup": "2",
+			"type": "info"
+			}
+		],
+       "Control":
+		    [
+            {
+            "ControlType": "label",
+            "Label":       {"lang_tag": "pair_desc","text": "Securely Login/Pair with the gateway - Press the green sync button on the gateway first "},
+            "Display":     {"Top": 20,"Left": 50,"Width": 200,"Height": 20}
+            },
+            {
+            "ControlType":  "button",
+            "Label":       {"lang_tag": "pair_command","text": "Pair"},
+			"Display":     {"Top": 37,"Left": 50, "Width": 100, "Height": 20 },
+            "Command":     {"Service": "urn:pmcode-org:serviceId:TCPLightingGateway1", "Action": "Pair", "Parameters": []}
+			},
+            {
+            "ControlType": "label",
+            "Label":       {"lang_tag": "synchronize_desc","text": "Sync to the gateway and configure lights - Gateway ip must be specified in Advanced tab"},
+            "Display":     {"Top": 80,"Left": 50,"Width": 200,"Height": 20}
+            },
+            {
+            "ControlGroup": "1",
+            "ControlType":  "button",
+            "Label":       {"lang_tag": "synchronize_command","text": "Sync"},
+			"Display":     {"Top": 97,"Left": 50, "Width": 100, "Height": 20 },
+            "Command":     {"Service": "urn:pmcode-org:serviceId:TCPLightingGateway1", "Action": "Synchronize", "Parameters": []}
+			},
+            {
+            "ControlType": "label",
+            "Label":       {"lang_tag": "error_text_label","text": "Gateway Errors:"},
+			"Display":     {"Top": 140,"Left": 50,"Width": 200, "Height": 20}
+			},
+            {
+            "ControlGroup": "2",
+            "ControlType": "variable",
+            "Display":     {"Top": 155, "Left": 70, "Width": 200, "Height": 20,
+			                "Service": "urn:pmcode-org:serviceId:TCPLightingGateway1", "Variable": "ErrorText"}
+			}
+            ]
+        },
+
+        {
+        "Label":      {"lang_tag": "advanced","text": "Advanced"},
+        "Position":   "1",
+        "TabType":    "javascript",
+        "ScriptName": "shared.js",
+        "Function":   "advanced_device"
+        }
+    ],
+    "eventList":   {},
+    "eventList2":  [],
+    "DeviceType":  "urn:schemas-pmcode-org:service:TCPLightingGateway:1",
+    "device_type": "urn:schemas-pmcode-org:service:TCPLightingGateway:1"
+}
Index: /tags/v2.0A1/S_TCPLightingGateway1.xml
===================================================================
--- /tags/v2.0A1/S_TCPLightingGateway1.xml	(revision 30)
+++ /tags/v2.0A1/S_TCPLightingGateway1.xml	(revision 30)
@@ -0,0 +1,30 @@
+<?xml version="1.0"?>
+<scpd xmlns="urn:schemas-upnp-org:service-1-0">
+  <specVersion>
+    <major>1</major>
+    <minor>0</minor>
+  </specVersion>
+  <serviceStateTable>
+    <stateVariable sendEvents="no">
+      <name>AutoDiscovered</name>
+      <dataType>boolean</dataType>
+      <defaultValue>0</defaultValue>
+    </stateVariable>
+    <stateVariable sendEvents="yes">
+      <name>ErrorText</name>
+      <dataType>string</dataType>
+    </stateVariable>
+    <stateVariable sendEvents="no">
+      <name>OfflineDeviceNameFormat</name>
+      <dataType>string</dataType>
+    </stateVariable>
+  </serviceStateTable>
+  <actionList>
+    <action>
+      <name>Login</name>
+    </action>
+    <action>
+      <name>Synchronize</name>
+    </action>
+  </actionList>
+</scpd>
Index: /tags/v2.0A1/D_TCPLightingGateway1.xml
===================================================================
--- /tags/v2.0A1/D_TCPLightingGateway1.xml	(revision 30)
+++ /tags/v2.0A1/D_TCPLightingGateway1.xml	(revision 30)
@@ -0,0 +1,26 @@
+<?xml version="1.0"?>
+<root xmlns="urn:schemas-upnp-org:device-1-0">
+  <specVersion>
+    <major>1</major>
+    <minor>0</minor>
+  </specVersion>
+  <device>
+    <deviceType>urn:schemas-pmcode-org:service:TCPLightingGateway:1</deviceType>
+    <staticJson>D_TCPLightingGateway1.json</staticJson>
+    <serviceList>
+      <service>
+        <serviceType>urn:schemas-pmcode-org:service:TCPLightingGateway:1</serviceType>
+        <serviceId>urn:pmcode-org:serviceId:TCPLightingGateway1</serviceId>
+        <SCPDURL>S_TCPLightingGateway1.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>
+    <implementationList>
+      <implementationFile>I_TCPLighting1.xml</implementationFile>
+    </implementationList>
+  </device>
+</root>
Index: /tags/v2.0A1/D_TCPLighting1.xml
===================================================================
--- /tags/v2.0A1/D_TCPLighting1.xml	(revision 30)
+++ /tags/v2.0A1/D_TCPLighting1.xml	(revision 30)
@@ -0,0 +1,31 @@
+<?xml version="1.0"?>
+<root xmlns="urn:schemas-upnp-org:device-1-0">
+  <specVersion>
+    <major>1</major>
+    <minor>0</minor>
+  </specVersion>
+  <device>
+    <deviceType>urn:schemas-upnp-org:device:DimmableLight:1</deviceType>
+    <staticJson>D_DimmableLight1.json</staticJson>
+    <serviceList>
+      <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-upnp-org:service:Dimming:1</serviceType>
+        <serviceId>urn:upnp-org:serviceId:Dimming1</serviceId>
+        <SCPDURL>S_Dimming1.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>
+    <implementationList>
+      <implementationFile>I_TCPLighting1.xml</implementationFile>
+    </implementationList>
+  </device>
+</root>
Index: /tags/v2.0A1/test.lua
===================================================================
--- /tags/v2.0A1/test.lua	(revision 30)
+++ /tags/v2.0A1/test.lua	(revision 30)
@@ -0,0 +1,25 @@
+local tcplighting = require("L_TCPLighting1")
+local json = require ("json")
+
+
+-- initialize gateway
+gateway = tcplighting_gateway_init(null) --
+print (json.encode(gateway))
+
+
+-- modify a light's intensity
+if (gateway.initialized) then
+    -- generate a random intensity
+    math.randomseed(os.time())
+    math.random(); math.random(); math.random()
+    local load_level_target = math.random(100)
+    --  call the gateway
+    tcplighting_set_load_level_target_by_name(gateway, "Front Entry Light", "Front Entry", load_level_target)
+    tcplighting_set_target_by_name(gateway, "Front Entry Light", "Front Entry", 1)
+    -- poll to get latest status
+    tcplighting_gateway_poll(gateway)
+    print (json.encode(gateway.devices))
+else
+    print ("--- ERROR: " .. gateway.error_text)
+end
+
Index: /tags/v2.0A1/I_TCPLighting1.xml
===================================================================
--- /tags/v2.0A1/I_TCPLighting1.xml	(revision 30)
+++ /tags/v2.0A1/I_TCPLighting1.xml	(revision 30)
@@ -0,0 +1,544 @@
+<?xml version="1.0"?>
+<implementation>
+  <functions>
+
+       -- ---------------------------------------------------------------------
+       -- Constants
+       -- ---------------------------------------------------------------------
+       local GATEWAY_DEVICE_TYPE = "urn:schemas-pmcode-org:service:TCPLightingGateway:1"
+       local GATEWAY_SERVICE_ID  = "urn:pmcode-org:serviceId:TCPLightingGateway1"
+       local HA_DEVICE_TYPE      = "urn:schemas-micasaverde-com:service:HaDevice:1"
+       local HA_SERVICE_ID       = "urn:micasaverde-com:serviceId:HaDevice1"
+       local DIMMER_DEVICE_TYPE  = "urn:schemas-upnp-org:device:DimmableLight:1"
+       local DIMMER_SERVICE_ID   = "urn:upnp-org:serviceId:Dimming1"
+       local SWITCH_DEVICE_TYPE  = "urn:schemas-upnp-org:device:SwitchPower:1"
+       local SWITCH_SERVICE_ID   = "urn:upnp-org:serviceId:SwitchPower1"
+       local LIGHT_DEVICE_FILE   = "D_TCPLighting1.xml"
+       
+       
+       -- ---------------------------------------------------------------------
+       -- Pseudo-Constants: Can be overridden by parent device variables
+       -- ---------------------------------------------------------------------
+       local OFFLINE_DEVICE_NAME_FORMAT = "[Offline] %s"
+
+
+       -- ---------------------------------------------------------------------
+       -- table representing the gateway and its devices
+       -- ---------------------------------------------------------------------
+       local tcplighting_gateway = nil
+
+
+       -- ---------------------------------------------------------------------
+       -- Get a reference to the gateway (will init if necessary)
+       -- ---------------------------------------------------------------------
+	   local function get_gateway (gateway_ip)
+		  if (not tcplighting_gateway) or (not gateway_ip) or (gateway_ip == "") then
+             luup.log ("TCPLighting:  get_gateway - calling tcplighting_gateway_init,  IP=" .. tostring(gateway_ip))
+			 tcplighting_gateway = tcplighting_gateway_init (gateway_ip)
+			 if tcplighting_gateway.error_text then
+				luup.task(tcplighting_gateway.error_text, 2, "TCPLighting", -1)
+			 end
+		  end
+		  return tcplighting_gateway
+	   end
+
+
+       -- ---------------------------------------------------------------------
+       -- Clear the gateway object
+       -- ---------------------------------------------------------------------
+	   local function reset_gateway ()
+		  tcplighting_gateway = nil
+		  return
+	   end
+
+
+       -- ---------------------------------------------------------------------
+       -- Returns true if the device is offline
+       -- ---------------------------------------------------------------------
+       local function is_offline (device)
+           return ((not device) or (device.offline and device.offline == "1"))
+       end
+
+
+       -- ---------------------------------------------------------------------
+       -- Returns true if the device has dimming capabilities
+       -- ---------------------------------------------------------------------
+       local function is_dimmer_switch (device)
+           return (device and device.rangemax and device.rangemax == "99")
+       end
+
+
+       -- ---------------------------------------------------------------------
+       -- Returns true if the device has on/off capabilities
+       -- ---------------------------------------------------------------------
+       local function is_binary_switch (device)
+           return (device and device.capability and string.find(device.capability,"switch_binary"))
+       end
+
+
+       -- ---------------------------------------------------------------------
+       -- See if we have a matching room number
+       -- ---------------------------------------------------------------------
+       local function room_number (input_room_name)
+          local room_num = 0
+          if input_room_name then
+              for i, room_name in ipairs(luup.rooms) do
+                  if room_name == input_room_name then
+                      room_num = i
+                      break
+                  end
+              end
+          end
+          return room_num
+       end
+
+
+       -- ---------------------------------------------------------------------
+       -- Sets a LUUP device variable, but only if it has no current value
+       -- ---------------------------------------------------------------------
+       local function initialize_variable (service_id, variable_name, new_value, device_number)
+          local current_value = luup.variable_get (service_id, variable_name, device_number)
+          if (not current_value) or (current_value == "") then
+              luup.variable_set (service_id, variable_name, new_value, device_number)
+          end
+       end
+
+
+       -- ---------------------------------------------------------------------
+       -- Sets a LUUP device attribute, but only if it has no current value
+       -- ---------------------------------------------------------------------
+       local function initialize_attr (attr_name, new_value, device_number)
+          local current_value = luup.attr_get (attr_name, device_number)
+          if (not current_value) or (current_value == "") then
+              luup.attr_set (attr_name, new_value, device_number)
+          end
+       end
+
+
+       -- ---------------------------------------------------------------------
+       -- Updates a LUUP device variable, but only if its value has changed
+       -- ---------------------------------------------------------------------
+       local function update_variable (service_id, variable_name, new_value, device_number)
+          local current_value = luup.variable_get (service_id, variable_name, device_number)
+          if (not current_value) or (new_value ~= current_value) then
+              luup.variable_set (service_id, variable_name, new_value, device_number)
+          end
+       end
+
+
+       -- ---------------------------------------------------------------------
+       -- Updates a LUUP device attribute, but only if its value has changed
+       -- ---------------------------------------------------------------------
+       local function update_attr (attr_name, new_value, device_number)
+          local current_value = luup.attr_get (attr_name, device_number)
+          if (not current_value) or (new_value ~= current_value) then
+		      luup.log ("TCPLighting:  update_attr - device_number=" .. device_number)
+		      luup.log ("TCPLighting:  update_attr - attr_name=" .. attr_name)
+		      luup.log ("TCPLighting:  update_attr - new_value=" .. new_value)
+              luup.attr_set (attr_name, new_value, device_number)
+          end
+       end
+
+
+       -- ---------------------------------------------------------------------
+       -- Update a device to match info from gateway
+       -- ---------------------------------------------------------------------
+       local function update_device_settings (gateway, did, lul_device)
+
+           if not (gateway and did and lul_device) then
+              return
+           end
+
+           local device_number = tonumber(lul_device)
+           local device = gateway.devices[did]
+           
+		   -- indicate if we don't have data for device
+		   if (not device) then
+		       luup.log ("TCPLighting:  update_device_settings - device with id=" .. lul_device .. " has no data from gateway")
+			   return
+		   end
+           
+           
+		   -- indicate device is offline if we don't have data for it 
+           update_attr ("invisible", "", device_number) -- legacy code: prior versions made offline devices invisible
+		   if is_offline(device) then
+		       luup.log ("TCPLighting:  update_device_settings - device with id=" .. lul_device .. " is offline")
+		       update_attr ("name", string.format(OFFLINE_DEVICE_NAME_FORMAT, device.name), device_number)
+			   return
+		   else
+		       update_attr ("name", device.name, device_number)
+		   end
+
+           -- update the device
+		   update_attr ("manufacturer", device.manufacturer, device_number)
+		   update_attr ("model",        device.prodmodel,    device_number)
+
+           if device and device.room and device.room.name then
+               local room_num = room_number(device.room.name)
+               if room_num and room_num ~= 0 then
+                  local room_num_string = tostring(room_num)
+                  update_attr ("room_num", room_num_string, device_number)
+                  update_attr ("room",     room_num_string, device_number)
+               else
+                  luup.log("TCPLighting:  update_device_settings - unknown room " .. device.room.name)
+               end
+           end
+
+           if device and is_binary_switch(device) then
+		       update_variable (SWITCH_SERVICE_ID, "Status", device.state, device_number)
+		       update_variable (SWITCH_SERVICE_ID, "Target", device.state, device_number)
+           end
+
+           if device and is_dimmer_switch(device) then
+		      update_variable (DIMMER_SERVICE_ID, "LoadLevelStatus", device.level, device_number)
+		      update_variable (DIMMER_SERVICE_ID, "LoadLevelTarget", device.level, device_number)
+           end
+
+           return
+
+       end
+
+
+       -- ---------------------------------------------------------------------
+       -- Update all of the children of the specified device
+       -- ---------------------------------------------------------------------
+       local function sync_child_devices (gateway, parent_lul_device)
+
+           if not (gateway and parent_lul_device) then
+              return
+           end
+
+           -- poll the gateway for most current device data
+           tcplighting_gateway_poll(gateway)
+
+           -- find matching child devices
+           local parent_device_number = tonumber (parent_lul_device)
+           for k, luup_device in pairs(luup.devices) do
+               if (luup_device.device_num_parent == parent_device_number) and (luup_device.id) then
+                    luup.log ("TCPLighting:  sync_child_devices for " .. parent_lul_device .. " - updating child " .. k .. " " .. luup_device.description)
+                    update_device_settings (gateway, luup_device.id, k)
+               end
+           end
+
+           return
+
+       end
+
+
+       -- ---------------------------------------------------------------------
+       -- Create child devices of gateway if necessary
+       -- ---------------------------------------------------------------------
+       local function create_child_devices (gateway, parent_lul_device)
+
+           if gateway and gateway.initialized then
+
+              luup.log ("TCPLighting:  create_child_devices - gateway is initialized")
+
+			  local parent_device_number = tonumber (parent_lul_device)
+
+              -- Build up a table of child devices, indexed by did, with value being the device type
+			  local child_device_map = {}
+
+			  -- find existing children
+              for k, luup_device in pairs(luup.devices) do
+                  if (luup_device.device_num_parent == parent_device_number) and (luup_device.id) and (luup_device.device_type) then
+					  local did = luup.attr_get ("altid", k)
+					  if (did) then
+				         child_device_map[did] = {name=luup_device.description, device_type = luup_device.device_type}
+					  end
+                  end
+			  end
+
+              -- see if we have new children
+			  local new_devices_found = false
+              for did,gateway_device in pairs(gateway.devices) do
+				  if child_device_map[gateway_device.did] == nil then
+                     if is_dimmer_switch (gateway_device) then
+				        child_device_map[gateway_device.did] = {name=gateway_device.name, device_type=DIMMER_DEVICE_TYPE}
+						new_devices_found = true
+                     elseif is_binary_switch (gateway_device) then
+				        child_device_map[gateway_device.did] = {name=gateway_device.name, device_type=SWITCH_DEVICE_TYPE}
+						new_devices_found = true
+                     end
+				  end
+              end
+
+			  -- resync the combined list of child devices if new ones were found
+              if new_devices_found then
+			     local chdev_context = luup.chdev.start (parent_device_number)
+				 for did,device in pairs(child_device_map) do
+                     luup.log ("TCPLighting:  create_child_devices - creating child with did=" .. did .. ", name=" .. device.name)
+                     luup.chdev.append (parent_device_number, chdev_context, did, device.name, device.device_type, LIGHT_DEVICE_FILE,"","",false)
+                 end
+                 luup.chdev.sync (parent_device_number, chdev_context)
+			  else
+		         luup.log ("TCPLighting:  create_child_devices - no new child devices")
+              end
+
+           end
+
+       end
+
+
+       -- ---------------------------------------------------------------------
+       -- Schedule the next poll
+       -- ---------------------------------------------------------------------
+	   local function schedule_next_poll (lul_device)
+
+           local device_number = tonumber(lul_device)
+
+		   -- make sure the polling variables have values
+		   initialize_variable (HA_SERVICE_ID, "PollingEnabled", "1",  device_number)
+		   initialize_variable (HA_SERVICE_ID, "PollMinDelay",   "60", device_number)
+
+           -- get current polling settings (may be different from above if overriden by user)
+		   local polling_enabled  = luup.variable_get (HA_SERVICE_ID, "PollingEnabled", device_number)
+		   local polling_interval = luup.variable_get (HA_SERVICE_ID, "PollMinDelay",   device_number)
+
+		   -- make a delayed call to the synchronization functions
+		   if polling_enabled == "true" or polling_enabled == "1" then
+		      luup.call_timer("synchronize_gateway", 1, tonumber(polling_interval), "", lul_device)
+			  luup.log ("TCPLighting:  schedule_next_poll - timer set")
+		   end
+
+	   end
+
+
+       -- ---------------------------------------------------------------------
+       -- Authenticate to the gateway and obtain a token that can be used
+       -- for subsequent calls
+       -- ---------------------------------------------------------------------
+	   function login_to_gateway (lul_device)
+
+		   luup.log ("TCPLighting: login_to_gateway - start")
+
+           local device_number = tonumber(lul_device)
+
+           -- will create and populate a new gateway configuration object if necessary
+           local gateway_ip = luup.attr_get ("ip", device_number)
+           local gateway    = get_gateway (gateway_ip)
+
+           -- Assign gateway UUID if necessary
+           if (not gateway.gateway_uuid) then
+               local gateway_uuid = luup.variable_get  (GATEWAY_SERVICE_ID, "GatewayUUID", device_number)
+               if (gateway_uuid) then
+                   gateway.gateway_uuid = gateway_uuid
+               else
+                   -- Assign gateway UUID from the device UDN 
+                   local device_udn = luup.attr_get ("local_udn", device_number)
+                   if (device_udn) and (device_udn ~= "") and (string.len(device_udn) > 5) then
+                       gateway.gateway_uuid = string.sub(device_udn,6) -- take everything after "uuid:" prefix
+                   end
+               end
+           end
+
+           -- clear the current token
+           update_variable (GATEWAY_SERVICE_ID, "GatewayToken", "", device_number)
+
+           -- login to the gateway
+           luup.log ("TCPLighting:  login_to_gateway - IP=" .. tostring(gateway.gateway_ip))
+           luup.log ("TCPLighting:  login_to_gateway - UUID=" .. tostring(gateway.gateway_uuid))
+           tcplighting_gateway_login (gateway)
+           luup.log ("TCPLighting:  login_to_gateway - Token=" .. tostring(gateway.gateway_token))
+
+		   if (gateway.gateway_token) then
+		   
+               update_variable (GATEWAY_SERVICE_ID, "GatewayToken", gateway.gateway_token, device_number)
+               update_variable (GATEWAY_SERVICE_ID, "GatewayUUID",  gateway.gateway_uuid,  device_number)
+               update_variable (GATEWAY_SERVICE_ID, "ErrorText",    gateway.error_text,    device_number)
+
+		   else
+
+		       -- flag that there was an error
+		       luup.log ("TCPLighting:  login_to_gateway - error: " .. gateway.error_text)
+               update_variable (GATEWAY_SERVICE_ID, "ErrorText", gateway.error_text, device_number)
+
+           end
+
+		   luup.log ("TCPLighting: login_to_gateway - end")
+
+           return true
+
+	   end
+
+
+       -- ---------------------------------------------------------------------
+       -- Contact the gateway and initialize the gateway object
+       -- ---------------------------------------------------------------------
+	   function synchronize_gateway (lul_device)
+
+		   luup.log ("TCPLighting: synchronize_gateway - start")
+
+           local device_number = tonumber(lul_device)
+           local gateway_ip = luup.attr_get ("ip", device_number)
+
+           -- will create and populate a new gateway configuration object if necessary
+           local gateway = get_gateway (gateway_ip)
+
+           -- Make sure we have a gateway token
+           local gateway_token = luup.variable_get  (GATEWAY_SERVICE_ID, "GatewayToken", device_number)
+           if (not gateway_token) or (gateway_token == "") then
+               gateway.error_text = "No gateway token assigned, use device configuration UI to login first"
+               update_variable (GATEWAY_SERVICE_ID, "ErrorText", gateway.error_text, device_number)
+               return true
+           end
+           if (gateway_token) then
+               gateway.gateway_token = gateway_token
+           end
+
+		   if (gateway.error_text == nil) or (gateway.error_text == "") then
+		   
+               -- Sync up gateway properties
+			   initialize_attr ("name",  "TCP Lighting Gateway", device_number)
+			   if gateway.ssdp_uuid then
+			      initialize_attr ("altid", gateway.ssdp_uuid,  device_number)
+			   end
+               update_variable (GATEWAY_SERVICE_ID, "ErrorText", "", device_number)
+			   if gateway.autodiscovered then
+				  -- Use IP address from autodiscovery
+		          update_attr     ("ip", gateway.gateway_ip, device_number)
+				  update_variable (GATEWAY_SERVICE_ID, "AutoDiscovered", "1", device_number)
+			   end
+
+			   -- check to see if prefix for offline devices has been overridden
+			   --   some of this code looks redundant, but is required  
+			   --   in case previous versions of plugin were installed
+			   initialize_variable (GATEWAY_SERVICE_ID, "OfflineDeviceNameFormat", OFFLINE_DEVICE_NAME_FORMAT, device_number) 
+			   local offline_device_name_format_override = luup.variable_get (GATEWAY_SERVICE_ID, "OfflineDeviceNameFormat", device_number)
+			   if offline_device_name_format_override then
+			       OFFLINE_DEVICE_NAME_FORMAT = offline_device_name_format_override
+			   end
+
+               -- create and synchronize all children
+		       create_child_devices (gateway, lul_device)
+               sync_child_devices (gateway, lul_device)
+
+		   else
+
+		       -- flag that there was an error
+		       luup.log ("TCPLighting:  synchronize_gateway - error: " .. gateway.error_text)
+               update_variable (GATEWAY_SERVICE_ID, "ErrorText", gateway.error_text, device_number)
+
+               -- clear autodiscovered IP on failures
+               local previously_autodiscovered = luup.variable_get  (GATEWAY_SERVICE_ID, "AutoDiscovered", device_number)
+			   if previously_autodiscovered and previously_autodiscovered == "1" then
+				  -- Clear IP address from autodiscovery
+		          update_attr     ("ip", "", device_number)
+				  update_variable (GATEWAY_SERVICE_ID, "AutoDiscovered", "0", device_number)
+		          luup.log ("TCPLighting:  synchronize_gateway - cleared previously autodiscovered IP = " .. tostring(gateway_ip))
+			   end
+
+		       -- reset the gateway object
+			   reset_gateway ()
+
+           end
+
+		   -- set up polling so that changes made externally to the plugin show up in the UI
+		   schedule_next_poll (lul_device)
+
+		   luup.log ("TCPLighting: synchronize_gateway - end")
+
+           return true
+
+	   end
+
+
+       -- ---------------------------------------------------------------------
+       -- Set the dimming level on a light
+       -- ---------------------------------------------------------------------
+       local function set_load_level_target (lul_device, lul_settings)
+	       local device_number = tonumber(lul_device)
+           local gateway_ip = luup.devices[device_number].ip
+           local did = luup.devices[device_number].id
+		   local gateway = get_gateway (gateway_ip)
+		   if gateway.initialized and did then
+               tcplighting_set_load_level_target (gateway, did, tonumber(lul_settings.newLoadlevelTarget))
+               tcplighting_gateway_poll (gateway)
+			   update_device_settings (gateway, did, lul_device)
+           end
+           return true
+	   end
+
+
+       -- ---------------------------------------------------------------------
+       -- Flips switch on/off
+       -- ---------------------------------------------------------------------
+       local function set_target (lul_device, lul_settings)
+	       local device_number = tonumber(lul_device)
+           local gateway_ip = luup.devices[device_number].ip
+           local did = luup.devices[device_number].id
+		   local gateway = get_gateway (gateway_ip)
+		   if gateway.initialized and did then
+               tcplighting_set_target (gateway, did, tonumber(lul_settings.newTargetValue))
+               tcplighting_gateway_poll (gateway)
+			   update_device_settings (gateway, did, lul_device)
+           end
+           return true
+	   end
+
+
+       -- ---------------------------------------------------------------------
+       -- Plugin startup
+       -- Will initialize the gateway, which in turn creates child devices
+       -- for each light as necessary
+       -- ---------------------------------------------------------------------
+      function tcplighting_plugin_startup (lul_device)
+
+		  luup.log ("TCPLighting: tcplighting_plugin_startup - start")
+
+	      local device_number = tonumber(lul_device)
+          local device_type = luup.devices[device_number].device_type
+
+	      if device_type == GATEWAY_DEVICE_TYPE then
+		     synchronize_gateway (lul_device)
+          else
+             luup.log ("TCPLighting: tcplighting_plugin_startup - not gateway")
+		  end
+
+		  luup.log ("TCPLighting: tcplighting_plugin_startup - end")
+
+	  end
+
+
+  </functions>
+
+  <startup>tcplighting_plugin_startup</startup>
+
+  <files>L_TCPLighting1.lua</files>
+
+  <actionList>
+
+    <action>
+      <serviceId>urn:upnp-org:serviceId:SwitchPower1</serviceId>
+      <name>SetTarget</name>
+      <run>return set_target (lul_device, lul_settings)</run>
+    </action>
+
+    <action>
+      <serviceId>urn:upnp-org:serviceId:Dimming1</serviceId>
+      <name>SetLoadLevelTarget</name>
+      <run>return set_load_level_target (lul_device, lul_settings)</run>
+    </action>
+
+    <action>
+      <serviceId>urn:pmcode-org:serviceId:TCPLightingGateway1</serviceId>
+      <name>Login</name>
+      <run>return login_to_gateway (lul_device)</run>
+    </action>
+
+    <action>
+      <serviceId>urn:pmcode-org:serviceId:TCPLightingGateway1</serviceId>
+      <name>Synchronize</name>
+      <run>return synchronize_gateway (lul_device)</run>
+    </action>
+
+	<action>
+      <serviceId>urn:micasaverde-com:serviceId:HaDevice1</serviceId>
+      <name>Poll</name>
+      <run>return synchronize_gateway (lul_device)</run>
+    </action>
+
+   </actionList>
+
+</implementation>
+
Index: /tags/v2.0A1/L_TCPLighting1.lua
===================================================================
--- /tags/v2.0A1/L_TCPLighting1.lua	(revision 30)
+++ /tags/v2.0A1/L_TCPLighting1.lua	(revision 30)
@@ -0,0 +1,402 @@
+-- ----------------------------------------------------------------------------
+-- Search for a Connected by TCP gateway via SSDP
+-- ----------------------------------------------------------------------------
+function tcplighting_gateway_ssdp_search ()
+
+    -- Create an SSDP request - note that the Connected By TCP gateways
+    -- do not appear to respond to ssdp:all requests, so we'll directly
+    -- target the Greenwave Reality gateways that TCPi uses
+    -- See: http://upnp.org/specs/arch/UPnP-arch-DeviceArchitecture-v1.1.pdf
+    local ssdp_headers =
+        {
+        [1] = "M-SEARCH * HTTP/1.1\r\n",
+        [2] = "HOST: 239.255.255.250:1900\r\n",
+        [3] = "MAN: \"ssdp:discover\"\r\n",
+        [4] = "MX: 3\r\n",
+        [5] = "ST: urn:greenwavereality-com:service:gop:1\r\n\r\n"
+        }
+    local ssdp_request = table.concat (ssdp_headers )
+
+    -- Get a UDP socket
+    local http = require("socket.http")
+    local udp = socket.udp()
+
+     -- Send out the SSDP request.  Don't wait forever, they normally respond quickly or not at all.
+    udp:sendto (ssdp_request, "239.255.255.250", 1900)
+    udp:settimeout (3)
+
+    -- Loop over response packets until we get and IP or there is no more data
+    local gateway_ip   = nil
+	local ssdp_uuid    = nil
+
+    repeat
+        local received_datagram = udp:receive()
+        if received_datagram then
+           -- pull the gateway IP out of the LOCATION header in the response
+           gateway_ip = string.match (received_datagram, "LOCATION:[%s+]http://([%d%.]+)")
+           -- pull the gateway UID out of the USN header
+           ssdp_uuid  = string.match (received_datagram, "USN:[%s+]uuid:(%d+)")
+        else
+           break
+        end
+    until (gateway_ip)
+
+    -- return the IP address & UUID of the discovered gateway (or nil if we are SOL)
+    return gateway_ip, ssdp_uuid
+end
+
+-- ----------------------------------------------------------------------------
+-- Login to a Connected by TCP gateway
+--
+-- Note: User must put gateway into "pairing" mode by pressing 
+--       green sync button (green on the model I have anyway)
+-- ----------------------------------------------------------------------------
+function tcplighting_gateway_login (gateway)
+
+    -- Bail if we don't a gateway
+    if (not gateway) then
+       gateway.error_text = "No gateway object provided, login failed"
+       return gateway
+    end
+
+    -- Bail if we don't a gateway IP
+    if (not gateway.gateway_ip) then
+       gateway.error_text = "No gateway IP address specified, login failed"
+       return gateway
+    end
+
+    -- Bail if we  don't have a gateway UUID
+    if (not gateway.gateway_uuid) then
+       gateway.error_text = "No gateway UUID address derived, login failed"
+       return 
+    end
+
+    local http  = require("socket.http")
+    local https = require("ssl.https")
+
+    http.TIMEOUT = 5
+    
+    local result, status  = https.request("https://" .. gateway.gateway_ip
+                                          .. "/gwr/gop.php",
+                                         "cmd=GWRLogin&data="
+                                          .. "<gip>"
+                                          .. "<version>1</version>"
+                                          .. "<email>" .. gateway.gateway_uuid .. "</email>"
+                                          .. "<password>" .. gateway.gateway_uuid .. "</password>"
+                                          .. "</gip>"
+                                          .. "&fmt=xml")
+    
+    local rc_value    = nil
+    if (status == 200 and result) then
+       -- The result is a small bit of XML that can easily be parsed through string manipulation
+       rc_value    = extract_xml_element_value (result, "rc")
+       gateway.gateway_token = extract_xml_element_value (result, "token")
+       gateway.error_text = ""
+    end
+
+    if (not gateway.gateway_token) then
+       gateway.error_text = "No gateway token extracted, login failed"
+       if status   then gateway.error_text = gateway.error_text .. " - HTTPS status=" .. status  end
+       if rc_value then gateway.error_text = gateway.error_text .. " - Gateway RC=" .. rc_value  end
+    end
+
+    return 
+
+end
+
+
+-- ----------------------------------------------------------------------------
+-- Implements urn:upnp-org:serviceId:SwitchPower1.SetTarget
+-- ----------------------------------------------------------------------------
+function tcplighting_set_target (gateway, did, target_value)
+
+    local http  = require("socket.http")
+    local https = require("ssl.https")
+
+    http.TIMEOUT = gateway.HTTP_TIMEOUT
+
+    return https.request("https://" .. gateway.devices[did].gateway_ip
+                        .. "/gwr/gop.php",
+                        "cmd=DeviceSendCommand&data="
+                        .. "<gip>"
+                        .. "<version>1</version>"
+                        .. "<token>" .. gateway.gateway_token .. "</token>"
+                        .. "<did>" .. did .. "</did>"
+                        .. "<value>" .. target_value .. "</value>"
+                        .. "</gip>"
+                        .. "&fmt=xml")
+end
+
+-- ----------------------------------------------------------------------------
+-- Implements urn:upnp-org:serviceId:Dimming1.SetLoadLevelTarget
+-- ----------------------------------------------------------------------------
+function tcplighting_set_load_level_target (gateway, did, load_percent)
+
+    local http  = require("socket.http")
+    local https = require("ssl.https")
+
+    http.TIMEOUT = gateway.HTTP_TIMEOUT
+
+    -- make sure light is on/off to match request
+    if load_percent > 0 then
+        tcplighting_set_target (gateway, did, 1)
+    else
+        tcplighting_set_target (gateway, did, 0)
+    end
+
+    -- change the dimming level
+    return https.request("https://" .. gateway.devices[did].gateway_ip
+                        .. "/gwr/gop.php",
+                        "cmd=DeviceSendCommand&data="
+                        .. "<gip>"
+                        .. "<version>1</version>"
+                        .. "<token>" .. gateway.gateway_token .. "</token>"
+                        .. "<did>" .. did .. "</did>"
+                        .. "<value>" .. load_percent .. "</value>"
+                        .. "<type>level</type>"
+                        .. "</gip>"
+                        .. "&fmt=xml")
+end
+
+
+-- ----------------------------------------------------------------------------
+-- Create a gateway object
+-- ----------------------------------------------------------------------------
+function tcplighting_gateway_create (gateway_ip)
+
+    -- create an empty gateway object
+    local gateway = {}
+    gateway.authenticated  = false
+    gateway.initialized    = false
+    gateway.autodiscovered = false
+    gateway.error_text     = nil    -- error text for last failed operation
+    gateway.gateway_ip     = gateway_ip
+    gateway.gateway_uuid   = nil
+    gateway.gateway_token  = nil
+    gateway.ssdp_uuid      = nil
+    gateway.devices        = {}    -- a cache of device info indexed by unique did
+    gateway.HTTP_TIMEOUT   = 5     -- 5 seconds should be fine on a local network
+
+    return gateway
+
+end
+
+
+-- ----------------------------------------------------------------------------
+-- Loads the gateway configuration
+-- ----------------------------------------------------------------------------
+function tcplighting_gateway_init (gateway_ip)
+
+    local MAX_SSDP_RETRIES = 5
+
+    -- create a gateway object
+    local gateway = tcplighting_gateway_create (gateway_ip)
+
+	-- Attempt auto-discovery via SSDP if IP not specified (not likely to work with new "secure" gateways)
+    if (gateway_ip == nil) or (string.len(gateway_ip) == 0) then
+        -- since UDP doesn't provide a reliable transport, try the SSDP request few times if necessary
+        for i = 1, MAX_SSDP_RETRIES do
+           gateway.gateway_ip, gateway.ssdp_uuid = tcplighting_gateway_ssdp_search ()
+           if gateway.gateway_ip then
+		      gateway.autodiscovered = true
+		      break
+		   end
+        end
+	else
+	    -- used specified IP
+	    gateway.gateway_ip = gateway_ip
+	end
+
+    -- Bail if we still don't have an IP
+    if not gateway.gateway_ip then
+       gateway.error_text = "No gateway IP address specified, autodiscovery failed"
+       return gateway
+    end
+
+    return gateway
+
+end
+
+
+-- ----------------------------------------------------------------------------
+-- Get current values from the gateway
+-- ----------------------------------------------------------------------------
+function tcplighting_gateway_poll (gateway)
+
+    -- Bail if we don't a gateway IP
+    if (not gateway.gateway_ip) then
+       gateway.error_text = "No gateway IP address specified, polling failed"
+       return gateway
+    end
+
+    -- Get the gateway's configuration data
+    local result, status = tcplighting_get_gateway_config (gateway)
+
+    -- Check the HTTP status
+    if status ~= 200 then
+       gateway.error_text = "HTTP gateway query request to " .. gateway.gateway_ip .. " failed"
+       if status then gateway.error_text = gateway.error_text .. " - " .. status     end
+       if result then gateway.error_text = gateway.error_text .. " - " .. result     end
+       return
+    end
+
+    -- Make sure something came back
+    if not result then
+       gateway.error_text = "HTTP gateway query request to " .. gateway.gateway_ip .. " did not return any data"
+       return
+    end
+
+    -- Only parse if the HTTP request was successful
+    tcplighting_parse_gateway_config(gateway, result)
+    return
+
+end
+
+
+-- ----------------------------------------------------------------------------
+-- Queries a Connected by TCP Gateway to determine which lights are available
+-- ----------------------------------------------------------------------------
+function tcplighting_get_gateway_config (gateway)
+
+    local http  = require("socket.http")
+    local https = require("ssl.https")
+
+
+    http.TIMEOUT = gateway.HTTP_TIMEOUT
+
+    return https.request("https://" .. gateway.gateway_ip
+                        .. "/gwr/gop.php",
+                        "cmd=GWRBatch&data="
+                        .. "<gwrcmds>"
+                        .. "<gwrcmd>"
+                        .. "<gcmd>RoomGetCarousel</gcmd>"
+                        .. "<gdata>"
+                        .. "<gip>"
+                        .. "<version>1</version>"
+                        .. "<token>" .. gateway.gateway_token .. "</token>"
+                        .. "<fields>name,image,imageurl,control,power,product,class,realtype,status</fields>"
+                        .. "</gip>"
+                        .. "</gdata>"
+                        .. "</gwrcmd>"
+                        .. "</gwrcmds>"
+                        .. "&fmt=xml")
+end
+
+
+-- ----------------------------------------------------------------------------
+-- Parse the gateway configuration
+-- ----------------------------------------------------------------------------
+function tcplighting_parse_gateway_config (gateway, config_xml)
+
+    local lxp = require("lxp")
+
+    -- Local collection devices - will be added to master table after parsing
+    local new_devices = {}
+
+    -- enumerate the XML elements that we want to load into our tables
+    local room_elements_of_interest = "|rid|name|desc|"
+    local device_elements_of_interest = "|did|name|desc|state|level|offline|type|rangemin|rangemax|power|poweravg|energy|manufacturer|prodbrand|prodmodel|prodtype|capability|"
+
+    -- used to keep track of the current context during the parsing process
+    local current_room_object = nil
+    local current_device_object = nil
+    local current_element_name = nil
+
+    -- XML parsing handler for the start of an element
+    local function start_element_handler (parser, name)
+        current_element_name = name
+        if name == "room" then
+            current_room_object = {}
+        elseif name == "device" then
+            current_device_object = {}
+            current_device_object["gateway_ip"] = gateway.gateway_ip
+            current_device_object["room"] = current_room_object
+        end
+    end
+
+    -- XML parsing handler for the end of an element
+    local function end_element_handler (parser, name)
+        current_element_name = name
+        if name == "room" then
+            current_room_object = nil
+        elseif name == "device" then
+             new_devices[#new_devices+1] = current_device_object
+            current_device_object = nil
+        end
+    end
+
+    -- XML parsing handler for the text of an element
+    local function character_handler (parser, value)
+        if current_device_object and string.find(device_elements_of_interest,"|"..current_element_name.."|") then
+            current_device_object[current_element_name] = value
+        elseif current_room_object and (not current_device_object) and string.find(room_elements_of_interest,"|"..current_element_name.."|") then
+            current_room_object[current_element_name] = value
+        end
+    end
+
+    -- Create a parser
+    local callbacks = {StartElement = start_element_handler, EndElement = end_element_handler, CharacterData = character_handler}
+    local parser = lxp.new(callbacks)
+
+    -- Parse the XML
+    parser:parse(config_xml)
+    parser:parse() -- finishes the document
+    parser:close()
+
+    -- Save the devices, uniquely indexed by did
+    if #new_devices > 0 then
+        for i,device in ipairs(new_devices) do
+            gateway.devices[device.did] = device
+            -- tweak for interpretation of load level for dimmers - always set to zero if device is off
+            if device.state == "0" then
+               device.level = "0"
+            end
+        end
+        gateway.initialized = true
+        gateway.error_text = nil
+    else
+        gateway.error_text = "No devices found at gateway " .. gateway.gateway_ip .. " while parsing " .. config_xml
+    end
+
+    return
+
+end
+
+
+-- ----------------------------------------------------------------------------
+-- Extracts the contents of the first occurrence of the given element 
+-- Note: element_name should not contain angle brackets (i.e. "xxx", not "<xxx>")
+-- ----------------------------------------------------------------------------
+function extract_xml_element_value (xml_string, element_name)
+    local element_value = nil
+    if (xml_string and element_name) then
+       local element_start_pos = string.find (xml_string, "<" .. element_name .. ">")
+       local element_end_pos   = string.find (xml_string, "</" .. element_name .. ">")
+       if (element_start_pos and element_end_pos) then
+           element_value = string.sub (xml_string, element_start_pos+string.len(element_name)+2, element_end_pos-1)
+       end
+    end
+    return element_value
+end
+
+
+-- ----------------------------------------------------------------------------
+-- Helper functions for testing - call devices by name/room
+-- ----------------------------------------------------------------------------
+function tcplighting_get_did (gateway, name, roomname)
+   local did = nil
+   for key,device in pairs(gateway.devices) do
+      if device.name == name and device.room.name == roomname then
+         did = device.did
+      end
+   end
+   return did
+end
+
+function tcplighting_set_target_by_name  (gateway, name, roomname, target_value)
+    return tcplighting_set_target(gateway, tcplighting_get_did (gateway, name, roomname), target_value)
+end
+
+function tcplighting_set_load_level_target_by_name (gateway, name, roomname, load_percent)
+    return tcplighting_set_load_level_target(gateway, tcplighting_get_did (gateway, name, roomname), load_percent)
+end
Index: /tags/v2.0A1/test-ssdp.lua
===================================================================
--- /tags/v2.0A1/test-ssdp.lua	(revision 30)
+++ /tags/v2.0A1/test-ssdp.lua	(revision 30)
@@ -0,0 +1,43 @@
+-- Event handling sequence:
+--
+-- 1. Is  IP specified?
+--     1a. If No IP: trigger auto-discovery
+--     1b. If auto discovery failed set error text and bail
+--     1c. Set IP and set auto-discovered=true
+--
+--
+--
+--
+--
+--
+--
+--
+--
+--
+--
+
+
+
+require ("L_TCPLighting1")
+
+
+    local MAX_SSDP_RETRIES = 5
+
+
+    function discover_gateway ()
+        local gateway_ip   = null
+        local gateway_uuid = null
+        -- since UDP doesn't provide a reliable transport, try the SSDP request few times if necessary
+        for i = 1, MAX_SSDP_RETRIES do
+           gateway_ip, gateway_uuid = tcplighting_gateway_ssdp_search ()
+           if gateway_ip then break end
+        end
+        return gateway_ip, gateway_uuid
+    end
+
+
+    local start_clock = os.clock()
+    print (discover_gateway ())
+    local end_clock = os.clock()
+
+    print ((end_clock-start_clock)*1000, " ms")
Index: /tags/v2.0A1/D_TCPLightingGateway1.json
===================================================================
--- /tags/v2.0A1/D_TCPLightingGateway1.json	(revision 30)
+++ /tags/v2.0A1/D_TCPLightingGateway1.json	(revision 30)
@@ -0,0 +1,106 @@
+{
+    "flashicon":      "icons/generic_sensor.swf",
+    "imgIconBody":     "",
+    "imgIconDimmable": "",
+    "imgIconTurnable": "",
+    "imgIconMin":      "",
+    "imgIconMax":      "",
+    "halloIconsDir":   "pics/hallo",
+    "inScene":         "0",
+    "DisplayStatus":   {},
+    "doc_url":
+       {
+       "doc_language": 1,
+	   "doc_manual":   1,
+       "doc_version":  1,
+       "doc_platform": 0,
+       "doc_page":     "lighting_devices"
+       },
+    "Tabs":
+	   [
+
+       {
+       "Label":    {"lang_tag": "tcp_lighting_gateway", "text": "Gateway"},
+       "Position": "0",
+       "TabType":  "flash",
+	   "SceneGroup":
+	        [
+		    {
+			"id": "1",
+			"top": "1",
+			"left": "0",
+			"x": "1",
+			"y": "1"
+		    },
+		    {
+			"id": "2",
+			"top": "2",
+			"left": "0",
+			"x": "1",
+			"y": "2"
+		    }
+		    ],
+		"ControlGroup": [
+			{
+			"id": "1",
+			"scenegroup": "1",
+			"isSingle": "1"
+			},
+			{
+			"id": "2",
+			"scenegroup": "2",
+			"type": "info"
+			}
+		],
+       "Control":
+		    [
+            {
+            "ControlType": "label",
+            "Label":       {"lang_tag": "login_desc","text": "Securely Login/Pair with the gateway - Press the green sync button on the gateway first "},
+            "Display":     {"Top": 20,"Left": 50,"Width": 200,"Height": 20}
+            },
+            {
+            "ControlType":  "button",
+            "Label":       {"lang_tag": "login_command","text": "Login"},
+			"Display":     {"Top": 37,"Left": 50, "Width": 100, "Height": 20 },
+            "Command":     {"Service": "urn:pmcode-org:serviceId:TCPLightingGateway1", "Action": "Login", "Parameters": []}
+			},
+            {
+            "ControlType": "label",
+            "Label":       {"lang_tag": "synchronize_desc","text": "Sync to the gateway and configure lights - Gateway ip must be specified in Advanced tab"},
+            "Display":     {"Top": 80,"Left": 50,"Width": 200,"Height": 20}
+            },
+            {
+            "ControlGroup": "1",
+            "ControlType":  "button",
+            "Label":       {"lang_tag": "synchronize_command","text": "Sync"},
+			"Display":     {"Top": 97,"Left": 50, "Width": 100, "Height": 20 },
+            "Command":     {"Service": "urn:pmcode-org:serviceId:TCPLightingGateway1", "Action": "Synchronize", "Parameters": []}
+			},
+            {
+            "ControlType": "label",
+            "Label":       {"lang_tag": "error_text_label","text": "Gateway Errors:"},
+			"Display":     {"Top": 140,"Left": 50,"Width": 200, "Height": 20}
+			},
+            {
+            "ControlGroup": "2",
+            "ControlType": "variable",
+            "Display":     {"Top": 155, "Left": 70, "Width": 200, "Height": 20,
+			                "Service": "urn:pmcode-org:serviceId:TCPLightingGateway1", "Variable": "ErrorText"}
+			}
+            ]
+        },
+
+        {
+        "Label":      {"lang_tag": "advanced","text": "Advanced"},
+        "Position":   "1",
+        "TabType":    "javascript",
+        "ScriptName": "shared.js",
+        "Function":   "advanced_device"
+        }
+    ],
+    "eventList":   {},
+    "eventList2":  [],
+    "DeviceType":  "urn:schemas-pmcode-org:service:TCPLightingGateway:1",
+    "device_type": "urn:schemas-pmcode-org:service:TCPLightingGateway:1"
+}
Index: /trunk/S_TCPLightingGateway1.xml
===================================================================
--- /trunk/S_TCPLightingGateway1.xml	(revision 20)
+++ /trunk/S_TCPLightingGateway1.xml	(revision 30)
@@ -22,5 +22,5 @@
   <actionList>
     <action>
-      <name>Login</name>
+      <name>Pair</name>
     </action>
     <action>
Index: /trunk/I_TCPLighting1.xml
===================================================================
--- /trunk/I_TCPLighting1.xml	(revision 20)
+++ /trunk/I_TCPLighting1.xml	(revision 30)
@@ -133,7 +133,7 @@
           local current_value = luup.attr_get (attr_name, device_number)
           if (not current_value) or (new_value ~= current_value) then
-		      luup.log ("TCPLighting:  update_attr - device_number=" .. device_number)
-		      luup.log ("TCPLighting:  update_attr - attr_name=" .. attr_name)
-		      luup.log ("TCPLighting:  update_attr - new_value=" .. new_value)
+		      luup.log ("TCPLighting:  update_attr - device_number=" .. tostring(device_number))
+		      luup.log ("TCPLighting:  update_attr - attr_name=" .. tostring(attr_name))
+		      luup.log ("TCPLighting:  update_attr - new_value=" .. tostring(new_value))
               luup.attr_set (attr_name, new_value, device_number)
           end
@@ -309,7 +309,7 @@
        -- for subsequent calls
        -- ---------------------------------------------------------------------
-	   function login_to_gateway (lul_device)
-
-		   luup.log ("TCPLighting: login_to_gateway - start")
+	   function pair_with_gateway (lul_device)
+
+		   luup.log ("TCPLighting: pair_with_gateway - start")
 
            local device_number = tonumber(lul_device)
@@ -337,24 +337,29 @@
 
            -- login to the gateway
-           luup.log ("TCPLighting:  login_to_gateway - IP=" .. tostring(gateway.gateway_ip))
-           luup.log ("TCPLighting:  login_to_gateway - UUID=" .. tostring(gateway.gateway_uuid))
-           tcplighting_gateway_login (gateway)
-           luup.log ("TCPLighting:  login_to_gateway - Token=" .. tostring(gateway.gateway_token))
+           luup.log ("TCPLighting:  pair_with_gateway - IP=" .. tostring(gateway.gateway_ip))
+           luup.log ("TCPLighting:  pair_with_gateway - UUID=" .. tostring(gateway.gateway_uuid))
+           tcplighting_gateway_pair (gateway)
+           luup.log ("TCPLighting:  pair_with_gateway - Token=" .. tostring(gateway.gateway_token))
 
 		   if (gateway.gateway_token) then
 		   
+		       -- update device variables
                update_variable (GATEWAY_SERVICE_ID, "GatewayToken", gateway.gateway_token, device_number)
                update_variable (GATEWAY_SERVICE_ID, "GatewayUUID",  gateway.gateway_uuid,  device_number)
                update_variable (GATEWAY_SERVICE_ID, "ErrorText",    gateway.error_text,    device_number)
+               
+               -- initiate a sync
+		       luup.log ("TCPLighting: pair_with_gateway - synchronizing")
+               return synchronize_gateway (lul_device)
 
 		   else
 
 		       -- flag that there was an error
-		       luup.log ("TCPLighting:  login_to_gateway - error: " .. gateway.error_text)
+		       luup.log ("TCPLighting:  pair_with_gateway - error: " .. gateway.error_text)
                update_variable (GATEWAY_SERVICE_ID, "ErrorText", gateway.error_text, device_number)
 
            end
 
-		   luup.log ("TCPLighting: login_to_gateway - end")
+		   luup.log ("TCPLighting: pair_with_gateway - end")
 
            return true
@@ -379,5 +384,5 @@
            local gateway_token = luup.variable_get  (GATEWAY_SERVICE_ID, "GatewayToken", device_number)
            if (not gateway_token) or (gateway_token == "") then
-               gateway.error_text = "No gateway token assigned, use device configuration UI to login first"
+               gateway.error_text = "No gateway token assigned, use device configuration UI to pair first"
                update_variable (GATEWAY_SERVICE_ID, "ErrorText", gateway.error_text, device_number)
                return true
@@ -523,6 +528,6 @@
     <action>
       <serviceId>urn:pmcode-org:serviceId:TCPLightingGateway1</serviceId>
-      <name>Login</name>
-      <run>return login_to_gateway (lul_device)</run>
+      <name>Pair</name>
+      <run>return pair_with_gateway (lul_device)</run>
     </action>
 
Index: /trunk/L_TCPLighting1.lua
===================================================================
--- /trunk/L_TCPLighting1.lua	(revision 20)
+++ /trunk/L_TCPLighting1.lua	(revision 30)
@@ -47,14 +47,14 @@
 
 -- ----------------------------------------------------------------------------
--- Login to a Connected by TCP gateway
+-- Pair with a Connected by TCP gateway
 --
 -- Note: User must put gateway into "pairing" mode by pressing 
 --       green sync button (green on the model I have anyway)
 -- ----------------------------------------------------------------------------
-function tcplighting_gateway_login (gateway)
+function tcplighting_gateway_pair (gateway)
 
     -- Bail if we don't a gateway
     if (not gateway) then
-       gateway.error_text = "No gateway object provided, login failed"
+       gateway.error_text = "No gateway object provided, pairing failed"
        return gateway
     end
@@ -62,5 +62,5 @@
     -- Bail if we don't a gateway IP
     if (not gateway.gateway_ip) then
-       gateway.error_text = "No gateway IP address specified, login failed"
+       gateway.error_text = "No gateway IP address specified, pairing failed"
        return gateway
     end
@@ -68,5 +68,5 @@
     -- Bail if we  don't have a gateway UUID
     if (not gateway.gateway_uuid) then
-       gateway.error_text = "No gateway UUID address derived, login failed"
+       gateway.error_text = "No gateway UUID address derived, pairing failed"
        return 
     end
@@ -96,5 +96,5 @@
 
     if (not gateway.gateway_token) then
-       gateway.error_text = "No gateway token extracted, login failed"
+       gateway.error_text = "No gateway token extracted, pairing failed"
        if status   then gateway.error_text = gateway.error_text .. " - HTTPS status=" .. status  end
        if rc_value then gateway.error_text = gateway.error_text .. " - Gateway RC=" .. rc_value  end
Index: /trunk/D_TCPLightingGateway1.json
===================================================================
--- /trunk/D_TCPLightingGateway1.json	(revision 20)
+++ /trunk/D_TCPLightingGateway1.json	(revision 30)
@@ -57,12 +57,12 @@
             {
             "ControlType": "label",
-            "Label":       {"lang_tag": "login_desc","text": "Securely Login/Pair with the gateway - Press the green sync button on the gateway first "},
+            "Label":       {"lang_tag": "pair_desc","text": "Securely Login/Pair with the gateway - Press the green sync button on the gateway first "},
             "Display":     {"Top": 20,"Left": 50,"Width": 200,"Height": 20}
             },
             {
             "ControlType":  "button",
-            "Label":       {"lang_tag": "login_command","text": "Login"},
+            "Label":       {"lang_tag": "pair_command","text": "Pair"},
 			"Display":     {"Top": 37,"Left": 50, "Width": 100, "Height": 20 },
-            "Command":     {"Service": "urn:pmcode-org:serviceId:TCPLightingGateway1", "Action": "Login", "Parameters": []}
+            "Command":     {"Service": "urn:pmcode-org:serviceId:TCPLightingGateway1", "Action": "Pair", "Parameters": []}
 			},
             {
