From 947bb3d7fe9a031ef3db7064c6c73508fc85f9f9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jean-Micha=C3=ABl=20Celerier?= Date: Sat, 11 Nov 2023 17:50:03 +0100 Subject: [PATCH] [wip] Raw I/O support with libsimpleio --- 3rdparty/3rdparty.cmake | 1 + 3rdparty/libsimpleio.cmake | 41 + 3rdparty/libsimpleio/libsimpleio/cplusplus.h | 41 + 3rdparty/libsimpleio/libsimpleio/errmsg.c | 53 ++ 3rdparty/libsimpleio/libsimpleio/errmsg.inc | 27 + 3rdparty/libsimpleio/libsimpleio/libadc.c | 193 ++++ 3rdparty/libsimpleio/libsimpleio/libadc.h | 43 + 3rdparty/libsimpleio/libsimpleio/libdac.c | 175 ++++ 3rdparty/libsimpleio/libsimpleio/libdac.h | 43 + 3rdparty/libsimpleio/libsimpleio/libevent.c | 241 +++++ 3rdparty/libsimpleio/libsimpleio/libevent.h | 49 + 3rdparty/libsimpleio/libsimpleio/libgpio.c | 900 ++++++++++++++++++ 3rdparty/libsimpleio/libsimpleio/libgpio.h | 102 ++ 3rdparty/libsimpleio/libsimpleio/libhidraw.c | 264 +++++ 3rdparty/libsimpleio/libsimpleio/libhidraw.h | 54 ++ 3rdparty/libsimpleio/libsimpleio/libi2c.c | 147 +++ 3rdparty/libsimpleio/libsimpleio/libi2c.h | 40 + 3rdparty/libsimpleio/libsimpleio/libipv4.c | 572 +++++++++++ 3rdparty/libsimpleio/libsimpleio/libipv4.h | 91 ++ 3rdparty/libsimpleio/libsimpleio/liblinux.c | 777 +++++++++++++++ 3rdparty/libsimpleio/libsimpleio/liblinux.h | 132 +++ 3rdparty/libsimpleio/libsimpleio/liblinx.c | 448 +++++++++ 3rdparty/libsimpleio/libsimpleio/liblinx.h | 192 ++++ 3rdparty/libsimpleio/libsimpleio/libpwm.c | 377 ++++++++ 3rdparty/libsimpleio/libsimpleio/libpwm.h | 49 + 3rdparty/libsimpleio/libsimpleio/libserial.c | 407 ++++++++ 3rdparty/libsimpleio/libsimpleio/libserial.h | 51 + 3rdparty/libsimpleio/libsimpleio/libspi.c | 259 +++++ 3rdparty/libsimpleio/libsimpleio/libspi.h | 46 + 3rdparty/libsimpleio/libsimpleio/libstream.c | 383 ++++++++ 3rdparty/libsimpleio/libsimpleio/libstream.h | 68 ++ .../libsimpleio/libsimpleio/libwatchdog.c | 122 +++ .../libsimpleio/libsimpleio/libwatchdog.h | 44 + .../score-plugin-protocols/CMakeLists.txt | 21 + .../Protocols/SimpleIO/SimpleIODevice.cpp | 278 ++++++ .../Protocols/SimpleIO/SimpleIODevice.hpp | 25 + .../SimpleIO/SimpleIOProtocolFactory.cpp | 76 ++ .../SimpleIO/SimpleIOProtocolFactory.hpp | 34 + .../SimpleIOProtocolSettingsWidget.cpp | 437 +++++++++ .../SimpleIOProtocolSettingsWidget.hpp | 39 + .../SimpleIO/SimpleIOSpecificSettings.hpp | 63 ++ .../SimpleIOSpecificSettingsSerialization.cpp | 165 ++++ .../score_plugin_protocols.cpp | 7 + 43 files changed, 7577 insertions(+) create mode 100644 3rdparty/libsimpleio.cmake create mode 100644 3rdparty/libsimpleio/libsimpleio/cplusplus.h create mode 100644 3rdparty/libsimpleio/libsimpleio/errmsg.c create mode 100644 3rdparty/libsimpleio/libsimpleio/errmsg.inc create mode 100644 3rdparty/libsimpleio/libsimpleio/libadc.c create mode 100644 3rdparty/libsimpleio/libsimpleio/libadc.h create mode 100644 3rdparty/libsimpleio/libsimpleio/libdac.c create mode 100644 3rdparty/libsimpleio/libsimpleio/libdac.h create mode 100644 3rdparty/libsimpleio/libsimpleio/libevent.c create mode 100644 3rdparty/libsimpleio/libsimpleio/libevent.h create mode 100644 3rdparty/libsimpleio/libsimpleio/libgpio.c create mode 100644 3rdparty/libsimpleio/libsimpleio/libgpio.h create mode 100644 3rdparty/libsimpleio/libsimpleio/libhidraw.c create mode 100644 3rdparty/libsimpleio/libsimpleio/libhidraw.h create mode 100644 3rdparty/libsimpleio/libsimpleio/libi2c.c create mode 100644 3rdparty/libsimpleio/libsimpleio/libi2c.h create mode 100644 3rdparty/libsimpleio/libsimpleio/libipv4.c create mode 100644 3rdparty/libsimpleio/libsimpleio/libipv4.h create mode 100644 3rdparty/libsimpleio/libsimpleio/liblinux.c create mode 100644 3rdparty/libsimpleio/libsimpleio/liblinux.h create mode 100644 3rdparty/libsimpleio/libsimpleio/liblinx.c create mode 100644 3rdparty/libsimpleio/libsimpleio/liblinx.h create mode 100644 3rdparty/libsimpleio/libsimpleio/libpwm.c create mode 100644 3rdparty/libsimpleio/libsimpleio/libpwm.h create mode 100644 3rdparty/libsimpleio/libsimpleio/libserial.c create mode 100644 3rdparty/libsimpleio/libsimpleio/libserial.h create mode 100644 3rdparty/libsimpleio/libsimpleio/libspi.c create mode 100644 3rdparty/libsimpleio/libsimpleio/libspi.h create mode 100644 3rdparty/libsimpleio/libsimpleio/libstream.c create mode 100644 3rdparty/libsimpleio/libsimpleio/libstream.h create mode 100644 3rdparty/libsimpleio/libsimpleio/libwatchdog.c create mode 100644 3rdparty/libsimpleio/libsimpleio/libwatchdog.h create mode 100644 src/plugins/score-plugin-protocols/Protocols/SimpleIO/SimpleIODevice.cpp create mode 100644 src/plugins/score-plugin-protocols/Protocols/SimpleIO/SimpleIODevice.hpp create mode 100644 src/plugins/score-plugin-protocols/Protocols/SimpleIO/SimpleIOProtocolFactory.cpp create mode 100644 src/plugins/score-plugin-protocols/Protocols/SimpleIO/SimpleIOProtocolFactory.hpp create mode 100644 src/plugins/score-plugin-protocols/Protocols/SimpleIO/SimpleIOProtocolSettingsWidget.cpp create mode 100644 src/plugins/score-plugin-protocols/Protocols/SimpleIO/SimpleIOProtocolSettingsWidget.hpp create mode 100644 src/plugins/score-plugin-protocols/Protocols/SimpleIO/SimpleIOSpecificSettings.hpp create mode 100644 src/plugins/score-plugin-protocols/Protocols/SimpleIO/SimpleIOSpecificSettingsSerialization.cpp diff --git a/3rdparty/3rdparty.cmake b/3rdparty/3rdparty.cmake index 19535e0751..565101b855 100644 --- a/3rdparty/3rdparty.cmake +++ b/3rdparty/3rdparty.cmake @@ -23,5 +23,6 @@ include(3rdparty/gamma.cmake) include(3rdparty/r8brain.cmake) include(3rdparty/mimalloc.cmake) include(3rdparty/snappy.cmake) +include(3rdparty/libsimpleio.cmake) include(3rdparty/shmdata.cmake) include(3rdparty/sndfile.cmake) diff --git a/3rdparty/libsimpleio.cmake b/3rdparty/libsimpleio.cmake new file mode 100644 index 0000000000..eb9d990d07 --- /dev/null +++ b/3rdparty/libsimpleio.cmake @@ -0,0 +1,41 @@ +if(UNIX AND NOT APPLE AND NOT EMSCRIPTEN) +add_library(simpleio STATIC + "${CMAKE_CURRENT_LIST_DIR}/libsimpleio/libsimpleio/cplusplus.h" + "${CMAKE_CURRENT_LIST_DIR}/libsimpleio/libsimpleio/errmsg.c" + "${CMAKE_CURRENT_LIST_DIR}/libsimpleio/libsimpleio/errmsg.inc" + "${CMAKE_CURRENT_LIST_DIR}/libsimpleio/libsimpleio/libadc.c" + "${CMAKE_CURRENT_LIST_DIR}/libsimpleio/libsimpleio/libadc.h" + "${CMAKE_CURRENT_LIST_DIR}/libsimpleio/libsimpleio/libdac.c" + "${CMAKE_CURRENT_LIST_DIR}/libsimpleio/libsimpleio/libdac.h" +# "${CMAKE_CURRENT_LIST_DIR}/libsimpleio/libsimpleio/libevent.c" +# "${CMAKE_CURRENT_LIST_DIR}/libsimpleio/libsimpleio/libevent.h" + "${CMAKE_CURRENT_LIST_DIR}/libsimpleio/libsimpleio/libgpio.c" + "${CMAKE_CURRENT_LIST_DIR}/libsimpleio/libsimpleio/libgpio.h" + "${CMAKE_CURRENT_LIST_DIR}/libsimpleio/libsimpleio/libhidraw.c" + "${CMAKE_CURRENT_LIST_DIR}/libsimpleio/libsimpleio/libhidraw.h" +# "${CMAKE_CURRENT_LIST_DIR}/libsimpleio/libsimpleio/libi2c.c" +# "${CMAKE_CURRENT_LIST_DIR}/libsimpleio/libsimpleio/libi2c.h" +# "${CMAKE_CURRENT_LIST_DIR}/libsimpleio/libsimpleio/libipv4.c" +# "${CMAKE_CURRENT_LIST_DIR}/libsimpleio/libsimpleio/libipv4.h" + "${CMAKE_CURRENT_LIST_DIR}/libsimpleio/libsimpleio/liblinux.c" + "${CMAKE_CURRENT_LIST_DIR}/libsimpleio/libsimpleio/liblinux.h" +# "${CMAKE_CURRENT_LIST_DIR}/libsimpleio/libsimpleio/liblinx.c" +# "${CMAKE_CURRENT_LIST_DIR}/libsimpleio/libsimpleio/liblinx.h" + "${CMAKE_CURRENT_LIST_DIR}/libsimpleio/libsimpleio/libpwm.c" + "${CMAKE_CURRENT_LIST_DIR}/libsimpleio/libsimpleio/libpwm.h" +# "${CMAKE_CURRENT_LIST_DIR}/libsimpleio/libsimpleio/libserial.c" +# "${CMAKE_CURRENT_LIST_DIR}/libsimpleio/libsimpleio/libserial.h" +# "${CMAKE_CURRENT_LIST_DIR}/libsimpleio/libsimpleio/libspi.c" +# "${CMAKE_CURRENT_LIST_DIR}/libsimpleio/libsimpleio/libspi.h" +# "${CMAKE_CURRENT_LIST_DIR}/libsimpleio/libsimpleio/libstream.c" +# "${CMAKE_CURRENT_LIST_DIR}/libsimpleio/libsimpleio/libstream.h" +# "${CMAKE_CURRENT_LIST_DIR}/libsimpleio/libsimpleio/libwatchdog.c" +# "${CMAKE_CURRENT_LIST_DIR}/libsimpleio/libsimpleio/libwatchdog.h" + ) + +target_include_directories( + simpleio + PUBLIC + "${CMAKE_CURRENT_LIST_DIR}/libsimpleio/" +) +endif() diff --git a/3rdparty/libsimpleio/libsimpleio/cplusplus.h b/3rdparty/libsimpleio/libsimpleio/cplusplus.h new file mode 100644 index 0000000000..876a787dfb --- /dev/null +++ b/3rdparty/libsimpleio/libsimpleio/cplusplus.h @@ -0,0 +1,41 @@ +// C++ linkage goop: Define _BEGIN_STD_C and _END_STD_C, if necessary + +// Copyright (C)2013-2023, Philip Munts dba Munts Technologies. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: +// +// * Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE +// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +// POSSIBILITY OF SUCH DAMAGE. + +#ifndef CPLUSPLUS_H +#define CPLUSPLUS_H + +#if !(defined(_BEGIN_STD_C) && defined(_END_STD_C)) +#ifdef __cplusplus +#ifdef _HAVE_STD_CXX +#define _BEGIN_STD_C namespace std { extern "C" { +#define _END_STD_C } } +#else +#define _BEGIN_STD_C extern "C" { +#define _END_STD_C } +#endif +#else +#define _BEGIN_STD_C +#define _END_STD_C +#endif +#endif + +#endif diff --git a/3rdparty/libsimpleio/libsimpleio/errmsg.c b/3rdparty/libsimpleio/libsimpleio/errmsg.c new file mode 100644 index 0000000000..ceadc404ff --- /dev/null +++ b/3rdparty/libsimpleio/libsimpleio/errmsg.c @@ -0,0 +1,53 @@ +// Define macros for display error messages + +// Copyright (C)2016-2023, Philip Munts dba Munts Technologies. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: +// +// * Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE +// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +// POSSIBILITY OF SUCH DAMAGE. + +#include +#include +#include +#include + +void PrintErrorMessage(const char *func, const char *file, int line, const char *msg, int err) +{ + char *slevel = getenv("DEBUGLEVEL"); + if (slevel == NULL) return; + + int ilevel = atoi(slevel); + + switch (ilevel) + { + case 1 : + fprintf(stderr, "ERROR in %s(), at %s line %d: %s, %s\n", func, file, line, msg, strerror(err)); + break; + + case 2 : + syslog(LOG_ERR, "ERROR in %s(), at %s line %d: %s, %s\n", func, file, line, msg, strerror(err)); + break; + + case 3 : + fprintf(stderr, "ERROR in %s(), at %s line %d: %s, %s\n", func, file, line, msg, strerror(err)); + syslog(LOG_ERR, "ERROR in %s(), at %s line %d: %s, %s\n", func, file, line, msg, strerror(err)); + break; + + default : + break; + } +} diff --git a/3rdparty/libsimpleio/libsimpleio/errmsg.inc b/3rdparty/libsimpleio/libsimpleio/errmsg.inc new file mode 100644 index 0000000000..d38cb20554 --- /dev/null +++ b/3rdparty/libsimpleio/libsimpleio/errmsg.inc @@ -0,0 +1,27 @@ +// Define macros for display error messages + +// Copyright (C)2016-2023, Philip Munts dba Munts Technologies. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: +// +// * Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE +// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +// POSSIBILITY OF SUCH DAMAGE. + +#include + +void PrintErrorMessage(const char *func, const char *file, int line, const char *msg, int err); + +#define ERRORMSG(msg, err, line) PrintErrorMessage(__func__, __FILE__, line, msg, err) diff --git a/3rdparty/libsimpleio/libsimpleio/libadc.c b/3rdparty/libsimpleio/libsimpleio/libadc.c new file mode 100644 index 0000000000..4c4eaf3a18 --- /dev/null +++ b/3rdparty/libsimpleio/libsimpleio/libadc.c @@ -0,0 +1,193 @@ +/* Industrial I/O A/D Converter services for Linux */ + +// Copyright (C)2017-2023, Philip Munts dba Munts Technologies. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: +// +// * Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE +// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +// POSSIBILITY OF SUCH DAMAGE. + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "errmsg.inc" +#include "libadc.h" + +#define NAME_FILE "/sys/bus/iio/devices/iio:device%d/name" +#define DATA_FILE "/sys/bus/iio/devices/iio:device%d/in_voltage%d_raw" + +void ADC_get_name(int32_t chip, char *name, int32_t namesize, int32_t *error) +{ + assert(error != NULL); + + char filename[MAXPATHLEN]; + int fd; + ssize_t len; + + // Validate parameters + + if (chip < 0) + { + *error = EINVAL; + ERRORMSG("chip argument is invalid", *error, __LINE__ - 3); + return; + } + + if (name == NULL) + { + *error = EINVAL; + ERRORMSG("name argument is NULL", *error, __LINE__ - 3); + return; + } + + if (namesize < 16) + { + *error = EINVAL; + ERRORMSG("namesize argument is too small", *error, __LINE__ - 3); + return; + } + + memset(filename, 0, sizeof(filename)); + snprintf(filename, sizeof(filename), NAME_FILE, chip); + + fd = open(filename, O_RDONLY); + if (fd < 0) + { + *error = errno; + ERRORMSG("open() failed", *error, __LINE__ - 4); + return; + } + + memset(name, 0, namesize); + len = read(fd, name, namesize - 1); + + if (len >= 0) + *error = 0; + else + { + *error = errno; + ERRORMSG("read() failed", *error, __LINE__ - 7); + } + + while ((len > 0) && isspace(name[len-1])) + name[--len] = 0; + + close(fd); +} + +void ADC_open(int32_t chip, int32_t channel, int32_t *fd, int32_t *error) +{ + assert(error != NULL); + + // Validate parameters + + if (fd == NULL) + { + *error = EINVAL; + ERRORMSG("fd argument is NULL", *error, __LINE__ - 3); + return; + } + + if (chip < 0) + { + *fd = -1; + *error = EINVAL; + ERRORMSG("chip argument is invalid", *error, __LINE__ - 4); + return; + } + + // Validate parameters + + if (channel < 0) + { + *fd = -1; + *error = EINVAL; + ERRORMSG("channel argument is invalid", *error, __LINE__ - 4); + return; + } + + char filename[MAXPATHLEN]; + snprintf(filename, sizeof(filename), DATA_FILE, chip, channel); + + *fd = open(filename, O_RDONLY); + if (*fd < 0) + { + *fd = -1; + *error = errno; + ERRORMSG("open() failed", *error, __LINE__ - 5); + return; + } + + *error = 0; +} + +void ADC_read(int32_t fd, int32_t *sample, int32_t *error) +{ + assert(error != NULL); + + char buf[32]; + ssize_t len; + + // Validate parameters + + if (fd < 0) + { + *sample = 0; + *error = EINVAL; + ERRORMSG("fd argument is invalid", *error, __LINE__ - 3); + return; + } + + if (sample == NULL) + { + *error = EINVAL; + ERRORMSG("sample argument is NULL", *error, __LINE__ - 3); + return; + } + + // Rewind the raw data file + + if (lseek(fd, SEEK_SET, 0) < 0) + { + *sample = 0; + *error = errno; + ERRORMSG("lseek() failed", *error, __LINE__ - 4); + return; + } + + // Read the raw data file + + len = read(fd, buf, sizeof(buf) - 1); + + if (len < 0) + { + *sample = 0; + *error = errno; + ERRORMSG("read() failed", *error, __LINE__ - 4); + return; + } + + buf[len] = 0; + *sample = atoi(buf); + *error = 0; +} diff --git a/3rdparty/libsimpleio/libsimpleio/libadc.h b/3rdparty/libsimpleio/libsimpleio/libadc.h new file mode 100644 index 0000000000..736060181e --- /dev/null +++ b/3rdparty/libsimpleio/libsimpleio/libadc.h @@ -0,0 +1,43 @@ +/* Industrial I/O A/D Converter services for Linux */ + +// Copyright (C)2017-2023, Philip Munts dba Munts Technologies. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: +// +// * Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE +// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +// POSSIBILITY OF SUCH DAMAGE. + +#ifndef LIBADC_H +#define LIBADC_H + +#include +#include + +_BEGIN_STD_C + +extern void ADC_get_name(int32_t chip, char *name, int32_t namesize, + int32_t *error); + +extern void ADC_open(int32_t chip, int32_t channel, int32_t *fd, + int32_t *error); + +extern void ADC_close(int32_t fd, int32_t *error); + +extern void ADC_read(int32_t fd, int32_t *sample, int32_t *error); + +_END_STD_C + +#endif diff --git a/3rdparty/libsimpleio/libsimpleio/libdac.c b/3rdparty/libsimpleio/libsimpleio/libdac.c new file mode 100644 index 0000000000..f664dcc7df --- /dev/null +++ b/3rdparty/libsimpleio/libsimpleio/libdac.c @@ -0,0 +1,175 @@ +/* Industrial I/O D/A Converter services for Linux */ + +// Copyright (C)2017-2023, Philip Munts dba Munts Technologies. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: +// +// * Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE +// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +// POSSIBILITY OF SUCH DAMAGE. + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "errmsg.inc" +#include "libadc.h" + +#define NAME_FILE "/sys/bus/iio/devices/iio:device%d/name" +#define DATA_FILE "/sys/bus/iio/devices/iio:device%d/out_voltage%d_raw" + +void DAC_get_name(int32_t chip, char *name, int32_t namesize, int32_t *error) +{ + assert(error != NULL); + + char filename[MAXPATHLEN]; + int fd; + ssize_t len; + + // Validate parameters + + if (chip < 0) + { + *error = EINVAL; + ERRORMSG("chip argument is invalid", *error, __LINE__ - 3); + return; + } + + if (name == NULL) + { + *error = EINVAL; + ERRORMSG("name argument is NULL", *error, __LINE__ - 3); + return; + } + + if (namesize < 16) + { + *error = EINVAL; + ERRORMSG("namesize argument is too small", *error, __LINE__ - 3); + return; + } + + memset(filename, 0, sizeof(filename)); + snprintf(filename, sizeof(filename), NAME_FILE, chip); + + fd = open(filename, O_RDONLY); + if (fd < 0) + { + *error = errno; + ERRORMSG("open() failed", *error, __LINE__ - 4); + return; + } + + memset(name, 0, namesize); + len = read(fd, name, namesize - 1); + + if (len >= 0) + *error = 0; + else + { + *error = errno; + ERRORMSG("read() failed", *error, __LINE__ - 7); + } + + while ((len > 0) && isspace(name[len-1])) + name[--len] = 0; + + close(fd); +} + +void DAC_open(int32_t chip, int32_t channel, int32_t *fd, int32_t *error) +{ + assert(error != NULL); + + // Validate parameters + + if (fd == NULL) + { + *error = EINVAL; + ERRORMSG("fd argument is NULL", *error, __LINE__ - 3); + return; + } + + if (chip < 0) + { + *fd = -1; + *error = EINVAL; + ERRORMSG("chip argument is invalid", *error, __LINE__ - 4); + return; + } + + // Validate parameters + + if (channel < 0) + { + *fd = -1; + *error = EINVAL; + ERRORMSG("channel argument is invalid", *error, __LINE__ - 4); + return; + } + + char filename[MAXPATHLEN]; + snprintf(filename, sizeof(filename), DATA_FILE, chip, channel); + + *fd = open(filename, O_WRONLY); + if (*fd < 0) + { + *fd = -1; + *error = errno; + ERRORMSG("open() failed", *error, __LINE__ - 5); + return; + } + + *error = 0; +} + +void DAC_write(int32_t fd, int32_t sample, int32_t *error) +{ + assert(error != NULL); + + char buf[32]; + int count; + ssize_t len; + + // Validate parameters + + if (fd < 0) + { + *error = EINVAL; + ERRORMSG("fd argument is invalid", *error, __LINE__ - 3); + return; + } + + // Write the raw data file + + count = snprintf(buf, sizeof(buf), "%d\n", sample); + + len = write(fd, buf, count); + + if (len < 0) + { + *error = errno; + ERRORMSG("write() failed", *error, __LINE__ - 4); + return; + } + + *error = 0; +} diff --git a/3rdparty/libsimpleio/libsimpleio/libdac.h b/3rdparty/libsimpleio/libsimpleio/libdac.h new file mode 100644 index 0000000000..59c57f03b6 --- /dev/null +++ b/3rdparty/libsimpleio/libsimpleio/libdac.h @@ -0,0 +1,43 @@ +/* Industrial I/O D/A Converter services for Linux */ + +// Copyright (C)2017-2023, Philip Munts dba Munts Technologies. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: +// +// * Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE +// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +// POSSIBILITY OF SUCH DAMAGE. + +#ifndef LIBDAC_H +#define LIBDAC_H + +#include +#include + +_BEGIN_STD_C + +extern void DAC_get_name(int32_t chip, char *name, int32_t namesize, + int32_t *error); + +extern void DAC_open(int32_t chip, int32_t channel, int32_t *fd, + int32_t *error); + +extern void DAC_close(int32_t fd, int32_t *error); + +extern void DAC_write(int32_t fd, int32_t sample, int32_t *error); + +_END_STD_C + +#endif diff --git a/3rdparty/libsimpleio/libsimpleio/libevent.c b/3rdparty/libsimpleio/libsimpleio/libevent.c new file mode 100644 index 0000000000..020f667681 --- /dev/null +++ b/3rdparty/libsimpleio/libsimpleio/libevent.c @@ -0,0 +1,241 @@ +/* epoll wrapper services for Linux */ + +// Copyright (C)2016-2023, Philip Munts dba Munts Technologies. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: +// +// * Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE +// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +// POSSIBILITY OF SUCH DAMAGE. + +#include +#include +#include +#include +#include + +#include "errmsg.inc" +#include "libevent.h" + +void EVENT_open(int32_t *epfd, int32_t *error) +{ + assert(error != NULL); + + // Validate parameters + + if (epfd == NULL) + { + *error = EINVAL; + ERRORMSG("epfd argument is NULL", *error, __LINE__ - 3); + return; + } + + *epfd = epoll_create(256); + if (*epfd < 0) + { + *epfd = -1; + *error = errno; + ERRORMSG("epoll_create() failed", *error, __LINE__ - 5); + return; + } + + *error = 0; +} + +void EVENT_register_fd(int32_t epfd, int32_t fd, int32_t events, + int32_t handle, int32_t *error) +{ + assert(error != NULL); + + // Validate parameters + + if (epfd < 0) + { + *error = EINVAL; + ERRORMSG("epfd argument is invalid", *error, __LINE__ - 3); + return; + } + + if (fd < 0) + { + *error = EINVAL; + ERRORMSG("fd argument is invalid", *error, __LINE__ - 3); + return; + } + + struct epoll_event ev; + + memset(&ev, 0, sizeof(ev)); + ev.events = events; + ev.data.fd = fd; + ev.data.u32 = handle; + + if (epoll_ctl(epfd, EPOLL_CTL_ADD, fd, &ev)) + { + *error = errno; + ERRORMSG("epoll_ctl() failed", *error, __LINE__ - 3); + return; + } + + *error = 0; +} + +void EVENT_modify_fd(int32_t epfd, int32_t fd, int32_t events, + int32_t handle, int32_t *error) +{ + assert(error != NULL); + + // Validate parameters + + if (epfd < 0) + { + *error = EINVAL; + ERRORMSG("epfd argument is invalid", *error, __LINE__ - 3); + return; + } + + if (fd < 0) + { + *error = EINVAL; + ERRORMSG("fd argument is invalid", *error, __LINE__ - 3); + return; + } + + struct epoll_event ev; + + memset(&ev, 0, sizeof(ev)); + ev.events = events; + ev.data.fd = fd; + ev.data.u32 = handle; + + if (epoll_ctl(epfd, EPOLL_CTL_MOD, fd, &ev)) + { + *error = errno; + ERRORMSG("epoll_ctl() failed", *error, __LINE__ - 3); + return; + } + + *error = 0; +} + +void EVENT_unregister_fd(int32_t epfd, int32_t fd, int32_t *error) +{ + assert(error != NULL); + + // Validate parameters + + if (epfd < 0) + { + *error = EINVAL; + ERRORMSG("epfd argument is invalid", *error, __LINE__ - 3); + return; + } + + if (fd < 0) + { + *error = EINVAL; + ERRORMSG("fd argument is invalid", *error, __LINE__ - 3); + return; + } + + if (epoll_ctl(epfd, EPOLL_CTL_DEL, fd, NULL)) + { + *error = errno; + ERRORMSG("epoll_ctl() failed", *error, __LINE__ - 3); + return; + } + + *error = 0; +} + +void EVENT_wait(int32_t epfd, int32_t *fd, int32_t *event, + int32_t *handle, int32_t timeoutms, int32_t *error) +{ + assert(error != NULL); + + // Validate parameters + + if (epfd < 0) + { + *error = EINVAL; + ERRORMSG("epfd argument is invalid", *error, __LINE__ - 3); + return; + } + + if (fd == NULL) + { + *error = EINVAL; + ERRORMSG("fd argument is NULL", *error, __LINE__ - 3); + return; + } + + if (event == NULL) + { + *error = EINVAL; + ERRORMSG("event argument is NULL", *error, __LINE__ - 3); + return; + } + + if (handle == NULL) + { + *error = EINVAL; + ERRORMSG("handle argument is NULL", *error, __LINE__ - 3); + return; + } + + if (timeoutms < -1) + { + *error = EINVAL; + ERRORMSG("timeut argument is invalid", *error, __LINE__ - 3); + return; + } + + int status; + struct epoll_event ev; + + memset(&ev, 0, sizeof(ev)); + + status = epoll_wait(epfd, &ev, 1, timeoutms); + + // An error occurred: + + if (status < 0) + { + *fd = 0; + *event = 0; + *handle = 0; + *error = errno; + ERRORMSG("epoll_wait() failed", *error, __LINE__ - 3); + return; + } + + // No events are available: + + if (status == 0) + { + *fd = 0; + *event = 0; + *handle = 0; + *error = EAGAIN; + return; + } + + // An event occurred: + + *fd = ev.data.fd; + *event = ev.events; + *handle = ev.data.u32; + *error = 0; +} diff --git a/3rdparty/libsimpleio/libsimpleio/libevent.h b/3rdparty/libsimpleio/libsimpleio/libevent.h new file mode 100644 index 0000000000..53eaf78a98 --- /dev/null +++ b/3rdparty/libsimpleio/libsimpleio/libevent.h @@ -0,0 +1,49 @@ +/* epoll wrapper services for Linux */ + +// Copyright (C)2016-2023, Philip Munts dba Munts Technologies. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: +// +// * Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE +// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +// POSSIBILITY OF SUCH DAMAGE. + +#ifndef LIBEVENT_H +#define LIBEVENT_H + +#include +#include +#include + +_BEGIN_STD_C + +extern void EVENT_open(int32_t *epfd, int32_t *error); + +extern void EVENT_register_fd(int32_t epfd, int32_t fd, int32_t events, + int32_t handle, int32_t *error); + +extern void EVENT_modify_fd(int32_t epfd, int32_t fd, int32_t events, + int32_t handle, int32_t *error); + +extern void EVENT_unregister_fd(int32_t epfd, int32_t fd, int32_t *error); + +extern void EVENT_wait(int32_t epfd, int32_t *fd, int32_t *event, + int32_t *handle, int32_t timeoutms, int32_t *error); + +extern void EVENT_close(int32_t epfd, int32_t *error); + +_END_STD_C + +#endif diff --git a/3rdparty/libsimpleio/libsimpleio/libgpio.c b/3rdparty/libsimpleio/libsimpleio/libgpio.c new file mode 100644 index 0000000000..aefc5bb30a --- /dev/null +++ b/3rdparty/libsimpleio/libsimpleio/libgpio.c @@ -0,0 +1,900 @@ +/* GPIO services for Linux */ + +// Copyright (C)2016-2023, Philip Munts dba Munts Technologies. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: +// +// * Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE +// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +// POSSIBILITY OF SUCH DAMAGE. + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "errmsg.inc" +#include "libgpio.h" + +void GPIO_chip_info(int32_t chip, char *name, int32_t namesize, + char *label, int32_t labelsize, int32_t *lines, int32_t *error) +{ + assert(error != NULL); + + // Validate parameters + + if (chip < 0) + { + *error = EINVAL; + ERRORMSG("chip argument is invalid", *error, __LINE__ - 3); + return; + } + + if (name == NULL) + { + *error = EINVAL; + ERRORMSG("name argument is NULL", *error, __LINE__ - 3); + return; + } + + if (namesize < 32) + { + *error = EINVAL; + ERRORMSG("namesize argument is invalid", *error, __LINE__ - 3); + return; + } + + if (label == NULL) + { + *error = EINVAL; + ERRORMSG("label argument is NULL", *error, __LINE__ - 3); + return; + } + + if (labelsize < 32) + { + *error = EINVAL; + ERRORMSG("labelsize argument is invalid", *error, __LINE__ - 3); + return; + } + + if (lines == NULL) + { + *error = EINVAL; + ERRORMSG("lines argument is NULL", *error, __LINE__ - 3); + return; + } + + // Open the GPIO controller device + + char nodename[32]; + snprintf(nodename, sizeof(nodename), "/dev/gpiochip%d", chip); + + int chipfd = open(nodename, O_RDWR); + + if (chipfd < 0) + { + *error = errno; + ERRORMSG("open() failed", *error, __LINE__ - 3); + return; + } + + // Query the GPIO controller device info + + struct gpiochip_info info; + + if (ioctl(chipfd, GPIO_GET_CHIPINFO_IOCTL, &info) < 0) + { + *error = errno; + ERRORMSG("ioctl() failed", *error, __LINE__ - 3); + close(chipfd); + return; + } + + close(chipfd); + + memset(name, 0, namesize); + strncpy(name, info.name, namesize - 1); + + memset(label, 0, labelsize); + strncpy(label, info.label, labelsize - 1); + + *lines = info.lines; + *error = 0; +} + +void GPIO_line_info(int32_t chip, int32_t line, int32_t *flags, char *name, + int32_t namesize, char *label, int32_t labelsize, int32_t *error) +{ + assert(error != NULL); + + // Validate parameters + + if (chip < 0) + { + *error = EINVAL; + ERRORMSG("chip argument is invalid", *error, __LINE__ - 3); + return; + } + + if (line < 0) + { + *error = EINVAL; + ERRORMSG("line argument is invalid", *error, __LINE__ - 3); + return; + } + + if (flags == NULL) + { + *error = EINVAL; + ERRORMSG("flags argument is NULL", *error, __LINE__ - 3); + return; + } + + if (name == NULL) + { + *error = EINVAL; + ERRORMSG("name argument is NULL", *error, __LINE__ - 3); + return; + } + + if (namesize < 32) + { + *error = EINVAL; + ERRORMSG("namesize argument is invalid", *error, __LINE__ - 3); + return; + } + + if (label == NULL) + { + *error = EINVAL; + ERRORMSG("label argument is NULL", *error, __LINE__ - 3); + return; + } + + if (labelsize < 32) + { + *error = EINVAL; + ERRORMSG("labelsize argument is invalid", *error, __LINE__ - 3); + return; + } + + // Open the GPIO controller device + + char nodename[32]; + snprintf(nodename, sizeof(nodename), "/dev/gpiochip%d", chip); + + int chipfd = open(nodename, O_RDWR); + + if (chipfd < 0) + { + *error = errno; + ERRORMSG("open() failed", *error, __LINE__ - 3); + return; + } + + // Query the GPIO line info + + struct gpioline_info info = { line }; + + if (ioctl(chipfd, GPIO_GET_LINEINFO_IOCTL, &info) < 0 ) + { + *error = errno; + ERRORMSG("ioctl() failed", *error, __LINE__ - 3); + close(chipfd); + return; + } + + memset(name, 0, namesize); + memset(label, 0, labelsize); + + *flags = info.flags; + strncpy(name, info.name, namesize - 1); + strncpy(label, info.consumer, labelsize - 1); + + *error = 0; +} + +// Exhaustive list of valid handle request flag combinations + +static const bool ValidFlags[32] = +{ + false, // 0x00 + true, // 0x01 -- INPUT + true, // 0x02 -- OUTPUT + false, // 0x03 -- INPUT|OUTPUT + false, // 0x04 -- ACTIVE_LOW + true, // 0x05 -- INPUT|ACTIVE_LOW + true, // 0x06 -- OUTPUT|ACTIVE_LOW + false, // 0x07 -- INPUT|OUTPUT|ACTIVELOW + false, // 0x08 -- OPEN_DRAIN + false, // 0x09 -- INPUT|OPEN_DRAIN + true, // 0x0A -- OUTPUT|OPEN_DRAIN + false, // 0x0B -- INPUT|OUTPUT|OPEN_DRAIN + false, // 0x0C -- ACTIVE_LOW|OPEN_DRAIN + false, // 0x0D -- INPUT|ACTIVE_LOW|OPEN_DRAIN + true, // 0x0E -- OUTPUT|ACTIVE_LOW|OPEN_DRAIN + false, // 0x0F -- INPUT|OUTPUT|ACTIVE_LOW|OPEN_DRAIN + false, // 0x10 -- OPEN_SOURCE + false, // 0x11 -- INPUT|OPEN_SOURCE + true, // 0x12 -- OUTPUT|OPEN_SOURCE + false, // 0x13 -- INPUT|OUTPUT|OPEN_SOURCE + false, // 0x14 -- ACTIVE_LOW|OPEN_SOURCE + false, // 0x15 -- INPUT|ACTIVE_LOW|OPEN_SOURCE + true, // 0x16 -- OUTPUT_ACTIVE_LOW|OPEN_SOURCE + false, // 0x17 -- INPUT|OUTPUT|ACTIVE_LOW|OPEN_SOURCE + false, // 0x18 -- OPEN_DRAIN|OPEN_SOURCE + false, // 0x19 -- INPUT|OPEN_DRAIN|OPEN_SOURCE + false, // 0x1A -- OUTPUT|OPEN_DRAIN|OPEN_SOURCE + false, // 0x1B -- INPUT|OUTPUT|OPEN_DRAIN|OPEN_SOURCE + false, // 0x1C -- ACTIVE_LOW|OPEN_DRAIN|OPEN_SOURCE + false, // 0x1D -- INPUT|ACTIVE_LOW|OPEN_DRAIN|OPEN_SOURCE + false, // 0x1E -- OUTPUT|ACTIVE_LOW|OPEN_DRAIN|OPEN_SOURCE + false, // 0x1F -- INPUT|OUTPUT|ACTIVE_LOW|OPEN_DRAIN|OPEN_SOURCE +}; + +void GPIO_line_open(int32_t chip, int32_t line, int32_t flags, int32_t events, + int32_t state, int32_t *fd, int32_t *error) +{ + assert(error != NULL); + + // Validate parameters + + if (fd == NULL) + { + *error = EINVAL; + ERRORMSG("fd argument is NULL", *error, __LINE__ - 3); + return; + } + + if (chip < 0) + { + *fd = -1; + *error = EINVAL; + ERRORMSG("chip argument is invalid", *error, __LINE__ - 4); + return; + } + + if (line < 0) + { + *fd = -1; + *error = EINVAL; + ERRORMSG("line argument is invalid", *error, __LINE__ - 4); + return; + } + + if (flags & 0xFFFFFFE0) + { + *fd = -1; + *error = EINVAL; + ERRORMSG("flags argument is invalid", *error, __LINE__ - 4); + return; + } + + if (!ValidFlags[flags]) + { + *fd = -1; + *error = EINVAL; + ERRORMSG("flags argument is inconsistent", *error, __LINE__ - 4); + return; + } + + if (events & 0xFFFFFFFC) + { + *fd = -1; + *error = EINVAL; + ERRORMSG("events argument is invalid", *error, __LINE__ - 4); + return; + } + + if ((flags & GPIOHANDLE_REQUEST_OUTPUT) && (events > GPIO_EDGE_NONE)) + { + *fd = -1; + *error = EINVAL; + ERRORMSG("flags and events are inconsistent", *error, __LINE__ - 4); + return; + } + + if ((state < 0) || (state > 1)) + { + *fd = -1; + *error = EINVAL; + ERRORMSG("state argument is invalid", *error, __LINE__ - 4); + return; + } + + // Open GPIO controller device + + char nodename[32]; + snprintf(nodename, sizeof(nodename), "/dev/gpiochip%d", chip); + + int chipfd = open(nodename, O_RDWR); + + if (chipfd < 0) + { + *fd = -1; + *error = errno; + ERRORMSG("open() failed", *error, __LINE__ - 4); + return; + } + + if (events) + { + // Request GPIO event handle + + struct gpioevent_request req; + memset(&req, 0, sizeof(req)); + req.lineoffset = line; + req.handleflags = flags; + req.eventflags = events; + + if (ioctl(chipfd, GPIO_GET_LINEEVENT_IOCTL, &req) < 0) + { + *fd = -1; + *error = errno; + ERRORMSG("ioctl() failed", *error, __LINE__ - 4); + close(chipfd); + return; + } + + *fd = req.fd; + } + else + { + // Request GPIO line handle + + struct gpiohandle_request req; + memset(&req, 0, sizeof(req)); + req.lineoffsets[0] = line; + req.flags = flags; + req.default_values[0] = state; + req.lines = 1; + + if (ioctl(chipfd, GPIO_GET_LINEHANDLE_IOCTL, &req) < 0) + { + *fd = -1; + *error = errno; + ERRORMSG("ioctl() failed", *error, __LINE__ - 4); + close(chipfd); + return; + } + + *fd = req.fd; + } + + close(chipfd); + + *error = 0; +} + +void GPIO_line_read(int32_t fd, int32_t *state, int32_t *error) +{ + assert(error != NULL); + + // Validate parameters + + if (fd < 0) + { + *error = EINVAL; + ERRORMSG("fd argument is invalid", *error, __LINE__ - 3); + return; + } + + if (state == NULL) + { + *error = EINVAL; + ERRORMSG("state argument is NULL", *error, __LINE__ - 3); + return; + } + + struct gpiohandle_data data = {{ 0 }}; + + if (ioctl(fd, GPIOHANDLE_GET_LINE_VALUES_IOCTL, &data) < 0) + { + *error = errno; + ERRORMSG("ioctl() failed", *error, __LINE__ - 3); + return; + } + + *state = data.values[0]; + *error = 0; +} + +void GPIO_line_write(int32_t fd, int32_t state, int32_t *error) +{ + assert(error != NULL); + + // Validate parameters + + if (fd < 0) + { + *error = EINVAL; + ERRORMSG("fd argument is invalid", *error, __LINE__ - 3); + return; + } + + if ((state < 0) || (state > 1)) + { + *error = EINVAL; + ERRORMSG("state argument is invalid", *error, __LINE__ - 3); + return; + } + + struct gpiohandle_data data = {{ state }}; + + if (ioctl(fd, GPIOHANDLE_SET_LINE_VALUES_IOCTL, &data) < 0) + { + *error = errno; + ERRORMSG("ioctl() failed", *error, __LINE__ - 3); + return; + } + + *error = 0; +} + +void GPIO_line_event(int32_t fd, int32_t *state, int32_t *error) +{ + assert(error != NULL); + + // Validate parameters + + if (fd < 0) + { + *error = EINVAL; + ERRORMSG("fd argument is invalid", *error, __LINE__ - 3); + return; + } + + if (state == NULL) + { + *error = EINVAL; + ERRORMSG("event argument is NULL", *error, __LINE__ - 3); + return; + } + + // Read event data structure + + struct gpioevent_data data; + + if (read(fd, &data, sizeof(data)) != sizeof(data)) + { + *error = EIO; + ERRORMSG("read() failed", *error, __LINE__ - 3); + return; + } + + // Decode result + + switch (data.id) + { + case GPIOEVENT_EVENT_RISING_EDGE: + *state = true; + *error = 0; + break; + + case GPIOEVENT_EVENT_FALLING_EDGE: + *state = false; + *error = 0; + break; + + default: + *error = EIO; + break; + } +} + +//******************* Old GPIO sysfs API (now deprecated): ******************** + +// Device nodes + +#define GPIODIR "/sys/class/gpio" +#define EXPORT GPIODIR "/export" +#define UNEXPORT GPIODIR "/unexport" +#define PINDIR GPIODIR "/gpio%d" +#define ACTIVELOW PINDIR "/active_low" +#define DIRECTION PINDIR "/direction" +#define EDGE PINDIR "/edge" +#define VALUE PINDIR "/value" + +static uint64_t milliseconds(void) +{ + struct timespec t; + + clock_gettime(CLOCK_REALTIME, &t); + return t.tv_sec*1000LL + (1LL*t.tv_nsec)/1000000LL; +} + +// Configure GPIO pin device + +void GPIO_configure(int32_t pin, int32_t direction, int32_t state, int32_t edge, + int32_t polarity, int32_t *error) +{ + assert(error != NULL); + + char name_direction[MAXPATHLEN]; + char name_edge[MAXPATHLEN]; + char name_polarity[MAXPATHLEN]; + char name_value[MAXPATHLEN]; + char buf[16]; + int fd; + uint64_t start; + int status; + + // Validate parameters + + if (pin < 0) + { + *error = EINVAL; + ERRORMSG("pin number argument is invalid", *error, __LINE__ - 3); + return; + } + + if ((direction < GPIO_DIRECTION_INPUT) || (direction > GPIO_DIRECTION_OUTPUT)) + { + *error = EINVAL; + ERRORMSG("direction argument is invalid", *error, __LINE__ - 3); + return; + } + + if ((state < false) || (state > true)) + { + *error = EINVAL; + ERRORMSG("state argument is invalid", *error, __LINE__ - 3); + return; + } + + if ((direction == GPIO_DIRECTION_INPUT) && state) + { + *error = EINVAL; + ERRORMSG("state argument is invalid", *error, __LINE__ - 3); + return; + } + + if ((edge < GPIO_EDGE_NONE) || (edge > GPIO_EDGE_BOTH)) + { + *error = EINVAL; + ERRORMSG("edge argument is invalid", *error, __LINE__ - 3); + return; + } + + if ((direction == GPIO_DIRECTION_OUTPUT) && (edge != GPIO_EDGE_NONE)) + { + *error = EINVAL; + ERRORMSG("edge argument is invalid", *error, __LINE__ - 3); + return; + } + + if ((polarity < GPIO_POLARITY_ACTIVELOW) || (polarity > GPIO_POLARITY_ACTIVEHIGH)) + { + *error = EINVAL; + ERRORMSG("polarity argument is invalid", *error, __LINE__ - 3); + return; + } + + snprintf(name_direction, sizeof(name_direction), DIRECTION, pin); + snprintf(name_edge, sizeof(name_edge), EDGE, pin); + snprintf(name_polarity, sizeof(name_polarity), ACTIVELOW, pin); + snprintf(name_value, sizeof(name_value), VALUE, pin); + + // Export the GPIO pin if necessary + + if (access(name_value, W_OK)) + { + fd = open(EXPORT, O_WRONLY); + if (fd < 0) + { + *error = errno; + ERRORMSG("open() failed", *error, __LINE__ - 4); + return; + } + + snprintf(buf, sizeof(buf), "%d\n", pin); + + if (write(fd, buf, strlen(buf)) < 0) + { + *error = errno; + ERRORMSG("write() failed", *error, __LINE__ - 3); + close(fd); + return; + } + + if (close(fd)) + { + *error = errno; + ERRORMSG("close() failed", *error, __LINE__ - 3); + return; + } + + // Wait for the GPIO pin device to be created + + start = milliseconds(); + + while (access(name_direction, W_OK) || + access(name_edge, W_OK) || + access(name_polarity, W_OK) || + access(name_value, W_OK)) + { + if (milliseconds() - start > 1000) + { + *error = EIO; + ERRORMSG("Timed out waiting for GPIO pin export", *error, + __LINE__ - 3); + return; + } + + usleep(100000); + } + } + + // Set polarity + + fd = open(name_polarity, O_WRONLY); + if (fd < 0) + { + *error = errno; + ERRORMSG("open() failed", *error, __LINE__ - 4); + return; + } + + if (write(fd, polarity ? "0\n" : "1\n", 2) < 2) + { + *error = errno; + ERRORMSG("write() failed", *error, __LINE__ - 3); + close(fd); + return; + } + + if (close(fd)) + { + *error = errno; + ERRORMSG("close() failed", *error, __LINE__ - 3); + return; + } + + // Set direction and possibly initial output state + + fd = open(name_direction, O_WRONLY); + if (fd < 0) + { + *error = errno; + ERRORMSG("open() failed", *error, __LINE__ - 4); + return; + } + + if (direction == GPIO_DIRECTION_INPUT) + status = write(fd, "in\n", 3); + else if (state) + status = write(fd, "high\n", 5); + else + status = write(fd, "low\n", 4); + + if (status < 0) + { + *error = errno; + ERRORMSG("write() failed", *error, __LINE__ - 3); + close(fd); + return; + } + + if (close(fd)) + { + *error = errno; + ERRORMSG("close() failed", *error, __LINE__ - 3); + return; + } + + // Set active edge for input pin + + if (direction == GPIO_DIRECTION_INPUT) + { + fd = open(name_edge, O_WRONLY); + if (fd < 0) + { + *error = errno; + ERRORMSG("open() failed", *error, __LINE__ - 4); + return; + } + + switch (edge) + { + case GPIO_EDGE_NONE : + status = write(fd, "none\n", 5); + break; + + case GPIO_EDGE_RISING : + status = write(fd, "rising\n", 7); + break; + + case GPIO_EDGE_FALLING : + status = write(fd, "falling\n", 8); + break; + + case GPIO_EDGE_BOTH : + status = write(fd, "both\n", 5); + break; + } + + if (status < 0) + { + *error = errno; + ERRORMSG("write() failed", *error, __LINE__ - 3); + close(fd); + return; + } + + if (close(fd)) + { + *error = errno; + ERRORMSG("close() failed", *error, __LINE__ - 3); + return; + } + } + + *error = 0; +} + +// Open GPIO pin device + +void GPIO_open(int32_t pin, int32_t *fd, int32_t *error) +{ + char filename[MAXPATHLEN]; + char buf[16]; + + // Validate parameters + + assert(error != NULL); + + if (fd == NULL) + { + *error = EINVAL; + ERRORMSG("fd argument is NULL", *error, __LINE__ - 3); + return; + } + + if (pin < 0) + { + *fd = -1; + *error = EINVAL; + ERRORMSG("pin number argument is invalid", *error, __LINE__ - 4); + return; + } + + snprintf(filename, sizeof(filename), VALUE, pin); + + *fd = open(filename, O_RDWR); + if (*fd < 0) + { + *fd = -1; + *error = errno; + ERRORMSG("open() failed", *error, __LINE__ - 5); + return; + } + + // Priming read, needed to make edge detection work properly + + if (lseek(*fd, 0, SEEK_SET) < 0) + { + *error = errno; + ERRORMSG("lseek() failed", *error, __LINE__ - 3); + return; + } + + if (read(*fd, buf, sizeof(buf)) < 0) + { + *error = errno; + ERRORMSG("read() failed", *error, __LINE__ - 3); + return; + } + + *error = 0; +} + +// Read state from GPIO pin device + +void GPIO_read(int32_t fd, int32_t *state, int32_t *error) +{ + assert(error != NULL); + + char buf[4]; + + // Validate parameters + + if (fd < 0) + { + *error = EINVAL; + ERRORMSG("fd argument is invalid", *error, __LINE__ - 3); + return; + } + + if (state == NULL) + { + *error = EINVAL; + ERRORMSG("state argument is NULL", *error, __LINE__ - 3); + return; + } + + if (lseek(fd, 0, SEEK_SET) < 0) + { + *error = errno; + ERRORMSG("lseek() failed", *error, __LINE__ - 3); + return; + } + + memset(buf, 0, sizeof(buf)); + + if (read(fd, buf, sizeof(buf)) < 0) + { + *error = errno; + ERRORMSG("read() failed", *error, __LINE__ - 3); + return; + } + + switch(buf[0]) + { + case '0' : + *state = 0; + break; + + case '1' : + *state = 1; + break; + + default : + *error = EINVAL; + return; + } + + *error = 0; +} + +// Write state to GPIO pin device + +void GPIO_write(int32_t fd, int32_t state, int32_t *error) +{ + assert(error != NULL); + + // Validate parameters + + if (fd < 0) + { + *error = EINVAL; + ERRORMSG("fd argument is invalid", *error, __LINE__ - 3); + return; + } + + if ((state < 0) || (state > 1)) + { + *error = EINVAL; + ERRORMSG("state argument is invalid", *error, __LINE__ - 3); + return; + } + + if (write(fd, state ? "1\n" : "0\n", 2) < 2) + { + *error = errno; + ERRORMSG("write() failed", *error, __LINE__ - 3); + return; + } + + *error = 0; +} + diff --git a/3rdparty/libsimpleio/libsimpleio/libgpio.h b/3rdparty/libsimpleio/libsimpleio/libgpio.h new file mode 100644 index 0000000000..bf605cc6ab --- /dev/null +++ b/3rdparty/libsimpleio/libsimpleio/libgpio.h @@ -0,0 +1,102 @@ +/* GPIO services for Linux */ + +// Copyright (C)2016-2023, Philip Munts dba Munts Technologies. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: +// +// * Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE +// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +// POSSIBILITY OF SUCH DAMAGE. + +#ifndef LIBGPIO_H +#define LIBGPIO_H + +#include +#include +#include + +// These flags are not defined in linux/gpio.h + +#define GPIOHANDLE_REQUEST_ACTIVE_HIGH 0 +#define GPIOHANDLE_REQUEST_PUSH_PULL 0 +#define GPIOEVENT_REQUEST_NONE 0 + +_BEGIN_STD_C + +extern void GPIO_chip_info(int32_t chip, char *name, int32_t namesize, + char *label, int32_t labelsize, int32_t *lines, int32_t *error); + +extern void GPIO_line_info(int32_t chip, int32_t line, int32_t *flags, + char *name, int32_t namesize, char *label, int32_t labelsize, + int32_t *error); + +extern void GPIO_line_open(int32_t chip, int32_t line, int32_t flags, + int32_t events, int32_t state, int32_t *fd, int32_t *error); + +extern void GPIO_line_read(int32_t fd, int32_t *state, int32_t *error); + +extern void GPIO_line_write(int32_t fd, int32_t state, int32_t *error); + +extern void GPIO_line_event(int32_t fd, int32_t *state, int32_t *error); + +extern void GPIO_line_close(int32_t fd, int32_t *error); + +_END_STD_C + +//******************* Old GPIO sysfs API (now deprecated): ******************** + +typedef enum +{ + GPIO_DIRECTION_INPUT, + GPIO_DIRECTION_OUTPUT, +} GPIO_DIRECTION_t; + +typedef enum +{ + GPIO_DRIVER_PUSHPULL, + GPIO_DRIVER_OPEN_DRAIN, + GPIO_DRIVER_OPEN_SOURCE, +} GPIO_DRIVER_t; + +typedef enum +{ + GPIO_POLARITY_ACTIVELOW, + GPIO_POLARITY_ACTIVEHIGH, +} GPIO_POLARITY_t; + +typedef enum +{ + GPIO_EDGE_NONE, + GPIO_EDGE_RISING, + GPIO_EDGE_FALLING, + GPIO_EDGE_BOTH, +} GPIO_EDGE_t; + +_BEGIN_STD_C + +extern void GPIO_configure(int32_t pin, int32_t direction, int32_t state, + int32_t edge, int32_t polarity, int32_t *error); + +extern void GPIO_open(int32_t pin, int32_t *fd, int32_t *error); + +extern void GPIO_read(int32_t fd, int32_t *state, int32_t *error); + +extern void GPIO_write(int32_t fd, int32_t state, int32_t *error); + +extern void GPIO_close(int32_t fd, int32_t *error); + +_END_STD_C + +#endif diff --git a/3rdparty/libsimpleio/libsimpleio/libhidraw.c b/3rdparty/libsimpleio/libsimpleio/libhidraw.c new file mode 100644 index 0000000000..dd7434114f --- /dev/null +++ b/3rdparty/libsimpleio/libsimpleio/libhidraw.c @@ -0,0 +1,264 @@ +/* Raw HID device wrapper services for Linux */ + +// Copyright (C)2016-2023, Philip Munts dba Munts Technologies. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: +// +// * Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE +// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +// POSSIBILITY OF SUCH DAMAGE. + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "errmsg.inc" +#include "libhidraw.h" + +// Compatibility shims + +void HIDRAW_open(const char *name, int32_t *fd, int32_t *error) +{ + HIDRAW_open1(name, fd, error); +} + +void HIDRAW_open_id(int32_t VID, int32_t PID, int32_t *fd, int32_t *error) +{ + HIDRAW_open3(VID, PID, NULL, fd, error); +} + +// Open the first raw HID device with matching vendor and product ID's + +void HIDRAW_open2(int32_t VID, int32_t PID, int32_t *fd, int32_t *error) +{ + HIDRAW_open3(VID, PID, NULL, fd, error); +} + +// Open the the raw HID device with matching vendor, product, and serial number + +void HIDRAW_open3(int32_t VID, int32_t PID, const char *serial, int32_t *fd, + int32_t *error) +{ + assert(error != NULL); + + // Validate parameters + + if (fd == NULL) + { + *error = EINVAL; + ERRORMSG("fd argument is NULL", *error, __LINE__ - 3); + return; + } + + int i; + char name[MAXPATHLEN]; + int32_t b, v, p, e; + char serialpath[MAXPATHLEN]; + char devserial[256]; + ssize_t len; + + // Search raw HID devices, looking for matching VID and PID + + for (i = 0; i < 255; i++) + { + snprintf(name, sizeof(name), "/dev/hidraw%d", i); + + // Open the candidate device node + + *fd = open(name, O_RDWR); + if (*fd < 0) continue; + + // Try to get HID device info for the candidate device + + HIDRAW_get_info(*fd, &b, &v, &p, &e); + if (e) continue; + + // Look for a matching device + + if ((VID != v) || (PID != p)) + { + close(*fd); + continue; + } + + // If no serial number was specified, we are done + + if (serial == NULL) + { + *error = 0; + return; + } + + if (strlen(serial) == 0) + { + *error = 0; + return; + } + + // Fetch the serial number for this candidate device + + snprintf(serialpath, sizeof(serialpath), + "/sys/class/hidraw/hidraw%d/../../../../serial", i); + + int serialfd = open(serialpath, O_RDONLY); + + if (serialfd < 0) + { + close(*fd); + continue; + } + + memset(devserial, 0, sizeof(0)); + len = read(serialfd, devserial, sizeof(devserial)-1); + close(serialfd); + + // Check for successful read + + if (len < 1) + { + close(*fd); + continue; + } + + // Check whether we found a serial number + + if (strlen(devserial) == 0) + { + close(*fd); + continue; + } + + // Remove trailing LF, if any + + if (devserial[strlen(devserial)-1] == 10) + devserial[strlen(devserial)-1] = 0; + + // Check for matching serial number + + if (!strcmp(serial, devserial)) + { + *error = 0; + return; + } + + // Close the candidate device node + + close(*fd); + } + + *fd = -1; + *error = ENODEV; + ERRORMSG("Cannot find matching raw HID device", *error, __LINE__ - 1); +} + +// Get device information string (manufacturer + product) + +void HIDRAW_get_name(int32_t fd, char *name, int32_t namesize, int32_t *error) +{ + assert(error != NULL); + + // Validate parameters + + if (fd < 0) + { + *error = EINVAL; + ERRORMSG("fd argument is invalid", *error, __LINE__ - 3); + return; + } + + if (name == NULL) + { + *error = EINVAL; + ERRORMSG("name argument is NULL", *error, __LINE__ - 3); + return; + } + + if (namesize < 16) + { + *error = EINVAL; + ERRORMSG("namesize argument is too small", *error, __LINE__ - 3); + return; + } + + memset(name, 0, namesize); + + if (ioctl(fd, HIDIOCGRAWNAME(namesize), name) < 0) + { + *error = errno; + ERRORMSG("ioctl() for HIDIOCGRAWNAME failed", *error, __LINE__ - 3); + return; + } + + *error = 0; +} + +// Get device bus type, vendor and product information + +void HIDRAW_get_info(int32_t fd, int32_t *bustype, int32_t *vendor, int32_t *product, int32_t *error) +{ + assert(error != NULL); + + // Validate parameters + + if (fd < 0) + { + *error = EINVAL; + ERRORMSG("fd argument is invalid", *error, __LINE__ - 3); + return; + } + + if (bustype == NULL) + { + *error = EINVAL; + ERRORMSG("bustype argument is NULL", *error, __LINE__ - 3); + return; + } + + if (vendor == NULL) + { + *error = EINVAL; + ERRORMSG("vendor argument is NULL", *error, __LINE__ - 3); + return; + } + + if (product == NULL) + { + *error = EINVAL; + ERRORMSG("product argument is NULL", *error, __LINE__ - 3); + return; + } + + struct hidraw_devinfo devinfo; + + if (ioctl(fd, HIDIOCGRAWINFO, &devinfo) < 0) + { + *error = errno; + ERRORMSG("ioctl() for HIDIOCGRAWINFO failed", *error, __LINE__ - 3); + return; + } + + *bustype = devinfo.bustype; + *vendor = devinfo.vendor; + *product = devinfo.product; + *error = 0; +} diff --git a/3rdparty/libsimpleio/libsimpleio/libhidraw.h b/3rdparty/libsimpleio/libsimpleio/libhidraw.h new file mode 100644 index 0000000000..35841f3648 --- /dev/null +++ b/3rdparty/libsimpleio/libsimpleio/libhidraw.h @@ -0,0 +1,54 @@ +/* Raw HID device wrapper services for Linux */ + +// Copyright (C)2016-2023, Philip Munts dba Munts Technologies. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: +// +// * Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE +// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +// POSSIBILITY OF SUCH DAMAGE. + +#ifndef LIBHIDRAW_H +#define LIBHIDRAW_H + +#include +#include + +_BEGIN_STD_C + +extern void HIDRAW_open1(const char *name, int32_t *fd, int32_t *error); + +extern void HIDRAW_open2(int32_t VID, int32_t PID, int32_t *fd, int32_t *error); + +extern void HIDRAW_open3(int32_t VID, int32_t PID, const char *serial, + int32_t *fd, int32_t *error); + +extern void HIDRAW_close(int32_t fd, int32_t *error); + +extern void HIDRAW_get_name(int32_t fd, char *name, int32_t namesize, + int32_t *error); + +extern void HIDRAW_get_info(int32_t fd, int32_t *bustype, int32_t *vendor, + int32_t *product, int32_t *error); + +extern void HIDRAW_send(int32_t fd, void *buf, int32_t bufsize, int32_t *count, + int32_t *error); + +extern void HIDRAW_receive(int32_t fd, void *buf, int32_t bufsize, int32_t *count, + int32_t *error); + +_END_STD_C + +#endif diff --git a/3rdparty/libsimpleio/libsimpleio/libi2c.c b/3rdparty/libsimpleio/libsimpleio/libi2c.c new file mode 100644 index 0000000000..a0c0660c16 --- /dev/null +++ b/3rdparty/libsimpleio/libsimpleio/libi2c.c @@ -0,0 +1,147 @@ +/* I2C services for Linux */ + +// Copyright (C)2016-2023, Philip Munts dba Munts Technologies. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: +// +// * Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE +// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +// POSSIBILITY OF SUCH DAMAGE. + +#include +#include +#include +#include +#include +#include +#include +#ifndef I2C_M_RD +#include +#endif +#include +#include + +#include "errmsg.inc" +#include "libi2c.h" + +// Perform an I2C transaction + +void I2C_transaction(int32_t fd, int32_t slaveaddr, void *cmd, int32_t cmdlen, + void *resp, int32_t resplen, int32_t *error) +{ + assert(error != NULL); + + // Validate parameters + + if (fd < 0) + { + *error = EINVAL; + ERRORMSG("fd argument is invalid", *error, __LINE__ - 3); + return; + } + + if ((slaveaddr < 0) || (slaveaddr > 127)) + { + *error = EINVAL; + ERRORMSG("slaveaddr argument is invalid", *error, __LINE__ - 3); + return; + } + + if (cmdlen < 0) + { + *error = EINVAL; + ERRORMSG("cmdlen argument is invalid", *error, __LINE__ - 3); + return; + } + + if (resplen < 0) + { + *error = EINVAL; + ERRORMSG("resplen argument is invalid", *error, __LINE__ - 3); + return; + } + + if ((cmd == NULL) && (cmdlen != 0)) + { + *error = EINVAL; + ERRORMSG("cmd and cmdlen arguments are inconsistent", *error, __LINE__ - 3); + return; + } + + if ((cmd != NULL) && (cmdlen == 0)) + { + *error = EINVAL; + ERRORMSG("cmd and cmdlen arguments are inconsistent", *error, __LINE__ - 3); + return; + } + + if ((resp == NULL) && (resplen != 0)) + { + *error = EINVAL; + ERRORMSG("resp and resplen arguments are inconsistent", *error, __LINE__ - 3); + return; + } + + if ((resp != NULL) && (resplen == 0)) + { + *error = EINVAL; + ERRORMSG("resp and resplen arguments are inconsistent", *error, __LINE__ - 3); + return; + } + + if ((cmd == NULL) && (resp == NULL)) + { + *error = EINVAL; + ERRORMSG("cmd and resp arguments are both NULL", *error, __LINE__ - 3); + return; + } + + struct i2c_rdwr_ioctl_data cmdblk; + struct i2c_msg msgs[2]; + struct i2c_msg *p; + + memset(&cmdblk, 0, sizeof(cmdblk)); + cmdblk.msgs = msgs; + + memset(&msgs, 0, sizeof(msgs)); + p = msgs; + + if ((cmd != NULL) && (cmdlen != 0)) + { + p->addr = slaveaddr; + p->len = cmdlen; + p->buf = cmd; + p++; + cmdblk.nmsgs++; + } + + if ((resp != NULL) && (resplen != 0)) + { + p->addr = slaveaddr; + p->flags = I2C_M_RD; + p->len = resplen; + p->buf = resp; + cmdblk.nmsgs++; + } + + if (ioctl(fd, I2C_RDWR, &cmdblk) < 0) + { + *error = errno; + ERRORMSG("ioctl() for I2C_RDWR failed", *error, __LINE__ - 3); + return; + } + + *error = 0; +} diff --git a/3rdparty/libsimpleio/libsimpleio/libi2c.h b/3rdparty/libsimpleio/libsimpleio/libi2c.h new file mode 100644 index 0000000000..8694108de5 --- /dev/null +++ b/3rdparty/libsimpleio/libsimpleio/libi2c.h @@ -0,0 +1,40 @@ +/* I2C services for Linux */ + +// Copyright (C)2016-2023, Philip Munts dba Munts Technologies. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: +// +// * Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE +// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +// POSSIBILITY OF SUCH DAMAGE. + +#ifndef LIBI2C_H +#define LIBI2C_H + +#include +#include + +_BEGIN_STD_C + +extern void I2C_open(const char *name, int32_t *fd, int32_t *error); + +extern void I2C_close(int32_t fd, int32_t *error); + +extern void I2C_transaction(int32_t fd, int32_t slaveaddr, void *cmd, + int32_t cmdlen, void *resp, int32_t resplen, int32_t *error); + +_END_STD_C + +#endif diff --git a/3rdparty/libsimpleio/libsimpleio/libipv4.c b/3rdparty/libsimpleio/libsimpleio/libipv4.c new file mode 100644 index 0000000000..faf90cec07 --- /dev/null +++ b/3rdparty/libsimpleio/libsimpleio/libipv4.c @@ -0,0 +1,572 @@ +// Simple IPv4 TCP client and server routines + +// Copyright (C)2016-2023, Philip Munts dba Munts Technologies. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: +// +// * Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE +// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +// POSSIBILITY OF SUCH DAMAGE. + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "errmsg.inc" +#include "libipv4.h" + +// Resolve host name to IPV4 address + +void IPV4_resolve(const char *name, int32_t *addr, int32_t *error) +{ + assert(error != NULL); + + // Validate parameters + + if (name == NULL) + { + *error = EINVAL; + ERRORMSG("name argument is NULL", *error, __LINE__ - 3); + return; + } + + if (addr == NULL) + { + *error = EINVAL; + ERRORMSG("addr argument is NULL", *error, __LINE__ - 3); + return; + } + + struct hostent *he = gethostbyname2(name, AF_INET); + + if (he == NULL) + { + // Map h_errno to errno + + switch(h_errno) + { + case HOST_NOT_FOUND : + case NO_ADDRESS : + *error = EIO; + break; + + case TRY_AGAIN : + *error = EAGAIN; + ERRORMSG("gethostbyname2() failed", *error, __LINE__ - 15); + break; + + default : + *error = EIO; + ERRORMSG("gethostbyname2() failed", *error, __LINE__ - 20); + break; + } + + return; + } + + *addr = ntohl(*(int32_t *)he->h_addr); + *error = 0; +} + +// Convert IPV4 address to dotted decimal string + +void IPV4_ntoa(int32_t addr, char *dst, int32_t dstsize, int32_t *error) +{ + assert(error != NULL); + + // Validate parameters + + if (dst == NULL) + { + *error = EINVAL; + ERRORMSG("dst argument is NULL", *error, __LINE__ - 3); + return; + } + + if (dstsize < 16) + { + *error = EINVAL; + ERRORMSG("dstsize argument is too small", *error, __LINE__ - 3); + return; + } + + struct in_addr in; + in.s_addr = htonl(addr); + + memset(dst, 0, dstsize); + + strncpy(dst, inet_ntoa(in), dstsize - 1); + *error = 0; +} + +// Connect to a TCP server + +void TCP4_connect(int32_t addr, int32_t port, int32_t *fd, int32_t *error) +{ + assert(error != NULL); + + // Validate parameters + + if ((addr == 0x00000000) || (addr == 0xFFFFFFFF)) + { + *error = EINVAL; + ERRORMSG("addr argument is invalid", *error, __LINE__ - 3); + return; + } + + if ((port < 1) || (port > 65535)) + { + *error = EINVAL; + ERRORMSG("port argument is invalid", *error, __LINE__ - 3); + return; + } + + if (fd == NULL) + { + *error = EINVAL; + ERRORMSG("fd argument is NULL", *error, __LINE__ - 3); + return; + } + + // Attempt to create a socket + + int s; + struct sockaddr_in destaddr; + + s = socket(AF_INET, SOCK_STREAM, 0); + if (s < 0) + { + *error = errno; + ERRORMSG("socket() failed", *error, __LINE__ - 3); + return; + } + + // Build address structure for the specified server + + memset(&destaddr, 0, sizeof(destaddr)); + destaddr.sin_family = AF_INET; + destaddr.sin_addr.s_addr = htonl(addr); + destaddr.sin_port = htons(port); + + // Attempt to open connection to the server + + if (connect(s, (struct sockaddr *)&destaddr, sizeof(destaddr))) + { + *error = errno; + ERRORMSG("connect() failed", *error, __LINE__ - 3); + return; + } + + // Prevent SIGPIPE + + signal(SIGPIPE, SIG_IGN); + + *fd = s; + *error = 0; +} + +// Wait (block) for exactly one connection from a TCP client, then +// return a file descriptor for the new connection + +void TCP4_accept(int32_t addr, int32_t port, int32_t *fd, int32_t *error) +{ + assert(error != NULL); + + // Validate parameters + + if (addr == 0xFFFFFFFF) + { + *error = EINVAL; + ERRORMSG("addr argument is invalid", *error, __LINE__ - 3); + return; + } + + if ((port < 1) || (port > 65535)) + { + *error = EINVAL; + ERRORMSG("port argument is invalid", *error, __LINE__ - 3); + return; + } + + if (fd == NULL) + { + *error = EINVAL; + ERRORMSG("fd argument is NULL", *error, __LINE__ - 3); + return; + } + + // Attempt to create a socket + + int s1, s2; + struct sockaddr_in myaddr; + + s1 = socket(AF_INET, SOCK_STREAM, 0); + if (s1 < 0) + { + *error = errno; + ERRORMSG("socket() failed", *error, __LINE__ - 3); + return; + } + + // Attempt to bind socket + + memset(&myaddr, 0, sizeof(myaddr)); + myaddr.sin_family = AF_INET; + myaddr.sin_addr.s_addr = htonl(addr); + myaddr.sin_port = htons(port); + + if (bind(s1, (struct sockaddr *)&myaddr, sizeof(myaddr))) + { + *error = errno; + ERRORMSG("bind() failed", *error, __LINE__ - 3); + return; + } + + // Establish incoming connection queue + + if (listen(s1, 5)) + { + *error = errno; + ERRORMSG("listen() failed", *error, __LINE__ - 3); + return; + } + + // Wait for incoming connection + + s2 = accept(s1, NULL, NULL); + if (s2 == -1) + { + *error = errno; + ERRORMSG("accept() failed", *error, __LINE__ - 3); + return; + } + + close(s1); + + // Prevent SIGPIPE + + signal(SIGPIPE, SIG_IGN); + + *fd = s2; + *error = 0; +} + +// Wait (block) until a client connects, then fork and return a file +// descriptor to the child process + +void TCP4_server(int32_t addr, int32_t port, int32_t *fd, int32_t *error) +{ + assert(error != NULL); + + // Validate parameters + + if (addr == 0xFFFFFFFF) + { + *error = EINVAL; + ERRORMSG("addr argument is invalid", *error, __LINE__ - 3); + return; + } + + if ((port < 1) || (port > 65535)) + { + *error = EINVAL; + ERRORMSG("port argument is invalid", *error, __LINE__ - 3); + return; + } + + if (fd == NULL) + { + *error = EINVAL; + ERRORMSG("fd argument is NULL", *error, __LINE__ - 3); + return; + } + + // Attempt to create a socket + + int s1, s2; + struct sockaddr_in myaddr; + + s1 = socket(AF_INET, SOCK_STREAM, 0); + if (s1 < 0) + { + *error = errno; + ERRORMSG("socket() failed", *error, __LINE__ - 3); + return; + } + + // Attempt to bind socket + + memset(&myaddr, 0, sizeof(myaddr)); + myaddr.sin_family = AF_INET; + myaddr.sin_addr.s_addr = htonl(addr); + myaddr.sin_port = htons(port); + + if (bind(s1, (struct sockaddr *)&myaddr, sizeof(myaddr))) + { + *error = errno; + ERRORMSG("bind() failed", *error, __LINE__ - 3); + return; + } + + // Establish incoming connection queue + + if (listen(s1, 5)) + { + *error = errno; + ERRORMSG("listen() failed", *error, __LINE__ - 3); + return; + } + + // Prevent zombie children + + signal(SIGCHLD, SIG_IGN); + + // Main service loop + + for (;;) + { + // Wait for incoming connection + + s2 = accept(s1, NULL, NULL); + if (s2 == -1) + { + *error = errno; + ERRORMSG("accept() failed", *error, __LINE__ - 3); + return; + } + + // Spawn child process for the new connection + + if (fork() == 0) + { + close(s1); + + // Prevent SIGPIPE + + signal(SIGPIPE, SIG_IGN); + + *error = 0; + *fd = s2; + return; + } + + close(s2); + } +} + +// Open a UDP socket + +void UDP4_open(int32_t addr, int32_t port, int32_t *fd, int32_t *error) +{ + assert(error != NULL); + + // Validate parameters + + if (fd == NULL) + { + *error = EINVAL; + ERRORMSG("fd argument is NULL", *error, __LINE__ - 3); + return; + } + + if (port < 0) + { + *fd = -1; + *error = EINVAL; + ERRORMSG("port argument is invalid", *error, __LINE__ - 4); + return; + } + + // Create the UDP socket + + struct sockaddr_in srcaddr; + + *fd = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); + if (*fd < 0) + { + *fd = -1; + *error = errno; + return; + } + + // Bind the socket to the specified interface(s) + + memset(&srcaddr, 0, sizeof(srcaddr)); + + srcaddr.sin_family = AF_INET; + srcaddr.sin_addr.s_addr = htonl(addr); + srcaddr.sin_port = htons(port); + + if (bind(*fd, (struct sockaddr *) &srcaddr, sizeof(srcaddr)) < 0) + { + close(*fd); + *fd = -1; + *error = errno; + return; + } + + *error = 0; +} + +// Send a UDP datagram + +void UDP4_send(int32_t fd, int32_t addr, int32_t port, void *buf, + int32_t bufsize, int32_t flags, int32_t *count, int32_t *error) +{ + assert(error != NULL); + + // Validate parameters + + if (fd < 0) + { + *error = EINVAL; + ERRORMSG("fd argument is invalid", *error, __LINE__ - 3); + return; + } + + if (port < 1) + { + *error = EINVAL; + ERRORMSG("port argument is invalid", *error, __LINE__ - 3); + return; + } + + if (buf == NULL) + { + *error = EINVAL; + ERRORMSG("buf argument is NULL", *error, __LINE__ - 3); + return; + } + + if (bufsize < 1) + { + *error = EINVAL; + ERRORMSG("bufsize argument is invalid", *error, __LINE__ - 3); + return; + } + + if (count == NULL) + { + *error = EINVAL; + ERRORMSG("count argument is NULL", *error, __LINE__ - 3); + return; + } + + // Build the destination address structure + + struct sockaddr_in dstaddr; + + memset(&dstaddr, 0, sizeof(dstaddr)); + + dstaddr.sin_family = AF_INET; + dstaddr.sin_addr.s_addr = htonl(addr); + dstaddr.sin_port = htons(port); + + // Send the UDP datagram + + *count = sendto(fd, buf, bufsize, 0, (struct sockaddr *) &dstaddr, + sizeof(dstaddr)); + + if (*count < 0) + { + *count = 0; + *error = errno; + return; + } + + *error = 0; +} + +// Receive a UDP datagram + +void UDP4_receive(int32_t fd, int32_t *addr, int32_t *port, void *buf, + int32_t bufsize, int32_t flags, int32_t *count, int32_t *error) +{ + assert(error != NULL); + + // Validate parameters + + if (fd < 0) + { + *error = EINVAL; + ERRORMSG("fd argument is invalid", *error, __LINE__ - 3); + return; + } + + if (addr == NULL) + { + *error = EINVAL; + ERRORMSG("addr argument is NULL", *error, __LINE__ - 3); + return; + } + + if (port == NULL) + { + *error = EINVAL; + ERRORMSG("port argument is NULL", *error, __LINE__ - 3); + return; + } + + if (buf == NULL) + { + *error = EINVAL; + ERRORMSG("buf argument is NULL", *error, __LINE__ - 3); + return; + } + + if (bufsize < 1) + { + *error = EINVAL; + ERRORMSG("bufsize argument is invalid", *error, __LINE__ - 3); + return; + } + + if (count == NULL) + { + *error = EINVAL; + ERRORMSG("count argument is NULL", *error, __LINE__ - 3); + return; + } + + // Receive a UDP datagram + + struct sockaddr_in srcaddr; + socklen_t addrlen = sizeof(srcaddr); + + memset(&srcaddr, 0, sizeof(srcaddr)); + + *count = recvfrom(fd, buf, bufsize, flags, (struct sockaddr *) &srcaddr, + &addrlen); + + if (*count < 0) + { + *count = 0; + *error = errno; + return; + } + + // Retrieve sender IP address and UDP port number + + *addr = ntohl(srcaddr.sin_addr.s_addr); + *port = ntohs(srcaddr.sin_port); + *error = 0; +} diff --git a/3rdparty/libsimpleio/libsimpleio/libipv4.h b/3rdparty/libsimpleio/libsimpleio/libipv4.h new file mode 100644 index 0000000000..89ba4813e4 --- /dev/null +++ b/3rdparty/libsimpleio/libsimpleio/libipv4.h @@ -0,0 +1,91 @@ +// Simple IPv4 TCP client and server routines + +// Copyright (C)2016-2023, Philip Munts dba Munts Technologies. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: +// +// * Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE +// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +// POSSIBILITY OF SUCH DAMAGE. + +#ifndef _LIBIPV4_H_ +#define _LIBIPV4_H_ + +#include +#include + +_BEGIN_STD_C + +// Resolve host name to IPV4 address + +extern void IPV4_resolve(const char *name, int32_t *addr, int32_t *error); + +// Convert IPV4 address to dotted decimal string + +extern void IPV4_ntoa(int32_t addr, char *dst, int32_t dstsize, + int32_t *error); + +// Connect to a TCP server + +extern void TCP4_connect(int32_t addr, int32_t port, + int32_t *fd, int32_t *error); + +// Wait (block) for exactly one connection from a TCP client, then +// return a file descriptor for the new connection + +extern void TCP4_accept(int32_t addr, int32_t port, + int32_t *fd, int32_t *error); + +// Wait (block) until a client connects, then fork and return a file +// descriptor for the new connection to the child process + +extern void TCP4_server(int32_t addr, int32_t port, + int32_t *fd, int32_t *error); + +// Close connection + +extern void TCP4_close(int32_t fd, int32_t *error); + +// Send data + +extern void TCP4_send(int32_t fd, void *buf, int32_t bufsize, int32_t *count, + int32_t *error); + +// Receive data + +extern void TCP4_receive(int32_t fd, void *buf, int32_t bufsize, + int32_t *count, int32_t *error); + +// Open a UDP socket + +extern void UDP4_open(int32_t addr, int32_t port, int32_t *fd, int32_t *error); + +// Close a UDP socket + +extern void UDP4_close(int32_t fd, int32_t *error); + +// Send a UDP datagram + +extern void UDP4_send(int32_t fd, int32_t addr, int32_t port, void *buf, + int32_t bufsize, int32_t flags, int32_t *count, int32_t *error); + +// Receive a UDP datagram + +extern void UDP4_receive(int32_t fd, int32_t *addr, int32_t *port, void *buf, + int32_t bufsize, int32_t flags, int32_t *count, int32_t *error); + +_END_STD_C + +#endif diff --git a/3rdparty/libsimpleio/libsimpleio/liblinux.c b/3rdparty/libsimpleio/libsimpleio/liblinux.c new file mode 100644 index 0000000000..47a8cdcfc9 --- /dev/null +++ b/3rdparty/libsimpleio/libsimpleio/liblinux.c @@ -0,0 +1,777 @@ +/* Linux syscall wrappers. These are primarily for the benefit of other */ +/* programming languages, such as Ada, C#, Free Pascal, Go, etc. */ + +// Copyright (C)2016-2023, Philip Munts dba Munts Technologies. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: +// +// * Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE +// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +// POSSIBILITY OF SUCH DAMAGE. + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "errmsg.inc" +#include "liblinux.h" + +// Detach process from the controlling terminal and run it in the background + +void LINUX_detach(int32_t *error) +{ + assert(error != NULL); + + if (daemon(0, 0)) + { + *error = errno; + ERRORMSG("daemon() failed", *error, __LINE__ - 3); + return; + } + + *error = 0; +} + +// Drop privileges from superuser to ordinary user + +void LINUX_drop_privileges(const char *username, int32_t *error) +{ + assert(error != NULL); + + // Validate parameters + + if (username == NULL) + { + *error = EINVAL; + ERRORMSG("username argument is NULL", *error, __LINE__ - 3); + return; + } + + struct passwd *pwent; + + // Look up the user name + + pwent = getpwnam(username); + if (pwent == NULL) + { + *error = errno; + ERRORMSG("getpwnam() failed", *error, __LINE__ - 4); + return; + } + + // Set group membership + + if (initgroups(pwent->pw_name, pwent->pw_gid)) + { + *error = errno; + ERRORMSG("initgroups() failed", *error, __LINE__ - 3); + return; + } + + // Change gid + + if (setgid(pwent->pw_gid)) + { + *error = errno; + ERRORMSG("setgid() failed", *error, __LINE__ - 3); + return; + } + + // Change uid + + if (setuid(pwent->pw_uid)) + { + *error = errno; + ERRORMSG("setuid() failed", *error, __LINE__ - 3); + return; + } + + *error = 0; +} + +// Open syslog connection + +void LINUX_openlog(const char *id, int32_t options, int32_t facility, + int32_t *error) +{ + assert(error != NULL); + + // Validate parameters + + if (id == NULL) + { + *error = EINVAL; + ERRORMSG("id argument is NULL", *error, __LINE__ - 3); + return; + } + + if ((facility >> 3) >= LOG_NFACILITIES) + { + *error = EINVAL; + ERRORMSG("facility argument is invalid", *error, __LINE__ - 3); + return; + } + + // Save the identity string in a static buffer + + static char ident[256]; + memset(ident, 0, sizeof(ident)); + strncpy(ident, id, sizeof(ident) - 1); + + openlog(strlen(ident) ? ident : NULL, options, facility); + *error = 0; +} + +// Post syslog message + +void LINUX_syslog(int32_t priority, const char *msg, int32_t *error) +{ + assert(error != NULL); + + if (msg == NULL) + { + *error = EINVAL; + ERRORMSG("msg argument is NULL", *error, __LINE__ - 3); + return; + } + + syslog(priority, "%s", msg); + *error = 0; +} + +// Close syslog connection + +void LINUX_closelog(int32_t *error) +{ + assert(error != NULL); + + closelog(); + *error = 0; +} + +// Retrieve errno value + +int32_t LINUX_errno(void) +{ + return errno; +} + +// Retrieve errno message + +void LINUX_strerror(int32_t error, char *buf, int32_t bufsize) +{ + // Validate parameters + + if (buf == NULL) + { + ERRORMSG("buf argument is NULL", EINVAL, __LINE__ - 2); + return; + } + + if (bufsize < 16) + { + ERRORMSG("bufsize argument is too small", EINVAL, __LINE__ - 2); + return; + } + + memset(buf, 0, bufsize); + strerror_r(error, buf, bufsize); +} + +// Wait for an event on one or more files + +void LINUX_poll(int32_t numfiles, int32_t *files, int32_t *events, + int32_t *results, int32_t timeout, int32_t *error) +{ + assert(error != NULL); + + // Validate parameters + + if (numfiles < 1) + { + *error = EINVAL; + ERRORMSG("numfiles argument is invalid", *error, __LINE__ - 3); + return; + } + + if (files == NULL) + { + *error = EINVAL; + ERRORMSG("files argument is NULL", *error, __LINE__ - 3); + return; + } + + if (events == NULL) + { + *error = EINVAL; + ERRORMSG("events argument is NULL", *error, __LINE__ - 3); + return; + } + + if (results == NULL) + { + *error = EINVAL; + ERRORMSG("results argument is NULL", *error, __LINE__ - 3); + return; + } + + if (timeout < -1) + { + *error = EINVAL; + ERRORMSG("timeout argument is out of range", *error, __LINE__ - 3); + return; + } + + // Prepare the poll request structure + + struct pollfd fds[10]; + + int i; + + for (i = 0; i < numfiles; i++) + { + fds[i].fd = files[i]; + fds[i].events = events[i]; + fds[i].revents = 0; + } + + // Wait for something to happen + + int count = poll(fds, numfiles, timeout); + + // Timeout occurred + + if (count == 0) + { + *error = EAGAIN; + return; + } + + // An error occurred + + if (count < 0) + { + *error = errno; + ERRORMSG("poll() failed", *error, __LINE__ - 3); + return; + } + + // An event occurred + + for (i = 0; i < numfiles; i++) + results[i] = fds[i].revents; + + *error = 0; +} + +// Sleep for some number of microseconds + +void LINUX_usleep(int32_t microseconds, int32_t *error) +{ + if (usleep(microseconds)) + *error = errno; + else + *error = 0; +} + +// Execute a shell command string + +void LINUX_command(const char *cmd, int32_t *status, int32_t *error) +{ + assert(error != NULL); + + // Validate parameters + + if (status == NULL) + { + *error = EINVAL; + ERRORMSG("status argument is NULL", *error, __LINE__ - 3); + return; + } + + if (cmd == NULL) + { + *error = EINVAL; + *status = 0; + ERRORMSG("cmd argument is NULL", *error, __LINE__ - 4); + return; + } + + int ret = system(cmd); + + if (ret < 0) + { + *error = errno; + *status = 0; + ERRORMSG("system() failed", *error, __LINE__ - 4); + return; + } + + *error = 0; + *status = WEXITSTATUS(ret); +} + +// Open a file descriptor + +void LINUX_open(const char *name, int32_t flags, int32_t mode, int32_t *fd, + int32_t *error) +{ + assert(error != NULL); + + // Validate parameters + + if (fd == NULL) + { + *error = EINVAL; + ERRORMSG("fd argument is NULL", *error, __LINE__ - 3); + return; + } + + if (name == NULL) + { + *fd = -1; + *error = EINVAL; + ERRORMSG("name argument is NULL", *error, __LINE__ - 4); + return; + } + + *fd = open(name, flags, mode); + if (*fd < 0) + { + *fd = -1; + *error = errno; + ERRORMSG("open() failed", *error, __LINE__ - 5); + return; + } + + *error = 0; +} + +// Open a file descriptor for read access + +void LINUX_open_read(const char *name, int32_t *fd, int32_t *error) +{ + assert(error != NULL); + + // Validate parameters + + if (fd == NULL) + { + *error = EINVAL; + ERRORMSG("fd argument is NULL", *error, __LINE__ - 3); + return; + } + + if (name == NULL) + { + *fd = -1; + *error = EINVAL; + ERRORMSG("name argument is NULL", *error, __LINE__ - 4); + return; + } + + *fd = open(name, O_RDONLY); + if (*fd < 0) + { + *fd = -1; + *error = errno; + ERRORMSG("open() failed", *error, __LINE__ - 5); + return; + } + + *error = 0; +} + +// Open a file descriptor for write access + +void LINUX_open_write(const char *name, int32_t *fd, int32_t *error) +{ + assert(error != NULL); + + // Validate parameters + + if (fd == NULL) + { + *error = EINVAL; + ERRORMSG("fd argument is NULL", *error, __LINE__ - 3); + return; + } + + if (name == NULL) + { + *fd = -1; + *error = EINVAL; + ERRORMSG("name argument is NULL", *error, __LINE__ - 4); + return; + } + + *fd = open(name, O_WRONLY); + if (*fd < 0) + { + *fd = -1; + *error = errno; + ERRORMSG("open() failed", *error, __LINE__ - 5); + return; + } + + *error = 0; +} + +// Open a file descriptor for read/write access + +void LINUX_open_readwrite(const char *name, int32_t *fd, int32_t *error) +{ + assert(error != NULL); + + // Validate parameters + + if (fd == NULL) + { + *error = EINVAL; + ERRORMSG("fd argument is NULL", *error, __LINE__ - 3); + return; + } + + if (name == NULL) + { + *fd = -1; + *error = EINVAL; + ERRORMSG("name argument is NULL", *error, __LINE__ - 4); + return; + } + + *fd = open(name, O_RDWR); + if (*fd < 0) + { + *fd = -1; + *error = errno; + ERRORMSG("open() failed", *error, __LINE__ - 5); + return; + } + + *error = 0; +} + +// Open a file descriptor for create/overwrite access + +void LINUX_open_create(const char *name, int32_t mode, int32_t *fd, + int32_t *error) +{ + assert(error != NULL); + + // Validate parameters + + if (fd == NULL) + { + *error = EINVAL; + ERRORMSG("fd argument is NULL", *error, __LINE__ - 3); + return; + } + + if (name == NULL) + { + *fd = -1; + *error = EINVAL; + ERRORMSG("name argument is NULL", *error, __LINE__ - 4); + return; + } + + *fd = creat(name, mode); + if (*fd < 0) + { + *fd = -1; + *error = errno; + ERRORMSG("creat() failed", *error, __LINE__ - 5); + return; + } + + *error = 0; +} + +// Close a file descriptor + +void LINUX_close(int32_t fd, int32_t *error) +{ + assert(error != NULL); + + // Validate parameters + + if (fd < 0) + { + *error = EINVAL; + ERRORMSG("fd argument is invalid", *error, __LINE__ - 3); + return; + } + + if (close(fd)) + { + *error = errno; + ERRORMSG("close() failed", *error, __LINE__ - 3); + return; + } + + *error = 0; +} + +// Read from a file descriptor + +void LINUX_read(int32_t fd, void *buf, int32_t bufsize, int32_t *count, + int32_t *error) +{ + assert(error != NULL); + + // Validate parameters + + if (fd < 0) + { + *error = EINVAL; + ERRORMSG("fd argument is invalid", *error, __LINE__ - 3); + return; + } + + if (buf == NULL) + { + *error = EINVAL; + ERRORMSG("buf argument is NULL", *error, __LINE__ - 3); + return; + } + + if (bufsize < 1) + { + *error = EINVAL; + ERRORMSG("bufsize argument is invalid", *error, __LINE__ - 3); + return; + } + + if (count == NULL) + { + *error = EINVAL; + ERRORMSG("count argument is NULL", *error, __LINE__ - 3); + return; + } + + int32_t len = read(fd, buf, bufsize); + if (len < 0) + { + *count = 0; + *error = errno; + ERRORMSG("read() failed", *error, __LINE__ - 5); + return; + } + + *count = len; + *error = 0; +} + +// Write to a file descriptor + +void LINUX_write(int32_t fd, void *buf, int32_t bufsize, int32_t *count, + int32_t *error) +{ + assert(error != NULL); + + // Validate parameters + + if (fd < 0) + { + *error = EINVAL; + ERRORMSG("fd argument is invalid", *error, __LINE__ - 3); + return; + } + + if (buf == NULL) + { + *error = EINVAL; + ERRORMSG("buf argument is NULL", *error, __LINE__ - 3); + return; + } + + if (bufsize < 1) + { + *error = EINVAL; + ERRORMSG("bufsize argument is invalid", *error, __LINE__ - 3); + return; + } + + if (count == NULL) + { + *error = EINVAL; + ERRORMSG("count argument is NULL", *error, __LINE__ - 3); + return; + } + + int32_t len = write(fd, buf, bufsize); + if (len < 0) + { + *count = 0; + *error = errno; + ERRORMSG("write() failed", *error, __LINE__ - 5); + return; + } + + *count = len; + *error = 0; +} + +// Open a pipe from another program + +void LINUX_popen_read(const char *cmd, void **stream, int32_t *error) +{ + assert(error != NULL); + + // Validate parameters + + if (cmd == NULL) + { + *error = EINVAL; + ERRORMSG("cmd argument is NULL", *error, __LINE__ - 3); + return; + } + + if (strlen(cmd) == 0) + { + *error = EINVAL; + ERRORMSG("cmd argument is empty", *error, __LINE__ - 3); + return; + } + + if (stream == NULL) + { + *error = EINVAL; + ERRORMSG("stream argument is NULL", *error, __LINE__ - 3); + return; + } + + *stream = popen(cmd, "re"); + + if (*stream == NULL) + { + *error = errno; + ERRORMSG("popen() failed", *error, __LINE__ - 5); + return; + } + + *error = 0; +} + +// Open a pipe to another program + +void LINUX_popen_write(const char *cmd, void **stream, int32_t *error) +{ + assert(error != NULL); + + // Validate parameters + + if (cmd == NULL) + { + *error = EINVAL; + ERRORMSG("cmd argument is NULL", *error, __LINE__ - 3); + return; + } + + if (strlen(cmd) == 0) + { + *error = EINVAL; + ERRORMSG("cmd argument is empty", *error, __LINE__ - 3); + return; + } + + if (stream == NULL) + { + *error = EINVAL; + ERRORMSG("stream argument is NULL", *error, __LINE__ - 3); + return; + } + + *stream = popen(cmd, "we"); + + if (*stream == NULL) + { + *error = errno; + ERRORMSG("popen() failed", *error, __LINE__ - 5); + return; + } + + *error = 0; +} + +// Close a pipe + +void LINUX_pclose(void *stream, int32_t *error) +{ + assert(error != NULL); + + // Validate parameters + + if (stream == NULL) + { + *error = EINVAL; + ERRORMSG("stream argument is NULL", *error, __LINE__ - 3); + return; + } + + // Close the pipe + + if (pclose(stream) < 0) + { + *error = errno; + ERRORMSG("pclose() failed", *error, __LINE__ - 3); + return; + } + + *error = 0; +} + +// It is hard to index a C pointer to pointer(s) in Ada + +void *LINUX_indexpp(void **p, int32_t i) +{ + if (p == NULL) return NULL; + if (i < 0) return NULL; + return p[i]; +} + +// Function aliases + +#define ALIAS(orig) __attribute__((weak, alias(orig))) + +void ADC_close(int32_t fd, int32_t *error) ALIAS("LINUX_close"); +void DAC_close(int32_t fd, int32_t *error) ALIAS("LINUX_close"); +void EVENT_close(int32_t fd, int32_t *error) ALIAS("LINUX_close"); +void GPIO_close(int32_t fd, int32_t *error) ALIAS("LINUX_close"); +void GPIO_line_close(int32_t fd, int32_t *error) ALIAS("LINUX_close"); +void HIDRAW_close(int32_t fd, int32_t *error) ALIAS("LINUX_close"); +void HIDRAW_open1(const char *name, int32_t *fd, int32_t *error) ALIAS("LINUX_open_readwrite"); +void HIDRAW_receive(int32_t fd, void *buf, int32_t bufsize, int32_t *count, int32_t *error) ALIAS("LINUX_read"); +void HIDRAW_send(int32_t fd, void *buf, int32_t bufsize, int32_t *count, int32_t *error) ALIAS("LINUX_write"); +void I2C_close(int32_t fd, int32_t *error) ALIAS("LINUX_close"); +void I2C_open(const char *name, int32_t *fd, int32_t *error) ALIAS("LINUX_open_readwrite"); +void PWM_close(int32_t fd, int32_t *error) ALIAS("LINUX_close"); +void SERIAL_close(int32_t fd, int32_t *error) ALIAS("LINUX_close"); +void SERIAL_receive(int32_t fd, void *buf, int32_t bufsize, int32_t *count, int32_t *error) ALIAS("LINUX_read"); +void SERIAL_send(int32_t fd, void *buf, int32_t bufsize, int32_t *count, int32_t *error) ALIAS("LINUX_write"); +void SPI_close(int32_t fd, int32_t *error) ALIAS("LINUX_close"); +void TCP4_close(int32_t fd, int32_t *error) ALIAS("LINUX_close"); +void TCP4_receive(int32_t fd, void *buf, int32_t bufsize, int32_t *count, int32_t *error) ALIAS("LINUX_read"); +void TCP4_send(int32_t fd, void *buf, int32_t bufsize, int32_t *count, int32_t *error) ALIAS("LINUX_write"); +void UDP4_close(int32_t fd, int32_t *error) ALIAS("LINUX_close"); +void WATCHDOG_close(int32_t fd, int32_t *error) ALIAS("LINUX_close"); +void WATCHDOG_open(const char *name, int32_t *fd, int32_t *error) ALIAS("LINUX_open_readwrite"); diff --git a/3rdparty/libsimpleio/libsimpleio/liblinux.h b/3rdparty/libsimpleio/libsimpleio/liblinux.h new file mode 100644 index 0000000000..2d30df3551 --- /dev/null +++ b/3rdparty/libsimpleio/libsimpleio/liblinux.h @@ -0,0 +1,132 @@ +/* Linux syscall wrappers. These are primarily for the benefit of other */ +/* programming languages, such as Ada, C#, Free Pascal, Go, etc. */ + +// Copyright (C)2016-2023, Philip Munts dba Munts Technologies. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: +// +// * Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE +// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +// POSSIBILITY OF SUCH DAMAGE. + +#ifndef LIBLINUX_H +#define LIBLINUX_H + +#include +#include +#include +#include + +#define LOG_PROGNAME "" + +_BEGIN_STD_C + +// Detach process from the controlling terminal and run it in the background + +extern void LINUX_detach(int32_t *error); + +// Drop privileges from superuser to ordinary user + +extern void LINUX_drop_privileges(const char *username, int32_t *error); + +// Open syslog connection + +extern void LINUX_openlog(const char *id, int32_t options, int32_t facility, + int32_t *error); + +// Post syslog message + +extern void LINUX_syslog(int32_t priority, const char *msg, int32_t *error); + +// Close syslog connection + +extern void LINUX_closelog(int32_t *error); + +// Retrieve errno value + +extern int32_t LINUX_errno(void); + +// Retrieve errno message + +extern void LINUX_strerror(int32_t error, char *buf, int32_t bufsize); + +// Wait for an event on one or more files + +extern void LINUX_poll(int32_t numfiles, int32_t *files, int32_t *events, + int32_t *results, int32_t timeout, int32_t *error); + +// Sleep for some number of microseconds + +extern void LINUX_usleep(int32_t microseconds, int32_t *error); + +// Execute a shell command string + +extern void LINUX_command(const char *cmd, int32_t *ret, int32_t *error); + +// Open a file descriptor + +extern void LINUX_open(const char *name, int32_t flags, int32_t mode, + int32_t *fd, int32_t *error); + +// Open a file descriptor for read access + +extern void LINUX_open_read(const char *name, int32_t *fd, int32_t *error); + +// Open a file descriptor for write access + +extern void LINUX_open_write(const char *name, int32_t *fd, int32_t *error); + +// Open a file descriptor for read/write access + +extern void LINUX_open_readwrite(const char *name, int32_t *fd, int32_t *error); + +// Open a file descriptor for create/overwrite access + +extern void LINUX_open_create(const char *name, int32_t mode, int32_t *fd, + int32_t *error); + +// Close a file descriptor + +extern void LINUX_close(int32_t fd, int32_t *error); + +// Read from a file descriptor + +extern void LINUX_read(int32_t fd, void *buf, int32_t bufsize, int32_t *count, + int32_t *error); + +// Write to a file descriptor + +extern void LINUX_write(int32_t fd, void *buf, int32_t bufsize, int32_t *count, + int32_t *error); + +// Open a pipe from another program + +extern void LINUX_popen_read(const char *cmd, void **stream, int32_t *error); + +// Open a pipe to another program + +extern void LINUX_popen_write(const char *cmd, void **stream, int32_t *error); + +// Close a pipe + +extern void LINUX_pclose(void *stream, int32_t *error); + +// It is hard to index a C pointer to pointer(s) in Ada + +extern void *LINUX_indexpp(void **p, int32_t i); + +_END_STD_C + +#endif diff --git a/3rdparty/libsimpleio/libsimpleio/liblinx.c b/3rdparty/libsimpleio/libsimpleio/liblinx.c new file mode 100644 index 0000000000..71357ac2e5 --- /dev/null +++ b/3rdparty/libsimpleio/libsimpleio/liblinx.c @@ -0,0 +1,448 @@ +// LabView LINX device firmware definitions + +// Copyright (C)2016-2023, Philip Munts dba Munts Technologies. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: +// +// * Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE +// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +// POSSIBILITY OF SUCH DAMAGE. + +#include +#include +#include +#include +#include + +#include "liblinx.h" + +void LINX_transmit_command(int32_t fd, LINX_command_t *cmd, int32_t *error) +{ + assert(error != NULL); + + uint8_t *p = (uint8_t *) cmd; + uint8_t checksum = 0; + int i; + int status; + + // Validate parameters + + if (fd < 0) + { + *error = EINVAL; + return; + } + + if (cmd == NULL) + { + *error = EINVAL; + return; + } + + // Validate frame structure + + if (cmd->SoF != LINX_SOF) + { + *error = EINVAL; + return; + } + + if ((cmd->PacketSize < 7) || (cmd->PacketSize > sizeof(LINX_command_t))) + { + *error = EINVAL; + return; + } + + // Convert PacketNum and Command fields to network byte order + + cmd->PacketNum = htons(cmd->PacketNum); + cmd->Command = htons(cmd->Command); + + // Calculate packet checksum + + for (i = 0; i < cmd->PacketSize - 1; i++) + checksum += *p++; + + cmd->Args[cmd->PacketSize-7] = checksum; + + // Transmit the frame + + status = write(fd, cmd, cmd->PacketSize); + if (status < 0) + { + *error = errno; + return; + } + + *error = 0; +} + +void LINX_receive_command(int32_t fd, LINX_command_t *cmd, int32_t *count, int32_t *error) +{ + assert(error != NULL); + + int status; + uint8_t b; + uint8_t checksum; + uint8_t *p; + int i; + + // Validate parameters + + if (fd < 0) + { + *error = EINVAL; + return; + } + + if (cmd == NULL) + { + *error = EINVAL; + return; + } + + if (count == NULL) + { + *error = EINVAL; + return; + } + + // Check for buffer overrun + + if (*count >= sizeof(LINX_command_t)) + { + *count = 0; + *error = EINVAL; + return; + } + + // Read next incoming byte + + status = read(fd, &b, 1); + + // read() failed + + if (status < 0) + { + *count = 0; + *error = errno; + return; + } + + // No data available, connection was closed + + if (status == 0) + { + *count = 0; + *error = EPIPE; + return; + } + + // Assemble frame + + switch (*count) + { + case 0 : + if (b != LINX_SOF) + { + *count = 0; + *error = EINVAL; + return; + } + cmd->SoF = b; + break; + + case 1 : + if ((b < 7) || (b > sizeof(LINX_command_t))) + { + *count = 0; + *error = EINVAL; + return; + } + cmd->PacketSize = b; + break; + + case 2 : + cmd->PacketNum = b << 8; + break; + + case 3 : + cmd->PacketNum += b; + break; + + case 4 : + cmd->Command = b << 8; + break; + + case 5 : + cmd->Command += b; + break; + + default : + cmd->Args[*count-6] = b; + break; + } + + // Increment byte counter + + *count += 1; + + // Check for complete frame + + if ((*count < 6) || (*count < cmd->PacketSize)) + { + *error = EAGAIN; + return; + } + + // Calculate frame checksum + + p = (uint8_t *) cmd; + + checksum = 0; + + for (i = 0; i < *count-1; i++) + checksum += p[i]; + + // Validate frame checksum + + if (checksum != cmd->Args[*count-7]) + { + *count = 0; + *error = EINVAL; + return; + } + + // Frame is complete and valid + + *count = 0; + *error = 0; +} + +void LINX_transmit_response(int32_t fd, LINX_response_t *resp, int32_t *error) +{ + assert(error != NULL); + + uint8_t *p = (uint8_t *) resp; + uint8_t checksum = 0; + int i; + int status; + + // Validate parameters + + if (fd < 0) + { + *error = EINVAL; + return; + } + + if (resp == NULL) + { + *error = EINVAL; + return; + } + + // Validate frame structure + + if (resp->SoF != LINX_SOF) + { + *error = EINVAL; + return; + } + + if ((resp->PacketSize < 6) || (resp->PacketSize > sizeof(LINX_response_t))) + { + *error = EINVAL; + return; + } + + // Convert PacketSize and Command fields to network byte order + + resp->PacketNum = htons(resp->PacketNum); + + // Calculate packet checksum + + for (i = 0; i < resp->PacketSize - 1; i++) + checksum += *p++; + + resp->Data[resp->PacketSize-6] = checksum; + + // Transmit the frame + + status = write(fd, resp, resp->PacketSize); + if (status < 0) + { + *error = errno; + return; + } + + *error = 0; +} + +void LINX_receive_response(int32_t fd, LINX_response_t *resp, int32_t *count, int32_t *error) +{ + assert(error != NULL); + + int status; + uint8_t b; + uint8_t checksum; + uint8_t *p; + int i; + + // Validate parameters + + if (fd < 0) + { + *error = EINVAL; + return; + } + + if (resp == NULL) + { + *error = EINVAL; + return; + } + + if (count == NULL) + { + *error = EINVAL; + return; + } + + // Check for buffer overrun + + if (*count >= sizeof(LINX_response_t)) + { + *count = 0; + *error = EINVAL; + return; + } + + // Read next incoming byte + + status = read(fd, &b, 1); + + // read() failed + + if (status < 0) + { + *count = 0; + *error = errno; + return; + } + + // No data available, connection was closed + + if (status == 0) + { + *count = 0; + *error = EPIPE; + return; + } + + // Assemble frame + + switch (*count) + { + case 0 : + if (b != LINX_SOF) + { + *count = 0; + *error = EINVAL; + return; + } + resp->SoF = b; + break; + + case 1 : + if ((b < 6) || (b > sizeof(LINX_response_t))) + { + *count = 0; + *error = EINVAL; + return; + } + resp->PacketSize = b; + break; + + case 2 : + resp->PacketNum = b << 8; + break; + + case 3 : + resp->PacketNum += b; + break; + + case 4 : + resp->Status = b; + break; + + default : + resp->Data[*count-5] = b; + break; + } + + // Increment byte counter + + *count += 1; + + // Check for complete frame + + if ((*count < 5) || (*count < resp->PacketSize)) + { + *error = EAGAIN; + return; + } + + // Calculate frame checksum + + p = (uint8_t *) resp; + + checksum = 0; + + for (i = 0; i < *count-1; i++) + checksum += p[i]; + + // Validate frame checksum + + if (checksum != resp->Data[*count-6]) + { + *count = 0; + *error = EINVAL; + return; + } + + *count = 0; + *error = 0; +} + +uint16_t LINX_makeu16(uint8_t b0, uint8_t b1) +{ + return (b0 << 8) + b1; +} + +uint32_t LINX_makeu32(uint8_t b0, uint8_t b1, uint8_t b2, uint8_t b3) +{ + return (b0 << 24) + (b1 << 16) + (b2 << 8) + b3; +} + +uint8_t LINX_splitu16(uint16_t u, int32_t bn) +{ + return (u >> ((1-bn)*8)) & 0xFF; +} + +uint8_t LINX_splitu32(uint32_t u, int32_t bn) +{ + return (u >> ((3-bn)*8)) & 0xFF; +} diff --git a/3rdparty/libsimpleio/libsimpleio/liblinx.h b/3rdparty/libsimpleio/libsimpleio/liblinx.h new file mode 100644 index 0000000000..d64f364654 --- /dev/null +++ b/3rdparty/libsimpleio/libsimpleio/liblinx.h @@ -0,0 +1,192 @@ +// LabView LINX device firmware definitions + +// Copyright (C)2016-2023, Philip Munts dba Munts Technologies. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: +// +// * Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE +// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +// POSSIBILITY OF SUCH DAMAGE. + +#ifndef _LIBLINX_H_ +#define _LIBLINX_H_ + +#include +#ifndef _BEGIN_STD_C +#include +#endif +#include + +#define LINX_SOF 0xFF +#define LINX_VERSION 0x03000000 + +// LINX command structure + +typedef struct +{ + uint8_t SoF; + uint8_t PacketSize; + uint16_t PacketNum; + uint16_t Command; + uint8_t Args[54]; +} LINX_command_t; + +// LINX response structure + +typedef struct +{ + uint8_t SoF; + uint8_t PacketSize; + uint16_t PacketNum; + uint8_t Status; + uint8_t Data[55]; +} LINX_response_t; + +// LINX command codes + +#define CMD_SYNC 0x0000 +#define CMD_FLUSH 0x0001 +#define CMD_SYSTEM_RESET 0x0002 +#define CMD_GET_DEVICE_ID 0x0003 +#define CMD_GET_LINX_API_VERSION 0x0004 +#define CMD_GET_MAX_BAUD_RATE 0x0005 +#define CMD_SET_BAUD_RATE 0x0006 +#define CMD_GET_MAX_PACKET_SIZE 0x0007 +#define CMD_GET_GPIO_CHANNELS 0x0008 +#define CMD_GET_ANALOG_IN_CHANNELS 0x0009 +#define CMD_GET_ANALOG_OUT_CHANNELS 0x000A +#define CMD_GET_PWM_CHANNELS 0x000B +#define CMD_GET_QE_CHANNELS 0x000C +#define CMD_GET_UART_CHANNELS 0x000D +#define CMD_GET_I2C_CHANNELS 0x000E +#define CMD_GET_SPI_CHANNELS 0x000F +#define CMD_GET_CAN_CHANNELS 0x0010 +#define CMD_DISCONNECT 0x0011 +#define CMD_SET_DEVICE_USER_ID 0x0012 +#define CMD_GET_DEVICE_USER_ID 0x0013 +#define CMD_SET_DEVICE_ETHERNET_IPADDR 0x0014 +#define CMD_GET_DEVICE_ETHERNET_IPADDR 0x0015 +#define CMD_SET_DEVICE_ETHERNET_TCPPORT 0x0016 +#define CMD_GET_DEVICE_ETHERNET_TCPPORT 0x0017 +#define CMD_SET_DEVICE_WIFI_IPADDR 0x0018 +#define CMD_GET_DEVICE_WIFI_IPADDR 0x0019 +#define CMD_SET_DEVICE_WIFI_TCPPORT 0x001A +#define CMD_GET_DEVICE_WIFI_TCPPORT 0x001B +#define CMD_SET_DEVICE_WIFI_SSID 0x001C +#define CMD_GET_DEVICE_WIFI_SSID 0x001D +#define CMD_SET_DEVICE_WIFI_SECURITY 0x001E +#define CMD_GET_DEVICE_WIFI_SECURITY 0x001F +#define CMD_SET_DEVICE_WIFI_PASSWORD 0x0020 +#define CMD_GET_DEVICE_WIFI_PASSWORD 0x0021 +#define CMD_SET_DEVICE_LINX_MAX BAUD RATE 0x0022 +#define CMD_GET_DEVICE_LINX_MAX_BAUD_RATE 0x0023 +#define CMD_GET_DEVICE_NAME 0x0024 +#define CMD_GET_SERVO_CHANNELS 0x0025 +#define CMD_GPIO_CONFIGURE 0x0040 +#define CMD_GPIO_WRITE 0x0041 +#define CMD_GPIO_READ 0x0042 +#define CMD_GPIO_SQUARE_WAVE 0x0043 +#define CMD_GPIO_PULSE_WIDTH 0x0044 +#define CMD_SET_ANALOG_REFERENCE 0x0060 +#define CMD_GET_ANALOG_REFERENCE 0x0061 +#define CMD_SET_ANALOG_RESOLUTION 0x0062 +#define CMD_GET_ANALOG_RESOLUTION 0x0063 +#define CMD_ANALOG_READ 0x0064 +#define CMD_ANALOG_WRITE 0x0065 +#define CMD_PWM_OPEN 0x0080 +#define CMD_PWM_SET_MODE 0x0081 +#define CMD_PWM_SET_FREQUENCY 0x0082 +#define CMD_PWM_SET_DUTYCYCLE 0x0083 +#define CMD_PWM_CLOSE 0x0084 +#define CMD_UART_OPEN 0x00C0 +#define CMD_UART_SET_BAUD_RATE 0x00C1 +#define CMD_UART_GET_BYTES_AVAILABLE 0x00C2 +#define CMD_UART_READ 0x00C3 +#define CMD_UART_WRITE 0x00C4 +#define CMD_UART_CLOSE 0x00C5 +#define CMD_I2C_OPEN 0x00E0 +#define CMD_I2C_SET_SPEED 0x00E1 +#define CMD_I2C_WRITE 0x00E2 +#define CMD_I2C_READ 0x00E3 +#define CMD_I2C_CLOSE 0x00E4 +#define CMD_SPI_OPEN 0x0100 +#define CMD_SPI_SET_BIT_ORDER 0x0101 +#define CMD_SPI_SET_CLOCK_FREQUENCY 0x0102 +#define CMD_SPI_SET_MODE 0x0103 +#define CMD_SPI_SET_FRAME_SIZE 0x0104 +#define CMD_SPI_SET_CS_LOGIC_LEVEL 0x0105 +#define CMD_SPI_SET_CS_PIN 0x0106 +#define CMD_SPI_WRITE_READ 0x0107 +#define CMD_SERVO_OPEN 0x0140 +#define CMD_SERVO_SET_PULSE_WIDTH 0x0141 +#define CMD_SERVO_CLOSE 0x0142 +#define CMD_WS2812_OPEN 0x0160 +#define CMD_WS2812_WRITE_ONE_PIXEL 0x0161 +#define CMD_WS2812_WRITE_N_PIXELS 0x0162 +#define CMD_WS2812_REFRESH 0x0163 +#define CMD_WS2812_CLOSE 0x0164 +#define CMD_CUSTOM_BASE 0xFC00 +#define CMD_CUSTOM0 (CMD_CUSTOM_BASE + 0) +#define CMD_CUSTOM1 (CMD_CUSTOM_BASE + 1) +#define CMD_CUSTOM2 (CMD_CUSTOM_BASE + 2) +#define CMD_CUSTOM3 (CMD_CUSTOM_BASE + 3) +#define CMD_CUSTOM4 (CMD_CUSTOM_BASE + 4) +#define CMD_CUSTOM5 (CMD_CUSTOM_BASE + 5) +#define CMD_CUSTOM6 (CMD_CUSTOM_BASE + 6) +#define CMD_CUSTOM7 (CMD_CUSTOM_BASE + 7) +#define CMD_CUSTOM8 (CMD_CUSTOM_BASE + 8) +#define CMD_CUSTOM9 (CMD_CUSTOM_BASE + 9) + +// LINX status codes + +#define L_OK 0x00 +#define L_FUNCTION_NOT_SUPPORTED 0x01 +#define L_REQUEST_RESEND 0x02 +#define L_UNKNOWN_ERROR 0x03 +#define L_DISCONNECT 0x04 + +// LINX packet framing routines + +_BEGIN_STD_C + +extern void LINX_transmit_command(int32_t fd, LINX_command_t *cmd, + int32_t *error); + +extern void LINX_receive_command(int32_t fd, LINX_command_t *cmd, + int32_t *count, int32_t *error); + +extern void LINX_transmit_response(int32_t fd, LINX_response_t *resp, + int32_t *error); + +extern void LINX_receive_response(int32_t fd, LINX_response_t *resp, + int32_t *count, int32_t *error); + +// Byte packing and unpacking routines + +// For all of the following routines, byte 0 is the most +// significant byte, and byte 1 or byte 3 is the least +// significant byte (i.e. network byte order) + +extern uint16_t LINX_makeu16(uint8_t b0, uint8_t b1); + +extern uint32_t LINX_makeu32(uint8_t b0, uint8_t b1, uint8_t b2, uint8_t b3); + +extern uint8_t LINX_splitu16(uint16_t u16, int32_t bn); + +extern uint8_t LINX_splitu32(uint32_t u32, int32_t bn); + +_END_STD_C + +#endif diff --git a/3rdparty/libsimpleio/libsimpleio/libpwm.c b/3rdparty/libsimpleio/libsimpleio/libpwm.c new file mode 100644 index 0000000000..ab4bd704d8 --- /dev/null +++ b/3rdparty/libsimpleio/libsimpleio/libpwm.c @@ -0,0 +1,377 @@ +/* PWM (Pulse Width Modulated) output services for Linux */ + +// Copyright (C)2017-2023, Philip Munts dba Munts Technologies. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: +// +// * Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE +// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +// POSSIBILITY OF SUCH DAMAGE. + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "errmsg.inc" +#include "libpwm.h" + +// Device nodes + +#define DIR_CHIP "/sys/class/pwm/pwmchip%d" +#define FILE_EXPORT DIR_CHIP "/export" +#define FILE_UNEXPORT DIR_CHIP "/unexport" +#define DIR_CHAN DIR_CHIP "/pwm%d" +#define FILE_ENABLE DIR_CHAN "/enable" +#define FILE_ONTIME DIR_CHAN "/duty_cycle" // nanoseconds +#define FILE_PERIOD DIR_CHAN "/period" // nanoseconds +#define FILE_POLARITY DIR_CHAN "/polarity" // "normal" or "inversed" [sic] + +static uint64_t milliseconds(void) +{ + struct timespec t; + + clock_gettime(CLOCK_REALTIME, &t); + return t.tv_sec*1000LL + (1LL*t.tv_nsec)/1000000LL; +} + +// Configure PWM output device + +void PWM_configure(int32_t chip, int32_t channel, int32_t period, + int32_t ontime, int32_t polarity, int32_t *error) +{ + assert(error != NULL); + + char filename_export[MAXPATHLEN]; + char filename_enable[MAXPATHLEN]; + char filename_ontime[MAXPATHLEN]; + char filename_period[MAXPATHLEN]; + char filename_polarity[MAXPATHLEN]; + char buf[16]; + int fd; + int len; + + // Validate parameters + + if (chip < 0) + { + *error = EINVAL; + ERRORMSG("chip argument is invalid", *error, __LINE__ - 3); + return; + } + + if (channel < 0) + { + *error = EINVAL; + ERRORMSG("channel argument is invalid", *error, __LINE__ - 3); + return; + } + + if (period < 0) + { + *error = EINVAL; + ERRORMSG("period argument is invalid", *error, __LINE__ - 3); + return; + } + + if (ontime < 0) + { + *error = EINVAL; + ERRORMSG("ontime argument is invalid", *error, __LINE__ - 3); + return; + } + + if ((polarity < PWM_POLARITY_ACTIVELOW) || (polarity > PWM_POLARITY_ACTIVEHIGH)) + { + *error = EINVAL; + ERRORMSG("polarity argument is invalid", *error, __LINE__ - 3); + return; + } + + snprintf(filename_export, sizeof(filename_export), FILE_EXPORT, chip); + snprintf(filename_enable, sizeof(filename_enable), FILE_ENABLE, chip, channel); + snprintf(filename_ontime, sizeof(filename_ontime), FILE_ONTIME, chip, channel); + snprintf(filename_period, sizeof(filename_period), FILE_PERIOD, chip, channel); + snprintf(filename_polarity, sizeof(filename_polarity), FILE_POLARITY, chip, channel); + + // Export the PWM channel, if necessary + + if (access(filename_ontime, W_OK)) + { + + // Open the PWM chip export file + + fd = open(filename_export, O_WRONLY); + if (fd < 0) + { + *error = errno; + ERRORMSG("Cannot open export", *error, __LINE__ - 4); + return; + } + + // Export the PWM output channel + + len = snprintf(buf, sizeof(buf), "%d\n", channel); + + if (write(fd, buf, len) < len) + { + *error = errno; + ERRORMSG("Cannot write to export", *error, __LINE__ - 3); + close(fd); + return; + } + + // Close the PWM chip export file + + close(fd); + + // Wait for the PWM output channel device to be created + + uint64_t start = milliseconds(); + + while (access(filename_enable, W_OK) || + access(filename_ontime, W_OK) || + access(filename_period, W_OK) || + access(filename_polarity, W_OK)) + { + if (milliseconds() - start > 1000) + { + *error = EIO; + ERRORMSG("Timed out waiting for PWM output export", *error, + __LINE__ - 3); + return; + } + + usleep(100000); + } + } + + // Try to write initial 0 to duty_cycle, so we can change the period + // if this output has previously been configured. + + fd = open(filename_ontime, O_WRONLY); + if (fd < 0) + { + *error = errno; + ERRORMSG("Cannot open duty_cycle", *error, __LINE__ - 4); + return; + } + + len = write(fd, "0\n", 2); // Don't care if write() fails here... + close(fd); + + // Write to period + + fd = open(filename_period, O_WRONLY); + if (fd < 0) + { + *error = errno; + ERRORMSG("Cannot open period", *error, __LINE__ - 4); + return; + } + + len = snprintf(buf, sizeof(buf), "%d\n", period); + + if (write(fd, buf, len) < len) + { + *error = errno; + ERRORMSG("Cannot write to period", *error, __LINE__ - 3); + close(fd); + return; + } + + close(fd); + + // Disable the PWM output + + fd = open(filename_enable, O_WRONLY); + if (fd < 0) + { + *error = errno; + ERRORMSG("Cannot open enable", *error, __LINE__ - 4); + return; + } + + if (write(fd, "0\n", 2) < 2) + { + *error = errno; + ERRORMSG("Cannot write to enable", *error, __LINE__ - 3); + close(fd); + return; + } + + close(fd); + + // Write to polarity + + fd = open(filename_polarity, O_WRONLY); + if (fd < 0) + { + *error = errno; + ERRORMSG("Cannot open polarity", *error, __LINE__ - 4); + return; + } + + if (polarity == PWM_POLARITY_ACTIVEHIGH) + len = snprintf(buf, sizeof(buf), "normal\n"); + else + len = snprintf(buf, sizeof(buf), "inversed\n"); + + if (write(fd, buf, len) < len) + { + *error = errno; + ERRORMSG("Cannot write to polarity", *error, __LINE__ - 3); + close(fd); + return; + } + + close(fd); + + // Reenable the PWM output + + fd = open(filename_enable, O_WRONLY); + if (fd < 0) + { + *error = errno; + ERRORMSG("Cannot open enable", *error, __LINE__ - 4); + return; + } + + if (write(fd, "1\n", 2) < 2) + { + *error = errno; + ERRORMSG("Cannot write to enable", *error, __LINE__ - 3); + close(fd); + return; + } + + close(fd); + + // Write to duty_cycle (which is actually the on time in nanosecods...) + + fd = open(filename_ontime, O_WRONLY); + if (fd < 0) + { + *error = errno; + ERRORMSG("Cannot open duty_cycle", *error, __LINE__ - 4); + return; + } + + len = snprintf(buf, sizeof(buf), "%d\n", ontime); + + if (write(fd, buf, len) < len) + { + *error = errno; + ERRORMSG("Cannot write to duty_cycle", *error, __LINE__ - 3); + close(fd); + return; + } + + close(fd); + + *error = 0; +} + +// Open PWM output device + +void PWM_open(int32_t chip, int32_t channel, int32_t *fd, int32_t *error) +{ + char filename[MAXPATHLEN]; + + // Validate parameters + + assert(error != NULL); + + if (fd == NULL) + { + *error = EINVAL; + ERRORMSG("fd argument is NULL", *error, __LINE__ - 3); + return; + } + + if (chip < 0) + { + *fd = -1; + *error = EINVAL; + ERRORMSG("chip argument is invalid", *error, __LINE__ - 4); + return; + } + + if (channel < 0) + { + *fd = -1; + *error = EINVAL; + ERRORMSG("channel argument is invalid", *error, __LINE__ - 4); + return; + } + + snprintf(filename, sizeof(filename), FILE_ONTIME, chip, channel); + + *fd = open(filename, O_WRONLY); + if (*fd < 0) + { + *fd = -1; + *error = errno; + ERRORMSG("open() failed", *error, __LINE__ - 4); + return; + } + + *error = 0; +} + +// Write to PWM output device + +void PWM_write(int32_t fd, int32_t ontime, int32_t *error) +{ + assert(error != NULL); + + // Validate parameters + + if (fd < 0) + { + *error = EINVAL; + ERRORMSG("fd argument is invalid", *error, __LINE__ - 3); + return; + } + + if (ontime < 0) + { + *error = EINVAL; + ERRORMSG("ontime argument is invalid", *error, __LINE__ - 3); + return; + } + + char buf[16]; + int len; + + // Convert on time to string + + len = snprintf(buf, sizeof(buf), "%d\n", ontime); + + // Write string to the device + + if (write(fd, buf, len) < len) + { + *error = errno; + ERRORMSG("write() failed", *error, __LINE__ - 3); + return; + } + + *error = 0; +} diff --git a/3rdparty/libsimpleio/libsimpleio/libpwm.h b/3rdparty/libsimpleio/libsimpleio/libpwm.h new file mode 100644 index 0000000000..e12d5327cd --- /dev/null +++ b/3rdparty/libsimpleio/libsimpleio/libpwm.h @@ -0,0 +1,49 @@ +/* PWM (Pulse Width Modulated) output services for Linux */ + +// Copyright (C)2017-2023, Philip Munts dba Munts Technologies. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: +// +// * Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE +// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +// POSSIBILITY OF SUCH DAMAGE. + +#ifndef LIBPWM_H +#define LIBPWM_H + +#include +#include + +typedef enum +{ + PWM_POLARITY_ACTIVELOW, + PWM_POLARITY_ACTIVEHIGH, +} PWM_POLARITY_t; + +_BEGIN_STD_C + +extern void PWM_configure(int32_t chip, int32_t channel, int32_t period, + int32_t ontime, int32_t polarity, int32_t *error); + +extern void PWM_open(int32_t chip, int32_t channel, int32_t *fd, + int32_t *error); + +extern void PWM_close(int32_t fd, int32_t *error); + +extern void PWM_write(int32_t fd, int32_t ontime, int32_t *error); + +_END_STD_C + +#endif diff --git a/3rdparty/libsimpleio/libsimpleio/libserial.c b/3rdparty/libsimpleio/libsimpleio/libserial.c new file mode 100644 index 0000000000..36f00f8642 --- /dev/null +++ b/3rdparty/libsimpleio/libsimpleio/libserial.c @@ -0,0 +1,407 @@ +/* Serial port device wrapper services for Linux */ + +// Copyright (C)2016-2023, Philip Munts dba Munts Technologies. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: +// +// * Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE +// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +// POSSIBILITY OF SUCH DAMAGE. + +#include +#include +#include +#include +#include +#include +#include + +#include "errmsg.inc" +#include "libserial.h" + +void SERIAL_open(const char *name, int32_t baudrate, int32_t parity, + int32_t databits, int32_t stopbits, int32_t *fd, int32_t *error) +{ + assert(error != NULL); + + struct termios cfg; + + // Validate parameters + + if (fd == NULL) + { + *error = EINVAL; + ERRORMSG("fd argument is NULL", *error, __LINE__ - 3); + return; + } + + if (name == NULL) + { + *fd = -1; + *error = EINVAL; + ERRORMSG("name argument is NULL", *error, __LINE__ - 4); + return; + } + + switch (baudrate) + { + case 50 : + case 75 : + case 110 : + case 134 : + case 150 : + case 200 : + case 300 : + case 600 : + case 1200 : + case 1800 : + case 2400 : + case 4800 : + case 9600 : + case 19200 : + case 38400 : + case 57600 : + case 115200 : + case 230400 : +#ifdef B460800 + case 460800 : +#endif +#ifdef B500000 + case 500000 : +#endif +#ifdef B576000 + case 576000 : +#endif +#ifdef B921600 + case 921600 : +#endif +#ifdef B1000000 + case 1000000 : +#endif +#ifdef B1152000 + case 1152000 : +#endif +#ifdef B1500000 + case 1500000 : +#endif +#ifdef B2000000 + case 2000000 : +#endif +#ifdef B2500000 + case 2500000 : +#endif +#ifdef B3000000 + case 3000000 : +#endif +#ifdef B3500000 + case 3500000 : +#endif +#ifdef B4000000 + case 4000000 : +#endif + break; + + default : + *fd = -1; + *error = EINVAL; + ERRORMSG("baudrate argument is invalid", *error, __LINE__ - 25); + return; + } + + if ((parity < SERIAL_PARITY_NONE) || (parity > SERIAL_PARITY_ODD)) + { + *fd = -1; + *error = EINVAL; + ERRORMSG("parity argument is invalid", *error, __LINE__ - 4); + return; + } + + if ((databits < 5) || (databits > 8)) + { + *fd = -1; + *error = EINVAL; + ERRORMSG("databits argument is invalid", *error, __LINE__ - 4); + return; + } + + if ((stopbits < 1) || (stopbits > 2)) + { + *fd = -1; + *error = EINVAL; + ERRORMSG("stopbits argument is invalid", *error, __LINE__ - 4); + return; + } + + // Open serial port device + + *fd = open(name, O_RDWR|O_NOCTTY); + if (*fd < 0) + { + *fd = -1; + *error = errno; + ERRORMSG("open() failed", *error, __LINE__ - 5); + return; + } + + // Configure serial port device + + if (tcgetattr(*fd, &cfg)) + { + *error = errno; + ERRORMSG("tcgetattr() failed", *error, __LINE__ - 3); + close(*fd); + *fd = -1; + return; + } + + // Put serial port into raw mode + + cfmakeraw(&cfg); + + cfg.c_cflag &= ~HUPCL; + cfg.c_iflag = 0; + cfg.c_oflag = 0; + cfg.c_lflag = 0; + + // Set baud rate + + cfg.c_cflag &= ~CBAUD; + + switch (baudrate) + { + case 50: + cfg.c_cflag |= B50; + break; + + case 75: + cfg.c_cflag |= B75; + break; + + case 110: + cfg.c_cflag |= B110; + break; + + case 134: + cfg.c_cflag |= B134; + break; + + case 150: + cfg.c_cflag |= B150; + break; + + case 200: + cfg.c_cflag |= B200; + break; + + case 300: + cfg.c_cflag |= B300; + break; + + case 600: + cfg.c_cflag |= B600; + break; + + case 1200: + cfg.c_cflag |= B1200; + break; + + case 1800: + cfg.c_cflag |= B1800; + break; + + case 2400: + cfg.c_cflag |= B2400; + break; + + case 4800: + cfg.c_cflag |= B4800; + break; + + case 9600: + cfg.c_cflag |= B9600; + break; + + case 19200: + cfg.c_cflag |= B19200; + break; + + case 38400: + cfg.c_cflag |= B38400; + break; + + case 57600: + cfg.c_cflag |= B57600; + break; + + case 115200: + cfg.c_cflag |= B115200; + break; + + case 230400: + cfg.c_cflag |= B230400; + break; + +#ifdef B460800 + case 460800: + cfg.c_cflag |= B460800; + break; +#endif + +#ifdef B500000 + case 500000: + cfg.c_cflag |= B500000; + break; +#endif + +#ifdef B576000 + case 576000: + cfg.c_cflag |= B576000; + break; +#endif + +#ifdef B921600 + case 921600: + cfg.c_cflag |= B921600; + break; +#endif + +#ifdef B1000000 + case 1000000: + cfg.c_cflag |= B1000000; + break; +#endif + +#ifdef B1152000 + case 1152000: + cfg.c_cflag |= B1152000; + break; +#endif + +#ifdef B1500000 + case 1500000: + cfg.c_cflag |= B1500000; + break; +#endif + +#ifdef B2000000 + case 2000000: + cfg.c_cflag |= B2000000; + break; +#endif + +#ifdef B2500000 + case 2500000: + cfg.c_cflag |= B2500000; + break; +#endif + +#ifdef B3000000 + case 3000000: + cfg.c_cflag |= B3000000; + break; +#endif + +#ifdef B350000 + case 3500000: + cfg.c_cflag |= B3500000; + break; +#endif + +#ifdef B4000000 + case 4000000: + cfg.c_cflag |= B4000000; + break; +#endif + } + + // Set parity mode + + switch (parity) + { + case SERIAL_PARITY_NONE : + cfg.c_cflag &= ~PARENB; + cfg.c_cflag &= ~PARODD; + cfg.c_iflag &= ~INPCK; + break; + + case SERIAL_PARITY_EVEN : + cfg.c_cflag |= PARENB; + cfg.c_cflag &= ~PARODD; + cfg.c_iflag |= INPCK; + break; + + case SERIAL_PARITY_ODD : + cfg.c_cflag |= PARENB; + cfg.c_cflag |= PARODD; + cfg.c_iflag |= INPCK; + break; + } + + // Set data bits + + cfg.c_cflag &= ~CSIZE; + + switch (databits) + { + case 5 : + cfg.c_cflag |= CS5; + break; + + case 6 : + cfg.c_cflag |= CS6; + break; + + case 7 : + cfg.c_cflag |= CS7; + break; + + case 8 : + cfg.c_cflag |= CS8; + break; + } + + // Set stop bits + + switch (stopbits) + { + case 2 : + cfg.c_cflag |= CSTOPB; + break; + } + + if (tcsetattr(*fd, TCSANOW, &cfg)) + { + *error = errno; + ERRORMSG("tcgetattr() failed", *error, __LINE__ - 3); + close(*fd); + *fd = -1; + return; + } + + // Flush serial buffers + + usleep(100000); // Don't know why this delay is necessary + + if (tcflush(*fd, TCIOFLUSH) < 0) + { + *error = errno; + ERRORMSG("tcflush() failed", *error, __LINE__ - 3); + close(*fd); + *fd = -1; + return; + } + + *error = 0; +} diff --git a/3rdparty/libsimpleio/libsimpleio/libserial.h b/3rdparty/libsimpleio/libsimpleio/libserial.h new file mode 100644 index 0000000000..1da2ea510e --- /dev/null +++ b/3rdparty/libsimpleio/libsimpleio/libserial.h @@ -0,0 +1,51 @@ +/* Serial port device wrapper services for Linux */ + +// Copyright (C)2016-2023, Philip Munts dba Munts Technologies. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: +// +// * Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE +// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +// POSSIBILITY OF SUCH DAMAGE. + +#ifndef LIBSERIAL_H +#define LIBSERIAL_H + +#include +#include + +typedef enum +{ + SERIAL_PARITY_NONE, + SERIAL_PARITY_EVEN, + SERIAL_PARITY_ODD, +} SERIAL_PARITY_t; + +_BEGIN_STD_C + +extern void SERIAL_open(const char *name, int32_t baudrate, int32_t parity, + int32_t databits, int32_t stopbits, int32_t *fd, int32_t *error); + +extern void SERIAL_close(int32_t fd, int32_t *error); + +extern void SERIAL_send(int32_t fd, void *buf, int32_t bufsize, + int32_t *count, int32_t *error); + +extern void SERIAL_receive(int32_t fd, void *buf, int32_t bufsize, + int32_t *count, int32_t *error); + +_END_STD_C + +#endif diff --git a/3rdparty/libsimpleio/libsimpleio/libspi.c b/3rdparty/libsimpleio/libsimpleio/libspi.c new file mode 100644 index 0000000000..9672b824a6 --- /dev/null +++ b/3rdparty/libsimpleio/libsimpleio/libspi.c @@ -0,0 +1,259 @@ +/* SPI transaction services for Linux */ + +// Copyright (C)2016-2023, Philip Munts dba Munts Technologies. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: +// +// * Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE +// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +// POSSIBILITY OF SUCH DAMAGE. + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "errmsg.inc" +#include "libgpio.h" +#include "libspi.h" + +// Open and configure the SPI port + +void SPI_open(const char *name, int32_t mode, int32_t wordsize, + int32_t speed, int32_t *fd, int32_t *error) +{ + assert(error != NULL); + + // Validate parameters + + if (fd == NULL) + { + *error = EINVAL; + ERRORMSG("fd argument is NULL", *error, __LINE__ - 3); + return; + } + + if (name == NULL) + { + *fd = -1; + *error = EINVAL; + ERRORMSG("name argument is NULL", *error, __LINE__ - 4); + return; + } + + if ((mode < 0) || (mode > 3)) + { + *fd = -1; + *error = EINVAL; + ERRORMSG("mode argument is invalid", *error, __LINE__ - 4); + return; + } + + if ((wordsize != 0) && (wordsize != 8) && (wordsize != 16) && + (wordsize != 32)) + { + *fd = -1; + *error = EINVAL; + ERRORMSG("wordsize argument is invalid", *error, __LINE__ - 5); + return; + } + + if (speed < 1) + { + *fd = -1; + *error = EINVAL; + ERRORMSG("speed argument is invalid", *error, __LINE__ - 4); + return; + } + + // Open the SPI device + + *fd = open(name, O_RDWR); + if (*fd < 0) + { + *fd = -1; + *error = errno; + ERRORMSG("open() failed", *error, __LINE__ - 5); + return; + } + + // Configure SPI transfer mode (clock polarity and phase) + + if (ioctl(*fd, SPI_IOC_WR_MODE, &mode) < 0) + { + *error = errno; + ERRORMSG("ioctl() for SPI_IOC_WR_MODE failed", *error, __LINE__ - 3); + close(*fd); + *fd = -1; + return; + } + + // Configure SPI transfer word size + + if (ioctl(*fd, SPI_IOC_WR_BITS_PER_WORD, &wordsize) < 0) + { + *error = errno; + ERRORMSG("ioctl() for SPI_IOC_WR_BITS_PER_WORD failed", *error, __LINE__ - 3); + close(*fd); + *fd = -1; + return; + } + + // Configure (maximum) SPI transfer speed + + if (ioctl(*fd, SPI_IOC_WR_MAX_SPEED_HZ, &speed) < 0) + { + *error = errno; + ERRORMSG("ioctl() for SPI_IOC_WR_MAX_SPEED_HZ failed", *error, __LINE__ - 3); + close(*fd); + *fd = -1; + return; + } + + *error = 0; +} + +// Perform an SPI I/O transaction (command and response) + +void SPI_transaction(int32_t spifd, int32_t csfd, void *cmd, + int32_t cmdlen, int32_t delayus, void *resp, int32_t resplen, int32_t *error) +{ + assert(error != NULL); + + // Validate parameters + + if (spifd < 0) + { + *error = EINVAL; + ERRORMSG("spifd argument is invalid", *error, __LINE__ - 3); + return; + } + + if (csfd < SPI_CS_AUTO) + { + *error = EINVAL; + ERRORMSG("csfd argument is invalid", *error, __LINE__ - 3); + return; + } + + if (cmdlen < 0) + { + *error = EINVAL; + ERRORMSG("cmdlen argument is invalid", *error, __LINE__ - 3); + return; + } + + if (delayus < 0) + { + *error = EINVAL; + ERRORMSG("delayus argument is invalid", *error, __LINE__ - 3); + return; + } + + if (resplen < 0) + { + *error = EINVAL; + ERRORMSG("resplen argument is invalid", *error, __LINE__ - 3); + return; + } + + if ((cmd == NULL) && (cmdlen != 0)) + { + *error = EINVAL; + ERRORMSG("cmd and cmdlen arguments are inconsistent", *error, + __LINE__ - 3); + return; + } + + if ((cmd != NULL) && (cmdlen == 0)) + { + *error = EINVAL; + ERRORMSG("cmd and cmdlen arguments are inconsistent", *error, + __LINE__ - 3); + return; + } + + if ((resp == NULL) && (resplen != 0)) + { + *error = EINVAL; + ERRORMSG("resp and resplen arguments are inconsistent", *error, + __LINE__ - 3); + return; + } + + if ((resp != NULL) && (resplen == 0)) + { + *error = EINVAL; + ERRORMSG("resp and resplen arguments are inconsistent", *error, + __LINE__ - 3); + return; + } + + if ((cmd == NULL) && (resp == NULL)) + { + *error = EINVAL; + ERRORMSG("cmd and resp arguments are both NULL", *error, __LINE__ - 3); + return; + } + + struct spi_ioc_transfer xfer[2]; + + // Prepare the SPI ioctl transfer structure + // xfer[0] is the outgoing command to the slave MCU + // xfer[1] is the incoming response from the slave MCU + + // The command and response transfers are executed back to back with + // with a inter-transfer delay in microseconds specfied by xfer[0].delay + // This delay determines the time available to the slave MCU to decode the + // command and generate the response. + + memset(xfer, 0, sizeof(xfer)); + xfer[0].tx_buf = (typeof(xfer[0].tx_buf)) cmd; + xfer[0].len = cmdlen; + xfer[0].delay_usecs = delayus; + xfer[1].rx_buf = (typeof(xfer[1].rx_buf)) resp; + xfer[1].len = resplen; + + // Assert GPIO controlled chip select (if any) + + if (csfd > 0) + { + GPIO_line_write(csfd, 0, error); + if (*error) return; + } + + // Execute the SPI transfer operations + + if (ioctl(spifd, SPI_IOC_MESSAGE(2), xfer) < 0) + { + *error = errno; + ERRORMSG("ioctl() for SPI_IOC_MESSAGE failed", *error, __LINE__ - 3); + return; + } + + // Deassert GPIO controlled chip select (if any) + + if (csfd > 0) + { + GPIO_line_write(csfd, 1, error); + if (*error) return; + } + + *error = 0; +} diff --git a/3rdparty/libsimpleio/libsimpleio/libspi.h b/3rdparty/libsimpleio/libsimpleio/libspi.h new file mode 100644 index 0000000000..9212a3fadb --- /dev/null +++ b/3rdparty/libsimpleio/libsimpleio/libspi.h @@ -0,0 +1,46 @@ +/* SPI transaction services for Linux */ + +// Copyright (C)2016-2023, Philip Munts dba Munts Technologies. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: +// +// * Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE +// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +// POSSIBILITY OF SUCH DAMAGE. + +#ifndef LIBSPI_H +#define LIBSPI_H + +#include +#include +#include + +// Use hardware controlled chip select + +#define SPI_CS_AUTO -1 + +_BEGIN_STD_C + +extern void SPI_open(const char *name, int32_t mode, int32_t wordsize, + int32_t speed, int32_t *fd, int32_t *error); + +extern void SPI_close(int32_t fd, int32_t *error); + +extern void SPI_transaction(int32_t spifd, int32_t csfd, void *cmd, + int32_t cmdlen, int32_t delayus, void *resp, int32_t resplen, int32_t *error); + +_END_STD_C + +#endif diff --git a/3rdparty/libsimpleio/libsimpleio/libstream.c b/3rdparty/libsimpleio/libsimpleio/libstream.c new file mode 100644 index 0000000000..91abb7dc5d --- /dev/null +++ b/3rdparty/libsimpleio/libsimpleio/libstream.c @@ -0,0 +1,383 @@ +// Simple byte stream message framing library + +// Copyright (C)2016-2023, Philip Munts dba Munts Technologies. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: +// +// * Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE +// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +// POSSIBILITY OF SUCH DAMAGE. + +#include +#include +#include + +#include "libstream.h" + +#define DLE 0x10 +#define STX 0x02 +#define ETX 0x03 + +// The following CRC16-CCITT subroutine came from: +// http://stackoverflow.com/questions/10564491/function-to-calculate-a-crc16-checksum + +static uint16_t crc16(const uint8_t* data_p, uint8_t length){ + uint8_t x; + uint16_t crc = 0x1D0F; + + while (length--){ + x = crc >> 8 ^ *data_p++; + x ^= x>>4; + crc = (crc << 8) ^ ((uint16_t)(x << 12)) ^ ((uint16_t)(x <<5)) ^ ((uint16_t)x); + } + return crc; +} + +// Error check macro + +#define FAILIF(c) if (c) { *error = EINVAL; return; } + +// Allow overriding the read() function (e.g. use lwip_read) + +#ifdef __WITH_AVRLIBC__ +// AVR-Libc doesn't export read() +static STREAM_readfn_t readfn = NULL; +#else +static STREAM_readfn_t readfn = read; +#endif + +void STREAM_change_readfn(STREAM_readfn_t newread, int32_t *error) +{ + FAILIF(newread == NULL); + readfn = newread; + *error = 0; +} + +// Allow overriding the write() function (e.g. use lwip_write) + +#ifdef __WITH_AVRLIBC__ +// AVR-Libc doesn't export write() +static STREAM_writefn_t writefn = NULL; +#else +static STREAM_writefn_t writefn = write; +#endif + +void STREAM_change_writefn(STREAM_writefn_t newwrite, int32_t *error) +{ + FAILIF(newwrite == NULL); + writefn = newwrite; + *error = 0; +} + +// Error check macro + +#undef FAILIF +#define FAILIF(c) if (c) { if (dstlen != NULL) *dstlen = 0; *error = EINVAL; return; } + +// Encode a message frame, with DLE byte stuffing and CRC16-CCITT +// frame check sequence. The size of the destination buffer should +// be twice the size of the source buffer, plus 8 bytes worst case. +// Return zero if successfully encoded. + +void STREAM_encode_frame(void *src, int32_t srclen, void *dst, int32_t dstsize, int32_t *dstlen, int32_t *error) +{ + assert(error != NULL); + + // Validate parameters + + FAILIF(src == NULL); + FAILIF(srclen < 0); + FAILIF(dst == NULL); + FAILIF(dstsize < 6); + FAILIF(dstlen == NULL); + + uint8_t *p = (uint8_t *) src; + uint8_t *q = (uint8_t *) dst; + uint16_t crc = 0; + + // Calculate frame check sequence (CRC16-CCITT of payload bytes) + + crc = crc16((uint8_t *) src, srclen); + + // Prefix start of frame delimiter + + *q++ = DLE; + *q++ = STX; + *dstlen = 2; + + // Copy data bytes, with DLE byte stuffing + + while (srclen--) + { + if (*p == DLE) + { + *q++ = DLE; + *dstlen += 1; + + FAILIF(*dstlen == dstsize); + } + + *q++ = *p++; + *dstlen += 1; + + FAILIF(*dstlen == dstsize); + } + + // Append frame check sequence high byte + + *q++ = crc >> 8; + *dstlen += 1; + + FAILIF(*dstlen == dstsize); + + if (q[-1] == DLE) + { + *q++ = DLE; + *dstlen += 1; + + FAILIF(*dstlen == dstsize); + } + + // Append frame check sequence low byte + + *q++ = crc & 0xFF; + *dstlen += 1; + + FAILIF(*dstlen == dstsize); + + if (q[-1] == DLE) + { + *q++ = DLE; + *dstlen += 1; + FAILIF(*dstlen == dstsize); + } + + // Append end of frame delimiter + + FAILIF(*dstlen + 2 > dstsize); + + *q++ = DLE; + *q++ = ETX; + *dstlen += 2; + + *error = 0; +} + +// Decode a message frame, with DLE byte stuffing and CRC16-CCITT +// frame check sequence. Return zero if successfully decoded. + +void STREAM_decode_frame(void *src, int32_t srclen, void *dst, int32_t dstsize, int32_t *dstlen, int32_t *error) +{ + assert(error != NULL); + + // Validate parameters + + FAILIF(src == NULL); + FAILIF(srclen < 6); + FAILIF(dst == NULL); + FAILIF(dstsize < 0); + FAILIF(dstlen == NULL); + + uint8_t *p = (uint8_t *) src; + uint8_t *q = (uint8_t *) dst; + uint16_t crccalc; + uint16_t crcsent; + + *dstlen = 0; + + // Verify frame delimiters + + FAILIF(p[0] != DLE); + FAILIF(p[1] != STX); + FAILIF(p[srclen-2] != DLE); + FAILIF(p[srclen-1] != ETX); + + // Skip delimiter bytes at both ends + + p += 2; + srclen -= 4; + + // Verify DLE stuffing in frame check sequence + + if (p[srclen-1] != DLE) + srclen--; + else if ((p[srclen-1] == DLE) && (p[srclen-2] == DLE)) + srclen -= 2; + else + FAILIF((p[srclen-1] == DLE) && (p[srclen-2] != DLE)); + + if (p[srclen-1] != DLE) + srclen--; + else if ((p[srclen-1] == DLE) && (p[srclen-2] == DLE)) + srclen -= 2; + else + FAILIF((p[srclen-1] == DLE) && (p[srclen-2] != DLE)); + + // Copy payload bytes, removing any stuffed DLE's + + while (srclen) + { + if (*p == DLE) + { + p++; + if (--srclen == 0) break; + } + + *q++ = *p++; + *dstlen += 1; + srclen--; + + FAILIF((srclen > 0) && (*dstlen == dstsize)); + } + + // Calculate expected frame check sequence + + crccalc = crc16((uint8_t *) dst, *dstlen); + + // Calculate received frame check sequences + + if (*p == DLE) + p++; + + crcsent = *p++ << 8; + + if (*p == DLE) + p++; + + crcsent += *p; + + // Compare expected and received frame check sequences + + FAILIF(crccalc != crcsent); + + *error = 0; +} + +#undef FAILIF +#define FAILIF(c, e) if (c) { if (framesize != NULL) *framesize = 0; *error = e; return; } + +// Receive a frame, one byte at a time + +void STREAM_receive_frame(int32_t fd, void *buf, int32_t bufsize, int32_t *framesize, int32_t *error) +{ + assert(error != NULL); + + // Validate parameters + + FAILIF((fd < 0), EINVAL); + FAILIF((buf == NULL), EINVAL); + FAILIF((bufsize < 6), EINVAL); + FAILIF((framesize == NULL), EINVAL); + FAILIF((*framesize >= bufsize), EINVAL); + + int status; + uint8_t b; + uint8_t *bp = (uint8_t *) buf; + + // Read a byte from the stream + + status = readfn(fd, &b, 1); + + // Check for O_NONBLOCK and EAGAIN + + if ((status < 0) && (errno == EAGAIN)) + { + *error = EAGAIN; + return; + } + + FAILIF((status < 0), errno); + FAILIF((status == 0), EPIPE); + + // Process beginning frame delimiters + + switch (*framesize) + { + case 0 : + FAILIF((b != DLE), EAGAIN); + bp[*framesize] = b; + *framesize += 1; + *error = EAGAIN; + return; + + case 1 : + FAILIF((b != STX), EAGAIN); + bp[*framesize] = b; + *framesize += 1; + *error = EAGAIN; + return; + + default : + bp[*framesize] = b; + *framesize += 1; + break; + } + + // Check for complete frame + + if ((*framesize >= 6) && (bp[*framesize-2] == DLE) && (bp[*framesize-1] == ETX)) + { + unsigned i; + unsigned dc = 1; + + // Count the DLE's before the ETX + + for (i = 3;; i++) + { + if (bp[*framesize-i] == DLE) + dc++; + else + break; + } + + // Odd number of DLE's implies complete frame + // Even number of DLE's implies DLE ETX in the payload data + + if (dc & 0x01) + { + *error = 0; + return; + } + } + + // Check for impending buffer overrun + + FAILIF((*framesize == bufsize), EAGAIN); + + // Frame is still incomplete; try again + + *error = EAGAIN; +} + +#undef FAILIF +#define FAILIF(c, e) if (c) { if (count != NULL) *count = 0; *error = e; return; } + +// Send a frame + +void STREAM_send_frame(int32_t fd, void *buf, int32_t bufsize, int32_t *count, int32_t *error) +{ + // Validate parameters + + FAILIF((fd < 0), EINVAL); + FAILIF((buf == NULL), EINVAL); + FAILIF((bufsize < 6), EINVAL); + FAILIF((count == NULL), EINVAL); + + int32_t len = writefn(fd, buf, bufsize); + FAILIF((len < 0), errno); + + *count = len; + *error = 0; +} diff --git a/3rdparty/libsimpleio/libsimpleio/libstream.h b/3rdparty/libsimpleio/libsimpleio/libstream.h new file mode 100644 index 0000000000..36f0984b7f --- /dev/null +++ b/3rdparty/libsimpleio/libsimpleio/libstream.h @@ -0,0 +1,68 @@ +// Simple byte stream message framing library + +// Copyright (C)2016-2023, Philip Munts dba Munts Technologies. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: +// +// * Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE +// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +// POSSIBILITY OF SUCH DAMAGE. + +#ifndef _LIBSTREAM_H +#define _LIBSTREAM_H + +#include +#include +#include + +#ifndef _BEGIN_STD_C +#ifdef __linux__ +#include +#else +#include +#endif +#endif + +#ifdef __WITH_AVRLIBC__ +// AVR-libc does not define size_t or ssize_t +#define size_t uint16_t +#define ssize_t int16_t +#endif + +_BEGIN_STD_C + +typedef ssize_t (*STREAM_readfn_t)(int fd, void *buf, size_t count); + +typedef ssize_t (*STREAM_writefn_t)(int fd, const void *buf, size_t count); + +extern void STREAM_change_readfn(STREAM_readfn_t newread, int32_t *error); + +extern void STREAM_change_writefn(STREAM_writefn_t newwrite, int32_t *error); + +extern void STREAM_encode_frame(void *src, int32_t srclen, void *dst, + int32_t dstsize, int32_t *dstlen, int32_t *error); + +extern void STREAM_decode_frame(void *src, int32_t srclen, void *dst, + int32_t dstsize, int32_t *dstlen, int32_t *error); + +extern void STREAM_receive_frame(int32_t fd, void *buf, int32_t bufsize, + int32_t *framesize, int32_t *error); + +extern void STREAM_send_frame(int32_t fd, void *buf, int32_t bufsize, + int32_t *count, int32_t *error); + +_END_STD_C + +#endif diff --git a/3rdparty/libsimpleio/libsimpleio/libwatchdog.c b/3rdparty/libsimpleio/libsimpleio/libwatchdog.c new file mode 100644 index 0000000000..add99a1957 --- /dev/null +++ b/3rdparty/libsimpleio/libsimpleio/libwatchdog.c @@ -0,0 +1,122 @@ +/* Watchdog timer services for Linux */ + +// Copyright (C)2017-2023, Philip Munts dba Munts Technologies. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: +// +// * Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE +// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +// POSSIBILITY OF SUCH DAMAGE. + +#include +#include +#include +#include + +#include "errmsg.inc" +#include "libwatchdog.h" + +void WATCHDOG_get_timeout(int32_t fd, int32_t *timeout, int32_t *error) +{ + assert(error != NULL); + + // Validate parameters + + if (fd < 0) + { + *error = EINVAL; + ERRORMSG("fd argument is invalid", *error, __LINE__ - 3); + return; + } + + if (timeout == NULL) + { + *error = EINVAL; + ERRORMSG("timeout argument is NULL", *error, __LINE__ - 3); + return; + } + + if (ioctl(fd, WDIOC_GETTIMEOUT, timeout) < 0) + { + *error = errno; + ERRORMSG("ioctl() for WDIOC_GETTIMEOUT failed", *error, __LINE__ - 3); + return; + } + + *error = 0; +} + +void WATCHDOG_set_timeout(int32_t fd, int32_t newtimeout, + int32_t *timeout, int32_t *error) +{ + assert(error != NULL); + + // Validate parameters + + if (fd < 0) + { + *error = EINVAL; + ERRORMSG("fd argument is invalid", *error, __LINE__ - 3); + return; + } + + if (newtimeout < 0) + { + *error = EINVAL; + ERRORMSG("newtimeout argument is invalid", *error, __LINE__ - 3); + return; + } + + if (timeout == NULL) + { + *error = EINVAL; + ERRORMSG("timeout argument is NULL", *error, __LINE__ - 3); + return; + } + + *timeout = newtimeout; + + if (ioctl(fd, WDIOC_SETTIMEOUT, timeout) < 0) + { + *error = errno; + ERRORMSG("ioctl() for WDIOC_SETTIMEOUT failed", *error, __LINE__ - 3); + return; + } + + *error = 0; +} + +void WATCHDOG_kick(int32_t fd, int32_t *error) +{ + assert(error != NULL); + + // Validate parameters + + if (fd < 0) + { + *error = EINVAL; + ERRORMSG("fd argument is invalid", *error, __LINE__ - 3); + return; + } + + if (ioctl(fd, WDIOC_KEEPALIVE, 0) < 0) + { + *error = errno; + ERRORMSG("ioctl() for WDIOC_KEEPALIVE failed", *error, __LINE__ - 3); + return; + } + + *error = 0; +} diff --git a/3rdparty/libsimpleio/libsimpleio/libwatchdog.h b/3rdparty/libsimpleio/libsimpleio/libwatchdog.h new file mode 100644 index 0000000000..82955afb7e --- /dev/null +++ b/3rdparty/libsimpleio/libsimpleio/libwatchdog.h @@ -0,0 +1,44 @@ +/* Watchdog timer services for Linux */ + +// Copyright (C)2017-2023, Philip Munts dba Munts Technologies. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: +// +// * Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE +// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +// POSSIBILITY OF SUCH DAMAGE. + +#ifndef LIBWATCHDOG_H +#define LIBWATCHDOG_H + +#include +#include + +_BEGIN_STD_C + +extern void WATCHDOG_open(const char *name, int32_t *fd, int32_t *error); + +extern void WATCHDOG_close(int32_t fd, int32_t *error); + +extern void WATCHDOG_get_timeout(int32_t fd, int32_t *timeout, int32_t *error); + +extern void WATCHDOG_set_timeout(int32_t fd, int32_t newtimeout, + int32_t *timeout, int32_t *error); + +extern void WATCHDOG_kick(int32_t fd, int32_t *error); + +_END_STD_C + +#endif diff --git a/src/plugins/score-plugin-protocols/CMakeLists.txt b/src/plugins/score-plugin-protocols/CMakeLists.txt index 5e44849b83..01cc3978d3 100644 --- a/src/plugins/score-plugin-protocols/CMakeLists.txt +++ b/src/plugins/score-plugin-protocols/CMakeLists.txt @@ -193,6 +193,20 @@ set(ARTNET_SRCS "${CMAKE_CURRENT_SOURCE_DIR}/Protocols/Artnet/ArtnetSpecificSettingsSerialization.cpp" ) +set(SIMPLEIO_HDRS + "${CMAKE_CURRENT_SOURCE_DIR}/Protocols/SimpleIO/SimpleIODevice.hpp" + "${CMAKE_CURRENT_SOURCE_DIR}/Protocols/SimpleIO/SimpleIOProtocolFactory.hpp" + "${CMAKE_CURRENT_SOURCE_DIR}/Protocols/SimpleIO/SimpleIOProtocolSettingsWidget.hpp" + "${CMAKE_CURRENT_SOURCE_DIR}/Protocols/SimpleIO/SimpleIOSpecificSettings.hpp" +) + +set(SIMPLEIO_SRCS + "${CMAKE_CURRENT_SOURCE_DIR}/Protocols/SimpleIO/SimpleIODevice.cpp" + "${CMAKE_CURRENT_SOURCE_DIR}/Protocols/SimpleIO/SimpleIOProtocolFactory.cpp" + "${CMAKE_CURRENT_SOURCE_DIR}/Protocols/SimpleIO/SimpleIOProtocolSettingsWidget.cpp" + "${CMAKE_CURRENT_SOURCE_DIR}/Protocols/SimpleIO/SimpleIOSpecificSettingsSerialization.cpp" +) + set(MAPPER_SRCS "${CMAKE_CURRENT_SOURCE_DIR}/Protocols/Mapper/MapperDevice.hpp" "${CMAKE_CURRENT_SOURCE_DIR}/Protocols/Mapper/MapperDevice.cpp" @@ -278,6 +292,13 @@ if(OSSIA_PROTOCOL_ARTNET) endif() endif() +if(UNIX AND NOT APPLE AND NOT EMSCRIPTEN) + target_sources(${PROJECT_NAME} PRIVATE ${SIMPLEIO_HDRS} ${SIMPLEIO_SRCS}) + target_compile_definitions(${PROJECT_NAME} PRIVATE OSSIA_PROTOCOL_SIMPLEIO) + target_link_libraries(${PROJECT_NAME} PRIVATE simpleio) + list(APPEND SCORE_FEATURES_LIST protocol_simpleio) +endif() + target_link_libraries(${PROJECT_NAME} PUBLIC ${QT_PREFIX}::Core ${QT_PREFIX}::Widgets ${QT_PREFIX}::Network diff --git a/src/plugins/score-plugin-protocols/Protocols/SimpleIO/SimpleIODevice.cpp b/src/plugins/score-plugin-protocols/Protocols/SimpleIO/SimpleIODevice.cpp new file mode 100644 index 0000000000..760d3b147f --- /dev/null +++ b/src/plugins/score-plugin-protocols/Protocols/SimpleIO/SimpleIODevice.cpp @@ -0,0 +1,278 @@ +#include + +#include +#if defined(OSSIA_PROTOCOL_SIMPLEIO) +#include "SimpleIODevice.hpp" +#include "SimpleIOSpecificSettings.hpp" + +#include + +#include + +#include +#include +#include +#include +#include +#include + +#include + +#include +#include +#include +#include + +#include +W_OBJECT_IMPL(Protocols::SimpleIODevice) + +namespace ossia::net +{ + +struct simpleio_protocol : public ossia::net::protocol_base +{ + + // protocol_base interface +public: + simpleio_protocol(ossia::net::network_context_ptr ctx) + : protocol_base{flags{}} + , m_context{ctx} + { + } + + ~simpleio_protocol() + { + int error; + for(auto& adc : m_adc) + ADC_close(adc.second.fd, &error); + for(auto& dac : m_dac) + DAC_close(dac.second.fd, &error); + for(auto& pwm : m_pwm) + PWM_close(pwm.second.fd, &error); + for(auto& gpio : m_gpio_in) + GPIO_close(gpio.second.fd, &error); + for(auto& gpio : m_gpio_out) + GPIO_close(gpio.second.fd, &error); + } + + void init(const Protocols::SimpleIOSpecificSettings& conf) + { + namespace sio = Protocols::SimpleIO; + + ossia::small_vector adc; + ossia::small_vector dac; + ossia::small_vector pwm; + ossia::small_vector gpio; + + for(auto& port : conf.ports) + { + if(auto ptr = ossia::get_if(&port.control)) + { + adc.push_back(ptr); + } + else if(auto ptr = ossia::get_if(&port.control)) + { + dac.push_back(ptr); + } + else if(auto ptr = ossia::get_if(&port.control)) + { + pwm.push_back(ptr); + } + else if(auto ptr = ossia::get_if(&port.control)) + { + gpio.push_back(ptr); + } + } + + static_assert(offsetof(sio::Port, control) == 0); + SCORE_ASSERT(m_device); + auto& root = m_device->get_root_node(); + for(auto* ptr : adc) + { + auto& port = *reinterpret_cast(ptr); + auto param + = ossia::create_parameter(root, "/adc/" + port.name.toStdString(), "float"); + + ADC_impl impl{}; + int32_t error; + ADC_open(ptr->chip, ptr->channel, &impl.fd, &error); + m_adc.emplace(param, impl); + } + + for(auto* ptr : dac) + { + auto& port = *reinterpret_cast(ptr); + auto param + = ossia::create_parameter(root, "/dac/" + port.name.toStdString(), "float"); + + DAC_impl impl{}; + int32_t error; + DAC_open(ptr->chip, ptr->channel, &impl.fd, &error); + m_dac.emplace(param, impl); + } + + for(auto* ptr : pwm) + { + auto& port = *reinterpret_cast(ptr); + auto param + = ossia::create_parameter(root, "/pwm/" + port.name.toStdString(), "float"); + + PWM_impl impl{}; + int32_t error; + PWM_open(ptr->chip, ptr->channel, &impl.fd, &error); + m_pwm.emplace(param, impl); + } + + for(auto* ptr : gpio) + { + auto& port = *reinterpret_cast(ptr); + auto param + = ossia::create_parameter(root, "/gpio/" + port.name.toStdString(), "bool"); + + GPIO_impl impl{}; + int32_t error; + int32_t flags + = ptr->direction ? GPIOHANDLE_REQUEST_OUTPUT : GPIOHANDLE_REQUEST_INPUT; + int32_t events{}; + int32_t state{}; + + GPIO_line_open(ptr->chip, ptr->line, flags, events, state, &impl.fd, &error); + + if(ptr->direction) + m_gpio_out.emplace(param, impl); + else + m_gpio_in.emplace(param, impl); + } + } + + bool pull(parameter_base& v) override + { + if(auto it = m_adc.find(&v); it != m_adc.end()) + { + int32_t sample, error; + ADC_read(it->second.fd, &sample, &error); + v.set_value(sample); + return true; + } + + if(auto it = m_gpio_in.find(&v); it != m_gpio_in.end()) + { + int32_t sample, error; + GPIO_line_read(it->second.fd, &sample, &error); + v.set_value(sample); + return true; + } + return false; + } + + bool push(const parameter_base& p, const value& v) override + { + if(auto it = m_dac.find(&p); it != m_dac.end()) + { + int32_t error; + DAC_write(it->second.fd, ossia::convert(v), &error); + return true; + } + if(auto it = m_pwm.find(&p); it != m_pwm.end()) + { + int32_t error; + PWM_write(it->second.fd, ossia::convert(v), &error); + return true; + } + if(auto it = m_gpio_out.find(&p); it != m_gpio_out.end()) + { + int32_t error; + GPIO_line_write(it->second.fd, ossia::convert(v), &error); + return true; + } + return false; + } + bool push_raw(const full_parameter_data&) override { return false; } + bool observe(parameter_base&, bool) override { return false; } + bool update(node_base& node_base) override { return false; } + + ossia::net::network_context_ptr m_context; + ossia::net::device_base* m_device{}; + + struct ADC_impl + { + int fd{}; + float value{}; + }; + struct DAC_impl + { + int fd{}; + float value{}; + }; + struct PWM_impl + { + int fd{}; + float value{}; + }; + struct GPIO_impl + { + int fd{}; + int value{}; + }; + + ossia::flat_map m_adc; + ossia::flat_map m_dac; + ossia::flat_map m_pwm; + ossia::flat_map m_gpio_in; + ossia::flat_map m_gpio_out; +}; +} +namespace Protocols +{ + +SimpleIODevice::SimpleIODevice( + const Device::DeviceSettings& settings, const ossia::net::network_context_ptr& ctx) + : OwningDeviceInterface{settings} + , m_ctx{ctx} +{ + m_capas.canRefreshTree = true; + m_capas.canAddNode = false; + m_capas.canRemoveNode = false; + m_capas.canRenameNode = false; + m_capas.canSetProperties = false; + m_capas.canSerialize = false; +} + +SimpleIODevice::~SimpleIODevice() { } + +bool SimpleIODevice::reconnect() +{ + disconnect(); + + try + { + const auto& set + = m_settings.deviceSpecificSettings.value(); + { + auto pproto = std::make_unique(m_ctx); + auto& proto = *pproto; + auto dev = std::make_unique( + std::move(pproto), settings().name.toStdString()); + proto.init(set); + m_dev = std::move(dev); + } + deviceChanged(nullptr, m_dev.get()); + } + catch(const std::runtime_error& e) + { + qDebug() << "SimpleIO error: " << e.what(); + } + catch(...) + { + qDebug() << "SimpleIO error"; + } + + return connected(); +} + +void SimpleIODevice::disconnect() +{ + OwningDeviceInterface::disconnect(); +} +} +#endif diff --git a/src/plugins/score-plugin-protocols/Protocols/SimpleIO/SimpleIODevice.hpp b/src/plugins/score-plugin-protocols/Protocols/SimpleIO/SimpleIODevice.hpp new file mode 100644 index 0000000000..d76a843d05 --- /dev/null +++ b/src/plugins/score-plugin-protocols/Protocols/SimpleIO/SimpleIODevice.hpp @@ -0,0 +1,25 @@ +#pragma once +#include +#if defined(OSSIA_PROTOCOL_SIMPLEIO) +#include + +namespace Protocols +{ +class SimpleIODevice final : public Device::OwningDeviceInterface +{ + + W_OBJECT(SimpleIODevice) +public: + SimpleIODevice( + const Device::DeviceSettings& settings, + const ossia::net::network_context_ptr& ctx); + ~SimpleIODevice(); + + bool reconnect() override; + void disconnect() override; + +private: + const ossia::net::network_context_ptr& m_ctx; +}; +} +#endif diff --git a/src/plugins/score-plugin-protocols/Protocols/SimpleIO/SimpleIOProtocolFactory.cpp b/src/plugins/score-plugin-protocols/Protocols/SimpleIO/SimpleIOProtocolFactory.cpp new file mode 100644 index 0000000000..ad1609605b --- /dev/null +++ b/src/plugins/score-plugin-protocols/Protocols/SimpleIO/SimpleIOProtocolFactory.cpp @@ -0,0 +1,76 @@ +#include +#if defined(OSSIA_PROTOCOL_SIMPLEIO) +#include "SimpleIODevice.hpp" +#include "SimpleIOProtocolFactory.hpp" +#include "SimpleIOProtocolSettingsWidget.hpp" +#include "SimpleIOSpecificSettings.hpp" + +#include + +#include + +#include +#include + +#include +#include +#include + +namespace Protocols +{ + +QString SimpleIOProtocolFactory::prettyName() const noexcept +{ + return QObject::tr("SimpleIO"); +} + +QString SimpleIOProtocolFactory::category() const noexcept +{ + return StandardCategories::lights; +} + +Device::DeviceInterface* SimpleIOProtocolFactory::makeDevice( + const Device::DeviceSettings& settings, const Explorer::DeviceDocumentPlugin& plugin, + const score::DocumentContext& ctx) +{ + return new SimpleIODevice{settings, plugin.networkContext()}; +} + +const Device::DeviceSettings& SimpleIOProtocolFactory::defaultSettings() const noexcept +{ + static const Device::DeviceSettings& settings = [&]() { + Device::DeviceSettings s; + s.protocol = concreteKey(); + s.name = "SimpleIO"; + SimpleIOSpecificSettings settings; + s.deviceSpecificSettings = QVariant::fromValue(settings); + return s; + }(); + + return settings; +} + +Device::ProtocolSettingsWidget* SimpleIOProtocolFactory::makeSettingsWidget() +{ + return new SimpleIOProtocolSettingsWidget; +} + +QVariant +SimpleIOProtocolFactory::makeProtocolSpecificSettings(const VisitorVariant& visitor) const +{ + return makeProtocolSpecificSettings_T(visitor); +} + +void SimpleIOProtocolFactory::serializeProtocolSpecificSettings( + const QVariant& data, const VisitorVariant& visitor) const +{ + serializeProtocolSpecificSettings_T(data, visitor); +} + +bool SimpleIOProtocolFactory::checkCompatibility( + const Device::DeviceSettings& a, const Device::DeviceSettings& b) const noexcept +{ + return false; // TODO +} +} +#endif diff --git a/src/plugins/score-plugin-protocols/Protocols/SimpleIO/SimpleIOProtocolFactory.hpp b/src/plugins/score-plugin-protocols/Protocols/SimpleIO/SimpleIOProtocolFactory.hpp new file mode 100644 index 0000000000..44621b41c9 --- /dev/null +++ b/src/plugins/score-plugin-protocols/Protocols/SimpleIO/SimpleIOProtocolFactory.hpp @@ -0,0 +1,34 @@ +#pragma once +#include +#if defined(OSSIA_PROTOCOL_SIMPLEIO) +#include + +namespace Protocols +{ + +class SimpleIOProtocolFactory final : public DefaultProtocolFactory +{ + SCORE_CONCRETE("f0c58b8c-c1ea-4dee-9496-245c90a3e5ad") + + QString prettyName() const noexcept override; + QString category() const noexcept override; + + Device::DeviceInterface* makeDevice( + const Device::DeviceSettings& settings, const Explorer::DeviceDocumentPlugin& plug, + const score::DocumentContext& ctx) override; + + const Device::DeviceSettings& defaultSettings() const noexcept override; + + Device::ProtocolSettingsWidget* makeSettingsWidget() override; + + QVariant makeProtocolSpecificSettings(const VisitorVariant& visitor) const override; + + void serializeProtocolSpecificSettings( + const QVariant& data, const VisitorVariant& visitor) const override; + + bool checkCompatibility( + const Device::DeviceSettings& a, + const Device::DeviceSettings& b) const noexcept override; +}; +} +#endif diff --git a/src/plugins/score-plugin-protocols/Protocols/SimpleIO/SimpleIOProtocolSettingsWidget.cpp b/src/plugins/score-plugin-protocols/Protocols/SimpleIO/SimpleIOProtocolSettingsWidget.cpp new file mode 100644 index 0000000000..d23683b2d9 --- /dev/null +++ b/src/plugins/score-plugin-protocols/Protocols/SimpleIO/SimpleIOProtocolSettingsWidget.cpp @@ -0,0 +1,437 @@ +#include "libsimpleio/libgpio.h" +#include +#if defined(OSSIA_PROTOCOL_SIMPLEIO) +#include "SimpleIOProtocolFactory.hpp" +#include "SimpleIOProtocolSettingsWidget.hpp" +#include "SimpleIOSpecificSettings.hpp" + +#include + +#include + +#include +#include +#include +#include + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +W_OBJECT_IMPL(Protocols::SimpleIOProtocolSettingsWidget) + +namespace Protocols +{ + + +struct SimpleIOData +{ + QString name; + QString path; + +}; + +using SimpleIONode = TreeNode; + + +class SimpleIODatabase : public TreeNodeBasedItemModel +{ +public: + SimpleIODatabase() + { + beginResetModel(); + auto& pwms = m_root.emplace_back(SimpleIOData{.name = "PWMs"}, &m_root); + auto& adcs = m_root.emplace_back(SimpleIOData{.name = "ADCs"}, &m_root); + auto& dacs = m_root.emplace_back(SimpleIOData{.name = "DACs"}, &m_root); + auto& gpios = m_root.emplace_back(SimpleIOData{.name = "GPIOs"}, &m_root); + auto& hid = m_root.emplace_back(SimpleIOData{.name = "HIDs"}, &m_root); + + enumeratePWMs(pwms); + enumerateADCs(adcs); + enumerateDACs(dacs); + enumerateGPIOs(gpios); + enumerateHIDs(gpios); + + // List ADCs + + // List DACs + + endResetModel(); + + } + + void enumeratePWMs(SimpleIONode& n) { + QDirIterator dir{"/sys/class/pwm", + QDir::Dirs | QDir::NoDotAndDotDot, + QDirIterator::NoIteratorFlags}; + + while(dir.hasNext()) { + auto pwm = dir.nextFileInfo(); + n.emplace_back(SimpleIOData{.name = pwm.fileName(), .path = pwm.absoluteFilePath()}, &m_root); + } + } + + void enumerateADCs(SimpleIONode& n) { + + } + + void enumerateDACs(SimpleIONode& n) { + + } + + void enumerateHIDs(SimpleIONode& n) { + for(int i = 0; i < 128; i++) { + if(auto dev = QString{"/dev/hidraw%1"}.arg(i); QFile::exists(dev)) { + n.emplace_back(SimpleIOData{.name = QString{"hidraw%1"}.arg(i), .path = dev}, &m_root); + } + else + { + break; + } + } + } + + void enumerateGPIOs(SimpleIONode& n) { + for(int chip = 0; chip < 128; chip++) { + char name[256]; + char label[256]; + int32_t lines, error; + GPIO_chip_info(chip, name, 255, label, 255, &lines, &error); + + if(error != 0) + break; + qDebug() << "GPIO Chip " << chip << " : " << name << label << lines; + auto& chip_node = n.emplace_back(SimpleIOData{.name = QString{"GPIO chip %1"}.arg(chip), .path = name}, &n); + { + for(int line = 0; line < lines; line++) + { + int32_t flags; + GPIO_line_info(chip, line, &flags, name, 255, label, 255, &error); + qDebug() << " - Line " << line << " : " << name << label << lines; + chip_node.emplace_back(SimpleIOData{.name = QString{"%1: %2"}.arg(chip).arg(name), .path = QString{"%1:%2"}.arg(chip).arg(line)}, &chip_node); + } + } + } + } + + + SimpleIONode& rootNode() override { return m_root; } + const SimpleIONode& rootNode() const override { return m_root; } + + int columnCount(const QModelIndex& parent) const override { return 2; } + + QVariant data(const QModelIndex& index, int role) const override + { + const auto& node = nodeFromModelIndex(index); + if(index.column() == 0) + { + switch(role) + { + case Qt::DisplayRole: + return node.name; + // case Qt::DecorationRole: + // return node.icon; + } + } + return QVariant{}; + } + + QVariant headerData(int section, Qt::Orientation orientation, int role) const override + { + if(role != Qt::DisplayRole) + return TreeNodeBasedItemModel::headerData(section, orientation, role); + switch(section) + { + case 0: + return tr("Name"); + case 1: + return tr("Path"); + default: + return {}; + } + } + Qt::ItemFlags flags(const QModelIndex& index) const override + { + Qt::ItemFlags f; + + f = Qt::ItemIsEnabled | Qt::ItemIsSelectable; + + return f; + } + + static SimpleIODatabase& instance() + { + static SimpleIODatabase db; + return db; + } + + SimpleIONode m_root; +}; + +class SimpleIOTreeView : public QTreeView +{ +public: + SimpleIOTreeView(QWidget* parent = nullptr) + : QTreeView{parent} + { + setAllColumnsShowFocus(true); + setSelectionBehavior(QAbstractItemView::SelectRows); + setSelectionMode(QAbstractItemView::ExtendedSelection); + setDragDropMode(QAbstractItemView::NoDragDrop); + setAlternatingRowColors(true); + setUniformRowHeights(true); + } + + std::function onSelectionChanged; + void selectionChanged(const QItemSelection& selected, const QItemSelection& deselected) + { + auto sel = this->selectedIndexes(); + if(!sel.empty()) + { + auto obj = (SimpleIONode*)sel.at(0).internalPointer(); + if(obj) + { + onSelectionChanged(*obj); + } + } + } +}; + + + +class AddPortDialog : public QDialog +{ +public: + AddPortDialog(SimpleIOProtocolSettingsWidget& parent) + : QDialog{&parent} + , m_name{this} + , m_buttons{ + QDialogButtonBox::StandardButton::Ok + | QDialogButtonBox::StandardButton::Cancel, + this} + { + this->setLayout(&m_layout); + m_layout.addWidget(&m_availablePorts); + m_layout.addLayout(&m_setupLayoutContainer); + m_layout.setStretch(0, 3); + m_layout.setStretch(1, 5); + + m_availablePorts.setModel(&SimpleIODatabase::instance()); + m_availablePorts.header()->resizeSection(0, 180); + m_availablePorts.onSelectionChanged = [&](const SimpleIONode& newFixt) { + // Manufacturer, do nothing + if(newFixt.childCount() > 0) + return; + + updateParameters(newFixt); + }; + + m_address.setRange(1, 512); + + m_setupLayoutContainer.addLayout(&m_setupLayout); + m_setupLayout.addRow(tr("Name"), &m_name); + m_setupLayout.addRow(tr("Address"), &m_address); + m_setupLayout.addRow(tr("Mode"), &m_mode); + m_setupLayout.addRow(tr("Channels"), &m_content); + m_setupLayoutContainer.addStretch(0); + m_setupLayoutContainer.addWidget(&m_buttons); + connect(&m_buttons, &QDialogButtonBox::accepted, this, &AddPortDialog::accept); + connect(&m_buttons, &QDialogButtonBox::rejected, this, &AddPortDialog::reject); + + connect( + &m_mode, qOverload(&QComboBox::currentIndexChanged), this, + &AddPortDialog::setMode); + } + + void updateParameters(const SimpleIONode& fixt) + {/* + m_name.setText(fixt.name); + + m_mode.clear(); + for(auto& mode : fixt.modes) + m_mode.addItem(mode.name); + m_mode.setCurrentIndex(0); + + m_currentPort = &fixt; + + setMode(0);*/ + } + + void setMode(int mode_index) + {/* + if(!m_currentPort) + return; + if(!ossia::valid_index(mode_index, m_currentPort->modes)) + return; + + const PortMode& mode = m_currentPort->modes[mode_index]; + int numChannels = mode.allChannels.size(); + m_address.setRange(1, 513 - numChannels); + + m_content.setText(mode.content());*/ + } + + QSize sizeHint() const override { return QSize{800, 600}; } + + SimpleIO::Port port() const noexcept + { + SimpleIO::Port f; + /* + if(!m_currentPort) + return f; + + int mode_index = m_mode.currentIndex(); + if(!ossia::valid_index(mode_index, m_currentPort->modes)) + return f; + + auto& mode = m_currentPort->modes[mode_index]; + + f.portName = m_currentPort->name; + f.modeName = m_mode.currentText(); + f.mode.channelNames = mode.allChannels; + f.address = m_address.value() - 1; + f.controls = mode.channels; +*/ + return f; + } + +private: + QHBoxLayout m_layout; + SimpleIOTreeView m_availablePorts; + + QVBoxLayout m_setupLayoutContainer; + QFormLayout m_setupLayout; + State::AddressFragmentLineEdit m_name; + QSpinBox m_address; + QComboBox m_mode; + QLabel m_content; + QDialogButtonBox m_buttons; +}; + +SimpleIOProtocolSettingsWidget::SimpleIOProtocolSettingsWidget(QWidget* parent) + : Device::ProtocolSettingsWidget(parent) +{ + m_deviceNameEdit = new State::AddressFragmentLineEdit{this}; + m_deviceNameEdit->setText("SimpleIO"); + + auto layout = new QFormLayout; + layout->addRow(tr("Name"), m_deviceNameEdit); + + m_portsWidget = new QTableWidget; + layout->addRow(m_portsWidget); + + m_portsWidget->setSelectionBehavior(QAbstractItemView::SelectRows); + m_portsWidget->setSelectionMode(QAbstractItemView::ExtendedSelection); + m_portsWidget->insertColumn(0); + m_portsWidget->insertColumn(1); + m_portsWidget->insertColumn(2); + m_portsWidget->setHorizontalHeaderLabels({tr("Name"), tr("Type"), tr("Mode")}); + + auto btns = new QHBoxLayout; + m_addPort = new QPushButton{"Add a port"}; + m_rmPort = new QPushButton{"Remove selected port"}; + btns->addWidget(m_addPort); + btns->addWidget(m_rmPort); + layout->addRow(btns); + + connect(m_addPort, &QPushButton::clicked, this, [this] { + +#if 0 + auto dial = new AddPortDialog{*this}; + if(dial->exec() == QDialog::Accepted) + { + auto port = dial->port(); + if(!port.name.isEmpty() && !port.controls.empty()) + { + m_ports.push_back(port); + updateTable(); + } + } +#endif + }); + connect(m_rmPort, &QPushButton::clicked, this, [this] { + ossia::flat_set rows_to_remove; + for(auto item : m_portsWidget->selectedItems()) + { + rows_to_remove.insert(item->row()); + } + + for(auto it = rows_to_remove.rbegin(); it != rows_to_remove.rend(); ++it) + { + m_ports.erase(m_ports.begin() + *it); + } + + updateTable(); + }); + + setLayout(layout); +} + +void SimpleIOProtocolSettingsWidget::updateTable() +{ + while(m_portsWidget->rowCount() > 0) + m_portsWidget->removeRow(int(m_portsWidget->rowCount()) - 1); + + int row = 0; + for(auto& fixt : m_ports) + { +#if 0 + auto name_item = new QTableWidgetItem{fixt.portName}; + auto mode_item = new QTableWidgetItem{fixt.modeName}; + auto address = new QTableWidgetItem{QString::number(fixt.address + 1)}; + auto controls = new QTableWidgetItem{QString::number(fixt.controls.size())}; + name_item->setFlags(Qt::ItemIsEnabled | Qt::ItemIsSelectable); + mode_item->setFlags(Qt::ItemIsEnabled | Qt::ItemIsSelectable); + address->setFlags(Qt::ItemIsEnabled | Qt::ItemIsSelectable); + controls->setFlags(Qt::ItemIsEnabled | Qt::ItemIsSelectable); + + m_portsWidget->insertRow(row); + m_portsWidget->setItem(row, 0, name_item); + m_portsWidget->setItem(row, 1, mode_item); + m_portsWidget->setItem(row, 2, address); + m_portsWidget->setItem(row, 3, controls); + row++; +#endif + } +} +SimpleIOProtocolSettingsWidget::~SimpleIOProtocolSettingsWidget() { } + +Device::DeviceSettings SimpleIOProtocolSettingsWidget::getSettings() const +{ + // TODO should be = m_settings to follow the other patterns. + Device::DeviceSettings s; + s.name = m_deviceNameEdit->text(); + s.protocol = SimpleIOProtocolFactory::static_concreteKey(); + + SimpleIOSpecificSettings settings{}; + settings.ports = this->m_ports; + s.deviceSpecificSettings = QVariant::fromValue(settings); + + return s; +} + +void SimpleIOProtocolSettingsWidget::setSettings(const Device::DeviceSettings& settings) +{ + m_deviceNameEdit->setText(settings.name); + const auto& specif = settings.deviceSpecificSettings.value(); + m_ports = specif.ports; + updateTable(); +} +} +#endif diff --git a/src/plugins/score-plugin-protocols/Protocols/SimpleIO/SimpleIOProtocolSettingsWidget.hpp b/src/plugins/score-plugin-protocols/Protocols/SimpleIO/SimpleIOProtocolSettingsWidget.hpp new file mode 100644 index 0000000000..971fe55789 --- /dev/null +++ b/src/plugins/score-plugin-protocols/Protocols/SimpleIO/SimpleIOProtocolSettingsWidget.hpp @@ -0,0 +1,39 @@ +#pragma once +#include +#if defined(OSSIA_PROTOCOL_SIMPLEIO) + +#include +#include + +#include + +#include + +class QLineEdit; +class QSpinBox; +class QTableWidget; +class QPushButton; + +namespace Protocols +{ + +class SimpleIOProtocolSettingsWidget final : public Device::ProtocolSettingsWidget +{ + W_OBJECT(SimpleIOProtocolSettingsWidget) + +public: + SimpleIOProtocolSettingsWidget(QWidget* parent = nullptr); + virtual ~SimpleIOProtocolSettingsWidget(); + Device::DeviceSettings getSettings() const override; + void setSettings(const Device::DeviceSettings& settings) override; + +private: + void updateTable(); + QLineEdit* m_deviceNameEdit{}; + QTableWidget* m_portsWidget{}; + QPushButton* m_addPort{}; + QPushButton* m_rmPort{}; + std::vector m_ports; +}; +} +#endif diff --git a/src/plugins/score-plugin-protocols/Protocols/SimpleIO/SimpleIOSpecificSettings.hpp b/src/plugins/score-plugin-protocols/Protocols/SimpleIO/SimpleIOSpecificSettings.hpp new file mode 100644 index 0000000000..8d7d1eb7a2 --- /dev/null +++ b/src/plugins/score-plugin-protocols/Protocols/SimpleIO/SimpleIOSpecificSettings.hpp @@ -0,0 +1,63 @@ +#pragma once +#include +#if defined(OSSIA_PROTOCOL_SIMPLEIO) +#include + +#include + +#include + +#include +#include +#include + +namespace Protocols +{ + +namespace SimpleIO +{ +struct GPIO +{ + int32_t chip{}; + int32_t line{}; + int32_t flags{}; + int32_t events{}; + int32_t state{}; + bool direction{}; +}; +struct PWM +{ + int32_t chip{}; + int32_t channel{}; +}; +struct ADC +{ + int32_t chip{}; + int32_t channel{}; +}; +struct DAC +{ + int32_t chip{}; + int32_t channel{}; +}; + +using Type = ossia::variant; + +struct Port +{ + Type control; + QString name; + QString path; +}; + +} + +struct SimpleIOSpecificSettings +{ + std::vector ports; +}; +} + +Q_DECLARE_METATYPE(Protocols::SimpleIOSpecificSettings) +W_REGISTER_ARGTYPE(Protocols::SimpleIOSpecificSettings) +#endif diff --git a/src/plugins/score-plugin-protocols/Protocols/SimpleIO/SimpleIOSpecificSettingsSerialization.cpp b/src/plugins/score-plugin-protocols/Protocols/SimpleIO/SimpleIOSpecificSettingsSerialization.cpp new file mode 100644 index 0000000000..fda1b4e837 --- /dev/null +++ b/src/plugins/score-plugin-protocols/Protocols/SimpleIO/SimpleIOSpecificSettingsSerialization.cpp @@ -0,0 +1,165 @@ +#include +#if defined(OSSIA_PROTOCOL_SIMPLEIO) +#include "SimpleIOSpecificSettings.hpp" + +#include +#include +#include + +JSON_METADATA(Protocols::SimpleIO::GPIO, "GPIO") +JSON_METADATA(Protocols::SimpleIO::PWM, "PWM") +JSON_METADATA(Protocols::SimpleIO::ADC, "ADC") +JSON_METADATA(Protocols::SimpleIO::DAC, "DAC") + +template <> +void DataStreamReader::read(const Protocols::SimpleIO::GPIO& n) +{ + insertDelimiter(); +} + +template <> +void DataStreamWriter::write(Protocols::SimpleIO::GPIO& n) +{ + checkDelimiter(); +} + +template <> +void JSONReader::read(const Protocols::SimpleIO::GPIO& n) +{ + stream.StartObject(); + stream.EndObject(); +} + +template <> +void JSONWriter::write(Protocols::SimpleIO::GPIO& n) +{ +} + +template <> +void DataStreamReader::read(const Protocols::SimpleIO::ADC& n) +{ + insertDelimiter(); +} + +template <> +void DataStreamWriter::write(Protocols::SimpleIO::ADC& n) +{ + checkDelimiter(); +} + +template <> +void JSONReader::read(const Protocols::SimpleIO::ADC& n) +{ + stream.StartObject(); + stream.EndObject(); +} + +template <> +void JSONWriter::write(Protocols::SimpleIO::ADC& n) +{ +} + +template <> +void DataStreamReader::read(const Protocols::SimpleIO::DAC& n) +{ + insertDelimiter(); +} + +template <> +void DataStreamWriter::write(Protocols::SimpleIO::DAC& n) +{ + checkDelimiter(); +} + +template <> +void JSONReader::read(const Protocols::SimpleIO::DAC& n) +{ + stream.StartObject(); + stream.EndObject(); +} + +template <> +void JSONWriter::write(Protocols::SimpleIO::DAC& n) +{ +} + +template <> +void DataStreamReader::read(const Protocols::SimpleIO::PWM& n) +{ + insertDelimiter(); +} + +template <> +void DataStreamWriter::write(Protocols::SimpleIO::PWM& n) +{ + checkDelimiter(); +} + +template <> +void JSONReader::read(const Protocols::SimpleIO::PWM& n) +{ + stream.StartObject(); + stream.EndObject(); +} + +template <> +void JSONWriter::write(Protocols::SimpleIO::PWM& n) +{ +} + +template <> +void DataStreamReader::read(const Protocols::SimpleIO::Port& n) +{ + insertDelimiter(); +} + +template <> +void DataStreamWriter::write(Protocols::SimpleIO::Port& n) +{ + checkDelimiter(); +} + +template <> +void JSONReader::read(const Protocols::SimpleIO::Port& n) +{ + stream.StartObject(); + obj["Name"] = n.name; + obj["Path"] = n.path; + obj["Control"] = n.control; + stream.EndObject(); +} + +template <> +void JSONWriter::write(Protocols::SimpleIO::Port& n) +{ + n.name <<= obj["Name"]; + n.path <<= obj["Path"]; + n.control <<= obj["Control"]; +} + +template <> +void DataStreamReader::read(const Protocols::SimpleIOSpecificSettings& n) +{ + m_stream << n.ports; + insertDelimiter(); +} + +template <> +void DataStreamWriter::write(Protocols::SimpleIOSpecificSettings& n) +{ + m_stream >> n.ports; + checkDelimiter(); +} + +template <> +void JSONReader::read(const Protocols::SimpleIOSpecificSettings& n) +{ + obj["Ports"] = n.ports; +} + +template <> +void JSONWriter::write(Protocols::SimpleIOSpecificSettings& n) +{ + n.ports <<= obj["Ports"]; +} +#endif diff --git a/src/plugins/score-plugin-protocols/score_plugin_protocols.cpp b/src/plugins/score-plugin-protocols/score_plugin_protocols.cpp index 32471e8e2c..7ffd9ab493 100644 --- a/src/plugins/score-plugin-protocols/score_plugin_protocols.cpp +++ b/src/plugins/score-plugin-protocols/score_plugin_protocols.cpp @@ -53,6 +53,9 @@ #if defined(OSSIA_PROTOCOL_LIBMAPPER) #include #endif +#if defined(OSSIA_PROTOCOL_SIMPLEIO) +#include +#endif #include @@ -128,6 +131,10 @@ std::vector score_plugin_protocols::factories( , Protocols::ArtnetProtocolFactory #endif +#if defined(OSSIA_PROTOCOL_SIMPLEIO) + , + Protocols::SimpleIOProtocolFactory +#endif #if defined(OSSIA_PROTOCOL_LIBMAPPER) , Protocols::LibmapperClientProtocolFactory