First version of a headless server for Telldus Live!
This commit is contained in:
parent
83dbd675cf
commit
648b713955
6 changed files with 388 additions and 0 deletions
119
examples/python/live/server/Client.py
Normal file
119
examples/python/live/server/Client.py
Normal 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)
|
71
examples/python/live/server/LiveMessage.py
Normal file
71
examples/python/live/server/LiveMessage.py
Normal 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()
|
114
examples/python/live/server/LiveMessageToken.py
Normal file
114
examples/python/live/server/LiveMessageToken.py
Normal 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)
|
||||
|
34
examples/python/live/server/ServerList.py
Normal file
34
examples/python/live/server/ServerList.py
Normal 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)
|
40
examples/python/live/server/TelldusCore.py
Normal file
40
examples/python/live/server/TelldusCore.py
Normal 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
|
10
examples/python/live/server/run-client.py
Executable file
10
examples/python/live/server/run-client.py
Executable 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()
|
Loading…
Add table
Add a link
Reference in a new issue