diff --git a/.gitignore b/.gitignore index 6a5b347a..550dcb07 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,5 @@ /*.user /build /build-x86 +.DS_Store + diff --git a/CMakeLists.txt b/CMakeLists.txt index 36fc2e56..05ba74a8 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -19,10 +19,13 @@ message(STATUS "ENABLE_V4L2 = " ${ENABLE_V4L2}) option(ENABLE_X11 "Enable the X11 grabber" ON) message(STATUS "ENABLE_X11 = " ${ENABLE_X11}) +option(ENABLE_TINKERFORGE "Enable the TINKERFORGE device" ON) +message(STATUS "ENABLE_TINKERFORGE = " ${ENABLE_TINKERFORGE}) + # Createt the configuration file # configure a header file to pass some of the CMake settings # to the source code -configure_file ("${PROJECT_SOURCE_DIR}/HyperionConfig.h.in" "${PROJECT_BINARY_DIR}/HyperionConfig.h") +configure_file("${PROJECT_SOURCE_DIR}/HyperionConfig.h.in" "${PROJECT_BINARY_DIR}/HyperionConfig.h") include_directories("${PROJECT_BINARY_DIR}") # Add project specific cmake modules (find, etc) @@ -65,6 +68,10 @@ set(CMAKE_FIND_LIBRARY_SUFFIXES_OLD) find_package(libusb-1.0 REQUIRED) find_package(Threads REQUIRED) +if (ENABLE_TINKERFORGE) + #find_package(libtinkerforge-1.0 REQUIRED) +endif (ENABLE_TINKERFORGE) + include(${QT_USE_FILE}) add_definitions(${QT_DEFINITIONS}) # TODO[TvdZ]: This linking directory should only be added if we are cross compiling diff --git a/CompileHowto.txt b/CompileHowto.txt index fca86d56..17a274e3 100644 --- a/CompileHowto.txt +++ b/CompileHowto.txt @@ -19,8 +19,8 @@ cd "$HYPERION_DIR/build" # run cmake to generate make files on the rsapberry pi cmake .. -# or if you are not compiling on the raspberry pi and need to disable the Disppmanx grabber -cmake -DENABLE_DISPMANX=OFF .. +# or if you are not compiling on the raspberry pi and need to disable the Dispmanx grabber and support for spi devices +cmake -DENABLE_DISPMANX=OFF -DENABLE_SPIDEV=OFF .. # run make to build Hyperion make diff --git a/HyperionConfig.h.in b/HyperionConfig.h.in index 14500c71..17f040c0 100644 --- a/HyperionConfig.h.in +++ b/HyperionConfig.h.in @@ -8,3 +8,6 @@ // Define to enable the spi-device #cmakedefine ENABLE_SPIDEV + +// Define to enable the spi-device +#cmakedefine ENABLE_TINKERFORGE diff --git a/bin/hyperion.init.sh b/bin/hyperion.init.sh index 50e7d7f8..d83bdb28 100644 --- a/bin/hyperion.init.sh +++ b/bin/hyperion.init.sh @@ -7,22 +7,27 @@ DAEMON=hyperiond DAEMONOPTS="/etc/hyperion.config.json" DAEMON_PATH="/usr/bin" -NAME=$DEAMON +NAME=$DAEMON DESC="Hyperion ambilight server" PIDFILE=/var/run/$NAME.pid SCRIPTNAME=/etc/init.d/$NAME - case "$1" in start) - printf "%-50s" "Starting $NAME..." - cd $DAEMON_PATH - PID=`$DAEMON $DAEMONOPTS > /dev/null 2>&1 & echo $!` - #echo "Saving PID" $PID " to " $PIDFILE - if [ -z $PID ]; then - printf "%s\n" "Fail" + if [ $(pgrep -l $NAME |wc -l) = 1 ] + then + printf "%-50s\n" "Already running..." + exit 1 else - echo $PID > $PIDFILE - printf "%s\n" "Ok" + printf "%-50s" "Starting $NAME..." + cd $DAEMON_PATH + PID=`$DAEMON $DAEMONOPTS > /dev/null 2>&1 & echo $!` + #echo "Saving PID" $PID " to " $PIDFILE + if [ -z $PID ]; then + printf "%s\n" "Fail" + else + echo $PID > $PIDFILE + printf "%s\n" "Ok" + fi fi ;; status) @@ -39,24 +44,31 @@ status) fi ;; stop) - printf "%-50s" "Stopping $NAME" - PID=`cat $PIDFILE` - cd $DAEMON_PATH - if [ -f $PIDFILE ]; then - kill -HUP $PID - printf "%s\n" "Ok" - rm -f $PIDFILE + if [ -f $PIDFILE ] + then + printf "%-50s" "Stopping $NAME" + PID=`cat $PIDFILE` + cd $DAEMON_PATH + if [ -f $PIDFILE ]; then + kill -HUP $PID + printf "%s\n" "Ok" + rm -f $PIDFILE + else + printf "%s\n" "pidfile not found" + fi else - printf "%s\n" "pidfile not found" + printf "%-50s\n" "No PID file $NAME not running?" fi ;; restart) - $0 stop - $0 start + $0 stop + $0 start ;; *) echo "Usage: $0 {status|start|stop|restart}" exit 1 esac + +exit 0 diff --git a/config/hyperion_x86.config.json b/config/hyperion_x86.config.json index 071713aa..4abd64db 100644 --- a/config/hyperion_x86.config.json +++ b/config/hyperion_x86.config.json @@ -14,7 +14,7 @@ { "name" : "MyPi", "type" : "adalight", - "output" : "/dev/ttyUSB0", + "output" : "/dev/ttyUSB0", "rate" : 115200, "colorOrder" : "rgb" }, @@ -363,7 +363,7 @@ { "paths" : [ - "/opt/hyperion/effects" + "/home/dincs/projects/hyperion/effects" ] }, diff --git a/dependencies/build/CMakeLists.txt b/dependencies/build/CMakeLists.txt index 88f6ca5f..e931ea79 100644 --- a/dependencies/build/CMakeLists.txt +++ b/dependencies/build/CMakeLists.txt @@ -1,5 +1,6 @@ -add_subdirectory(jsoncpp) add_subdirectory(getoptPlusPlus) -add_subdirectory(serial) add_subdirectory(hidapi) +add_subdirectory(jsoncpp) +add_subdirectory(serial) +add_subdirectory(tinkerforge) diff --git a/dependencies/build/getoptPlusPlus/getoptpp.cc b/dependencies/build/getoptPlusPlus/getoptpp.cc index e7b8b420..5444a325 100644 --- a/dependencies/build/getoptPlusPlus/getoptpp.cc +++ b/dependencies/build/getoptPlusPlus/getoptpp.cc @@ -280,7 +280,6 @@ void PresettableUniquelySwitchable::preset() { template<> PODParameter::PODParameter(char shortOption, const char *longOption, const char* description) : CommonParameter(shortOption, longOption, description) { - preset(); } diff --git a/dependencies/build/tinkerforge/CMakeLists.txt b/dependencies/build/tinkerforge/CMakeLists.txt new file mode 100644 index 00000000..f1f82f5e --- /dev/null +++ b/dependencies/build/tinkerforge/CMakeLists.txt @@ -0,0 +1,14 @@ +project(tinkerforge) + +# define the current source/header path +set(CURRENT_HEADER_DIR ${CMAKE_SOURCE_DIR}/dependencies/include/tinkerforge) +set(CURRENT_SOURCE_DIR ${CMAKE_SOURCE_DIR}/dependencies/build/tinkerforge) + +include_directories(${CURRENT_HEADER_DIR}) + +add_library(tinkerforge + ${CURRENT_HEADER_DIR}/bricklet_led_strip.h + ${CURRENT_HEADER_DIR}/ip_connection.h + + ${CURRENT_SOURCE_DIR}/bricklet_led_strip.c + ${CURRENT_SOURCE_DIR}/ip_connection.c) diff --git a/dependencies/build/tinkerforge/bricklet_led_strip.c b/dependencies/build/tinkerforge/bricklet_led_strip.c new file mode 100644 index 00000000..a67486fb --- /dev/null +++ b/dependencies/build/tinkerforge/bricklet_led_strip.c @@ -0,0 +1,373 @@ +/* *********************************************************** + * This file was automatically generated on 2013-12-19. * + * * + * Bindings Version 2.0.13 * + * * + * If you have a bugfix for this file and want to commit it, * + * please fix the bug in the generator. You can find a link * + * to the generator git on tinkerforge.com * + *************************************************************/ + + +#define IPCON_EXPOSE_INTERNALS + +#include "bricklet_led_strip.h" + +#include + + + +typedef void (*FrameRenderedCallbackFunction)(uint16_t, void *); + +#if defined _MSC_VER || defined __BORLANDC__ + #pragma pack(push) + #pragma pack(1) + #define ATTRIBUTE_PACKED +#elif defined __GNUC__ + #ifdef _WIN32 + // workaround struct packing bug in GCC 4.7 on Windows + // http://gcc.gnu.org/bugzilla/show_bug.cgi?id=52991 + #define ATTRIBUTE_PACKED __attribute__((gcc_struct, packed)) + #else + #define ATTRIBUTE_PACKED __attribute__((packed)) + #endif +#else + #error unknown compiler, do not know how to enable struct packing +#endif + +typedef struct { + PacketHeader header; + uint16_t index; + uint8_t length; + uint8_t r[16]; + uint8_t g[16]; + uint8_t b[16]; +} ATTRIBUTE_PACKED SetRGBValues_; + +typedef struct { + PacketHeader header; + uint16_t index; + uint8_t length; +} ATTRIBUTE_PACKED GetRGBValues_; + +typedef struct { + PacketHeader header; + uint8_t r[16]; + uint8_t g[16]; + uint8_t b[16]; +} ATTRIBUTE_PACKED GetRGBValuesResponse_; + +typedef struct { + PacketHeader header; + uint16_t duration; +} ATTRIBUTE_PACKED SetFrameDuration_; + +typedef struct { + PacketHeader header; +} ATTRIBUTE_PACKED GetFrameDuration_; + +typedef struct { + PacketHeader header; + uint16_t duration; +} ATTRIBUTE_PACKED GetFrameDurationResponse_; + +typedef struct { + PacketHeader header; +} ATTRIBUTE_PACKED GetSupplyVoltage_; + +typedef struct { + PacketHeader header; + uint16_t voltage; +} ATTRIBUTE_PACKED GetSupplyVoltageResponse_; + +typedef struct { + PacketHeader header; + uint16_t length; +} ATTRIBUTE_PACKED FrameRenderedCallback_; + +typedef struct { + PacketHeader header; + uint32_t frequency; +} ATTRIBUTE_PACKED SetClockFrequency_; + +typedef struct { + PacketHeader header; +} ATTRIBUTE_PACKED GetClockFrequency_; + +typedef struct { + PacketHeader header; + uint32_t frequency; +} ATTRIBUTE_PACKED GetClockFrequencyResponse_; + +typedef struct { + PacketHeader header; +} ATTRIBUTE_PACKED GetIdentity_; + +typedef struct { + PacketHeader header; + char uid[8]; + char connected_uid[8]; + char position; + uint8_t hardware_version[3]; + uint8_t firmware_version[3]; + uint16_t device_identifier; +} ATTRIBUTE_PACKED GetIdentityResponse_; + +#if defined _MSC_VER || defined __BORLANDC__ + #pragma pack(pop) +#endif +#undef ATTRIBUTE_PACKED + +static void led_strip_callback_wrapper_frame_rendered(DevicePrivate *device_p, Packet *packet) { + FrameRenderedCallbackFunction callback_function; + void *user_data = device_p->registered_callback_user_data[LED_STRIP_CALLBACK_FRAME_RENDERED]; + FrameRenderedCallback_ *callback = (FrameRenderedCallback_ *)packet; + *(void **)(&callback_function) = device_p->registered_callbacks[LED_STRIP_CALLBACK_FRAME_RENDERED]; + + if (callback_function == NULL) { + return; + } + + callback->length = leconvert_uint16_from(callback->length); + + callback_function(callback->length, user_data); +} + +void led_strip_create(LEDStrip *led_strip, const char *uid, IPConnection *ipcon) { + DevicePrivate *device_p; + + device_create(led_strip, uid, ipcon->p, 2, 0, 1); + + device_p = led_strip->p; + + device_p->response_expected[LED_STRIP_FUNCTION_SET_RGB_VALUES] = DEVICE_RESPONSE_EXPECTED_FALSE; + device_p->response_expected[LED_STRIP_FUNCTION_GET_RGB_VALUES] = DEVICE_RESPONSE_EXPECTED_ALWAYS_TRUE; + device_p->response_expected[LED_STRIP_FUNCTION_SET_FRAME_DURATION] = DEVICE_RESPONSE_EXPECTED_FALSE; + device_p->response_expected[LED_STRIP_FUNCTION_GET_FRAME_DURATION] = DEVICE_RESPONSE_EXPECTED_ALWAYS_TRUE; + device_p->response_expected[LED_STRIP_FUNCTION_GET_SUPPLY_VOLTAGE] = DEVICE_RESPONSE_EXPECTED_ALWAYS_TRUE; + device_p->response_expected[LED_STRIP_CALLBACK_FRAME_RENDERED] = DEVICE_RESPONSE_EXPECTED_ALWAYS_FALSE; + device_p->response_expected[LED_STRIP_FUNCTION_SET_CLOCK_FREQUENCY] = DEVICE_RESPONSE_EXPECTED_FALSE; + device_p->response_expected[LED_STRIP_FUNCTION_GET_CLOCK_FREQUENCY] = DEVICE_RESPONSE_EXPECTED_ALWAYS_TRUE; + device_p->response_expected[LED_STRIP_FUNCTION_GET_IDENTITY] = DEVICE_RESPONSE_EXPECTED_ALWAYS_TRUE; + + device_p->callback_wrappers[LED_STRIP_CALLBACK_FRAME_RENDERED] = led_strip_callback_wrapper_frame_rendered; +} + +void led_strip_destroy(LEDStrip *led_strip) { + device_destroy(led_strip); +} + +int led_strip_get_response_expected(LEDStrip *led_strip, uint8_t function_id, bool *ret_response_expected) { + return device_get_response_expected(led_strip->p, function_id, ret_response_expected); +} + +int led_strip_set_response_expected(LEDStrip *led_strip, uint8_t function_id, bool response_expected) { + return device_set_response_expected(led_strip->p, function_id, response_expected); +} + +int led_strip_set_response_expected_all(LEDStrip *led_strip, bool response_expected) { + return device_set_response_expected_all(led_strip->p, response_expected); +} + +void led_strip_register_callback(LEDStrip *led_strip, uint8_t id, void *callback, void *user_data) { + device_register_callback(led_strip->p, id, callback, user_data); +} + +int led_strip_get_api_version(LEDStrip *led_strip, uint8_t ret_api_version[3]) { + return device_get_api_version(led_strip->p, ret_api_version); +} + +int led_strip_set_rgb_values(LEDStrip *led_strip, uint16_t index, uint8_t length, uint8_t r[16], uint8_t g[16], uint8_t b[16]) { + DevicePrivate *device_p = led_strip->p; + SetRGBValues_ request; + int ret; + + ret = packet_header_create(&request.header, sizeof(request), LED_STRIP_FUNCTION_SET_RGB_VALUES, device_p->ipcon_p, device_p); + + if (ret < 0) { + return ret; + } + + request.index = leconvert_uint16_to(index); + request.length = length; + memcpy(request.r, r, 16 * sizeof(uint8_t)); + memcpy(request.g, g, 16 * sizeof(uint8_t)); + memcpy(request.b, b, 16 * sizeof(uint8_t)); + + ret = device_send_request(device_p, (Packet *)&request, NULL); + + + return ret; +} + +int led_strip_get_rgb_values(LEDStrip *led_strip, uint16_t index, uint8_t length, uint8_t ret_r[16], uint8_t ret_g[16], uint8_t ret_b[16]) { + DevicePrivate *device_p = led_strip->p; + GetRGBValues_ request; + GetRGBValuesResponse_ response; + int ret; + + ret = packet_header_create(&request.header, sizeof(request), LED_STRIP_FUNCTION_GET_RGB_VALUES, device_p->ipcon_p, device_p); + + if (ret < 0) { + return ret; + } + + request.index = leconvert_uint16_to(index); + request.length = length; + + ret = device_send_request(device_p, (Packet *)&request, (Packet *)&response); + + if (ret < 0) { + return ret; + } + memcpy(ret_r, response.r, 16 * sizeof(uint8_t)); + memcpy(ret_g, response.g, 16 * sizeof(uint8_t)); + memcpy(ret_b, response.b, 16 * sizeof(uint8_t)); + + + + return ret; +} + +int led_strip_set_frame_duration(LEDStrip *led_strip, uint16_t duration) { + DevicePrivate *device_p = led_strip->p; + SetFrameDuration_ request; + int ret; + + ret = packet_header_create(&request.header, sizeof(request), LED_STRIP_FUNCTION_SET_FRAME_DURATION, device_p->ipcon_p, device_p); + + if (ret < 0) { + return ret; + } + + request.duration = leconvert_uint16_to(duration); + + ret = device_send_request(device_p, (Packet *)&request, NULL); + + + return ret; +} + +int led_strip_get_frame_duration(LEDStrip *led_strip, uint16_t *ret_duration) { + DevicePrivate *device_p = led_strip->p; + GetFrameDuration_ request; + GetFrameDurationResponse_ response; + int ret; + + ret = packet_header_create(&request.header, sizeof(request), LED_STRIP_FUNCTION_GET_FRAME_DURATION, device_p->ipcon_p, device_p); + + if (ret < 0) { + return ret; + } + + + ret = device_send_request(device_p, (Packet *)&request, (Packet *)&response); + + if (ret < 0) { + return ret; + } + *ret_duration = leconvert_uint16_from(response.duration); + + + + return ret; +} + +int led_strip_get_supply_voltage(LEDStrip *led_strip, uint16_t *ret_voltage) { + DevicePrivate *device_p = led_strip->p; + GetSupplyVoltage_ request; + GetSupplyVoltageResponse_ response; + int ret; + + ret = packet_header_create(&request.header, sizeof(request), LED_STRIP_FUNCTION_GET_SUPPLY_VOLTAGE, device_p->ipcon_p, device_p); + + if (ret < 0) { + return ret; + } + + + ret = device_send_request(device_p, (Packet *)&request, (Packet *)&response); + + if (ret < 0) { + return ret; + } + *ret_voltage = leconvert_uint16_from(response.voltage); + + + + return ret; +} + +int led_strip_set_clock_frequency(LEDStrip *led_strip, uint32_t frequency) { + DevicePrivate *device_p = led_strip->p; + SetClockFrequency_ request; + int ret; + + ret = packet_header_create(&request.header, sizeof(request), LED_STRIP_FUNCTION_SET_CLOCK_FREQUENCY, device_p->ipcon_p, device_p); + + if (ret < 0) { + return ret; + } + + request.frequency = leconvert_uint32_to(frequency); + + ret = device_send_request(device_p, (Packet *)&request, NULL); + + + return ret; +} + +int led_strip_get_clock_frequency(LEDStrip *led_strip, uint32_t *ret_frequency) { + DevicePrivate *device_p = led_strip->p; + GetClockFrequency_ request; + GetClockFrequencyResponse_ response; + int ret; + + ret = packet_header_create(&request.header, sizeof(request), LED_STRIP_FUNCTION_GET_CLOCK_FREQUENCY, device_p->ipcon_p, device_p); + + if (ret < 0) { + return ret; + } + + + ret = device_send_request(device_p, (Packet *)&request, (Packet *)&response); + + if (ret < 0) { + return ret; + } + *ret_frequency = leconvert_uint32_from(response.frequency); + + + + return ret; +} + +int led_strip_get_identity(LEDStrip *led_strip, char ret_uid[8], char ret_connected_uid[8], char *ret_position, uint8_t ret_hardware_version[3], uint8_t ret_firmware_version[3], uint16_t *ret_device_identifier) { + DevicePrivate *device_p = led_strip->p; + GetIdentity_ request; + GetIdentityResponse_ response; + int ret; + + ret = packet_header_create(&request.header, sizeof(request), LED_STRIP_FUNCTION_GET_IDENTITY, device_p->ipcon_p, device_p); + + if (ret < 0) { + return ret; + } + + + ret = device_send_request(device_p, (Packet *)&request, (Packet *)&response); + + if (ret < 0) { + return ret; + } + strncpy(ret_uid, response.uid, 8); + strncpy(ret_connected_uid, response.connected_uid, 8); + *ret_position = response.position; + memcpy(ret_hardware_version, response.hardware_version, 3 * sizeof(uint8_t)); + memcpy(ret_firmware_version, response.firmware_version, 3 * sizeof(uint8_t)); + *ret_device_identifier = leconvert_uint16_from(response.device_identifier); + + + + return ret; +} diff --git a/dependencies/build/tinkerforge/ip_connection.c b/dependencies/build/tinkerforge/ip_connection.c new file mode 100644 index 00000000..31cf4aee --- /dev/null +++ b/dependencies/build/tinkerforge/ip_connection.c @@ -0,0 +1,2013 @@ +/* + * Copyright (C) 2012-2013 Matthias Bolte + * Copyright (C) 2011 Olaf Lüke + * + * Redistribution and use in source and binary forms of this file, + * with or without modification, are permitted. + */ + +#ifndef _WIN32 + #define _BSD_SOURCE // for usleep from unistd.h +#endif + +#include +#include +#include +#include +#include +#include + +#ifdef _WIN32 + #include +#else + #include + #include + #include // gettimeofday + #include // connect + #include + #include // TCP_NO_DELAY + #include // gethostbyname + #include // struct sockaddr_in +#endif + +#define IPCON_EXPOSE_INTERNALS + +#include "ip_connection.h" + +#if defined _MSC_VER || defined __BORLANDC__ + #pragma pack(push) + #pragma pack(1) + #define ATTRIBUTE_PACKED +#elif defined __GNUC__ + #ifdef _WIN32 + // workaround struct packing bug in GCC 4.7 on Windows + // http://gcc.gnu.org/bugzilla/show_bug.cgi?id=52991 + #define ATTRIBUTE_PACKED __attribute__((gcc_struct, packed)) + #else + #define ATTRIBUTE_PACKED __attribute__((packed)) + #endif +#else + #error unknown compiler, do not know how to enable struct packing +#endif + +typedef struct { + PacketHeader header; +} ATTRIBUTE_PACKED Enumerate; + +typedef struct { + PacketHeader header; + char uid[8]; + char connected_uid[8]; + char position; + uint8_t hardware_version[3]; + uint8_t firmware_version[3]; + uint16_t device_identifier; + uint8_t enumeration_type; +} ATTRIBUTE_PACKED EnumerateCallback; + +#if defined _MSC_VER || defined __BORLANDC__ + #pragma pack(pop) +#endif +#undef ATTRIBUTE_PACKED + +#ifndef __cplusplus + #ifdef __GNUC__ + #ifndef __GNUC_PREREQ + #define __GNUC_PREREQ(major, minor) \ + ((((__GNUC__) << 16) + (__GNUC_MINOR__)) >= (((major) << 16) + (minor))) + #endif + #if __GNUC_PREREQ(4, 6) + #define STATIC_ASSERT(condition, message) \ + _Static_assert(condition, message) + #else + #define STATIC_ASSERT(condition, message) // FIXME + #endif + #else + #define STATIC_ASSERT(condition, message) // FIXME + #endif + + STATIC_ASSERT(sizeof(PacketHeader) == 8, "PacketHeader has invalid size"); + STATIC_ASSERT(sizeof(Packet) == 80, "Packet has invalid size"); + STATIC_ASSERT(sizeof(EnumerateCallback) == 34, "EnumerateCallback has invalid size"); +#endif + +/***************************************************************************** + * + * BASE58 + * + *****************************************************************************/ + +#define BASE58_MAX_STR_SIZE 13 + +static const char BASE58_ALPHABET[] = \ + "123456789abcdefghijkmnopqrstuvwxyzABCDEFGHJKLMNPQRSTUVWXYZ"; + +#if 0 +static void base58_encode(uint64_t value, char *str) { + uint32_t mod; + char reverse_str[BASE58_MAX_STR_SIZE] = {'\0'}; + int i = 0; + int k = 0; + + while (value >= 58) { + mod = value % 58; + reverse_str[i] = BASE58_ALPHABET[mod]; + value = value / 58; + ++i; + } + + reverse_str[i] = BASE58_ALPHABET[value]; + + for (k = 0; k <= i; k++) { + str[k] = reverse_str[i - k]; + } + + for (; k < BASE58_MAX_STR_SIZE; k++) { + str[k] = '\0'; + } +} +#endif + +static uint64_t base58_decode(const char *str) { + int i; + int k; + uint64_t value = 0; + uint64_t base = 1; + + for (i = 0; i < BASE58_MAX_STR_SIZE; i++) { + if (str[i] == '\0') { + break; + } + } + + --i; + + for (; i >= 0; i--) { + if (str[i] == '\0') { + continue; + } + + for (k = 0; k < 58; k++) { + if (BASE58_ALPHABET[k] == str[i]) { + break; + } + } + + value += k * base; + base *= 58; + } + + return value; +} + +/***************************************************************************** + * + * Socket + * + *****************************************************************************/ + +struct _Socket { +#ifdef _WIN32 + SOCKET handle; +#else + int handle; +#endif + Mutex send_mutex; // used to serialize socket_send calls +}; + +#ifdef _WIN32 + +static int socket_create(Socket *socket_, int domain, int type, int protocol) { + BOOL flag = 1; + + socket_->handle = socket(domain, type, protocol); + + if (socket_->handle == INVALID_SOCKET) { + return -1; + } + + if (setsockopt(socket_->handle, IPPROTO_TCP, TCP_NODELAY, + (const char *)&flag, sizeof(flag)) == SOCKET_ERROR) { + closesocket(socket_->handle); + + return -1; + } + + mutex_create(&socket_->send_mutex); + + return 0; +} + +static void socket_destroy(Socket *socket) { + mutex_destroy(&socket->send_mutex); + + closesocket(socket->handle); +} + +static int socket_connect(Socket *socket, struct sockaddr_in *address, int length) { + return connect(socket->handle, (struct sockaddr *)address, length) == SOCKET_ERROR ? -1 : 0; +} + +static void socket_shutdown(Socket *socket) { + shutdown(socket->handle, SD_BOTH); +} + +static int socket_receive(Socket *socket, void *buffer, int length) { + length = recv(socket->handle, (char *)buffer, length, 0); + + if (length == SOCKET_ERROR) { + length = -1; + + if (WSAGetLastError() == WSAEINTR) { + errno = EINTR; + } else { + errno = EFAULT; + } + } + + return length; +} + +static int socket_send(Socket *socket, void *buffer, int length) { + mutex_lock(&socket->send_mutex); + + length = send(socket->handle, (const char *)buffer, length, 0); + + mutex_unlock(&socket->send_mutex); + + if (length == SOCKET_ERROR) { + length = -1; + } + + return length; +} + +#else + +static int socket_create(Socket *socket_, int domain, int type, int protocol) { + int flag = 1; + + socket_->handle = socket(domain, type, protocol); + + if (socket_->handle < 0) { + return -1; + } + + if (setsockopt(socket_->handle, IPPROTO_TCP, TCP_NODELAY, (void *)&flag, + sizeof(flag)) < 0) { + close(socket_->handle); + + return -1; + } + + mutex_create(&socket_->send_mutex); + + return 0; +} + +static void socket_destroy(Socket *socket) { + mutex_destroy(&socket->send_mutex); + + close(socket->handle); +} + +static int socket_connect(Socket *socket, struct sockaddr_in *address, int length) { + return connect(socket->handle, (struct sockaddr *)address, length); +} + +static void socket_shutdown(Socket *socket) { + shutdown(socket->handle, SHUT_RDWR); +} + +static int socket_receive(Socket *socket, void *buffer, int length) { + return recv(socket->handle, buffer, length, 0); +} + +static int socket_send(Socket *socket, void *buffer, int length) { + int rc; + + mutex_lock(&socket->send_mutex); + + rc = send(socket->handle, buffer, length, 0); + + mutex_unlock(&socket->send_mutex); + + return rc; +} + +#endif + +/***************************************************************************** + * + * Mutex + * + *****************************************************************************/ + +#ifdef _WIN32 + +void mutex_create(Mutex *mutex) { + InitializeCriticalSection(&mutex->handle); +} + +void mutex_destroy(Mutex *mutex) { + DeleteCriticalSection(&mutex->handle); +} + +void mutex_lock(Mutex *mutex) { + EnterCriticalSection(&mutex->handle); +} + +void mutex_unlock(Mutex *mutex) { + LeaveCriticalSection(&mutex->handle); +} + +#else + +void mutex_create(Mutex *mutex) { + pthread_mutex_init(&mutex->handle, NULL); +} + +void mutex_destroy(Mutex *mutex) { + pthread_mutex_destroy(&mutex->handle); +} + +void mutex_lock(Mutex *mutex) { + pthread_mutex_lock(&mutex->handle); +} + +void mutex_unlock(Mutex *mutex) { + pthread_mutex_unlock(&mutex->handle); +} +#endif + +/***************************************************************************** + * + * Event + * + *****************************************************************************/ + +#ifdef _WIN32 + +static void event_create(Event *event) { + event->handle = CreateEvent(NULL, TRUE, FALSE, NULL); +} + +static void event_destroy(Event *event) { + CloseHandle(event->handle); +} + +static void event_set(Event *event) { + SetEvent(event->handle); +} + +static void event_reset(Event *event) { + ResetEvent(event->handle); +} + +static int event_wait(Event *event, uint32_t timeout) { // in msec + return WaitForSingleObject(event->handle, timeout) == WAIT_OBJECT_0 ? 0 : -1; +} + +#else + +static void event_create(Event *event) { + pthread_mutex_init(&event->mutex, NULL); + pthread_cond_init(&event->condition, NULL); + + event->flag = false; +} + +static void event_destroy(Event *event) { + pthread_mutex_destroy(&event->mutex); + pthread_cond_destroy(&event->condition); +} + +static void event_set(Event *event) { + pthread_mutex_lock(&event->mutex); + + event->flag = true; + + pthread_cond_signal(&event->condition); + pthread_mutex_unlock(&event->mutex); +} + +static void event_reset(Event *event) { + pthread_mutex_lock(&event->mutex); + + event->flag = false; + + pthread_mutex_unlock(&event->mutex); +} + +static int event_wait(Event *event, uint32_t timeout) { // in msec + struct timeval tp; + struct timespec ts; + int ret = 0; + + gettimeofday(&tp, NULL); + + ts.tv_sec = tp.tv_sec + timeout / 1000; + ts.tv_nsec = (tp.tv_usec + (timeout % 1000) * 1000) * 1000; + + while (ts.tv_nsec >= 1000000000L) { + ts.tv_sec += 1; + ts.tv_nsec -= 1000000000L; + } + + pthread_mutex_lock(&event->mutex); + + while (!event->flag) { + ret = pthread_cond_timedwait(&event->condition, &event->mutex, &ts); + + if (ret != 0) { + ret = -1; + break; + } + } + + pthread_mutex_unlock(&event->mutex); + + return ret; +} + +#endif + +/***************************************************************************** + * + * Semaphore + * + *****************************************************************************/ + +#ifdef _WIN32 + +static int semaphore_create(Semaphore *semaphore) { + semaphore->handle = CreateSemaphore(NULL, 0, INT32_MAX, NULL); + + return semaphore->handle == NULL ? -1 : 0; +} + +static void semaphore_destroy(Semaphore *semaphore) { + CloseHandle(semaphore->handle); +} + +static int semaphore_acquire(Semaphore *semaphore) { + return WaitForSingleObject(semaphore->handle, INFINITE) != WAIT_OBJECT_0 ? -1 : 0; +} + +static void semaphore_release(Semaphore *semaphore) { + ReleaseSemaphore(semaphore->handle, 1, NULL); +} + +#else + +static int semaphore_create(Semaphore *semaphore) { +#ifdef __APPLE__ + // Mac OS X does not support unnamed semaphores, so we fake them. Unlink + // first to ensure that there is no existing semaphore with that name. + // Then open the semaphore to create a new one. Finally unlink it again to + // avoid leaking the name. The semaphore will work fine without a name. + char name[100]; + + snprintf(name, sizeof(name), "tf-ipcon-%p", semaphore); + + sem_unlink(name); + semaphore->pointer = sem_open(name, O_CREAT | O_EXCL, S_IRWXU, 0); + sem_unlink(name); + + if (semaphore->pointer == SEM_FAILED) { + return -1; + } +#else + semaphore->pointer = &semaphore->object; + + if (sem_init(semaphore->pointer, 0, 0) < 0) { + return -1; + } +#endif + + return 0; +} + +static void semaphore_destroy(Semaphore *semaphore) { +#ifdef __APPLE__ + sem_close(semaphore->pointer); +#else + sem_destroy(semaphore->pointer); +#endif +} + +static int semaphore_acquire(Semaphore *semaphore) { + return sem_wait(semaphore->pointer) < 0 ? -1 : 0; +} + +static void semaphore_release(Semaphore *semaphore) { + sem_post(semaphore->pointer); +} + +#endif + +/***************************************************************************** + * + * Thread + * + *****************************************************************************/ + +#ifdef _WIN32 + +static DWORD WINAPI thread_wrapper(void *opaque) { + Thread *thread = (Thread *)opaque; + + thread->function(thread->opaque); + + return 0; +} + +static int thread_create(Thread *thread, ThreadFunction function, void *opaque) { + thread->function = function; + thread->opaque = opaque; + + thread->handle = CreateThread(NULL, 0, thread_wrapper, thread, 0, &thread->id); + + return thread->handle == NULL ? -1 : 0; +} + +static void thread_destroy(Thread *thread) { + CloseHandle(thread->handle); +} + +static bool thread_is_current(Thread *thread) { + return thread->id == GetCurrentThreadId(); +} + +static void thread_join(Thread *thread) { + WaitForSingleObject(thread->handle, INFINITE); +} + +static void thread_sleep(int msec) { + Sleep(msec); +} + +#else + +static void *thread_wrapper(void *opaque) { + Thread *thread = (Thread *)opaque; + + thread->function(thread->opaque); + + return NULL; +} + +static int thread_create(Thread *thread, ThreadFunction function, void *opaque) { + thread->function = function; + thread->opaque = opaque; + + return pthread_create(&thread->handle, NULL, thread_wrapper, thread); +} + +static void thread_destroy(Thread *thread) { + (void)thread; +} + +static bool thread_is_current(Thread *thread) { + return pthread_equal(thread->handle, pthread_self()) ? true : false; +} + +static void thread_join(Thread *thread) { + pthread_join(thread->handle, NULL); +} + +static void thread_sleep(int msec) { + usleep(msec * 1000); +} + +#endif + +/***************************************************************************** + * + * Table + * + *****************************************************************************/ + +static void table_create(Table *table) { + mutex_create(&table->mutex); + + table->used = 0; + table->allocated = 16; + table->keys = (uint32_t *)malloc(sizeof(uint32_t) * table->allocated); + table->values = (void **)malloc(sizeof(void *) * table->allocated); +} + +static void table_destroy(Table *table) { + free(table->keys); + free(table->values); + + mutex_destroy(&table->mutex); +} + +static void table_insert(Table *table, uint32_t key, void *value) { + int i; + + mutex_lock(&table->mutex); + + for (i = 0; i < table->used; ++i) { + if (table->keys[i] == key) { + table->values[i] = value; + + mutex_unlock(&table->mutex); + + return; + } + } + + if (table->allocated <= table->used) { + table->allocated += 16; + table->keys = (uint32_t *)realloc(table->keys, sizeof(uint32_t) * table->allocated); + table->values = (void **)realloc(table->values, sizeof(void *) * table->allocated); + } + + table->keys[table->used] = key; + table->values[table->used] = value; + + ++table->used; + + mutex_unlock(&table->mutex); +} + +static void table_remove(Table *table, uint32_t key) { + int i; + int tail; + + mutex_lock(&table->mutex); + + for (i = 0; i < table->used; ++i) { + if (table->keys[i] == key) { + tail = table->used - i - 1; + + if (tail > 0) { + memmove(table->keys + i, table->keys + i + 1, sizeof(uint32_t) * tail); + memmove(table->values + i, table->values + i + 1, sizeof(void *) * tail); + } + + --table->used; + + break; + } + } + + mutex_unlock(&table->mutex); +} + +static void *table_get(Table *table, uint32_t key) { + int i; + void *value = NULL; + + mutex_lock(&table->mutex); + + for (i = 0; i < table->used; ++i) { + if (table->keys[i] == key) { + value = table->values[i]; + + break; + } + } + + mutex_unlock(&table->mutex); + + return value; +} + +/***************************************************************************** + * + * Queue + * + *****************************************************************************/ + +enum { + QUEUE_KIND_EXIT = 0, + QUEUE_KIND_META, + QUEUE_KIND_PACKET +}; + +typedef struct { + uint8_t function_id; + uint8_t parameter; + uint64_t socket_id; +} Meta; + +static void queue_create(Queue *queue) { + queue->head = NULL; + queue->tail = NULL; + + mutex_create(&queue->mutex); + semaphore_create(&queue->semaphore); +} + +static void queue_destroy(Queue *queue) { + QueueItem *item = queue->head; + QueueItem *next; + + while (item != NULL) { + next = item->next; + + free(item->data); + free(item); + + item = next; + } + + mutex_destroy(&queue->mutex); + semaphore_destroy(&queue->semaphore); +} + +static void queue_put(Queue *queue, int kind, void *data, int length) { + QueueItem *item = (QueueItem *)malloc(sizeof(QueueItem)); + + item->next = NULL; + item->kind = kind; + item->data = NULL; + item->length = length; + + if (data != NULL) { + item->data = malloc(length); + memcpy(item->data, data, length); + } + + mutex_lock(&queue->mutex); + + if (queue->tail == NULL) { + queue->head = item; + queue->tail = item; + } else { + queue->tail->next = item; + queue->tail = item; + } + + mutex_unlock(&queue->mutex); + semaphore_release(&queue->semaphore); +} + +static int queue_get(Queue *queue, int *kind, void **data, int *length) { + QueueItem *item; + + if (semaphore_acquire(&queue->semaphore) < 0) { + return -1; + } + + mutex_lock(&queue->mutex); + + if (queue->head == NULL) { + mutex_unlock(&queue->mutex); + + return -1; + } + + item = queue->head; + queue->head = item->next; + item->next = NULL; + + if (queue->tail == item) { + queue->head = NULL; + queue->tail = NULL; + } + + mutex_unlock(&queue->mutex); + + *kind = item->kind; + *data = item->data; + *length = item->length; + + free(item); + + return 0; +} + +/***************************************************************************** + * + * Device + * + *****************************************************************************/ + +enum { + IPCON_FUNCTION_ENUMERATE = 254 +}; + +static int ipcon_send_request(IPConnectionPrivate *ipcon_p, Packet *request); + +void device_create(Device *device, const char *uid_str, + IPConnectionPrivate *ipcon_p, uint8_t api_version_major, + uint8_t api_version_minor, uint8_t api_version_release) { + DevicePrivate *device_p; + uint64_t uid; + uint32_t value1; + uint32_t value2; + int i; + + device_p = (DevicePrivate *)malloc(sizeof(DevicePrivate)); + device->p = device_p; + + uid = base58_decode(uid_str); + + if (uid > 0xFFFFFFFF) { + // convert from 64bit to 32bit + value1 = uid & 0xFFFFFFFF; + value2 = (uid >> 32) & 0xFFFFFFFF; + + uid = (value1 & 0x00000FFF); + uid |= (value1 & 0x0F000000) >> 12; + uid |= (value2 & 0x0000003F) << 16; + uid |= (value2 & 0x000F0000) << 6; + uid |= (value2 & 0x3F000000) << 2; + } + + device_p->uid = uid & 0xFFFFFFFF; + + device_p->ipcon_p = ipcon_p; + + device_p->api_version[0] = api_version_major; + device_p->api_version[1] = api_version_minor; + device_p->api_version[2] = api_version_release; + + // request + mutex_create(&device_p->request_mutex); + + // response + device_p->expected_response_function_id = 0; + device_p->expected_response_sequence_number = 0; + + mutex_create(&device_p->response_mutex); + + memset(&device_p->response_packet, 0, sizeof(Packet)); + + event_create(&device_p->response_event); + + for (i = 0; i < DEVICE_NUM_FUNCTION_IDS; i++) { + device_p->response_expected[i] = DEVICE_RESPONSE_EXPECTED_INVALID_FUNCTION_ID; + } + + device_p->response_expected[IPCON_FUNCTION_ENUMERATE] = DEVICE_RESPONSE_EXPECTED_ALWAYS_FALSE; + device_p->response_expected[IPCON_CALLBACK_ENUMERATE] = DEVICE_RESPONSE_EXPECTED_ALWAYS_FALSE; + + // callbacks + for (i = 0; i < DEVICE_NUM_FUNCTION_IDS; i++) { + device_p->registered_callbacks[i] = NULL; + device_p->registered_callback_user_data[i] = NULL; + device_p->callback_wrappers[i] = NULL; + } + + // add to IPConnection + table_insert(&ipcon_p->devices, device_p->uid, device_p); +} + +void device_destroy(Device *device) { + DevicePrivate *device_p = device->p; + + table_remove(&device_p->ipcon_p->devices, device_p->uid); + + event_destroy(&device_p->response_event); + + mutex_destroy(&device_p->response_mutex); + + mutex_destroy(&device_p->request_mutex); + + free(device_p); +} + +int device_get_response_expected(DevicePrivate *device_p, uint8_t function_id, + bool *ret_response_expected) { + int flag = device_p->response_expected[function_id]; + + if (flag == DEVICE_RESPONSE_EXPECTED_INVALID_FUNCTION_ID) { + return E_INVALID_PARAMETER; + } + + if (flag == DEVICE_RESPONSE_EXPECTED_ALWAYS_TRUE || + flag == DEVICE_RESPONSE_EXPECTED_TRUE) { + *ret_response_expected = true; + } else { + *ret_response_expected = false; + } + + return E_OK; +} + +int device_set_response_expected(DevicePrivate *device_p, uint8_t function_id, + bool response_expected) { + int current_flag = device_p->response_expected[function_id]; + + if (current_flag != DEVICE_RESPONSE_EXPECTED_TRUE && + current_flag != DEVICE_RESPONSE_EXPECTED_FALSE) { + return E_INVALID_PARAMETER; + } + + device_p->response_expected[function_id] = + response_expected ? DEVICE_RESPONSE_EXPECTED_TRUE + : DEVICE_RESPONSE_EXPECTED_FALSE; + + return E_OK; +} + +int device_set_response_expected_all(DevicePrivate *device_p, bool response_expected) { + int flag = response_expected ? DEVICE_RESPONSE_EXPECTED_TRUE + : DEVICE_RESPONSE_EXPECTED_FALSE; + int i; + + for (i = 0; i < DEVICE_NUM_FUNCTION_IDS; ++i) { + if (device_p->response_expected[i] == DEVICE_RESPONSE_EXPECTED_TRUE || + device_p->response_expected[i] == DEVICE_RESPONSE_EXPECTED_FALSE) { + device_p->response_expected[i] = flag; + } + } + + return E_OK; +} + +void device_register_callback(DevicePrivate *device_p, uint8_t id, void *callback, + void *user_data) { + device_p->registered_callbacks[id] = callback; + device_p->registered_callback_user_data[id] = user_data; +} + +int device_get_api_version(DevicePrivate *device_p, uint8_t ret_api_version[3]) { + ret_api_version[0] = device_p->api_version[0]; + ret_api_version[1] = device_p->api_version[1]; + ret_api_version[2] = device_p->api_version[2]; + + return E_OK; +} + +int device_send_request(DevicePrivate *device_p, Packet *request, Packet *response) { + int ret = E_OK; + uint8_t sequence_number = packet_header_get_sequence_number(&request->header); + uint8_t response_expected = packet_header_get_response_expected(&request->header); + uint8_t error_code; + + if (response_expected) { + mutex_lock(&device_p->request_mutex); + + event_reset(&device_p->response_event); + + device_p->expected_response_function_id = request->header.function_id; + device_p->expected_response_sequence_number = sequence_number; + } + + ret = ipcon_send_request(device_p->ipcon_p, request); + + if (ret != E_OK) { + if (response_expected) { + mutex_unlock(&device_p->request_mutex); + } + + return ret; + } + + if (response_expected) { + if (event_wait(&device_p->response_event, device_p->ipcon_p->timeout) < 0) { + ret = E_TIMEOUT; + } + + device_p->expected_response_function_id = 0; + device_p->expected_response_sequence_number = 0; + + event_reset(&device_p->response_event); + + if (ret == E_OK) { + mutex_lock(&device_p->response_mutex); + + error_code = packet_header_get_error_code(&device_p->response_packet.header); + + if (device_p->response_packet.header.function_id != request->header.function_id || + packet_header_get_sequence_number(&device_p->response_packet.header) != sequence_number) { + ret = E_TIMEOUT; + } else if (error_code == 0) { + // no error + if (response != NULL) { + memcpy(response, &device_p->response_packet, + device_p->response_packet.header.length); + } + } else if (error_code == 1) { + ret = E_INVALID_PARAMETER; + } else if (error_code == 2) { + ret = E_NOT_SUPPORTED; + } else { + ret = E_UNKNOWN_ERROR_CODE; + } + + mutex_unlock(&device_p->response_mutex); + } + + mutex_unlock(&device_p->request_mutex); + } + + return ret; +} + +/***************************************************************************** + * + * IPConnection + * + *****************************************************************************/ + +struct _CallbackContext { + IPConnectionPrivate *ipcon_p; + Queue queue; + Thread thread; + Mutex mutex; + bool packet_dispatch_allowed; +}; + +static int ipcon_connect_unlocked(IPConnectionPrivate *ipcon_p, bool is_auto_reconnect); +static void ipcon_disconnect_unlocked(IPConnectionPrivate *ipcon_p); + +static void ipcon_dispatch_meta(IPConnectionPrivate *ipcon_p, Meta *meta) { + ConnectedCallbackFunction connected_callback_function; + DisconnectedCallbackFunction disconnected_callback_function; + void *user_data; + bool retry; + + if (meta->function_id == IPCON_CALLBACK_CONNECTED) { + if (ipcon_p->registered_callbacks[IPCON_CALLBACK_CONNECTED] != NULL) { + *(void **)(&connected_callback_function) = ipcon_p->registered_callbacks[IPCON_CALLBACK_CONNECTED]; + user_data = ipcon_p->registered_callback_user_data[IPCON_CALLBACK_CONNECTED]; + + connected_callback_function(meta->parameter, user_data); + } + } else if (meta->function_id == IPCON_CALLBACK_DISCONNECTED) { + // need to do this here, the receive loop is not allowed to + // hold the socket mutex because this could cause a deadlock + // with a concurrent call to the (dis-)connect function + if (meta->parameter != IPCON_DISCONNECT_REASON_REQUEST) { + mutex_lock(&ipcon_p->socket_mutex); + + // don't close the socket if it got disconnected or + // reconnected in the meantime + if (ipcon_p->socket != NULL && ipcon_p->socket_id == meta->socket_id) { + // destroy disconnect probe thread + event_set(&ipcon_p->disconnect_probe_event); + thread_join(&ipcon_p->disconnect_probe_thread); + thread_destroy(&ipcon_p->disconnect_probe_thread); + + // destroy socket + socket_destroy(ipcon_p->socket); + free(ipcon_p->socket); + ipcon_p->socket = NULL; + } + + mutex_unlock(&ipcon_p->socket_mutex); + } + + // FIXME: wait a moment here, otherwise the next connect + // attempt will succeed, even if there is no open server + // socket. the first receive will then fail directly + thread_sleep(100); + + if (ipcon_p->registered_callbacks[IPCON_CALLBACK_DISCONNECTED] != NULL) { + *(void **)(&disconnected_callback_function) = ipcon_p->registered_callbacks[IPCON_CALLBACK_DISCONNECTED]; + user_data = ipcon_p->registered_callback_user_data[IPCON_CALLBACK_DISCONNECTED]; + + disconnected_callback_function(meta->parameter, user_data); + } + + if (meta->parameter != IPCON_DISCONNECT_REASON_REQUEST && + ipcon_p->auto_reconnect && ipcon_p->auto_reconnect_allowed) { + ipcon_p->auto_reconnect_pending = true; + retry = true; + + // block here until reconnect. this is okay, there is no + // callback to deliver when there is no connection + while (retry) { + retry = false; + + mutex_lock(&ipcon_p->socket_mutex); + + if (ipcon_p->auto_reconnect_allowed && ipcon_p->socket == NULL) { + if (ipcon_connect_unlocked(ipcon_p, true) < 0) { + retry = true; + } + } else { + ipcon_p->auto_reconnect_pending = false; + } + + mutex_unlock(&ipcon_p->socket_mutex); + + if (retry) { + // wait a moment to give another thread a chance to + // interrupt the auto-reconnect + thread_sleep(100); + } + } + } + } +} + +static void ipcon_dispatch_packet(IPConnectionPrivate *ipcon_p, Packet *packet) { + EnumerateCallbackFunction enumerate_callback_function; + void *user_data; + EnumerateCallback *enumerate_callback; + DevicePrivate *device_p; + CallbackWrapperFunction callback_wrapper_function; + + if (packet->header.function_id == IPCON_CALLBACK_ENUMERATE) { + if (ipcon_p->registered_callbacks[IPCON_CALLBACK_ENUMERATE] != NULL) { + *(void **)(&enumerate_callback_function) = ipcon_p->registered_callbacks[IPCON_CALLBACK_ENUMERATE]; + user_data = ipcon_p->registered_callback_user_data[IPCON_CALLBACK_ENUMERATE]; + enumerate_callback = (EnumerateCallback *)packet; + + enumerate_callback_function(enumerate_callback->uid, + enumerate_callback->connected_uid, + enumerate_callback->position, + enumerate_callback->hardware_version, + enumerate_callback->firmware_version, + leconvert_uint16_from(enumerate_callback->device_identifier), + enumerate_callback->enumeration_type, + user_data); + } + } else { + device_p = (DevicePrivate *)table_get(&ipcon_p->devices, packet->header.uid); + + if (device_p == NULL) { + return; + } + + callback_wrapper_function = device_p->callback_wrappers[packet->header.function_id]; + + if (callback_wrapper_function == NULL) { + return; + } + + callback_wrapper_function(device_p, packet); + } +} + +static void ipcon_callback_loop(void *opaque) { + CallbackContext *callback = (CallbackContext *)opaque; + int kind; + void *data; + int length; + + while (true) { + if (queue_get(&callback->queue, &kind, &data, &length) < 0) { + // FIXME: what to do here? try again? exit? + break; + } + + // FIXME: cannot lock callback mutex here because this can + // deadlock due to an ordering problem with the socket mutex + //mutex_lock(&callback->mutex); + + if (kind == QUEUE_KIND_EXIT) { + //mutex_unlock(&callback->mutex); + break; + } else if (kind == QUEUE_KIND_META) { + ipcon_dispatch_meta(callback->ipcon_p, (Meta *)data); + } else if (kind == QUEUE_KIND_PACKET) { + // don't dispatch callbacks when the receive thread isn't running + if (callback->packet_dispatch_allowed) { + ipcon_dispatch_packet(callback->ipcon_p, (Packet *)data); + } + } + + //mutex_unlock(&callback->mutex); + + free(data); + } + + // cleanup + mutex_destroy(&callback->mutex); + queue_destroy(&callback->queue); + thread_destroy(&callback->thread); + + free(callback); +} + +// NOTE: assumes that socket_mutex is locked if disconnect_immediately is true +static void ipcon_handle_disconnect_by_peer(IPConnectionPrivate *ipcon_p, + uint8_t disconnect_reason, + uint64_t socket_id, + bool disconnect_immediately) { + Meta meta; + + ipcon_p->auto_reconnect_allowed = true; + + if (disconnect_immediately) { + ipcon_disconnect_unlocked(ipcon_p); + } + + meta.function_id = IPCON_CALLBACK_DISCONNECTED; + meta.parameter = disconnect_reason; + meta.socket_id = socket_id; + + queue_put(&ipcon_p->callback->queue, QUEUE_KIND_META, &meta, sizeof(meta)); +} + +enum { + IPCON_DISCONNECT_PROBE_INTERVAL = 5000 +}; + +enum { + IPCON_FUNCTION_DISCONNECT_PROBE = 128 +}; + +// NOTE: the disconnect probe loop is not allowed to hold the socket_mutex at any +// time because it is created and joined while the socket_mutex is locked +static void ipcon_disconnect_probe_loop(void *opaque) { + IPConnectionPrivate *ipcon_p = (IPConnectionPrivate *)opaque; + PacketHeader disconnect_probe; + + packet_header_create(&disconnect_probe, sizeof(PacketHeader), + IPCON_FUNCTION_DISCONNECT_PROBE, ipcon_p, NULL); + + while (event_wait(&ipcon_p->disconnect_probe_event, + IPCON_DISCONNECT_PROBE_INTERVAL) < 0) { + if (ipcon_p->disconnect_probe_flag) { + // FIXME: this might block + if (socket_send(ipcon_p->socket, &disconnect_probe, + disconnect_probe.length) < 0) { + ipcon_handle_disconnect_by_peer(ipcon_p, IPCON_DISCONNECT_REASON_ERROR, + ipcon_p->socket_id, false); + break; + } + } else { + ipcon_p->disconnect_probe_flag = true; + } + } +} + +static void ipcon_handle_response(IPConnectionPrivate *ipcon_p, Packet *response) { + DevicePrivate *device_p; + uint8_t sequence_number = packet_header_get_sequence_number(&response->header); + + ipcon_p->disconnect_probe_flag = false; + + response->header.uid = leconvert_uint32_from(response->header.uid); + + if (sequence_number == 0 && + response->header.function_id == IPCON_CALLBACK_ENUMERATE) { + if (ipcon_p->registered_callbacks[IPCON_CALLBACK_ENUMERATE] != NULL) { + queue_put(&ipcon_p->callback->queue, QUEUE_KIND_PACKET, response, + response->header.length); + } + + return; + } + + device_p = (DevicePrivate *)table_get(&ipcon_p->devices, response->header.uid); + + if (device_p == NULL) { + // ignoring response for an unknown device + return; + } + + if (sequence_number == 0) { + if (device_p->registered_callbacks[response->header.function_id] != NULL) { + queue_put(&ipcon_p->callback->queue, QUEUE_KIND_PACKET, response, + response->header.length); + } + + return; + } + + if (device_p->expected_response_function_id == response->header.function_id && + device_p->expected_response_sequence_number == sequence_number) { + mutex_lock(&device_p->response_mutex); + memcpy(&device_p->response_packet, response, response->header.length); + mutex_unlock(&device_p->response_mutex); + + event_set(&device_p->response_event); + return; + } + + // response seems to be OK, but can't be handled +} + +// NOTE: the receive loop is now allowed to hold the socket_mutex at any time +// because it is created and joined while the socket_mutex is locked +static void ipcon_receive_loop(void *opaque) { + IPConnectionPrivate *ipcon_p = (IPConnectionPrivate *)opaque; + uint64_t socket_id = ipcon_p->socket_id; + Packet pending_data[10]; + int pending_length = 0; + int length; + uint8_t disconnect_reason; + + while (ipcon_p->receive_flag) { + length = socket_receive(ipcon_p->socket, (uint8_t *)pending_data + pending_length, + sizeof(pending_data) - pending_length); + + if (!ipcon_p->receive_flag) { + return; + } + + if (length <= 0) { + if (length < 0 && errno == EINTR) { + continue; + } + + if (length == 0) { + disconnect_reason = IPCON_DISCONNECT_REASON_SHUTDOWN; + } else { + disconnect_reason = IPCON_DISCONNECT_REASON_ERROR; + } + + ipcon_handle_disconnect_by_peer(ipcon_p, disconnect_reason, socket_id, false); + return; + } + + pending_length += length; + + while (ipcon_p->receive_flag) { + if (pending_length < 8) { + // wait for complete header + break; + } + + length = pending_data[0].header.length; + + if (pending_length < length) { + // wait for complete packet + break; + } + + ipcon_handle_response(ipcon_p, pending_data); + + memmove(pending_data, (uint8_t *)pending_data + length, + pending_length - length); + pending_length -= length; + } + } +} + +// NOTE: assumes that socket_mutex is locked +static int ipcon_connect_unlocked(IPConnectionPrivate *ipcon_p, bool is_auto_reconnect) { + struct hostent *entity; + struct sockaddr_in address; + uint8_t connect_reason; + Meta meta; + + // create callback queue and thread + if (ipcon_p->callback == NULL) { + ipcon_p->callback = (CallbackContext *)malloc(sizeof(CallbackContext)); + + ipcon_p->callback->ipcon_p = ipcon_p; + ipcon_p->callback->packet_dispatch_allowed = false; + + queue_create(&ipcon_p->callback->queue); + mutex_create(&ipcon_p->callback->mutex); + + if (thread_create(&ipcon_p->callback->thread, ipcon_callback_loop, + ipcon_p->callback) < 0) { + mutex_destroy(&ipcon_p->callback->mutex); + queue_destroy(&ipcon_p->callback->queue); + + free(ipcon_p->callback); + ipcon_p->callback = NULL; + + return E_NO_THREAD; + } + } + + // create and connect socket + entity = gethostbyname(ipcon_p->host); + + if (entity == NULL) { + // destroy callback thread + if (!is_auto_reconnect) { + queue_put(&ipcon_p->callback->queue, QUEUE_KIND_EXIT, NULL, 0); + + if (!thread_is_current(&ipcon_p->callback->thread)) { + thread_join(&ipcon_p->callback->thread); + } + + ipcon_p->callback = NULL; + } + + return E_HOSTNAME_INVALID; + } + + memset(&address, 0, sizeof(struct sockaddr_in)); + memcpy(&address.sin_addr, entity->h_addr_list[0], entity->h_length); + + address.sin_family = AF_INET; + address.sin_port = htons(ipcon_p->port); + + ipcon_p->socket = (Socket *)malloc(sizeof(Socket)); + + if (socket_create(ipcon_p->socket, AF_INET, SOCK_STREAM, 0) < 0) { + // destroy callback thread + if (!is_auto_reconnect) { + queue_put(&ipcon_p->callback->queue, QUEUE_KIND_EXIT, NULL, 0); + + if (!thread_is_current(&ipcon_p->callback->thread)) { + thread_join(&ipcon_p->callback->thread); + } + + ipcon_p->callback = NULL; + } + + // destroy socket + free(ipcon_p->socket); + ipcon_p->socket = NULL; + + return E_NO_STREAM_SOCKET; + } + + if (socket_connect(ipcon_p->socket, &address, sizeof(address)) < 0) { + // destroy callback thread + if (!is_auto_reconnect) { + queue_put(&ipcon_p->callback->queue, QUEUE_KIND_EXIT, NULL, 0); + + if (!thread_is_current(&ipcon_p->callback->thread)) { + thread_join(&ipcon_p->callback->thread); + } + + ipcon_p->callback = NULL; + } + + // destroy socket + socket_destroy(ipcon_p->socket); + free(ipcon_p->socket); + ipcon_p->socket = NULL; + + return E_NO_CONNECT; + } + + ++ipcon_p->socket_id; + + // create disconnect probe thread + ipcon_p->disconnect_probe_flag = true; + + event_reset(&ipcon_p->disconnect_probe_event); + + if (thread_create(&ipcon_p->disconnect_probe_thread, + ipcon_disconnect_probe_loop, ipcon_p) < 0) { + // destroy callback thread + if (!is_auto_reconnect) { + queue_put(&ipcon_p->callback->queue, QUEUE_KIND_EXIT, NULL, 0); + + if (!thread_is_current(&ipcon_p->callback->thread)) { + thread_join(&ipcon_p->callback->thread); + } + + ipcon_p->callback = NULL; + } + + // destroy socket + socket_destroy(ipcon_p->socket); + free(ipcon_p->socket); + ipcon_p->socket = NULL; + + return E_NO_THREAD; + } + + // create receive thread + ipcon_p->receive_flag = true; + ipcon_p->callback->packet_dispatch_allowed = true; + + if (thread_create(&ipcon_p->receive_thread, ipcon_receive_loop, ipcon_p) < 0) { + ipcon_disconnect_unlocked(ipcon_p); + + // destroy callback thread + if (!is_auto_reconnect) { + queue_put(&ipcon_p->callback->queue, QUEUE_KIND_EXIT, NULL, 0); + + if (!thread_is_current(&ipcon_p->callback->thread)) { + thread_join(&ipcon_p->callback->thread); + } + + ipcon_p->callback = NULL; + } + + return E_NO_THREAD; + } + + ipcon_p->auto_reconnect_allowed = false; + ipcon_p->auto_reconnect_pending = false; + + // trigger connected callback + if (is_auto_reconnect) { + connect_reason = IPCON_CONNECT_REASON_AUTO_RECONNECT; + } else { + connect_reason = IPCON_CONNECT_REASON_REQUEST; + } + + meta.function_id = IPCON_CALLBACK_CONNECTED; + meta.parameter = connect_reason; + meta.socket_id = 0; + + queue_put(&ipcon_p->callback->queue, QUEUE_KIND_META, &meta, sizeof(meta)); + + return E_OK; +} + +// NOTE: assumes that socket_mutex is locked +static void ipcon_disconnect_unlocked(IPConnectionPrivate *ipcon_p) { + // destroy disconnect probe thread + event_set(&ipcon_p->disconnect_probe_event); + thread_join(&ipcon_p->disconnect_probe_thread); + thread_destroy(&ipcon_p->disconnect_probe_thread); + + // stop dispatching packet callbacks before ending the receive + // thread to avoid timeout exceptions due to callback functions + // trying to call getters + if (!thread_is_current(&ipcon_p->callback->thread)) { + // FIXME: cannot lock callback mutex here because this can + // deadlock due to an ordering problem with the socket mutex + //mutex_lock(&ipcon->callback->mutex); + + ipcon_p->callback->packet_dispatch_allowed = false; + + //mutex_unlock(&ipcon->callback->mutex); + } else { + ipcon_p->callback->packet_dispatch_allowed = false; + } + + // destroy receive thread + if (ipcon_p->receive_flag) { + ipcon_p->receive_flag = false; + + socket_shutdown(ipcon_p->socket); + + thread_join(&ipcon_p->receive_thread); + thread_destroy(&ipcon_p->receive_thread); + } + + // destroy socket + socket_destroy(ipcon_p->socket); + free(ipcon_p->socket); + ipcon_p->socket = NULL; +} + +static int ipcon_send_request(IPConnectionPrivate *ipcon_p, Packet *request) { + int ret = E_OK; + + mutex_lock(&ipcon_p->socket_mutex); + + if (ipcon_p->socket == NULL) { + ret = E_NOT_CONNECTED; + } + + if (ret == E_OK) { + if (socket_send(ipcon_p->socket, request, request->header.length) < 0) { + ipcon_handle_disconnect_by_peer(ipcon_p, IPCON_DISCONNECT_REASON_ERROR, + 0, true); + + ret = E_NOT_CONNECTED; + } else { + ipcon_p->disconnect_probe_flag = false; + } + } + + mutex_unlock(&ipcon_p->socket_mutex); + + return ret; +} + +void ipcon_create(IPConnection *ipcon) { + IPConnectionPrivate *ipcon_p; + int i; + + ipcon_p = (IPConnectionPrivate *)malloc(sizeof(IPConnectionPrivate)); + ipcon->p = ipcon_p; + +#ifdef _WIN32 + ipcon_p->wsa_startup_done = false; +#endif + + ipcon_p->host = NULL; + ipcon_p->port = 0; + + ipcon_p->timeout = 2500; + + ipcon_p->auto_reconnect = true; + ipcon_p->auto_reconnect_allowed = false; + ipcon_p->auto_reconnect_pending = false; + + mutex_create(&ipcon_p->sequence_number_mutex); + ipcon_p->next_sequence_number = 0; + + table_create(&ipcon_p->devices); + + for (i = 0; i < IPCON_NUM_CALLBACK_IDS; ++i) { + ipcon_p->registered_callbacks[i] = NULL; + ipcon_p->registered_callback_user_data[i] = NULL; + } + + mutex_create(&ipcon_p->socket_mutex); + ipcon_p->socket = NULL; + ipcon_p->socket_id = 0; + + ipcon_p->receive_flag = false; + + ipcon_p->callback = NULL; + + ipcon_p->disconnect_probe_flag = false; + event_create(&ipcon_p->disconnect_probe_event); + + semaphore_create(&ipcon_p->wait); +} + +void ipcon_destroy(IPConnection *ipcon) { + IPConnectionPrivate *ipcon_p = ipcon->p; + + ipcon_disconnect(ipcon); // FIXME: disable disconnected callback before? + + mutex_destroy(&ipcon_p->sequence_number_mutex); + + table_destroy(&ipcon_p->devices); + + mutex_destroy(&ipcon_p->socket_mutex); + + event_destroy(&ipcon_p->disconnect_probe_event); + + semaphore_destroy(&ipcon_p->wait); + + free(ipcon_p->host); + + free(ipcon_p); +} + +int ipcon_connect(IPConnection *ipcon, const char *host, uint16_t port) { + IPConnectionPrivate *ipcon_p = ipcon->p; + int ret; +#ifdef _WIN32 + WSADATA wsa_data; +#endif + + mutex_lock(&ipcon_p->socket_mutex); + +#ifdef _WIN32 + if (!ipcon_p->wsa_startup_done) { + if (WSAStartup(MAKEWORD(2, 2), &wsa_data) != 0) { + mutex_unlock(&ipcon_p->socket_mutex); + + return E_NO_STREAM_SOCKET; + } + + ipcon_p->wsa_startup_done = true; + } +#endif + + if (ipcon_p->socket != NULL) { + mutex_unlock(&ipcon_p->socket_mutex); + + return E_ALREADY_CONNECTED; + } + + free(ipcon_p->host); + + ipcon_p->host = strdup(host); + ipcon_p->port = port; + + ret = ipcon_connect_unlocked(ipcon_p, false); + + mutex_unlock(&ipcon_p->socket_mutex); + + return ret; +} + +int ipcon_disconnect(IPConnection *ipcon) { + IPConnectionPrivate *ipcon_p = ipcon->p; + CallbackContext *callback; + Meta meta; + + mutex_lock(&ipcon_p->socket_mutex); + + ipcon_p->auto_reconnect_allowed = false; + + if (ipcon_p->auto_reconnect_pending) { + // abort pending auto-reconnect + ipcon_p->auto_reconnect_pending = false; + } else { + if (ipcon_p->socket == NULL) { + mutex_unlock(&ipcon_p->socket_mutex); + + return E_NOT_CONNECTED; + } + + ipcon_disconnect_unlocked(ipcon_p); + } + + // destroy callback thread + callback = ipcon_p->callback; + ipcon_p->callback = NULL; + + mutex_unlock(&ipcon_p->socket_mutex); + + // do this outside of socket_mutex to allow calling (dis-)connect from + // the callbacks while blocking on the join call here + meta.function_id = IPCON_CALLBACK_DISCONNECTED; + meta.parameter = IPCON_DISCONNECT_REASON_REQUEST; + meta.socket_id = 0; + + queue_put(&callback->queue, QUEUE_KIND_META, &meta, sizeof(meta)); + queue_put(&callback->queue, QUEUE_KIND_EXIT, NULL, 0); + + if (!thread_is_current(&callback->thread)) { + thread_join(&callback->thread); + } + + // NOTE: no further cleanup of the callback queue and thread here, the + // callback thread is doing this on exit + + return E_OK; +} + +int ipcon_get_connection_state(IPConnection *ipcon) { + IPConnectionPrivate *ipcon_p = ipcon->p; + + if (ipcon_p->socket != NULL) { + return IPCON_CONNECTION_STATE_CONNECTED; + } else if (ipcon_p->auto_reconnect_pending) { + return IPCON_CONNECTION_STATE_PENDING; + } else { + return IPCON_CONNECTION_STATE_DISCONNECTED; + } +} + +void ipcon_set_auto_reconnect(IPConnection *ipcon, bool auto_reconnect) { + IPConnectionPrivate *ipcon_p = ipcon->p; + + ipcon_p->auto_reconnect = auto_reconnect; + + if (!ipcon_p->auto_reconnect) { + // abort potentially pending auto reconnect + ipcon_p->auto_reconnect_allowed = false; + } +} + +bool ipcon_get_auto_reconnect(IPConnection *ipcon) { + return ipcon->p->auto_reconnect; +} + +void ipcon_set_timeout(IPConnection *ipcon, uint32_t timeout) { // in msec + ipcon->p->timeout = timeout; +} + +uint32_t ipcon_get_timeout(IPConnection *ipcon) { // in msec + return ipcon->p->timeout; +} + +int ipcon_enumerate(IPConnection *ipcon) { + IPConnectionPrivate *ipcon_p = ipcon->p; + Enumerate enumerate; + int ret; + + ret = packet_header_create(&enumerate.header, sizeof(Enumerate), + IPCON_FUNCTION_ENUMERATE, ipcon_p, NULL); + + if (ret < 0) { + return ret; + } + + return ipcon_send_request(ipcon_p, (Packet *)&enumerate); +} + +void ipcon_wait(IPConnection *ipcon) { + semaphore_acquire(&ipcon->p->wait); +} + +void ipcon_unwait(IPConnection *ipcon) { + semaphore_release(&ipcon->p->wait); +} + +void ipcon_register_callback(IPConnection *ipcon, uint8_t id, void *callback, + void *user_data) { + IPConnectionPrivate *ipcon_p = ipcon->p; + + ipcon_p->registered_callbacks[id] = callback; + ipcon_p->registered_callback_user_data[id] = user_data; +} + +int packet_header_create(PacketHeader *header, uint8_t length, + uint8_t function_id, IPConnectionPrivate *ipcon_p, + DevicePrivate *device_p) { + uint8_t sequence_number; + bool response_expected = false; + int ret = E_OK; + + mutex_lock(&ipcon_p->sequence_number_mutex); + + sequence_number = ipcon_p->next_sequence_number + 1; + ipcon_p->next_sequence_number = sequence_number % 15; + + mutex_unlock(&ipcon_p->sequence_number_mutex); + + memset(header, 0, sizeof(PacketHeader)); + + if (device_p != NULL) { + header->uid = leconvert_uint32_to(device_p->uid); + } + + header->length = length; + header->function_id = function_id; + packet_header_set_sequence_number(header, sequence_number); + + if (device_p != NULL) { + ret = device_get_response_expected(device_p, function_id, &response_expected); + packet_header_set_response_expected(header, response_expected ? 1 : 0); + } + + return ret; +} + +uint8_t packet_header_get_sequence_number(PacketHeader *header) { + return (header->sequence_number_and_options >> 4) & 0x0F; +} + +void packet_header_set_sequence_number(PacketHeader *header, + uint8_t sequence_number) { + header->sequence_number_and_options |= (sequence_number << 4) & 0xF0; +} + +uint8_t packet_header_get_response_expected(PacketHeader *header) { + return (header->sequence_number_and_options >> 3) & 0x01; +} + +void packet_header_set_response_expected(PacketHeader *header, + uint8_t response_expected) { + header->sequence_number_and_options |= (response_expected << 3) & 0x08; +} + +uint8_t packet_header_get_error_code(PacketHeader *header) { + return (header->error_code_and_future_use >> 6) & 0x03; +} + +// undefine potential defines from /usr/include/endian.h +#undef LITTLE_ENDIAN +#undef BIG_ENDIAN + +#define LITTLE_ENDIAN 0x03020100ul +#define BIG_ENDIAN 0x00010203ul + +static const union { + uint8_t bytes[4]; + uint32_t value; +} native_endian = { + { 0, 1, 2, 3 } +}; + +static void *leconvert_swap16(void *data) { + uint8_t *s = (uint8_t *)data; + uint8_t d[2]; + + d[0] = s[1]; + d[1] = s[0]; + + s[0] = d[0]; + s[1] = d[1]; + + return data; +} + +static void *leconvert_swap32(void *data) { + uint8_t *s = (uint8_t *)data; + uint8_t d[4]; + + d[0] = s[3]; + d[1] = s[2]; + d[2] = s[1]; + d[3] = s[0]; + + s[0] = d[0]; + s[1] = d[1]; + s[2] = d[2]; + s[3] = d[3]; + + return data; +} + +static void *leconvert_swap64(void *data) { + uint8_t *s = (uint8_t *)data; + uint8_t d[8]; + + d[0] = s[7]; + d[1] = s[6]; + d[2] = s[5]; + d[3] = s[4]; + d[4] = s[3]; + d[5] = s[2]; + d[6] = s[1]; + d[7] = s[0]; + + s[0] = d[0]; + s[1] = d[1]; + s[2] = d[2]; + s[3] = d[3]; + s[4] = d[4]; + s[5] = d[5]; + s[6] = d[6]; + s[7] = d[7]; + + return data; +} + +int16_t leconvert_int16_to(int16_t native) { + if (native_endian.value == LITTLE_ENDIAN) { + return native; + } else { + return *(int16_t *)leconvert_swap16(&native); + } +} + +uint16_t leconvert_uint16_to(uint16_t native) { + if (native_endian.value == LITTLE_ENDIAN) { + return native; + } else { + return *(uint16_t *)leconvert_swap16(&native); + } +} + +int32_t leconvert_int32_to(int32_t native) { + if (native_endian.value == LITTLE_ENDIAN) { + return native; + } else { + return *(int32_t *)leconvert_swap32(&native); + } +} + +uint32_t leconvert_uint32_to(uint32_t native) { + if (native_endian.value == LITTLE_ENDIAN) { + return native; + } else { + return *(uint32_t *)leconvert_swap32(&native); + } +} + +int64_t leconvert_int64_to(int64_t native) { + if (native_endian.value == LITTLE_ENDIAN) { + return native; + } else { + return *(int64_t *)leconvert_swap64(&native); + } +} + +uint64_t leconvert_uint64_to(uint64_t native) { + if (native_endian.value == LITTLE_ENDIAN) { + return native; + } else { + return *(uint64_t *)leconvert_swap64(&native); + } +} + +float leconvert_float_to(float native) { + if (native_endian.value == LITTLE_ENDIAN) { + return native; + } else { + return *(float *)leconvert_swap32(&native); + } +} + +int16_t leconvert_int16_from(int16_t little) { + if (native_endian.value == LITTLE_ENDIAN) { + return little; + } else { + return *(int16_t *)leconvert_swap16(&little); + } +} + +uint16_t leconvert_uint16_from(uint16_t little) { + if (native_endian.value == LITTLE_ENDIAN) { + return little; + } else { + return *(uint16_t *)leconvert_swap16(&little); + } +} + +int32_t leconvert_int32_from(int32_t little) { + if (native_endian.value == LITTLE_ENDIAN) { + return little; + } else { + return *(int32_t *)leconvert_swap32(&little); + } +} + +uint32_t leconvert_uint32_from(uint32_t little) { + if (native_endian.value == LITTLE_ENDIAN) { + return little; + } else { + return *(uint32_t *)leconvert_swap32(&little); + } +} + +int64_t leconvert_int64_from(int64_t little) { + if (native_endian.value == LITTLE_ENDIAN) { + return little; + } else { + return *(int64_t *)leconvert_swap64(&little); + } +} + +uint64_t leconvert_uint64_from(uint64_t little) { + if (native_endian.value == LITTLE_ENDIAN) { + return little; + } else { + return *(uint64_t *)leconvert_swap64(&little); + } +} + +float leconvert_float_from(float little) { + if (native_endian.value == LITTLE_ENDIAN) { + return little; + } else { + return *(float *)leconvert_swap32(&little); + } +} diff --git a/dependencies/include/tinkerforge/bricklet_led_strip.h b/dependencies/include/tinkerforge/bricklet_led_strip.h new file mode 100644 index 00000000..11d608c7 --- /dev/null +++ b/dependencies/include/tinkerforge/bricklet_led_strip.h @@ -0,0 +1,301 @@ +/* *********************************************************** + * This file was automatically generated on 2013-12-19. * + * * + * Bindings Version 2.0.13 * + * * + * If you have a bugfix for this file and want to commit it, * + * please fix the bug in the generator. You can find a link * + * to the generator git on tinkerforge.com * + *************************************************************/ + +#ifndef BRICKLET_LED_STRIP_H +#define BRICKLET_LED_STRIP_H + +#include "ip_connection.h" + +/** + * \defgroup BrickletLEDStrip LEDStrip Bricklet + */ + +/** + * \ingroup BrickletLEDStrip + * + * Device to control up to 320 RGB LEDs + */ +typedef Device LEDStrip; + +/** + * \ingroup BrickletLEDStrip + */ +#define LED_STRIP_FUNCTION_SET_RGB_VALUES 1 + +/** + * \ingroup BrickletLEDStrip + */ +#define LED_STRIP_FUNCTION_GET_RGB_VALUES 2 + +/** + * \ingroup BrickletLEDStrip + */ +#define LED_STRIP_FUNCTION_SET_FRAME_DURATION 3 + +/** + * \ingroup BrickletLEDStrip + */ +#define LED_STRIP_FUNCTION_GET_FRAME_DURATION 4 + +/** + * \ingroup BrickletLEDStrip + */ +#define LED_STRIP_FUNCTION_GET_SUPPLY_VOLTAGE 5 + +/** + * \ingroup BrickletLEDStrip + */ +#define LED_STRIP_FUNCTION_SET_CLOCK_FREQUENCY 7 + +/** + * \ingroup BrickletLEDStrip + */ +#define LED_STRIP_FUNCTION_GET_CLOCK_FREQUENCY 8 + +/** + * \ingroup BrickletLEDStrip + */ +#define LED_STRIP_FUNCTION_GET_IDENTITY 255 + +/** + * \ingroup BrickletLEDStrip + * + * Signature: \code void callback(uint16_t length, void *user_data) \endcode + * + * This callback is triggered directly after a new frame is rendered. + * + * You should send the data for the next frame directly after this callback + * was triggered. + * + * For an explanation of the general approach see {@link led_strip_set_rgb_values}. + */ +#define LED_STRIP_CALLBACK_FRAME_RENDERED 6 + + +/** + * \ingroup BrickletLEDStrip + * + * This constant is used to identify a LEDStrip Bricklet. + * + * The {@link led_strip_get_identity} function and the + * {@link IPCON_CALLBACK_ENUMERATE} callback of the IP Connection have a + * \c device_identifier parameter to specify the Brick's or Bricklet's type. + */ +#define LED_STRIP_DEVICE_IDENTIFIER 231 + +/** + * \ingroup BrickletLEDStrip + * + * Creates the device object \c led_strip with the unique device ID \c uid and adds + * it to the IPConnection \c ipcon. + */ +void led_strip_create(LEDStrip *led_strip, const char *uid, IPConnection *ipcon); + +/** + * \ingroup BrickletLEDStrip + * + * Removes the device object \c led_strip from its IPConnection and destroys it. + * The device object cannot be used anymore afterwards. + */ +void led_strip_destroy(LEDStrip *led_strip); + +/** + * \ingroup BrickletLEDStrip + * + * Returns the response expected flag for the function specified by the + * \c function_id parameter. It is *true* if the function is expected to + * send a response, *false* otherwise. + * + * For getter functions this is enabled by default and cannot be disabled, + * because those functions will always send a response. For callback + * configuration functions it is enabled by default too, but can be disabled + * via the led_strip_set_response_expected function. For setter functions it is + * disabled by default and can be enabled. + * + * Enabling the response expected flag for a setter function allows to + * detect timeouts and other error conditions calls of this setter as well. + * The device will then send a response for this purpose. If this flag is + * disabled for a setter function then no response is send and errors are + * silently ignored, because they cannot be detected. + */ +int led_strip_get_response_expected(LEDStrip *led_strip, uint8_t function_id, bool *ret_response_expected); + +/** + * \ingroup BrickletLEDStrip + * + * Changes the response expected flag of the function specified by the + * \c function_id parameter. This flag can only be changed for setter + * (default value: *false*) and callback configuration functions + * (default value: *true*). For getter functions it is always enabled and + * callbacks it is always disabled. + * + * Enabling the response expected flag for a setter function allows to detect + * timeouts and other error conditions calls of this setter as well. The device + * will then send a response for this purpose. If this flag is disabled for a + * setter function then no response is send and errors are silently ignored, + * because they cannot be detected. + */ +int led_strip_set_response_expected(LEDStrip *led_strip, uint8_t function_id, bool response_expected); + +/** + * \ingroup BrickletLEDStrip + * + * Changes the response expected flag for all setter and callback configuration + * functions of this device at once. + */ +int led_strip_set_response_expected_all(LEDStrip *led_strip, bool response_expected); + +/** + * \ingroup BrickletLEDStrip + * + * Registers a callback with ID \c id to the function \c callback. The + * \c user_data will be given as a parameter of the callback. + */ +void led_strip_register_callback(LEDStrip *led_strip, uint8_t id, void *callback, void *user_data); + +/** + * \ingroup BrickletLEDStrip + * + * Returns the API version (major, minor, release) of the bindings for this + * device. + */ +int led_strip_get_api_version(LEDStrip *led_strip, uint8_t ret_api_version[3]); + +/** + * \ingroup BrickletLEDStrip + * + * Sets the *rgb* values for the LEDs with the given *length* starting + * from *index*. + * + * The maximum length is 16, the index goes from 0 to 319 and the rgb values + * have 8 bits each. + * + * Example: If you set + * + * * index to 5, + * * length to 3, + * * r to [255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + * * g to [0, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] and + * * b to [0, 0, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + * + * the LED with index 5 will be red, 6 will be green and 7 will be blue. + * + * The colors will be transfered to actual LEDs when the next + * frame duration ends, see {@link led_strip_set_frame_duration}. + * + * Generic approach: + * + * * Set the frame duration to a value that represents + * the number of frames per second you want to achieve. + * * Set all of the LED colors for one frame. + * * Wait for the {@link LED_STRIP_CALLBACK_FRAME_RENDERED} callback. + * * Set all of the LED colors for next frame. + * * Wait for the {@link LED_STRIP_CALLBACK_FRAME_RENDERED} callback. + * * and so on. + * + * This approach ensures that you can change the LED colors with + * a fixed frame rate. + * + * The actual number of controllable LEDs depends on the number of free + * Bricklet ports. See :ref:`here ` for more + * information. A call of {@link led_strip_set_rgb_values} with index + length above the + * bounds is ignored completely. + */ +int led_strip_set_rgb_values(LEDStrip *led_strip, uint16_t index, uint8_t length, uint8_t r[16], uint8_t g[16], uint8_t b[16]); + +/** + * \ingroup BrickletLEDStrip + * + * Returns the rgb with the given *length* starting from the + * given *index*. + * + * The values are the last values that were set by {@link led_strip_set_rgb_values}. + */ +int led_strip_get_rgb_values(LEDStrip *led_strip, uint16_t index, uint8_t length, uint8_t ret_r[16], uint8_t ret_g[16], uint8_t ret_b[16]); + +/** + * \ingroup BrickletLEDStrip + * + * Sets the frame duration in ms. + * + * Example: If you want to achieve 20 frames per second, you should + * set the frame duration to 50ms (50ms * 20 = 1 second). + * + * For an explanation of the general approach see {@link led_strip_set_rgb_values}. + * + * Default value: 100ms (10 frames per second). + */ +int led_strip_set_frame_duration(LEDStrip *led_strip, uint16_t duration); + +/** + * \ingroup BrickletLEDStrip + * + * Returns the frame duration as set by {@link led_strip_set_frame_duration}. + */ +int led_strip_get_frame_duration(LEDStrip *led_strip, uint16_t *ret_duration); + +/** + * \ingroup BrickletLEDStrip + * + * Returns the current supply voltage of the LEDs. The voltage is given in mV. + */ +int led_strip_get_supply_voltage(LEDStrip *led_strip, uint16_t *ret_voltage); + +/** + * \ingroup BrickletLEDStrip + * + * Sets the frequency of the clock in Hz. The range is 10000Hz (10kHz) up to + * 2000000Hz (2MHz). + * + * The Bricklet will choose the nearest achievable frequency, which may + * be off by a few Hz. You can get the exact frequency that is used by + * calling {@link led_strip_get_clock_frequency}. + * + * If you have problems with flickering LEDs, they may be bits flipping. You + * can fix this by either making the connection between the LEDs and the + * Bricklet shorter or by reducing the frequency. + * + * With a decreasing frequency your maximum frames per second will decrease + * too. + * + * The default value is 1.66MHz. + * + * \note + * The frequency in firmware version 2.0.0 is fixed at 2MHz. + * + * .. versionadded:: 2.0.1~(Plugin) + */ +int led_strip_set_clock_frequency(LEDStrip *led_strip, uint32_t frequency); + +/** + * \ingroup BrickletLEDStrip + * + * Returns the currently used clock frequency. + * + * .. versionadded:: 2.0.1~(Plugin) + */ +int led_strip_get_clock_frequency(LEDStrip *led_strip, uint32_t *ret_frequency); + +/** + * \ingroup BrickletLEDStrip + * + * Returns the UID, the UID where the Bricklet is connected to, + * the position, the hardware and firmware version as well as the + * device identifier. + * + * The position can be 'a', 'b', 'c' or 'd'. + * + * The device identifiers can be found :ref:`here `. + * + * .. versionadded:: 2.0.0~(Plugin) + */ +int led_strip_get_identity(LEDStrip *led_strip, char ret_uid[8], char ret_connected_uid[8], char *ret_position, uint8_t ret_hardware_version[3], uint8_t ret_firmware_version[3], uint16_t *ret_device_identifier); + +#endif diff --git a/dependencies/include/tinkerforge/ip_connection.h b/dependencies/include/tinkerforge/ip_connection.h new file mode 100644 index 00000000..5369bf76 --- /dev/null +++ b/dependencies/include/tinkerforge/ip_connection.h @@ -0,0 +1,630 @@ +/* + * Copyright (C) 2012-2013 Matthias Bolte + * Copyright (C) 2011 Olaf Lüke + * + * Redistribution and use in source and binary forms of this file, + * with or without modification, are permitted. + */ + +#ifndef IP_CONNECTION_H +#define IP_CONNECTION_H + +/** + * \defgroup IPConnection IP Connection + */ + +#ifndef __STDC_LIMIT_MACROS + #define __STDC_LIMIT_MACROS +#endif +#include +#include +#include + +#if !defined __cplusplus && defined __GNUC__ + #include +#endif + +#ifdef _WIN32 + #ifndef WIN32_LEAN_AND_MEAN + #define WIN32_LEAN_AND_MEAN + #endif + #include +#else + #include + #include +#endif + +enum { + E_OK = 0, + E_TIMEOUT = -1, + E_NO_STREAM_SOCKET = -2, + E_HOSTNAME_INVALID = -3, + E_NO_CONNECT = -4, + E_NO_THREAD = -5, + E_NOT_ADDED = -6, // unused since v2.0 + E_ALREADY_CONNECTED = -7, + E_NOT_CONNECTED = -8, + E_INVALID_PARAMETER = -9, // error response from device + E_NOT_SUPPORTED = -10, // error response from device + E_UNKNOWN_ERROR_CODE = -11 // error response from device +}; + +#ifdef IPCON_EXPOSE_INTERNALS + +typedef struct _Socket Socket; + +typedef struct { +#ifdef _WIN32 + CRITICAL_SECTION handle; +#else + pthread_mutex_t handle; +#endif +} Mutex; + +void mutex_create(Mutex *mutex); + +void mutex_destroy(Mutex *mutex); + +void mutex_lock(Mutex *mutex); + +void mutex_unlock(Mutex *mutex); + +typedef struct { +#ifdef _WIN32 + HANDLE handle; +#else + pthread_cond_t condition; + pthread_mutex_t mutex; + bool flag; +#endif +} Event; + +typedef struct { +#ifdef _WIN32 + HANDLE handle; +#else + sem_t object; + sem_t *pointer; +#endif +} Semaphore; + +typedef void (*ThreadFunction)(void *opaque); + +typedef struct { +#ifdef _WIN32 + HANDLE handle; + DWORD id; +#else + pthread_t handle; +#endif + ThreadFunction function; + void *opaque; +} Thread; + +typedef struct { + Mutex mutex; + int used; + int allocated; + uint32_t *keys; + void **values; +} Table; + +typedef struct _QueueItem { + struct _QueueItem *next; + int kind; + void *data; + int length; +} QueueItem; + +typedef struct { + Mutex mutex; + Semaphore semaphore; + QueueItem *head; + QueueItem *tail; +} Queue; + +#if defined _MSC_VER || defined __BORLANDC__ + #pragma pack(push) + #pragma pack(1) + #define ATTRIBUTE_PACKED +#elif defined __GNUC__ + #ifdef _WIN32 + // workaround struct packing bug in GCC 4.7 on Windows + // http://gcc.gnu.org/bugzilla/show_bug.cgi?id=52991 + #define ATTRIBUTE_PACKED __attribute__((gcc_struct, packed)) + #else + #define ATTRIBUTE_PACKED __attribute__((packed)) + #endif +#else + #error unknown compiler, do not know how to enable struct packing +#endif + +typedef struct { + uint32_t uid; + uint8_t length; + uint8_t function_id; + uint8_t sequence_number_and_options; + uint8_t error_code_and_future_use; +} ATTRIBUTE_PACKED PacketHeader; + +typedef struct { + PacketHeader header; + uint8_t payload[64]; + uint8_t optional_data[8]; +} ATTRIBUTE_PACKED Packet; + +#if defined _MSC_VER || defined __BORLANDC__ + #pragma pack(pop) +#endif +#undef ATTRIBUTE_PACKED + +#endif // IPCON_EXPOSE_INTERNALS + +typedef struct _IPConnection IPConnection; +typedef struct _IPConnectionPrivate IPConnectionPrivate; +typedef struct _Device Device; +typedef struct _DevicePrivate DevicePrivate; + +#ifdef IPCON_EXPOSE_INTERNALS + +typedef struct _CallbackContext CallbackContext; + +#endif + +typedef void (*EnumerateCallbackFunction)(const char *uid, + const char *connected_uid, + char position, + uint8_t hardware_version[3], + uint8_t firmware_version[3], + uint16_t device_identifier, + uint8_t enumeration_type, + void *user_data); +typedef void (*ConnectedCallbackFunction)(uint8_t connect_reason, + void *user_data); +typedef void (*DisconnectedCallbackFunction)(uint8_t disconnect_reason, + void *user_data); + +#ifdef IPCON_EXPOSE_INTERNALS + +typedef void (*CallbackWrapperFunction)(DevicePrivate *device_p, Packet *packet); + +#endif + +/** + * \internal + */ +struct _Device { + DevicePrivate *p; +}; + +#ifdef IPCON_EXPOSE_INTERNALS + +#define DEVICE_NUM_FUNCTION_IDS 256 + +/** + * \internal + */ +struct _DevicePrivate { + uint32_t uid; + + IPConnectionPrivate *ipcon_p; + + uint8_t api_version[3]; + + Mutex request_mutex; + + uint8_t expected_response_function_id; // protected by request_mutex + uint8_t expected_response_sequence_number; // protected by request_mutex + Mutex response_mutex; + Packet response_packet; // protected by response_mutex + Event response_event; + int response_expected[DEVICE_NUM_FUNCTION_IDS]; + + void *registered_callbacks[DEVICE_NUM_FUNCTION_IDS]; + void *registered_callback_user_data[DEVICE_NUM_FUNCTION_IDS]; + CallbackWrapperFunction callback_wrappers[DEVICE_NUM_FUNCTION_IDS]; +}; + +/** + * \internal + */ +enum { + DEVICE_RESPONSE_EXPECTED_INVALID_FUNCTION_ID = 0, + DEVICE_RESPONSE_EXPECTED_ALWAYS_TRUE, // getter + DEVICE_RESPONSE_EXPECTED_ALWAYS_FALSE, // callback + DEVICE_RESPONSE_EXPECTED_TRUE, // setter + DEVICE_RESPONSE_EXPECTED_FALSE // setter, default +}; + +/** + * \internal + */ +void device_create(Device *device, const char *uid, + IPConnectionPrivate *ipcon_p, uint8_t api_version_major, + uint8_t api_version_minor, uint8_t api_version_release); + +/** + * \internal + */ +void device_destroy(Device *device); + +/** + * \internal + */ +int device_get_response_expected(DevicePrivate *device_p, uint8_t function_id, + bool *ret_response_expected); + +/** + * \internal + */ +int device_set_response_expected(DevicePrivate *device_p, uint8_t function_id, + bool response_expected); + +/** + * \internal + */ +int device_set_response_expected_all(DevicePrivate *device_p, bool response_expected); + +/** + * \internal + */ +void device_register_callback(DevicePrivate *device_p, uint8_t id, void *callback, + void *user_data); + +/** + * \internal + */ +int device_get_api_version(DevicePrivate *device_p, uint8_t ret_api_version[3]); + +/** + * \internal + */ +int device_send_request(DevicePrivate *device_p, Packet *request, Packet *response); + +#endif // IPCON_EXPOSE_INTERNALS + +/** + * \ingroup IPConnection + * + * Possible IDs for ipcon_register_callback. + */ +enum { + IPCON_CALLBACK_ENUMERATE = 253, + IPCON_CALLBACK_CONNECTED = 0, + IPCON_CALLBACK_DISCONNECTED = 1 +}; + +/** + * \ingroup IPConnection + * + * Possible values for enumeration_type parameter of EnumerateCallback. + */ +enum { + IPCON_ENUMERATION_TYPE_AVAILABLE = 0, + IPCON_ENUMERATION_TYPE_CONNECTED = 1, + IPCON_ENUMERATION_TYPE_DISCONNECTED = 2 +}; + +/** + * \ingroup IPConnection + * + * Possible values for connect_reason parameter of ConnectedCallback. + */ +enum { + IPCON_CONNECT_REASON_REQUEST = 0, + IPCON_CONNECT_REASON_AUTO_RECONNECT = 1 +}; + +/** + * \ingroup IPConnection + * + * Possible values for disconnect_reason parameter of DisconnectedCallback. + */ +enum { + IPCON_DISCONNECT_REASON_REQUEST = 0, + IPCON_DISCONNECT_REASON_ERROR = 1, + IPCON_DISCONNECT_REASON_SHUTDOWN = 2 +}; + +/** + * \ingroup IPConnection + * + * Possible return values of ipcon_get_connection_state. + */ +enum { + IPCON_CONNECTION_STATE_DISCONNECTED = 0, + IPCON_CONNECTION_STATE_CONNECTED = 1, + IPCON_CONNECTION_STATE_PENDING = 2 // auto-reconnect in progress +}; + +/** + * \internal + */ +struct _IPConnection { + IPConnectionPrivate *p; +}; + +#ifdef IPCON_EXPOSE_INTERNALS + +#define IPCON_NUM_CALLBACK_IDS 256 + +/** + * \internal + */ +struct _IPConnectionPrivate { +#ifdef _WIN32 + bool wsa_startup_done; // protected by socket_mutex +#endif + + char *host; + uint16_t port; + + uint32_t timeout; // in msec + + bool auto_reconnect; + bool auto_reconnect_allowed; + bool auto_reconnect_pending; + + Mutex sequence_number_mutex; + uint8_t next_sequence_number; // protected by sequence_number_mutex + + Table devices; + + void *registered_callbacks[IPCON_NUM_CALLBACK_IDS]; + void *registered_callback_user_data[IPCON_NUM_CALLBACK_IDS]; + + Mutex socket_mutex; + Socket *socket; // protected by socket_mutex + uint64_t socket_id; // protected by socket_mutex + + bool receive_flag; + Thread receive_thread; // protected by socket_mutex + + CallbackContext *callback; + + bool disconnect_probe_flag; + Thread disconnect_probe_thread; // protected by socket_mutex + Event disconnect_probe_event; + + Semaphore wait; +}; + +#endif // IPCON_EXPOSE_INTERNALS + +/** + * \ingroup IPConnection + * + * Creates an IP Connection object that can be used to enumerate the available + * devices. It is also required for the constructor of Bricks and Bricklets. + */ +void ipcon_create(IPConnection *ipcon); + +/** + * \ingroup IPConnection + * + * Destroys the IP Connection object. Calls ipcon_disconnect internally. + * The connection to the Brick Daemon gets closed and the threads of the + * IP Connection are terminated. + */ +void ipcon_destroy(IPConnection *ipcon); + +/** + * \ingroup IPConnection + * + * Creates a TCP/IP connection to the given \c host and c\ port. The host and + * port can point to a Brick Daemon or to a WIFI/Ethernet Extension. + * + * Devices can only be controlled when the connection was established + * successfully. + * + * Blocks until the connection is established and returns an error code if + * there is no Brick Daemon or WIFI/Ethernet Extension listening at the given + * host and port. + */ +int ipcon_connect(IPConnection *ipcon, const char *host, uint16_t port); + +/** + * \ingroup IPConnection + * + * Disconnects the TCP/IP connection from the Brick Daemon or the WIFI/Ethernet + * Extension. + */ +int ipcon_disconnect(IPConnection *ipcon); + +/** + * \ingroup IPConnection + * + * Can return the following states: + * + * - IPCON_CONNECTION_STATE_DISCONNECTED: No connection is established. + * - IPCON_CONNECTION_STATE_CONNECTED: A connection to the Brick Daemon or + * the WIFI/Ethernet Extension is established. + * - IPCON_CONNECTION_STATE_PENDING: IP Connection is currently trying to + * connect. + */ +int ipcon_get_connection_state(IPConnection *ipcon); + +/** + * \ingroup IPConnection + * + * Enables or disables auto-reconnect. If auto-reconnect is enabled, + * the IP Connection will try to reconnect to the previously given + * host and port, if the connection is lost. + * + * Default value is *true*. + */ +void ipcon_set_auto_reconnect(IPConnection *ipcon, bool auto_reconnect); + +/** + * \ingroup IPConnection + * + * Returns *true* if auto-reconnect is enabled, *false* otherwise. + */ +bool ipcon_get_auto_reconnect(IPConnection *ipcon); + +/** + * \ingroup IPConnection + * + * Sets the timeout in milliseconds for getters and for setters for which the + * response expected flag is activated. + * + * Default timeout is 2500. + */ +void ipcon_set_timeout(IPConnection *ipcon, uint32_t timeout); + +/** + * \ingroup IPConnection + * + * Returns the timeout as set by ipcon_set_timeout. + */ +uint32_t ipcon_get_timeout(IPConnection *ipcon); + +/** + * \ingroup IPConnection + * + * Broadcasts an enumerate request. All devices will respond with an enumerate + * callback. + */ +int ipcon_enumerate(IPConnection *ipcon); + +/** + * \ingroup IPConnection + * + * Stops the current thread until ipcon_unwait is called. + * + * This is useful if you rely solely on callbacks for events, if you want + * to wait for a specific callback or if the IP Connection was created in + * a thread. + * + * ipcon_wait and ipcon_unwait act in the same way as "acquire" and "release" + * of a semaphore. + */ +void ipcon_wait(IPConnection *ipcon); + +/** + * \ingroup IPConnection + * + * Unwaits the thread previously stopped by ipcon_wait. + * + * ipcon_wait and ipcon_unwait act in the same way as "acquire" and "release" + * of a semaphore. + */ +void ipcon_unwait(IPConnection *ipcon); + +/** + * \ingroup IPConnection + * + * Registers a callback for a given ID. + */ +void ipcon_register_callback(IPConnection *ipcon, uint8_t id, + void *callback, void *user_data); + +#ifdef IPCON_EXPOSE_INTERNALS + +/** + * \internal + */ +int packet_header_create(PacketHeader *header, uint8_t length, + uint8_t function_id, IPConnectionPrivate *ipcon_p, + DevicePrivate *device_p); + +/** + * \internal + */ +uint8_t packet_header_get_sequence_number(PacketHeader *header); + +/** + * \internal + */ +void packet_header_set_sequence_number(PacketHeader *header, + uint8_t sequence_number); + +/** + * \internal + */ +uint8_t packet_header_get_response_expected(PacketHeader *header); + +/** + * \internal + */ +void packet_header_set_response_expected(PacketHeader *header, + uint8_t response_expected); + +/** + * \internal + */ +uint8_t packet_header_get_error_code(PacketHeader *header); + +/** + * \internal + */ +int16_t leconvert_int16_to(int16_t native); + +/** + * \internal + */ +uint16_t leconvert_uint16_to(uint16_t native); + +/** + * \internal + */ +int32_t leconvert_int32_to(int32_t native); + +/** + * \internal + */ +uint32_t leconvert_uint32_to(uint32_t native); + +/** + * \internal + */ +int64_t leconvert_int64_to(int64_t native); + +/** + * \internal + */ +uint64_t leconvert_uint64_to(uint64_t native); + +/** + * \internal + */ +float leconvert_float_to(float native); + +/** + * \internal + */ +int16_t leconvert_int16_from(int16_t little); + +/** + * \internal + */ +uint16_t leconvert_uint16_from(uint16_t little); + +/** + * \internal + */ +int32_t leconvert_int32_from(int32_t little); + +/** + * \internal + */ +uint32_t leconvert_uint32_from(uint32_t little); + +/** + * \internal + */ +int64_t leconvert_int64_from(int64_t little); + +/** + * \internal + */ +uint64_t leconvert_uint64_from(uint64_t little); + +/** + * \internal + */ +float leconvert_float_from(float little); + +#endif // IPCON_EXPOSE_INTERNALS + +#endif diff --git a/dependencies/tinkerforge_c_bindings_2_0_13.zip b/dependencies/tinkerforge_c_bindings_2_0_13.zip new file mode 100644 index 00000000..198d9688 Binary files /dev/null and b/dependencies/tinkerforge_c_bindings_2_0_13.zip differ diff --git a/deploy/hyperion.tar.gz.REMOVED.git-id b/deploy/hyperion.tar.gz.REMOVED.git-id index 362f60df..a3a391be 100644 --- a/deploy/hyperion.tar.gz.REMOVED.git-id +++ b/deploy/hyperion.tar.gz.REMOVED.git-id @@ -1 +1 @@ -08d42deff1de4c4296e4c6e22c783a0096ed3396 \ No newline at end of file +d61b685eca164580cb39eb5bc3cf65b89afad410 \ No newline at end of file diff --git a/effects/snake.json b/effects/snake.json new file mode 100644 index 00000000..2c7ba395 --- /dev/null +++ b/effects/snake.json @@ -0,0 +1,10 @@ +{ + "name" : "Snake", + "script" : "snake.py", + "args" : + { + "rotation-time" : 10.0, + "color" : [255, 0, 0], + "percentage" : 25 + } +} diff --git a/effects/snake.py b/effects/snake.py new file mode 100644 index 00000000..2ae5cb37 --- /dev/null +++ b/effects/snake.py @@ -0,0 +1,41 @@ +import hyperion +import time +import colorsys + +# Get the parameters +rotationTime = float(hyperion.args.get('rotation-time', 10.0)) +color = hyperion.args.get('color', (255,0,0)) +percentage = int(hyperion.args.get('percentage', 10)) + +# Check parameters +rotationTime = max(0.1, rotationTime) +percentage = max(1, min(percentage, 100)) + +# Process parameters +factor = percentage/100.0 +hsv = colorsys.rgb_to_hsv(color[0]/255.0, color[1]/255.0, color[2]/255.0) + +# Initialize the led data +snakeLeds = max(1, int(hyperion.ledCount*factor)) +ledData = bytearray() + +for i in range(hyperion.ledCount-snakeLeds): + ledData += bytearray((0, 0, 0)) + +for i in range(1,snakeLeds+1): + rgb = colorsys.hsv_to_rgb(hsv[0], hsv[1], hsv[2]/i) + ledData += bytearray((int(rgb[0]*255), int(rgb[1]*255), int(rgb[2]*255))) + +# Calculate the sleep time and rotation increment +increment = 3 +sleepTime = rotationTime / hyperion.ledCount +while sleepTime < 0.05: + increment *= 2 + sleepTime *= 2 +increment %= hyperion.ledCount + +# Start the write data loop +while not hyperion.abort(): + hyperion.setColor(ledData) + ledData = ledData[increment:] + ledData[:increment] + time.sleep(sleepTime) diff --git a/include/blackborder/BlackBorderDetector.h b/include/blackborder/BlackBorderDetector.h index ce0bf25d..c5ee91e7 100644 --- a/include/blackborder/BlackBorderDetector.h +++ b/include/blackborder/BlackBorderDetector.h @@ -1,4 +1,3 @@ - #pragma once // Utils includes @@ -126,7 +125,7 @@ namespace hyperion inline bool isBlack(const Pixel_T & color) { // Return the simple compare of the color against black - return color.red < _blackborderThreshold && color.green < _blackborderThreshold && color.green < _blackborderThreshold; + return color.red < _blackborderThreshold && color.green < _blackborderThreshold && color.blue < _blackborderThreshold; } private: diff --git a/include/effectengine/EffectEngine.h b/include/effectengine/EffectEngine.h index bea45576..c40732a1 100644 --- a/include/effectengine/EffectEngine.h +++ b/include/effectengine/EffectEngine.h @@ -55,5 +55,5 @@ private: std::list _activeEffects; - PyThreadState * _mainThreadState; + PyThreadState * _mainThreadState; }; diff --git a/include/dispmanx-grabber/DispmanxWrapper.h b/include/grabber/DispmanxWrapper.h similarity index 98% rename from include/dispmanx-grabber/DispmanxWrapper.h rename to include/grabber/DispmanxWrapper.h index f0156740..4868c1a0 100644 --- a/include/dispmanx-grabber/DispmanxWrapper.h +++ b/include/grabber/DispmanxWrapper.h @@ -73,7 +73,7 @@ private: const int _updateInterval_ms; /// The timeout of the led colors [ms] const int _timeout_ms; - /// The priority of the led colors [ms] + /// The priority of the led colors const int _priority; /// The timer for generating events with the specified update rate diff --git a/include/grabber/PixelFormat.h b/include/grabber/PixelFormat.h new file mode 100644 index 00000000..6afc7510 --- /dev/null +++ b/include/grabber/PixelFormat.h @@ -0,0 +1,36 @@ +#pragma once + +#include +#include + +/** + * Enumeration of the possible pixel formats the grabber can be set to + */ +enum PixelFormat { + PIXELFORMAT_YUYV, + PIXELFORMAT_UYVY, + PIXELFORMAT_RGB32, + PIXELFORMAT_NO_CHANGE +}; + +inline PixelFormat parsePixelFormat(std::string pixelFormat) +{ + // convert to lower case + std::transform(pixelFormat.begin(), pixelFormat.end(), pixelFormat.begin(), ::tolower); + + if (pixelFormat == "yuyv") + { + return PIXELFORMAT_YUYV; + } + else if (pixelFormat == "uyvy") + { + return PIXELFORMAT_UYVY; + } + else if (pixelFormat == "rgb32") + { + return PIXELFORMAT_RGB32; + } + + // return the default NO_CHANGE + return PIXELFORMAT_NO_CHANGE; +} diff --git a/src/hyperion-v4l2/V4L2Grabber.h b/include/grabber/V4L2Grabber.h similarity index 50% rename from src/hyperion-v4l2/V4L2Grabber.h rename to include/grabber/V4L2Grabber.h index ca05204f..fe867112 100644 --- a/src/hyperion-v4l2/V4L2Grabber.h +++ b/include/grabber/V4L2Grabber.h @@ -4,34 +4,60 @@ #include #include +// Qt includes +#include +#include + // util includes #include #include +#include + +// grabber includes +#include +#include /// Capture class for V4L2 devices /// /// @see http://linuxtv.org/downloads/v4l-dvb-apis/capture-example.html -class V4L2Grabber +class V4L2Grabber : public QObject { -public: - typedef void (*ImageCallback)(void * arg, const Image & image); - - enum VideoStandard { - PAL, NTSC, NO_CHANGE - }; + Q_OBJECT public: - V4L2Grabber(const std::string & device, int input, VideoStandard videoStandard, int width, int height, int cropHorizontal, int cropVertical, int frameDecimation, int pixelDecimation); + V4L2Grabber(const std::string & device, + int input, + VideoStandard videoStandard, PixelFormat pixelFormat, + int width, + int height, + int frameDecimation, + int horizontalPixelDecimation, + int verticalPixelDecimation); virtual ~V4L2Grabber(); - void setCallback(ImageCallback callback, void * arg); +public slots: + void setCropping(int cropLeft, + int cropRight, + int cropTop, + int cropBottom); + + void set3D(VideoMode mode); + + void setSignalThreshold(double redSignalThreshold, + double greenSignalThreshold, + double blueSignalThreshold, + int noSignalCounterThreshold); void start(); - void capture(int frameCount = -1); - void stop(); +signals: + void newFrame(const Image & image); + +private slots: + int read_frame(); + private: void open_device(); @@ -51,8 +77,6 @@ private: void stop_capturing(); - int read_frame(); - bool process_image(const void *p, int size); void process_image(const uint8_t *p); @@ -81,16 +105,25 @@ private: int _fileDescriptor; std::vector _buffers; - uint32_t _pixelFormat; + PixelFormat _pixelFormat; int _width; int _height; - const int _cropWidth; - const int _cropHeight; - const int _frameDecimation; - const int _pixelDecimation; + int _frameByteSize; + int _cropLeft; + int _cropRight; + int _cropTop; + int _cropBottom; + int _frameDecimation; + int _horizontalPixelDecimation; + int _verticalPixelDecimation; + int _noSignalCounterThreshold; + + ColorRgb _noSignalThresholdColor; + + VideoMode _mode3D; int _currentFrame; + int _noSignalCounter; - ImageCallback _callback; - void * _callbackArg; + QSocketNotifier * _streamNotifier; }; diff --git a/include/grabber/V4L2Wrapper.h b/include/grabber/V4L2Wrapper.h new file mode 100644 index 00000000..2a9ece2c --- /dev/null +++ b/include/grabber/V4L2Wrapper.h @@ -0,0 +1,74 @@ +#pragma once + +// Qt includes +#include + +// Hyperion includes +#include +#include + +// Grabber includes +#include + +class V4L2Wrapper : public QObject +{ + Q_OBJECT + +public: + V4L2Wrapper(const std::string & device, + int input, + VideoStandard videoStandard, + PixelFormat pixelFormat, + int width, + int height, + int frameDecimation, + int pixelDecimation, + double redSignalThreshold, + double greenSignalThreshold, + double blueSignalThreshold, + Hyperion * hyperion, + int hyperionPriority); + virtual ~V4L2Wrapper(); + +public slots: + void start(); + + void stop(); + + void setCropping(int cropLeft, + int cropRight, + int cropTop, + int cropBottom); + + void set3D(VideoMode mode); + +signals: + void emitColors(int priority, const std::vector &ledColors, const int timeout_ms); + +private slots: + void newFrame(const Image & image); + + void checkSources(); + +private: + /// The timeout of the led colors [ms] + const int _timeout_ms; + + /// The priority of the led colors + const int _priority; + + /// The V4L2 grabber + V4L2Grabber _grabber; + + /// The processor for transforming images to led colors + ImageProcessor * _processor; + + /// The Hyperion instance + Hyperion * _hyperion; + + /// The list with computed led colors + std::vector _ledColors; + + /// Timer which tests if a higher priority source is active + QTimer _timer; +}; diff --git a/include/grabber/VideoStandard.h b/include/grabber/VideoStandard.h new file mode 100644 index 00000000..56636af8 --- /dev/null +++ b/include/grabber/VideoStandard.h @@ -0,0 +1,31 @@ +#pragma once + +#include +#include + +/** + * Enumeration of the possible video standards the grabber can be set to + */ +enum VideoStandard { + VIDEOSTANDARD_PAL, + VIDEOSTANDARD_NTSC, + VIDEOSTANDARD_NO_CHANGE +}; + +inline VideoStandard parseVideoStandard(std::string videoStandard) +{ + // convert to lower case + std::transform(videoStandard.begin(), videoStandard.end(), videoStandard.begin(), ::tolower); + + if (videoStandard == "pal") + { + return VIDEOSTANDARD_PAL; + } + else if (videoStandard == "ntsc") + { + return VIDEOSTANDARD_NTSC; + } + + // return the default NO_CHANGE + return VIDEOSTANDARD_NO_CHANGE; +} diff --git a/include/utils/ColorRgb.h b/include/utils/ColorRgb.h index c578d69f..bbf9f615 100644 --- a/include/utils/ColorRgb.h +++ b/include/utils/ColorRgb.h @@ -48,3 +48,16 @@ inline std::ostream& operator<<(std::ostream& os, const ColorRgb& color) os << "{" << unsigned(color.red) << "," << unsigned(color.green) << "," << unsigned(color.blue) << "}"; return os; } + + +/// Compare operator to check if a color is 'smaller' than another color +inline bool operator<(const ColorRgb & lhs, const ColorRgb & rhs) +{ + return (lhs.red < rhs.red) && (lhs.green < rhs.green) && (lhs.blue < rhs.blue); +} + +/// Compare operator to check if a color is 'smaller' than or 'equal' to another color +inline bool operator<=(const ColorRgb & lhs, const ColorRgb & rhs) +{ + return (lhs.red <= rhs.red) && (lhs.green <= rhs.green) && (lhs.blue <= rhs.blue); +} diff --git a/include/utils/Image.h b/include/utils/Image.h index 48625bbb..5e8b45ad 100644 --- a/include/utils/Image.h +++ b/include/utils/Image.h @@ -13,6 +13,18 @@ public: typedef Pixel_T pixel_type; + /// + /// Default constructor for an image + /// + Image() : + _width(1), + _height(1), + _pixels(new Pixel_T[2]), + _endOfPixels(_pixels + 1) + { + memset(_pixels, 0, 2*sizeof(Pixel_T)); + } + /// /// Constructor for an image with specified width and height /// @@ -22,8 +34,8 @@ public: Image(const unsigned width, const unsigned height) : _width(width), _height(height), - _pixels(new Pixel_T[width*height + 1]), - _endOfPixels(_pixels + width*height) + _pixels(new Pixel_T[width * height + 1]), + _endOfPixels(_pixels + width * height) { memset(_pixels, 0, (_width*_height+1)*sizeof(Pixel_T)); } @@ -38,12 +50,24 @@ public: Image(const unsigned width, const unsigned height, const Pixel_T background) : _width(width), _height(height), - _pixels(new Pixel_T[width*height + 1]), - _endOfPixels(_pixels + width*height) + _pixels(new Pixel_T[width * height + 1]), + _endOfPixels(_pixels + width * height) { std::fill(_pixels, _endOfPixels, background); } + /// + /// Copy constructor for an image + /// + Image(const Image & other) : + _width(other._width), + _height(other._height), + _pixels(new Pixel_T[other._width * other._height + 1]), + _endOfPixels(_pixels + other._width * other._height) + { + memcpy(_pixels, other._pixels, other._width * other._height * sizeof(Pixel_T)); + } + /// /// Destructor /// diff --git a/include/utils/VideoMode.h b/include/utils/VideoMode.h index 16c75b37..f5cae94f 100644 --- a/include/utils/VideoMode.h +++ b/include/utils/VideoMode.h @@ -1,5 +1,8 @@ #pragma once +#include +#include + /** * Enumeration of the possible modes in which video can be playing (2D, 3D) */ @@ -9,3 +12,21 @@ enum VideoMode VIDEO_3DSBS, VIDEO_3DTAB }; + +inline VideoMode parse3DMode(std::string videoMode) +{ + // convert to lower case + std::transform(videoMode.begin(), videoMode.end(), videoMode.begin(), ::tolower); + + if (videoMode == "23DTAB") + { + return VIDEO_3DTAB; + } + else if (videoMode == "3DSBS") + { + return VIDEO_3DSBS; + } + + // return the default 2D + return VIDEO_2D; +} diff --git a/libsrc/CMakeLists.txt b/libsrc/CMakeLists.txt index 8db13505..62911dd7 100644 --- a/libsrc/CMakeLists.txt +++ b/libsrc/CMakeLists.txt @@ -12,7 +12,4 @@ add_subdirectory(leddevice) add_subdirectory(utils) add_subdirectory(xbmcvideochecker) add_subdirectory(effectengine) - -if (ENABLE_DISPMANX) - add_subdirectory(dispmanx-grabber) -endif (ENABLE_DISPMANX) +add_subdirectory(grabber) diff --git a/libsrc/boblightserver/CMakeLists.txt b/libsrc/boblightserver/CMakeLists.txt index af825e74..1bb30cdb 100644 --- a/libsrc/boblightserver/CMakeLists.txt +++ b/libsrc/boblightserver/CMakeLists.txt @@ -28,9 +28,5 @@ add_library(boblightserver target_link_libraries(boblightserver hyperion - hyperion-utils) - -qt4_use_modules(boblightserver - Core - Gui - Network) + hyperion-utils + ${QT_LIBRARIES}) diff --git a/libsrc/effectengine/Effect.cpp b/libsrc/effectengine/Effect.cpp index e09f8979..09411c87 100644 --- a/libsrc/effectengine/Effect.cpp +++ b/libsrc/effectengine/Effect.cpp @@ -19,9 +19,39 @@ PyMethodDef Effect::effectMethods[] = { {NULL, NULL, 0, NULL} }; +#if PY_MAJOR_VERSION >= 3 +// create the hyperion module +struct PyModuleDef Effect::moduleDef = { + PyModuleDef_HEAD_INIT, + "hyperion", /* m_name */ + "Hyperion module", /* m_doc */ + -1, /* m_size */ + Effect::effectMethods, /* m_methods */ + NULL, /* m_reload */ + NULL, /* m_traverse */ + NULL, /* m_clear */ + NULL, /* m_free */ +}; -Effect::Effect(int priority, int timeout, const std::string & script, const Json::Value & args) : +PyObject* Effect::PyInit_hyperion() +{ + return PyModule_Create(&moduleDef); +} +#else +void Effect::PyInit_hyperion() +{ + Py_InitModule("hyperion", effectMethods); +} +#endif + +void Effect::registerHyperionExtensionModule() +{ + PyImport_AppendInittab("hyperion", &PyInit_hyperion); +} + +Effect::Effect(PyThreadState * mainThreadState, int priority, int timeout, const std::string & script, const Json::Value & args) : QThread(), + _mainThreadState(mainThreadState), _priority(priority), _timeout(timeout), _script(script), @@ -44,20 +74,26 @@ Effect::~Effect() void Effect::run() { + // switch to the main thread state and acquire the GIL + PyEval_RestoreThread(_mainThreadState); + // Initialize a new thread state - PyEval_AcquireLock(); // Get the GIL _interpreterThreadState = Py_NewInterpreter(); - // add methods extra builtin methods to the interpreter - PyObject * thisCapsule = PyCapsule_New(this, nullptr, nullptr); - PyObject * module = Py_InitModule4("hyperion", effectMethods, nullptr, thisCapsule, PYTHON_API_VERSION); + // import the buildtin Hyperion module + PyObject * module = PyImport_ImportModule("hyperion"); + + // add a capsule containing 'this' to the module to be able to retrieve the effect from the callback function + PyObject_SetAttrString(module, "__effectObj", PyCapsule_New(this, nullptr, nullptr)); // add ledCount variable to the interpreter PyObject_SetAttrString(module, "ledCount", Py_BuildValue("i", _imageProcessor->getLedCount())); // add a args variable to the interpreter PyObject_SetAttrString(module, "args", json2python(_args)); - //PyObject_SetAttrString(module, "args", Py_BuildValue("s", _args.c_str())); + + // decref the module + Py_XDECREF(module); // Set the end time if applicable if (_timeout > 0) @@ -119,19 +155,23 @@ PyObject *Effect::json2python(const Json::Value &json) const return Py_BuildValue("s", json.asCString()); case Json::objectValue: { - PyObject * obj = PyDict_New(); + PyObject * dict= PyDict_New(); for (Json::Value::iterator i = json.begin(); i != json.end(); ++i) { - PyDict_SetItemString(obj, i.memberName(), json2python(*i)); + PyObject * obj = json2python(*i); + PyDict_SetItemString(dict, i.memberName(), obj); + Py_XDECREF(obj); } - return obj; + return dict; } case Json::arrayValue: { PyObject * list = PyList_New(json.size()); for (Json::Value::iterator i = json.begin(); i != json.end(); ++i) { - PyList_SetItem(list, i.index(), json2python(*i)); + PyObject * obj = json2python(*i); + PyList_SetItem(list, i.index(), obj); + Py_XDECREF(obj); } return list; } @@ -144,7 +184,7 @@ PyObject *Effect::json2python(const Json::Value &json) const PyObject* Effect::wrapSetColor(PyObject *self, PyObject *args) { // get the effect - Effect * effect = getEffect(self); + Effect * effect = getEffect(); // check if we have aborted already if (effect->_abortRequested) @@ -229,7 +269,7 @@ PyObject* Effect::wrapSetColor(PyObject *self, PyObject *args) PyObject* Effect::wrapSetImage(PyObject *self, PyObject *args) { // get the effect - Effect * effect = getEffect(self); + Effect * effect = getEffect(); // check if we have aborted already if (effect->_abortRequested) @@ -292,7 +332,7 @@ PyObject* Effect::wrapSetImage(PyObject *self, PyObject *args) PyObject* Effect::wrapAbort(PyObject *self, PyObject *) { - Effect * effect = getEffect(self); + Effect * effect = getEffect(); // Test if the effect has reached it end time if (effect->_timeout > 0 && QDateTime::currentMSecsSinceEpoch() > effect->_endTime) @@ -303,8 +343,33 @@ PyObject* Effect::wrapAbort(PyObject *self, PyObject *) return Py_BuildValue("i", effect->_abortRequested ? 1 : 0); } -Effect * Effect::getEffect(PyObject *self) +Effect * Effect::getEffect() { - // Get the effect from the capsule in the self pointer - return reinterpret_cast(PyCapsule_GetPointer(self, nullptr)); + // extract the module from the runtime + PyObject * module = PyObject_GetAttrString(PyImport_AddModule("__main__"), "hyperion"); + + if (!PyModule_Check(module)) + { + // something is wrong + Py_XDECREF(module); + std::cerr << "Unable to retrieve the effect object from the Python runtime" << std::endl; + return nullptr; + } + + // retrieve the capsule with the effect + PyObject * effectCapsule = PyObject_GetAttrString(module, "__effectObj"); + Py_XDECREF(module); + + if (!PyCapsule_CheckExact(effectCapsule)) + { + // something is wrong + Py_XDECREF(effectCapsule); + std::cerr << "Unable to retrieve the effect object from the Python runtime" << std::endl; + return nullptr; + } + + // Get the effect from the capsule + Effect * effect = reinterpret_cast(PyCapsule_GetPointer(effectCapsule, nullptr)); + Py_XDECREF(effectCapsule); + return effect; } diff --git a/libsrc/effectengine/Effect.h b/libsrc/effectengine/Effect.h index 62c2ed75..8540d507 100644 --- a/libsrc/effectengine/Effect.h +++ b/libsrc/effectengine/Effect.h @@ -14,7 +14,7 @@ class Effect : public QThread Q_OBJECT public: - Effect(int priority, int timeout, const std::string & script, const Json::Value & args = Json::Value()); + Effect(PyThreadState * mainThreadState, int priority, int timeout, const std::string & script, const Json::Value & args = Json::Value()); virtual ~Effect(); virtual void run(); @@ -23,6 +23,9 @@ public: bool isAbortRequested() const; + /// This function registers the extension module in Python + static void registerHyperionExtensionModule(); + public slots: void abort(); @@ -38,13 +41,22 @@ private: PyObject * json2python(const Json::Value & json) const; // Wrapper methods for Python interpreter extra buildin methods - static PyMethodDef effectMethods[]; - static PyObject* wrapSetColor(PyObject *self, PyObject *args); + static PyMethodDef effectMethods[]; + static PyObject* wrapSetColor(PyObject *self, PyObject *args); static PyObject* wrapSetImage(PyObject *self, PyObject *args); static PyObject* wrapAbort(PyObject *self, PyObject *args); - static Effect * getEffect(PyObject *self); + static Effect * getEffect(); + +#if PY_MAJOR_VERSION >= 3 + static struct PyModuleDef moduleDef; + static PyObject* PyInit_hyperion(); +#else + static void PyInit_hyperion(); +#endif private: + PyThreadState * _mainThreadState; + const int _priority; const int _timeout; diff --git a/libsrc/effectengine/EffectEngine.cpp b/libsrc/effectengine/EffectEngine.cpp index a343ae4e..f7057e21 100644 --- a/libsrc/effectengine/EffectEngine.cpp +++ b/libsrc/effectengine/EffectEngine.cpp @@ -54,6 +54,7 @@ EffectEngine::EffectEngine(Hyperion * hyperion, const Json::Value & jsonEffectCo // initialize the python interpreter std::cout << "Initializing Python interpreter" << std::endl; + Effect::registerHyperionExtensionModule(); Py_InitializeEx(0); PyEval_InitThreads(); // Create the GIL _mainThreadState = PyEval_SaveThread(); @@ -151,7 +152,7 @@ int EffectEngine::runEffectScript(const std::string &script, const Json::Value & channelCleared(priority); // create the effect - Effect * effect = new Effect(priority, timeout, script, args); + Effect * effect = new Effect(_mainThreadState, priority, timeout, script, args); connect(effect, SIGNAL(setColors(int,std::vector,int,bool)), _hyperion, SLOT(setColors(int,std::vector,int,bool)), Qt::QueuedConnection); connect(effect, SIGNAL(effectFinished(Effect*)), this, SLOT(effectFinished(Effect*))); _activeEffects.push_back(effect); diff --git a/libsrc/grabber/CMakeLists.txt b/libsrc/grabber/CMakeLists.txt new file mode 100644 index 00000000..322a5a98 --- /dev/null +++ b/libsrc/grabber/CMakeLists.txt @@ -0,0 +1,8 @@ + +if (ENABLE_DISPMANX) + add_subdirectory(dispmanx) +endif (ENABLE_DISPMANX) + +if (ENABLE_V4L2) + add_subdirectory(v4l2) +endif (ENABLE_V4L2) diff --git a/libsrc/dispmanx-grabber/CMakeLists.txt b/libsrc/grabber/dispmanx/CMakeLists.txt similarity index 81% rename from libsrc/dispmanx-grabber/CMakeLists.txt rename to libsrc/grabber/dispmanx/CMakeLists.txt index f8bf6ad8..20714dba 100644 --- a/libsrc/dispmanx-grabber/CMakeLists.txt +++ b/libsrc/grabber/dispmanx/CMakeLists.txt @@ -4,8 +4,8 @@ find_package(BCM REQUIRED) include_directories(${BCM_INCLUDE_DIRS}) # Define the current source locations -SET(CURRENT_HEADER_DIR ${CMAKE_SOURCE_DIR}/include/dispmanx-grabber) -SET(CURRENT_SOURCE_DIR ${CMAKE_SOURCE_DIR}/libsrc/dispmanx-grabber) +SET(CURRENT_HEADER_DIR ${CMAKE_SOURCE_DIR}/include/grabber) +SET(CURRENT_SOURCE_DIR ${CMAKE_SOURCE_DIR}/libsrc/grabber/dispmanx) # Group the headers that go through the MOC compiler SET(DispmanxGrabberQT_HEADERS diff --git a/libsrc/dispmanx-grabber/DispmanxFrameGrabber.cpp b/libsrc/grabber/dispmanx/DispmanxFrameGrabber.cpp similarity index 100% rename from libsrc/dispmanx-grabber/DispmanxFrameGrabber.cpp rename to libsrc/grabber/dispmanx/DispmanxFrameGrabber.cpp diff --git a/libsrc/dispmanx-grabber/DispmanxFrameGrabber.h b/libsrc/grabber/dispmanx/DispmanxFrameGrabber.h similarity index 100% rename from libsrc/dispmanx-grabber/DispmanxFrameGrabber.h rename to libsrc/grabber/dispmanx/DispmanxFrameGrabber.h diff --git a/libsrc/dispmanx-grabber/DispmanxWrapper.cpp b/libsrc/grabber/dispmanx/DispmanxWrapper.cpp similarity index 96% rename from libsrc/dispmanx-grabber/DispmanxWrapper.cpp rename to libsrc/grabber/dispmanx/DispmanxWrapper.cpp index 5ffb2a48..c9d2b22c 100644 --- a/libsrc/dispmanx-grabber/DispmanxWrapper.cpp +++ b/libsrc/grabber/dispmanx/DispmanxWrapper.cpp @@ -7,8 +7,8 @@ #include #include -// Local-dispmanx includes -#include +// Dispmanx grabber includes +#include #include "DispmanxFrameGrabber.h" diff --git a/libsrc/grabber/v4l2/CMakeLists.txt b/libsrc/grabber/v4l2/CMakeLists.txt new file mode 100644 index 00000000..540a1217 --- /dev/null +++ b/libsrc/grabber/v4l2/CMakeLists.txt @@ -0,0 +1,32 @@ +# Define the current source locations +SET(CURRENT_HEADER_DIR ${CMAKE_SOURCE_DIR}/include/grabber) +SET(CURRENT_SOURCE_DIR ${CMAKE_SOURCE_DIR}/libsrc/grabber/v4l2) + +SET(V4L2_QT_HEADERS + ${CURRENT_HEADER_DIR}/V4L2Grabber.h + ${CURRENT_HEADER_DIR}/V4L2Wrapper.h +) + +SET(V4L2_HEADERS + ${CURRENT_HEADER_DIR}/VideoStandard.h + ${CURRENT_HEADER_DIR}/PixelFormat.h +) + +SET(V4L2_SOURCES + ${CURRENT_SOURCE_DIR}/V4L2Grabber.cpp + ${CURRENT_SOURCE_DIR}/V4L2Wrapper.cpp +) + +QT4_WRAP_CPP(V4L2_HEADERS_MOC ${V4L2_QT_HEADERS}) + +add_library(v4l2-grabber + ${V4L2_HEADERS} + ${V4L2_SOURCES} + ${V4L2_QT_HEADERS} + ${V4L2_HEADERS_MOC} +) + +target_link_libraries(v4l2-grabber + hyperion + ${QT_LIBRARIES} +) diff --git a/src/hyperion-v4l2/V4L2Grabber.cpp b/libsrc/grabber/v4l2/V4L2Grabber.cpp similarity index 65% rename from src/hyperion-v4l2/V4L2Grabber.cpp rename to libsrc/grabber/v4l2/V4L2Grabber.cpp index 3c8f5a91..2df80524 100644 --- a/src/hyperion-v4l2/V4L2Grabber.cpp +++ b/libsrc/grabber/v4l2/V4L2Grabber.cpp @@ -14,7 +14,7 @@ #include #include -#include "V4L2Grabber.h" +#include "grabber/V4L2Grabber.h" #define CLEAR(x) memset(&(x), 0, sizeof(x)) @@ -36,21 +36,36 @@ static void yuv2rgb(uint8_t y, uint8_t u, uint8_t v, uint8_t & r, uint8_t & g, u } -V4L2Grabber::V4L2Grabber(const std::string &device, int input, VideoStandard videoStandard, int width, int height, int cropHorizontal, int cropVertical, int frameDecimation, int pixelDecimation) : +V4L2Grabber::V4L2Grabber(const std::string & device, + int input, + VideoStandard videoStandard, + PixelFormat pixelFormat, + int width, + int height, + int frameDecimation, + int horizontalPixelDecimation, + int verticalPixelDecimation) : _deviceName(device), _ioMethod(IO_METHOD_MMAP), _fileDescriptor(-1), _buffers(), - _pixelFormat(0), + _pixelFormat(pixelFormat), _width(width), _height(height), - _cropWidth(cropHorizontal), - _cropHeight(cropVertical), + _frameByteSize(-1), + _cropLeft(0), + _cropRight(0), + _cropTop(0), + _cropBottom(0), _frameDecimation(std::max(1, frameDecimation)), - _pixelDecimation(std::max(1, pixelDecimation)), + _horizontalPixelDecimation(std::max(1, horizontalPixelDecimation)), + _verticalPixelDecimation(std::max(1, verticalPixelDecimation)), + _noSignalCounterThreshold(50), + _noSignalThresholdColor(ColorRgb{0,0,0}), + _mode3D(VIDEO_2D), _currentFrame(0), - _callback(nullptr), - _callbackArg(nullptr) + _noSignalCounter(0), + _streamNotifier(nullptr) { open_device(); init_device(videoStandard, input); @@ -58,65 +73,53 @@ V4L2Grabber::V4L2Grabber(const std::string &device, int input, VideoStandard vid V4L2Grabber::~V4L2Grabber() { + // stop if the grabber was not stopped + stop(); uninit_device(); close_device(); } -void V4L2Grabber::setCallback(V4L2Grabber::ImageCallback callback, void *arg) +void V4L2Grabber::setCropping(int cropLeft, int cropRight, int cropTop, int cropBottom) { - _callback = callback; - _callbackArg = arg; + _cropLeft = cropLeft; + _cropRight = cropRight; + _cropTop = cropTop; + _cropBottom = cropBottom; +} + +void V4L2Grabber::set3D(VideoMode mode) +{ + _mode3D = mode; +} + +void V4L2Grabber::setSignalThreshold(double redSignalThreshold, double greenSignalThreshold, double blueSignalThreshold, int noSignalCounterThreshold) +{ + _noSignalThresholdColor.red = uint8_t(255*redSignalThreshold); + _noSignalThresholdColor.green = uint8_t(255*greenSignalThreshold); + _noSignalThresholdColor.blue = uint8_t(255*blueSignalThreshold); + _noSignalCounterThreshold = std::max(1, noSignalCounterThreshold); + + std::cout << "V4L2 grabber signal threshold set to: " << _noSignalThresholdColor << std::endl; } void V4L2Grabber::start() { - start_capturing(); -} - -void V4L2Grabber::capture(int frameCount) -{ - for (int count = 0; count < frameCount || frameCount < 0; ++count) + if (_streamNotifier != nullptr && !_streamNotifier->isEnabled()) { - for (;;) - { - // the set of file descriptors for select - fd_set fds; - FD_ZERO(&fds); - FD_SET(_fileDescriptor, &fds); - - // timeout - struct timeval tv; - tv.tv_sec = 2; - tv.tv_usec = 0; - - // block until data is available - int r = select(_fileDescriptor + 1, &fds, NULL, NULL, &tv); - - if (-1 == r) - { - if (EINTR == errno) - continue; - throw_errno_exception("select"); - } - - if (0 == r) - { - throw_exception("select timeout"); - } - - if (read_frame()) - { - break; - } - - /* EAGAIN - continue select loop. */ - } + _streamNotifier->setEnabled(true); + start_capturing(); + std::cout << "V4L2 grabber started" << std::endl; } } void V4L2Grabber::stop() { - stop_capturing(); + if (_streamNotifier != nullptr && _streamNotifier->isEnabled()) + { + stop_capturing(); + _streamNotifier->setEnabled(false); + std::cout << "V4L2 grabber stopped" << std::endl; + } } void V4L2Grabber::open_device() @@ -145,6 +148,11 @@ void V4L2Grabber::open_device() oss << "Cannot open '" << _deviceName << "'"; throw_errno_exception(oss.str()); } + + // create the notifier for when a new frame is available + _streamNotifier = new QSocketNotifier(_fileDescriptor, QSocketNotifier::Read); + _streamNotifier->setEnabled(false); + connect(_streamNotifier, SIGNAL(activated(int)), this, SLOT(read_frame())); } void V4L2Grabber::close_device() @@ -153,6 +161,12 @@ void V4L2Grabber::close_device() throw_errno_exception("close"); _fileDescriptor = -1; + + if (_streamNotifier != nullptr) + { + delete _streamNotifier; + _streamNotifier = nullptr; + } } void V4L2Grabber::init_read(unsigned int buffer_size) @@ -334,7 +348,7 @@ void V4L2Grabber::init_device(VideoStandard videoStandard, int input) // set the video standard if needed switch (videoStandard) { - case PAL: + case VIDEOSTANDARD_PAL: { v4l2_std_id std_id = V4L2_STD_PAL; if (-1 == xioctl(VIDIOC_S_STD, &std_id)) @@ -343,7 +357,7 @@ void V4L2Grabber::init_device(VideoStandard videoStandard, int input) } } break; - case NTSC: + case VIDEOSTANDARD_NTSC: { v4l2_std_id std_id = V4L2_STD_NTSC; if (-1 == xioctl(VIDIOC_S_STD, &std_id)) @@ -352,7 +366,7 @@ void V4L2Grabber::init_device(VideoStandard videoStandard, int input) } } break; - case NO_CHANGE: + case VIDEOSTANDARD_NO_CHANGE: default: // No change to device settings break; @@ -368,17 +382,25 @@ void V4L2Grabber::init_device(VideoStandard videoStandard, int input) throw_errno_exception("VIDIOC_G_FMT"); } - // check pixel format - switch (fmt.fmt.pix.pixelformat) + // set the requested pixel format + switch (_pixelFormat) { - case V4L2_PIX_FMT_UYVY: - case V4L2_PIX_FMT_YUYV: - _pixelFormat = fmt.fmt.pix.pixelformat; + case PIXELFORMAT_UYVY: + fmt.fmt.pix.pixelformat = V4L2_PIX_FMT_UYVY; break; + case PIXELFORMAT_YUYV: + fmt.fmt.pix.pixelformat = V4L2_PIX_FMT_YUYV; + break; + case PIXELFORMAT_RGB32: + fmt.fmt.pix.pixelformat = V4L2_PIX_FMT_RGB32; + break; + case PIXELFORMAT_NO_CHANGE: default: - throw_exception("Only pixel formats UYVY and YUYV are supported"); + // No change to device settings + break; } + // set the requested withd and height if (_width > 0 || _height > 0) { if (_width > 0) @@ -390,19 +412,19 @@ void V4L2Grabber::init_device(VideoStandard videoStandard, int input) { fmt.fmt.pix.height = _height; } + } - // set the settings - if (-1 == xioctl(VIDIOC_S_FMT, &fmt)) - { - throw_errno_exception("VIDIOC_S_FMT"); - } + // set the settings + if (-1 == xioctl(VIDIOC_S_FMT, &fmt)) + { + throw_errno_exception("VIDIOC_S_FMT"); + } - // get the format settings again - // (the size may not have been accepted without an error) - if (-1 == xioctl(VIDIOC_G_FMT, &fmt)) - { - throw_errno_exception("VIDIOC_G_FMT"); - } + // get the format settings again + // (the size may not have been accepted without an error) + if (-1 == xioctl(VIDIOC_G_FMT, &fmt)) + { + throw_errno_exception("VIDIOC_G_FMT"); } // store width & height @@ -412,6 +434,28 @@ void V4L2Grabber::init_device(VideoStandard videoStandard, int input) // print the eventually used width and height std::cout << "V4L2 width=" << _width << " height=" << _height << std::endl; + // check pixel format and frame size + switch (fmt.fmt.pix.pixelformat) + { + case V4L2_PIX_FMT_UYVY: + _pixelFormat = PIXELFORMAT_UYVY; + _frameByteSize = _width * _height * 2; + std::cout << "V4L2 pixel format=UYVY" << std::endl; + break; + case V4L2_PIX_FMT_YUYV: + _pixelFormat = PIXELFORMAT_YUYV; + _frameByteSize = _width * _height * 2; + std::cout << "V4L2 pixel format=YUYV" << std::endl; + break; + case V4L2_PIX_FMT_RGB32: + _pixelFormat = PIXELFORMAT_RGB32; + _frameByteSize = _width * _height * 4; + std::cout << "V4L2 pixel format=RGB32" << std::endl; + break; + default: + throw_exception("Only pixel formats UYVY, YUYV, and RGB32 are supported"); + } + switch (_ioMethod) { case IO_METHOD_READ: init_read(fmt.fmt.pix.sizeimage); @@ -627,9 +671,9 @@ bool V4L2Grabber::process_image(const void *p, int size) { // We do want a new frame... - if (size != 2*_width*_height) + if (size != _frameByteSize) { - std::cout << "Frame too small: " << size << " != " << (2*_width*_height) << std::endl; + std::cout << "Frame too small: " << size << " != " << _frameByteSize << std::endl; } else { @@ -644,42 +688,103 @@ bool V4L2Grabber::process_image(const void *p, int size) void V4L2Grabber::process_image(const uint8_t * data) { - int width = (_width - 2 * _cropWidth + _pixelDecimation/2) / _pixelDecimation; - int height = (_height - 2 * _cropHeight + _pixelDecimation/2) / _pixelDecimation; + int width = _width; + int height = _height; - Image image(width, height); - - for (int ySource = _cropHeight + _pixelDecimation/2, yDest = 0; ySource < _height - _cropHeight; ySource += _pixelDecimation, ++yDest) + switch (_mode3D) { - for (int xSource = _cropWidth + _pixelDecimation/2, xDest = 0; xSource < _width - _cropWidth; xSource += _pixelDecimation, ++xDest) + case VIDEO_3DSBS: + width = _width/2; + break; + case VIDEO_3DTAB: + height = _height/2; + break; + default: + break; + } + + // create output structure + int outputWidth = (width - _cropLeft - _cropRight + _horizontalPixelDecimation/2) / _horizontalPixelDecimation; + int outputHeight = (height - _cropTop - _cropBottom + _verticalPixelDecimation/2) / _verticalPixelDecimation; + Image image(outputWidth, outputHeight); + + for (int ySource = _cropTop + _verticalPixelDecimation/2, yDest = 0; ySource < height - _cropBottom; ySource += _verticalPixelDecimation, ++yDest) + { + for (int xSource = _cropLeft + _horizontalPixelDecimation/2, xDest = 0; xSource < width - _cropRight; xSource += _horizontalPixelDecimation, ++xDest) { - int index = (_width * ySource + xSource) * 2; - uint8_t y = 0; - uint8_t u = 0; - uint8_t v = 0; + ColorRgb & rgb = image(xDest, yDest); switch (_pixelFormat) { - case V4L2_PIX_FMT_UYVY: - y = data[index+1]; - u = (xSource%2 == 0) ? data[index ] : data[index-2]; - v = (xSource%2 == 0) ? data[index+2] : data[index ]; + case PIXELFORMAT_UYVY: + { + int index = (_width * ySource + xSource) * 2; + uint8_t y = data[index+1]; + uint8_t u = (xSource%2 == 0) ? data[index ] : data[index-2]; + uint8_t v = (xSource%2 == 0) ? data[index+2] : data[index ]; + yuv2rgb(y, u, v, rgb.red, rgb.green, rgb.blue); + } break; - case V4L2_PIX_FMT_YUYV: - y = data[index]; - u = (xSource%2 == 0) ? data[index+1] : data[index-1]; - v = (xSource%2 == 0) ? data[index+3] : data[index+1]; + case PIXELFORMAT_YUYV: + { + int index = (_width * ySource + xSource) * 2; + uint8_t y = data[index]; + uint8_t u = (xSource%2 == 0) ? data[index+1] : data[index-1]; + uint8_t v = (xSource%2 == 0) ? data[index+3] : data[index+1]; + yuv2rgb(y, u, v, rgb.red, rgb.green, rgb.blue); + } + break; + case PIXELFORMAT_RGB32: + { + int index = (_width * ySource + xSource) * 4; + rgb.red = data[index ]; + rgb.green = data[index+1]; + rgb.blue = data[index+2]; + } + break; + default: + // this should not be possible break; } - - ColorRgb & rgb = image(xDest, yDest); - yuv2rgb(y, u, v, rgb.red, rgb.green, rgb.blue); } } - if (_callback != nullptr) + // check signal (only in center of the resulting image, because some grabbers have noise values along the borders) + bool noSignal = true; + for (unsigned x = 0; noSignal && x < (image.width()>>1); ++x) { - (*_callback)(_callbackArg, image); + int xImage = (image.width()>>2) + x; + + for (unsigned y = 0; noSignal && y < (image.height()>>1); ++y) + { + int yImage = (image.height()>>2) + y; + + ColorRgb & rgb = image(xImage, yImage); + noSignal &= rgb <= _noSignalThresholdColor; + } + } + + if (noSignal) + { + ++_noSignalCounter; + } + else + { + if (_noSignalCounter >= _noSignalCounterThreshold) + { + std::cout << "V4L2 Grabber: " << "Signal detected" << std::endl; + } + + _noSignalCounter = 0; + } + + if (_noSignalCounter < _noSignalCounterThreshold) + { + emit newFrame(image); + } + else if (_noSignalCounter == _noSignalCounterThreshold) + { + std::cout << "V4L2 Grabber: " << "Signal lost" << std::endl; } } diff --git a/libsrc/grabber/v4l2/V4L2Wrapper.cpp b/libsrc/grabber/v4l2/V4L2Wrapper.cpp new file mode 100644 index 00000000..76b16ab7 --- /dev/null +++ b/libsrc/grabber/v4l2/V4L2Wrapper.cpp @@ -0,0 +1,117 @@ +#include + +#include + +#include + +V4L2Wrapper::V4L2Wrapper(const std::string &device, + int input, + VideoStandard videoStandard, + PixelFormat pixelFormat, + int width, + int height, + int frameDecimation, + int pixelDecimation, + double redSignalThreshold, + double greenSignalThreshold, + double blueSignalThreshold, + Hyperion *hyperion, + int hyperionPriority) : + _timeout_ms(1000), + _priority(hyperionPriority), + _grabber(device, + input, + videoStandard, + pixelFormat, + width, + height, + frameDecimation, + pixelDecimation, + pixelDecimation), + _processor(ImageProcessorFactory::getInstance().newImageProcessor()), + _hyperion(hyperion), + _ledColors(hyperion->getLedCount(), ColorRgb{0,0,0}), + _timer() +{ + // set the signal detection threshold of the grabber + _grabber.setSignalThreshold( + redSignalThreshold, + greenSignalThreshold, + blueSignalThreshold, + 50); + + // register the image type + qRegisterMetaType>("Image"); + qRegisterMetaType>("std::vector"); + + // Handle the image in the captured thread using a direct connection + QObject::connect( + &_grabber, SIGNAL(newFrame(Image)), + this, SLOT(newFrame(Image)), + Qt::DirectConnection); + + // send color data to Hyperion using a queued connection to handle the data over to the main event loop + QObject::connect( + this, SIGNAL(emitColors(int,std::vector,int)), + _hyperion, SLOT(setColors(int,std::vector,int)), + Qt::QueuedConnection); + + // setup the higher prio source checker + // this will disable the v4l2 grabber when a source with hisher priority is active + _timer.setInterval(500); + _timer.setSingleShot(false); + QObject::connect(&_timer, SIGNAL(timeout()), this, SLOT(checkSources())); + _timer.start(); +} + +V4L2Wrapper::~V4L2Wrapper() +{ + delete _processor; +} + +void V4L2Wrapper::start() +{ + _grabber.start(); +} + +void V4L2Wrapper::stop() +{ + _grabber.stop(); +} + +void V4L2Wrapper::setCropping(int cropLeft, int cropRight, int cropTop, int cropBottom) +{ + _grabber.setCropping(cropLeft, cropRight, cropTop, cropBottom); +} + +void V4L2Wrapper::set3D(VideoMode mode) +{ + _grabber.set3D(mode); +} + +void V4L2Wrapper::newFrame(const Image &image) +{ + // process the new image + _processor->process(image, _ledColors); + + // send colors to Hyperion + emit emitColors(_priority, _ledColors, _timeout_ms); +} + +void V4L2Wrapper::checkSources() +{ + QList activePriorities = _hyperion->getActivePriorities(); + + for (int x : activePriorities) + { + if (x < _priority) + { + // found a higher priority source: grabber should be disabled + _grabber.stop(); + return; + } + } + + // no higher priority source was found: grabber should be enabled + _grabber.start(); +} diff --git a/libsrc/hyperion/ImageToLedsMap.cpp b/libsrc/hyperion/ImageToLedsMap.cpp index 3e9a7fd3..202f22ff 100644 --- a/libsrc/hyperion/ImageToLedsMap.cpp +++ b/libsrc/hyperion/ImageToLedsMap.cpp @@ -1,4 +1,3 @@ - // STL includes #include #include @@ -33,17 +32,35 @@ ImageToLedsMap::ImageToLedsMap( for (const Led& led : leds) { + // skip leds without area + if ((led.maxX_frac-led.minX_frac) < 1e-6 || (led.maxY_frac-led.minY_frac) < 1e-6) + { + continue; + } + // Compute the index boundaries for this led - const unsigned minX_idx = xOffset + unsigned(std::round((actualWidth-1) * led.minX_frac)); - const unsigned maxX_idx = xOffset + unsigned(std::round((actualWidth-1) * led.maxX_frac)); - const unsigned minY_idx = yOffset + unsigned(std::round((actualHeight-1) * led.minY_frac)); - const unsigned maxY_idx = yOffset + unsigned(std::round((actualHeight-1) * led.maxY_frac)); + unsigned minX_idx = xOffset + unsigned(std::round(actualWidth * led.minX_frac)); + unsigned maxX_idx = xOffset + unsigned(std::round(actualWidth * led.maxX_frac)); + unsigned minY_idx = yOffset + unsigned(std::round(actualHeight * led.minY_frac)); + unsigned maxY_idx = yOffset + unsigned(std::round(actualHeight * led.maxY_frac)); + + // make sure that the area is at least a single led large + minX_idx = std::min(minX_idx, xOffset + actualWidth - 1); + if (minX_idx == maxX_idx) + { + maxX_idx = minX_idx + 1; + } + minY_idx = std::min(minY_idx, yOffset + actualHeight - 1); + if (minY_idx == maxY_idx) + { + maxY_idx = minY_idx + 1; + } // Add all the indices in the above defined rectangle to the indices for this led std::vector ledColors; - for (unsigned y = minY_idx; y<=maxY_idx && y +#include // Build configuration #include @@ -13,6 +16,10 @@ #include "LedDeviceWs2801.h" #endif +#ifdef ENABLE_TINKERFORGE + #include "LedDeviceTinkerforge.h" +#endif + #include "LedDeviceAdalight.h" #include "LedDeviceLightpack.h" #include "LedDeviceMultiLightpack.h" @@ -20,8 +27,7 @@ #include "LedDevicePiBlaster.h" #include "LedDeviceSedu.h" #include "LedDeviceTest.h" -#include "LedDeviceWs2811.h" -#include "LedDeviceWs2812b.h" +#include "LedDeviceHyperionUsbasp.h" LedDevice * LedDeviceFactory::construct(const Json::Value & deviceConfig) { @@ -84,23 +90,20 @@ LedDevice * LedDeviceFactory::construct(const Json::Value & deviceConfig) device = deviceWs2801; } #endif -// else if (type == "ws2811") -// { -// const std::string output = deviceConfig["output"].asString(); -// const std::string outputSpeed = deviceConfig["output"].asString(); -// const std::string timingOption = deviceConfig["timingOption"].asString(); +#ifdef ENABLE_TINKERFORGE + else if (type=="tinkerforge") + { + const std::string host = deviceConfig.get("output", "127.0.0.1").asString(); + const uint16_t port = deviceConfig.get("port", 4223).asInt(); + const std::string uid = deviceConfig["uid"].asString(); + const unsigned rate = deviceConfig["rate"].asInt(); -// ws2811::SpeedMode speedMode = (outputSpeed == "high")? ws2811::highspeed : ws2811::lowspeed; -// if (outputSpeed != "high" && outputSpeed != "low") -// { -// std::cerr << "Incorrect speed-mode selected for WS2811: " << outputSpeed << " != {'high', 'low'}" << std::endl; -// } + LedDeviceTinkerforge* deviceTinkerforge = new LedDeviceTinkerforge(host, port, uid, rate); + deviceTinkerforge->open(); -// LedDeviceWs2811 * deviceWs2811 = new LedDeviceWs2811(output, ws2811::fromString(timingOption, ws2811::option_2855), speedMode); -// deviceWs2811->open(); - -// device = deviceWs2811; -// } + device = deviceTinkerforge; + } +#endif else if (type == "lightpack") { const std::string output = deviceConfig.get("output", "").asString(); @@ -144,18 +147,23 @@ LedDevice * LedDeviceFactory::construct(const Json::Value & deviceConfig) device = deviceSedu; } + else if (type == "hyperion-usbasp-ws2801") + { + LedDeviceHyperionUsbasp * deviceHyperionUsbasp = new LedDeviceHyperionUsbasp(LedDeviceHyperionUsbasp::CMD_WRITE_WS2801); + deviceHyperionUsbasp->open(); + device = deviceHyperionUsbasp; + } + else if (type == "hyperion-usbasp-ws2812") + { + LedDeviceHyperionUsbasp * deviceHyperionUsbasp = new LedDeviceHyperionUsbasp(LedDeviceHyperionUsbasp::CMD_WRITE_WS2812); + deviceHyperionUsbasp->open(); + device = deviceHyperionUsbasp; + } else if (type == "test") { const std::string output = deviceConfig["output"].asString(); device = new LedDeviceTest(output); } - else if (type == "ws2812b") - { - LedDeviceWs2812b * deviceWs2812b = new LedDeviceWs2812b(); - deviceWs2812b->open(); - - device = deviceWs2812b; - } else { std::cout << "Unable to create device " << type << std::endl; diff --git a/libsrc/leddevice/LedDeviceHyperionUsbasp.cpp b/libsrc/leddevice/LedDeviceHyperionUsbasp.cpp new file mode 100644 index 00000000..9eddde90 --- /dev/null +++ b/libsrc/leddevice/LedDeviceHyperionUsbasp.cpp @@ -0,0 +1,205 @@ +// stl includes +#include +#include + +// Local Hyperion includes +#include "LedDeviceHyperionUsbasp.h" + +// Static constants which define the Hyperion Usbasp device +uint16_t LedDeviceHyperionUsbasp::_usbVendorId = 0x16c0; +uint16_t LedDeviceHyperionUsbasp::_usbProductId = 0x05dc; +std::string LedDeviceHyperionUsbasp::_usbProductDescription = "Hyperion led controller"; + + +LedDeviceHyperionUsbasp::LedDeviceHyperionUsbasp(uint8_t writeLedsCommand) : + LedDevice(), + _writeLedsCommand(writeLedsCommand), + _libusbContext(nullptr), + _deviceHandle(nullptr), + _ledCount(256) +{ +} + +LedDeviceHyperionUsbasp::~LedDeviceHyperionUsbasp() +{ + if (_deviceHandle != nullptr) + { + libusb_release_interface(_deviceHandle, 0); + libusb_attach_kernel_driver(_deviceHandle, 0); + libusb_close(_deviceHandle); + + _deviceHandle = nullptr; + } + + if (_libusbContext != nullptr) + { + libusb_exit(_libusbContext); + _libusbContext = nullptr; + } +} + +int LedDeviceHyperionUsbasp::open() +{ + int error; + + // initialize the usb context + if ((error = libusb_init(&_libusbContext)) != LIBUSB_SUCCESS) + { + std::cerr << "Error while initializing USB context(" << error << "): " << libusb_error_name(error) << std::endl; + _libusbContext = nullptr; + return -1; + } + //libusb_set_debug(_libusbContext, 3); + std::cout << "USB context initialized" << std::endl; + + // retrieve the list of usb devices + libusb_device ** deviceList; + ssize_t deviceCount = libusb_get_device_list(_libusbContext, &deviceList); + + // iterate the list of devices + for (ssize_t i = 0 ; i < deviceCount; ++i) + { + // try to open and initialize the device + error = testAndOpen(deviceList[i]); + + if (error == 0) + { + // a device was sucessfully opened. break from list + break; + } + } + + // free the device list + libusb_free_device_list(deviceList, 1); + + if (_deviceHandle == nullptr) + { + std::cerr << "No " << _usbProductDescription << " has been found" << std::endl; + } + + return _deviceHandle == nullptr ? -1 : 0; +} + +int LedDeviceHyperionUsbasp::testAndOpen(libusb_device * device) +{ + libusb_device_descriptor deviceDescriptor; + int error = libusb_get_device_descriptor(device, &deviceDescriptor); + if (error != LIBUSB_SUCCESS) + { + std::cerr << "Error while retrieving device descriptor(" << error << "): " << libusb_error_name(error) << std::endl; + return -1; + } + + if (deviceDescriptor.idVendor == _usbVendorId && + deviceDescriptor.idProduct == _usbProductId && + deviceDescriptor.iProduct != 0 && + getString(device, deviceDescriptor.iProduct) == _usbProductDescription) + { + // get the hardware address + int busNumber = libusb_get_bus_number(device); + int addressNumber = libusb_get_device_address(device); + + std::cout << _usbProductDescription << " found: bus=" << busNumber << " address=" << addressNumber << std::endl; + + try + { + _deviceHandle = openDevice(device); + std::cout << _usbProductDescription << " successfully opened" << std::endl; + return 0; + } + catch(int e) + { + _deviceHandle = nullptr; + std::cerr << "Unable to open " << _usbProductDescription << ". Searching for other device(" << e << "): " << libusb_error_name(e) << std::endl; + } + } + + return -1; +} + +int LedDeviceHyperionUsbasp::write(const std::vector &ledValues) +{ + _ledCount = ledValues.size(); + + int nbytes = libusb_control_transfer( + _deviceHandle, // device handle + LIBUSB_REQUEST_TYPE_VENDOR | LIBUSB_RECIPIENT_DEVICE | LIBUSB_ENDPOINT_OUT, // request type + _writeLedsCommand, // request + 0, // value + 0, // index + (uint8_t *) ledValues.data(), // data + (3*_ledCount) & 0xffff, // length + 5000); // timeout + + // Disabling interupts for a little while on the device results in a PIPE error. All seems to keep functioning though... + if(nbytes < 0 && nbytes != LIBUSB_ERROR_PIPE) + { + std::cerr << "Error while writing data to " << _usbProductDescription << " (" << libusb_error_name(nbytes) << ")" << std::endl; + return -1; + } + + return 0; +} + +int LedDeviceHyperionUsbasp::switchOff() +{ + std::vector ledValues(_ledCount, ColorRgb::BLACK); + return write(ledValues); +} + +libusb_device_handle * LedDeviceHyperionUsbasp::openDevice(libusb_device *device) +{ + libusb_device_handle * handle = nullptr; + + int error = libusb_open(device, &handle); + if (error != LIBUSB_SUCCESS) + { + std::cerr << "unable to open device(" << error << "): " << libusb_error_name(error) << std::endl; + throw error; + } + + // detach kernel driver if it is active + if (libusb_kernel_driver_active(handle, 0) == 1) + { + error = libusb_detach_kernel_driver(handle, 0); + if (error != LIBUSB_SUCCESS) + { + std::cerr << "unable to detach kernel driver(" << error << "): " << libusb_error_name(error) << std::endl; + libusb_close(handle); + throw error; + } + } + + error = libusb_claim_interface(handle, 0); + if (error != LIBUSB_SUCCESS) + { + std::cerr << "unable to claim interface(" << error << "): " << libusb_error_name(error) << std::endl; + libusb_attach_kernel_driver(handle, 0); + libusb_close(handle); + throw error; + } + + return handle; +} + +std::string LedDeviceHyperionUsbasp::getString(libusb_device * device, int stringDescriptorIndex) +{ + libusb_device_handle * handle = nullptr; + + int error = libusb_open(device, &handle); + if (error != LIBUSB_SUCCESS) + { + throw error; + } + + char buffer[256]; + error = libusb_get_string_descriptor_ascii(handle, stringDescriptorIndex, reinterpret_cast(buffer), sizeof(buffer)); + if (error <= 0) + { + libusb_close(handle); + throw error; + } + + libusb_close(handle); + return std::string(buffer, error); +} diff --git a/libsrc/leddevice/LedDeviceHyperionUsbasp.h b/libsrc/leddevice/LedDeviceHyperionUsbasp.h new file mode 100644 index 00000000..a8f91cc7 --- /dev/null +++ b/libsrc/leddevice/LedDeviceHyperionUsbasp.h @@ -0,0 +1,88 @@ +#pragma once + +// stl includes +#include +#include +#include + +// libusb include +#include + +// Hyperion includes +#include + +/// +/// LedDevice implementation for a lightpack device (http://code.google.com/p/light-pack/) +/// +class LedDeviceHyperionUsbasp : public LedDevice +{ +public: + // Commands to the Device + enum Commands { + CMD_WRITE_WS2801 = 10, + CMD_WRITE_WS2812 = 11 + }; + + /// + /// Constructs the LedDeviceLightpack + /// + LedDeviceHyperionUsbasp(uint8_t writeLedsCommand); + + /// + /// Destructor of the LedDevice; closes the output device if it is open + /// + virtual ~LedDeviceHyperionUsbasp(); + + /// + /// Opens and configures the output device + /// + /// @return Zero on succes else negative + /// + int open(); + + /// + /// Writes the RGB-Color values to the leds. + /// + /// @param[in] ledValues The RGB-color per led + /// + /// @return Zero on success else negative + /// + virtual int write(const std::vector& ledValues); + + /// + /// Switch the leds off + /// + /// @return Zero on success else negative + /// + virtual int switchOff(); + +private: + /// + /// Test if the device is a Hyperion Usbasp device + /// + /// @return Zero on succes else negative + /// + int testAndOpen(libusb_device * device); + + static libusb_device_handle * openDevice(libusb_device * device); + + static std::string getString(libusb_device * device, int stringDescriptorIndex); + +private: + /// command to write the leds + const uint8_t _writeLedsCommand; + + /// libusb context + libusb_context * _libusbContext; + + /// libusb device handle + libusb_device_handle * _deviceHandle; + + /// Number of leds + int _ledCount; + + /// Usb device identifiers + static uint16_t _usbVendorId; + static uint16_t _usbProductId; + static std::string _usbProductDescription; +}; diff --git a/libsrc/leddevice/LedDeviceLpd6803.cpp b/libsrc/leddevice/LedDeviceLpd6803.cpp index bfaa010b..09d5f838 100644 --- a/libsrc/leddevice/LedDeviceLpd6803.cpp +++ b/libsrc/leddevice/LedDeviceLpd6803.cpp @@ -19,11 +19,12 @@ LedDeviceLpd6803::LedDeviceLpd6803(const std::string& outputDevice, const unsign int LedDeviceLpd6803::write(const std::vector &ledValues) { + unsigned messageLength = 4 + 2*ledValues.size() + ledValues.size()/8 + 1; // Reconfigure if the current connfiguration does not match the required configuration - if (4 + 2*ledValues.size() != _ledBuffer.size()) + if (messageLength != _ledBuffer.size()) { // Initialise the buffer - _ledBuffer.resize(4 + 2*ledValues.size(), 0x00); + _ledBuffer.resize(messageLength, 0x00); } // Copy the colors from the ColorRgb vector to the Ldp6803 data vector diff --git a/libsrc/leddevice/LedDeviceSedu.cpp b/libsrc/leddevice/LedDeviceSedu.cpp index 71ace421..c81eb2c4 100644 --- a/libsrc/leddevice/LedDeviceSedu.cpp +++ b/libsrc/leddevice/LedDeviceSedu.cpp @@ -28,7 +28,7 @@ int LedDeviceSedu::write(const std::vector &ledValues) { if (_ledBuffer.size() == 0) { - std::vector frameSpecs{{0xA0, 96}, {0xA1, 256}, {0xA2, 512}, {0xB0, 768}, {0xB1, 1536}, {0xB2, 3072} }; + std::vector frameSpecs{{0xA1, 256}, {0xA2, 512}, {0xB0, 768}, {0xB1, 1536}, {0xB2, 3072} }; const unsigned reqColorChannels = ledValues.size() * sizeof(ColorRgb); diff --git a/libsrc/leddevice/LedDeviceTinkerforge.cpp b/libsrc/leddevice/LedDeviceTinkerforge.cpp new file mode 100644 index 00000000..83367042 --- /dev/null +++ b/libsrc/leddevice/LedDeviceTinkerforge.cpp @@ -0,0 +1,143 @@ + +// STL includes +#include +#include + +// Local LedDevice includes +#include "LedDeviceTinkerforge.h" + +static const unsigned MAX_NUM_LEDS = 320; +static const unsigned MAX_NUM_LEDS_SETTABLE = 16; + +LedDeviceTinkerforge::LedDeviceTinkerforge(const std::string & host, uint16_t port, const std::string & uid, const unsigned interval) : + LedDevice(), + _host(host), + _port(port), + _uid(uid), + _interval(interval), + _ipConnection(nullptr), + _ledStrip(nullptr), + _colorChannelSize(0) +{ + // empty +} + +LedDeviceTinkerforge::~LedDeviceTinkerforge() +{ + // Close the device (if it is opened) + if (_ipConnection != nullptr && _ledStrip != nullptr) + { + switchOff(); + } + + // Clean up claimed resources + delete _ipConnection; + delete _ledStrip; +} + +int LedDeviceTinkerforge::open() +{ + // Check if connection is already createds + if (_ipConnection != nullptr) + { + std::cout << "Attempt to open existing connection; close before opening" << std::endl; + return -1; + } + + // Initialise a new connection + _ipConnection = new IPConnection; + ipcon_create(_ipConnection); + + int connectionStatus = ipcon_connect(_ipConnection, _host.c_str(), _port); + if (connectionStatus < 0) + { + std::cerr << "Attempt to connect to master brick (" << _host << ":" << _port << ") failed with status " << connectionStatus << std::endl; + return -1; + } + + // Create the 'LedStrip' + _ledStrip = new LEDStrip; + led_strip_create(_ledStrip, _uid.c_str(), _ipConnection); + + int frameStatus = led_strip_set_frame_duration(_ledStrip, _interval); + if (frameStatus < 0) + { + std::cerr << "Attempt to connect to led strip bricklet (led_strip_set_frame_duration()) failed with status " << frameStatus << std::endl; + return -1; + } + + return 0; +} + +int LedDeviceTinkerforge::write(const std::vector &ledValues) +{ + unsigned nrLedValues = ledValues.size(); + + if (nrLedValues > MAX_NUM_LEDS) + { + std::cerr << "Invalid attempt to write led values. Not more than " << MAX_NUM_LEDS << " leds are allowed." << std::endl; + return -1; + } + + if (_colorChannelSize < nrLedValues) + { + _redChannel.resize(nrLedValues, uint8_t(0)); + _greenChannel.resize(nrLedValues, uint8_t(0)); + _blueChannel.resize(nrLedValues, uint8_t(0)); + } + _colorChannelSize = nrLedValues; + + auto redIt = _redChannel.begin(); + auto greenIt = _greenChannel.begin(); + auto blueIt = _blueChannel.begin(); + + for (const ColorRgb &ledValue : ledValues) + { + *redIt = ledValue.red; + ++redIt; + *greenIt = ledValue.green; + ++greenIt; + *blueIt = ledValue.blue; + ++blueIt; + } + + return transferLedData(_ledStrip, 0, _colorChannelSize, _redChannel.data(), _greenChannel.data(), _blueChannel.data()); +} + +int LedDeviceTinkerforge::switchOff() +{ + std::fill(_redChannel.begin(), _redChannel.end(), 0); + std::fill(_greenChannel.begin(), _greenChannel.end(), 0); + std::fill(_blueChannel.begin(), _blueChannel.end(), 0); + + return transferLedData(_ledStrip, 0, _colorChannelSize, _redChannel.data(), _greenChannel.data(), _blueChannel.data()); +} + +int LedDeviceTinkerforge::transferLedData(LEDStrip *ledStrip, unsigned index, unsigned length, uint8_t *redChannel, uint8_t *greenChannel, uint8_t *blueChannel) +{ + if (length == 0 || index >= length || length > MAX_NUM_LEDS) + { + return E_INVALID_PARAMETER; + } + + uint8_t reds[MAX_NUM_LEDS_SETTABLE]; + uint8_t greens[MAX_NUM_LEDS_SETTABLE]; + uint8_t blues[MAX_NUM_LEDS_SETTABLE]; + + for (unsigned i=index; i length) ? length - i : MAX_NUM_LEDS_SETTABLE; + memcpy(reds, redChannel + i, copyLength); + memcpy(greens, greenChannel + i, copyLength); + memcpy(blues, blueChannel + i, copyLength); + + const int status = led_strip_set_rgb_values(ledStrip, i, copyLength, reds, greens, blues); + if (status != E_OK) + { + std::cerr << "Setting led values failed with status " << status << std::endl; + return status; + } + } + + return E_OK; +} diff --git a/libsrc/leddevice/LedDeviceTinkerforge.h b/libsrc/leddevice/LedDeviceTinkerforge.h new file mode 100644 index 00000000..95f43332 --- /dev/null +++ b/libsrc/leddevice/LedDeviceTinkerforge.h @@ -0,0 +1,82 @@ + +#pragma once + +// STL includes +#include + +// Hyperion-Leddevice includes +#include + + +extern "C" { + #include + #include +} + +class LedDeviceTinkerforge : public LedDevice +{ +public: + + LedDeviceTinkerforge(const std::string &host, uint16_t port, const std::string &uid, const unsigned interval); + + virtual ~LedDeviceTinkerforge(); + + /// + /// Attempts to open a connection to the master bricklet and the led strip bricklet. + /// + /// @return Zero on succes else negative + /// + int open(); + + /// + /// Writes the colors to the led strip bricklet + /// + /// @param ledValues The color value for each led + /// + /// @return Zero on success else negative + /// + virtual int write(const std::vector &ledValues); + + /// + /// Switches off the leds + /// + /// @return Zero on success else negative + /// + virtual int switchOff(); + +private: + /// + /// Writes the data to the led strip blicklet + int transferLedData(LEDStrip *ledstrip, unsigned int index, unsigned int length, uint8_t *redChannel, uint8_t *greenChannel, uint8_t *blueChannel); + + /// The host of the master brick + const std::string _host; + + /// The port of the master brick + const uint16_t _port; + + /// The uid of the led strip bricklet + const std::string _uid; + + /// The interval/rate + const unsigned _interval; + + /// ip connection handle + IPConnection *_ipConnection; + + /// led strip handle + LEDStrip *_ledStrip; + + /// buffer for red channel led data + std::vector _redChannel; + + /// buffer for red channel led data + std::vector _greenChannel; + + /// buffer for red channel led data + std::vector _blueChannel; + + /// buffer size of the color channels + unsigned int _colorChannelSize; + +}; diff --git a/libsrc/leddevice/LedDeviceWs2811.cpp b/libsrc/leddevice/LedDeviceWs2811.cpp deleted file mode 100644 index 9fe0f8a3..00000000 --- a/libsrc/leddevice/LedDeviceWs2811.cpp +++ /dev/null @@ -1,182 +0,0 @@ - -// Local hyperion includes -#include "LedDeviceWs2811.h" - - -ws2811::SignalTiming ws2811::fromString(const std::string& signalTiming, const SignalTiming defaultValue) -{ - SignalTiming result = defaultValue; - if (signalTiming == "3755" || signalTiming == "option_3755") - { - result = option_3755; - } - else if (signalTiming == "3773" || signalTiming == "option_3773") - { - result = option_3773; - } - else if (signalTiming == "2855" || signalTiming == "option_2855") - { - result = option_2855; - } - else if (signalTiming == "2882" || signalTiming == "option_2882") - { - result = option_2882; - } - - return result; -} - -unsigned ws2811::getBaudrate(const SpeedMode speedMode) -{ - switch (speedMode) - { - case highspeed: - // Bit length: 125ns - return 8000000; - case lowspeed: - // Bit length: 250ns - return 4000000; - } - - return 0; -} -inline unsigned ws2811::getLength(const SignalTiming timing, const TimeOption option) -{ - switch (timing) - { - case option_3755: - // Reference: http://www.mikrocontroller.net/attachment/180459/WS2812B_preliminary.pdf - // Unit length: 125ns - switch (option) - { - case T0H: - return 3; // 400ns +-150ns - case T0L: - return 7; // 850ns +-150ns - case T1H: - return 7; // 800ns +-150ns - case T1L: - return 3; // 450ns +-150ns - } - case option_3773: - // Reference: www.adafruit.com/datasheets/WS2812.pdf‎ - // Unit length: 125ns - switch (option) - { - case T0H: - return 3; // 350ns +-150ns - case T0L: - return 7; // 800ns +-150ns - case T1H: - return 7; // 700ns +-150ns - case T1L: - return 3; // 600ns +-150ns - } - case option_2855: - // Reference: www.adafruit.com/datasheets/WS2811.pdf‎ - // Unit length: 250ns - switch (option) - { - case T0H: - return 2; // 500ns +-150ns - case T0L: - return 8; // 2000ns +-150ns - case T1H: - return 5; // 1200ns +-150ns - case T1L: - return 5; // 1300ns +-150ns - } - case option_2882: - // Reference: www.szparkson.net/download/WS2811.pdf‎ - // Unit length: 250ns - switch (option) - { - case T0H: - return 2; // 500ns +-150ns - case T0L: - return 8; // 2000ns +-150ns - case T1H: - return 8; // 2000ns +-150ns - case T1L: - return 2; // 500ns +-150ns - } - default: - std::cerr << "Unknown signal timing for ws2811: " << timing << std::endl; - } - return 0; -} - -uint8_t ws2811::bitToSignal(unsigned lenHigh) -{ - // Sanity check on the length of the 'high' signal - assert(0 < lenHigh && lenHigh < 10); - - uint8_t result = 0x00; - for (unsigned i=1; i & ledValues) -{ - if (_ledBuffer.size() != ledValues.size() * 3) - { - _ledBuffer.resize(ledValues.size() * 3); - } - - auto bufIt = _ledBuffer.begin(); - for (const ColorRgb & color : ledValues) - { - *bufIt = _byteToSignalTable[color.red ]; - ++bufIt; - *bufIt = _byteToSignalTable[color.green]; - ++bufIt; - *bufIt = _byteToSignalTable[color.blue ]; - ++bufIt; - } - - writeBytes(_ledBuffer.size() * 3, reinterpret_cast(_ledBuffer.data())); - - return 0; -} - -int LedDeviceWs2811::switchOff() -{ - write(std::vector(_ledBuffer.size()/3, ColorRgb::BLACK)); - return 0; -} - -void LedDeviceWs2811::fillEncodeTable(const ws2811::SignalTiming ledOption) -{ - _byteToSignalTable.resize(256); - for (unsigned byteValue=0; byteValue<256; ++byteValue) - { - const uint8_t byteVal = uint8_t(byteValue); - _byteToSignalTable[byteValue] = ws2811::translate(ledOption, byteVal); - } -} diff --git a/libsrc/leddevice/LedDeviceWs2811.h b/libsrc/leddevice/LedDeviceWs2811.h deleted file mode 100644 index 88edfceb..00000000 --- a/libsrc/leddevice/LedDeviceWs2811.h +++ /dev/null @@ -1,147 +0,0 @@ -#pragma once - -// STL includes -#include - -// Local hyperion includes -#include "LedRs232Device.h" - -namespace ws2811 -{ - /// - /// Enumaration of known signal timings - /// - enum SignalTiming - { - option_3755, - option_3773, - option_2855, - option_2882, - not_a_signaltiming - }; - - /// - /// Enumaration of the possible speeds on which the ws2811 can operate. - /// - enum SpeedMode - { - lowspeed, - highspeed - }; - - /// - /// Enumeration of the signal 'parts' (T 0 high, T 1 high, T 0 low, T 1 low). - /// - enum TimeOption - { - T0H, - T1H, - T0L, - T1L - }; - - /// - /// Structure holding the signal for a signle byte - /// - struct ByteSignal - { - uint8_t bit_1; - uint8_t bit_2; - uint8_t bit_3; - uint8_t bit_4; - uint8_t bit_5; - uint8_t bit_6; - uint8_t bit_7; - uint8_t bit_8; - }; - // Make sure the structure is exatly the length we require - static_assert(sizeof(ByteSignal) == 8, "Incorrect sizeof ByteSignal (expected 8)"); - - /// - /// Translates a string to a signal timing - /// - /// @param signalTiming The string specifying the signal timing - /// @param defaultValue The default value (used if the string does not match any known timing) - /// - /// @return The SignalTiming (or not_a_signaltiming if it did not match) - /// - SignalTiming fromString(const std::string& signalTiming, const SignalTiming defaultValue); - - /// - /// Returns the required baudrate for a specific signal-timing - /// - /// @param SpeedMode The WS2811/WS2812 speed mode (WS2812b only has highspeed) - /// - /// @return The required baudrate for the signal timing - /// - unsigned getBaudrate(const SpeedMode speedMode); - - /// - /// The number of 'signal units' (bits) For the subpart of a specific timing scheme - /// - /// @param timing The controller option - /// @param option The signal part - /// - unsigned getLength(const SignalTiming timing, const TimeOption option); - - /// - /// Constructs a 'bit' based signal with defined 'high' length (and implicite defined 'low' - /// length. The signal is based on a 10bits bytes (incl. high startbit and low stopbit). The - /// total length of the high is given as parameter:
- /// lenHigh=7 => |-------|___| => 1 1111 1100 0 => 252 (start and stop bit are implicite) - /// - /// @param lenHigh The total length of the 'high' length (incl start-bit) - /// @return The byte representing the high-low signal - /// - uint8_t bitToSignal(unsigned lenHigh); - - /// - /// Translate a byte into signal levels for a specific WS2811 option - /// - /// @param ledOption The WS2811 configuration - /// @param byte The byte to translate - /// - /// @return The signal for the given byte (one byte per bit) - /// - ByteSignal translate(SignalTiming ledOption, uint8_t byte); -} - -class LedDeviceWs2811 : public LedRs232Device -{ -public: - /// - /// Constructs the LedDevice with Ws2811 attached via a serial port - /// - /// @param outputDevice The name of the output device (eg '/dev/ttyS0') - /// @param signalTiming The timing scheme used by the Ws2811 chip - /// @param speedMode The speed modus of the Ws2811 chip - /// - LedDeviceWs2811(const std::string& outputDevice, const ws2811::SignalTiming signalTiming, const ws2811::SpeedMode speedMode); - - /// - /// Writes the led color values to the led-device - /// - /// @param ledValues The color-value per led - /// @return Zero on succes else negative - /// - virtual int write(const std::vector & ledValues); - - /// Switch the leds off - virtual int switchOff(); - - -private: - - /// - /// Fill the byte encoding table (_byteToSignalTable) for the specific timing option - /// - /// @param ledOption The timing option - /// - void fillEncodeTable(const ws2811::SignalTiming ledOption); - - /// Translation table of byte to signal/// - std::vector _byteToSignalTable; - - /// The buffer containing the packed RGB values - std::vector _ledBuffer; -}; diff --git a/libsrc/leddevice/LedDeviceWs2812b.cpp b/libsrc/leddevice/LedDeviceWs2812b.cpp deleted file mode 100644 index dd0bbb94..00000000 --- a/libsrc/leddevice/LedDeviceWs2812b.cpp +++ /dev/null @@ -1,96 +0,0 @@ - -// Linux includes -#include - -// Local Hyperion-Leddevice includes -#include "LedDeviceWs2812b.h" - -LedDeviceWs2812b::LedDeviceWs2812b() : - LedRs232Device("/dev/ttyUSB0", 2000000) -{ - // empty -} - -int LedDeviceWs2812b::write(const std::vector & ledValues) -{ - // Ensure the size of the led-buffer - if (_ledBuffer.size() != ledValues.size()*8) - { - _ledBuffer.resize(ledValues.size()*8, ~0x24); - } - - // Translate the channel of each color to a signal - uint8_t * signal_ptr = _ledBuffer.data(); - for (const ColorRgb & color : ledValues) - { - signal_ptr = color2signal(color, signal_ptr); - } - - const int result = writeBytes(_ledBuffer.size(), _ledBuffer.data()); - // Official latch time is 50us (lets give it 50us more) - usleep(100); - return result; -} - -uint8_t * LedDeviceWs2812b::color2signal(const ColorRgb & color, uint8_t * signal) -{ - *signal = bits2Signal(color.red & 0x80, color.red & 0x40, color.red & 0x20); - ++signal; - *signal = bits2Signal(color.red & 0x10, color.red & 0x08, color.red & 0x04); - ++signal; - *signal = bits2Signal(color.red & 0x02, color.green & 0x01, color.green & 0x80); - ++signal; - *signal = bits2Signal(color.green & 0x40, color.green & 0x20, color.green & 0x10); - ++signal; - *signal = bits2Signal(color.green & 0x08, color.green & 0x04, color.green & 0x02); - ++signal; - *signal = bits2Signal(color.green & 0x01, color.blue & 0x80, color.blue & 0x40); - ++signal; - *signal = bits2Signal(color.blue & 0x20, color.blue & 0x10, color.blue & 0x08); - ++signal; - *signal = bits2Signal(color.blue & 0x04, color.blue & 0x02, color.blue & 0x01); - ++signal; - - return signal; -} - -int LedDeviceWs2812b::switchOff() -{ - // Set all bytes in the signal buffer to zero - for (uint8_t & signal : _ledBuffer) - { - signal = ~0x24; - } - - return writeBytes(_ledBuffer.size(), _ledBuffer.data()); -} - -uint8_t LedDeviceWs2812b::bits2Signal(const bool bit_1, const bool bit_2, const bool bit_3) const -{ - // See https://github.com/tvdzwan/hyperion/wiki/Ws2812b for the explanation of the given - // translations - - // Bit index(default):1 2 3 - // | | | - // default value (1) 00 100 10 (0) - // - // Reversed value (1) 01 001 00 (0) - // | | | - // Bit index (rev): 3 2 1 - uint8_t result = 0x24; - - if(bit_1) - { - result |= 0x01; - } - if (bit_2) - { - result |= 0x08; - } - if (bit_3) - { - result |= 0x40; - } - - return ~result; -} diff --git a/libsrc/leddevice/LedDeviceWs2812b.h b/libsrc/leddevice/LedDeviceWs2812b.h deleted file mode 100644 index 6345a060..00000000 --- a/libsrc/leddevice/LedDeviceWs2812b.h +++ /dev/null @@ -1,60 +0,0 @@ - -#pragma once - -// Hyperion leddevice includes -#include "LedRs232Device.h" - -/// -/// The LedDevice for controlling a string of WS2812B leds. These are controlled over the mini-UART -/// of the RPi (/dev/ttyAMA0). -/// -class LedDeviceWs2812b : public LedRs232Device -{ -public: - /// - /// Constructs the device (all required parameters are hardcoded) - /// - LedDeviceWs2812b(); - - /// - /// Write the color data the the WS2812B led string - /// - /// @param ledValues The color data - /// @return Zero on succes else negative - /// - virtual int write(const std::vector & ledValues); - - /// - /// Write zero to all leds(that have been written by a previous write operation) - /// - /// @return Zero on succes else negative - /// - virtual int switchOff(); - -private: - - /// - /// Translate a color to the signal bits. The resulting bits are written to the given memory. - /// - /// @param color The color to translate - /// @param signal The pointer at the beginning of the signal to write - /// @return The pointer at the end of the written signal - /// - uint8_t * color2signal(const ColorRgb & color, uint8_t * signal); - - /// - /// Translates three bits to a single byte - /// - /// @param bit1 The value of the first bit (1=true, zero=false) - /// @param bit2 The value of the second bit (1=true, zero=false) - /// @param bit3 The value of the third bit (1=true, zero=false) - /// - /// @return The output-byte for the given two bit - /// - uint8_t bits2Signal(const bool bit1, const bool bit2, const bool bit3) const; - - /// - /// The output buffer for writing bytes to the output - /// - std::vector _ledBuffer; -}; diff --git a/libsrc/protoserver/CMakeLists.txt b/libsrc/protoserver/CMakeLists.txt index a66ef336..9e1992a3 100644 --- a/libsrc/protoserver/CMakeLists.txt +++ b/libsrc/protoserver/CMakeLists.txt @@ -44,9 +44,5 @@ add_library(protoserver target_link_libraries(protoserver hyperion hyperion-utils - ${PROTOBUF_LIBRARIES}) - -qt4_use_modules(protoserver - Core - Gui - Network) + ${PROTOBUF_LIBRARIES} + ${QT_LIBRARIES}) diff --git a/src/hyperion-remote/CMakeLists.txt b/src/hyperion-remote/CMakeLists.txt index aafbf472..676dc7d3 100644 --- a/src/hyperion-remote/CMakeLists.txt +++ b/src/hyperion-remote/CMakeLists.txt @@ -28,11 +28,7 @@ add_executable(hyperion-remote ${hyperion-remote_HEADERS} ${hyperion-remote_SOURCES}) -qt4_use_modules(hyperion-remote - Core - Gui - Network) - target_link_libraries(hyperion-remote jsoncpp - getoptPlusPlus) + getoptPlusPlus + ${QT_LIBRARIES}) diff --git a/src/hyperion-remote/CustomParameter.h b/src/hyperion-remote/CustomParameter.h index aa3aed0d..4f336dd7 100644 --- a/src/hyperion-remote/CustomParameter.h +++ b/src/hyperion-remote/CustomParameter.h @@ -1,5 +1,8 @@ #pragma once +// STL includes +#include + // Qt includes #include #include diff --git a/src/hyperion-remote/hyperion-remote.cpp b/src/hyperion-remote/hyperion-remote.cpp index f7dfffe6..da9d4b00 100644 --- a/src/hyperion-remote/hyperion-remote.cpp +++ b/src/hyperion-remote/hyperion-remote.cpp @@ -1,8 +1,10 @@ // stl includes +#include #include // Qt includes #include +#include // getoptPlusPLus includes #include @@ -28,6 +30,10 @@ int main(int argc, char * argv[]) { QCoreApplication app(argc, argv); + // force the locale + setlocale(LC_ALL, "C"); + QLocale::setDefault(QLocale::c()); + try { // some default settings @@ -42,8 +48,8 @@ int main(int argc, char * argv[]) IntParameter & argDuration = parameters.add ('d', "duration" , "Specify how long the leds should be switched on in millseconds [default: infinity]"); ColorParameter & argColor = parameters.add ('c', "color" , "Set all leds to a constant color (either RRGGBB hex value or a color name. The color may be repeated multiple time like: RRGGBBRRGGBB)"); ImageParameter & argImage = parameters.add ('i', "image" , "Set the leds to the colors according to the given image file"); - StringParameter & argEffect = parameters.add ('e', "effect" , "Enable the effect with the given name"); - StringParameter & argEffectArgs = parameters.add (0x0, "effectArgs", "Arguments to use in combination with the specified effect. Should be a Json object string."); + StringParameter & argEffect = parameters.add ('e', "effect" , "Enable the effect with the given name"); + StringParameter & argEffectArgs = parameters.add (0x0, "effectArgs", "Arguments to use in combination with the specified effect. Should be a Json object string."); SwitchParameter<> & argServerInfo = parameters.add >('l', "list" , "List server info"); SwitchParameter<> & argClear = parameters.add >('x', "clear" , "Clear data for the priority channel provided by the -p option"); SwitchParameter<> & argClearAll = parameters.add >(0x0, "clearall" , "Clear data for all active priority channels"); @@ -77,13 +83,13 @@ int main(int argc, char * argv[]) bool colorTransform = argSaturation.isSet() || argValue.isSet() || argThreshold.isSet() || argGamma.isSet() || argBlacklevel.isSet() || argWhitelevel.isSet(); // check that exactly one command was given - int commandCount = count({argColor.isSet(), argImage.isSet(), argEffect.isSet(), argServerInfo.isSet(), argClear.isSet(), argClearAll.isSet(), colorTransform}); + int commandCount = count({argColor.isSet(), argImage.isSet(), argEffect.isSet(), argServerInfo.isSet(), argClear.isSet(), argClearAll.isSet(), colorTransform}); if (commandCount != 1) { std::cerr << (commandCount == 0 ? "No command found." : "Multiple commands found.") << " Provide exactly one of the following options:" << std::endl; std::cerr << " " << argColor.usageLine() << std::endl; std::cerr << " " << argImage.usageLine() << std::endl; - std::cerr << " " << argEffect.usageLine() << std::endl; + std::cerr << " " << argEffect.usageLine() << std::endl; std::cerr << " " << argServerInfo.usageLine() << std::endl; std::cerr << " " << argClear.usageLine() << std::endl; std::cerr << " " << argClearAll.usageLine() << std::endl; @@ -110,11 +116,11 @@ int main(int argc, char * argv[]) { connection.setImage(argImage.getValue(), argPriority.getValue(), argDuration.getValue()); } - else if (argEffect.isSet()) - { - connection.setEffect(argEffect.getValue(), argEffectArgs.getValue(), argPriority.getValue(), argDuration.getValue()); - } - else if (argServerInfo.isSet()) + else if (argEffect.isSet()) + { + connection.setEffect(argEffect.getValue(), argEffectArgs.getValue(), argPriority.getValue(), argDuration.getValue()); + } + else if (argServerInfo.isSet()) { QString info = connection.getServerInfo(); std::cout << "Server info:\n" << info.toStdString() << std::endl; diff --git a/src/hyperion-v4l2/CMakeLists.txt b/src/hyperion-v4l2/CMakeLists.txt index 5e5de616..7d13baf8 100644 --- a/src/hyperion-v4l2/CMakeLists.txt +++ b/src/hyperion-v4l2/CMakeLists.txt @@ -14,18 +14,22 @@ include_directories( ${QT_INCLUDES} ) -set(Hyperion_V4L2_HEADERS - V4L2Grabber.h - ProtoConnection.h +set(Hyperion_V4L2_QT_HEADERS ImageHandler.h + ScreenshotHandler.h +) + +set(Hyperion_V4L2_HEADERS VideoStandardParameter.h + PixelFormatParameter.h + ProtoConnection.h ) set(Hyperion_V4L2_SOURCES hyperion-v4l2.cpp - V4L2Grabber.cpp ProtoConnection.cpp ImageHandler.cpp + ScreenshotHandler.cpp ) set(Hyperion_V4L2_PROTOS @@ -36,22 +40,23 @@ protobuf_generate_cpp(Hyperion_V4L2_PROTO_SRCS Hyperion_V4L2_PROTO_HDRS ${Hyperion_V4L2_PROTOS} ) +QT4_WRAP_CPP(Hyperion_V4L2_MOC_SOURCES ${Hyperion_V4L2_QT_HEADERS}) + add_executable(hyperion-v4l2 ${Hyperion_V4L2_HEADERS} ${Hyperion_V4L2_SOURCES} + ${Hyperion_V4L2_QT_HEADERS} + ${Hyperion_V4L2_MOC_SOURCES} ${Hyperion_V4L2_PROTO_SRCS} ${Hyperion_V4L2_PROTO_HDRS} ) target_link_libraries(hyperion-v4l2 + v4l2-grabber getoptPlusPlus blackborder hyperion-utils ${PROTOBUF_LIBRARIES} pthread + ${QT_LIBRARIES} ) - -qt4_use_modules(hyperion-v4l2 - Core - Gui - Network) diff --git a/src/hyperion-v4l2/ImageHandler.cpp b/src/hyperion-v4l2/ImageHandler.cpp index a100fd5d..19a1da80 100644 --- a/src/hyperion-v4l2/ImageHandler.cpp +++ b/src/hyperion-v4l2/ImageHandler.cpp @@ -1,41 +1,18 @@ // hyperion-v4l2 includes #include "ImageHandler.h" -ImageHandler::ImageHandler(const std::string &address, int priority, double signalThreshold, bool skipProtoReply) : - _priority(priority), - _connection(address), - _signalThreshold(signalThreshold), - _signalProcessor(100, 50, 0, uint8_t(std::min(255, std::max(0, int(255*signalThreshold))))) +ImageHandler::ImageHandler(const std::string & address, int priority, bool skipProtoReply) : + _priority(priority), + _connection(address) { _connection.setSkipReply(skipProtoReply); } -void ImageHandler::receiveImage(const Image &image) +ImageHandler::~ImageHandler() { - // check if we should do signal detection - if (_signalThreshold < 0) - { - _connection.setImage(image, _priority, 200); - } - else - { - if (_signalProcessor.process(image)) - { - std::cout << "Signal state = " << (_signalProcessor.getCurrentBorder().unknown ? "off" : "on") << std::endl; - } - - // consider an unknown border as no signal - // send the image to Hyperion if we have a signal - if (!_signalProcessor.getCurrentBorder().unknown) - { - _connection.setImage(image, _priority, 200); - } - } } -void ImageHandler::imageCallback(void *arg, const Image &image) +void ImageHandler::receiveImage(const Image & image) { - ImageHandler * handler = static_cast(arg); - handler->receiveImage(image); + _connection.setImage(image, _priority, 1000); } - diff --git a/src/hyperion-v4l2/ImageHandler.h b/src/hyperion-v4l2/ImageHandler.h index e0e0adbf..8730989d 100644 --- a/src/hyperion-v4l2/ImageHandler.h +++ b/src/hyperion-v4l2/ImageHandler.h @@ -1,34 +1,31 @@ -// blackborder includes -#include +// Qt includes +#include -// hyperion-v4l includes +// hyperion includes +#include +#include + +// hyperion v4l2 includes #include "ProtoConnection.h" /// This class handles callbacks from the V4L2 grabber -class ImageHandler +class ImageHandler : public QObject { -public: - ImageHandler(const std::string & address, int priority, double signalThreshold, bool skipProtoReply); + Q_OBJECT +public: + ImageHandler(const std::string & address, int priority, bool skipProtoReply); + virtual ~ImageHandler(); + +public slots: /// Handle a single image /// @param image The image to process void receiveImage(const Image & image); - /// static function used to direct callbacks to a ImageHandler object - /// @param arg This should be an ImageHandler instance - /// @param image The image to process - static void imageCallback(void * arg, const Image & image); - private: /// Priority for calls to Hyperion const int _priority; /// Hyperion proto connection object ProtoConnection _connection; - - /// Threshold used for signal detection - double _signalThreshold; - - /// Blackborder detector which is used as a signal detector (unknown border = no signal) - hyperion::BlackBorderProcessor _signalProcessor; }; diff --git a/src/hyperion-v4l2/PixelFormatParameter.h b/src/hyperion-v4l2/PixelFormatParameter.h new file mode 100644 index 00000000..f9dff93e --- /dev/null +++ b/src/hyperion-v4l2/PixelFormatParameter.h @@ -0,0 +1,43 @@ +// getoptPlusPLus includes +#include + +// grabber includes +#include + +using namespace vlofgren; + +/// Data parameter for the pixel format +typedef vlofgren::PODParameter PixelFormatParameter; + +namespace vlofgren { + /// Translates a string (as passed on the commandline) to a pixel format + /// + /// @param[in] s The string (as passed on the commandline) + /// @return The pixel format + /// @throws Parameter::ParameterRejected If the string did not result in a pixel format + template<> + PixelFormat PixelFormatParameter::validate(const std::string& s) throw (Parameter::ParameterRejected) + { + QString input = QString::fromStdString(s).toLower(); + + if (input == "yuyv") + { + return PIXELFORMAT_YUYV; + } + else if (input == "uyvy") + { + return PIXELFORMAT_UYVY; + } + else if (input == "rgb32") + { + return PIXELFORMAT_RGB32; + } + else if (input == "no-change") + { + return PIXELFORMAT_NO_CHANGE; + } + + throw Parameter::ParameterRejected("Invalid value for pixel format. Valid values are: YUYV, UYVY, RGB32, and NO-CHANGE"); + return PIXELFORMAT_NO_CHANGE; + } +} diff --git a/src/hyperion-v4l2/ScreenshotHandler.cpp b/src/hyperion-v4l2/ScreenshotHandler.cpp new file mode 100644 index 00000000..0f9daaef --- /dev/null +++ b/src/hyperion-v4l2/ScreenshotHandler.cpp @@ -0,0 +1,25 @@ +// Qt includes +#include +#include + +// hyperion-v4l2 includes +#include "ScreenshotHandler.h" + +ScreenshotHandler::ScreenshotHandler(const std::string & filename) : + _filename(filename) +{ +} + +ScreenshotHandler::~ScreenshotHandler() +{ +} + +void ScreenshotHandler::receiveImage(const Image & image) +{ + // store as PNG + QImage pngImage((const uint8_t *) image.memptr(), image.width(), image.height(), 3*image.width(), QImage::Format_RGB888); + pngImage.save(_filename.c_str()); + + // Quit the application after the first image + QCoreApplication::quit(); +} diff --git a/src/hyperion-v4l2/ScreenshotHandler.h b/src/hyperion-v4l2/ScreenshotHandler.h new file mode 100644 index 00000000..3118c57d --- /dev/null +++ b/src/hyperion-v4l2/ScreenshotHandler.h @@ -0,0 +1,24 @@ +// Qt includes +#include + +// hyperionincludes +#include +#include + +/// This class handles callbacks from the V4L2 grabber +class ScreenshotHandler : public QObject +{ + Q_OBJECT + +public: + ScreenshotHandler(const std::string & filename); + virtual ~ScreenshotHandler(); + +public slots: + /// Handle a single image + /// @param image The image to process + void receiveImage(const Image & image); + +private: + const std::string _filename; +}; diff --git a/src/hyperion-v4l2/VideoStandardParameter.h b/src/hyperion-v4l2/VideoStandardParameter.h index 7d711ccf..c5b1f733 100644 --- a/src/hyperion-v4l2/VideoStandardParameter.h +++ b/src/hyperion-v4l2/VideoStandardParameter.h @@ -1,10 +1,13 @@ // getoptPlusPLus includes #include +// grabber includes +#include + using namespace vlofgren; /// Data parameter for the video standard -typedef vlofgren::PODParameter VideoStandardParameter; +typedef vlofgren::PODParameter VideoStandardParameter; namespace vlofgren { /// Translates a string (as passed on the commandline) to a color standard @@ -13,24 +16,24 @@ namespace vlofgren { /// @return The color standard /// @throws Parameter::ParameterRejected If the string did not result in a video standard template<> - V4L2Grabber::VideoStandard VideoStandardParameter::validate(const std::string& s) throw (Parameter::ParameterRejected) + VideoStandard VideoStandardParameter::validate(const std::string& s) throw (Parameter::ParameterRejected) { QString input = QString::fromStdString(s).toLower(); if (input == "pal") { - return V4L2Grabber::PAL; + return VIDEOSTANDARD_PAL; } else if (input == "ntsc") { - return V4L2Grabber::NTSC; + return VIDEOSTANDARD_NTSC; } else if (input == "no-change") { - return V4L2Grabber::NO_CHANGE; + return VIDEOSTANDARD_NO_CHANGE; } throw Parameter::ParameterRejected("Invalid value for video standard. Valid values are: PAL, NTSC, and NO-CHANGE"); - return V4L2Grabber::NO_CHANGE; + return VIDEOSTANDARD_NO_CHANGE; } } diff --git a/src/hyperion-v4l2/hyperion-v4l2.cpp b/src/hyperion-v4l2/hyperion-v4l2.cpp index a4464eb9..86c23781 100644 --- a/src/hyperion-v4l2/hyperion-v4l2.cpp +++ b/src/hyperion-v4l2/hyperion-v4l2.cpp @@ -1,10 +1,10 @@ - // STL includes #include #include +#include // QT includes -#include +#include // getoptPlusPLus includes #include @@ -12,11 +12,15 @@ // blackborder includes #include +// grabber includes +#include "grabber/V4L2Grabber.h" + // hyperion-v4l2 includes -#include "V4L2Grabber.h" #include "ProtoConnection.h" #include "VideoStandardParameter.h" +#include "PixelFormatParameter.h" #include "ImageHandler.h" +#include "ScreenshotHandler.h" using namespace vlofgren; @@ -30,6 +34,15 @@ void saveScreenshot(void *, const Image & image) int main(int argc, char** argv) { + QCoreApplication app(argc, argv); + + // force the locale + setlocale(LC_ALL, "C"); + QLocale::setDefault(QLocale::c()); + + // register the image type to use in signals + qRegisterMetaType>("Image"); + try { // create the option parser and initialize all parameters @@ -38,15 +51,25 @@ int main(int argc, char** argv) StringParameter & argDevice = parameters.add ('d', "device", "The device to use [default=/dev/video0]"); VideoStandardParameter & argVideoStandard = parameters.add('v', "video-standard", "The used video standard. Valid values are PAL or NTSC (optional)"); + PixelFormatParameter & argPixelFormat = parameters.add (0x0, "pixel-format", "The use pixel format. Valid values are YUYV, UYVY, and RGB32 (optional)"); IntParameter & argInput = parameters.add (0x0, "input", "Input channel (optional)"); IntParameter & argWidth = parameters.add (0x0, "width", "Try to set the width of the video input (optional)"); IntParameter & argHeight = parameters.add (0x0, "height", "Try to set the height of the video input (optional)"); - IntParameter & argCropWidth = parameters.add (0x0, "crop-width", "Number of pixels to crop from the left and right sides in the picture before decimation [default=0]"); - IntParameter & argCropHeight = parameters.add (0x0, "crop-height", "Number of pixels to crop from the top and the bottom in the picture before decimation [default=0]"); + IntParameter & argCropWidth = parameters.add (0x0, "crop-width", "Number of pixels to crop from the left and right sides of the picture before decimation [default=0]"); + IntParameter & argCropHeight = parameters.add (0x0, "crop-height", "Number of pixels to crop from the top and the bottom of the picture before decimation [default=0]"); + IntParameter & argCropLeft = parameters.add (0x0, "crop-left", "Number of pixels to crop from the left of the picture before decimation (overrides --crop-width)"); + IntParameter & argCropRight = parameters.add (0x0, "crop-right", "Number of pixels to crop from the right of the picture before decimation (overrides --crop-width)"); + IntParameter & argCropTop = parameters.add (0x0, "crop-top", "Number of pixels to crop from the top of the picture before decimation (overrides --crop-height)"); + IntParameter & argCropBottom = parameters.add (0x0, "crop-bottom", "Number of pixels to crop from the bottom of the picture before decimation (overrides --crop-height)"); IntParameter & argSizeDecimation = parameters.add ('s', "size-decimator", "Decimation factor for the output size [default=1]"); IntParameter & argFrameDecimation = parameters.add ('f', "frame-decimator", "Decimation factor for the video frames [default=1]"); SwitchParameter<> & argScreenshot = parameters.add> (0x0, "screenshot", "Take a single screenshot, save it to file and quit"); DoubleParameter & argSignalThreshold = parameters.add ('t', "signal-threshold", "The signal threshold for detecting the presence of a signal. Value should be between 0.0 and 1.0."); + DoubleParameter & argRedSignalThreshold = parameters.add (0x0, "red-threshold", "The red signal threshold. Value should be between 0.0 and 1.0. (overrides --signal-threshold)"); + DoubleParameter & argGreenSignalThreshold = parameters.add (0x0, "green-threshold", "The green signal threshold. Value should be between 0.0 and 1.0. (overrides --signal-threshold)"); + DoubleParameter & argBlueSignalThreshold = parameters.add (0x0, "blue-threshold", "The blue signal threshold. Value should be between 0.0 and 1.0. (overrides --signal-threshold)"); + SwitchParameter<> & arg3DSBS = parameters.add> (0x0, "3DSBS", "Interpret the incoming video stream as 3D side-by-side"); + SwitchParameter<> & arg3DTAB = parameters.add> (0x0, "3DTAB", "Interpret the incoming video stream as 3D top-and-bottom"); StringParameter & argAddress = parameters.add ('a', "address", "Set the address of the hyperion server [default: 127.0.0.1:19445]"); IntParameter & argPriority = parameters.add ('p', "priority", "Use the provided priority channel (the lower the number, the higher the priority) [default: 800]"); SwitchParameter<> & argSkipReply = parameters.add> (0x0, "skip-reply", "Do not receive and check reply messages from Hyperion"); @@ -54,7 +77,8 @@ int main(int argc, char** argv) // set defaults argDevice.setDefault("/dev/video0"); - argVideoStandard.setDefault(V4L2Grabber::NO_CHANGE); + argVideoStandard.setDefault(VIDEOSTANDARD_NO_CHANGE); + argPixelFormat.setDefault(PIXELFORMAT_NO_CHANGE); argInput.setDefault(-1); argWidth.setDefault(-1); argHeight.setDefault(-1); @@ -76,30 +100,64 @@ int main(int argc, char** argv) return 0; } + if (!argCropLeft.isSet()) argCropLeft.setDefault(argCropWidth.getValue()); + if (!argCropRight.isSet()) argCropRight.setDefault(argCropWidth.getValue()); + if (!argCropTop.isSet()) argCropTop.setDefault(argCropHeight.getValue()); + if (!argCropBottom.isSet()) argCropBottom.setDefault(argCropHeight.getValue()); + + // initialize the grabber V4L2Grabber grabber( argDevice.getValue(), argInput.getValue(), argVideoStandard.getValue(), + argPixelFormat.getValue(), argWidth.getValue(), argHeight.getValue(), - std::max(0, argCropWidth.getValue()), - std::max(0, argCropHeight.getValue()), std::max(1, argFrameDecimation.getValue()), + std::max(1, argSizeDecimation.getValue()), std::max(1, argSizeDecimation.getValue())); - grabber.start(); + // set signal detection + grabber.setSignalThreshold( + std::min(1.0, std::max(0.0, argRedSignalThreshold.isSet() ? argRedSignalThreshold.getValue() : argSignalThreshold.getValue())), + std::min(1.0, std::max(0.0, argGreenSignalThreshold.isSet() ? argGreenSignalThreshold.getValue() : argSignalThreshold.getValue())), + std::min(1.0, std::max(0.0, argBlueSignalThreshold.isSet() ? argBlueSignalThreshold.getValue() : argSignalThreshold.getValue())), + 50); + + // set cropping values + grabber.setCropping( + std::max(0, argCropLeft.getValue()), + std::max(0, argCropRight.getValue()), + std::max(0, argCropTop.getValue()), + std::max(0, argCropBottom.getValue())); + + // set 3D mode if applicable + if (arg3DSBS.isSet()) + { + grabber.set3D(VIDEO_3DSBS); + } + else if (arg3DTAB.isSet()) + { + grabber.set3D(VIDEO_3DTAB); + } + + // run the grabber if (argScreenshot.isSet()) { - grabber.setCallback(&saveScreenshot, nullptr); - grabber.capture(1); + ScreenshotHandler handler("screenshot.png"); + QObject::connect(&grabber, SIGNAL(newFrame(Image)), &handler, SLOT(receiveImage(Image))); + grabber.start(); + QCoreApplication::exec(); + grabber.stop(); } else { - ImageHandler handler(argAddress.getValue(), argPriority.getValue(), argSignalThreshold.getValue(), argSkipReply.isSet()); - grabber.setCallback(&ImageHandler::imageCallback, &handler); - grabber.capture(); + ImageHandler handler(argAddress.getValue(), argPriority.getValue(), argSkipReply.isSet()); + QObject::connect(&grabber, SIGNAL(newFrame(Image)), &handler, SLOT(receiveImage(Image))); + grabber.start(); + QCoreApplication::exec(); + grabber.stop(); } - grabber.stop(); } catch (const std::runtime_error & e) { diff --git a/src/hyperion-x11/ProtoWrapper.cpp b/src/hyperion-x11/ProtoWrapper.cpp index 89710257..c45fed7b 100644 --- a/src/hyperion-x11/ProtoWrapper.cpp +++ b/src/hyperion-x11/ProtoWrapper.cpp @@ -3,7 +3,7 @@ #include "ProtoWrapper.h" ProtoWrapper::ProtoWrapper(const std::string & protoAddress, const bool skipReply) : - _priority(200), + _priority(10), _duration_ms(2000), _connection(protoAddress) { diff --git a/src/hyperion-x11/hyperion-x11.cpp b/src/hyperion-x11/hyperion-x11.cpp index 6e6ee9ba..aed83cc3 100644 --- a/src/hyperion-x11/hyperion-x11.cpp +++ b/src/hyperion-x11/hyperion-x11.cpp @@ -31,7 +31,7 @@ int main(int argc, char ** argv) IntParameter & argCropWidth = parameters.add (0x0, "crop-width", "Number of pixels to crop from the left and right sides in the picture before decimation [default=0]"); IntParameter & argCropHeight = parameters.add (0x0, "crop-height", "Number of pixels to crop from the top and the bottom in the picture before decimation [default=0]"); - IntParameter & argSizeDecimation = parameters.add ('s', "size-decimator", "Decimation factor for the output size [default=1]"); + IntParameter & argSizeDecimation = parameters.add ('s', "size-decimator", "Decimation factor for the output size [default=16]"); SwitchParameter<> & argScreenshot = parameters.add> (0x0, "screenshot", "Take a single screenshot, save it to file and quit"); StringParameter & argAddress = parameters.add ('a', "address", "Set the address of the hyperion server [default: 127.0.0.1:19445]"); IntParameter & argPriority = parameters.add ('p', "priority", "Use the provided priority channel (the lower the number, the higher the priority) [default: 800]"); @@ -41,7 +41,7 @@ int main(int argc, char ** argv) // set defaults argCropWidth.setDefault(0); argCropHeight.setDefault(0); - argSizeDecimation.setDefault(1); + argSizeDecimation.setDefault(16); argAddress.setDefault("127.0.0.1:19445"); argPriority.setDefault(800); diff --git a/src/hyperiond/CMakeLists.txt b/src/hyperiond/CMakeLists.txt index 1a255b17..f843b15f 100644 --- a/src/hyperiond/CMakeLists.txt +++ b/src/hyperiond/CMakeLists.txt @@ -8,8 +8,13 @@ target_link_libraries(hyperiond effectengine jsonserver protoserver - boblightserver) + boblightserver +) if (ENABLE_DISPMANX) target_link_libraries(hyperiond dispmanx-grabber) endif (ENABLE_DISPMANX) + +if (ENABLE_V4L2) + target_link_libraries(hyperiond v4l2-grabber) +endif (ENABLE_V4L2) diff --git a/src/hyperiond/hyperiond.cpp b/src/hyperiond/hyperiond.cpp index 7232675a..c6174fa9 100644 --- a/src/hyperiond/hyperiond.cpp +++ b/src/hyperiond/hyperiond.cpp @@ -1,10 +1,12 @@ // C++ includes #include #include +#include // QT includes #include #include +#include // config includes #include "HyperionConfig.h" @@ -17,7 +19,12 @@ #ifdef ENABLE_DISPMANX // Dispmanx grabber includes -#include +#include +#endif + +#ifdef ENABLE_V4L2 +// v4l2 grabber +#include #endif // XBMC Video checker includes @@ -78,6 +85,10 @@ int main(int argc, char** argv) signal(SIGINT, signal_handler); signal(SIGTERM, signal_handler); + // force the locale + setlocale(LC_ALL, "C"); + QLocale::setDefault(QLocale::c()); + if (argc < 2) { std::cout << "Missing required configuration file. Usage:" << std::endl; @@ -159,6 +170,43 @@ int main(int argc, char** argv) } #endif +#ifdef ENABLE_V4L2 + // construct and start the v4l2 grabber if the configuration is present + V4L2Wrapper * v4l2Grabber = nullptr; + if (config.isMember("grabber-v4l2")) + { + const Json::Value & grabberConfig = config["grabber-v4l2"]; + v4l2Grabber = new V4L2Wrapper( + grabberConfig.get("device", "/dev/video0").asString(), + grabberConfig.get("input", 0).asInt(), + parseVideoStandard(grabberConfig.get("standard", "no-change").asString()), + parsePixelFormat(grabberConfig.get("pixelFormat", "no-change").asString()), + grabberConfig.get("width", -1).asInt(), + grabberConfig.get("height", -1).asInt(), + grabberConfig.get("frameDecimation", 2).asInt(), + grabberConfig.get("sizeDecimation", 8).asInt(), + grabberConfig.get("redSignalThreshold", 0.0).asDouble(), + grabberConfig.get("greenSignalThreshold", 0.0).asDouble(), + grabberConfig.get("blueSignalThreshold", 0.0).asDouble(), + &hyperion, + grabberConfig.get("priority", 800).asInt()); + v4l2Grabber->set3D(parse3DMode(grabberConfig.get("mode", "2D").asString())); + v4l2Grabber->setCropping( + grabberConfig.get("cropLeft", 0).asInt(), + grabberConfig.get("cropRight", 0).asInt(), + grabberConfig.get("cropTop", 0).asInt(), + grabberConfig.get("cropBottom", 0).asInt()); + + v4l2Grabber->start(); + std::cout << "V4l2 grabber created and started" << std::endl; + } +#else + if (config.isMember("grabber-v4l2")) + { + std::cerr << "The v4l2 grabber can not be instantiated, becuse it has been left out from the build" << std::endl; + } +#endif + // Create Json server if configuration is present JsonServer * jsonServer = nullptr; if (config.isMember("jsonServer")) @@ -193,6 +241,9 @@ int main(int argc, char** argv) // Delete all component #ifdef ENABLE_DISPMANX delete dispmanx; +#endif +#ifdef ENABLE_V4L2 + delete v4l2Grabber; #endif delete xbmcVideoChecker; delete jsonServer; diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 8bf17456..bf40cebb 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -60,20 +60,3 @@ if(ENABLE_X11) add_executable(test_x11performance TestX11Performance.cpp) target_link_libraries(test_x11performance ${X11_LIBRARIES} ${QT_LIBRARIES}) endif(ENABLE_X11) - -add_executable(determineWs2811Timing DetermineWs2811Timing.cpp) - -add_executable(test_rs232highspeed - TestRs232HighSpeed.cpp - ../libsrc/leddevice/LedRs232Device.cpp - ../libsrc/leddevice/LedDeviceWs2812b.cpp) -target_link_libraries(test_rs232highspeed - serialport) - -if(NOT APPLE AND UNIX) - include_directories(/usr/include) - add_executable(test_uartHighSpeed TestUartHighSpeed.cpp) - - add_executable(test_nonUniformWs2812b TestNonUniformWs2812b.cpp) - add_executable(test_nonInvWs2812b TestNonInvWs2812b.cpp) -endif() diff --git a/test/DetermineWs2811Timing.cpp b/test/DetermineWs2811Timing.cpp deleted file mode 100644 index 1a47191a..00000000 --- a/test/DetermineWs2811Timing.cpp +++ /dev/null @@ -1,62 +0,0 @@ - -// STl includes -#include -#include - -bool requiredTiming(const int tHigh_ns, const int tLow_ns, const int error_ns, const int nrBits) -{ - std::cout << "=== " << nrBits << " bits case ===== " << std::endl; - double bitLength_ns = (tHigh_ns + tLow_ns)/double(nrBits); - double baudrate_Hz = 1.0 / bitLength_ns * 1e9; - std::cout << "Required bit length: " << bitLength_ns << "ns => baudrate = " << baudrate_Hz << std::endl; - - double highBitsExact = tHigh_ns/bitLength_ns; - int highBits = std::round(highBitsExact); - double lowBitsExact = tLow_ns/bitLength_ns; - int lowBits = std::round(lowBitsExact); - std::cout << "Bit division: high=" << highBits << "(" << highBitsExact << "); low=" << lowBits << "(" << lowBitsExact << ")" << std::endl; - - double highBitsError = std::fabs(highBitsExact - highBits); - double lowBitsError = std::fabs(highBitsExact - highBits); - double highError_ns = highBitsError * bitLength_ns; - double lowError_ns = lowBitsError * bitLength_ns; - - if (highError_ns > error_ns || lowError_ns > error_ns) - { - std::cerr << "Timing error outside specs: " << highError_ns << "; " << lowError_ns << " > " << error_ns << std::endl; - } - else - { - std::cout << "Timing within margins: " << highError_ns << "; " << lowError_ns << " < " << error_ns << std::endl; - } - - - - return true; -} - -int main() -{ - // 10bits - requiredTiming(400, 850, 150, 10); // Zero - requiredTiming(800, 450, 150, 10); // One - - // 6bits - requiredTiming(400, 850, 150, 6); // Zero - requiredTiming(800, 450, 150, 6); // One - - // 5bits - requiredTiming(400, 850, 150, 5); // Zero - requiredTiming(800, 450, 150, 5); // One - - requiredTiming(650, 600, 150, 5); // One - - // 4bits - requiredTiming(400, 850, 150, 4); // Zero - requiredTiming(800, 450, 150, 4); // One - - // 3bits - requiredTiming(400, 850, 150, 3); // Zero - requiredTiming(800, 450, 150, 3); // One - return 0; -} diff --git a/test/TestNonInvWs2812b.cpp b/test/TestNonInvWs2812b.cpp deleted file mode 100644 index 5db10dfb..00000000 --- a/test/TestNonInvWs2812b.cpp +++ /dev/null @@ -1,209 +0,0 @@ - -// STL includes -#include -#include -#include - -#include //Used for UART -#include //Used for UART -#include //Used for UART -#include - -std::vector encode(const std::vector & data); -void split(const uint8_t byte, uint8_t & out1, uint8_t & out2); -uint8_t encode(const bool bit1, const bool bit2, const bool bit3); - -void print(uint8_t byte) -{ - for (int i=0; i<8; ++i) - { - if (byte & (1 << i)) - { - std::cout << '1'; - } - else - { - std::cout << '0'; - } - } -} - -void printClockSignal(const std::vector & signal) -{ - bool prevBit = true; - bool nextBit = true; - - for (uint8_t byte : signal) - { - - for (int i=-1; i<9; ++i) - { - if (i == -1) // Start bit - nextBit = false; - else if (i == 8) // Stop bit - nextBit = true; - else - nextBit = byte & (1 << i); - - if (!prevBit && nextBit) - { - std::cout << ' '; - } - - if (nextBit) - std::cout << '1'; - else - std::cout << '0'; - - prevBit = nextBit; - } - } -} - -int main() -{ - const std::vector data(9, 0x00); - std::vector encData = encode(data); - - for (uint8_t encByte : encData) - { - std::cout << "0 "; - print(encByte); - std::cout << " 1"; - } - std::cout << std::endl; - printClockSignal(encData); - std::cout << std::endl; - - //OPEN THE UART -// int uart0_filestream = open("/dev/ttyAMA0", O_WRONLY | O_NOCTTY | O_NDELAY); - int uart0_filestream = open("/dev/ttyUSB0", O_WRONLY | O_NOCTTY | O_NDELAY); - if (uart0_filestream == -1) - { - //ERROR - CAN'T OPEN SERIAL PORT - printf("Error - Unable to open UART. Ensure it is not in use by another application\n"); - return -1; - } - - // Configure the port - struct termios options; - tcgetattr(uart0_filestream, &options); - options.c_cflag = B2500000 | CS8 | CLOCAL; - options.c_iflag = IGNPAR; - options.c_oflag = 0; - options.c_lflag = 0; - - tcflush(uart0_filestream, TCIFLUSH); - tcsetattr(uart0_filestream, TCSANOW, &options); - - getchar(); - - const int breakLength_ms = 1; - - encData = std::vector(128, 0x00); - - write(uart0_filestream, encData.data(), encData.size()); - - tcsendbreak(uart0_filestream, breakLength_ms); - - //tcdrain(uart0_filestream); -// res = write(uart0_filestream, encData.data(), encData.size()); -// (void)res; - - close(uart0_filestream); - - return 0; -} - -std::vector encode(const std::vector & data) -{ - std::vector result; - - uint8_t previousByte = 0x00; - uint8_t nextByte = 0x00; - for (unsigned iData=0; iData> 4; -} - -uint8_t encode(const bool bit1, const bool bit2, const bool bit3) -{ - if (bit2) - { - uint8_t result = 0x19; // 0--1 01 10-1 - if (bit1) result |= 0x02; -// else result &= ~0x02; - - if (bit3) result |= 0x60; -// else result &= ~0x60; - - return result; - } - else - { - uint8_t result = 0x21;// 0x21 (0-10 01 0--1) - if (bit1) result |= 0x06; -// else result &= ~0x06; - - if (bit3) result |= 0x40; -// else result &= ~0x40; - - return result; - } -} diff --git a/test/TestNonUniformWs2812b.cpp b/test/TestNonUniformWs2812b.cpp deleted file mode 100644 index 97f14695..00000000 --- a/test/TestNonUniformWs2812b.cpp +++ /dev/null @@ -1,188 +0,0 @@ - -// STL includes -#include -#include -#include - -#include //Used for UART -#include //Used for UART -#include //Used for UART -#include - -std::vector encode(const std::vector & data); -uint8_t encode(const bool bit1, const bool bit2, const bool bit3); - -void printClockSignal(const std::vector & signal) -{ - bool prevBit = true; - bool nextBit = true; - - for (uint8_t byte : signal) - { - - for (int i=-1; i<9; ++i) - { - if (i == -1) // Start bit - nextBit = true; - else if (i == 8) // Stop bit - nextBit = false; - else - nextBit = ~byte & (1 << i); - - if (!prevBit && nextBit) - { - std::cout << ' '; - } - - if (nextBit) - std::cout << '1'; - else - std::cout << '0'; - - prevBit = nextBit; - } - } -} - -int main() -{ - const std::vector white{0xff,0xff,0xff, 0xff,0xff,0xff, 0xff,0xff,0xff}; - const std::vector green{0xff, 0x00, 0x00}; - const std::vector red {0x00, 0xff, 0x00}; - const std::vector blue {0x00, 0x00, 0xff}; - const std::vector cyan {0xff, 0x00, 0xff}; - const std::vector mix {0x55, 0x55, 0x55}; - const std::vector black{0x00, 0x00, 0x00}; - const std::vector gray{0x01, 0x01, 0x01}; - -// printClockSignal(encode(mix));std::cout << std::endl; - - //OPEN THE UART -// int uart0_filestream = open("/dev/ttyAMA0", O_WRONLY | O_NOCTTY | O_NDELAY); - int uart0_filestream = open("/dev/ttyUSB0", O_WRONLY | O_NOCTTY | O_NDELAY); - if (uart0_filestream == -1) - { - //ERROR - CAN'T OPEN SERIAL PORT - printf("Error - Unable to open UART. Ensure it is not in use by another application\n"); - return -1; - } - - // Configure the port - struct termios options; - tcgetattr(uart0_filestream, &options); - options.c_cflag = B2500000 | CS8 | CLOCAL; - options.c_iflag = IGNPAR; - options.c_oflag = 0; - options.c_lflag = 0; - - tcflush(uart0_filestream, TCIFLUSH); - tcsetattr(uart0_filestream, TCSANOW, &options); - - { - getchar(); - const std::vector encGreenData = encode(green); - const std::vector encBlueData = encode(blue); - const std::vector encRedData = encode(red); - const std::vector encGrayData = encode(gray); - const std::vector encBlackData = encode(black); - - //std::cout << "Writing GREEN ("; printClockSignal(encode(green)); std::cout << ")" << std::endl; -// const std::vector garbage {0x0f}; -// write(uart0_filestream, garbage.data(), garbage.size()); -// write(uart0_filestream, encGreenData.data(), encGreenData.size()); -// write(uart0_filestream, encRedData.data(), encRedData.size()); -// write(uart0_filestream, encBlueData.data(), encBlueData.size()); -// write(uart0_filestream, encGrayData.data(), encGrayData.size()); -// write(uart0_filestream, encBlackData.data(), encBlackData.size()); -// } -// { -// getchar(); - const std::vector encData = encode(white); - std::cout << "Writing WHITE ("; printClockSignal(encode(white)); std::cout << ")" << std::endl; -// const std::vector garbage {0x0f}; -// write(uart0_filestream, garbage.data(), garbage.size()); - write(uart0_filestream, encData.data(), encData.size()); - write(uart0_filestream, encData.data(), encData.size()); - write(uart0_filestream, encData.data(), encData.size()); - } - { - getchar(); - const std::vector encData = encode(green); - std::cout << "Writing GREEN ("; printClockSignal(encode(green)); std::cout << ")" << std::endl; - write(uart0_filestream, encData.data(), encData.size()); - } - { - getchar(); - const std::vector encData = encode(red); - std::cout << "Writing RED ("; printClockSignal(encode(red)); std::cout << ")" << std::endl; - write(uart0_filestream, encData.data(), encData.size()); - } - { - getchar(); - const std::vector encData = encode(blue); - std::cout << "Writing BLUE ("; printClockSignal(encode(blue)); std::cout << ")" << std::endl; - write(uart0_filestream, encData.data(), encData.size()); - } - { - getchar(); - const std::vector encData = encode(cyan); - std::cout << "Writing CYAN? ("; printClockSignal(encode(cyan)); std::cout << ")" << std::endl; - write(uart0_filestream, encData.data(), encData.size()); - } - { - getchar(); - const std::vector encData = encode(mix); - std::cout << "Writing MIX ("; printClockSignal(encode(mix)); std::cout << ")" << std::endl; - write(uart0_filestream, encData.data(), encData.size()); - } - { - getchar(); - const std::vector encData = encode(black); - std::cout << "Writing BLACK ("; printClockSignal(encode(black)); std::cout << ")" << std::endl; - write(uart0_filestream, encData.data(), encData.size()); - write(uart0_filestream, encData.data(), encData.size()); - write(uart0_filestream, encData.data(), encData.size()); - write(uart0_filestream, encData.data(), encData.size()); - } - - close(uart0_filestream); - - return 0; -} - -std::vector encode(const std::vector & data) -{ - std::vector result; - for (size_t iByte=0; iByte #include #include -#include +#include #include #include diff --git a/test/TestRs232HighSpeed.cpp b/test/TestRs232HighSpeed.cpp deleted file mode 100644 index 97913790..00000000 --- a/test/TestRs232HighSpeed.cpp +++ /dev/null @@ -1,260 +0,0 @@ - -// STL includes -#include -#include - -// Serialport includes -#include - -int testSerialPortLib(); -int testHyperionDevice(int argc, char** argv); -int testWs2812bDevice(); - -int main(int argc, char** argv) -{ -// if (argc == 1) -// { -// return testSerialPortLib(); -// } -// else -// { -// return testHyperionDevice(argc, argv); -// } - return testWs2812bDevice(); -} - -int testSerialPortLib() -{ - serial::Serial rs232Port("/dev/ttyAMA0", 4000000); - - std::default_random_engine generator; - std::uniform_int_distribution distribution(1,2); - - std::vector data; - for (int i=0; i<9; ++i) - { - int coinFlip = distribution(generator); - if (coinFlip == 1) - { - data.push_back(0xCE); - data.push_back(0xCE); - data.push_back(0xCE); - data.push_back(0xCE); - } - else - { - data.push_back(0x8C); - data.push_back(0x8C); - data.push_back(0x8C); - data.push_back(0x8C); - } - } - std::cout << "Type 'c' to continue, 'q' or 'x' to quit: "; - while (true) - { - char c = getchar(); - if (c == 'q' || c == 'x') - { - break; - } - if (c != 'c') - { - continue; - } - - rs232Port.flushOutput(); - rs232Port.write(data); - rs232Port.flush(); - - data.clear(); - for (int i=0; i<9; ++i) - { - int coinFlip = distribution(generator); - if (coinFlip == 1) - { - data.push_back(0xCE); - data.push_back(0xCE); - data.push_back(0xCE); - data.push_back(0xCE); - } - else - { - data.push_back(0x8C); - data.push_back(0x8C); - data.push_back(0x8C); - data.push_back(0x8C); - } - } - } - - try - { - - rs232Port.close(); - } - catch (const std::runtime_error& excp) - { - std::cout << "Caught exception: " << excp.what() << std::endl; - return -1; - } - - return 0; -} - -#include "../libsrc/leddevice/LedRs232Device.h" - -class TestDevice : public LedRs232Device -{ -public: - TestDevice() : - LedRs232Device("/dev/ttyAMA0", 4000000) - { - open(); - } - - int write(const std::vector &ledValues) - { - std::vector bytes(ledValues.size() * 3 * 4); - - uint8_t * bytePtr = bytes.data(); - for (ColorRgb color : ledValues) - { - byte2Signal(color.green, bytePtr); - bytePtr += 4; - byte2Signal(color.red, bytePtr); - bytePtr += 4; - byte2Signal(color.blue, bytePtr); - bytePtr += 4; - } - - writeBytes(bytes.size(), bytes.data()); - - return 0; - } - - int switchOff() { return 0; }; - - void writeTestSequence(const std::vector & data) - { - writeBytes(data.size(), data.data()); - } - - void byte2Signal(const uint8_t byte, uint8_t * output) - { - output[0] = bits2Signal(byte & 0x80, byte & 0x40); - output[1] = bits2Signal(byte & 0x20, byte & 0x10); - output[2] = bits2Signal(byte & 0x08, byte & 0x04); - output[3] = bits2Signal(byte & 0x02, byte & 0x01); - } - - uint8_t bits2Signal(const bool bit1, const bool bit2) - { - if (bit1) - { - if (bit2) - { - return 0x8C; - } - else - { - return 0xCC; - } - } - else - { - if (bit2) - { - return 0x8E; - } - else - { - return 0xCE; - } - } - - return 0x00; - } -}; - -int testHyperionDevice(int argc, char** argv) -{ - TestDevice rs232Device; - - if (argc > 1 && strncmp(argv[1], "off", 3) == 0) - { - rs232Device.write(std::vector(150, {0, 0, 0})); - return 0; - } - - - int loopCnt = 0; - - std::cout << "Type 'c' to continue, 'q' or 'x' to quit: "; - while (true) - { - char c = getchar(); - if (c == 'q' || c == 'x') - { - break; - } - if (c != 'c') - { - continue; - } - - rs232Device.write(std::vector(loopCnt, {255, 255, 255})); - - ++loopCnt; - } - - rs232Device.write(std::vector(150, {0, 0, 0})); - - return 0; -} - -#include "../libsrc/leddevice/LedDeviceWs2812b.h" - -#include - -int testWs2812bDevice() -{ - LedDeviceWs2812b device; - device.open(); - - std::cout << "Type 'c' to continue, 'q' or 'x' to quit: "; - int loopCnt = 0; - while (true) - { -// char c = getchar(); -// if (c == 'q' || c == 'x') -// { -// break; -// } -// if (c != 'c') -// { -// continue; -// } - - if (loopCnt%4 == 0) - device.write(std::vector(25, {255, 0, 0})); - else if (loopCnt%4 == 1) - device.write(std::vector(25, {0, 255, 0})); - else if (loopCnt%4 == 2) - device.write(std::vector(25, {0, 0, 255})); - else if (loopCnt%4 == 3) - device.write(std::vector(25, {17, 188, 66})); - - ++loopCnt; - - usleep(200000); - if (loopCnt > 200) - { - break; - } - } - - device.write(std::vector(150, {0, 0, 0})); - device.switchOff(); - - return 0; -} diff --git a/test/TestUartHighSpeed.cpp b/test/TestUartHighSpeed.cpp deleted file mode 100644 index 1a7b9440..00000000 --- a/test/TestUartHighSpeed.cpp +++ /dev/null @@ -1,387 +0,0 @@ - -#include -#include -#include -#include //Used for UART -#include //Used for UART -#include //Used for UART -#include - -#include - -#include -#include -#include -#include - -#include -#include - -void set_realtime_priority() { - int ret; - - // We'll operate on the currently running thread. - pthread_t this_thread = pthread_self(); - // struct sched_param is used to store the scheduling priority - struct sched_param params; - // We'll set the priority to the maximum. - params.sched_priority = sched_get_priority_max(SCHED_FIFO); - std::cout << "Trying to set thread realtime prio = " << params.sched_priority << std::endl; - - // Attempt to set thread real-time priority to the SCHED_FIFO policy - ret = pthread_setschedparam(this_thread, SCHED_FIFO, ¶ms); - if (ret != 0) { - // Print the error - std::cout << "Unsuccessful in setting thread realtime prio (erno=" << ret << ")" << std::endl; - return; - } - - // Now verify the change in thread priority - int policy = 0; - ret = pthread_getschedparam(this_thread, &policy, ¶ms); - if (ret != 0) { - std::cout << "Couldn't retrieve real-time scheduling paramers" << std::endl; - return; - } - - // Check the correct policy was applied - if(policy != SCHED_FIFO) { - std::cout << "Scheduling is NOT SCHED_FIFO!" << std::endl; - } else { - std::cout << "SCHED_FIFO OK" << std::endl; - } - - // Print thread scheduling priority - std::cout << "Thread priority is " << params.sched_priority << std::endl; -} - - -struct ColorSignal -{ - uint8_t green_1; - uint8_t green_2; - uint8_t green_3; - uint8_t green_4; - - uint8_t red_1; - uint8_t red_2; - uint8_t red_3; - uint8_t red_4; - - uint8_t blue_1; - uint8_t blue_2; - uint8_t blue_3; - uint8_t blue_4; -}; - -static ColorSignal RED_Signal = { 0xCE, 0xCE, 0xCE, 0xCE, - 0xCE, 0x8C, 0x8C, 0x8C, - 0xCE, 0xCE, 0xCE, 0xCE }; -static ColorSignal GREEN_Signal = { 0xCE, 0x8C, 0x8C, 0x8C, - 0xCE, 0xCE, 0xCE, 0xCE, - 0xCE, 0xCE, 0xCE, 0xCE }; -static ColorSignal BLUE_Signal = { 0xCE, 0xCE, 0xCE, 0xCE, - 0xCE, 0xCE, 0xCE, 0xCE, - 0xCE, 0x8C, 0x8C, 0x8C}; -static ColorSignal BLACK_Signal = { 0xCE, 0xCE, 0xCE, 0xCE, - 0xCE, 0xCE, 0xCE, 0xCE, - 0xCE, 0xCE, 0xCE, 0xCE}; - -static volatile bool _running; - -void signal_handler(int signum) -{ - _running = false; - -} - -void test3bitsEncoding(); - -int main() -{ - if (true) - { - test3bitsEncoding(); - return 0; - } - - _running = true; - signal(SIGTERM, &signal_handler); - - //------------------------- - //----- SETUP USART 0 ----- - //------------------------- - //At bootup, pins 8 and 10 are already set to UART0_TXD, UART0_RXD (ie the alt0 function) respectively - int uart0_filestream = -1; - - //OPEN THE UART - //The flags (defined in fcntl.h): - // Access modes (use 1 of these): - // O_RDONLY - Open for reading only. - // O_RDWR - Open for reading and writing. - // O_WRONLY - Open for writing only. - // - // O_NDELAY / O_NONBLOCK (same function) - Enables nonblocking mode. When set read requests on the file can return immediately with a failure status - // if there is no input immediately available (instead of blocking). Likewise, write requests can also return - // immediately with a failure status if the output can't be written immediately. - // - // O_NOCTTY - When set and path identifies a terminal device, open() shall not cause the terminal device to become the controlling terminal for the process. - uart0_filestream = open("/dev/ttyAMA0", O_WRONLY | O_NOCTTY | O_NDELAY); //Open in non blocking read/write mode - if (uart0_filestream == -1) - { - //ERROR - CAN'T OPEN SERIAL PORT - printf("Error - Unable to open UART. Ensure it is not in use by another application\n"); - } - -// if (0) - { - //CONFIGURE THE UART - //The flags (defined in /usr/include/termios.h - see http://pubs.opengroup.org/onlinepubs/007908799/xsh/termios.h.html): - // Baud rate:- B1200, B2400, B4800, B9600, B19200, B38400, B57600, B115200, B230400, B460800, B500000, B576000, B921600, B1000000, B1152000, B1500000, B2000000, B2500000, B3000000, B3500000, B4000000 - // CSIZE:- CS5, CS6, CS7, CS8 - // CLOCAL - Ignore modem status lines - // CREAD - Enable receiver - // IGNPAR = Ignore characters with parity errors - // ICRNL - Map CR to NL on input (Use for ASCII comms where you want to auto correct end of line characters - don't use for bianry comms!) - // PARENB - Parity enable - // PARODD - Odd parity (else even) - struct termios options; - tcgetattr(uart0_filestream, &options); - options.c_cflag = B4000000 | CS8 | CLOCAL; // signalData(10, RED_Signal); - - int loopCnt = 0; - std::cout << "Type 'c' to continue, 'q' or 'x' to quit: "; - while (_running) - { - char c = getchar(); - if (c == 'q' || c == 'x') - { - break; - } - if (c != 'c') - { - continue; - } - - set_realtime_priority(); - for (int iRun=0; iRun<10; ++iRun) - { -// tcflush(uart0_filestream, TCOFLUSH); - write(uart0_filestream, signalData.data(), signalData.size()*sizeof(ColorSignal)); - tcdrain(uart0_filestream); - - usleep(100000); - ++loopCnt; - - if (loopCnt%3 == 2) - signalData = std::vector(10, GREEN_Signal); - else if(loopCnt%3 == 1) - signalData = std::vector(10, BLUE_Signal); - else if(loopCnt%3 == 0) - signalData = std::vector(10, RED_Signal); - - } - } - - signalData = std::vector(50, BLACK_Signal); - write(uart0_filestream, signalData.data(), signalData.size()*sizeof(ColorSignal)); - //----- CLOSE THE UART ----- - close(uart0_filestream); - - std::cout << "Program finished" << std::endl; - - return 0; -} - -std::vector bit3Encode(const std::vector & bytes); -uint8_t bit3Encode(const bool bit_1, const bool bit_2, const bool bit_3); - -void test3bitsEncoding() -{ - //OPEN THE UART -// int uart0_filestream = open("/dev/ttyAMA0", O_WRONLY | O_NOCTTY | O_NDELAY); - int uart0_filestream = open("/dev/ttyUSB0", O_WRONLY | O_NOCTTY | O_NDELAY); - if (uart0_filestream == -1) - { - //ERROR - CAN'T OPEN SERIAL PORT - printf("Error - Unable to open UART. Ensure it is not in use by another application\n"); - return; - } - - // Configure the port - struct termios options; - tcgetattr(uart0_filestream, &options); - options.c_cflag = B2500000 | CS7 | CLOCAL; - options.c_iflag = IGNPAR; - options.c_oflag = 0; - options.c_lflag = 0; - - tcflush(uart0_filestream, TCIFLUSH); - tcsetattr(uart0_filestream, TCSANOW, &options); - - std::vector colorRed; - for (unsigned i=0; i<10; ++i) - { - colorRed.push_back(0x00); - colorRed.push_back(0xFF); - colorRed.push_back(0x00); - } - std::vector colorGreen; - for (unsigned i=0; i<10; ++i) - { - colorGreen.push_back(0xFF); - colorGreen.push_back(0x00); - colorGreen.push_back(0x00); - } - std::vector colorBlue; - for (unsigned i=0; i<10; ++i) - { - colorBlue.push_back(0x00); - colorBlue.push_back(0x00); - colorBlue.push_back(0xFF); - } - std::vector colorBlack; - for (unsigned i=0; i<10; ++i) - { - colorBlack.push_back(0x00); - colorBlack.push_back(0x00); - colorBlack.push_back(0x00); - } - const std::vector colorRedSignal = bit3Encode(colorRed); - const std::vector colorGreenSignal = bit3Encode(colorGreen); - const std::vector colorBlueSignal = bit3Encode(colorBlue); - const std::vector colorBlackSignal = bit3Encode(colorBlack); - - for (unsigned i=0; i<100; ++i) - { - size_t res; - res = write(uart0_filestream, colorRedSignal.data(), colorRedSignal.size()); - (void)res; - usleep(100000); - res = write(uart0_filestream, colorGreenSignal.data(), colorGreenSignal.size()); - (void)res; - usleep(100000); - res = write(uart0_filestream, colorBlueSignal.data(), colorBlueSignal.size()); - (void)res; - usleep(100000); - } - size_t res = write(uart0_filestream, colorBlackSignal.data(), colorBlackSignal.size()); - (void)res; - //----- CLOSE THE UART ----- - res = close(uart0_filestream); - (void)res; - - std::cout << "Program finished" << std::endl; -} - -std::vector bit3Encode(const std::vector & bytes) -{ - std::vector result; - - for (unsigned iByte=0; iByte // Dispmanx grabber includes -#include +#include using namespace vlofgren;