First version of a headless server for Telldus Live!

This commit is contained in:
Micke Prag 2012-06-21 17:19:45 +02:00
parent 83dbd675cf
commit 648b713955
6 changed files with 388 additions and 0 deletions

View file

@ -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)

View file

@ -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()

View file

@ -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)

View file

@ -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)

View file

@ -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

View file

@ -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()