New Hunter Douglas PowerView Platform

I see the following in the FAQ regarding the new PowerView system for Hunter Douglas blinds:

Can I control my window coverings with PowerView™ Motorization from my home automation system?

With the addition of the PowerView Hub, window coverings with PowerView
Motorization can be integrated into a home automation system via RS-232
serial input or IP (internet protocol) from many common systems.

Can someone translate for someone who can’t do any code nor know what any of those words mean? Can I integrate this some way (obviously not, since I can’t code :slight_smile: ), but if it can be integrated with ST, there may be somebody who might do this in the future!

http://www.hunterdouglas.com/operating-systems/powerview-motorization/support

1 Like

Page wouldn’t load for me. But it sounds like the PowerView Hub will have some sort of IP based API or control commands that might be able to be accessible from the ST hub.

So, yes it might be possible, somewhere down the line.

So you’re saying there’s a chance!

I’m interested in this too. I’m looking into getting the powerview hub and then I will give it a shot.

1 Like

I ordered mine for my new home (building slated for completion in February). The sales person demoed the blinds with the app and it was pretty sharp. As I have several months to go I’m hoping for ST integration but, even if it doesn’t happen, my criteria for buying these blinds–I can make the house looked lived in if I’m coming home late or staying away for a few days–will be met with the Hunter Douglas app and an internet connection.

Only game in town I can find with tops down bottoms up motorized. I need a combo of both.

App looks very nice which will lack one big, albeit superficial novel item…Amazon Echo control. :smile:

Preparing to redo living room with home theater setup in the next year. Automated blind are high on the nice to have list. These landed on my radar. Looking forward to hearing your experience, theanimator - or from anyone else who takes a crack at it.

I’m also very interested in this concept (and specific product).

subscribed :slight_smile:

This is a copy of the vera code to made it possible, if i’m reading it right it looks like a TCP socket connection. So while yes, it might be possible to make it happen… it won’t be easy as SmartThings doesn’t officially do TCP sockets.

-- Hunter Douglas Platinum bridge driver version 1.2 by Gengen
-- This software is distributed under the terms of the GNU General Public License 2.0
-- http://www.gnu.org/licenses/old-licenses/gpl-2.0.en.html

local PlatinumShade_PollStarted = false
local PlatinumShade_Connected = 0
local PlatinumShade_ConnectTime = nil
local socket = require("socket")
local http = require("socket.http")
local bit = require "bit"
local PlatinumShade_pollInterval = 180 -- 3 minutes
local PlatinumShade_Db = { rooms={}, shades={}, scenes={}, schedules={} }
local PlatinumShade_NeedSync = false
local PlatinumShade_Queue = {}
local PlatinumShade_WaitingFor = nil
local PlatinumShade_ActLevel = 0

local kUPD01 = 1
local KShadeActionPending = 2

local ANSI_RED     = "\027[31m"
local ANSI_GREEN   = "\027[32m"
local ANSI_YELLOW  = "\027[33m"
local ANSI_BLUE    = "\027[34m"
local ANSI_MAGENTA = "\027[35m"
local ANSI_CYAN    = "\027[36m"
local ANSI_WHITE   = "\027[37m"
local ANSI_RESET   = "\027[0m"

-- All logging goes through this function.
-- Comment/uncomment the luup.log line to enable/disable verbose logging
function log(msg) 
  -- luup.log("Platinum Bridge: " .. msg)
end

