Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for IIO events #1081

Merged
merged 16 commits into from
Nov 22, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 15 additions & 1 deletion CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,21 @@ endif()

option(BUILD_SHARED_LIBS "Build shared libraries" ON)

add_library(iio attr.c backend.c block.c channel.c device.c context.c buffer.c mask.c utilities.c scan.c sort.c task.c stream.c
add_library(iio
attr.c
backend.c
block.c
buffer.c
channel.c
context.c
device.c
events.c
mask.c
scan.c
sort.c
stream.c
task.c
utilities.c
${CMAKE_CURRENT_BINARY_DIR}/iio-config.h
)

Expand Down
129 changes: 129 additions & 0 deletions bindings/python/iio.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,9 @@
Structure,
c_char_p,
c_uint,
c_uint64,
c_int,
c_int64,
c_long,
c_longlong,
c_size_t,
Expand All @@ -34,6 +36,7 @@
memmove as _memmove,
byref as _byref,
)
from contextlib import contextmanager
from ctypes.util import find_library
from enum import Enum
from os import strerror as _strerror
Expand Down Expand Up @@ -108,6 +111,8 @@ class _Channel(Structure):
class _ChannelsMask(Structure):
pass

class _EventStream(Structure):
pass

class _Buffer(Structure):
pass
Expand Down Expand Up @@ -140,6 +145,47 @@ class DataFormat(Structure):
("offset", c_double),
]

class EventType(Enum):
"""Represents the type of an IIO event."""

IIO_EV_TYPE_THRESH = 0
IIO_EV_TYPE_MAG = 1
IIO_EV_TYPE_ROC = 2
IIO_EV_TYPE_THRESH_ADAPTIVE = 3
IIO_EV_TYPE_MAG_ADAPTIVE = 4
IIO_EV_TYPE_CHANGE = 5
IIO_EV_TYPE_MAG_REFERENCED = 6
IIO_EV_TYPE_GESTURE = 7

class EventDirection(Enum):
"""Represents the direction of an IIO event."""

IIO_EV_DIR_EITHER = 0
IIO_EV_DIR_RISING = 1
IIO_EV_DIR_FALLING = 2
IIO_EV_DIR_NONE = 3
IIO_EV_DIR_SINGLETAP = 4
IIO_EV_DIR_DOUBLETAP = 5

class Event(Structure):
"""Represents an IIO event."""

_fields_ = [('id', c_uint64), ('timestamp', c_int64)]

type = property(
lambda self: EventType((self.id >> 56) & 0xff),
None,
None,
"The type of the IIO event.\n\ttype=iio.EventType(Enum)",
)

direction = property(
lambda self: EventDirection((self.id >> 48) & 0x7f),
None,
None,
"The direction of the IIO event.\n\ttype=iio.EventDirection(Enum)",
)


class ChannelModifier(Enum):
"""Contains the modifier type of an IIO channel."""
Expand Down Expand Up @@ -238,6 +284,8 @@ class ChannelType(Enum):
_DevicePtr = _POINTER(_Device)
_ChannelPtr = _POINTER(_Channel)
_ChannelsMaskPtr = _POINTER(_ChannelsMask)
_EventPtr = _POINTER(Event)
_EventStreamPtr = _POINTER(_EventStream)
_BufferPtr = _POINTER(_Buffer)
_BlockPtr = _POINTER(_Block)
_DataFormatPtr = _POINTER(DataFormat)
Expand Down Expand Up @@ -400,6 +448,11 @@ class ChannelType(Enum):
_d_get_attr.argtypes = (_DevicePtr, c_uint)
_d_get_attr.errcheck = _check_null

_d_create_evstream = _lib.iio_device_create_event_stream
_d_create_evstream.restype = _EventStreamPtr
_d_create_evstream.argtypes = (_DevicePtr,)
_d_create_evstream.errcheck = _check_ptr_err

_d_debug_attr_count = _lib.iio_device_get_debug_attrs_count
_d_debug_attr_count.restype = c_uint
_d_debug_attr_count.argtypes = (_DevicePtr,)
Expand Down Expand Up @@ -631,6 +684,18 @@ class ChannelType(Enum):
_stream_get_next_block.argtypes = (_StreamPtr,)
_stream_get_next_block.errcheck = _check_ptr_err

