From 0b00f94b947523c6eb2ccb41b11a4aeb59231ece Mon Sep 17 00:00:00 2001 From: Alexander Myskin Date: Wed, 8 Nov 2017 00:49:56 +0300 Subject: [PATCH 1/6] logger, async context manager, read method --- apigpio/__init__.py | 2 +- apigpio/apigpio.py | 365 ++++++++++++++++++++++----------------- apigpio/utils.py | 35 +++- samples/gpio_debounce.py | 6 +- 4 files changed, 233 insertions(+), 175 deletions(-) diff --git a/apigpio/__init__.py b/apigpio/__init__.py index 97d061d..e938a25 100644 --- a/apigpio/__init__.py +++ b/apigpio/__init__.py @@ -1,3 +1,3 @@ from .ctes import * from .apigpio import Pi -from .utils import Debounce +from .utils import debounce diff --git a/apigpio/apigpio.py b/apigpio/apigpio.py index a30f1be..554f27a 100644 --- a/apigpio/apigpio.py +++ b/apigpio/apigpio.py @@ -1,12 +1,15 @@ import asyncio +import logging import socket import struct import sys -import functools + from .ctes import * exceptions = True +logger = logging.getLogger('apigpio') + # pigpio command numbers _PI_CMD_MODES = 0 _PI_CMD_MODEG = 1 @@ -130,130 +133,131 @@ # pigpio error text _errors = [ - [PI_INIT_FAILED, "pigpio initialisation failed"], - [PI_BAD_USER_GPIO, "gpio not 0-31"], - [PI_BAD_GPIO, "gpio not 0-53"], - [PI_BAD_MODE, "mode not 0-7"], - [PI_BAD_LEVEL, "level not 0-1"], - [PI_BAD_PUD, "pud not 0-2"], - [PI_BAD_PULSEWIDTH, "pulsewidth not 0 or 500-2500"], - [PI_BAD_DUTYCYCLE, "dutycycle not 0-range (default 255)"], - [PI_BAD_TIMER, "timer not 0-9"], - [PI_BAD_MS, "ms not 10-60000"], - [PI_BAD_TIMETYPE, "timetype not 0-1"], - [PI_BAD_SECONDS, "seconds < 0"], - [PI_BAD_MICROS, "micros not 0-999999"], - [PI_TIMER_FAILED, "gpioSetTimerFunc failed"], - [PI_BAD_WDOG_TIMEOUT, "timeout not 0-60000"], - [PI_NO_ALERT_FUNC, "DEPRECATED"], - [PI_BAD_CLK_PERIPH, "clock peripheral not 0-1"], - [PI_BAD_CLK_SOURCE, "DEPRECATED"], - [PI_BAD_CLK_MICROS, "clock micros not 1, 2, 4, 5, 8, or 10"], - [PI_BAD_BUF_MILLIS, "buf millis not 100-10000"], - [PI_BAD_DUTYRANGE, "dutycycle range not 25-40000"], - [PI_BAD_SIGNUM, "signum not 0-63"], - [PI_BAD_PATHNAME, "can't open pathname"], - [PI_NO_HANDLE, "no handle available"], - [PI_BAD_HANDLE, "unknown handle"], - [PI_BAD_IF_FLAGS, "ifFlags > 3"], - [PI_BAD_CHANNEL, "DMA channel not 0-14"], - [PI_BAD_SOCKET_PORT, "socket port not 1024-30000"], - [PI_BAD_FIFO_COMMAND, "unknown fifo command"], - [PI_BAD_SECO_CHANNEL, "DMA secondary channel not 0-6"], - [PI_NOT_INITIALISED, "function called before gpioInitialise"], - [PI_INITIALISED, "function called after gpioInitialise"], - [PI_BAD_WAVE_MODE, "waveform mode not 0-1"], - [PI_BAD_CFG_INTERNAL, "bad parameter in gpioCfgInternals call"], - [PI_BAD_WAVE_BAUD, "baud rate not 50-250000(RX)/1000000(TX)"], - [PI_TOO_MANY_PULSES, "waveform has too many pulses"], - [PI_TOO_MANY_CHARS, "waveform has too many chars"], - [PI_NOT_SERIAL_GPIO, "no bit bang serial read in progress on gpio"], - [PI_NOT_PERMITTED, "no permission to update gpio"], - [PI_SOME_PERMITTED, "no permission to update one or more gpios"], - [PI_BAD_WVSC_COMMND, "bad WVSC subcommand"], - [PI_BAD_WVSM_COMMND, "bad WVSM subcommand"], - [PI_BAD_WVSP_COMMND, "bad WVSP subcommand"], - [PI_BAD_PULSELEN, "trigger pulse length not 1-100"], - [PI_BAD_SCRIPT, "invalid script"], - [PI_BAD_SCRIPT_ID, "unknown script id"], - [PI_BAD_SER_OFFSET, "add serial data offset > 30 minute"], - [PI_GPIO_IN_USE, "gpio already in use"], - [PI_BAD_SERIAL_COUNT, "must read at least a byte at a time"], - [PI_BAD_PARAM_NUM, "script parameter id not 0-9"], - [PI_DUP_TAG, "script has duplicate tag"], - [PI_TOO_MANY_TAGS, "script has too many tags"], - [PI_BAD_SCRIPT_CMD, "illegal script command"], - [PI_BAD_VAR_NUM, "script variable id not 0-149"], - [PI_NO_SCRIPT_ROOM, "no more room for scripts"], - [PI_NO_MEMORY, "can't allocate temporary memory"], - [PI_SOCK_READ_FAILED, "socket read failed"], - [PI_SOCK_WRIT_FAILED, "socket write failed"], - [PI_TOO_MANY_PARAM, "too many script parameters (> 10)"], - [PI_NOT_HALTED, "script already running or failed"], - [PI_BAD_TAG, "script has unresolved tag"], - [PI_BAD_MICS_DELAY, "bad MICS delay (too large)"], - [PI_BAD_MILS_DELAY, "bad MILS delay (too large)"], - [PI_BAD_WAVE_ID, "non existent wave id"], - [PI_TOO_MANY_CBS, "No more CBs for waveform"], - [PI_TOO_MANY_OOL, "No more OOL for waveform"], - [PI_EMPTY_WAVEFORM, "attempt to create an empty waveform"], - [PI_NO_WAVEFORM_ID, "No more waveform ids"], - [PI_I2C_OPEN_FAILED, "can't open I2C device"], - [PI_SER_OPEN_FAILED, "can't open serial device"], - [PI_SPI_OPEN_FAILED, "can't open SPI device"], - [PI_BAD_I2C_BUS, "bad I2C bus"], - [PI_BAD_I2C_ADDR, "bad I2C address"], - [PI_BAD_SPI_CHANNEL, "bad SPI channel"], - [PI_BAD_FLAGS, "bad i2c/spi/ser open flags"], - [PI_BAD_SPI_SPEED, "bad SPI speed"], - [PI_BAD_SER_DEVICE, "bad serial device name"], - [PI_BAD_SER_SPEED, "bad serial baud rate"], - [PI_BAD_PARAM, "bad i2c/spi/ser parameter"], - [PI_I2C_WRITE_FAILED, "I2C write failed"], - [PI_I2C_READ_FAILED, "I2C read failed"], - [PI_BAD_SPI_COUNT, "bad SPI count"], - [PI_SER_WRITE_FAILED, "ser write failed"], - [PI_SER_READ_FAILED, "ser read failed"], - [PI_SER_READ_NO_DATA, "ser read no data available"], - [PI_UNKNOWN_COMMAND, "unknown command"], - [PI_SPI_XFER_FAILED, "SPI xfer/read/write failed"], - [PI_BAD_POINTER, "bad (NULL) pointer"], - [PI_NO_AUX_SPI, "need a A+/B+/Pi2 for auxiliary SPI"], - [PI_NOT_PWM_GPIO, "gpio is not in use for PWM"], - [PI_NOT_SERVO_GPIO, "gpio is not in use for servo pulses"], - [PI_NOT_HCLK_GPIO, "gpio has no hardware clock"], - [PI_NOT_HPWM_GPIO, "gpio has no hardware PWM"], - [PI_BAD_HPWM_FREQ, "hardware PWM frequency not 1-125M"], - [PI_BAD_HPWM_DUTY, "hardware PWM dutycycle not 0-1M"], - [PI_BAD_HCLK_FREQ, "hardware clock frequency not 4689-250M"], - [PI_BAD_HCLK_PASS, "need password to use hardware clock 1"], - [PI_HPWM_ILLEGAL, "illegal, PWM in use for main clock"], - [PI_BAD_DATABITS, "serial data bits not 1-32"], - [PI_BAD_STOPBITS, "serial (half) stop bits not 2-8"], - [PI_MSG_TOOBIG, "socket/pipe message too big"], - [PI_BAD_MALLOC_MODE, "bad memory allocation mode"], - [PI_TOO_MANY_SEGS, "too many I2C transaction segments"], - [PI_BAD_I2C_SEG, "an I2C transaction segment failed"], - [PI_BAD_SMBUS_CMD, "SMBus command not supported"], - [PI_NOT_I2C_GPIO, "no bit bang I2C in progress on gpio"], - [PI_BAD_I2C_WLEN, "bad I2C write length"], - [PI_BAD_I2C_RLEN, "bad I2C read length"], - [PI_BAD_I2C_CMD, "bad I2C command"], - [PI_BAD_I2C_BAUD, "bad I2C baud rate, not 50-500k"], - [PI_CHAIN_LOOP_CNT, "bad chain loop count"], - [PI_BAD_CHAIN_LOOP, "empty chain loop"], - [PI_CHAIN_COUNTER, "too many chain counters"], - [PI_BAD_CHAIN_CMD, "bad chain command"], - [PI_BAD_CHAIN_DELAY, "bad chain delay micros"], - [PI_CHAIN_NESTING, "chain counters nested too deeply"], - [PI_CHAIN_TOO_BIG, "chain is too long"], - [PI_DEPRECATED, "deprecated function removed"], - [PI_BAD_SER_INVERT, "bit bang serial invert not 0 or 1"], + [PI_INIT_FAILED, "pigpio initialisation failed"], + [PI_BAD_USER_GPIO, "gpio not 0-31"], + [PI_BAD_GPIO, "gpio not 0-53"], + [PI_BAD_MODE, "mode not 0-7"], + [PI_BAD_LEVEL, "level not 0-1"], + [PI_BAD_PUD, "pud not 0-2"], + [PI_BAD_PULSEWIDTH, "pulsewidth not 0 or 500-2500"], + [PI_BAD_DUTYCYCLE, "dutycycle not 0-range (default 255)"], + [PI_BAD_TIMER, "timer not 0-9"], + [PI_BAD_MS, "ms not 10-60000"], + [PI_BAD_TIMETYPE, "timetype not 0-1"], + [PI_BAD_SECONDS, "seconds < 0"], + [PI_BAD_MICROS, "micros not 0-999999"], + [PI_TIMER_FAILED, "gpioSetTimerFunc failed"], + [PI_BAD_WDOG_TIMEOUT, "timeout not 0-60000"], + [PI_NO_ALERT_FUNC, "DEPRECATED"], + [PI_BAD_CLK_PERIPH, "clock peripheral not 0-1"], + [PI_BAD_CLK_SOURCE, "DEPRECATED"], + [PI_BAD_CLK_MICROS, "clock micros not 1, 2, 4, 5, 8, or 10"], + [PI_BAD_BUF_MILLIS, "buf millis not 100-10000"], + [PI_BAD_DUTYRANGE, "dutycycle range not 25-40000"], + [PI_BAD_SIGNUM, "signum not 0-63"], + [PI_BAD_PATHNAME, "can't open pathname"], + [PI_NO_HANDLE, "no handle available"], + [PI_BAD_HANDLE, "unknown handle"], + [PI_BAD_IF_FLAGS, "ifFlags > 3"], + [PI_BAD_CHANNEL, "DMA channel not 0-14"], + [PI_BAD_SOCKET_PORT, "socket port not 1024-30000"], + [PI_BAD_FIFO_COMMAND, "unknown fifo command"], + [PI_BAD_SECO_CHANNEL, "DMA secondary channel not 0-6"], + [PI_NOT_INITIALISED, "function called before gpioInitialise"], + [PI_INITIALISED, "function called after gpioInitialise"], + [PI_BAD_WAVE_MODE, "waveform mode not 0-1"], + [PI_BAD_CFG_INTERNAL, "bad parameter in gpioCfgInternals call"], + [PI_BAD_WAVE_BAUD, "baud rate not 50-250000(RX)/1000000(TX)"], + [PI_TOO_MANY_PULSES, "waveform has too many pulses"], + [PI_TOO_MANY_CHARS, "waveform has too many chars"], + [PI_NOT_SERIAL_GPIO, "no bit bang serial read in progress on gpio"], + [PI_NOT_PERMITTED, "no permission to update gpio"], + [PI_SOME_PERMITTED, "no permission to update one or more gpios"], + [PI_BAD_WVSC_COMMND, "bad WVSC subcommand"], + [PI_BAD_WVSM_COMMND, "bad WVSM subcommand"], + [PI_BAD_WVSP_COMMND, "bad WVSP subcommand"], + [PI_BAD_PULSELEN, "trigger pulse length not 1-100"], + [PI_BAD_SCRIPT, "invalid script"], + [PI_BAD_SCRIPT_ID, "unknown script id"], + [PI_BAD_SER_OFFSET, "add serial data offset > 30 minute"], + [PI_GPIO_IN_USE, "gpio already in use"], + [PI_BAD_SERIAL_COUNT, "must read at least a byte at a time"], + [PI_BAD_PARAM_NUM, "script parameter id not 0-9"], + [PI_DUP_TAG, "script has duplicate tag"], + [PI_TOO_MANY_TAGS, "script has too many tags"], + [PI_BAD_SCRIPT_CMD, "illegal script command"], + [PI_BAD_VAR_NUM, "script variable id not 0-149"], + [PI_NO_SCRIPT_ROOM, "no more room for scripts"], + [PI_NO_MEMORY, "can't allocate temporary memory"], + [PI_SOCK_READ_FAILED, "socket read failed"], + [PI_SOCK_WRIT_FAILED, "socket write failed"], + [PI_TOO_MANY_PARAM, "too many script parameters (> 10)"], + [PI_NOT_HALTED, "script already running or failed"], + [PI_BAD_TAG, "script has unresolved tag"], + [PI_BAD_MICS_DELAY, "bad MICS delay (too large)"], + [PI_BAD_MILS_DELAY, "bad MILS delay (too large)"], + [PI_BAD_WAVE_ID, "non existent wave id"], + [PI_TOO_MANY_CBS, "No more CBs for waveform"], + [PI_TOO_MANY_OOL, "No more OOL for waveform"], + [PI_EMPTY_WAVEFORM, "attempt to create an empty waveform"], + [PI_NO_WAVEFORM_ID, "No more waveform ids"], + [PI_I2C_OPEN_FAILED, "can't open I2C device"], + [PI_SER_OPEN_FAILED, "can't open serial device"], + [PI_SPI_OPEN_FAILED, "can't open SPI device"], + [PI_BAD_I2C_BUS, "bad I2C bus"], + [PI_BAD_I2C_ADDR, "bad I2C address"], + [PI_BAD_SPI_CHANNEL, "bad SPI channel"], + [PI_BAD_FLAGS, "bad i2c/spi/ser open flags"], + [PI_BAD_SPI_SPEED, "bad SPI speed"], + [PI_BAD_SER_DEVICE, "bad serial device name"], + [PI_BAD_SER_SPEED, "bad serial baud rate"], + [PI_BAD_PARAM, "bad i2c/spi/ser parameter"], + [PI_I2C_WRITE_FAILED, "I2C write failed"], + [PI_I2C_READ_FAILED, "I2C read failed"], + [PI_BAD_SPI_COUNT, "bad SPI count"], + [PI_SER_WRITE_FAILED, "ser write failed"], + [PI_SER_READ_FAILED, "ser read failed"], + [PI_SER_READ_NO_DATA, "ser read no data available"], + [PI_UNKNOWN_COMMAND, "unknown command"], + [PI_SPI_XFER_FAILED, "SPI xfer/read/write failed"], + [PI_BAD_POINTER, "bad (NULL) pointer"], + [PI_NO_AUX_SPI, "need a A+/B+/Pi2 for auxiliary SPI"], + [PI_NOT_PWM_GPIO, "gpio is not in use for PWM"], + [PI_NOT_SERVO_GPIO, "gpio is not in use for servo pulses"], + [PI_NOT_HCLK_GPIO, "gpio has no hardware clock"], + [PI_NOT_HPWM_GPIO, "gpio has no hardware PWM"], + [PI_BAD_HPWM_FREQ, "hardware PWM frequency not 1-125M"], + [PI_BAD_HPWM_DUTY, "hardware PWM dutycycle not 0-1M"], + [PI_BAD_HCLK_FREQ, "hardware clock frequency not 4689-250M"], + [PI_BAD_HCLK_PASS, "need password to use hardware clock 1"], + [PI_HPWM_ILLEGAL, "illegal, PWM in use for main clock"], + [PI_BAD_DATABITS, "serial data bits not 1-32"], + [PI_BAD_STOPBITS, "serial (half) stop bits not 2-8"], + [PI_MSG_TOOBIG, "socket/pipe message too big"], + [PI_BAD_MALLOC_MODE, "bad memory allocation mode"], + [PI_TOO_MANY_SEGS, "too many I2C transaction segments"], + [PI_BAD_I2C_SEG, "an I2C transaction segment failed"], + [PI_BAD_SMBUS_CMD, "SMBus command not supported"], + [PI_NOT_I2C_GPIO, "no bit bang I2C in progress on gpio"], + [PI_BAD_I2C_WLEN, "bad I2C write length"], + [PI_BAD_I2C_RLEN, "bad I2C read length"], + [PI_BAD_I2C_CMD, "bad I2C command"], + [PI_BAD_I2C_BAUD, "bad I2C baud rate, not 50-500k"], + [PI_CHAIN_LOOP_CNT, "bad chain loop count"], + [PI_BAD_CHAIN_LOOP, "empty chain loop"], + [PI_CHAIN_COUNTER, "too many chain counters"], + [PI_BAD_CHAIN_CMD, "bad chain command"], + [PI_BAD_CHAIN_DELAY, "bad chain delay micros"], + [PI_CHAIN_NESTING, "chain counters nested too deeply"], + [PI_CHAIN_TOO_BIG, "chain is too long"], + [PI_DEPRECATED, "deprecated function removed"], + [PI_BAD_SER_INVERT, "bit bang serial invert not 0 or 1"], ] class ApigpioError(Exception): """pigpio module exception""" + def __init__(self, value): self.value = value @@ -268,7 +272,7 @@ def error_text(errnum): errnum:= <0, the error number ... - print(pigpio.error_text(-5)) + logger.debug(pigpio.error_text(-5)) level not 0-1 ... """ @@ -305,9 +309,9 @@ def u2i(uint32): uint32:= an unsigned 32 bit number ... - print(u2i(4294967272)) + logger.debug(u2i(4294967272)) -24 - print(u2i(37)) + logger.debug(u2i(37)) 37 ... """ @@ -355,7 +359,8 @@ def _f(*args, **kwargs): try: self._func(*args, **kwargs) except Exception as e: - print('Exception raised when running callback {}'.format(e)) + logger.debug('Exception raised when running callback {}'.format(e)) + return _f @@ -405,7 +410,7 @@ def _wait_for_notif(self): while True: MSG_SIZ = 12 f_recv = self._loop.sock_recv(self.s, MSG_SIZ) - done, pending = yield from asyncio.\ + done, pending = yield from asyncio. \ wait([f_recv, self.f_stop], return_when=asyncio.FIRST_COMPLETED) if self.f_stop in done: @@ -415,7 +420,7 @@ def _wait_for_notif(self): # buf = yield from self._loop.sock_recv(self.s, MSG_SIZ) while len(buf) < MSG_SIZ: - yield from self._loop.sock_recv(self.s, MSG_SIZ-len(buf)) + yield from self._loop.sock_recv(self.s, MSG_SIZ - len(buf)) seq, flags, tick, level = (struct.unpack('HHII', buf)) if flags == 0: @@ -430,19 +435,19 @@ def _wait_for_notif(self): cb.func(cb.gpio, new_level, tick) else: if flags & NTFY_FLAGS_WDOG: - print('watchdog signal') + logger.debug('watchdog signal') gpio = flags & NTFY_FLAGS_GPIO for cb in self.callbacks: if cb.gpio == gpio: cb.func(cb.gpio, TIMEOUT, tick) if flags & NTFY_FLAGS_ALIVE: - print('keep alive signal') - # no event for now - # elif flags & NTFY_FLAGS_EVENT: - # event = flags & NTFY_FLAGS_GPIO - # for cb in self.events: - # if cb.event == event: - # cb.func(event, tick) + logger.debug('keep alive signal') + # no event for now + # elif flags & NTFY_FLAGS_EVENT: + # event = flags & NTFY_FLAGS_GPIO + # for cb in self.events: + # if cb.event == event: + # cb.func(event, tick) self.s.close() self.f_stopped.set_result(True) @@ -456,7 +461,7 @@ def append(self, cb): self.monitor) @asyncio.coroutine - def _pigpio_aio_command(self, cmd, p1, p2,): + def _pigpio_aio_command(self, cmd, p1, p2, ): # FIXME: duplication with pi._pigpio_aio_command data = struct.pack('IIII', cmd, p1, p2, 0) self._loop.sock_sendall(self.s, data) @@ -498,10 +503,28 @@ def tally(self): return self.count -class Pi(object): +class Pi: + """ + Usage: + >>> address = ('192.168.1.10', 8888) + >>> async with Pi(address) as pi: + .... await pi.get_version() + """ + + def __init__(self, address, loop=None): + """ + :param address: a pair (address, port), the address must be already + """ + self.address = address + if loop is None: + loop = asyncio.get_event_loop() + self._loop = loop + self.s = None + self._notify = _callback_handler(self) + self._lock = asyncio.Lock() @asyncio.coroutine - def _pigpio_aio_command(self, cmd, p1, p2,): + def _pigpio_aio_command(self, cmd, p1=0, p2=0): """ Runs a pigpio socket command. @@ -512,7 +535,7 @@ def _pigpio_aio_command(self, cmd, p1, p2,): """ with (yield from self._lock): data = struct.pack('IIII', cmd, p1, p2, 0) - self._loop.sock_sendall(self.s, data) + yield from self._loop.sock_sendall(self.s, data) response = yield from self._loop.sock_recv(self.s, 16) _, res = struct.unpack('12sI', response) return res @@ -536,16 +559,15 @@ def _pigpio_aio_command_ext(self, cmd, p1, p2, p3, extents, rl=True): ext.extend(_b(x)) else: ext.extend(x) - self._loop.sock_sendall(self.s, ext) + yield from self._loop.sock_sendall(self.s, ext) response = yield from self._loop.sock_recv(self.s, 16) _, res = struct.unpack('12sI', response) return res @asyncio.coroutine - def connect(self, address): + def connect(self): """ Connect to a remote or local gpiod daemon. - :param address: a pair (address, port), the address must be already resolved (for example an ip address) :return: """ @@ -554,25 +576,31 @@ def connect(self, address): # Disable the Nagle algorithm. self.s.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1) - yield from self._loop.sock_connect(self.s, address) + yield from self._loop.sock_connect(self.s, self.address) - yield from self._notify._connect(address) + yield from self._notify._connect(self.address) @asyncio.coroutine def stop(self): """ - :return: """ - print('closing notifier') + logger.debug('closing notifier') yield from self._notify.close() - print('closing socket') + logger.debug('closing socket') self.s.close() + async def __aenter__(self): + await self.connect() + return self + + async def __aexit__(self, exc_type, exc_val, exc_tb): + await self.stop() + @asyncio.coroutine def get_version(self): res = yield from self._pigpio_aio_command(_PI_CMD_PIGPV) - print('version: {}'.format(res)) + return res @asyncio.coroutine def get_pigpio_version(self): @@ -583,7 +611,8 @@ def get_pigpio_version(self): v = pi.get_pigpio_version() ... """ - res = yield from self._pigpio_aio_command(_PI_CMD_PIGPV, 0, 0) + res = yield from self._pigpio_aio_command(_PI_CMD_PIGPV) + return res @asyncio.coroutine def store_script(self, script): @@ -676,7 +705,7 @@ def script_status(self, script_id): # data = self._rxbuf(bytes) data = yield from self._loop.sock_recv(self.s, bytes) while len(data) < bytes: - b = yield from self._loop.sock_recv(self.s, bytes-len(data)) + b = yield from self._loop.sock_recv(self.s, bytes - len(data)) data.extend(b) pars = struct.unpack('11i', _str(data)) @@ -724,7 +753,7 @@ def read_bank_1(self): gpio is high. Gpio n has bit value (1< tick: - delay = max_tick-self.last + tick + delay = max_tick - self.last + tick else: delay = tick - self.last + if delay > threshold: self._fn(*args, **kwargs) - print('call passed by debouncer {} {} {}' - .format(tick, self.last, threshold)) + logger.debug('call passed by debouncer {} {} {}'.format(tick, self.last, threshold)) self.last = tick else: - print('call filtered out by debouncer {} {} {}' - .format(tick, self.last, threshold)) + logger.debug('call filtered out by debouncer {} {} {}'.format(tick, self.last, threshold)) def __get__(self, instance, type=None): # with is called when an instance of `_decorated` is used as a class @@ -54,3 +57,19 @@ def __get__(self, instance, type=None): return functools.partial(self, instance) return _decorated + + +def tick_diff(t1, t2): + """ + Returns the microsecond difference between two ticks. + + t1:= the earlier tick + t2:= the later tick + ... + >>> tick_diff(4294967272, 12) + ... 36 + """ + diff = t2 - t1 + if diff < 0: + diff += (1 << 32) + return diff diff --git a/samples/gpio_debounce.py b/samples/gpio_debounce.py index 4cab2c9..397d9c0 100644 --- a/samples/gpio_debounce.py +++ b/samples/gpio_debounce.py @@ -3,7 +3,7 @@ import functools # This sample demonstrates both writing to gpio and listening to gpio changes. -# It also shows the Debounce decorator, which might be useful when registering +# It also shows the debounce decorator, which might be useful when registering # a callback for a gpio connected to a button, for example. BT_GPIO = 18 @@ -45,10 +45,10 @@ def toggle(self): self.blink = False # The DeBounce can simply be applied to your callback. -# Optionnally, the threshold can be specified in milliseconds : @Debounce(200) +# Optionnally, the threshold can be specified in milliseconds : @debounce(200) -@apigpio.Debounce() +@apigpio.debounce() def on_bt(gpio, level, tick, blinker=None): print('on_input {} {} {}'.format(gpio, level, tick)) blinker.toggle() From 86ee647868eeb5f96283a3fa7d7c1576a4f3dfc7 Mon Sep 17 00:00:00 2001 From: Alexander Myskin Date: Wed, 8 Nov 2017 00:57:25 +0300 Subject: [PATCH 2/6] set_watchdog method --- apigpio/apigpio.py | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/apigpio/apigpio.py b/apigpio/apigpio.py index 554f27a..b636f8a 100644 --- a/apigpio/apigpio.py +++ b/apigpio/apigpio.py @@ -900,6 +900,32 @@ def read(self, gpio): res = yield from self._pigpio_aio_command(_PI_CMD_READ, gpio, 0) return _u2i(res) + @asyncio.coroutine + def set_watchdog(self, user_gpio, wdog_timeout): + """ + Sets a watchdog timeout for a GPIO. + user_gpio:= 0-31. + wdog_timeout:= 0-60000. + + The watchdog is nominally in milliseconds. + + Only one watchdog may be registered per GPIO. + + The watchdog may be cancelled by setting timeout to 0. + + Once a watchdog has been started callbacks for the GPIO + will be triggered every timeout interval after the last + GPIO activity. + + The callback will receive the special level TIMEOUT. + ... + yield from pi.set_watchdog(23, 1000) # 1000 ms watchdog on GPIO 23 + yield from pi.set_watchdog(23, 0) # cancel watchdog on GPIO 23 + ... + """ + res = yield from self._pigpio_aio_command(_PI_CMD_WDOG, user_gpio, int(wdog_timeout)) + return _u2i(res) + @asyncio.coroutine def gpio_trigger(self, user_gpio, pulse_len=10, level=1): """ From bd7c1f633f266712c61f2dd32c18e60fa68c6343 Mon Sep 17 00:00:00 2001 From: Alexander Myskin Date: Wed, 8 Nov 2017 01:18:32 +0300 Subject: [PATCH 3/6] remove callback method --- apigpio/__init__.py | 2 +- apigpio/apigpio.py | 13 +++++++++++++ 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/apigpio/__init__.py b/apigpio/__init__.py index e938a25..7400d45 100644 --- a/apigpio/__init__.py +++ b/apigpio/__init__.py @@ -1,3 +1,3 @@ from .ctes import * from .apigpio import Pi -from .utils import debounce +from .utils import debounce, tick_diff diff --git a/apigpio/apigpio.py b/apigpio/apigpio.py index b636f8a..105ef9c 100644 --- a/apigpio/apigpio.py +++ b/apigpio/apigpio.py @@ -460,6 +460,19 @@ def append(self, cb): yield from self.pi._pigpio_aio_command(_PI_CMD_NB, self.handle, self.monitor) + @asyncio.coroutine + def remove(self, cb): + """Removes a callback.""" + if cb in self.callbacks: + self.callbacks.remove(cb) + new_monitor = 0 + for c in self.callbacks: + new_monitor |= c.bit + if new_monitor != self.monitor: + self.monitor = new_monitor + yield from self.pi._pigpio_aio_command( + _PI_CMD_NB, self.handle, self.monitor) + @asyncio.coroutine def _pigpio_aio_command(self, cmd, p1, p2, ): # FIXME: duplication with pi._pigpio_aio_command From 1db4774f20058ed3d0420fcb461f76db043ac026 Mon Sep 17 00:00:00 2001 From: Alexander Myskin Date: Wed, 8 Nov 2017 01:25:35 +0300 Subject: [PATCH 4/6] dht11 sample --- samples/gpio_dht11.py | 197 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 197 insertions(+) create mode 100644 samples/gpio_dht11.py diff --git a/samples/gpio_dht11.py b/samples/gpio_dht11.py new file mode 100644 index 0000000..ba8be6e --- /dev/null +++ b/samples/gpio_dht11.py @@ -0,0 +1,197 @@ +import asyncio +from collections import namedtuple + +import apigpio + + +class DHT11Result(namedtuple('DHT11Result', 'error_code temperature humidity')): + ERR_NO_ERROR = 0 + ERR_MISSING_DATA = 1 + ERR_CRC = 2 + + def __str__(self): + return 'Temperature: {}°C, Humidity {} %'.format(self.temperature, self.humidity) + + def is_valid(self): + return self.error_code == DHT11Result.ERR_NO_ERROR + + +class DHT11: + """ + Async version of DHT11 class made by joan2937 + - https://github.com/joan2937/pigpio/blob/master/EXAMPLES/Python/DHT11_SENSOR/dht11.py + + example code: + >>> address = ('192.168.1.10', 8888) + >>> gpio = 4 + >>> async with apigpio.Pi(address) as pi: + .... async with DHT11(pi, gpio) as sensor: # 4 is the data GPIO pin connected to your sensor + .... async for result in sensor: + .... if result.is_valid(): + .... print(result) + """ + + def __init__(self, pi, gpio): + """ + :type pi: apigpio.Pi + :type gpio: int + """ + self.temperature = self.humidity = 0 + self._pi = pi + self._gpio = gpio + self._high_tick = 0 + self._bit = 40 + self._either_edge_cb = None + self._loop = asyncio.get_event_loop() + self._error = DHT11Result.ERR_NO_ERROR + + async def connect(self): + """ + Clears the internal gpio pull-up/down resistor. + Kills any watchdogs. + """ + await self._pi.set_pull_up_down(self._gpio, apigpio.PUD_OFF) + await self._pi.set_watchdog(self._gpio, 0) + await self.register_callbacks() + + async def register_callbacks(self): + """ + Monitors RISING_EDGE changes using callback. + """ + self._either_edge_cb = await self._pi.add_callback( + self._gpio, + apigpio.EITHER_EDGE, + self.either_edge_callback + ) + + def either_edge_callback(self, gpio, level, tick): + """ + Either Edge callbacks, called each time the gpio edge changes. + Accumulate the 40 data bits from the dht11 sensor. + """ + level_handlers = { + apigpio.FALLING_EDGE: self._edge_FALL, + apigpio.RISING_EDGE: self._edge_RISE, + apigpio.EITHER_EDGE: self._edge_EITHER + } + handler = level_handlers[level] + diff = apigpio.tick_diff(self._high_tick, tick) + handler(tick, diff) + + def _edge_RISE(self, tick, diff): + """ + Handle Rise signal. + """ + val = 0 + if diff >= 50: + val = 1 + if diff >= 200: # Bad bit? + self.checksum = 256 # Force bad checksum + + if self._bit >= 40: # Message complete + self._bit = 40 + + elif self._bit >= 32: # In checksum byte + self.checksum = (self.checksum << 1) + val + if self._bit == 39: + # 40th bit received + loop.create_task(self._pi.set_watchdog(self._gpio, 0)) + total = self.humidity + self.temperature + # is checksum ok ? + if not (total & 255) == self.checksum: + self._error = DHT11Result.ERR_CRC + return + + elif 16 <= self._bit < 24: # in temperature byte + self.temperature = (self.temperature << 1) + val + self._error = DHT11Result.ERR_NO_ERROR + + elif 0 <= self._bit < 8: # in humidity byte + self.humidity = (self.humidity << 1) + val + self._error = DHT11Result.ERR_NO_ERROR + + else: # skip header bits + pass + + self._bit += 1 + + def _edge_FALL(self, tick, diff): + """ + Handle Fall signal. + """ + self._high_tick = tick + if diff <= 250000: + return + self._bit = -2 + self.checksum = 0 + self.temperature = 0 + self.humidity = 0 + + def _edge_EITHER(self, tick, diff): + """ + Handle Either signal. + """ + loop.create_task(self._pi.set_watchdog(self._gpio, 0)) + + async def read(self): + """ + Start reading over DHT11 sensor. + """ + await self._pi.write(self._gpio, apigpio.LOW) + await asyncio.sleep(0.017) # 17 ms + await self._pi.set_mode(self._gpio, apigpio.INPUT) + await self._pi.set_watchdog(self._gpio, 200) + await asyncio.sleep(0.2) + + async def close(self): + """ + Stop reading sensor, remove callbacks. + """ + await self._pi.set_watchdog(self._gpio, 0) + if self._either_edge_cb: + self._either_edge_cb.cancel() + self._either_edge_cb = None + + async def __aenter__(self): + await self.connect() + return self + + async def __aexit__(self, exc_type, exc_val, exc_tb): + await self.close() + + async def __aiter__(self): + """ + Support the iterator protocol. + """ + return self + + async def __anext__(self): + """ + Call the read method and return temperature and humidity informations. + """ + await asyncio.sleep(1) + await self.read() + return DHT11Result(error_code=self._error, + temperature=self.temperature, + humidity=self.humidity) + + +async def main(max_count=1): + address = ('192.168.1.6', 8888) + gpio = 4 + count = 0 + + async with apigpio.Pi(address) as pi: + async with DHT11(pi, gpio) as sensor: + async for result in sensor: + if result.is_valid(): + print(result) + count += 1 + + if count >= max_count: + break + + +if __name__ == '__main__': + loop = asyncio.get_event_loop() + loop.run_until_complete(main()) From 2df1aafff4a3e274c35bc6509afd06d28b08bc13 Mon Sep 17 00:00:00 2001 From: Alexander Myskin Date: Wed, 8 Nov 2017 21:47:13 +0300 Subject: [PATCH 5/6] loop variable --- samples/gpio_dht11.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/samples/gpio_dht11.py b/samples/gpio_dht11.py index ba8be6e..48e7f6c 100644 --- a/samples/gpio_dht11.py +++ b/samples/gpio_dht11.py @@ -25,7 +25,7 @@ class DHT11: >>> address = ('192.168.1.10', 8888) >>> gpio = 4 >>> async with apigpio.Pi(address) as pi: - .... async with DHT11(pi, gpio) as sensor: # 4 is the data GPIO pin connected to your sensor + .... async with DHT11(pi, gpio) as sensor: .... async for result in sensor: .... if result.is_valid(): .... print(result) @@ -95,7 +95,7 @@ def _edge_RISE(self, tick, diff): self.checksum = (self.checksum << 1) + val if self._bit == 39: # 40th bit received - loop.create_task(self._pi.set_watchdog(self._gpio, 0)) + self._loop.create_task(self._pi.set_watchdog(self._gpio, 0)) total = self.humidity + self.temperature # is checksum ok ? if not (total & 255) == self.checksum: @@ -131,7 +131,7 @@ def _edge_EITHER(self, tick, diff): """ Handle Either signal. """ - loop.create_task(self._pi.set_watchdog(self._gpio, 0)) + self._loop.create_task(self._pi.set_watchdog(self._gpio, 0)) async def read(self): """ From 632d8141f43f3f9dd9c85f673728de99060c9168 Mon Sep 17 00:00:00 2001 From: Alexander Myskin Date: Mon, 11 Jun 2018 11:04:44 +0300 Subject: [PATCH 6/6] wrap async def into if py35 --- apigpio/apigpio.py | 24 +++++++++++++++++------- 1 file changed, 17 insertions(+), 7 deletions(-) diff --git a/apigpio/apigpio.py b/apigpio/apigpio.py index 412e061..f02d97b 100644 --- a/apigpio/apigpio.py +++ b/apigpio/apigpio.py @@ -10,6 +10,8 @@ logger = logging.getLogger('apigpio') +PY_35 = sys.version_info >= (3, 5) + # pigpio command numbers _PI_CMD_MODES = 0 _PI_CMD_MODEG = 1 @@ -521,8 +523,15 @@ class Pi: """ Usage: >>> address = ('192.168.1.10', 8888) - >>> async with Pi(address) as pi: - .... await pi.get_version() + >>> async with Pi(address) as pi: # python3.5+ version + ... await pi.get_version() + >>> + >>> pi = Pi(address) # python3.4 version + >>> try: + ... yield from pi.connect() + ... yield from pi.get_version() + ... finally: + ... yield from pi.stop() """ def __init__(self, address, loop=None): @@ -599,12 +608,13 @@ def stop(self): logger.debug('closing socket') self.s.close() - async def __aenter__(self): - await self.connect() - return self + if PY_35: + async def __aenter__(self): + await self.connect() + return self - async def __aexit__(self, exc_type, exc_val, exc_tb): - await self.stop() + async def __aexit__(self, exc_type, exc_val, exc_tb): + await self.stop() @asyncio.coroutine def get_version(self):