diff --git a/examples/python/live/server/Client.py b/examples/python/live/server/Client.py new file mode 100644 index 00000000..925e64cf --- /dev/null +++ b/examples/python/live/server/Client.py @@ -0,0 +1,119 @@ +# -*- coding: utf-8 -*- + +import socket, ssl +import time, os + +from configobj import ConfigObj + +from ServerList import * +from TelldusCore import * +from LiveMessage import * + +class Client(): + def __init__(self): + self.publicKey = '' + self.privateKey = '' + self.hashMethod = 'sha1' + self.supportedMethods = 0 + self.tellduscore = TelldusCore() + self.serverList = ServerList() + + self.configPath = os.environ['HOME'] + '/.config/Telldus' + self.configFilename = 'TelldusLive.conf' + self.config = ConfigObj(self.configPath + '/' + self.configFilename) + self.connect(self.serverList.popServer()) + + def __del__(self): + try: + os.makedirs(self.configPath) + except: + pass + self.config.write() + + def connect(self, server): + s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + self.socket = ssl.wrap_socket(s, ssl_version=ssl.PROTOCOL_TLSv1, ca_certs="/etc/ssl/certs/ca-certificates.crt",cert_reqs=ssl.CERT_REQUIRED) + self.socket.settimeout(5) + self.socket.connect((server['address'], int(server['port']))) + + uuid = '' + try: + uuid = self.config['uuid'] + except: + pass + + msg = LiveMessage('Register') + msg.append({ + 'key': self.publicKey, + 'uuid': uuid, + 'hash': self.hashMethod + }) + msg.append({ + 'protocol': 2, + 'version': '1', + 'os': 'linux', + 'os-version': 'unknown' + }) + + self.socket.write(self.signedMessage(msg)) + while(1): + try: + resp = self.socket.read(1024) + except ssl.SSLError: + # Timeout, try again + # TODO(micke): Check pong timer here + continue + if (resp == ''): + print("no response") + break + continue + + envelope = LiveMessage.fromByteArray(resp) + if (envelope.verifySignature(self.hashMethod, self.privateKey)): + self.handleMessage(LiveMessage.fromByteArray(envelope.argument(0).stringVal)) + else: + print "Signature failed" + + def handleCommand(self, args): + if (args['action'].stringVal == 'turnon'): + self.tellduscore.turnon(args['id'].intVal) + elif (args['action'].stringVal == 'turnoff'): + self.tellduscore.turnoff(args['id'].intVal) + else: + return + + if ('ACK' in args): + #Respond to ack + msg = LiveMessage("ACK") + msg.append(args['ACK'].intVal) + self.socket.write(self.signedMessage(msg)) + + def handleMessage(self, message): + if (message.name() == "notregistered"): + params = message.argument(0).dictVal + self.config['uuid'] = params['uuid'].stringVal + self.config['activationUrl'] = params['url'].stringVal + print "This client isn't activated, please activate it using this url:\n%s" % params['url'].stringVal + return + + if (message.name() == "registered"): + params = message.argument(0).dictVal + self.supportedMethods = params['supportedMethods'].intVal + self.tellduscore.setSupportedMethods(self.supportedMethods) + self.sendDevicesReport() + return + + if (message.name() == "command"): + self.handleCommand(message.argument(0).dictVal) + return + + print "Did not understand: %s" % message.toByteArray() + + + def sendDevicesReport(self): + msg = LiveMessage("DevicesReport") + msg.append(self.tellduscore.getList()) + self.socket.write(self.signedMessage(msg)) + + def signedMessage(self, message): + return message.toSignedMessage(self.hashMethod, self.privateKey) diff --git a/examples/python/live/server/LiveMessage.py b/examples/python/live/server/LiveMessage.py new file mode 100644 index 00000000..1741f256 --- /dev/null +++ b/examples/python/live/server/LiveMessage.py @@ -0,0 +1,71 @@ +# -*- coding: utf-8 -*- + +import hashlib +from LiveMessageToken import * + +class LiveMessage(): + def __init__(self, name = ""): + if (name != ""): + self.args = [LiveMessageToken(name)] + else: + self.args = [] + + def append(self, argument): + self.args.append(LiveMessageToken(argument)) + + def argument(self,index): + if (len(self.args) > index+1): + return self.args[index+1] + + return LiveMessageToken() + + def count(self): + return len(self.args)-1 + + def name(self): + return self.argument(-1).stringVal.lower() + + def toByteArray(self): + retval = '' + for arg in self.args: + retval = retval + arg.toByteArray() + return retval + + def toSignedMessage(self, hashMethod, privateKey): + message = self.toByteArray() + envelope = LiveMessage(LiveMessage.signatureForMessage(message, hashMethod, privateKey)) + envelope.append(message) + return envelope.toByteArray() + + def verifySignature(self, hashMethod, privateKey): + signature = self.name() + rawMessage = self.argument(0).stringVal + return (self.signatureForMessage(rawMessage, hashMethod, privateKey) == signature) + + @staticmethod + def fromByteArray(rawString): + list = [] + start = 0 + while (start < len(rawString)): + start, token = LiveMessageToken.parseToken(rawString, start) + if (token.valueType == LiveMessageToken.TYPE_INVALID): + break + list.append(token) + + msg = LiveMessage() + msg.args = list + return msg + + @staticmethod + def signatureForMessage(msg, hashMethod, privateKey): + h = 0 + if (hashMethod == "sha512"): + h = hashlib.sha512() + elif (hashMethod == "sha256"): + h = hashlib.sha256() + else: + h = hashlib.sha1() + + h.update(msg) + h.update(privateKey) + return h.hexdigest().lower() diff --git a/examples/python/live/server/LiveMessageToken.py b/examples/python/live/server/LiveMessageToken.py new file mode 100644 index 00000000..8064cc7e --- /dev/null +++ b/examples/python/live/server/LiveMessageToken.py @@ -0,0 +1,114 @@ +# -*- coding: utf-8 -*- + +import base64 + +class LiveMessageToken(): + TYPE_INVALID, TYPE_INT, TYPE_STRING, TYPE_BASE64, TYPE_LIST, TYPE_DICTIONARY = range(6) + + def __init__(self, value = None): + self.valueType = LiveMessageToken.TYPE_INVALID + self.stringVal = '' + self.intVal = 0 + self.dictVal = {} + self.listVal = [] + if (type(value) is int): + self.valueType = self.TYPE_INT + self.intVal = value + + elif (type(value) is str): + self.valueType = self.TYPE_STRING + self.stringVal = value + + elif (type(value) is list): + self.valueType = self.TYPE_LIST + for v in value: + self.listVal.append(LiveMessageToken(v)) + + elif (type(value) is dict): + self.valueType = self.TYPE_DICTIONARY + for key in value: + self.dictVal[key] = LiveMessageToken(value[key]) + + + def toByteArray(self): + if (self.valueType == LiveMessageToken.TYPE_INT): + return 'i%Xs' % self.intVal + + if (self.valueType == LiveMessageToken.TYPE_LIST): + retval = 'l' + for token in self.listVal: + retval = retval + token.toByteArray() + return retval + 's' + + if (self.valueType == LiveMessageToken.TYPE_DICTIONARY): + retval = 'h' + for key in self.dictVal: + retval = retval + LiveMessageToken(key).toByteArray() + self.dictVal[key].toByteArray() + return retval + 's' + + return '%X:%s' % (len(self.stringVal), self.stringVal,) + + @staticmethod + def parseToken(string, start): + token = LiveMessageToken() + if (start >= len(string)): + return (start, token) + + if (string[start] == 'i'): + start+=1 + index = string.find('s', start) + if (index < 0): + return (start, token) + + try: + token.intVal = int(string[start:index], 16) + token.valueType = LiveMessageToken.TYPE_INT + start = index + 1 + except: + return (start, token) + + elif (string[start] == 'l'): + start+=1 + while (start < len(string) and string[start] != 's'): + start, listToken = LiveMessageToken.parseToken(string, start) + if (listToken.valueType == LiveMessageToken.TYPE_INVALID): + break + token.valueType = LiveMessageToken.TYPE_LIST + token.listVal.append(listToken) + start+=1 + + elif (string[start] == 'h'): + start+=1 + while (start < len(string) and string[start] != 's'): + start, keyToken = LiveMessageToken.parseToken(string, start) + if (keyToken.valueType == LiveMessageToken.TYPE_INVALID): + break + start, valueToken = LiveMessageToken.parseToken(string, start) + if (valueToken.valueType == LiveMessageToken.TYPE_INVALID): + break + token.valueType = LiveMessageToken.TYPE_DICTIONARY + token.dictVal[keyToken.stringVal] = valueToken + start+=1 + + elif (string[start] == 'u'): #Base64 + start+=1 + start, token = LiveMessageToken.parseToken(string, start) + token.valueType = LiveMessageToken.TYPE_BASE64 + token.stringVal = base64.decodestring(token.stringVal) + + else: #String + index = string.find(':', start) + if (index < 0): + return (start, token) + + try: + length = int(string[start:index], 16) + except: + return (start, token) + + start = index + length + 1 + token.stringVal = string[index+1:start] + token.valueType = LiveMessageToken.TYPE_STRING + + return (start, token) + diff --git a/examples/python/live/server/ServerList.py b/examples/python/live/server/ServerList.py new file mode 100644 index 00000000..00ebcb10 --- /dev/null +++ b/examples/python/live/server/ServerList.py @@ -0,0 +1,34 @@ +# -*- coding: utf-8 -*- + +import httplib +import xml.parsers.expat + +class ServerList(): + + def __init__(self): + self.list = [] + self.retrieveServerList() + + def popServer(self): + if (self.list == []): + self.retrieveServerList() + + if (self.list == []): + return False + + return self.list.pop(0) + + + def retrieveServerList(self): + conn = httplib.HTTPConnection("api.telldus.com:80") + conn.request('GET', "/server/assign?protocolVersion=2") + response = conn.getresponse() + + p = xml.parsers.expat.ParserCreate() + + p.StartElementHandler = self._startElement + p.Parse(response.read()) + + def _startElement(self, name, attrs): + if (name == 'server'): + self.list.append(attrs) diff --git a/examples/python/live/server/TelldusCore.py b/examples/python/live/server/TelldusCore.py new file mode 100644 index 00000000..692baa19 --- /dev/null +++ b/examples/python/live/server/TelldusCore.py @@ -0,0 +1,40 @@ +# -*- coding: utf-8 -*- + +import ctypes + +class TelldusCore(): + + def __init__(self): + self.supportedMethods = 0 + self.lib = ctypes.cdll.LoadLibrary('libtelldus-core.so') + self.list = [] + for i in range(self.lib.tdGetNumberOfDevices()): + id = self.lib.tdGetDeviceId(i) + device = {'id': id} + namePtr = self.lib.tdGetName(id) + device['name'] = ctypes.c_char_p(namePtr).value + self.lib.tdReleaseString(namePtr) + stateValuePtr = self.lib.tdLastSentValue(id) + device['stateValue'] = ctypes.c_char_p(stateValuePtr).value + self.lib.tdReleaseString(stateValuePtr) + self.list.append(device) + + def getList(self): + return self.list + + def setSupportedMethods(self, supportedMethods): + if (self.supportedMethods == supportedMethods): + return + + self.supportedMethods = supportedMethods + for device in self.list: + device['methods'] = self.lib.tdMethods(device['id'], supportedMethods) + device['state'] = self.lib.tdLastSentCommand(device['id'], supportedMethods) + + def turnoff(self, id): + self.lib.tdTurnOff(id) + print "Turning off: %i" % id + + def turnon(self, id): + self.lib.tdTurnOn(id) + print "Turning on: %i" % id diff --git a/examples/python/live/server/run-client.py b/examples/python/live/server/run-client.py new file mode 100755 index 00000000..4153955e --- /dev/null +++ b/examples/python/live/server/run-client.py @@ -0,0 +1,10 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +import signal +from Client import * + +if __name__ == "__main__": + c = Client() + #signal.signal(signal.SIGINT, c.shutdown) + #c.start()