_evstream_destroy = _lib.iio_event_stream_destroy
_evstream_destroy.argtypes = (_EventStreamPtr,)

_evstream_read = _lib.iio_event_stream_read
_evstream_read.restype = c_int
_evstream_read.argtypes = (_EventStreamPtr, _EventPtr, c_bool)
_evstream_read.errcheck = _check_negative

_ev_get_channel = _lib.iio_event_get_channel
_ev_get_channel.restype = _ChannelPtr
_ev_get_channel.argtypes = (_EventPtr, _DevicePtr, c_bool)
_ev_get_channel.errcheck = _check_null

# pylint: enable=invalid-name

Expand Down Expand Up @@ -1082,6 +1147,56 @@ def __next__(self):
return Block(self._buffer, self._block_size, next_hdl)


class EventStream(_IIO_Object):
"""Class used to read IIO events. Cannot be instantiated directly."""

def __init__(self):
# Prevent the EventStream from being initialized manually.
raise NotImplementedError

@classmethod
def _create(cls, device, stream):
self = cls.__new__(cls)
super(EventStream, self).__init__(stream, device)

return self

def read(self, nonblock=True):
"""
Read one event from the stream.

:param nonblock: type=bool
If set, return None if there are no events in the stream.
If unset, it will wait until an event arrives.

returns: type=(iio.Event, (iio.Channel, iio.Channel))
The first element of the tuple is the Event read.
The second element is a tuple itself, that contains the
two channels related to the event. For non-differential
events the second channel will always be None.
"""
event = Event()

try:
_evstream_read(self._hdl, _byref(event), nonblock)
except OSError as err:
if err.errno == 11: # EAGAIN
return None

raise

chn1 = None
chn2 = None

try:
dev = self._parent
chn1 = Channel(dev, _ev_get_channel(_byref(event), dev._device, False))
chn2 = Channel(dev, _ev_get_channel(_byref(event), dev._device, True))
except OSError:
pass
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Need to be more explicit for the exception

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I changed it to OSError.


return (event, (chn1, chn2))

class _DeviceOrTrigger(_IIO_Object):
def __init__(self, _ctx, _device):
super(_DeviceOrTrigger, self).__init__(_device, _ctx)
Expand Down Expand Up @@ -1238,6 +1353,20 @@ def _get_trigger(self):
return dev
return None

@contextmanager
def event_stream(self):
"""
Create an events stream.

returns: type=contextlib._GeneratorContextManager
A generator for a EventStream instance.
"""
try:
stream = _d_create_evstream(self._device)
yield EventStream._create(self, stream)
finally:
_evstream_destroy(stream)

