Compare commits
7 Commits
0.90
...
f28cbb920d
Author | SHA1 | Date | |
---|---|---|---|
f28cbb920d
|
|||
ffb476c587
|
|||
5a2ad122f5
|
|||
2e149a730a | |||
b664c738ba | |||
8b8d49b2ee | |||
29f63cf09f |
134
README.md
134
README.md
@ -6,15 +6,141 @@ Pulses is a python module to drive LEDs on RPi using PWM (Pulse Width Modulation
|
||||
|
||||
1. Clone the repo
|
||||
|
||||
`git clone https://web.mistrali.pw/gitea/musicalbox/pulses.git`
|
||||
`https://gitea.mistrali.pw/musicalbox/pulses.git`
|
||||
|
||||
2. `apt-get install python3-venv`
|
||||
3. `pip install requirements-dev.txt`
|
||||
4. `./build.sh`
|
||||
5. `pip install dist/pulses-0.90-py3-none-any.whl`
|
||||
4. `poetry build`
|
||||
5. `pip install dist/pulses-<version>-py3-none-any.whl`
|
||||
|
||||
|
||||
### GPIO and PWM
|
||||
For more info on PWM refer to [PWM](https://en.wikipedia.org/wiki/Pulse-width_modulation) and for information on the different GPIO pin you can read [this](https://projects.raspberrypi.org/en/projects/physical-computing/1).
|
||||
|
||||
|
||||
### CLI tool
|
||||
|
||||
There is a CLI tool, called `pulses` that you can use to test loops and pulses.
|
||||
Run `pulses -h` to read the usage instructions.
|
||||
|
||||
### Work model
|
||||
|
||||
Each `ledPulse` object has some attributes that define the **pulse**, i.e. the light pattern of the managed LED.
|
||||
|
||||
The state of the LED is controlled by the following parameters:
|
||||
- **min**: minimum brightness value, I suggest to avoid using `0`, start from `2`;
|
||||
- **max**: maximum brightness;
|
||||
- **delay**: the base delay between each step of the pattern;
|
||||
- **initialMethod**: the method used to calculate the initial steps of the pattern;
|
||||
- **loopMethod**: the method used to calculate the steps of the main loop;
|
||||
- **finalMethod**: the method used to calculate the final steps of the loop;
|
||||
- **delayMethod**: the method used to calculate the delay between each step;
|
||||
|
||||
Each pattern starts running 50 steps of initial values, then goes into a repeteated loop, on exit it runs other 50 steps of final values.
|
||||
|
||||
To change the above parameters we use a FIFO queue, each time we `set` a new value of one of the above attributes, `ledPulse` will calculate a `tuple` of 3 values, each element of the tuple is in turn an array of 2-ples, each of these 2-ples has the first element as the `value` at a specific step, the second element is the `delay` at a specific step.
|
||||
|
||||
- `initialValues`: 50 elements, if an initialMethod is defined, empy otherwise. These are the values that define the pattern of the LED at the start of the new loop;
|
||||
- `loopValues`: 100 elements, never empy. These are the values that define the pattern of the LED at the core infinite loop;
|
||||
- `finalValues`: 50 elements, if a finalMethod is defined, empy otherwise. These are the values that define the pattern of the LED at the end of a terminating loop;
|
||||
|
||||
#### An example
|
||||
|
||||
Let's say we define a new pattern that has:
|
||||
- `initialMethod = linear`;
|
||||
- `loopMethod = cos`;
|
||||
- `finalMethod = linear`;
|
||||
- `delayMethod = constant`;
|
||||
- `delay = 0.01`;
|
||||
- `min = 2`;
|
||||
- `max = 50`;
|
||||
|
||||
The LED starts from brightness `0`, in 50 steps goes linearly to brightness `50`, i.e. the brightness will increate of 1 at each step, there is a delay of `0.01` seconds between each step.
|
||||
After these first 50 steps, there will be a repeating loop, looping on `cosine` values, so going from brightness 50 (value of max), down to brightness 2 (value of min), the up again to brightness 50, still with a constant delay of 0.01 between each step.
|
||||
The moment we will add a new tuple of values to the queue, using the `set()` method, the worker will exit from the repeating loop, run on 50 steps of the final values and then start with a new similar loop with the new values.
|
||||
|
||||
### Class
|
||||
|
||||
The only class defined is called `ledPulse`, it is derived from `threading.Thread`
|
||||
and takes care of managing the led connected to a PWM GPIO.
|
||||
|
||||
#### Methods
|
||||
`__init__`: quite simple, the only parameter is the GPIO pin we want to use. This method takes care of:
|
||||
- setting up logging;
|
||||
- setting up PWM;
|
||||
- installing the default methods for loops and delays;
|
||||
- set up the queue that we will use later on;
|
||||
|
||||
`set()`: change one or more parameter of the state. Bear in mind that each parameter is standalone, so if `min` is set to `2` and `max` is set to `20`, calling `led.set(max=40)` will only change the value of `max` parameter, leaving all the others at the previous value. After successfully setting a parameter, the values for `initial`, `loop`, `final` and `delay` are recalculated and a new tuple is added to the queue;
|
||||
|
||||
`run`: this method runs the main loop, that executes first a loop of 50 steps with `initialValues` if this is not empty, then loops on `loopValues` until there is a new tuple in the queue OR the `stop_event` event is set. If there is a new tuple in the queue, the infinite loop on `loopValues` is interrupted, another loop of 50 steps runs through `finalValues` (if not empty) and then we go to the main loop, pull the tuple from the queue and start all over, unless `stop_event` is set; if that's the case we bail out of the external loop and the thread is going to stop;
|
||||
|
||||
#### Plugins
|
||||
Pulses uses `plugin methods` to calculate the values and delays for each loop.
|
||||
These plugin methods are nothing more than python functions, following this prototypes:
|
||||
|
||||
- for value plugins:
|
||||
```python
|
||||
def value_methodname(obj, step):
|
||||
return <value for step>
|
||||
```
|
||||
|
||||
- for delay plugins:
|
||||
```python
|
||||
def delay_methodname(obj, step):
|
||||
return <delay value for step>
|
||||
```
|
||||
|
||||
`obj` is the LED object that uses the method, so you can refer to the attributes of if, like `obj.max` or `obj.min`.
|
||||
|
||||
Some examples, that are predefined in Pulses:
|
||||
|
||||
```python
|
||||
def delay_constant(obj, step):
|
||||
# Delay method: constant
|
||||
return obj.delay
|
||||
|
||||
def value_sin(obj, step):
|
||||
"""
|
||||
Value method: sin
|
||||
Sinusoidal values, 0-1-0 /\
|
||||
"""
|
||||
|
||||
delta = obj.max - obj.min
|
||||
radians = math.radians(1.8 * step)
|
||||
return delta * math.sin(radians) + obj.min
|
||||
|
||||
def value_on(obj, step):
|
||||
"""
|
||||
Value method: on
|
||||
Always on at "max" brightness
|
||||
"""
|
||||
|
||||
return obj.max
|
||||
```
|
||||
|
||||
You can write your own method plugins, trying to keep them quite simple possibly, then you have to register them before being able to use them.
|
||||
You can use two specific methods to register plugins based on their kind:
|
||||
|
||||
- `register_delay_method(methodName, function)`;
|
||||
- `register_value_method(methodName, function)`;
|
||||
|
||||
For example, we can write a function that returns a random value between `min` and `max`:
|
||||
|
||||
in `mymodule.py`:
|
||||
```python
|
||||
def value_random(obj, step):
|
||||
return random.randint(obj.min, obj.max)
|
||||
```
|
||||
|
||||
then we can register it
|
||||
|
||||
```python
|
||||
from pulses import ledPulse
|
||||
from mymodule import value_random
|
||||
|
||||
led = ledPulse(12)
|
||||
led.register_value_method('random', value_random)
|
||||
led.set(loopMethod='random')
|
||||
```
|
||||
|
||||
from now on we can call `led.set(loopValue='random')` and our LED will blink with a random value at each step.
|
||||
|
9
build.sh
9
build.sh
@ -1,9 +0,0 @@
|
||||
#!/bin/sh
|
||||
|
||||
if [ "$1" = "clean" ]; then
|
||||
rm -rf build
|
||||
rm -rf dist
|
||||
rm -rf *.egg-info
|
||||
fi
|
||||
|
||||
python -m build .
|
16
poetry.lock
generated
Normal file
16
poetry.lock
generated
Normal file
@ -0,0 +1,16 @@
|
||||
# This file is automatically @generated by Poetry 1.7.1 and should not be changed by hand.
|
||||
|
||||
[[package]]
|
||||
name = "docopt"
|
||||
version = "0.6.2"
|
||||
description = "Pythonic argument parser, that will make you smile"
|
||||
optional = false
|
||||
python-versions = "*"
|
||||
files = [
|
||||
{file = "docopt-0.6.2.tar.gz", hash = "sha256:49b3a825280bd66b3aa83585ef59c4a8c82f2c8a522dbe754a8bc8d08c85c491"},
|
||||
]
|
||||
|
||||
[metadata]
|
||||
lock-version = "2.0"
|
||||
python-versions = "^3.11"
|
||||
content-hash = "020d3e03335e70ca8b1cfd769838e50546a63b9ed4e4034ba584fd3a3ec497fd"
|
@ -30,7 +30,7 @@ import signal
|
||||
from docopt import docopt
|
||||
from pulses import VERSION, ledPulse
|
||||
|
||||
logging.basicConfig(format="%(name)s %(msg)s", stream=sys.stdout)
|
||||
logging.basicConfig(format="%(asctime)-15s %(levelname)5s %(name)s[%(process)d] %(threadName)s: %(message)s")
|
||||
|
||||
|
||||
def signal_handler(sig, frame):
|
||||
|
@ -54,9 +54,21 @@ def value_linear(obj, step):
|
||||
return delta * (100-step)/50 + obj.min
|
||||
|
||||
|
||||
def value_vshape(obj, step):
|
||||
"""
|
||||
V-shape value, 1-0-1
|
||||
"""
|
||||
|
||||
delta = obj.max - obj.min
|
||||
if step < 50:
|
||||
return delta * (100-step)/50 + obj.min
|
||||
else:
|
||||
return delta*step/50 + obj.min
|
||||
|
||||
|
||||
def value_sin(obj, step):
|
||||
"""
|
||||
Sinusoidal values, 0-1-0 /\
|
||||
Sinusoidal values, 0-1-0
|
||||
"""
|
||||
|
||||
delta = obj.max - obj.min
|
||||
@ -66,7 +78,7 @@ def value_sin(obj, step):
|
||||
|
||||
def value_cos(obj, step):
|
||||
"""
|
||||
Absolute Cosinusoidal values, 1-0-1 \\//
|
||||
Absolute Cosinusoidal values, 1-0-1
|
||||
"""
|
||||
|
||||
delta = obj.max - obj.min
|
||||
|
@ -10,7 +10,7 @@ from .methods import * # NOQA
|
||||
class ledPulse(threading.Thread):
|
||||
|
||||
delayMethods = ['constant', 'sin', 'cos']
|
||||
valueMethods = ['on', 'off', 'linear', 'sin', 'cos']
|
||||
valueMethods = ['on', 'off', 'linear', 'vshape', 'sin', 'cos']
|
||||
methods = {'delay': {}, 'value': {}}
|
||||
|
||||
defaultSet = {
|
||||
@ -25,11 +25,12 @@ class ledPulse(threading.Thread):
|
||||
|
||||
def __init__(self, gpio, name="led0"):
|
||||
|
||||
super().__init__(name=name,
|
||||
daemon=True)
|
||||
|
||||
self.log = logging.getLogger(self.__class__.__name__)
|
||||
self.gpio = gpio
|
||||
|
||||
super().__init__(name=name, daemon=True)
|
||||
|
||||
self.pwm_setup()
|
||||
|
||||
self.log.info(f"platform '{self.model}' " +
|
||||
@ -68,7 +69,7 @@ class ledPulse(threading.Thread):
|
||||
self.model = sys.platform
|
||||
try:
|
||||
with open('/sys/firmware/devicetree/base/model', 'r') as m:
|
||||
model = m.read()
|
||||
model = m.read().strip()
|
||||
if model.lower().startswith('raspberry pi'):
|
||||
self.supported = True
|
||||
self.model = model
|
||||
|
@ -1,6 +1,18 @@
|
||||
[build-system]
|
||||
requires = ["setuptools", "wheel"]
|
||||
build-backend = "setuptools.build_meta"
|
||||
[tool.poetry]
|
||||
name = "pulses"
|
||||
version = "1.0.0"
|
||||
description = "Pulse LEDs on RPi"
|
||||
authors = ["Andrea Mistrali <andrea@mistrali.pw>"]
|
||||
readme = "README.md"
|
||||
|
||||
[tool.pyright]
|
||||
reportMissingImports = "information"
|
||||
[tool.poetry.dependencies]
|
||||
python = "^3.11"
|
||||
docopt = "^0.6.2"
|
||||
|
||||
[tool.poetry.scripts]
|
||||
pulses = "pulses.cli:main"
|
||||
|
||||
|
||||
[build-system]
|
||||
requires = ["poetry-core"]
|
||||
build-backend = "poetry.core.masonry.api"
|
||||
|
21
setup.cfg
21
setup.cfg
@ -1,21 +0,0 @@
|
||||
[metadata]
|
||||
name = pulses
|
||||
version = 0.90
|
||||
author = Andrea Mistrali
|
||||
author_email = akelge@gmail.com
|
||||
platform = linux
|
||||
description = RPI led(s) and display brightness
|
||||
long_description = file: README.md
|
||||
keywords = graylog, py3
|
||||
license = BSD 3-Clause License
|
||||
|
||||
[options]
|
||||
packages = pulses
|
||||
python_requires = >=3
|
||||
include_package_data = True
|
||||
install_requires =
|
||||
docopt
|
||||
|
||||
[options.entry_points]
|
||||
console_scripts =
|
||||
pulses = pulses.cli:main
|
Reference in New Issue
Block a user