1+ /* ===== HUBITAT INTEGRATION VERSION =====================================================
2+ Hubitat - Samsung TV Remote Driver Switch Only
3+ Copyright 2020 Dave Gutheinz
4+ Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
5+ except in compliance with the License. You may obtain a copy of the License at:
6+ http://www.apache.org/licenses/LICENSE-2.0
7+ Unless required by applicable law or agreed to in writing, software distributed under the
8+ License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
9+ either express or implied. See the License for the specific language governing permissions
10+ and limitations under the License.
11+ ===== DISCLAIMERS =========================================================================
12+ THE AUTHOR OF THIS INTEGRATION IS NOT ASSOCIATED WITH SAMSUNG. THIS CODE USES
13+ TECHNICAL DATA DERIVED FROM GITHUB SOURCES AND AS PERSONAL INVESTIGATION.
14+ ===== 2022 History
15+ 02.22 Created Switch Only Version.
16+
17+ ===========================================================================================*/
18+ def driverVer () { return " 1.0.0" }
19+ // Poll Timeout in seconds for user changes
20+ import groovy.json.JsonOutput
21+
22+ metadata {
23+ definition (name : " Samsung TV Switch" ,
24+ namespace : " davegut" ,
25+ author : " David Gutheinz" ,
26+ importUrl : " https://raw.githubusercontent.com/DaveGut/HubitatActive/master/SamsungTvSwitch/SamsungTVSwitch.groovy"
27+ ){
28+ capability " Switch" // On/Off
29+ capability " Polling" // Poll for on/off state of device / connected via wifi
30+ }
31+ preferences {
32+ input (" deviceIp" , " text" , title : " Samsung TV Ip" , defaultValue : " " )
33+ input (" tvWsToken" , " text" ,
34+ title : " The WS Token for your TV (from previous Installation)" ,
35+ defaultValue : state. token)
36+ input (" altWolMac" , " bool" , title : " Use alternate WOL MAC" , defaultValue : false )
37+ input (" debugLog" , " bool" ,
38+ title : " Enable debug logging for 30 minutes" , defaultValue : false )
39+ input (" infoLog" , " bool" ,
40+ title : " Enable description text logging" , defaultValue : true )
41+ }
42+ }
43+
44+ // ===== Installation, setup and update =====
45+ def installed () {
46+ state. token = " "
47+ runIn(1 , updated)
48+ }
49+
50+ def updated () {
51+ logInfo(" updated" )
52+ unschedule()
53+ def updateData = [:]
54+ def status = " OK"
55+ def statusReason
56+ def deviceData
57+ if (deviceIp) {
58+ // Get onOff status for use in setup
59+ updateData << [deviceIp : " deviceIp" ]
60+ if (deviceIp != deviceIp. trim()) {
61+ deviceIp = deviceIp. trim()
62+ device. updateSetting(" deviceIp" , [type :" text" , value : deviceIp])
63+ }
64+ deviceData = getDeviceData()
65+ } else {
66+ logInfo(" updated: [status: failed, statusReason: No device IP]" )
67+ return
68+ }
69+ if (deviceData. status == " failed" ) {
70+ logInfo(" updated: [status: failed, statusReason: Can not connect to TV]" )
71+ sendEvent(name : " switch" , value : " off" )
72+ return
73+ } else { sendEvent(name : " switch" , value : " on" ) }
74+
75+
76+ state. token = tvWsToken
77+ updateData << [tvToken : tvWsToken]
78+ if (debug) { runIn(1800 , debugOff) }
79+ updateData << [debugLog : debugLog, infoLog : infoLog]
80+ updateData << [driver : versionUpdate()]
81+ def updateStatus = [:]
82+ updateStatus << [status : status]
83+ if (statusReason != " " ) {
84+ updateStatus << [statusReason : statusReason]
85+ }
86+ updateStatus << [updateData : updateData, deviceData : deviceData]
87+ logInfo(" updated: ${ updateStatus} " )
88+ }
89+
90+ def getDeviceData () {
91+ def deviceData = [:]
92+ try {
93+ httpGet([uri : " http://${ deviceIp} :8001/api/v2/" , timeout : 5 ]) { resp ->
94+ deviceData << [status : " OK" ]
95+ def wifiMac = resp. data. device. wifiMac
96+ updateDataValue(" deviceMac" , wifiMac)
97+ deviceData << [mac : wifiMac]
98+ def alternateWolMac = wifiMac. replaceAll(" :" , " " ). toUpperCase()
99+ updateDataValue(" alternateWolMac" , alternateWolMac)
100+ deviceData << [alternateWolMac : alternateWolMac]
101+ def dni = getMACFromIP(deviceIp)
102+ device. setDeviceNetworkId(dni)
103+ deviceData << [dni : dni]
104+ def modelYear = " 20" + resp. data. device. model[0 .. 1 ]
105+ updateDataValue(" modelYear" , modelYear)
106+ deviceData << [modelYear : modelYear]
107+ def frameTv = " false"
108+ if (resp.data.device.FrameTVSupport ) {
109+ frameTv = resp.data.device.FrameTVSupport
110+ }
111+ updateDataValue(" frameTv" , frameTv)
112+ deviceData << [frameTv : frameTv]
113+
114+ def tokenSupport = false
115+ if (resp.data.device.TokenAuthSupport ) {
116+ tokenSupport = resp.data.device.TokenAuthSupport
117+ }
118+ updateDataValue(" tokenSupport" , tokenSupport)
119+ deviceData << [tokenSupport : tokenSupport]
120+ def uuid = resp. data. device. duid. substring(5 )
121+ updateDataValue(" uuid" , uuid)
122+ deviceData << [uuid : uuid]
123+ }
124+ } catch (error) {
125+ deviceData << [status : " failed" , statusReason : [error : error]]
126+ }
127+ return deviceData
128+ }
129+
130+ def versionUpdate () {
131+ if (! getDataValue(" driverVersion" ) || getDataValue(" driverVersion" ) != driverVer()) {
132+ updateDataValue(" driverVersion" , driverVer())
133+ }
134+ return driverVer()
135+ }
136+
137+ def poll () {
138+ def onOff
139+ try {
140+ httpGet([uri : " http://${ deviceIp} :8001/api/v2/" , timeout : 5 ]) { resp ->
141+ onOff = " on"
142+ }
143+ } catch (error) {
144+ onOff = " off"
145+ }
146+ if (device. currentValue(" switch" ) != onOff) {
147+ sendEvent(name : " switch" , value : onOff)
148+ logDebug(" poll: [switch: ${ onOff} ]" )
149+ }
150+ }
151+
152+ // ===== Commands =====
153+ // Switch
154+ def on () {
155+ def wolMac = device. deviceNetworkId
156+ if (altWolMac) {
157+ wolMac = getDataValue(" alternateWolMac" )
158+ }
159+ logDebug(" on: wolMac = ${ wolMac} " )
160+ def wol = new hubitat.device.HubAction (" wake on lan ${ wolMac} " ,
161+ hubitat.device.Protocol . LAN ,
162+ null )
163+ sendHubCommand(wol)
164+ sendEvent(name : " switch" , value : " on" )
165+ runIn(5 , poll)
166+ }
167+
168+ def off () {
169+ logDebug(" off: frameTv = ${ getDataValue("frameTv")} " )
170+ if (getDataValue(" frameTv" ) == " false" ) {
171+ sendKey(" POWER" )
172+ } else {
173+ sendKey(" POWER" , " Press" )
174+ pauseExecution(3000 )
175+ sendKey(" POWER" , " Release" )
176+ }
177+ sendEvent(name : " switch" , value : " off" )
178+ runIn(30 , poll)
179+ }
180+
181+ // ===== WebSocket Interace
182+ def sendKey (key , cmd = " Click" ) {
183+ key = " KEY_${ key.toUpperCase()} "
184+ def data = [method :" ms.remote.control" ,
185+ params :[Cmd :" ${ cmd} " ,
186+ DataOfCmd :" ${ key} " ,
187+ Option : false ,
188+ TypeOfRemote :" SendRemoteKey" ]]
189+ sendMessage(" remote" , JsonOutput . toJson(data) )
190+ }
191+
192+ def connect (funct ) {
193+ logDebug(" connect: function = ${ funct} " )
194+ def samsungMeth = " samsung.remote.control"
195+ if (funct == " frameArt" ) {
196+ samsungMeth = " com.samsung.art-app"
197+ }
198+ def url
199+ def name = " SHViaXRhdCBTYW1zdW5nIFJlbW90ZQ=="
200+ if (getDataValue(" tokenSupport" ) == " true" ) {
201+ url = " wss://${ deviceIp} :8002/api/v2/channels/${ samsungMeth} ?name=${ name} &token=${ state.token} "
202+ } else {
203+ url = " ws://${ deviceIp} :8001/api/v2/channels/${ samsungMeth} ?name=${ name} "
204+ }
205+ state. currentFunction = funct
206+ interfaces. webSocket. connect(url, ignoreSSLIssues : true , pingInterval : 60 )
207+ }
208+
209+ def sendMessage (funct , data ) {
210+ logDebug(" sendMessage: [function: ${ funct} , connectType: ${ state.currentFunction} , " +
211+ " data: ${ data} ]" )
212+ connect(funct)
213+ pauseExecution(400 )
214+ interfaces. webSocket. sendMessage(data)
215+ runIn(30 , close)
216+ }
217+
218+ def close () {
219+ if (device. currentValue(" switch" ) == " on" ) {
220+ interfaces. webSocket. close()
221+ } else {
222+ logDebug(" close: Not executed. Device is off" )
223+ }
224+ }
225+
226+ def webSocketStatus (message ) {
227+ logDebug(" webSocketStatus: [message: ${ message} ]" )
228+ }
229+
230+ def parse (resp ) {
231+ resp = parseJson(resp)
232+ logDebug(" parse: ${ resp} " )
233+ def event = resp. event
234+ def logMsg = " parse: event = ${ event} "
235+ if (event == " ms.channel.connect" ) {
236+ logMsg + = " , webSocket open"
237+ def newToken = resp. data. token
238+ if (newToken != null && newToken != state. token) {
239+ logMsg + = " , token updated to ${ newToken} "
240+ state. token = newToken
241+ }
242+ } else if (event == " ms.channel.ready" ) {
243+ logMsg + = " , webSocket connected"
244+ } else if (event == " ms.error" ) {
245+ logMsg + = " Error Event. Closing webSocket"
246+ } else {
247+ logMsg + = " , message = ${ resp} "
248+ }
249+ logDebug(logMsg)
250+ }
251+
252+ // ===== Logging=====
253+ def logTrace (msg ){
254+ log. trace " [${ device.label} , ${ driverVer()} ]:: ${ msg} "
255+ }
256+
257+ def logInfo (msg ) {
258+ if (infoLog == true ) {
259+ log. info " [${ device.label} , ${ driverVer()} ]:: ${ msg} "
260+ }
261+ }
262+
263+ def debugOff () {
264+ device. updateSetting(" debugLog" , [type :" bool" , value : false ])
265+ logInfo(" Debug logging is false." )
266+ }
267+
268+ def logDebug (msg ) {
269+ if (debugLog == true ) {
270+ log. debug " [${ device.label} , ${ driverVer()} ]:: ${ msg} "
271+ }
272+ }
273+
274+ def logWarn (msg ) { log. warn " [${ device.label} , ${ driverVer()} ]:: ${ msg} " }
275+
276+ // End-of-File
0 commit comments