trigger = property(
_get_trigger,
_set_trigger,
Expand Down
4 changes: 4 additions & 0 deletions channel.c
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,10 @@ static const char * const iio_chan_type_name_spec[] = {
[IIO_POSITIONRELATIVE] = "positionrelative",
[IIO_PHASE] = "phase",
[IIO_MASSCONCENTRATION] = "massconcentration",
[IIO_DELTA_ANGL] = "delta_angl",
[IIO_DELTA_VELOCITY] = "delta_velocity",
[IIO_COLORTEMP] = "colortemp",
[IIO_CHROMATICITY] = "chromaticity",
};

static const char * const hwmon_chan_type_name_spec[] = {
Expand Down
144 changes: 144 additions & 0 deletions events.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
// SPDX-License-Identifier: LGPL-2.1-or-later
/*
* libiio - Library for interfacing industrial I/O (IIO) devices
*
* Copyright (C) 2023 Analog Devices, Inc.
* Author: Paul Cercueil <[email protected]>
*/

#include "iio-private.h"

#include <ctype.h>
#include <errno.h>
#include <iio/iio-debug.h>

struct iio_event_stream_pdata;

struct iio_event_stream {
const struct iio_device *dev;
struct iio_event_stream_pdata *pdata;
};

/* Corresponds to IIO_EVENT_CODE_EXTRACT_CHAN() and
* IIO_EVENT_CODE_EXTRACT_CHAN2() macros of <linux/iio/events.h> */
static inline int16_t
iio_event_get_channel_id(const struct iio_event *event, unsigned int channel)
{
return (int16_t)(event->id >> (channel << 4));
}

/* Corresponds to IIO_EVENT_CODE_EXTRACT_DIFF() of <linux/iio/events.h> */
static inline bool
iio_event_is_differential(const struct iio_event *event)
{
return event->id & BIT(55);
}

/* Corresponds to IIO_EVENT_CODE_EXTRACT_MODIFIER() of <linux/iio/events.h> */
static inline enum iio_modifier
iio_event_get_modifier(const struct iio_event *event)
{
return (enum iio_modifier)((event->id >> 40) & 0xff);
}

/* Corresponds to IIO_EVENT_CODE_EXTRACT_CHAN_TYPE() of <linux/iio/events.h> */
static inline enum iio_chan_type
iio_event_get_chan_type(const struct iio_event *event)
{
return (enum iio_chan_type)((event->id >> 32) & 0xff);
}

const struct iio_channel *
iio_event_get_channel(const struct iio_event *event,
const struct iio_device *dev, bool diff)
{
const struct iio_channel *chn = NULL;
const char *ptr;
unsigned int i;
int16_t chid;

if (diff && !iio_event_is_differential(event))
return NULL;

chid = iio_event_get_channel_id(event, diff);
if (chid < 0)
return NULL;

if ((unsigned int)chid >= dev->nb_channels) {
dev_warn(dev, "Unexpected IIO event channel ID\n");
return NULL;
}

for (i = 0; i < dev->nb_channels; i++) {
chn = dev->channels[i];

if (chn->type != iio_event_get_chan_type(event)
|| chn->modifier != iio_event_get_modifier(event)) {
continue;
}

for (ptr = chn->id; *ptr && isalpha((unsigned char)*ptr); )
ptr++;

if (!*ptr && chid <= 0)
break;

if ((uint16_t)chid == strtoul(ptr, NULL, 10))
break;
}

if (chn) {
chn_dbg(chn, "Found channel %s for event\n",
iio_channel_get_id(chn));
} else {
dev_dbg(dev, "Unable to find channel for event\n");
}

return chn;
}

struct iio_event_stream *
iio_device_create_event_stream(const struct iio_device *dev)
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have always found it helpful to have the same namespace prefix for all functions in a file. So perhaps this could be iio_event_stream_create_for_device() instead?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That wouldn't be consistent with what we already have, e.g. iio_device_create_buffer, iio_buffer_create_block, iio_buffer_create_stream.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fair enough

{
struct iio_event_stream *stream;
int err;

if (!dev->ctx->ops->open_ev)
return iio_ptr(-ENOSYS);

stream = zalloc(sizeof(*stream));
if (!stream)
return iio_ptr(-ENOMEM);

stream->dev = dev;

stream->pdata = dev->ctx->ops->open_ev(dev);
err = iio_err(stream->pdata);
if (err) {
free(stream);
return iio_ptr(err);
}

return stream;
}

void iio_event_stream_destroy(struct iio_event_stream *stream)
{
if (stream->dev->ctx->ops->close_ev)
stream->dev->ctx->ops->close_ev(stream->pdata);

free(stream);
}

int iio_event_stream_read(struct iio_event_stream *stream,
struct iio_event *out_event,
bool nonblock)
{
if (!stream->dev->ctx->ops->read_ev)
return -ENOSYS;

if (!out_event)
return -EINVAL;

return stream->dev->ctx->ops->read_ev(stream->pdata, out_event, nonblock);
}
2 changes: 1 addition & 1 deletion iio-private.h
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@
#define is_little_endian() (__BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__)
#endif

#define BIT(x) (1 << (x))
#define BIT(x) (1ull << (x))
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seems odd to make this 64-bit but not the other BIT macros.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I only make the BIT macro able to create 64-bit masks, the BIT_MASK and BIT_WORD still need to work on 32-bit words. I could make them work with 64-bit words, but that means changing the iio_channels_mask struct, which is a bit outside this PR.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for reviewing btw.

#define BIT_MASK(bit) BIT((bit) % 32)
#define BIT_WORD(bit) ((bit) / 32)

Expand Down
Loading