|
| 1 | +#!/usr/bin/env python |
| 2 | +# |
| 3 | +# Copyright 2012 Steven Le ([email protected]) |
| 4 | +# |
| 5 | +# Licensed under the Apache License, Version 2.0 (the "License"); |
| 6 | +# you may not use this file except in compliance with the License. |
| 7 | +# You may obtain a copy of the License at |
| 8 | +# |
| 9 | +# http://www.apache.org/licenses/LICENSE-2.0 |
| 10 | +# |
| 11 | +# Unless required by applicable law or agreed to in writing, software |
| 12 | +# distributed under the License is distributed on an "AS IS" BASIS, |
| 13 | +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| 14 | +# See the License for the specific language governing permissions and |
| 15 | +# limitations under the License. |
| 16 | + |
| 17 | +"""Python clients for the Google TV Pairing and Anymote protocols.""" |
| 18 | + |
| 19 | +__author__ = '[email protected] (Steven Le)' |
| 20 | + |
| 21 | +import socket |
| 22 | +import ssl |
| 23 | +import struct |
| 24 | +from googletv.proto import polo_pb2 |
| 25 | +from googletv.proto import remote_pb2 |
| 26 | + |
| 27 | +ENCODING_TYPE_HEXADECIMAL = polo_pb2.Options.Encoding.ENCODING_TYPE_HEXADECIMAL |
| 28 | +ROLE_TYPE_INPUT = polo_pb2.Options.ROLE_TYPE_INPUT |
| 29 | + |
| 30 | + |
| 31 | +class Error(Exception): |
| 32 | + """Base class for all exceptions in this module.""" |
| 33 | + |
| 34 | + |
| 35 | +class BaseProtocol(object): |
| 36 | + """Base class for protocols used by this module. |
| 37 | +
|
| 38 | + Attributes: |
| 39 | + host: The host of the Google TV server. |
| 40 | + port: The port to connect to. Default is 9551 for Anymote Protocol and 9552 |
| 41 | + for Pairing Protocol. |
| 42 | + sock: A socket.socket object. |
| 43 | + ssl: SSL-wrapped socket.socket object. |
| 44 | + """ |
| 45 | + |
| 46 | + def __init__(self, host, port, certfile): |
| 47 | + self.host = host |
| 48 | + self.port = port |
| 49 | + self.sock = socket.socket() |
| 50 | + self.ssl = ssl.wrap_socket(self.sock, certfile=certfile) |
| 51 | + self.ssl.connect((self.host, self.port)) |
| 52 | + |
| 53 | + def __enter__(self): |
| 54 | + return self |
| 55 | + |
| 56 | + def __exit__(self): |
| 57 | + self.Close() |
| 58 | + |
| 59 | + def Close(self): |
| 60 | + self.ssl.close() |
| 61 | + |
| 62 | + def Send(self, data): |
| 63 | + data_len = struct.pack('!I', len(data)) |
| 64 | + sent = self.ssl.write(data_len + data) |
| 65 | + assert sent == len(data) + 4 |
| 66 | + return sent |
| 67 | + |
| 68 | + |
| 69 | +class PairingProtocol(BaseProtocol): |
| 70 | + """Google TV Pairing Protocol. |
| 71 | +
|
| 72 | + More info: |
| 73 | + http://code.google.com/tv/remote/docs/pairing.html |
| 74 | + """ |
| 75 | + |
| 76 | + def __init__(self, host, certfile, port=9552): |
| 77 | + super(PairingProtocol, self).__init__(host, port, certfile) |
| 78 | + |
| 79 | + def SendPairingRequest(self, client_name, service_name='AnyMote'): |
| 80 | + """Initiates a new PairingRequest with the Google TV server. |
| 81 | +
|
| 82 | + Args: |
| 83 | + client_name: A string that can be used to identify the client making reqs. |
| 84 | + service_name: The name of the service to pair with. |
| 85 | + """ |
| 86 | + req = polo_pb2.PairingRequest() |
| 87 | + req.service_name = service_name |
| 88 | + req.client_name = client_name |
| 89 | + self._SendMessage(req, polo_pb2.OuterMessage.MESSAGE_TYPE_PAIRING_REQUEST) |
| 90 | + |
| 91 | + def SendOptions(self, *args, **kwargs): |
| 92 | + """Sends an Options message to the Google TV server. |
| 93 | +
|
| 94 | + Currently, only a 4-length HEXADECIMAL message is supported. Will support |
| 95 | + other types in the future. |
| 96 | + """ |
| 97 | + options = polo_pb2.Options() |
| 98 | + encoding = options.input_encodings.add() |
| 99 | + encoding.type = ENCODING_TYPE_HEXADECIMAL |
| 100 | + encoding.symbol_length = 4 |
| 101 | + self._SendMessage(options, polo_pb2.OuterMessage.MESSAGE_TYPE_OPTIONS) |
| 102 | + |
| 103 | + def SendConfiguration(self, encoding_type=ENCODING_TYPE_HEXADECIMAL, |
| 104 | + symbol_length=4, client_role=ROLE_TYPE_INPUT): |
| 105 | + """Sends a Configuration message to the Google TV server. |
| 106 | +
|
| 107 | + Currently, only a 4-length HEXADECIMAL message is supported. Will support |
| 108 | + other types in the future. |
| 109 | + """ |
| 110 | + req = polo_pb2.Configuration() |
| 111 | + req.encoding.type = encoding_type |
| 112 | + req.encoding.symbol_length = symbol_length |
| 113 | + req.client_role = client_role |
| 114 | + self._SendMessage(req, polo_pb2.OuterMessage.MESSAGE_TYPE_CONFIGURATION) |
| 115 | + |
| 116 | + def SendSecret(self, code): |
| 117 | + """Sends a Secret message to the Google TV server. |
| 118 | +
|
| 119 | + Args: |
| 120 | + code: Hex code string displayed by the Google TV. |
| 121 | + """ |
| 122 | + req = polo_pb2.Secret() |
| 123 | + req.secret = self._EncodeHexSecret(code) |
| 124 | + self._SendMessage(req, polo_pb2.OuterMessage.MESSAGE_TYPE_SECRET) |
| 125 | + |
| 126 | + def _EncodeHexSecret(self, secret): |
| 127 | + """Encodes a hex secret. |
| 128 | +
|
| 129 | + Args: |
| 130 | + secret: The hex code string displayed by the Google TV. |
| 131 | +
|
| 132 | + Returns: |
| 133 | + An encoded value that can be used in the Secret message. |
| 134 | + """ |
| 135 | + # TODO(stevenle): Something further encodes the secret to a 64-char hex |
| 136 | + # string. For now, use adb logcat to figure out what the expected challenge |
| 137 | + # is. Eventually, make sure the encoding matches the server reference |
| 138 | + # implementation: |
| 139 | + # http://code.google.com/p/google-tv-pairing-protocol/source/browse/src/com/google/polo/pairing/PoloChallengeResponse.java |
| 140 | + result = bytearray(len(secret) / 2) |
| 141 | + for i in xrange(len(result)): |
| 142 | + start_index = 2 * i |
| 143 | + end_index = 2 * (i + 1) |
| 144 | + result[i] = int(code[start_index:end_index], 16) |
| 145 | + return bytes(result) |
| 146 | + |
| 147 | + def _SendMessage(self, message, message_type): |
| 148 | + """Sends a message to the Google TV server. |
| 149 | +
|
| 150 | + Args: |
| 151 | + message: A proto request message. |
| 152 | + message_type: A polo_pb2.OuterMessage.MESSAGE_TYPE_* constant. |
| 153 | +
|
| 154 | + Returns: |
| 155 | + The amount of data sent, in bytes. |
| 156 | + """ |
| 157 | + req = polo_pb2.OuterMessage() |
| 158 | + req.protocol_version = 1 |
| 159 | + req.status = polo_pb2.OuterMessage.STATUS_OK |
| 160 | + req.type = message_type |
| 161 | + req.payload = message.SerializeToString() |
| 162 | + data = req.SerializeToString() |
| 163 | + return self.Send(data) |
| 164 | + |
| 165 | + |
| 166 | +class AnymoteProtocol(BaseProtocol): |
| 167 | + |
| 168 | + def __init__(self): |
| 169 | + # Work in progress... |
| 170 | + raise NotImplementedError |
0 commit comments