Compare commits
22 Commits
Author | SHA1 | Date | |
---|---|---|---|
12ed601446 | |||
![]() |
bf4dcfaaf8 | ||
![]() |
7d6d289178 | ||
![]() |
db379ccdf0 | ||
![]() |
99a7252194 | ||
![]() |
a99d879298 | ||
![]() |
6ae2cc4fb8 | ||
![]() |
a65bb770fd | ||
![]() |
02f6837d27 | ||
![]() |
df90bc8a68 | ||
![]() |
f9d696b1b3 | ||
![]() |
aadcfafd00 | ||
![]() |
6f2a8faa6a | ||
![]() |
01ba0a4d2c | ||
![]() |
4c052b8edb | ||
![]() |
2bb52aa3a4 | ||
![]() |
d5f403d557 | ||
![]() |
87f2d12c22 | ||
![]() |
8ee1dd395c | ||
![]() |
4ad3fd50fb | ||
![]() |
7b67984c80 | ||
![]() |
1a568b194c |
27
README.md
27
README.md
@@ -1,14 +1,16 @@
|
|||||||
# LedD
|
# LedD
|
||||||
|
|
||||||
[![][codeclimate img]][codeclimate] [![][license img]][license]
|
[![][cq img]][cq] [![][license img]][license]
|
||||||
|
|
||||||
LedD is a daemon for interfacing LED stripes written in python3. It provides an abstract interface for effects to control any kind of LED stripe through an controller, although it is intented to be used with the PCA9685 chip. An Android application can connect and natively change any settings for the effects and stripes.
|
# DEPRECATED
|
||||||
|
|
||||||
## Goals
|
This project is no longer maintained and has been superseded by LedD.Go.
|
||||||
|
|
||||||
- manage multiple stripes and controllers at the same time
|
## General
|
||||||
- an open effects github repository with simple download-and-run system
|
|
||||||
- automatic enumeration of the connected controller devices, restart/reset and status querys
|
LedD is a multipurpose daemon for interfacing LED(s) written in python. It provides an abstract interface for effects to control any kind of LED through an backend, although its original purpose was interfacing a PCA9685 chip via i2c. Since there are multiple ways to control leds we made the decision to write LedD as open as possible to other ways of controlling leds. As a result of this decision we split LedD in a server part (this repository) and multiple clients, which can be written in any kind of language and can use a custom way to control its LEDs, as long as they implement LedD's protobuf protocol they can be controled with LedD.
|
||||||
|
|
||||||
|
As for frontends there is only an Android app available at this time, there will be more some time in the future. (You are encouraged to write your own!)
|
||||||
|
|
||||||
## Requirements
|
## Requirements
|
||||||
|
|
||||||
@@ -18,6 +20,15 @@ LedD is a daemon for interfacing LED stripes written in python3. It provides an
|
|||||||
- PCA9685
|
- PCA9685
|
||||||
- __Note__: Plugins can have different permission requirements
|
- __Note__: Plugins can have different permission requirements
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
Make sure your i2c devices are available (modprobe i2c-dev) before you follow these steps.
|
||||||
|
|
||||||
|
1. `apt-get install python3-dev python3-pip python3-cffi python3-docopt python3-nose python3-sqlalchemy python-smbus libffi-dev`
|
||||||
|
2. `pip3 install -U cffi` (fixes a bug where smbus-cffi can't install)
|
||||||
|
3. `pip3 install coloredlogs spectra json-rpc smbus-cffi`
|
||||||
|
4. `adduser $USER i2c`
|
||||||
|
|
||||||
### Plugins & Effects
|
### Plugins & Effects
|
||||||
|
|
||||||
Plugin functionality is planned as we provide APIs for effects and plugins to use. Here are some we are going to provide when they are finished.
|
Plugin functionality is planned as we provide APIs for effects and plugins to use. Here are some we are going to provide when they are finished.
|
||||||
@@ -43,5 +54,5 @@ This project is licensed under the conditions of the GNU GPL 3.0.
|
|||||||
|
|
||||||
[license]:LICENSE
|
[license]:LICENSE
|
||||||
[license img]:https://img.shields.io/github/license/led-freaks/ledd.svg?style=flat-square
|
[license img]:https://img.shields.io/github/license/led-freaks/ledd.svg?style=flat-square
|
||||||
[codeclimate]:https://codeclimate.com/github/LED-Freaks/LedD
|
[cq]:https://www.codacy.com/app/chefeificationful/LedD
|
||||||
[codeclimate img]:https://img.shields.io/codeclimate/github/LED-Freaks/LedD.svg?style=flat-square
|
[cq img]:https://img.shields.io/codacy/bb2de4e1587f48358141cd7465d2ea89.svg?style=flat-square
|
||||||
|
19
ledd.py
19
ledd.py
@@ -1,3 +1,5 @@
|
|||||||
|
#!/usr/bin/python3
|
||||||
|
|
||||||
# LEDD Project
|
# LEDD Project
|
||||||
# Copyright (C) 2015 LEDD Team
|
# Copyright (C) 2015 LEDD Team
|
||||||
#
|
#
|
||||||
@@ -17,16 +19,16 @@
|
|||||||
"""LedD Daemon
|
"""LedD Daemon
|
||||||
|
|
||||||
Usage:
|
Usage:
|
||||||
ledd.py [--daemon] [-d | --debug] [-v | --verbose]
|
ledd.py [--detach] [-d | --debug] [-v | --verbose]
|
||||||
ledd.py -h | --help
|
ledd.py -h | --help
|
||||||
ledd.py --version
|
ledd.py --version
|
||||||
|
|
||||||
Options:
|
Options:
|
||||||
-h --help Show this screen.
|
-h --help Show this screen.
|
||||||
--version Show version.
|
--version Show version.
|
||||||
-d --debug Show debug output. (not recommended for daily use)
|
-d --debug Show debug output. (not recommended)
|
||||||
-v --verbose Be verbose.
|
-v --verbose Be verbose.
|
||||||
--daemon Run in daemon mode.
|
--detach Detach after start.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
@@ -34,6 +36,7 @@ import sys
|
|||||||
import os
|
import os
|
||||||
from pkgutil import iter_modules
|
from pkgutil import iter_modules
|
||||||
|
|
||||||
|
import coloredlogs
|
||||||
from docopt import docopt
|
from docopt import docopt
|
||||||
|
|
||||||
import ledd.daemon
|
import ledd.daemon
|
||||||
@@ -89,10 +92,8 @@ if __name__ == "__main__":
|
|||||||
if arguments['--debug']:
|
if arguments['--debug']:
|
||||||
lvl = logging.DEBUG
|
lvl = logging.DEBUG
|
||||||
|
|
||||||
logging.basicConfig(level=lvl,
|
|
||||||
format="[%(asctime)s] %(levelname)s [%(name)s.%(funcName)s:%(lineno)d] %(message)s",
|
|
||||||
datefmt="%H:%M:%S")
|
|
||||||
log = logging.getLogger(__name__)
|
log = logging.getLogger(__name__)
|
||||||
|
coloredlogs.install(level=lvl)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
with open('ledd.pid', 'r') as f:
|
with open('ledd.pid', 'r') as f:
|
||||||
@@ -106,7 +107,7 @@ if __name__ == "__main__":
|
|||||||
except FileNotFoundError:
|
except FileNotFoundError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
if arguments['--daemon']:
|
if arguments['--detach']:
|
||||||
wdir = os.path.dirname(os.path.realpath(__file__))
|
wdir = os.path.dirname(os.path.realpath(__file__))
|
||||||
try:
|
try:
|
||||||
pid = os.fork()
|
pid = os.fork()
|
||||||
@@ -118,7 +119,7 @@ if __name__ == "__main__":
|
|||||||
os.chdir(wdir)
|
os.chdir(wdir)
|
||||||
with open("ledd.pid", 'w') as pidf:
|
with open("ledd.pid", 'w') as pidf:
|
||||||
pidf.write(str(os.getpid()) + '\n')
|
pidf.write(str(os.getpid()) + '\n')
|
||||||
daemon = ledd.daemon.Daemon()
|
ledd.daemon.run()
|
||||||
else:
|
else:
|
||||||
sys.exit()
|
sys.exit()
|
||||||
else:
|
else:
|
||||||
@@ -126,4 +127,4 @@ if __name__ == "__main__":
|
|||||||
except OSError as e:
|
except OSError as e:
|
||||||
log.fatal("Start failed: %s", e)
|
log.fatal("Start failed: %s", e)
|
||||||
else:
|
else:
|
||||||
daemon = ledd.daemon.Daemon()
|
ledd.daemon.run()
|
||||||
|
@@ -13,5 +13,23 @@
|
|||||||
#
|
#
|
||||||
# You should have received a copy of the GNU General Public License
|
# You should have received a copy of the GNU General Public License
|
||||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
from json import JSONEncoder
|
||||||
|
|
||||||
|
from sqlalchemy.orm import sessionmaker, scoped_session
|
||||||
|
from sqlalchemy.ext.declarative import declarative_base
|
||||||
|
|
||||||
VERSION = "0.1"
|
VERSION = "0.1"
|
||||||
|
|
||||||
|
engine = None
|
||||||
|
session = scoped_session(sessionmaker())
|
||||||
|
""" :type : sqlalchemy.orm.scoping.scoped_session """
|
||||||
|
Base = declarative_base()
|
||||||
|
Base.query = session.query_property()
|
||||||
|
|
||||||
|
|
||||||
|
def _default(self, obj):
|
||||||
|
return getattr(obj.__class__, "to_json", _default.default)(obj)
|
||||||
|
|
||||||
|
|
||||||
|
_default.default = JSONEncoder().default
|
||||||
|
JSONEncoder.default = _default
|
||||||
|
@@ -14,13 +14,15 @@
|
|||||||
# You should have received a copy of the GNU General Public License
|
# You should have received a copy of the GNU General Public License
|
||||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
from json import JSONEncoder
|
import errno
|
||||||
import logging
|
import logging
|
||||||
import time
|
import time
|
||||||
|
|
||||||
import smbus
|
import smbus
|
||||||
|
from sqlalchemy import Column, Integer, String
|
||||||
|
from sqlalchemy.orm import relationship, reconstructor
|
||||||
|
|
||||||
from ledd.stripe import Stripe
|
from . import Base
|
||||||
|
|
||||||
PCA9685_SUBADR1 = 0x2
|
PCA9685_SUBADR1 = 0x2
|
||||||
PCA9685_SUBADR2 = 0x3
|
PCA9685_SUBADR2 = 0x3
|
||||||
@@ -41,74 +43,60 @@ ALLLED_OFF_L = 0xFC
|
|||||||
ALLLED_OFF_H = 0xFD
|
ALLLED_OFF_H = 0xFD
|
||||||
|
|
||||||
|
|
||||||
class Controller:
|
class Controller(Base):
|
||||||
|
__tablename__ = "controller"
|
||||||
|
|
||||||
|
id = Column(Integer, primary_key=True)
|
||||||
|
channels = Column(Integer)
|
||||||
|
i2c_device = Column(Integer)
|
||||||
|
address = Column(String)
|
||||||
|
stripes = relationship("Stripe", backref="controller")
|
||||||
|
_pwm_freq = Column("pwm_freq", Integer, default=1526)
|
||||||
|
|
||||||
"""
|
"""
|
||||||
A controller controls a number of stripes.
|
A controller controls a number of stripes.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
@classmethod
|
def __init__(self, *args, **kwargs):
|
||||||
def from_row(cls, db, row):
|
super().__init__(*args, **kwargs)
|
||||||
# load from db
|
|
||||||
return cls(db, pwm_freq=row["pwm_freq"], channels=row["channels"], i2c_device=row["i2c_device"],
|
|
||||||
address=row["address"], cid=row["id"], from_db=True)
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def from_db(db):
|
|
||||||
l = []
|
|
||||||
cur = db.cursor()
|
|
||||||
for row in cur.execute("select * from controller"):
|
|
||||||
c = Controller.from_row(db, row)
|
|
||||||
l.append(c)
|
|
||||||
cur.close()
|
|
||||||
return l
|
|
||||||
|
|
||||||
def save_to_db(self):
|
|
||||||
cur = self.db.cursor()
|
|
||||||
if self.id == -1:
|
|
||||||
cur.execute("INSERT INTO controller (pwm_freq, channels, i2c_device, address) VALUES (?,?,?,?)",
|
|
||||||
(self._pwm_freq, self.channels, self.i2c_device, self.address))
|
|
||||||
self.id = cur.lastrowid
|
|
||||||
else:
|
|
||||||
cur.execute("UPDATE controller SET pwm_freq=?, channels=?, i2c_device=?, address=? WHERE id = ?",
|
|
||||||
(self._pwm_freq, self.channels, self.i2c_device, self.address, self.id))
|
|
||||||
cur.close()
|
|
||||||
self.db.commit()
|
|
||||||
|
|
||||||
def __init__(self, db, channels, i2c_device, address, pwm_freq=1526, cid=-1, from_db=False):
|
|
||||||
self._mode = None
|
self._mode = None
|
||||||
self.channels = channels
|
self.bus = smbus.SMBus(self.i2c_device)
|
||||||
self.i2c_device = i2c_device
|
self._address = int(self.address, 16)
|
||||||
self.bus = smbus.SMBus(i2c_device)
|
self.pwm_freq = self._pwm_freq
|
||||||
self.address = address
|
|
||||||
self._address = int(address, 16)
|
|
||||||
self.id = cid
|
|
||||||
self.db = db
|
|
||||||
self.stripes = []
|
|
||||||
self._pwm_freq = None
|
|
||||||
self.pwm_freq = pwm_freq
|
|
||||||
if not from_db:
|
|
||||||
self.save_to_db()
|
|
||||||
self.load_stripes()
|
|
||||||
|
|
||||||
def load_stripes(self):
|
@reconstructor
|
||||||
cur = self.db.cursor()
|
def init_on_load(self):
|
||||||
for stripe in cur.execute("select * from stripes where controller_id = ?", (self.id,)):
|
self._mode = None
|
||||||
self.stripes.append(Stripe.from_db(self, stripe))
|
self.bus = smbus.SMBus(self.i2c_device)
|
||||||
logging.getLogger(__name__).debug("Loaded %s stripes for controller %s", len(self.stripes), self.id)
|
self._address = int(self.address, 16)
|
||||||
cur.close()
|
self.pwm_freq = self._pwm_freq
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return "<Controller stripes={} cid={}>".format(len(self.stripes), self.id)
|
return "<Controller stripes={} cid={}>".format(len(self.stripes), self.id)
|
||||||
|
|
||||||
def set_channel(self, channel, val):
|
def set_channel(self, channel, val, gamma):
|
||||||
self.bus.write_word_data(self._address, LED0_OFF_L + 4 * channel, int(val * 4095))
|
self.bus.write_word_data(self._address, LED0_OFF_L + 4 * channel, self.gamma_correct(gamma, int(val * 4095),
|
||||||
|
4095))
|
||||||
self.bus.write_word_data(self._address, LED0_ON_L + 4 * channel, 0)
|
self.bus.write_word_data(self._address, LED0_ON_L + 4 * channel, 0)
|
||||||
|
|
||||||
def get_channel(self, channel):
|
def set_all_channel(self, val):
|
||||||
return self.bus.read_word_data(self._address, LED0_OFF_L + 4 * channel) / 4095
|
self.bus.write_word_data(self._address, ALLLED_OFF_L, int(val * 4095))
|
||||||
|
self.bus.write_word_data(self._address, ALLLED_ON_L, 0)
|
||||||
|
|
||||||
def add_stripe(self, stripe):
|
@staticmethod
|
||||||
self.stripes.append(stripe)
|
def gamma_correct(gamma, val, maxval):
|
||||||
|
corrected = int(pow(float(val) / float(maxval), float(gamma)) * float(maxval) + 0.5)
|
||||||
|
logging.getLogger(__name__).debug("GammaCorrect: in=%s out=%s, gamma=%s", val, corrected, gamma)
|
||||||
|
return corrected
|
||||||
|
|
||||||
|
def get_channel(self, channel):
|
||||||
|
try:
|
||||||
|
return self.bus.read_word_data(self._address, LED0_OFF_L + 4 * channel) / 4095
|
||||||
|
except OSError as e:
|
||||||
|
if int(e) == errno.ECOMM:
|
||||||
|
return 0
|
||||||
|
else:
|
||||||
|
raise
|
||||||
|
|
||||||
def reset(self):
|
def reset(self):
|
||||||
self.mode = int("0b00100001", 2) # MODE1 -> 0b00000001
|
self.mode = int("0b00100001", 2) # MODE1 -> 0b00000001
|
||||||
@@ -129,7 +117,7 @@ class Controller:
|
|||||||
|
|
||||||
@property
|
@property
|
||||||
def pwm_freq(self):
|
def pwm_freq(self):
|
||||||
self._pwm_freq = (self.bus.read_byte_data(self._address, PCA9685_PRESCALE) + 1) / 4096 * 25000000
|
self._pwm_freq = round(390625 / ((self.bus.read_byte_data(self._address, PCA9685_PRESCALE) + 1) * 64))
|
||||||
return self._pwm_freq
|
return self._pwm_freq
|
||||||
|
|
||||||
@pwm_freq.setter
|
@pwm_freq.setter
|
||||||
@@ -144,24 +132,17 @@ class Controller:
|
|||||||
self.reset()
|
self.reset()
|
||||||
self._pwm_freq = value
|
self._pwm_freq = value
|
||||||
|
|
||||||
|
def to_json(self):
|
||||||
|
return {
|
||||||
|
'id': self.id,
|
||||||
|
'pwm_freq': self.pwm_freq,
|
||||||
|
'channel': self.channels,
|
||||||
|
'address': self.address,
|
||||||
|
'stripes': self.stripes,
|
||||||
|
'cstripes': len(self.stripes),
|
||||||
|
'i2c_device': self.i2c_device,
|
||||||
|
'mode': self.mode
|
||||||
|
}
|
||||||
|
|
||||||
class ControllerEncoder(JSONEncoder):
|
def close(self):
|
||||||
def default(self, o):
|
self.bus.close()
|
||||||
if isinstance(o, Controller):
|
|
||||||
return {
|
|
||||||
'id': o.id,
|
|
||||||
'pwm_freq': o.pwm_freq,
|
|
||||||
'channel': o.channels,
|
|
||||||
'address': o.address,
|
|
||||||
'stripes': o.stripes,
|
|
||||||
'cstripes': len(o.stripes),
|
|
||||||
'i2c_device': o.i2c_device,
|
|
||||||
'mode': o.mode
|
|
||||||
}
|
|
||||||
elif isinstance(o, Stripe):
|
|
||||||
return {
|
|
||||||
'id': o.id,
|
|
||||||
'name': o.name,
|
|
||||||
'rgb': o.rgb,
|
|
||||||
'channel': o.channels
|
|
||||||
}
|
|
||||||
|
460
ledd/daemon.py
460
ledd/daemon.py
@@ -14,378 +14,369 @@
|
|||||||
# You should have received a copy of the GNU General Public License
|
# You should have received a copy of the GNU General Public License
|
||||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
import logging
|
|
||||||
import configparser
|
|
||||||
import json
|
|
||||||
import sqlite3
|
|
||||||
import os
|
|
||||||
import sys
|
|
||||||
import traceback
|
|
||||||
import asyncio
|
import asyncio
|
||||||
|
import configparser
|
||||||
|
import errno
|
||||||
|
import logging
|
||||||
|
import os
|
||||||
import signal
|
import signal
|
||||||
|
import sys
|
||||||
|
|
||||||
import spectra
|
import spectra
|
||||||
|
from jsonrpc import JSONRPCResponseManager, dispatcher
|
||||||
|
from jsonrpc.exceptions import JSONRPCError, JSONRPCInvalidParams
|
||||||
|
from sqlalchemy import create_engine
|
||||||
|
from sqlalchemy.exc import OperationalError
|
||||||
|
from sqlalchemy.orm.exc import NoResultFound
|
||||||
|
|
||||||
from ledd import controller, VERSION
|
from ledd import VERSION
|
||||||
from ledd.decorators import ledd_protocol
|
from ledd.controller import Controller
|
||||||
from ledd.effectstack import EffectStack
|
from ledd.effectstack import EffectStack
|
||||||
|
from ledd.models import Meta
|
||||||
from ledd.stripe import Stripe
|
from ledd.stripe import Stripe
|
||||||
|
from . import Base, session
|
||||||
|
|
||||||
log = logging.getLogger(__name__)
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
class Daemon:
|
|
||||||
daemonSection = 'daemon'
|
daemonSection = 'daemon'
|
||||||
databaseSection = 'db'
|
databaseSection = 'db'
|
||||||
instance = None
|
|
||||||
""":type : Daemon """
|
|
||||||
loop = None
|
|
||||||
""" :type : asyncio.BaseEventLoop """
|
""" :type : asyncio.BaseEventLoop """
|
||||||
protocol = {}
|
|
||||||
effects = []
|
effects = []
|
||||||
|
stripes = []
|
||||||
|
controller = []
|
||||||
|
|
||||||
def __init__(self):
|
|
||||||
Daemon.instance = self
|
|
||||||
|
|
||||||
|
def run():
|
||||||
try:
|
try:
|
||||||
# read config
|
# read config
|
||||||
self.config = configparser.ConfigParser()
|
config = configparser.ConfigParser()
|
||||||
try:
|
try:
|
||||||
with open('ledd.config', 'w+') as f:
|
with open('ledd.config', 'w+') as f:
|
||||||
self.config.read_file(f)
|
config.read_file(f)
|
||||||
except FileNotFoundError:
|
except FileNotFoundError:
|
||||||
log.info("No config file found!")
|
log.info("No config file found!")
|
||||||
pass
|
|
||||||
|
|
||||||
# SQL init
|
# SQL init
|
||||||
self.sqldb = sqlite3.connect(self.config.get(self.databaseSection, 'name', fallback='ledd.sqlite'))
|
global engine
|
||||||
self.sqldb.row_factory = sqlite3.Row
|
engine = create_engine("sqlite:///" + config.get(databaseSection, 'name', fallback='ledd.sqlite'),
|
||||||
|
echo=log.getEffectiveLevel() == logging.DEBUG)
|
||||||
|
session.configure(bind=engine)
|
||||||
|
Base.metadata.bind = engine
|
||||||
|
if not check_db():
|
||||||
|
init_db()
|
||||||
|
|
||||||
if not self.check_db():
|
|
||||||
self.init_db()
|
|
||||||
|
|
||||||
self.sqldb.commit()
|
|
||||||
|
|
||||||
# init controllers from db
|
|
||||||
self.controllers = controller.Controller.from_db(self.sqldb)
|
|
||||||
log.debug(self.controllers)
|
|
||||||
logging.getLogger("asyncio").setLevel(log.getEffectiveLevel())
|
logging.getLogger("asyncio").setLevel(log.getEffectiveLevel())
|
||||||
|
|
||||||
|
# Load to cache
|
||||||
|
global controller, stripes
|
||||||
|
controller = Controller.query.all()
|
||||||
|
|
||||||
|
for c in controller:
|
||||||
|
stripes.extend(c.stripes)
|
||||||
|
|
||||||
# sigterm handler
|
# sigterm handler
|
||||||
def sigterm_handler(_signo, _stack_frame):
|
def sigterm_handler():
|
||||||
sys.exit(0)
|
raise SystemExit
|
||||||
|
|
||||||
signal.signal(signal.SIGTERM, sigterm_handler)
|
signal.signal(signal.SIGTERM, sigterm_handler)
|
||||||
|
|
||||||
|
# init plugins
|
||||||
|
# TODO: check all plugins for existing hooks
|
||||||
|
|
||||||
# main loop
|
# main loop
|
||||||
self.loop = asyncio.get_event_loop()
|
global loop, server
|
||||||
coro = self.loop.create_server(LedDProtocol,
|
loop = asyncio.get_event_loop()
|
||||||
self.config.get(self.daemonSection, 'host', fallback='0.0.0.0'),
|
coro = loop.create_server(LedDProtocol,
|
||||||
self.config.get(self.daemonSection, 'port', fallback=1425))
|
config.get(daemonSection, 'host', fallback='0.0.0.0'),
|
||||||
self.server = self.loop.run_until_complete(coro)
|
config.get(daemonSection, 'port', fallback=1425))
|
||||||
self.loop.run_forever()
|
server = loop.run_until_complete(coro)
|
||||||
|
log.info("Start phase finished; starting main loop")
|
||||||
|
loop.run_forever()
|
||||||
except (KeyboardInterrupt, SystemExit):
|
except (KeyboardInterrupt, SystemExit):
|
||||||
log.info("Exiting")
|
log.info("Exiting")
|
||||||
|
|
||||||
|
for c in controller:
|
||||||
|
c.close()
|
||||||
|
|
||||||
try:
|
try:
|
||||||
os.remove("ledd.pid")
|
os.remove("ledd.pid")
|
||||||
except FileNotFoundError:
|
except FileNotFoundError:
|
||||||
pass
|
pass
|
||||||
self.sqldb.close()
|
session.commit()
|
||||||
self.server.close()
|
session.close()
|
||||||
self.loop.run_until_complete(self.server.wait_closed())
|
if server is not None:
|
||||||
self.loop.close()
|
server.close()
|
||||||
|
if loop is not None:
|
||||||
|
loop.run_until_complete(server.wait_closed())
|
||||||
|
loop.close()
|
||||||
sys.exit(0)
|
sys.exit(0)
|
||||||
|
|
||||||
def check_db(self):
|
|
||||||
|
def check_db():
|
||||||
"""
|
"""
|
||||||
Checks database version
|
Checks database version
|
||||||
:return: database validity
|
:return: database validity
|
||||||
:rtype: bool
|
:rtype: bool
|
||||||
"""
|
"""
|
||||||
c = self.sqldb.cursor()
|
|
||||||
try:
|
try:
|
||||||
c.execute("SELECT value FROM meta WHERE option = 'db_version'")
|
db_version = Meta.get_version()
|
||||||
db_version = c.fetchone()
|
|
||||||
c.close()
|
|
||||||
|
|
||||||
if db_version is not None:
|
if db_version is not None:
|
||||||
log.info("DB connection established; db-version=%s", db_version[0])
|
log.info("DB connection established; db_version=%s", db_version)
|
||||||
return True
|
return True
|
||||||
else:
|
except OperationalError:
|
||||||
return False
|
return False
|
||||||
except sqlite3.OperationalError:
|
|
||||||
c.close()
|
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def init_db(self):
|
|
||||||
self.sqldb.close()
|
|
||||||
if os.path.exists("ledd.sqlite"):
|
|
||||||
os.remove("ledd.sqlite")
|
|
||||||
self.sqldb = sqlite3.connect(self.config.get(self.databaseSection, 'name', fallback='ledd.sqlite'))
|
|
||||||
self.sqldb.row_factory = sqlite3.Row
|
|
||||||
with open("ledd/sql/ledd.sql", "r") as sqlfile:
|
|
||||||
c = self.sqldb.cursor()
|
|
||||||
c.executescript(sqlfile.read())
|
|
||||||
c.close()
|
|
||||||
self.check_db()
|
|
||||||
|
|
||||||
@ledd_protocol(protocol)
|
def init_db():
|
||||||
def start_effect(self, req_json):
|
Base.metadata.drop_all()
|
||||||
|
Base.metadata.create_all()
|
||||||
|
session.add(Meta(option="db_version", value="2"))
|
||||||
|
session.commit()
|
||||||
|
check_db()
|
||||||
|
|
||||||
|
|
||||||
|
@dispatcher.add_method
|
||||||
|
def start_effect(**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 parameters: stripe IDs: sids; effect id: eid, effect options: eopt
|
||||||
:param req_json: dict of request json
|
:param kwargs:
|
||||||
"""
|
"""
|
||||||
log.debug("recieved action: %s", req_json['action'])
|
|
||||||
|
|
||||||
stripes = []
|
if "sids" not in kwargs or "eid" not in kwargs or "eopt" not in kwargs:
|
||||||
|
return JSONRPCInvalidParams()
|
||||||
|
|
||||||
if "sids" in req_json:
|
for stripe in Stripe.query.filter(Stripe.id.in_(kwargs['sids'])):
|
||||||
for sid in req_json['sids']:
|
|
||||||
found_s = self.find_stripe(sid)
|
|
||||||
|
|
||||||
if found_s is not None:
|
|
||||||
stripes.append(found_s)
|
|
||||||
|
|
||||||
if len(stripes) > 0:
|
|
||||||
# TODO: add anything required to start effect with req_json['eid']
|
# TODO: add anything required to start effect with req_json['eid']
|
||||||
# on stripes[] with options in req_json['eopt']
|
# on stripes[] with options in req_json['eopt']
|
||||||
effect = EffectStack()
|
effect = EffectStack()
|
||||||
self.effects.append(effect)
|
effects.append(effect)
|
||||||
effect.stripes.append(self.controllers[0].stripes[0])
|
effect.stripes.append(stripe)
|
||||||
effect.start()
|
effect.start()
|
||||||
|
|
||||||
# asyncio.ensure_future(asyncio.get_event_loop().run_in_executor(self.executor, effect.execute))
|
# asyncio.ensure_future(asyncio.get_event_loop().run_in_executor(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:
|
|
||||||
rjson = {
|
|
||||||
'success': False,
|
|
||||||
'message': "No stripe with this id found",
|
|
||||||
'ref': req_json['ref']
|
|
||||||
}
|
|
||||||
|
|
||||||
return json.dumps(rjson)
|
return JSONRPCError(-1003, "Stripeid not found")
|
||||||
|
|
||||||
@ledd_protocol(protocol)
|
|
||||||
def stop_effect(self, req_json):
|
@dispatcher.add_method
|
||||||
|
def stop_effect(**kwargs):
|
||||||
"""
|
"""
|
||||||
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 parameters: effect identifier: eident
|
||||||
: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)
|
|
||||||
def get_effects(self, req_json):
|
@dispatcher.add_method
|
||||||
|
def get_effects(**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 parameters: -
|
||||||
: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)
|
|
||||||
def set_color(self, req_json):
|
@dispatcher.add_method
|
||||||
|
def set_color(**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 parameters: stripe ID: sid; HSV values hsv: h,s,v, controller id: cid
|
||||||
:param req_json: dict of request json
|
|
||||||
"""
|
"""
|
||||||
log.debug("recieved action: %s", req_json['action'])
|
|
||||||
|
|
||||||
found_s = self.find_stripe(req_json['sid'])
|
if "sid" not in kwargs or "hsv" not in kwargs:
|
||||||
|
return JSONRPCInvalidParams()
|
||||||
|
|
||||||
if found_s is None:
|
stripe = get_stripe(kwargs['sid'])
|
||||||
log.warning("Stripe not found: id=%s", req_json['sid'])
|
|
||||||
|
if stripe:
|
||||||
|
try:
|
||||||
|
stripe.set_color(spectra.hsv(kwargs['hsv']['h'], kwargs['hsv']['s'], kwargs['hsv']['v']))
|
||||||
|
except OSError as e:
|
||||||
|
if int(e) == errno.ECOMM:
|
||||||
|
log.warning("Communication error on I2C Bus")
|
||||||
|
return e
|
||||||
else:
|
else:
|
||||||
found_s.set_color(spectra.hsv(req_json['hsv']['h'], req_json['hsv']['s'], req_json['hsv']['v']))
|
raise
|
||||||
|
else:
|
||||||
|
log.warning("Stripe not found: id=%s", kwargs['sid'])
|
||||||
|
return JSONRPCError(-1003, "Stripeid not found")
|
||||||
|
|
||||||
def find_stripe(self, sid):
|
return ""
|
||||||
|
|
||||||
|
|
||||||
|
@dispatcher.add_method
|
||||||
|
def set_color_all(**kwargs):
|
||||||
"""
|
"""
|
||||||
Finds a given stripeid in the currently known controllers
|
Part of the Color API. Used to set brightness of all stripes a controller owns.
|
||||||
:param sid stripe id
|
Required parameters: controller id: cid, value: v
|
||||||
:return: stripe if found or none
|
|
||||||
:rtype: ledd.Stripe | None
|
|
||||||
"""
|
"""
|
||||||
for c in self.controllers:
|
|
||||||
for s in c.stripes:
|
|
||||||
if s.id == sid:
|
|
||||||
return s
|
|
||||||
|
|
||||||
return None
|
if "cid" not in kwargs or "v" not in kwargs:
|
||||||
|
return JSONRPCInvalidParams()
|
||||||
|
|
||||||
@ledd_protocol(protocol)
|
try:
|
||||||
def add_controller(self, req_json):
|
c = get_controller(kwargs['cid'])
|
||||||
|
""" :type c: ledd.controller.Controller """
|
||||||
|
|
||||||
|
c.set_all_channel(kwargs['v'])
|
||||||
|
except NoResultFound:
|
||||||
|
log.warning("Controller not found: id=%s", kwargs['cid'])
|
||||||
|
return JSONRPCError(-1002, "Controller not found")
|
||||||
|
|
||||||
|
return ""
|
||||||
|
|
||||||
|
|
||||||
|
@dispatcher.add_method
|
||||||
|
def add_controller(**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 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
|
|
||||||
"""
|
"""
|
||||||
log.debug("recieved action: %s", req_json['action'])
|
|
||||||
|
if "i2c_dev" not in kwargs or "channels" not in kwargs or "address" not in kwargs:
|
||||||
|
return JSONRPCInvalidParams()
|
||||||
|
|
||||||
try:
|
try:
|
||||||
ncontroller = controller.Controller(Daemon.instance.sqldb, req_json['channels'],
|
ncontroller = Controller(channels=int(kwargs['channels']), i2c_device=int(kwargs['i2c_dev']),
|
||||||
req_json['i2c_dev'], req_json['address'])
|
address=kwargs['address'], _pwm_freq=1526)
|
||||||
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)
|
session.add(ncontroller)
|
||||||
|
session.commit()
|
||||||
|
|
||||||
rjson = {
|
controller.append(ncontroller)
|
||||||
'success': True,
|
|
||||||
'cid': ncontroller.id,
|
|
||||||
'ref': req_json['ref']
|
|
||||||
}
|
|
||||||
|
|
||||||
return json.dumps(rjson)
|
return {'cid': ncontroller.id}
|
||||||
|
|
||||||
@ledd_protocol(protocol)
|
|
||||||
def get_color(self, req_json):
|
@dispatcher.add_method
|
||||||
|
def get_color(**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 parameters: sid
|
||||||
:param req_json: dict of request json
|
|
||||||
"""
|
"""
|
||||||
log.debug("recieved action: %s", req_json['action'])
|
|
||||||
|
|
||||||
found_s = self.find_stripe(req_json['sid'])
|
if "sid" not in kwargs:
|
||||||
|
return JSONRPCInvalidParams()
|
||||||
|
|
||||||
if found_s is None:
|
stripe = get_stripe(kwargs['sid'])
|
||||||
log.warning("Stripe not found: id=%s", req_json['sid'])
|
|
||||||
return {
|
|
||||||
'success': False,
|
|
||||||
'message': "Stripe not found",
|
|
||||||
'ref': req_json['ref']
|
|
||||||
}
|
|
||||||
|
|
||||||
rjson = {
|
if not stripe:
|
||||||
'success': True,
|
log.warning("Stripe not found: id=%s", kwargs['sid'])
|
||||||
'color': found_s.color.values,
|
return JSONRPCError(-1003, "Stripeid not found")
|
||||||
'ref': req_json['ref']
|
|
||||||
}
|
|
||||||
|
|
||||||
return json.dumps(rjson)
|
if stripe.color:
|
||||||
|
return {'color': stripe.color.values}
|
||||||
|
else:
|
||||||
|
log.warning("Stripe has no color: id=%s", kwargs['sid'])
|
||||||
|
return JSONRPCError(-1009, "Internal Error")
|
||||||
|
|
||||||
@ledd_protocol(protocol)
|
|
||||||
def add_stripe(self, req_json):
|
@dispatcher.add_method
|
||||||
|
def add_stripe(**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 parameters: name; rgb: bool; map: r: r-channel, g: g-channel, b: b-channel, cid
|
||||||
:param req_json: dict of request json
|
|
||||||
"""
|
"""
|
||||||
log.debug("recieved action: %s", req_json['action'])
|
|
||||||
|
|
||||||
if "stripe" in req_json:
|
if "name" not in kwargs or "rgb" not in kwargs or "map" not in kwargs or "cid" not in kwargs:
|
||||||
stripe = req_json['stripe']
|
return JSONRPCInvalidParams()
|
||||||
c = next((x for x in self.controllers if x.id == stripe['cid']), None)
|
|
||||||
|
c = get_controller(kwargs['cid'])
|
||||||
""" :type c: ledd.controller.Controller """
|
""" :type c: ledd.controller.Controller """
|
||||||
|
|
||||||
if c is None:
|
if c is None:
|
||||||
return {
|
log.warning("Controller not found: id=%s", kwargs['cid'])
|
||||||
'success': False,
|
return JSONRPCError(-1002, "Controller not found")
|
||||||
'message': "Controller not found",
|
|
||||||
'ref': stripe['ref']
|
|
||||||
}
|
|
||||||
|
|
||||||
s = Stripe(c, stripe['name'], stripe['rgb'],
|
s = Stripe(name=kwargs['name'], rgb=bool(kwargs['rgb']),
|
||||||
(stripe['map']['r'], stripe['map']['g'], stripe['map']['b']))
|
channel_r=kwargs['map']['r'], channel_g=kwargs['map']['g'], channel_b=kwargs['map']['b'])
|
||||||
|
s.controller = c
|
||||||
|
log.debug("Added stripe %s to controller %s; new len %s", s.id, c.id, len(c.stripes))
|
||||||
|
|
||||||
c.stripes.append(s)
|
session.add(s)
|
||||||
log.debug("Added stripe %s to controller %s; new len %s", c.id, s.id, len(c.stripes))
|
session.commit()
|
||||||
|
stripes.append(s)
|
||||||
|
|
||||||
rjson = {
|
return {'sid': s.id}
|
||||||
'success': True,
|
|
||||||
'sid': s.id,
|
|
||||||
'ref': req_json['ref']
|
|
||||||
}
|
|
||||||
|
|
||||||
return json.dumps(rjson)
|
|
||||||
|
|
||||||
@ledd_protocol(protocol)
|
@dispatcher.add_method
|
||||||
def get_stripes(self, req_json):
|
def get_stripes(**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 parameters: -
|
||||||
:param req_json: dict of request json
|
|
||||||
"""
|
"""
|
||||||
log.debug("recieved action: %s", req_json['action'])
|
|
||||||
|
|
||||||
rjson = {
|
rjson = {
|
||||||
'success': True,
|
'ccount': len(controller),
|
||||||
'ccount': len(Daemon.instance.controllers),
|
'controller': controller
|
||||||
'controller': Daemon.instance.controllers,
|
|
||||||
'ref': req_json['ref']
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return json.dumps(rjson, cls=controller.ControllerEncoder)
|
return rjson
|
||||||
|
|
||||||
@ledd_protocol(protocol)
|
|
||||||
def test_channel(self, req_json):
|
@dispatcher.add_method
|
||||||
|
def test_channel(**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 parameters: controller id: cid, channel, value
|
||||||
: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)
|
if "cid" not in kwargs or "channel" not in kwargs or "value" not in kwargs:
|
||||||
|
return JSONRPCInvalidParams()
|
||||||
|
|
||||||
|
contr = get_controller(kwargs['cid'])
|
||||||
""" :type : ledd.controller.Controller """
|
""" :type : ledd.controller.Controller """
|
||||||
|
|
||||||
if result is not None:
|
if contr is not None:
|
||||||
result.set_channel(req_json['channel'], req_json['value'])
|
try:
|
||||||
|
contr.set_channel(kwargs['channel'], kwargs['value'], 2.8)
|
||||||
|
except OSError as e:
|
||||||
|
return JSONRPCError(-1009, "Internal Error", e)
|
||||||
|
else:
|
||||||
|
return JSONRPCError(-1002, "Controller not found")
|
||||||
|
|
||||||
rjson = {
|
return ""
|
||||||
'success': True,
|
|
||||||
'ref': req_json['ref']
|
|
||||||
}
|
|
||||||
|
|
||||||
return json.dumps(rjson)
|
|
||||||
|
|
||||||
@ledd_protocol(protocol)
|
@dispatcher.add_method
|
||||||
def discover(self, req_json):
|
def discover(**kwargs):
|
||||||
"""
|
"""
|
||||||
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 parameters: -
|
||||||
:param req_json: dict of request json
|
|
||||||
"""
|
"""
|
||||||
log.debug("recieved action: %s", req_json['action'])
|
|
||||||
|
|
||||||
rjson = {
|
return {'version': VERSION}
|
||||||
'success': True,
|
|
||||||
'ref': req_json['ref'],
|
|
||||||
'version': VERSION
|
|
||||||
}
|
|
||||||
|
|
||||||
return json.dumps(rjson)
|
|
||||||
|
|
||||||
def no_action_found(self, req_json):
|
def get_stripe(sid):
|
||||||
rjson = {
|
for s in stripes:
|
||||||
'success': False,
|
if s.id == sid:
|
||||||
'message': "No action found",
|
return s
|
||||||
'ref': req_json['ref']
|
|
||||||
}
|
|
||||||
return json.dumps(rjson)
|
def get_controller(cid):
|
||||||
|
for c in controller:
|
||||||
|
if c.id == cid:
|
||||||
|
return c
|
||||||
|
|
||||||
|
|
||||||
class LedDProtocol(asyncio.Protocol):
|
class LedDProtocol(asyncio.Protocol):
|
||||||
@@ -401,32 +392,17 @@ class LedDProtocol(asyncio.Protocol):
|
|||||||
except UnicodeDecodeError:
|
except UnicodeDecodeError:
|
||||||
log.warning("Recieved undecodable data, ignoring")
|
log.warning("Recieved undecodable data, ignoring")
|
||||||
else:
|
else:
|
||||||
log.debug("Received: %s from: %s", d_decoded, self.transport.get_extra_info("peername"))
|
|
||||||
self.select_task(d_decoded)
|
self.select_task(d_decoded)
|
||||||
|
|
||||||
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)
|
|
||||||
for line in data_split:
|
for line in data_split:
|
||||||
if line:
|
if line:
|
||||||
json_decoded = json.loads(line)
|
try:
|
||||||
|
self.transport.write(JSONRPCResponseManager.handle(line, dispatcher).json.encode())
|
||||||
if "action" in json_decoded and "ref" in json_decoded:
|
except TypeError as te:
|
||||||
return_data = Daemon.instance.protocol.get(json_decoded['action'], Daemon.no_action_found)(
|
log.warning("Can't send response: %s", te)
|
||||||
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
|
|
||||||
# Daemon.loop.stop()
|
|
||||||
log.info("Lost connection to %s", self.transport.get_extra_info("peername"))
|
log.info("Lost connection to %s", self.transport.get_extra_info("peername"))
|
||||||
|
@@ -1,3 +1,19 @@
|
|||||||
|
# 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/>.
|
||||||
|
|
||||||
import asyncio
|
import asyncio
|
||||||
|
|
||||||
from ledd.effects.fadeeffect import FadeEffect
|
from ledd.effects.fadeeffect import FadeEffect
|
||||||
|
@@ -14,20 +14,16 @@
|
|||||||
# You should have received a copy of the GNU General Public License
|
# You should have received a copy of the GNU General Public License
|
||||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
from sqlalchemy import String, Column
|
||||||
|
|
||||||
def ledd_protocol(proto):
|
from . import Base
|
||||||
"""
|
|
||||||
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):
|
class Meta(Base):
|
||||||
f(*args)
|
__tablename__ = "meta"
|
||||||
|
option = Column(String, primary_key=True)
|
||||||
|
value = Column(String)
|
||||||
|
|
||||||
return wrapped_f
|
@classmethod
|
||||||
|
def get_version(cls):
|
||||||
return wrap
|
return cls.query.filter(Meta.option == "db_version").first()
|
15
ledd/plugins/__init__.py
Normal file
15
ledd/plugins/__init__.py
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
# 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/>.
|
@@ -5,13 +5,16 @@ CREATE TABLE `stripes` (
|
|||||||
`controller_id` INTEGER,
|
`controller_id` INTEGER,
|
||||||
`channel_r` INTEGER,
|
`channel_r` INTEGER,
|
||||||
`channel_g` INTEGER,
|
`channel_g` INTEGER,
|
||||||
`channel_b` INTEGER
|
`channel_b` INTEGER,
|
||||||
|
`channel_r_gamma` REAL DEFAULT 2.8,
|
||||||
|
`channel_g_gamma` REAL DEFAULT 2.8,
|
||||||
|
`channel_b_gamma` REAL DEFAULT 2.8
|
||||||
);
|
);
|
||||||
CREATE TABLE "meta" (
|
CREATE TABLE "meta" (
|
||||||
`option` TEXT,
|
`option` TEXT,
|
||||||
`value` TEXT
|
`value` TEXT
|
||||||
);
|
);
|
||||||
INSERT INTO `meta` VALUES ('db_version','1');
|
INSERT INTO `meta` VALUES ('db_version', '2');
|
||||||
CREATE TABLE "controller" (
|
CREATE TABLE "controller" (
|
||||||
`id` INTEGER PRIMARY KEY AUTOINCREMENT UNIQUE,
|
`id` INTEGER PRIMARY KEY AUTOINCREMENT UNIQUE,
|
||||||
`address` TEXT,
|
`address` TEXT,
|
||||||
|
5
ledd/sql/upgrade_1_2.sql
Normal file
5
ledd/sql/upgrade_1_2.sql
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
ALTER TABLE stripes ADD COLUMN channel_r_gamma REAL DEFAULT 2.8;
|
||||||
|
ALTER TABLE stripes ADD COLUMN channel_g_gamma REAL DEFAULT 2.8;
|
||||||
|
ALTER TABLE stripes ADD COLUMN channel_b_gamma REAL DEFAULT 2.8;
|
||||||
|
|
||||||
|
REPLACE INTO meta (`option`, `value`) VALUES (`db_version`, `2`);
|
@@ -13,38 +13,55 @@
|
|||||||
#
|
#
|
||||||
# You should have received a copy of the GNU General Public License
|
# You should have received a copy of the GNU General Public License
|
||||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
from spectra import Color
|
from spectra import Color
|
||||||
|
from sqlalchemy import Integer, ForeignKey, String, Float, Boolean
|
||||||
|
from sqlalchemy import Column
|
||||||
|
from sqlalchemy.orm import reconstructor
|
||||||
|
|
||||||
|
from . import Base
|
||||||
|
|
||||||
|
|
||||||
class Stripe:
|
class Stripe(Base):
|
||||||
"""
|
"""
|
||||||
A stripe is the smallest controllable unit.
|
A stripe is the smallest controllable unit.
|
||||||
"""
|
"""
|
||||||
|
__tablename__ = "stripe"
|
||||||
|
id = Column(Integer, primary_key=True)
|
||||||
|
controller_id = Column(Integer, ForeignKey('controller.id'))
|
||||||
|
name = Column(String)
|
||||||
|
channel_r = Column(Integer)
|
||||||
|
channel_g = Column(Integer)
|
||||||
|
channel_b = Column(Integer)
|
||||||
|
channel_r_gamma = Column(Float, default=2.8)
|
||||||
|
channel_g_gamma = Column(Float, default=2.8)
|
||||||
|
channel_b_gamma = Column(Float, default=2.8)
|
||||||
|
rgb = Column(Boolean)
|
||||||
|
|
||||||
def __init__(self, controller, name, rgb, channels, sid=-1, from_db=False):
|
@property
|
||||||
self.controller = controller
|
def channels(self):
|
||||||
self.name = name
|
return self.channel_r, self.channel_g, self.channel_b
|
||||||
self.rgb = bool(rgb)
|
|
||||||
self.channels = channels
|
# TODO save channels to db
|
||||||
self.id = sid
|
|
||||||
|
@channels.setter
|
||||||
|
def channels(self, t):
|
||||||
|
self.channel_r, self.channel_g, self.channel_b = t
|
||||||
|
|
||||||
|
def __init__(self, *args, **kwargs):
|
||||||
|
super().__init__(*args, **kwargs)
|
||||||
self._color = None
|
self._color = None
|
||||||
self.gamma_correct = (2.8, 2.8, 2.8) # TODO: add to DB
|
self.gamma_correct = (self.channel_r_gamma, self.channel_g_gamma, self.channel_b_gamma)
|
||||||
self.read_color()
|
self.read_color()
|
||||||
if not from_db:
|
|
||||||
self.save_to_db()
|
|
||||||
|
|
||||||
def save_to_db(self):
|
@reconstructor
|
||||||
cur = self.controller.db.cursor()
|
def init_on_load(self):
|
||||||
if self.id == -1:
|
self._color = None
|
||||||
cur.execute("INSERT INTO stripes DEFAULT VALUES")
|
self.gamma_correct = (self.channel_r_gamma, self.channel_g_gamma, self.channel_b_gamma)
|
||||||
self.id = cur.lastrowid
|
self.read_color()
|
||||||
cur.execute(
|
|
||||||
"UPDATE stripes SET channel_r = ?, channel_g = ?, channel_b = ?,controller_id = ?, name = ? WHERE id = ?",
|
|
||||||
self.channels + (self.controller.id, self.name, self.id))
|
|
||||||
cur.close()
|
|
||||||
self.controller.db.commit()
|
|
||||||
|
|
||||||
def read_color(self):
|
def read_color(self):
|
||||||
|
if self.controller:
|
||||||
rc = tuple([float(self.controller.get_channel(channel)) for channel in self.channels])
|
rc = tuple([float(self.controller.get_channel(channel)) for channel in self.channels])
|
||||||
c = Color("rgb", rc[0], rc[1], rc[2])
|
c = Color("rgb", rc[0], rc[1], rc[2])
|
||||||
self._color = c.to("hsv")
|
self._color = c.to("hsv")
|
||||||
@@ -52,17 +69,20 @@ class Stripe:
|
|||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return "<Stripe id={}>".format(self.id)
|
return "<Stripe id={}>".format(self.id)
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def from_db(cls, controller, row):
|
|
||||||
return cls(controller, name=row["name"], rgb=row["rgb"],
|
|
||||||
channels=(row["channel_r"], row["channel_g"], row["channel_b"]), sid=row["id"], from_db=True)
|
|
||||||
|
|
||||||
def set_color(self, c):
|
def set_color(self, c):
|
||||||
self._color = c
|
self._color = c
|
||||||
for channel, gamma_correct, value in zip(self.channels, self.gamma_correct, c.clamped_rgb):
|
for channel, gamma_correct, value in zip(self.channels, self.gamma_correct, c.clamped_rgb):
|
||||||
self.controller.set_channel(channel, value)
|
self.controller.set_channel(channel, value, gamma_correct)
|
||||||
|
|
||||||
def get_color(self):
|
def get_color(self):
|
||||||
return self._color
|
return self._color
|
||||||
|
|
||||||
|
def to_json(self):
|
||||||
|
return {
|
||||||
|
'id': self.id,
|
||||||
|
'name': self.name,
|
||||||
|
'rgb': self.rgb,
|
||||||
|
'channel': self.channels
|
||||||
|
}
|
||||||
|
|
||||||
color = property(get_color, set_color)
|
color = property(get_color, set_color)
|
||||||
|
2
setup.py
2
setup.py
@@ -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', 'sqlalchemy', 'coloredlogs'
|
||||||
],
|
],
|
||||||
zip_safe=False)
|
zip_safe=False)
|
||||||
|
Reference in New Issue
Block a user