]> git.basschouten.com Git - openhab-addons.git/blob
8f1f25c3837d525d891a3757935b097ee9e2dafa
[openhab-addons.git] /
1 /**
2  * Copyright (c) 2010-2023 Contributors to the openHAB project
3  *
4  * See the NOTICE file(s) distributed with this work for additional
5  * information.
6  *
7  * This program and the accompanying materials are made available under the
8  * terms of the Eclipse Public License 2.0 which is available at
9  * http://www.eclipse.org/legal/epl-2.0
10  *
11  * SPDX-License-Identifier: EPL-2.0
12  */
13 package org.openhab.binding.magentatv.internal.handler;
14
15 import static org.openhab.binding.magentatv.internal.MagentaTVBindingConstants.*;
16 import static org.openhab.binding.magentatv.internal.MagentaTVUtil.*;
17
18 import java.nio.charset.StandardCharsets;
19 import java.security.MessageDigest;
20 import java.security.NoSuchAlgorithmException;
21 import java.text.MessageFormat;
22 import java.util.HashMap;
23 import java.util.StringTokenizer;
24
25 import org.eclipse.jdt.annotation.NonNullByDefault;
26 import org.eclipse.jetty.client.HttpClient;
27 import org.openhab.binding.magentatv.internal.MagentaTVException;
28 import org.openhab.binding.magentatv.internal.config.MagentaTVDynamicConfig;
29 import org.openhab.binding.magentatv.internal.network.MagentaTVHttp;
30 import org.openhab.binding.magentatv.internal.network.MagentaTVNetwork;
31 import org.openhab.binding.magentatv.internal.network.MagentaTVOAuth;
32 import org.slf4j.Logger;
33 import org.slf4j.LoggerFactory;
34
35 /**
36  * The {@link MagentaTVControl} implements the control functions for the
37  * receiver.
38  *
39  * @author Markus Michels - Initial contribution
40  */
41 @NonNullByDefault
42 public class MagentaTVControl {
43     private final Logger logger = LoggerFactory.getLogger(MagentaTVControl.class);
44     private static final HashMap<String, String> KEY_MAP = new HashMap<>();
45
46     private final MagentaTVNetwork network;
47     private final MagentaTVHttp http = new MagentaTVHttp();
48     private final MagentaTVOAuth oauth;
49     private final MagentaTVDynamicConfig config;
50     private boolean initialized = false;
51     private String thingId = "";
52
53     public MagentaTVControl() {
54         config = new MagentaTVDynamicConfig();
55         network = new MagentaTVNetwork();
56         oauth = new MagentaTVOAuth(new HttpClient());
57     }
58
59     public MagentaTVControl(MagentaTVDynamicConfig config, MagentaTVNetwork network, HttpClient httpClient) {
60         this.thingId = config.getFriendlyName();
61         this.network = network;
62         this.oauth = new MagentaTVOAuth(httpClient);
63         this.config = config;
64         this.config.setTerminalID(computeMD5(network.getLocalMAC().toUpperCase() + config.getUDN()));
65         this.config.setLocalIP(network.getLocalIP());
66         this.config.setLocalMAC(network.getLocalMAC());
67         initialized = true;
68     }
69
70     public boolean isInitialized() {
71         return initialized;
72     }
73
74     public void setThingId(String thingId) {
75         this.thingId = thingId;
76     }
77
78     /**
79      * Returns the thingConfig - the Control class adds various attributes of the
80      * discovered device (like the MR model)
81      *
82      * @return thingConfig
83      */
84     public MagentaTVDynamicConfig getConfig() {
85         return config;
86     }
87
88     /**
89      * Initiate OAuth authentication
90      *
91      * @param accountName T-Online user id
92      * @param accountPassword T-Online password
93      * @return true: successful, false: failed
94      *
95      * @throws MagentaTVException
96      */
97     public String getUserId(String accountName, String accountPassword) throws MagentaTVException {
98         return oauth.getUserId(accountName, accountPassword);
99     }
100
101     /**
102      * Retries the device properties. This will result in an Exception if the device
103      * is not connected.
104      *
105      * <p>
106      * Response is returned in XMl format, e.g.:
107      *
108      * <p>
109      * {@code
110      * <?xml version="1.0"?> <root xmlns="urn:schemas-upnp-org:device-1-0">
111      * <specVersion><major>1</major><minor>0</minor></specVersion> <device>
112      * <UDN>uuid:70dff25c-1bdf-5731-a283-XXXXXXXX</UDN>
113      * <friendlyName>DMS_XXXXXXXXXXXX</friendlyName>
114      * <deviceType>urn:schemas-upnp-org:device:tvdevice:1</deviceType>
115      * <manufacturer>Zenterio</manufacturer> <modelName>MR401B</modelName>
116      * <modelNumber>R01A5</modelNumber> <productVersionNumber>&quot; 334
117      * &quot;</productVersionNumber> <productType>stb</productType>
118      * <serialNumber></serialNumber> <X_wakeOnLan>0</X_wakeOnLan> <serviceList>
119      * <service> <serviceType>urn:dial-multiscreen-org:service:dial:1</serviceType>
120      * <serviceId>urn:dial-multiscreen-org:service:dial</serviceId> </service>
121      * </serviceList> </device> </root>
122      * }
123      *
124      * @return true: device is online, false: device is offline
125      * @throws MagentaTVException
126      */
127     public boolean checkDev() throws MagentaTVException {
128         logger.debug("{}: Check device {} ({}:{})", thingId, config.getTerminalID(), config.getIpAddress(),
129                 config.getPort());
130
131         String url = MessageFormat.format(CHECKDEV_URI, config.getIpAddress(), config.getPort(),
132                 config.getDescriptionUrl());
133         String result = http.httpGet(buildHost(), url, "");
134         if (result.contains("<modelName>")) {
135             config.setModel(substringBetween(result, "<modelName>", "</modelName>"));
136         }
137         if (result.contains("<modelNumber>")) {
138             config.setHardwareVersion(substringBetween(result, "<modelNumber>", "</modelNumber>"));
139         }
140         if (result.contains("<X_wakeOnLan>")) {
141             String wol = substringBetween(result, "<X_wakeOnLan>", "</X_wakeOnLan>");
142             config.setWakeOnLAN(wol);
143             logger.debug("{}: Wake-on-LAN is {}", thingId, "0".equals(wol) ? "disabled" : "enabled");
144         }
145         if (result.contains("<productVersionNumber>")) {
146             String version;
147             if (result.contains("<productVersionNumber>&quot; ")) {
148                 version = substringBetween(result, "<productVersionNumber>&quot; ", " &quot;</productVersionNumber>");
149             } else {
150                 version = substringBetween(result, "<productVersionNumber>", "</productVersionNumber>");
151             }
152             config.setFirmwareVersion(version);
153         }
154         if (result.contains("<friendlyName>")) {
155             String friendlyName = result.substring(result.indexOf("<friendlyName>") + "<friendlyName>".length(),
156                     result.indexOf("</friendlyName>"));
157             config.setFriendlyName(friendlyName);
158         }
159         if (result.contains("<UDN>uuid:")) {
160             String udn = result.substring(result.indexOf("<UDN>uuid:") + "<UDN>uuid:".length(),
161                     result.indexOf("</UDN>"));
162             if (config.getUDN().isEmpty()) {
163                 config.setUDN(udn);
164             }
165         }
166         logger.trace("{}: Online status verified for device {}:{}, UDN={}", thingId, config.getIpAddress(),
167                 config.getPort(), config.getUDN());
168         return true;
169     }
170
171     /**
172      *
173      * Sends a SUBSCRIBE request to the MR. This also defines the local callback url
174      * used by the MR to return the pairing code and event information.
175      *
176      * Subscripbe to event channel a) receive the pairing code b) receive
177      * programInfo and playStatus events after successful paring
178      *
179      * {@code
180      * SUBSCRIBE /upnp/service/X-CTC_RemotePairing/Event HTTP/1.1\r\n HOST:
181      * $remote_ip:$remote_port CALLBACK: <http://$local_ip:$local_port/>\r\n // NT:
182      * upnp:event\r\n // TIMEOUT: Second-300\r\n // CONNECTION: close\r\n // \r\n
183      * }
184      *
185      * @throws MagentaTVException
186      */
187     public void subscribeEventChannel() throws MagentaTVException {
188         String sid = "";
189         logger.debug("{}: Subscribe Event Channel (terminalID={}, {}:{}", thingId, config.getTerminalID(),
190                 config.getIpAddress(), config.getPort());
191         String subscribe = MessageFormat.format(PAIRING_SUBSCRIBE, config.getIpAddress(), config.getPort(),
192                 network.getLocalIP(), network.getLocalPort(), PAIRING_NOTIFY_URI, PAIRING_TIMEOUT_SEC);
193         String response = http.sendData(config.getIpAddress(), config.getPort(), subscribe);
194         if (!response.contains("200 OK")) {
195             response = substringBefore(response, "SERVER");
196             throw new MagentaTVException("Unable to subscribe to pairing channel: " + response);
197         }
198         if (!response.contains(NOTIFY_SID)) {
199             throw new MagentaTVException("Unable to subscribe to pairing channel, SID missing: " + response);
200         }
201
202         StringTokenizer tokenizer = new StringTokenizer(response, "\r\n");
203         while (tokenizer.hasMoreElements()) {
204             String str = tokenizer.nextToken();
205             if (!str.isEmpty()) {
206                 if (str.contains(NOTIFY_SID)) {
207                     sid = str.substring("SID: uuid:".length());
208                     logger.debug("{}: SUBSCRIBE returned SID {}", thingId, sid);
209                     break;
210                 }
211             }
212         }
213     }
214
215     /**
216      * Send Pairing Request to the Media Receiver. The method waits for the
217      * response, but the pairing code will be received via the NOTIFY callback (see
218      * NotifyServlet).
219      *
220      * <p>
221      * XML format for Pairing Request:
222      *
223      * <p>
224      * {@code
225      * <s:Envelope
226      * xmlns:s=\"http://schemas.xmlsoap.org/soap/envelope/\"
227      * <s:encodingStyle=\"http://schemas.xmlsoap.org/soap/encoding/\"> <s:Body>\n
228      * <u:X-pairingRequest
229      * xmlns:u=\"urn:schemas-upnp-org:service:X-CTC_RemotePairing:1\">\n
230      * <pairingDeviceID>$pairingDeviceID</pairingDeviceID>\n
231      * <friendlyName>$friendlyName</friendlyName>\n <userID>$userID</userID>\n
232      * </u:X-pairingRequest>\n </s:Body> </s:Envelope>
233      * }
234      * 
235      * @return true: pairing successful
236      * @throws MagentaTVException
237      */
238     public boolean sendPairingRequest() throws MagentaTVException {
239         logger.debug("{}: Send Pairing Request (deviceID={}, type={}, userID={})", thingId, config.getTerminalID(),
240                 DEF_FRIENDLY_NAME, config.getUserId());
241         resetPairing();
242
243         String soapBody = MessageFormat.format(PAIRING_SOAP_BODY, config.getTerminalID(), DEF_FRIENDLY_NAME,
244                 config.getUserId());
245         String soapXml = MessageFormat.format(SOAP_ENVELOPE, soapBody);
246         String response = http.httpPOST(buildHost(), buildReceiverUrl(PAIRING_CONTROL_URI), soapXml,
247                 PAIRING_SOAP_ACTION, CONNECTION_CLOSE);
248
249         // pairingCode will be received by the Servlet, is calls onPairingResult()
250         // Exception if request failed (response code != HTTP_OK)
251         if (!response.contains("X-pairingRequestResponse") || !response.contains("<result>")) {
252             throw new MagentaTVException("Unexpected result for pairing response: " + response);
253         }
254
255         String result = substringBetween(response, "<result>", "</result>");
256         if (!"0".equals(result)) {
257             throw new MagentaTVException("Pairing failed, result=" + result);
258         }
259
260         logger.debug("{}: Pairing initiated (deviceID={}).", thingId, config.getTerminalID());
261         return true;
262     }
263
264     /**
265      * Calculates the verifificationCode to complete pairing. This will be triggered
266      * as a result after receiving the pairing code provided by the MR. The
267      * verification code is the MD5 hash of {@code <Pairing Code><Terminal-ID><User ID>}
268      *
269      * @param pairingCode Pairing code received from the MR
270      * @return true: a new code has been generated, false: the code matches a
271      *         previous pairing
272      */
273     public boolean generateVerificationCode(String pairingCode) {
274         if (config.getPairingCode().equals(pairingCode) && !config.getVerificationCode().isEmpty()) {
275             logger.debug("{}: Pairing code ({}) refreshed, verificationCode={}", thingId, pairingCode,
276                     config.getVerificationCode());
277             return false;
278         }
279         config.setPairingCode(pairingCode);
280         String md5Input = pairingCode + config.getTerminalID() + config.getUserId();
281         config.setVerificationCode(computeMD5(md5Input).toUpperCase());
282         logger.debug("{}: VerificationCode({}): Input={}, code={}", thingId, config.getTerminalID(), md5Input,
283                 config.getVerificationCode());
284         return true;
285     }
286
287     /**
288      * Send a pairing verification request to the receiver. This is important to
289      * complete the pairing process. You should see a message like "Connected to
290      * openHAB" on your TV screen.
291      *
292      * @return true: successful, false: a non-critical error occured, caller handles
293      *         this
294      * @throws MagentaTVException
295      */
296     public boolean verifyPairing() throws MagentaTVException {
297         logger.debug("{}: Verify pairing (id={}, code={}", thingId, config.getTerminalID(),
298                 config.getVerificationCode());
299         String soapBody = MessageFormat.format(PAIRCHECK_SOAP_BODY, config.getTerminalID(),
300                 config.getVerificationCode());
301         String soapXml = MessageFormat.format(SOAP_ENVELOPE, soapBody);
302         String response = http.httpPOST(buildHost(), buildReceiverUrl(PAIRCHECK_URI), soapXml, PAIRCHECK_SOAP_ACTION,
303                 CONNECTION_CLOSE);
304
305         // Exception if request failed (response code != HTTP_OK)
306         if (!response.contains("<pairingResult>")) {
307             throw new MagentaTVException("Unexpected result for pairing verification: " + response);
308         }
309
310         String result = getXmlValue(response, "pairingResult");
311         if (!"0".equals(result)) {
312             logger.debug("{}: Pairing failed or pairing no longer valid, result={}", thingId, result);
313             resetPairing();
314             // let the caller decide how to proceed
315             return false;
316         }
317
318         if (!config.isMR400()) {
319             String enable4K = getXmlValue(response, "Enable4K");
320             String enableSAT = getXmlValue(response, "EnableSAT");
321             logger.debug("{}: Features: Enable4K:{}, EnableSAT:{}", thingId, enable4K, enableSAT);
322         }
323         return true;
324     }
325
326     /**
327      *
328      * @return true if pairing is completed (verification code was generated)
329      */
330     public boolean isPaired() {
331         // pairing was completed successful if we have the verification code
332         return !config.getVerificationCode().isEmpty();
333     }
334
335     /**
336      * Reset pairing information (e.g. when verification failed)
337      */
338     public void resetPairing() {
339         // pairing no longer valid
340         config.setPairingCode("");
341         config.setVerificationCode("");
342     }
343
344     /**
345      * Send key code to the MR (via SOAP request). A key code could be send by it's
346      * code (0x.... notation) or with a symbolic namne, which will first be mapped
347      * to the key code
348      *
349      * <p>
350      * XML format for Send Key:
351      *
352      * <p>
353      * {@code
354      * <s:Envelope xmlns:s=\"http://schemas.xmlsoap.org/soap/envelope/\"
355      * s:encodingStyle=\"http://schemas.xmlsoap.org/soap/encoding/\"> <s:Body>\n
356      * <u:X_CTC_RemoteKey
357      * xmlns:u=\"urn:schemas-upnp-org:service:X-CTC_RemoteControl:1\">\n
358      * <InstanceID>0</InstanceID>\n
359      * <KeyCode>keyCode=$keyCode^$pairingDeviceID:$verificationCode^userID:$userID</KeyCode>\n
360      * </u:X_CTC_RemoteKey>\n </s:Body></s:Envelope>
361      * }
362      *
363      * @param keyName
364      * @return true: successful, false: failed, e.g. unkown key code
365      * @throws MagentaTVException
366      */
367     public boolean sendKey(String keyName) throws MagentaTVException {
368         String keyCode = getKeyCode(keyName);
369         logger.debug("{}: Send Key {} (keyCode={}, tid={})", thingId, keyName, keyCode, config.getTerminalID());
370         if (keyCode.length() <= "0x".length()) {
371             logger.debug("{}: Key {} is unkown!", thingId, keyCode);
372             return false;
373         }
374
375         String soapBody = MessageFormat.format(SENDKEY_SOAP_BODY, keyCode, config.getTerminalID(),
376                 config.getVerificationCode(), config.getUserId());
377         String soapXml = MessageFormat.format(SOAP_ENVELOPE, soapBody);
378         logger.debug("{}: send keyCode={} to {}:{}", thingId, keyCode, config.getIpAddress(), config.getPort());
379         logger.trace("{}: sendKey terminalid={}, pairingCode={}, verificationCode={}, userId={}", thingId,
380                 config.getTerminalID(), config.getPairingCode(), config.getVerificationCode(), config.getUserId());
381         http.httpPOST(buildHost(), buildReceiverUrl(SENDKEY_URI), soapXml, SENDKEY_SOAP_ACTION, CONNECTION_CLOSE);
382         // Exception if request failed (response code != HTTP_OK)
383         // pairingCode will be received by the Servlet, is calls onPairingResult()
384         return true;
385     }
386
387     /**
388      * Select channel for TV
389      *
390      * @param channel new channel (a sequence of numbers, which will be send one by one)
391      * @return true:ok, false: failed
392      */
393     public boolean selectChannel(String channel) throws MagentaTVException {
394         logger.debug("{}: Select channel {}", thingId, channel);
395         for (int i = 0; i < channel.length(); i++) {
396             if (!sendKey("" + channel.charAt(i))) {
397                 return false;
398             }
399             try {
400                 Thread.sleep(200);
401             } catch (InterruptedException e) {
402             }
403         }
404         return true;
405     }
406
407     /**
408      * Get key code to send to receiver
409      *
410      * @param key Key for which to get the key code
411      * @return
412      */
413     private String getKeyCode(String key) {
414         if (key.contains("0x")) {
415             // direct key code
416             return key;
417         }
418         String code = KEY_MAP.get(key);
419         return code != null ? code : "";
420     }
421
422     /**
423      * Map playStatus code to string for a list of codes see
424      * http://support.huawei.com/hedex/pages/DOC1100366313CEH0713H/01/DOC1100366313CEH0713H/01/resources/dsv_hdx_idp/DSV/en/en-us_topic_0094619231.html
425      *
426      * @param playStatus Integer code parsed form json (see EV_PLAYCHG_XXX)
427      * @return playStatus as String
428      */
429     public String getPlayStatus(int playStatus) {
430         switch (playStatus) {
431             case EV_PLAYCHG_PLAY:
432                 return "playing";
433             case EV_PLAYCHG_STOP:
434                 return "stopped";
435             case EV_PLAYCHG_PAUSE:
436                 return "paused";
437             case EV_PLAYCHG_TRICK:
438                 return "tricking";
439             case EV_PLAYCHG_MC_PLAY:
440                 return "playing (MC)";
441             case EV_PLAYCHG_UC_PLAY:
442                 return "playing (UC)";
443             case EV_PLAYCHG_BUFFERING:
444                 return "buffering";
445             default:
446                 return Integer.toString(playStatus);
447         }
448     }
449
450     /**
451      * Map runningStatus code to string for a list of codes see
452      * http://support.huawei.com/hedex/pages/DOC1100366313CEH0713H/01/DOC1100366313CEH0713H/01/resources/dsv_hdx_idp/DSV/en/en-us_topic_0094619523.html
453      *
454      * @param runStatus Integer code parsed form json (see EV_EITCHG_RUNNING_XXX)
455      * @return runningStatus as String
456      */
457     public String getRunStatus(int runStatus) {
458         switch (runStatus) {
459             case EV_EITCHG_RUNNING_NOT_RUNNING:
460                 return "stopped";
461             case EV_EITCHG_RUNNING_STARTING:
462                 return "starting";
463             case EV_EITCHG_RUNNING_PAUSING:
464                 return "paused";
465             case EV_EITCHG_RUNNING_RUNNING:
466                 return "running";
467             default:
468                 return Integer.toString(runStatus);
469         }
470     }
471
472     /**
473      * builds url from the discovered IP address/port and the requested uri
474      *
475      * @param uri requested URI
476      * @return the complete URL
477      */
478     public String buildReceiverUrl(String uri) {
479         return MessageFormat.format("http://{0}:{1}{2}", config.getIpAddress(), config.getPort(), uri);
480     }
481
482     /**
483      * build host string
484      *
485      * @return formatted string (<ip_address>:<port>)
486      */
487     private String buildHost() {
488         return config.getIpAddress() + ":" + config.getPort();
489     }
490
491     /**
492      * Given a string, return the MD5 hash of the String.
493      *
494      * @param unhashed The string contents to be hashed.
495      * @return MD5 Hashed value of the String. Null if there is a problem hashing
496      *         the String.
497      */
498     public static String computeMD5(String unhashed) {
499         try {
500             byte[] bytesOfMessage = unhashed.getBytes(StandardCharsets.UTF_8);
501
502             MessageDigest md5 = MessageDigest.getInstance(HASH_ALGORITHM_MD5);
503             byte[] hash = md5.digest(bytesOfMessage);
504             StringBuilder sb = new StringBuilder(2 * hash.length);
505             for (byte b : hash) {
506                 sb.append(String.format("%02x", b & 0xff));
507             }
508
509             return sb.toString();
510         } catch (NoSuchAlgorithmException e) {
511             return "";
512         }
513     }
514
515     /**
516      * Helper to parse a Xml tag value from string without using a complex XML class
517      *
518      * @param xml Input string in the format {@code <tag>value</tag>}
519      * @param tagName The tag to find
520      * @return Tag value (between {@code <tag>} and {@code </tag>})
521      */
522     public static String getXmlValue(String xml, String tagName) {
523         String open = "<" + tagName + ">";
524         String close = "</" + tagName + ">";
525         if (xml.contains(open) && xml.contains(close)) {
526             return substringBetween(xml, open, close);
527         }
528         return "";
529     }
530
531     /**
532      * Initialize key map (key name -> key code)
533      * "
534      * for a list of valid key codes see
535      * http://support.huawei.com/hedex/pages/DOC1100366313CEH0713H/01/DOC1100366313CEH0713H/01/resources/dsv_hdx_idp/DSV/en/en-us_topic_0094619112.html
536      */
537     static {
538         KEY_MAP.put("POWER", "0x0100");
539         KEY_MAP.put("MENU", "0x0110");
540         KEY_MAP.put("EPG", "0x0111");
541         KEY_MAP.put("TVMENU", "0x0454");
542         KEY_MAP.put("VODMENU", "0x0455");
543         KEY_MAP.put("TVODMENU", "0x0456");
544         KEY_MAP.put("NVODMENU", "0x0458");
545         KEY_MAP.put("INFO", "0x010C");
546         KEY_MAP.put("TTEXT", "0x0560");
547         KEY_MAP.put("0", "0x0030");
548         KEY_MAP.put("1", "0x0031");
549         KEY_MAP.put("2", "0x0032");
550         KEY_MAP.put("3", "0x0033");
551         KEY_MAP.put("4", "0x0034");
552         KEY_MAP.put("5", "0x0035");
553         KEY_MAP.put("6", "0x0036");
554         KEY_MAP.put("7", "0x0037");
555         KEY_MAP.put("8", "0x0038");
556         KEY_MAP.put("9", "0x0039");
557         KEY_MAP.put("SPACE", "0x0020");
558         KEY_MAP.put("POUND", "0x0069");
559         KEY_MAP.put("STAR", "0x006A");
560         KEY_MAP.put("UP", "0x0026");
561         KEY_MAP.put("DOWN", "0x0028");
562         KEY_MAP.put("LEFT", "0x0025");
563         KEY_MAP.put("RIGHT", "0x0027");
564         KEY_MAP.put("PGUP", "0x0021");
565         KEY_MAP.put("PGDOWN", "0x0022");
566         KEY_MAP.put("DELETE", "0x0008");
567         KEY_MAP.put("ENTER", "0x000D");
568         KEY_MAP.put("SEARCH", "0x0451");
569         KEY_MAP.put("RED", "0x0113");
570         KEY_MAP.put("GREEN", "0x0114");
571         KEY_MAP.put("YELLOW", "0x0115");
572         KEY_MAP.put("BLUE", "0x0116");
573         KEY_MAP.put("OPTION", "0x0460");
574         KEY_MAP.put("OK", "0x000D");
575         KEY_MAP.put("BACK", "0x0008");
576         KEY_MAP.put("EXIT", "0x045D");
577         KEY_MAP.put("PORTAL", "0x0110");
578         KEY_MAP.put("VOLUP", "0x0103");
579         KEY_MAP.put("VOLDOWN", "0x0104");
580         KEY_MAP.put("INTER", "0x010D");
581         KEY_MAP.put("HELP", "0x011C");
582         KEY_MAP.put("SETTINGS", "0x011D");
583         KEY_MAP.put("MUTE", "0x0105");
584         KEY_MAP.put("CHUP", "0x0101");
585         KEY_MAP.put("CHDOWN", "0x0102");
586         KEY_MAP.put("REWIND", "0x0109");
587         KEY_MAP.put("PLAY", "0x0107");
588         KEY_MAP.put("PAUSE", "0x0107");
589         KEY_MAP.put("FORWARD", "0x0108");
590         KEY_MAP.put("TRACK", "0x0106");
591         KEY_MAP.put("LASTCH", "0x045E");
592         KEY_MAP.put("PREVCH", "0x010B");
593         KEY_MAP.put("NEXTCH", "0x0107");
594         KEY_MAP.put("RECORD", "0x0461");
595         KEY_MAP.put("STOP", "0x010E");
596         KEY_MAP.put("BEGIN", "0x010B");
597         KEY_MAP.put("END", "0x010A");
598         KEY_MAP.put("REPLAY", "0x045B");
599         KEY_MAP.put("SKIP", "0x045C");
600         KEY_MAP.put("SUBTITLE", "0x236");
601         KEY_MAP.put("RECORDINGS", "0x045F");
602         KEY_MAP.put("FAV", "0x0119");
603         KEY_MAP.put("SOURCE", "0x0083");
604         KEY_MAP.put("SWITCH", "0x0118");
605         KEY_MAP.put("IPTV", "0x0081");
606         KEY_MAP.put("PC", "0x0082");
607         KEY_MAP.put("PIP", "0x0084");
608         KEY_MAP.put("MULTIVIEW", "0x0562");
609         KEY_MAP.put("F1", "0x0070");
610         KEY_MAP.put("F2", "0x0071");
611         KEY_MAP.put("F3", "0x0072");
612         KEY_MAP.put("F4", "0x0073");
613         KEY_MAP.put("F5", "0x0074");
614         KEY_MAP.put("F6", "0x0075");
615         KEY_MAP.put("F7", "0x0076");
616         KEY_MAP.put("F8", "0x0077");
617         KEY_MAP.put("F9", "0x0078");
618         KEY_MAP.put("F10", "0x0079");
619         KEY_MAP.put("F11", "0x007A");
620         KEY_MAP.put("F12", "0x007B");
621         KEY_MAP.put("F13", "0x007C");
622         KEY_MAP.put("F14", "0x007D");
623         KEY_MAP.put("F15", "0x007E");
624         KEY_MAP.put("F16", "0x007F");
625
626         KEY_MAP.put("PVR", "0x0461");
627         KEY_MAP.put("RADIO", "0x0462");
628
629         // Those key codes are missing and not included in the spec
630         // KEY_MAP.put("TV", "0x");
631         // KEY_MAP.put("RADIO", "0x");
632         // KEY_MAP.put("MOVIES", "0x");
633     }
634 }