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