first json rpc implementation

This commit is contained in:
Giovanni Harting
2015-10-09 20:30:21 +02:00
parent 7b67984c80
commit 4ad3fd50fb
3 changed files with 56 additions and 160 deletions

View File

@@ -20,14 +20,15 @@ import json
import sqlite3 import sqlite3
import os import os
import sys import sys
import traceback
import asyncio import asyncio
import signal import signal
from jsonrpc import JSONRPCResponseManager, dispatcher
from jsonrpc.exceptions import JSONRPCError
import spectra import spectra
from ledd import controller, VERSION from ledd import controller, VERSION
from ledd.decorators import ledd_protocol
from ledd.effectstack import EffectStack from ledd.effectstack import EffectStack
from ledd.stripe import Stripe from ledd.stripe import Stripe
@@ -41,7 +42,6 @@ class Daemon:
""":type : Daemon """ """:type : Daemon """
loop = None loop = None
""" :type : asyncio.BaseEventLoop """ """ :type : asyncio.BaseEventLoop """
protocol = {}
effects = [] effects = []
def __init__(self): def __init__(self):
@@ -73,7 +73,7 @@ class Daemon:
# sigterm handler # sigterm handler
def sigterm_handler(): def sigterm_handler():
sys.exit(0) raise SystemExit
signal.signal(signal.SIGTERM, sigterm_handler) signal.signal(signal.SIGTERM, sigterm_handler)
@@ -141,19 +141,17 @@ class Daemon:
c.close() c.close()
self.check_db() self.check_db()
@ledd_protocol(protocol) @dispatcher.add_method
def start_effect(self, req_json): def start_effect(self, **kwargs):
""" """
Part of the Color API. Used to start a specific effect. Part of the Color API. Used to start a specific effect.
Required JSON parameters: stripe IDs: sids; effect id: eid, effect options: eopt Required JSON parameters: stripe IDs: sids; effect id: eid, effect options: eopt
:param req_json: dict of request json :param req_json: dict of request json
""" """
log.debug("recieved action: %s", req_json['action'])
stripes = [] stripes = []
if "sids" in req_json: if "sids" in kwargs:
for sid in req_json['sids']: for sid in kwargs['sids']:
found_s = self.find_stripe(sid) found_s = self.find_stripe(sid)
if found_s is not None: if found_s is not None:
@@ -170,141 +168,109 @@ class Daemon:
# asyncio.ensure_future(asyncio.get_event_loop().run_in_executor(self.executor, effect.execute)) # asyncio.ensure_future(asyncio.get_event_loop().run_in_executor(self.executor, effect.execute))
rjson = { rjson = {
'success': True,
'eident': None, # unique effect identifier that identifies excatly this effect started on this set of 'eident': None, # unique effect identifier that identifies excatly this effect started on this set of
# stripes, used to stop them later and to give informations about running effects # stripes, used to stop them later and to give informations about running effects
'ref': req_json['ref']
} }
return json.dumps(rjson) return rjson
else: else:
rjson = { return JSONRPCError(-1003, "Stripeid not found")
'success': False,
'message': "No stripe with this id found",
'ref': req_json['ref']
}
return json.dumps(rjson) @dispatcher.add_method
def stop_effect(self, **kwargs):
@ledd_protocol(protocol)
def stop_effect(self, req_json):
""" """
Part of the Color API. Used to stop a specific effect. Part of the Color API. Used to stop a specific effect.
Required JSON parameters: effect identifier: eident Required JSON parameters: effect identifier: eident
:param req_json: dict of request json :param req_json: dict of request json
""" """
log.debug("recieved action: %s", req_json['action'])
# TODO: add stop effect by eident logic # TODO: add stop effect by eident logic
@ledd_protocol(protocol) @dispatcher.add_method
def get_effects(self, req_json): def get_effects(self, **kwargs):
""" """
Part of the Color API. Used to show all available and running effects. Part of the Color API. Used to show all available and running effects.
Required JSON parameters: - Required JSON parameters: -
:param req_json: dict of request json :param req_json: dict of request json
""" """
log.debug("recieved action: %s", req_json['action'])
# TODO: list all effects here and on which stripes they run atm # TODO: list all effects here and on which stripes they run atm
# TODO: all effects get runtime only ids, "eid"'s. They are shown here for the client to start effects. # TODO: all effects get runtime only ids, "eid"'s. They are shown here for the client to start effects.
# TODO: All options that an effect may have need to be transmitted here too with "eopt". # TODO: All options that an effect may have need to be transmitted here too with "eopt".
@ledd_protocol(protocol) @dispatcher.add_method
def set_color(self, req_json): def set_color(self, **kwargs):
""" """
Part of the Color API. Used to set color of a stripe. Part of the Color API. Used to set color of a stripe.
Required JSON parameters: stripe ID: sid; HSV values hsv: h,s,v, controller id: cid Required JSON parameters: stripe ID: sid; HSV values hsv: h,s,v, controller id: cid
:param req_json: dict of request json :param req_json: dict of request json
""" """
log.debug("recieved action: %s", req_json['action'])
found_s = self.find_stripe(req_json['sid']) found_s = self.find_stripe(kwargs['sid'])
if found_s is None: if found_s is None:
log.warning("Stripe not found: id=%s", req_json['sid']) log.warning("Stripe not found: id=%s", kwargs['sid'])
else: else:
found_s.set_color(spectra.hsv(req_json['hsv']['h'], req_json['hsv']['s'], req_json['hsv']['v'])) found_s.set_color(spectra.hsv(kwargs['hsv']['h'], kwargs['hsv']['s'], kwargs['hsv']['v']))
@ledd_protocol(protocol) @dispatcher.add_method
def add_controller(self, req_json): def add_controller(self, **kwargs):
""" """
Part of the Color API. Used to add a controller. Part of the Color API. Used to add a controller.
Required JSON parameters: channels; i2c_dev: number of i2c device (e.g. /dev/i2c-1 would be i2c_dev = 1); Required JSON parameters: channels; i2c_dev: number of i2c device (e.g. /dev/i2c-1 would be i2c_dev = 1);
address: hexdecimal address of controller on i2c bus, e.g. 0x40 address: hexdecimal address of controller on i2c bus, e.g. 0x40
:param req_json: dict of request json :param req_json: dict of request json
""" """
log.debug("recieved action: %s", req_json['action'])
try: try:
ncontroller = controller.Controller(Daemon.instance.sqldb, req_json['channels'], ncontroller = controller.Controller(Daemon.instance.sqldb, kwargs['channels'],
req_json['i2c_dev'], req_json['address']) kwargs['i2c_dev'], kwargs['address'])
except OSError as e: except OSError as e:
log.error("Error opening i2c device: %s (%s)", req_json['i2c_dev'], os.strerror(int(str(e)))) log.error("Error opening i2c device: %s (%s)", kwargs['i2c_dev'], e)
rjson = { return JSONRPCError(-1004, "Error while opening i2c device", e)
'success': False,
'message': "Error while opening i2c device",
'message_detail': os.strerror(int(str(e))),
'ref': req_json['ref']
}
return json.dumps(rjson)
self.controllers.append(ncontroller) self.controllers.append(ncontroller)
rjson = { rjson = {
'success': True,
'cid': ncontroller.id, 'cid': ncontroller.id,
'ref': req_json['ref']
} }
return json.dumps(rjson) return rjson
@ledd_protocol(protocol) @dispatcher.add_method
def get_color(self, req_json): def get_color(self, **kwargs):
""" """
Part of the Color API. Used to get the current color of an stripe. Part of the Color API. Used to get the current color of an stripe.
Required JSON parameters: stripes Required JSON parameters: stripes
:param req_json: dict of request json :param req_json: dict of request json
""" """
log.debug("recieved action: %s", req_json['action'])
found_s = self.find_stripe(req_json['sid']) found_s = self.find_stripe(kwargs['sid'])
if found_s is None: if found_s is None:
log.warning("Stripe not found: id=%s", req_json['sid']) log.warning("StripeId not found: id=%s", kwargs['sid'])
return { return JSONRPCError(-1003, "Stripeid not found")
'success': False,
'message': "Stripe not found",
'ref': req_json['ref']
}
rjson = { rjson = {
'success': True,
'color': found_s.color.values, 'color': found_s.color.values,
'ref': req_json['ref']
} }
return json.dumps(rjson) return rjson
@ledd_protocol(protocol) @dispatcher.add_method
def add_stripe(self, req_json): def add_stripe(self, **kwargs):
""" """
Part of the Color API. Used to add stripes. Part of the Color API. Used to add stripes.
Required JSON parameters: name; rgb: bool; map: r: r-channel, g: g-channel, b: b-channel, cid Required JSON parameters: name; rgb: bool; map: r: r-channel, g: g-channel, b: b-channel, cid
:param req_json: dict of request json :param req_json: dict of request json
""" """
log.debug("recieved action: %s", req_json['action'])
if "stripe" in req_json: if "stripe" in kwargs:
stripe = req_json['stripe'] stripe = kwargs['stripe']
c = next((x for x in self.controllers if x.id == stripe['cid']), None) c = next((x for x in self.controllers if x.id == stripe['cid']), None)
""" :type c: ledd.controller.Controller """ """ :type c: ledd.controller.Controller """
if c is None: if c is None:
return { return JSONRPCError(-1002, "Controller not found")
'success': False,
'message': "Controller not found",
'ref': stripe['ref']
}
s = Stripe(c, stripe['name'], stripe['rgb'], s = Stripe(c, stripe['name'], stripe['rgb'],
(stripe['map']['r'], stripe['map']['g'], stripe['map']['b'])) (stripe['map']['r'], stripe['map']['g'], stripe['map']['b']))
@@ -313,77 +279,54 @@ class Daemon:
log.debug("Added stripe %s to controller %s; new len %s", c.id, s.id, len(c.stripes)) log.debug("Added stripe %s to controller %s; new len %s", c.id, s.id, len(c.stripes))
rjson = { rjson = {
'success': True,
'sid': s.id, 'sid': s.id,
'ref': req_json['ref']
} }
return json.dumps(rjson) return rjson
@ledd_protocol(protocol) @dispatcher.add_method
def get_stripes(self, req_json): def get_stripes(self, **kwargs):
""" """
Part of the Color API. Used to get all registered stripes known to the daemon. Part of the Color API. Used to get all registered stripes known to the daemon.
Required JSON parameters: none Required JSON parameters: none
:param req_json: dict of request json :param req_json: dict of request json
""" """
log.debug("recieved action: %s", req_json['action'])
rjson = { rjson = {
'success': True,
'ccount': len(Daemon.instance.controllers), 'ccount': len(Daemon.instance.controllers),
'controller': Daemon.instance.controllers, 'controller': json.dumps(Daemon.instance.controllers, cls=controller.ControllerEncoder),
'ref': req_json['ref']
} }
return json.dumps(rjson, cls=controller.ControllerEncoder) return rjson
@ledd_protocol(protocol) @dispatcher.add_method
def test_channel(self, req_json): def test_channel(self, **kwargs):
""" """
Part of the Color API. Used to test a channel on a specified controller. Part of the Color API. Used to test a channel on a specified controller.
Required JSON parameters: controller id: cid, channel, value Required JSON parameters: controller id: cid, channel, value
:param req_json: dict of request json :param req_json: dict of request json
""" """
log.debug("recieved action: %s", req_json['action'])
result = next(filter(lambda x: x.id == req_json['cid'], self.controllers), None) result = next(filter(lambda x: x.id == kwargs['cid'], self.controllers), None)
""" :type : ledd.controller.Controller """ """ :type : ledd.controller.Controller """
if result is not None: if result is not None:
result.set_channel(req_json['channel'], req_json['value'], 2.8) result.set_channel(kwargs['channel'], kwargs['value'], 2.8)
rjson = { @dispatcher.add_method
'success': True, def discover(self, **kwargs):
'ref': req_json['ref']
}
return json.dumps(rjson)
@ledd_protocol(protocol)
def discover(self, req_json):
""" """
Part of the Color API. Used by mobile applications to find the controller. Part of the Color API. Used by mobile applications to find the controller.
Required JSON parameters: none Required JSON parameters: none
:param req_json: dict of request json :param req_json: dict of request json
""" """
log.debug("recieved action: %s", req_json['action']) log.debug("recieved action: %s", kwargs['action'])
rjson = { rjson = {
'success': True,
'ref': req_json['ref'],
'version': VERSION 'version': VERSION
} }
return json.dumps(rjson) return rjson
def no_action_found(self, req_json):
rjson = {
'success': False,
'message': "No action found",
'ref': req_json['ref']
}
return json.dumps(rjson)
def find_stripe(self, sid): def find_stripe(self, sid):
""" """
@@ -418,25 +361,11 @@ class LedDProtocol(asyncio.Protocol):
def select_task(self, data): def select_task(self, data):
if data: if data:
try: data_split = data.splitlines()
data_split = data.splitlines() log.debug(data_split)
log.debug(data_split) for line in data_split:
for line in data_split: if line:
if line: self.transport.write(JSONRPCResponseManager.handle(line, dispatcher))
json_decoded = json.loads(line)
if "action" in json_decoded and "ref" in json_decoded:
return_data = Daemon.instance.protocol.get(json_decoded['action'], Daemon.no_action_found)(
Daemon.instance, json_decoded)
if return_data is not None:
self.transport.write("{}\n".format(return_data).encode())
else:
log.debug("no action or ref value found in JSON, ignoring")
except TypeError:
log.debug("No valid JSON found: %s", traceback.format_exc())
except ValueError:
log.debug("No valid JSON detected: %s", traceback.format_exc())
def connection_lost(self, exc): def connection_lost(self, exc):
# The socket has been closed, stop the event loop # The socket has been closed, stop the event loop

View File

@@ -1,33 +0,0 @@
# LEDD Project
# Copyright (C) 2015 LEDD Team
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
def ledd_protocol(proto):
"""
Decorator used to add functions to action dict
:param proto: dict to add to
:type proto: dict
"""
def wrap(f):
proto[f.__name__] = f
def wrapped_f(*args):
f(*args)
return wrapped_f
return wrap

View File

@@ -25,6 +25,6 @@ setup(name='LedD',
license='GPLv3', license='GPLv3',
packages=['ledd'], packages=['ledd'],
install_requires=[ install_requires=[
'nose', 'spectra', 'docopt', 'nose', 'spectra', 'docopt', 'jsonrpc',
], ],
zip_safe=False) zip_safe=False)