Added PCA9685 PWM driver plugin

This commit is contained in:
Fabio Manganiello 2021-05-16 17:28:18 +02:00
parent d7d5bcdd0c
commit acc4f1c0e3
5 changed files with 128 additions and 0 deletions

View file

@ -19,6 +19,8 @@ Given the high speed of development in the first phase, changes are being report
- Added generic joystick backend `backend.joystick.jstest` which uses `jstest` from the - Added generic joystick backend `backend.joystick.jstest` which uses `jstest` from the
standard `joystick` system package to read the state of joysticks not compatible with standard `joystick` system package to read the state of joysticks not compatible with
`python-inputs`. `python-inputs`.
- Added PWM PCA9685 plugin.
### Changed ### Changed

View file

View file

@ -0,0 +1,120 @@
import time
from typing import Optional, Dict
from platypush.plugins import Plugin, action
class PwmPca9685Plugin(Plugin):
"""
This plugin interacts with an `Adafruit PCA9685 <https://learn.adafruit.com/16-channel-pwm-servo-driver?view=all>`_
circuit, a 16-channel PWM servo driver. You can leverage this plugin to control servos and motors through Platypush.
Note that the driver for PCA9685 has been written for CircuitPython, and in order to use it on a Raspberry Pi or
similar devices you need to
`follow these steps <https://learn.adafruit.com/16-channel-pwm-servo-driver?view=all#python-circuitpython>`_::
# pip3 install --upgrade adafruit-python-shell
$ wget https://raw.githubusercontent.com/adafruit/Raspberry-Pi-Installer-Scripts/master/raspi-blinka.py
# python3 raspi-blinka.py
Then reboot and check that the following virtual devices are available::
- /dev/i2c-1
- /dev/spidev0.0
- /dev/spidev0.1
Finally, install the PCA9685 drivers::
# pip3 install --upgrade adafruit-circuitpython-pca9685
This plugin works with a PCA9685 circuit connected to the Platypush host over I2C interface.
"""
MIN_PWM_VALUE = 0
MAX_PWM_VALUE = 0xffff
def __init__(self, frequency: float, step_value: float = 0.1, step_duration: float = 0.05, **kwargs):
"""
:param frequency: Default PWM frequency to use for the driver, in Hz.
:param step_value: How much each channel value should be increased/decreased on each step.
:param step_duration: Length of each step transient when writing PWM values (default: 0.05 seconds).
"""
super().__init__(**kwargs)
self.frequency = frequency
self.step_value = step_value
self.step_duration = step_duration
self._pca = None
@staticmethod
def _convert_percent_to_duty_cycle(value: float) -> int:
"""
Convert a duty cycle percentage value to a PCA9685 value between 0 and `0xffff`.
:param value: Duty cycle value, between 0 and 1.
:return: Duty cycle 16-bit value, between 0 and `0xffff`.
"""
return int(value * 65535)
@action
def execute(self, channels: Dict[int, float], frequency: Optional[float] = None, step_value: Optional[float] = None,
step_duration: Optional[float] = None):
"""
Send PWM values to the specified channels.
:param channels: Map of the values to be written, as ``channel_index -> value``, where value is a real number
between 0.0 (minimum duty cycle) and 1.0 (maximum duty cycle).
:param frequency: Override default frequency.
:param step_value: Override default step value.
:param step_duration: Override default step duration.
"""
import busio
from board import SCL, SDA
from adafruit_pca9685 import PCA9685
values = {k: self._convert_percent_to_duty_cycle(v) for k, v in channels.items()}
step_value = self._convert_percent_to_duty_cycle(step_value if step_value is not None else self.step_value)
step_duration = step_duration if step_duration is not None else self.step_duration
i2c_bus = busio.I2C(SCL, SDA)
pca = self._pca or PCA9685(i2c_bus)
pca.frequency = frequency or self.frequency
self._pca = pca
done = False
while not done:
done = True
for channel, value in values.items():
if value == pca.channels[channel].duty_cycle:
continue
done = False
if value > pca.channels[channel].duty_cycle:
pca.channels[channel].duty_cycle = min(pca.channels[channel].duty_cycle + step_value,
self.MAX_PWM_VALUE)
else:
pca.channels[channel].duty_cycle = max(pca.channels[channel].duty_cycle - step_value,
self.MIN_PWM_VALUE)
time.sleep(step_duration)
@action
def deinit(self):
"""
De-initialize the PCA9685 if it was previously initialized.
"""
if not self._pca:
self.logger.warning('The PCA9685 driver is not initialized')
return
self._pca.deinit()
@action
def reset(self):
"""
Reset the PCA9685 (set all the channels to zero).
"""
if not self._pca:
self.logger.warning('The PCA9685 driver is not initialized')
return
self._pca.reset()

View file

@ -317,3 +317,7 @@ croniter
# Support for file.monitor backend # Support for file.monitor backend
#watchdog #watchdog
# Support for Adafruit PCA9685 PWM controller
#adafruit-python-shell
#adafruit-circuitpython-pca9685

View file

@ -249,5 +249,7 @@ setup(
'smartthings': ['pysmartthings', 'aiohttp'], 'smartthings': ['pysmartthings', 'aiohttp'],
# Support for file.monitor backend # Support for file.monitor backend
'filemonitor': ['watchdog'], 'filemonitor': ['watchdog'],
# Support for Adafruit PCA9685 PWM controller
'pca9685': ['adafruit-python-shell', 'adafruit-circuitpython-pca9685'],
}, },
) )