Hide keyboard shortcuts

Hot-keys on this page

r m x p   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

72

73

74

75

76

77

78

79

80

81

82

83

84

85

86

87

88

89

90

91

92

93

94

95

96

97

98

99

100

101

102

103

104

105

106

107

108

109

110

111

112

113

114

115

116

117

118

119

120

121

122

123

124

125

126

127

128

129

130

131

132

133

134

135

136

137

138

139

140

141

142

143

144

145

146

147

148

149

150

151

152

153

154

155

156

157

158

159

160

161

162

163

164

165

166

167

168

169

170

171

172

173

174

175

176

177

"""LICENSE 

Copyright 2019 Hermann Krumrey <hermann@krumreyh.com> 

 

This file is part of samsung-ru7179-remote. 

 

samsung-ru7179-remote is free software: you can redistribute it and/or modify 

it under the terms of the GNU General Public License as published by 

the Free Software Foundation, either version 3 of the License, or 

(at your option) any later version. 

 

samsung-ru7179-remote is distributed in the hope that it will be useful, 

but WITHOUT ANY WARRANTY; without even the implied warranty of 

MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 

GNU General Public License for more details. 

 

You should have received a copy of the GNU General Public License 

along with samsung-ru7179-remote. If not, see <http://www.gnu.org/licenses/>. 

LICENSE""" 

 

import ssl 

import time 

import json 

import base64 

import logging 

import websocket 

from typing import Dict, Any, Optional 

from threading import Thread 

from samsung_ru7179_remote.config import load_config, write_config 

 

logger = logging.getLogger("samsung-ru7179-remote") 

 

 

def control_tv( 

command: Dict[str, Any], 

wait_for_response: bool = False, 

timeout: int = 10 

) -> Optional[Dict[str, Any]]: 

""" 

Controls the TV. 

Authentication is automatically handled here as well 

:param command: The command to send 

:param wait_for_response: Waits for a response from the TV after connecting 

:param timeout: Time before a timeout is recognized 

:return: Potential response message from the TV 

""" 

config = load_config() 

state = { 

"connected": False, 

"response": None 

} 

 

app_name = config["remote_name"] 

app_name = str(base64.b64encode(app_name.encode("utf-8")), "utf-8") 

 

url = \ 

"wss://{}:8002/api/v2/channels/samsung.remote.control?name={}&token={}" 

url = url.format(config["tv_ip"], app_name, config["token"]) 

 

def on_message(socket: websocket.WebSocket, message: str): 

""" 

Handles messages from the TV 

This is where authentication and responses are handled 

:param socket: The websocket 

:param message: The received message 

:return: None 

""" 

logger.debug(message) 

data = json.loads(message) 

 

if data["event"] in [ 

"ms.remote.touchDisable", 

"ms.remote.touchEnable" 

]: 

logger.info("Ignoring event {}".format(data["event"])) 

return 

 

if data["event"] == "ms.channel.connect": 

state["connected"] = True 

if "token" in data["data"]: 

config["token"] = data["data"]["token"] 

socket.close() 

else: 

state["response"] = data 

socket.close() 

 

def on_open(socket: websocket.WebSocket): 

""" 

Sends the command over the websocket and checks for timeouts 

:param socket: The websocket 

:return: None 

""" 

def run(): 

logger.info("Websocket connected") 

start = time.time() 

socket.send(json.dumps(command)) 

 

logger.debug("Waiting for connection...") 

while not state["connected"] and time.time() - start < timeout: 

time.sleep(0.1) 

 

if not state["connected"]: 

logger.warning("Connection timed out") 

 

if not wait_for_response or not state["connected"]: 

socket.close() 

 

Thread(target=run).start() 

 

websocket.WebSocketApp( 

url, 

on_message=on_message, 

on_open=on_open 

).run_forever(sslopt={"cert_reqs": ssl.CERT_NONE}) 

 

write_config(config) 

return state["response"] 

 

 

def execute_keypress(key_command: str): 

""" 

Executes a remote keypress on the TV 

A valid token must be in the configuration file 

:param key_command: The command to send. Should be one of the strings in 

the valid_keys set in the commands module 

""" 

logger.info("Sending key {}".format(key_command)) 

control_tv({ 

"method": "ms.remote.control", 

"params": { 

"Cmd": "Click", 

"Option": "false", 

"TypeOfRemote": "SendRemoteKey", 

"DataOfCmd": "KEY_" + key_command 

} 

}) 

 

 

def start_app(app_name: str): 

""" 

Starts an app on the TV 

:param app_name: The name of the app to start 

:return: None 

""" 

apps = list_apps() 

 

try: 

app_id = str(apps[app_name]) 

command = { 

"method": "ms.channel.emit", 

"params": { 

"event": "ed.apps.launch", 

"to": "host", 

"data": { 

"appId": app_id, 

"action_type": "NATIVE_LAUNCH" 

} 

} 

} 

control_tv(command) 

logger.info("Started app {}".format(app_name)) 

except KeyError: 

logger.warning("App does not exist") 

 

 

def list_apps() -> Dict[str, str]: 

""" 

Lists available apps on the TV 

:return: A dictionary mapping app names to app IDs 

""" 

resp = control_tv({ 

"method": "ms.channel.emit", 

"params": {"event": "ed.installedApp.get", "to": "host"} 

}, wait_for_response=True) 

app_info = {} 

for app in resp["data"]["data"]: 

app_info[app["name"]] = app["appId"] 

return app_info