Compare commits
12 Commits
Author | SHA1 | Date | |
---|---|---|---|
12ed601446 | |||
![]() |
bf4dcfaaf8 | ||
![]() |
7d6d289178 | ||
![]() |
db379ccdf0 | ||
![]() |
99a7252194 | ||
![]() |
a99d879298 | ||
![]() |
6ae2cc4fb8 | ||
![]() |
a65bb770fd | ||
![]() |
02f6837d27 | ||
![]() |
df90bc8a68 | ||
![]() |
f9d696b1b3 | ||
![]() |
aadcfafd00 |
19
README.md
19
README.md
@@ -2,13 +2,15 @@
|
|||||||
|
|
||||||
[![][cq img]][cq] [![][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
|
||||||
|
|
||||||
@@ -22,9 +24,10 @@ LedD is a daemon for interfacing LED stripes written in python3. It provides an
|
|||||||
|
|
||||||
Make sure your i2c devices are available (modprobe i2c-dev) before you follow these steps.
|
Make sure your i2c devices are available (modprobe i2c-dev) before you follow these steps.
|
||||||
|
|
||||||
1. `apt-get install python3-pip python3-cffi python3-docopt python3-nose python3-sqlalchemy python-smbus`
|
1. `apt-get install python3-dev python3-pip python3-cffi python3-docopt python3-nose python3-sqlalchemy python-smbus libffi-dev`
|
||||||
2. `pip3 install coloredlogs spectra json-rpc cffi smbus-cffi`
|
2. `pip3 install -U cffi` (fixes a bug where smbus-cffi can't install)
|
||||||
3. `adduser $USER i2c`
|
3. `pip3 install coloredlogs spectra json-rpc smbus-cffi`
|
||||||
|
4. `adduser $USER i2c`
|
||||||
|
|
||||||
### Plugins & Effects
|
### Plugins & Effects
|
||||||
|
|
||||||
|
@@ -14,12 +14,13 @@
|
|||||||
# 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 errno
|
||||||
import logging
|
import logging
|
||||||
import time
|
import time
|
||||||
|
|
||||||
|
import smbus
|
||||||
from sqlalchemy import Column, Integer, String
|
from sqlalchemy import Column, Integer, String
|
||||||
from sqlalchemy.orm import relationship, reconstructor
|
from sqlalchemy.orm import relationship, reconstructor
|
||||||
import smbus
|
|
||||||
|
|
||||||
from . import Base
|
from . import Base
|
||||||
|
|
||||||
@@ -50,7 +51,7 @@ class Controller(Base):
|
|||||||
i2c_device = Column(Integer)
|
i2c_device = Column(Integer)
|
||||||
address = Column(String)
|
address = Column(String)
|
||||||
stripes = relationship("Stripe", backref="controller")
|
stripes = relationship("Stripe", backref="controller")
|
||||||
_pwm_freq = Column("pwm_freq", Integer)
|
_pwm_freq = Column("pwm_freq", Integer, default=1526)
|
||||||
|
|
||||||
"""
|
"""
|
||||||
A controller controls a number of stripes.
|
A controller controls a number of stripes.
|
||||||
@@ -61,12 +62,14 @@ class Controller(Base):
|
|||||||
self._mode = None
|
self._mode = None
|
||||||
self.bus = smbus.SMBus(self.i2c_device)
|
self.bus = smbus.SMBus(self.i2c_device)
|
||||||
self._address = int(self.address, 16)
|
self._address = int(self.address, 16)
|
||||||
|
self.pwm_freq = self._pwm_freq
|
||||||
|
|
||||||
@reconstructor
|
@reconstructor
|
||||||
def init_on_load(self):
|
def init_on_load(self):
|
||||||
self._mode = None
|
self._mode = None
|
||||||
self.bus = smbus.SMBus(self.i2c_device)
|
self.bus = smbus.SMBus(self.i2c_device)
|
||||||
self._address = int(self.address, 16)
|
self._address = int(self.address, 16)
|
||||||
|
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)
|
||||||
@@ -87,7 +90,13 @@ class Controller(Base):
|
|||||||
return corrected
|
return corrected
|
||||||
|
|
||||||
def get_channel(self, channel):
|
def get_channel(self, channel):
|
||||||
return self.bus.read_word_data(self._address, LED0_OFF_L + 4 * channel) / 4095
|
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
|
||||||
@@ -108,7 +117,7 @@ class Controller(Base):
|
|||||||
|
|
||||||
@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
|
||||||
@@ -134,3 +143,6 @@ class Controller(Base):
|
|||||||
'i2c_device': self.i2c_device,
|
'i2c_device': self.i2c_device,
|
||||||
'mode': self.mode
|
'mode': self.mode
|
||||||
}
|
}
|
||||||
|
|
||||||
|
def close(self):
|
||||||
|
self.bus.close()
|
||||||
|
@@ -14,25 +14,26 @@
|
|||||||
# 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 os
|
|
||||||
import sys
|
|
||||||
import asyncio
|
import asyncio
|
||||||
|
import configparser
|
||||||
|
import errno
|
||||||
|
import logging
|
||||||
|
import os
|
||||||
import signal
|
import signal
|
||||||
|
import sys
|
||||||
|
|
||||||
from sqlalchemy import create_engine
|
import spectra
|
||||||
from jsonrpc import JSONRPCResponseManager, dispatcher
|
from jsonrpc import JSONRPCResponseManager, dispatcher
|
||||||
from jsonrpc.exceptions import JSONRPCError, JSONRPCInvalidParams
|
from jsonrpc.exceptions import JSONRPCError, JSONRPCInvalidParams
|
||||||
import spectra
|
from sqlalchemy import create_engine
|
||||||
from sqlalchemy.exc import OperationalError
|
from sqlalchemy.exc import OperationalError
|
||||||
from sqlalchemy.orm.exc import NoResultFound
|
from sqlalchemy.orm.exc import NoResultFound
|
||||||
|
|
||||||
from ledd import VERSION
|
from ledd import VERSION
|
||||||
|
from ledd.controller import Controller
|
||||||
from ledd.effectstack import EffectStack
|
from ledd.effectstack import EffectStack
|
||||||
from ledd.models import Meta
|
from ledd.models import Meta
|
||||||
from ledd.stripe import Stripe
|
from ledd.stripe import Stripe
|
||||||
from ledd.controller import Controller
|
|
||||||
from . import Base, session
|
from . import Base, session
|
||||||
|
|
||||||
log = logging.getLogger(__name__)
|
log = logging.getLogger(__name__)
|
||||||
@@ -64,7 +65,6 @@ def run():
|
|||||||
if not check_db():
|
if not check_db():
|
||||||
init_db()
|
init_db()
|
||||||
|
|
||||||
log.debug(Controller.query.all())
|
|
||||||
logging.getLogger("asyncio").setLevel(log.getEffectiveLevel())
|
logging.getLogger("asyncio").setLevel(log.getEffectiveLevel())
|
||||||
|
|
||||||
# Load to cache
|
# Load to cache
|
||||||
@@ -94,6 +94,10 @@ def run():
|
|||||||
loop.run_forever()
|
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:
|
||||||
@@ -138,6 +142,7 @@ 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 parameters: stripe IDs: sids; effect id: eid, effect options: eopt
|
Required parameters: stripe IDs: sids; effect id: eid, effect options: eopt
|
||||||
|
:param kwargs:
|
||||||
"""
|
"""
|
||||||
|
|
||||||
if "sids" not in kwargs or "eid" not in kwargs or "eopt" not in kwargs:
|
if "sids" not in kwargs or "eid" not in kwargs or "eopt" not in kwargs:
|
||||||
@@ -198,7 +203,14 @@ def set_color(**kwargs):
|
|||||||
stripe = get_stripe(kwargs['sid'])
|
stripe = get_stripe(kwargs['sid'])
|
||||||
|
|
||||||
if stripe:
|
if stripe:
|
||||||
stripe.set_color(spectra.hsv(kwargs['hsv']['h'], kwargs['hsv']['s'], kwargs['hsv']['v']))
|
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:
|
||||||
|
raise
|
||||||
else:
|
else:
|
||||||
log.warning("Stripe not found: id=%s", kwargs['sid'])
|
log.warning("Stripe not found: id=%s", kwargs['sid'])
|
||||||
return JSONRPCError(-1003, "Stripeid not found")
|
return JSONRPCError(-1003, "Stripeid not found")
|
||||||
@@ -241,7 +253,7 @@ def add_controller(**kwargs):
|
|||||||
|
|
||||||
try:
|
try:
|
||||||
ncontroller = Controller(channels=int(kwargs['channels']), i2c_device=int(kwargs['i2c_dev']),
|
ncontroller = Controller(channels=int(kwargs['channels']), i2c_device=int(kwargs['i2c_dev']),
|
||||||
address=kwargs['address'])
|
address=kwargs['address'], _pwm_freq=1526)
|
||||||
except OSError as e:
|
except OSError as e:
|
||||||
log.error("Error opening i2c device: %s (%s)", kwargs['i2c_dev'], e)
|
log.error("Error opening i2c device: %s (%s)", kwargs['i2c_dev'], e)
|
||||||
return JSONRPCError(-1004, "Error while opening i2c device", e)
|
return JSONRPCError(-1004, "Error while opening i2c device", e)
|
||||||
@@ -270,7 +282,11 @@ def get_color(**kwargs):
|
|||||||
log.warning("Stripe not found: id=%s", kwargs['sid'])
|
log.warning("Stripe not found: id=%s", kwargs['sid'])
|
||||||
return JSONRPCError(-1003, "Stripeid not found")
|
return JSONRPCError(-1003, "Stripeid not found")
|
||||||
|
|
||||||
return {'color': stripe.color.values}
|
if stripe.color:
|
||||||
|
return {'color': stripe.color.values}
|
||||||
|
else:
|
||||||
|
log.warning("Stripe has no color: id=%s", kwargs['sid'])
|
||||||
|
return JSONRPCError(-1009, "Internal Error")
|
||||||
|
|
||||||
|
|
||||||
@dispatcher.add_method
|
@dispatcher.add_method
|
||||||
@@ -331,7 +347,10 @@ def test_channel(**kwargs):
|
|||||||
""" :type : ledd.controller.Controller """
|
""" :type : ledd.controller.Controller """
|
||||||
|
|
||||||
if contr is not None:
|
if contr is not None:
|
||||||
contr.set_channel(kwargs['channel'], kwargs['value'], 2.8)
|
try:
|
||||||
|
contr.set_channel(kwargs['channel'], kwargs['value'], 2.8)
|
||||||
|
except OSError as e:
|
||||||
|
return JSONRPCError(-1009, "Internal Error", e)
|
||||||
else:
|
else:
|
||||||
return JSONRPCError(-1002, "Controller not found")
|
return JSONRPCError(-1002, "Controller not found")
|
||||||
|
|
||||||
@@ -373,16 +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:
|
||||||
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:
|
||||||
self.transport.write(JSONRPCResponseManager.handle(line, dispatcher).json.encode())
|
try:
|
||||||
|
self.transport.write(JSONRPCResponseManager.handle(line, dispatcher).json.encode())
|
||||||
|
except TypeError as te:
|
||||||
|
log.warning("Can't send response: %s", te)
|
||||||
|
|
||||||
def connection_lost(self, exc):
|
def connection_lost(self, exc):
|
||||||
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
|
||||||
|
Reference in New Issue
Block a user