-- A debugging function to output a complete LUA object heirarchically
function printTable(tab,prefix,hash)
	local k,v,s,i, top, pref
	if prefix == nil then
		prefix = ""
	end
	if hash == nil then
	    top = true
		hash = {}
	else
	    top = false
	end
	if hash[tab] ~= nil then
		log(prefix .. "recursive " .. hash[tab])
		return
	end
	if type(tab) ~= "table" then
		if type(tab) == "string" then
			tab = tab:gsub("\t", "\\t")
			tab = tab:gsub("\r", "\\r")
			tab = tab:gsub("\n", "\\n")
		end
	  	log(prefix .. " (" .. type(tab) .. ") = " .. tostring(tab))
	  	return
	end
	hash[tab] = prefix
	if top then
		log(prefix .. "{")
		pref = prefix
		prefix = prefix .. "  "
	end
	for k,v in pairs(tab) do
	  if type(k) == "table" then
	         log(prefix .. "key-{")
	         printTable(k,prefix .. "       ", hash);
			 s = "    }: "
	  else
	         s = tostring(k) .. ": "
	  end
	  if type(v) == "table" then
			 log(prefix .. s .. "{")
	         printTable(v,prefix .. string.rep(" ",#s) .. "  ", hash)
			 log(prefix .. string.rep(" ",#s) .. "}")
	  else
	         hash[v] = prefix;
			 if type(v) == "string" then
	        	v = v:gsub("\t", "\\t")
	        	v = v:gsub("\r", "\\r")
	        	v = v:gsub("\n", "\\n")
			 end
	         log(prefix .. s .. "(" .. type(v) .. ") " .. tostring(v))
	  end
	end
	if top then
		log(pref .. "}")
	end
end

-- A debugging function to convert any LUA objet to a string
function tableToString(tab)
   if type(tab) ~= "table" then
      return tostring(tab)
   end
   local k,v,s
   s = "{"
   for k,v in pairs(tab) do
      if s ~= "{" then
	     s = s .. ", "
	  end
      if type(k) == "table" then
	     s = s .. tableToString(k)
	  else
	     s = s .. tostring(k)
	  end
	  s = s .. "="
      if type(v) == "table" then
	     s = s .. tableToString(v)
	  else
	     s = s .. tostring(v)
	  end
   end
   s = s .. "}"
   return s
end

-- Here is where we automatically discover the bridge's IP address.
function FindPlatinumBridgeIPAddress(prevIp)
	PlatinumShade_Connected = 0
	local udp = socket.udp()
	udp:setoption("broadcast",true)
	udp:settimeout(1.0)
	local broadcast_ip = '255.255.255.255'
	local NBNS_port = 137 -- Netbios Name Service

	-- Netbios Name service query "PLATLINK-FDBU<00>"
	local NbnsQuery = "\000\001\001\016\000\001\000\000\000\000\000\000 FAEMEBFEEMEJEOELCNFAEEECFFCACAAA\000\000 \000\001"
	udp:sendto(NbnsQuery, broadcast_ip, NBNS_port)
	local response, bridgeIp, peerPort = udp:receivefrom()
	udp:close()
	if response then
		if prevIp == bridgeIp then
			log("NBNS query consistent response: bridgeIp=" .. tostring(bridgeIp))
			StartProtocol(bridgeIp)
		else
			-- At boot, the bridge sometimes returns bogus IP addresses like 192.168.xx.90 where
			-- xx increments every 500ms until it gets the real IP address from the DHCP server
			-- So we wait until we get a consistent answer twice in a row in 1 second intervals.
			log ("NBNS query first response: bridgeIp=" .. tostring(bridgeIp))
			prevIp = peerIp
			luup.call_delay("FindPlatinumBridgeIPAddress", 1, bridgeIp, true) 
		end
	else
		log("Cannot find Platinum Bridge: " .. bridgeIp)
		luup.call_delay("FindPlatinumBridgeIPAddress", 1, "", true) 
	end
end

-- Once we have found the bridge's IP address, we open TCP port 522 to start the session.
-- This also sets up the keep-alive timer
function StartProtocol(bridgeIp)
	local platinum_port = 522
	luup.io.open(PlatinumShade_Device, bridgeIp, platinum_port)
	log("Opening TCP socket at IP address: " .. bridgeIp .. ":" .. platinum_port)
	PlatinumShade_ConnectTime = os.time()
	PlatinumShade_Connected = 1
	if not PlatinumShade_PollStarted then
		PlatinumShade_PollStarted = true
		luup.call_delay("PlatinumPoll", PlatinumShade_pollInterval, "", true)
	end
end

-- This is the first function to be called when the LuaUPnP engine first starts (or restarts)
function PlatinumShade_Startup(lul_device)
	log("Startup(lul_device="..lul_device..")")
	PlatinumShade_Device = lul_device;
	FindPlatinumBridgeIPAddress("")
	InitializePlatinumDb()
  	return true,'ok','Platinum Shade'
end

-- Initialize the internal database with whatever Vera knows about the shades
-- We correllate this with the shade database that we receive from the bridge
-- before we do the sync to add new shades or delete old ones.
function InitializePlatinumDb()
	for k, v in pairs(luup.devices) do
		if v.device_num_parent == PlatinumShade_Device then
			local id_str = v.id
			local id_prefix = id_str:sub(1,1)
		    local id_num = tonumber(id_str:sub(2))
			if id_prefix == "S" then
				PlatinumShade_Db.shades[id_num+1] = {shade = id_num, device=k, description=v.description}
			elseif id_prefix == "R" then
				PlatinumShade_Db.rooms[id_num+1] = {room = id_num, device=k, description=v.description} 
			else
				log(ANSI_RED.."Error: Unkown ID: ".. tostring(v.id)..ANSI_RESET)
			end 
		end 
	end
	log("InitializePlatinumDb complete.")
	printTable(PlatinumShade_Db)
end

-- This sends a periodic keep-alive message to the bridge
-- to maintain the connection
function PlatinumPoll()
	log("Polling")
	if PlatinumShade_Connected >= 4 then
		Enqueue("$dmy", "^%d %$ack")
	end
	luup.call_delay("PlatinumPoll", PlatinumShade_pollInterval, "", true)
end

function Dequeue()
	-- log("Dequque: Queue depth="..#PlatinumShade_Queue.." WaitingFor="..tostring(PlatinumShade_WaitingFor).." ActLevel="..tostring(PlatinumShade_ActLevel).." Queue="..tableToString(PlatinumShade_Queue)) 
	while #PlatinumShade_Queue > 0 and PlatinumShade_WaitingFor == nil and PlatinumShade_ActLevel == 0 do
		local t = table.remove(PlatinumShade_Queue,1)
		PlatinumShade_WaitingFor = t.wait
		if not Outgoing(t.data) then
			return false
		end 
	end
	return true
end

-- Enqueue data to send but don't send any more data until we get a specific response
-- wait can either be nil to not wait, a Lua pattern to wait for that specific pattern
-- to be received, or it can be numeric constant meaning that special processing is needed.
function Enqueue(data, wait)
	log("Enqueue data="..tostring(data).." Wait="..tostring(wait).." queue depth="..#PlatinumShade_Queue.." WaitingFor="..tostring(PlatinumShade_WaitingFor).." ActLevel="..tostring(PlatinumShade_ActLevel)) 
	table.insert(PlatinumShade_Queue, {data=data, wait=wait})
	Dequeue()
end

-- Low level output function sends commands to the bridge
-- If an error occurs, assume that we lost the connection
function Outgoing(data)
	log("Outgoing: " .. data)
	local result = luup.io.write(data, PlatinumShade_Device)
	if not result then
		log("luup.io.write("..tostring(data)..", "..tostring(PlatinumShade_Device) .. ") returned " .. tostring(result))
		FindPlatinumBridgeIPAddress("")
		return false
	end
	return true
end

-- This is a list of shade types indexed by shade type + 1
-- name - The shade type name
-- order - an array of feature numbers supported by this shade type in the order in which they should be used
-- xover (optional - if shade has more than 1 feature) - The crossover point as a percentage value. Below this value, the second feature is used.
-- tdbu (optional) - Indicates special handling for top-down/bottom-up shade types
-- feature - an object matching the feature numbers to names
-- The last index (all) includes features common to all shades.
platinum_shade_types = {
	[ 0+1]={name="Alustra Woven Textures: Roller",                                   order={4},        						feature={[4]="Bottom-Up"}},
	[ 1+1]={name="Duette & Applause Honeycomb Shades: Standard",                     order={4},        						feature={[4]="Bottom-Up"}},  
	[ 2+1]={name="Duette & Applause Honeycomb Shades: DuoLite & Top-Down/Bottom-Up", order={4, 18},    xover=50, tdbu=true,	feature={[4]="Bottom Rail", [18]="Middle Rail"}},  
	[ 3+1]={name="Duette & Applause Honeycomb Shades: Top-Down",                     order={4},        						feature={[4]="Top-Down"}},  
	[ 4+1]={name="Duette & Applause Honeycomb Shades: Skylift",                      order={4},        						feature={[4]="Bottom-Up"}},  
	[ 5+1]={name="Desgner Roller & Screen Shades",                                   order={4},        						feature={[4]="Bottom-Up"}},  
	[ 6+1]={name="Luminette Privacy Sheers", 										 order={4, 19, 7}, xover=20,			feature={[4]="Traverse",    [19]="Close left",    [7]="Close right"}},
	[ 7+1]={name="Luminette Modern Draperies: Full Panel",                     	     order={4},        						feature={[4]="Traverse"}},  
	[ 8+1]={name="Luminette Modern Draperies: Dual Panel", 						     order={4, 19, 7}, xover=20,			feature={[4]="Traverse",    [19]="Close left",    [7]="Close right"}},
	[ 9+1]={name="Pirouette Window Shadings",               				         order={4},        						feature={[4]="Bottom-Up"}},  
    [10+1]={name="Silhouette & Nanticket Window Shadings",                           order={4, 7},     xover=20,			feature={[4]="Bottom-Up",   [ 7]="Vane"}},  
    [11+1]={name="Vignette Modern Roman Shades: Traditional",                        order={4},        						feature={[4]="Bottom-Up"}},  
    [12+1]={name="Vignette Modern Roman Shades: Tiered",                             order={4},        						feature={[4]="Bottom-Up"}},  
    [13+1]={name="Vignette Modern Roman Shades: Tiered Top-Down/Bottom-Up",          order={4, 18},    xover=50, tdbu=true,	feature={[4]="Bottom Rail", [18]="Middle Rail"}},  
    [14+1]={name="Skyline Gliding Window Panels",                              	     order={4},        						feature={[4]="Traverse"}},  
    [15+1]={name="Pleated Shades",               	            			         order={4},        						feature={[4]="Bottom-Up"}},  
    [16+1]={name="Alustra Woven Textures: Roman",                                    order={4},        						feature={[4]="Bottom-Up"}},
    [17+1]={name="Solera Soft Shades",                                               order={4},        						feature={[4]="Bottom-Up"}},
    [18+1]={name="Design Studio Roman Shades",                                       order={4},        						feature={[4]="Bottom-Up"}},
    all   ={name="Features common to all shades",                                    order={10,01,12}, 						feature={[10]="Intermediate stop", [01]="Sync", [12]="Test"}},
}  

local weekdays = {"Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday" }

-- This is called whenever a shade is changed either by vera or by the platinum app on the mobile device
function UpdateShade(shade_id, feature, position)
	local shade = PlatinumShade_Db.shades[shade_id+1]
	shade.deleted = nil
	if feature == 1 then -- Sync
		log("UpdateShade: ignoring sync to shade " .. shade_id .. ": " .. tostring(shade.description)) 
		return
	elseif feature == 12 then -- test
		log("UpdateShade: ignoring test to shade " .. shade_id .. ": " .. tostring(shade.description)) 
		return
	elseif feature == 10 then -- Intermediate stop. We don't know exactly where that is so assume 50%
		log("UpdateShade: Assuming intermediat stop at 50% for shade " .. shade_id .. ": " .. tostring(shade.description)) 
		shade.feature = 4
		shade.position = 128
	else
		shade.feature = feature
		shade.position = position
	end
	if shade.device then
		local room = PlatinumShade_Db.rooms[shade.room+1]
		local shade_type = platinum_shade_types[room.type+1]
		local percent
		local status = 0
		if shade_type.tdbu then
			-- special case for top-down/bottom-up shades
			if shade.feature == shade_type.order[1] then
				percent = shade_type.xover + math.floor((shade.position * (100-shade_type.xover) / 255) + .5)
			else
				percent = shade_type.xover - math.floor((shade.position * shade_type.xover / 255) + .5)
			end
			if percent ~= shade_type.xover then
				status = 1
			end
		else
			if #shade_type.order > 1 then
				if shade.feature == shade_type.order[1] then
					percent = shade_type.xover + math.floor((shade.position * (100-shade_type.xover) / 255) + .5)
				else
					percent = math.floor((shade.position * shade_type.xover / 255) + .5)
				end
			else
				percent = math.floor((shade.position * 100 / 255) + .5)
			end
			if percent > 0 then
				status = 1
			end
		end
		luup.variable_set("urn:upnp-org:serviceId:Dimming1",        "LoadLevelStatus", tostring(percent), shade.device, false)
		luup.variable_set("urn:upnp-org:serviceId:WindowCovering1", "LoadLevelStatus", tostring(percent), shade.device, false)
		luup.variable_set("urn:upnp-org:serviceId:SwitchPower1",    "Status",          tostring(status),  shade.device, false)
	end
end

-- Loose string matching used for room names
-- Case and whitespace are ignored in the comparison. n2 can be a prefix of n1
function NameMatch(n1, n2)
	return string.find(string.lower(string.gsub(n1,"%s","")), string.lower(string.gsub(n2,"%s","")), 1, true) == 1
end

function TableLength(T)
  	local count = 0
  	for x in pairs(T) do
  		count = count + 1
	end
  	return count
end

function URLEnclode(s)
	return string.gsub(s, "%A", function(c) return string.format("%%%02X", string.byte(c)) end)
end

-- This is where we synchronize the device database which we received from the bridge with Vera's.
-- Vera will add or delete child devices to match what the bridge gives us.
-- This is not to be confused with a manual shade "sync" operation used when adding new shades.
function SyncDevices()
	local children = luup.chdev.start(PlatinumShade_Device)
	for id, shade in pairs(PlatinumShade_Db.shades) do
		if shade.deleted then
			log( "SyncDevices: Not appending shade " .. id-1 .. ": " .. shade.description .. ".")
			PlatinumShade_Db.rooms[shade.room+1].shades[id] = nil
		  	PlatinumShade_Db.shades[id] = nil
		else	
			log( "SyncDevices: Appending shade " .. id-1 .. ": " .. shade.description .. ".")
			luup.chdev.append(PlatinumShade_Device, children, "S"..id-1, shade.description, "urn:schemas-micasaverde-com:device:WindowCovering:1", "D_WindowCovering1.xml", "", "", false)
		end
	end
	for id, room in pairs(PlatinumShade_Db.rooms) do
		if room.deleted then
			log("SyncDevices: Not appending room " .. id-1 .. ": " .. room.description .. " shades.")
		  	PlatinumShade_Db.rooms[id] = nil
		elseif TableLength(room.shades) > 1 then
			-- Create a device to change all shades in a room if the room has more than one shade
			log("SyncDevices: Appending room " .. id-1 .. ": " .. room.description .. " shades.")
			luup.chdev.append(PlatinumShade_Device, children, "R"..id-1, room.description .. " shades", "urn:schemas-micasaverde-com:device:WindowCovering:1", "D_WindowCovering1.xml", "", "", false)
		end
	end
	for id, scene in pairs(PlatinumShade_Db.scenes) do
		if scene.deleted then
			log( "SyncDevices: Deleting scene " .. id-1 ..".")
		  	PlatinumShade_Db.scenes[id] = nil
		end
	end
	for id, schedule in pairs(PlatinumShade_Db.schedules) do
		if schedule.deleted then
			log( "SyncDevices: Deleting schedule " .. id-1 ..".")
		  	PlatinumShade_Db.schedules[id] = nil
		end
	end
	luup.chdev.sync(PlatinumShade_Device, children)
	-- Now go through all child devices and set the rooms if there is a name match
	for k, v in pairs(luup.devices) do
		if v.device_num_parent == PlatinumShade_Device then
			local id_prefix = v.id:sub(1,1)
		    local id_num = tonumber(v.id:sub(2))
			local room
			if id_prefix == "S" then
				local shade = PlatinumShade_Db.shades[id_num+1]
				if not shade then
					break
				end
				room = PlatinumShade_Db.rooms[shade.room+1]
			else
				room = PlatinumShade_Db.rooms[id_num+1]
			end
			if not room then
				break
			end
			for i = 1, #luup.rooms do
				if NameMatch(room.description, luup.rooms[i]) then
					log("Vera Device " .. k .. ": " .. URLEnclode(v.description) .. " - Bridge room " ..  room.description .. " matches Vera room " .. luup.rooms[i])
					if v.room_num ~= i then
						log("Device=" .. k .. " old room_num=".. v.room_num .. " New room_num=" .. i); 
						-- For some reason, the call to luup.attr below to change the room does not always work, so we use a roundabout approach.
						--luup.attr_set("room_num", tostring(i), k)
						local response, status, headers, statusline = http.request("http://127.0.0.1:3480/data_request?id=device&action=rename&device="..tostring(k).."&name="..URLEnclode(v.description).."&room="..tostring(i))
						-- log("response="..tostring(response).." status="..tostring(status).." headers="..tableToString(headers).." statusline="..tostring(statusline))
					end
					break
				end
			end			
		end 
	end 
	log("SyncDevices complete")
end

-- This is where we parse any incomming status messages from the bridge
function PlatinumShade_Incoming(data)
	local handled = false
	log("Incoming: " .. string.format("%q",data))
	-- Remove any leading 0 bytes
	while string.byte(data,1) == 0 do
		data = string.sub(data,2)
	end
    -- $act01-00-    	Start action
    -- $act02-<room>-	Action in room
	-- $act00-00-    	End action
	-- $act messages may come due to our own actions or someone elses. We must wait until the
	-- action is completed either way.
	local act_level_str, act_arg_str = string.match(data, "^%d %$act(%d%d)-(%d%d)-.*$")
	if act_level_str ~= nil then
		PlatinumShade_ActLevel = tonumber(act_level_str)
		handled = true
	end
	-- see if the data matches a response that we were waiting for previously
	if type(PlatinumShade_WaitingFor) == "string" and string.match(data, PlatinumShade_WaitingFor) then
	   PlatinumShade_WaitingFor = nil	
	elseif string.match(data,"HunterDouglas Shade Controller") then
		PlatinumShade_Connected = 2
		PlatinumShade_WaitingFor = nil
		-- Set the date as $sdt02-21-07-2015-
		-- os.date returns Monday=1...Sunday=7
		-- $sdt needs Sunday=1..Saturday=7
		local dayOfWeek = tonumber(os.date("%u"))+1
		if dayOfWeek > 7 then
			dayOfWeek = 1
		end
		local dateString = os.date("$sdt%m-%d-0"..dayOfWeek.."-%Y-")
		Enqueue(dateString,"^%d %$done")
		local timeString = os.date("$stm%H-%M-%S-")
		-- Set time as $stm06-52-54-
		Enqueue(timeString,"^%d %$done")
		-- Get the first status inquiry
		Enqueue("$dat-", kUPD01)
		PlatinumShade_Connected = 4
	elseif string.match(data, "^%d %$done$") and PlatinumShade_WaitingFor == KShadeActionPending then
		-- $done - returned in response to $sdt (set date) and $stm (set time) and $pss (set shade position) commands
		-- Special handling for multiple pss commands in a scene.
		if #PlatinumShade_Queue > 0 and PlatinumShade_Queue[1].wait == KShadeActionPending then
			Outgoing(PlatinumShade_Queue[1].data)
			table.remove(PlatinumShade_Queue,1)
		else 
			PlatinumShade_WaitingFor = "^%d %$act00-00-"
			Outgoing("$rls")
		end
	elseif string.match(data, "^%d %$reset$") then
		-- $reset - A new database is about to be sent. Mark all objects as deleted for now and we will remove them when we sync
	    for k,v in pairs(PlatinumShade_Db.rooms) do
	    	v.deleted = true
	    end
	    for k,v in pairs(PlatinumShade_Db.shades) do
	    	v.deleted = true
	    end
	    for k,v in pairs(PlatinumShade_Db.scenes) do
	    	v.deleted = true
	    end
	    for k,v in pairs(PlatinumShade_Db.schedules) do
	    	v.deleted = true
	    end	    	  
	else
		-- $cr - Create Room
		-- 2 $cr00-10-0x056F-Dining Room
		local room_id_str, room_type_str, room_hash, room_desc = string.match(data, "^%d %$cr(%d%d)-(%d%d)-(0x%x%x%x%x)-(.*)$")
		if room_id_str ~= nil then
			local room_id = tonumber(room_id_str)
			local room_type = tonumber(room_type_str)
			if platinum_shade_types[room_type+1] == nil then
				log("Room="..room_id..
				    " type="..room_type.."="..ANSI_RED.."ERROR: Unknown type"..ANSI_RESET..
				    " description="..room_desc);
			else 
				local room = PlatinumShade_Db.rooms[room_id+1]
				if room == nil then
					room = {}
					PlatinumShade_Db.rooms[room_id+1] = room
				end
				room.room = room_id
				room.type = room_type
				room.description = room_desc
				room.deleted = nil
				room.shades = {}  
				log("Room="..room_id..
				    " type="..room_type.."="..platinum_shade_types[room_type+1].name..
				    " description="..room_desc);
			end
		else
		    -- $cs - Create Shade
			-- 2 $cs01-01-06-Front Window
			local shade_id_str, room_id_str, hash8, shade_desc = string.match(data, "^%d %$cs(%d%d)-(%d%d)-(%d%d)-(.*)$")
			if shade_id_str ~= nil then
				local shade_id = tonumber(shade_id_str)
				local room_id = tonumber(room_id_str)
				local room = PlatinumShade_Db.rooms[room_id+1]
				if room == nil then
					log("Shade="..shade_id..
					    " room="..room_id.."="..ANSI_RED.."ERROR: Unknown room"..ANSI_RESET.." description="..shade_desc);
				else 
					log("Shade="..shade_id..
					    " room="..room_id.."="..room.description..
					    " description="..shade_desc);
					local shade = PlatinumShade_Db.shades[shade_id+1]
					if shade == nil then
						shade = {}
						PlatinumShade_Db.shades[shade_id+1] = shade
					end
					shade.shade = shade_id
					shade.room = room_id
					shade.description = shade_desc
					shade.deleted = nil
					room.shades[shade_id+1] = true
					if PlatinumShade_Connected >= 5 then -- Do this only when receiving an unsoliscited $cs command
						SyncDevices()
					end  
				end 
			else
			    -- $cp - Report Shade position
				-- 2 $cp06-04-174-
				local shade_id_str,feature_str,position_str = string.match(data, "^%d %$cp(%d%d)-(%d%d)-(%d%d%d)-.*$")
				if shade_id_str ~= nil then
					local shade_id = tonumber(shade_id_str)
					local feature_num = tonumber(feature_str)
					local position = tonumber(position_str)
					if PlatinumShade_Db.shades[shade_id+1] == nil then
						log("Shade="..shade_id.."="..ANSI_RED.."ERROR Unknown shade"..ANSI_RESET)
					else
						local room_id = PlatinumShade_Db.shades[shade_id+1].room
						local room_type = PlatinumShade_Db.rooms[room_id+1].type
						if platinum_shade_types[room_type+1].feature[feature_num] == nil then
							if platinum_shade_types.all.feature[feature_num] == nil then
								log("Shade="..shade_id.."="..PlatinumShade_Db.shades[shade_id+1].description..
								    " room="..PlatinumShade_Db.rooms[room_id+1].description..
									" type="..platinum_shade_types[room_type+1].name..
									" feature="..feature_num.."="..ANSI_RED.."ERROR: Unknown feature for this shade type"..ANSI_RESET)
							else
								log("Shade="..shade_id.."="..PlatinumShade_Db.shades[shade_id+1].description..
								    " room="..PlatinumShade_Db.rooms[room_id+1].description..
									" type="..platinum_shade_types[room_type+1].name..
									" common feature="..feature_num.."="..platinum_shade_types.all.feature[feature_num]..
									" position="..position)
								UpdateShade(shade_id, feature_num, position)
							end
						else
							log("Shade="..shade_id.."="..PlatinumShade_Db.shades[shade_id+1].description..
							    " room="..PlatinumShade_Db.rooms[room_id+1].description..
								" type="..platinum_shade_types[room_type+1].name..
								" feature="..feature_num.."="..platinum_shade_types[room_type+1].feature[feature_num]..
								" position="..position)
							UpdateShade(shade_id, feature_num, position)
						end
					end
				else
				    -- $cm - Declare scene, name
					-- 1 $cm00-First Scene
					local scene_id_str, scene_desc = string.match(data, "^%d %$cm(%d%d)-(.*)$")
					if scene_id_str ~= nil then
						local scene_id = tonumber(scene_id_str)
						log("scene="..scene_id.." description="..scene_desc)
						local scene = PlatinumShade_Db.scenes[scene_id+1]
						if scene == nil then
							scene = {}
							PlatinumShade_Db.scenes[scene_id+1] = scene
						end 
						scene.scene = scene_id
						scene.description = scene_desc
						scene.deleted = nil
						if scene.shades == nil then
							scene.shades = {}
						end
						if scene.rooms == nil then
							scene.rooms = {}
						end
					else
					    -- $cp - declare shade in scene
						-- 1 $cq00-08-01-
						local scene_id_str, shade_id_str, enable_str = string.match(data, "^%d %$cq(%d%d)-(%d%d)-(%d%d)-.*$")
						if scene_id_str ~= nil then
							local scene_id = tonumber(scene_id_str)
							local shade_id = tonumber(shade_id_str)
							local enable = tonumber(enable_str)
							if PlatinumShade_Db.scenes[scene_id+1] == nil then
								log("Scene="..scene_id.."="..ANSI_RED.."ERROR: Unknown Scene"..ANSI_RESET)
							elseif PlatinumShade_Db.shades[shade_id+1] == nil then
								log("Scene="..scene_id.."="..PlatinumShade_Db.scenes[scene_id+1].description..
								    " Shade="..shade_id.."="..ANSI_RED.."ERROR: Unknown shade"..ANSI_RESET)
							else
								log("Scene="..scene_id.."="..PlatinumShade_Db.scenes[scene_id+1].description..
								    " 

Truncated…

In a recent Harmony upgrade, the following was added:

  • Support for Hunter Douglas PowerView Hub and Shades
    from Harmony Remote and Hub firmware (v4.8.24)

Does this potentially change how we can connect? Combo of ST/IFTTT/Harmony/HD to have ST trigger or schedule.
I would finally go ahead with the purchase if so.

Since you can create Home Automation only activities in Harmony, you could cut out IFTTT. Create activities in Harmony with just your HD stuff (E.g. shades full open, shades full close, shades halfway), then use ST to directly trigger those activities in Harmony based on a schedule or events.

Hmmmmm…and what would I need to connect Alexa?

@TANDZ, then you would need IFTTT. You can create custom command phrases for Alexa using IFTTT. Those can activate triggers, which you would set up for ST to trigger in Harmony. This is starting to sound like a fun weekend project. Let us know if you proceed and how it guess.

I agree that for Alexa, the way to go is directly to IFTTT via the “trigger ______” option because that will let you use your own natural phrasing.

Alternatively you could use Alexa with virtual switches in ST and the same Harmony Connect triggers. Then it would be “Alexa turn on shades open” for example.