Skip to content

Commit 796548e

Browse files
authored
Add files via upload
1 parent 9f3070e commit 796548e

File tree

1 file changed

+276
-0
lines changed

1 file changed

+276
-0
lines changed
+276
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,276 @@
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

Comments
 (0)