mirror of
https://github.com/hyperion-project/hyperion.ng.git
synced 2023-10-10 13:36:59 +02:00
Merge branch 'master' of https://github.com/tvdzwan/hyperion
Conflicts: CrossCompileHowto.txt Former-commit-id: a65ec1f7e9db8aa17d73351d8cd7b78d0399f8dd
This commit is contained in:
commit
23cd4bea0f
3
.gitignore
vendored
3
.gitignore
vendored
@ -1,5 +1,4 @@
|
|||||||
/*.user
|
/*.user
|
||||||
/build
|
/build*
|
||||||
/build-x86
|
|
||||||
.DS_Store
|
.DS_Store
|
||||||
|
|
||||||
|
3
.gitmodules
vendored
Normal file
3
.gitmodules
vendored
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
[submodule "dependencies/external/protobuf"]
|
||||||
|
path = dependencies/external/protobuf
|
||||||
|
url = https://github.com/tvdzwan/protobuf.git
|
@ -13,12 +13,25 @@ message(STATUS "ENABLE_DISPMANX = " ${ENABLE_DISPMANX})
|
|||||||
option(ENABLE_SPIDEV "Enable the SPIDEV device" ON)
|
option(ENABLE_SPIDEV "Enable the SPIDEV device" ON)
|
||||||
message(STATUS "ENABLE_SPIDEV = " ${ENABLE_SPIDEV})
|
message(STATUS "ENABLE_SPIDEV = " ${ENABLE_SPIDEV})
|
||||||
|
|
||||||
|
option(ENABLE_WS2812BPWM "Enable the WS2812b-PWM device" OFF)
|
||||||
|
message(STATUS "ENABLE_WS2812BPWM = " ${ENABLE_WS2812BPWM})
|
||||||
|
|
||||||
option(ENABLE_V4L2 "Enable the V4L2 grabber" ON)
|
option(ENABLE_V4L2 "Enable the V4L2 grabber" ON)
|
||||||
message(STATUS "ENABLE_V4L2 = " ${ENABLE_V4L2})
|
message(STATUS "ENABLE_V4L2 = " ${ENABLE_V4L2})
|
||||||
|
|
||||||
|
option(ENABLE_X11 "Enable the X11 grabber" OFF)
|
||||||
|
message(STATUS "ENABLE_X11 = " ${ENABLE_X11})
|
||||||
|
|
||||||
option(ENABLE_TINKERFORGE "Enable the TINKERFORGE device" ON)
|
option(ENABLE_TINKERFORGE "Enable the TINKERFORGE device" ON)
|
||||||
message(STATUS "ENABLE_TINKERFORGE = " ${ENABLE_TINKERFORGE})
|
message(STATUS "ENABLE_TINKERFORGE = " ${ENABLE_TINKERFORGE})
|
||||||
|
|
||||||
|
option(ENABLE_PROTOBUF "Enable PROTOBUF server" ON)
|
||||||
|
message(STATUS "ENABLE_PROTOBUF = " ${ENABLE_PROTOBUF})
|
||||||
|
|
||||||
|
if(ENABLE_V4L2 AND NOT ENABLE_PROTOBUF)
|
||||||
|
message(FATAL_ERROR "V4L2 grabber requires PROTOBUF. Disable V4L2 or enable PROTOBUF")
|
||||||
|
endif(ENABLE_V4L2 AND NOT ENABLE_PROTOBUF)
|
||||||
|
|
||||||
# Createt the configuration file
|
# Createt the configuration file
|
||||||
# configure a header file to pass some of the CMake settings
|
# configure a header file to pass some of the CMake settings
|
||||||
# to the source code
|
# to the source code
|
||||||
@ -44,8 +57,8 @@ include_directories(${CMAKE_SOURCE_DIR}/include)
|
|||||||
# Prefer static linking over dynamic
|
# Prefer static linking over dynamic
|
||||||
#set(CMAKE_FIND_LIBRARY_SUFFIXES ".a;.so")
|
#set(CMAKE_FIND_LIBRARY_SUFFIXES ".a;.so")
|
||||||
|
|
||||||
#set(CMAKE_BUILD_TYPE "Debug")
|
set(CMAKE_BUILD_TYPE "Debug")
|
||||||
set(CMAKE_BUILD_TYPE "Release")
|
#set(CMAKE_BUILD_TYPE "Release")
|
||||||
|
|
||||||
# enable C++11
|
# enable C++11
|
||||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++0x -Wall")
|
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++0x -Wall")
|
||||||
@ -54,13 +67,6 @@ set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++0x -Wall")
|
|||||||
# Configure the use of QT4
|
# Configure the use of QT4
|
||||||
find_package(Qt4 COMPONENTS QtCore QtGui QtNetwork REQUIRED QUIET)
|
find_package(Qt4 COMPONENTS QtCore QtGui QtNetwork REQUIRED QUIET)
|
||||||
|
|
||||||
# add protocol buffers (make sure to find the static version)
|
|
||||||
set(CMAKE_FIND_LIBRARY_SUFFIXES_OLD ${CMAKE_FIND_LIBRARY_SUFFIXES})
|
|
||||||
set(CMAKE_FIND_LIBRARY_SUFFIXES ".a")
|
|
||||||
find_package(Protobuf REQUIRED)
|
|
||||||
set(CMAKE_FIND_LIBRARY_SUFFIXES ${CMAKE_FIND_LIBRARY_SUFFIXES_OLD})
|
|
||||||
set(CMAKE_FIND_LIBRARY_SUFFIXES_OLD)
|
|
||||||
|
|
||||||
#add libusb and pthreads
|
#add libusb and pthreads
|
||||||
find_package(libusb-1.0 REQUIRED)
|
find_package(libusb-1.0 REQUIRED)
|
||||||
find_package(Threads REQUIRED)
|
find_package(Threads REQUIRED)
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
# Install the required tools and dependencies
|
# Install the required tools and dependencies
|
||||||
sudo apt-get update
|
sudo apt-get update
|
||||||
sudo apt-get install git cmake build-essential libprotobuf-dev libQt4-dev libusb-1.0-0-dev protobuf-compiler python-dev
|
sudo apt-get install git cmake build-essential libQt4-dev libusb-1.0-0-dev python-dev
|
||||||
|
|
||||||
# RPI ONLY: when you build on the rapberry pi and inlcude the dispmanx grabber (which is the default)
|
# RPI ONLY: when you build on the rapberry pi and inlcude the dispmanx grabber (which is the default)
|
||||||
# you also need the firmware including headers installed. This downloads the firmware from the raspberrypi github
|
# you also need the firmware including headers installed. This downloads the firmware from the raspberrypi github
|
||||||
@ -11,7 +11,7 @@ sudo cp -R "$FIRMWARE_DIR/hardfp/opt/*" /opt
|
|||||||
|
|
||||||
# create hyperion directory and checkout the code from github
|
# create hyperion directory and checkout the code from github
|
||||||
export HYPERION_DIR="hyperion"
|
export HYPERION_DIR="hyperion"
|
||||||
git clone https://github.com/tvdzwan/hyperion.git "$HYPERION_DIR"
|
git clone --recursive https://github.com/tvdzwan/hyperion.git "$HYPERION_DIR"
|
||||||
|
|
||||||
# create and enter the build directory
|
# create and enter the build directory
|
||||||
mkdir "$HYPERION_DIR/build"
|
mkdir "$HYPERION_DIR/build"
|
||||||
@ -20,7 +20,7 @@ cd "$HYPERION_DIR/build"
|
|||||||
# run cmake to generate make files on the rsapberry pi
|
# run cmake to generate make files on the rsapberry pi
|
||||||
cmake ..
|
cmake ..
|
||||||
# or if you are not compiling on the raspberry pi and need to disable the Dispmanx grabber and support for spi devices
|
# 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 ..
|
cmake -DENABLE_DISPMANX=OFF -DENABLE_SPIDEV=OFF -DENABLE_X11=ON ..
|
||||||
|
|
||||||
# run make to build Hyperion
|
# run make to build Hyperion
|
||||||
make
|
make
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
ON RASPBERRY
|
ON RASPBERRY
|
||||||
--------------
|
--------------
|
||||||
sudo apt-get install libprotobuf-dev libQt4-dev libusb-1.0-0-dev python-dev rsync
|
sudo apt-get install libQt4-dev libusb-1.0-0-dev python-dev rsync
|
||||||
|
|
||||||
ON HOST
|
ON HOST
|
||||||
---------
|
---------
|
||||||
@ -8,24 +8,36 @@ export RASPI=192.168.1.17
|
|||||||
export RASCROSS_DIR="$HOME/raspberrypi"
|
export RASCROSS_DIR="$HOME/raspberrypi"
|
||||||
export HYPERION_DIR="$HOME/hyperion"
|
export HYPERION_DIR="$HOME/hyperion"
|
||||||
|
|
||||||
|
# install required packages
|
||||||
|
sudo apt-get install git rsync cmake build-essential libQt4-dev libusb-1.0-0-dev python-dev
|
||||||
sudo apt-get install git rsync cmake ia32-libs protobuf-compiler
|
sudo apt-get install git rsync cmake ia32-libs protobuf-compiler
|
||||||
|
|
||||||
# On newer version of Ubuntu (and maybe other distros) the ia32-libs is not available, the following
|
# On newer version of Ubuntu (and maybe other distros) the ia32-libs is not available, the following
|
||||||
# install solved this for me (TODO: verify what is really required)
|
# install solved this for me (TODO: verify what is really required)
|
||||||
sudo apt-get install libc6:i386 libgcc1:i386 gcc-4.6-base:i386 libstdc++5:i386 libstdc++6:i386
|
sudo apt-get install libc6:i386 libgcc1:i386 gcc-4.6-base:i386 libstdc++5:i386 libstdc++6:i386
|
||||||
|
|
||||||
|
# create the rootfs by copying it from an rpi running for example raspbmc
|
||||||
mkdir -p "$RASCROSS_DIR/rootfs"
|
mkdir -p "$RASCROSS_DIR/rootfs"
|
||||||
git clone git://github.com/raspberrypi/tools.git "$RASCROSS_DIR/tools"
|
|
||||||
|
|
||||||
git clone https://github.com/raspberrypi/firmware.git "$RASCROSS_DIR/firmware"
|
|
||||||
ln -s "$RASCROSS_DIR/firmware/opt" "$RASCROSS_DIR/rootfs/opt"
|
|
||||||
|
|
||||||
rsync -rl --delete-after --safe-links pi@$RASPI:/{lib,usr} "$RASCROSS_DIR/rootfs"
|
rsync -rl --delete-after --safe-links pi@$RASPI:/{lib,usr} "$RASCROSS_DIR/rootfs"
|
||||||
|
|
||||||
git clone https://github.com/tvdzwan/hyperion.git "$HYPERION_DIR"
|
# get the raspberry pi firmware and add it to the rootfs
|
||||||
|
git clone https://github.com/raspberrypi/firmware.git "$RASCROSS_DIR/firmware"
|
||||||
|
ln -s "$RASCROSS_DIR/firmware/hardfp/opt" "$RASCROSS_DIR/rootfs/opt"
|
||||||
|
|
||||||
|
# get the compile tools
|
||||||
|
git clone git://github.com/raspberrypi/tools.git "$RASCROSS_DIR/tools"
|
||||||
|
|
||||||
|
# get the Hyperion sources
|
||||||
|
git clone --recursive https://github.com/tvdzwan/hyperion.git "$HYPERION_DIR"
|
||||||
|
|
||||||
|
# do a native build (to build the protobuf compiler for the native platform)
|
||||||
mkdir "$HYPERION_DIR/build"
|
mkdir "$HYPERION_DIR/build"
|
||||||
cmake -DCMAKE_TOOLCHAIN_FILE="$HYPERION_DIR/Toolchain-RaspberryPi.cmake" --build "$HYPERION_DIR/build" "$HYPERION_DIR"
|
cmake -DENABLE_DISPMANX=OFF --build "$HYPERION_DIR/build" "$HYPERION_DIR"
|
||||||
|
|
||||||
|
|
||||||
|
# do the rpi build
|
||||||
|
# specify the protoc export file to import the protobuf compiler from the native build
|
||||||
|
mkdir "$HYPERION_DIR/build-rpi"
|
||||||
|
cmake -DCMAKE_TOOLCHAIN_FILE="$HYPERION_DIR/Toolchain-RaspberryPi.cmake" -DIMPORT_PROTOC=$HYPERION_DIR/build/protoc_export.cmake --build "$HYPERION_DIR/build-rpi" "$HYPERION_DIR"
|
||||||
|
|
||||||
------------------------------------------------------------------------------
|
------------------------------------------------------------------------------
|
||||||
These instructions are based on the guide given by:
|
These instructions are based on the guide given by:
|
||||||
|
@ -9,5 +9,11 @@
|
|||||||
// Define to enable the spi-device
|
// Define to enable the spi-device
|
||||||
#cmakedefine ENABLE_SPIDEV
|
#cmakedefine ENABLE_SPIDEV
|
||||||
|
|
||||||
|
// Define to enable the ws2812b-pwm-device
|
||||||
|
#cmakedefine ENABLE_WS2812BPWM
|
||||||
|
|
||||||
// Define to enable the spi-device
|
// Define to enable the spi-device
|
||||||
#cmakedefine ENABLE_TINKERFORGE
|
#cmakedefine ENABLE_TINKERFORGE
|
||||||
|
|
||||||
|
// Define to enable PROTOBUF server
|
||||||
|
#cmakedefine ENABLE_PROTOBUF
|
||||||
|
@ -4,8 +4,8 @@ SET(CMAKE_SYSTEM_NAME Linux)
|
|||||||
SET(CMAKE_SYSTEM_VERSION 1)
|
SET(CMAKE_SYSTEM_VERSION 1)
|
||||||
|
|
||||||
# specify the cross compiler
|
# specify the cross compiler
|
||||||
SET(CMAKE_C_COMPILER ${RASPCROSS_DIR}/tools/arm-bcm2708/gcc-linaro-arm-linux-gnueabihf-raspbian/bin/arm-linux-gnueabihf-gcc)
|
SET(CMAKE_C_COMPILER ${RASPCROSS_DIR}/tools/arm-bcm2708/gcc-linaro-arm-linux-gnueabihf-raspbian-x64/bin/arm-linux-gnueabihf-gcc)
|
||||||
SET(CMAKE_CXX_COMPILER ${RASPCROSS_DIR}/tools/arm-bcm2708/gcc-linaro-arm-linux-gnueabihf-raspbian/bin/arm-linux-gnueabihf-g++)
|
SET(CMAKE_CXX_COMPILER ${RASPCROSS_DIR}/tools/arm-bcm2708/gcc-linaro-arm-linux-gnueabihf-raspbian-x64/bin/arm-linux-gnueabihf-g++)
|
||||||
|
|
||||||
# where is the target environment
|
# where is the target environment
|
||||||
SET(CMAKE_FIND_ROOT_PATH ${RASPCROSS_DIR}/rootfs)
|
SET(CMAKE_FIND_ROOT_PATH ${RASPCROSS_DIR}/rootfs)
|
||||||
|
@ -87,6 +87,7 @@ else (LIBUSB_1_LIBRARIES AND LIBUSB_1_INCLUDE_DIRS)
|
|||||||
message(STATUS " - Libraries: ${LIBUSB_1_LIBRARIES}")
|
message(STATUS " - Libraries: ${LIBUSB_1_LIBRARIES}")
|
||||||
endif (NOT libusb_1_FIND_QUIETLY)
|
endif (NOT libusb_1_FIND_QUIETLY)
|
||||||
else (LIBUSB_1_FOUND)
|
else (LIBUSB_1_FOUND)
|
||||||
|
unset(LIBUSB_1_LIBRARY CACHE)
|
||||||
if (libusb_1_FIND_REQUIRED)
|
if (libusb_1_FIND_REQUIRED)
|
||||||
message(FATAL_ERROR "Could not find libusb")
|
message(FATAL_ERROR "Could not find libusb")
|
||||||
endif (libusb_1_FIND_REQUIRED)
|
endif (libusb_1_FIND_REQUIRED)
|
||||||
|
@ -270,6 +270,16 @@ if(Q_WS_X11)
|
|||||||
|
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
|
if(Q_WS_QWS)
|
||||||
|
set(CMAKE_THREAD_PREFER_PTHREADS 1)
|
||||||
|
find_package(Threads)
|
||||||
|
if(CMAKE_USE_PTHREADS_INIT)
|
||||||
|
set(QT_QTCORE_LIB_DEPENDENCIES ${QT_QTCORE_LIB_DEPENDENCIES} ${CMAKE_THREAD_LIBS_INIT})
|
||||||
|
endif()
|
||||||
|
|
||||||
|
set (QT_QTCORE_LIB_DEPENDENCIES ${QT_QTCORE_LIB_DEPENDENCIES} ${CMAKE_DL_LIBS})
|
||||||
|
|
||||||
|
endif()
|
||||||
|
|
||||||
if(Q_WS_WIN)
|
if(Q_WS_WIN)
|
||||||
set(QT_QTGUI_LIB_DEPENDENCIES ${QT_QTGUI_LIB_DEPENDENCIES} imm32 winmm)
|
set(QT_QTGUI_LIB_DEPENDENCIES ${QT_QTGUI_LIB_DEPENDENCIES} imm32 winmm)
|
||||||
|
@ -80,7 +80,8 @@
|
|||||||
{
|
{
|
||||||
"type" : "none",
|
"type" : "none",
|
||||||
"time_ms" : 200,
|
"time_ms" : 200,
|
||||||
"updateFrequency" : 20.0000
|
"updateFrequency" : 20.0000,
|
||||||
|
"updateDelay" : 0
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
103
dependencies/CMakeLists.txt
vendored
103
dependencies/CMakeLists.txt
vendored
@ -1,2 +1,103 @@
|
|||||||
|
add_subdirectory(build/getoptPlusPlus)
|
||||||
|
add_subdirectory(build/hidapi)
|
||||||
|
add_subdirectory(build/jsoncpp)
|
||||||
|
add_subdirectory(build/serial)
|
||||||
|
add_subdirectory(build/tinkerforge)
|
||||||
|
|
||||||
add_subdirectory(build)
|
if(ENABLE_PROTOBUF)
|
||||||
|
set(BUILD_SHARED_LIBS OFF CACHE BOOL "Build shared protobuf library")
|
||||||
|
add_subdirectory(external/protobuf)
|
||||||
|
|
||||||
|
if(CMAKE_CROSSCOMPILING)
|
||||||
|
# when crosscompiling import the protoc executable targets from a file generated by a native build
|
||||||
|
option(IMPORT_PROTOC "Protoc export file (protoc_export.cmake) from a native build" "IMPORT_PROTOC-FILE_NOT_FOUND")
|
||||||
|
include(${IMPORT_PROTOC})
|
||||||
|
else()
|
||||||
|
# export the protoc compiler so it can be used when cross compiling
|
||||||
|
export(TARGETS protoc_compiler FILE "${CMAKE_BINARY_DIR}/protoc_export.cmake")
|
||||||
|
endif()
|
||||||
|
|
||||||
|
# define the include for the protobuf library at the parent scope
|
||||||
|
set(PROTOBUF_INCLUDE_DIRS "${CMAKE_CURRENT_SOURCE_DIR}/external/protobuf/src")
|
||||||
|
set(PROTOBUF_INCLUDE_DIRS ${PROTOBUF_INCLUDE_DIRS} PARENT_SCOPE)
|
||||||
|
|
||||||
|
# define the protoc executable at the parent scope
|
||||||
|
get_property(PROTOBUF_PROTOC_EXECUTABLE TARGET protoc_compiler PROPERTY LOCATION)
|
||||||
|
set(PROTOBUF_PROTOC_EXECUTABLE ${PROTOBUF_PROTOC_EXECUTABLE} PARENT_SCOPE)
|
||||||
|
message(STATUS "Using protobuf compiler: " ${PROTOBUF_PROTOC_EXECUTABLE})
|
||||||
|
|
||||||
|
#=============================================================================
|
||||||
|
# Copyright 2009 Kitware, Inc.
|
||||||
|
# Copyright 2009-2011 Philip Lowman <philip@yhbt.com>
|
||||||
|
# Copyright 2008 Esben Mose Hansen, Ange Optimization ApS
|
||||||
|
#
|
||||||
|
# Distributed under the OSI-approved BSD License (the "License");
|
||||||
|
# see accompanying file Copyright.txt for details.
|
||||||
|
#
|
||||||
|
# This software is distributed WITHOUT ANY WARRANTY; without even the
|
||||||
|
# implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||||
|
# See the License for more information.
|
||||||
|
#=============================================================================
|
||||||
|
# (To distribute this file outside of CMake, substitute the full
|
||||||
|
# License text for the above reference.)
|
||||||
|
function(PROTOBUF_GENERATE_CPP SRCS HDRS)
|
||||||
|
if(NOT ARGN)
|
||||||
|
message(SEND_ERROR "Error: PROTOBUF_GENERATE_CPP() called without any proto files")
|
||||||
|
return()
|
||||||
|
endif()
|
||||||
|
|
||||||
|
if(PROTOBUF_GENERATE_CPP_APPEND_PATH)
|
||||||
|
# Create an include path for each file specified
|
||||||
|
foreach(FIL ${ARGN})
|
||||||
|
get_filename_component(ABS_FIL ${FIL} ABSOLUTE)
|
||||||
|
get_filename_component(ABS_PATH ${ABS_FIL} PATH)
|
||||||
|
list(FIND _protobuf_include_path ${ABS_PATH} _contains_already)
|
||||||
|
if(${_contains_already} EQUAL -1)
|
||||||
|
list(APPEND _protobuf_include_path -I ${ABS_PATH})
|
||||||
|
endif()
|
||||||
|
endforeach()
|
||||||
|
else()
|
||||||
|
set(_protobuf_include_path -I ${CMAKE_CURRENT_SOURCE_DIR})
|
||||||
|
endif()
|
||||||
|
|
||||||
|
if(DEFINED PROTOBUF_IMPORT_DIRS)
|
||||||
|
foreach(DIR ${PROTOBUF_IMPORT_DIRS})
|
||||||
|
get_filename_component(ABS_PATH ${DIR} ABSOLUTE)
|
||||||
|
list(FIND _protobuf_include_path ${ABS_PATH} _contains_already)
|
||||||
|
if(${_contains_already} EQUAL -1)
|
||||||
|
list(APPEND _protobuf_include_path -I ${ABS_PATH})
|
||||||
|
endif()
|
||||||
|
endforeach()
|
||||||
|
endif()
|
||||||
|
|
||||||
|
if(CMAKE_CROSSCOMPILING)
|
||||||
|
set(PROTOC_DEPENDENCY ${PROTOBUF_PROTOC_EXECUTABLE})
|
||||||
|
else()
|
||||||
|
set(PROTOC_DEPENDENCY protoc_compiler)
|
||||||
|
endif()
|
||||||
|
|
||||||
|
set(${SRCS})
|
||||||
|
set(${HDRS})
|
||||||
|
foreach(FIL ${ARGN})
|
||||||
|
get_filename_component(ABS_FIL ${FIL} ABSOLUTE)
|
||||||
|
get_filename_component(FIL_WE ${FIL} NAME_WE)
|
||||||
|
|
||||||
|
list(APPEND ${SRCS} "${CMAKE_CURRENT_BINARY_DIR}/${FIL_WE}.pb.cc")
|
||||||
|
list(APPEND ${HDRS} "${CMAKE_CURRENT_BINARY_DIR}/${FIL_WE}.pb.h")
|
||||||
|
|
||||||
|
add_custom_command(
|
||||||
|
OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/${FIL_WE}.pb.cc"
|
||||||
|
"${CMAKE_CURRENT_BINARY_DIR}/${FIL_WE}.pb.h"
|
||||||
|
COMMAND ${PROTOBUF_PROTOC_EXECUTABLE}
|
||||||
|
ARGS --cpp_out ${CMAKE_CURRENT_BINARY_DIR} ${_protobuf_include_path} ${ABS_FIL}
|
||||||
|
DEPENDS ${ABS_FIL} ${PROTOC_DEPENDENCY}
|
||||||
|
COMMENT "Running C++ protocol buffer compiler on ${FIL}"
|
||||||
|
VERBATIM
|
||||||
|
)
|
||||||
|
endforeach()
|
||||||
|
|
||||||
|
set_source_files_properties(${${SRCS}} ${${HDRS}} PROPERTIES GENERATED TRUE)
|
||||||
|
set(${SRCS} ${${SRCS}} PARENT_SCOPE)
|
||||||
|
set(${HDRS} ${${HDRS}} PARENT_SCOPE)
|
||||||
|
endfunction()
|
||||||
|
endif()
|
||||||
|
6
dependencies/build/CMakeLists.txt
vendored
6
dependencies/build/CMakeLists.txt
vendored
@ -1,6 +0,0 @@
|
|||||||
|
|
||||||
add_subdirectory(getoptPlusPlus)
|
|
||||||
add_subdirectory(hidapi)
|
|
||||||
add_subdirectory(jsoncpp)
|
|
||||||
add_subdirectory(serial)
|
|
||||||
add_subdirectory(tinkerforge)
|
|
26
dependencies/build/getoptPlusPlus/getoptpp.cc
vendored
26
dependencies/build/getoptPlusPlus/getoptpp.cc
vendored
@ -17,6 +17,9 @@
|
|||||||
#include "getoptpp.h"
|
#include "getoptpp.h"
|
||||||
#include <stdexcept>
|
#include <stdexcept>
|
||||||
#include <cassert>
|
#include <cassert>
|
||||||
|
#include <cstdio>
|
||||||
|
#include <string>
|
||||||
|
#include <sys/ioctl.h>
|
||||||
|
|
||||||
using namespace std;
|
using namespace std;
|
||||||
|
|
||||||
@ -116,15 +119,32 @@ void OptionsParser::usage() const {
|
|||||||
|
|
||||||
cerr << "Parameters: " << endl;
|
cerr << "Parameters: " << endl;
|
||||||
|
|
||||||
|
int totalWidth = 80;
|
||||||
|
int usageWidth = 33;
|
||||||
|
|
||||||
|
// read total width from the terminal
|
||||||
|
struct winsize w;
|
||||||
|
if (ioctl(0, TIOCGWINSZ, &w) == 0)
|
||||||
|
{
|
||||||
|
if (w.ws_col > totalWidth)
|
||||||
|
totalWidth = w.ws_col;
|
||||||
|
}
|
||||||
|
|
||||||
std::list<Parameter*>::const_iterator i;
|
std::list<Parameter*>::const_iterator i;
|
||||||
for(i = parameters.parameters.begin();
|
for(i = parameters.parameters.begin();
|
||||||
i != parameters.parameters.end(); i++)
|
i != parameters.parameters.end(); i++)
|
||||||
{
|
{
|
||||||
cerr.width(33);
|
cerr.width(usageWidth);
|
||||||
cerr << std::left << " " + (*i)->usageLine();
|
cerr << std::left << " " + (*i)->usageLine();
|
||||||
|
|
||||||
cerr.width(40);
|
std::string description = (*i)->description();
|
||||||
cerr << std::left << (*i)->description() << endl;
|
while (int(description.length()) > (totalWidth - usageWidth))
|
||||||
|
{
|
||||||
|
size_t pos = description.find_last_of(' ', totalWidth - usageWidth);
|
||||||
|
cerr << description.substr(0, pos) << std::endl << std::string(usageWidth - 1, ' ');
|
||||||
|
description = description.substr(pos);
|
||||||
|
}
|
||||||
|
cerr << description << endl;
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
1
dependencies/external/protobuf
vendored
Submodule
1
dependencies/external/protobuf
vendored
Submodule
@ -0,0 +1 @@
|
|||||||
|
Subproject commit efb59b79e5a8f26eae4d15f38bbfb5667e23df60
|
1
deploy/hummingboard_prerelease.tar.gz.REMOVED.git-id
Normal file
1
deploy/hummingboard_prerelease.tar.gz.REMOVED.git-id
Normal file
@ -0,0 +1 @@
|
|||||||
|
63b2cc4bf190a0c0c996be6db30fcf03109c9625
|
@ -1 +1 @@
|
|||||||
9546e335179f732ff68ea9bc47020a19e4b6f44c
|
0598f021ab2b4e585b03034d9451b0559a85f038
|
BIN
doc/datasheets/APA102_LED.pdf
Normal file
BIN
doc/datasheets/APA102_LED.pdf
Normal file
Binary file not shown.
@ -11,11 +11,12 @@
|
|||||||
// util includes
|
// util includes
|
||||||
#include <utils/Image.h>
|
#include <utils/Image.h>
|
||||||
#include <utils/ColorRgb.h>
|
#include <utils/ColorRgb.h>
|
||||||
|
#include <utils/PixelFormat.h>
|
||||||
#include <utils/VideoMode.h>
|
#include <utils/VideoMode.h>
|
||||||
|
#include <utils/ImageResampler.h>
|
||||||
|
|
||||||
// grabber includes
|
// grabber includes
|
||||||
#include <grabber/VideoStandard.h>
|
#include <grabber/VideoStandard.h>
|
||||||
#include <grabber/PixelFormat.h>
|
|
||||||
|
|
||||||
/// Capture class for V4L2 devices
|
/// Capture class for V4L2 devices
|
||||||
///
|
///
|
||||||
@ -108,22 +109,17 @@ private:
|
|||||||
PixelFormat _pixelFormat;
|
PixelFormat _pixelFormat;
|
||||||
int _width;
|
int _width;
|
||||||
int _height;
|
int _height;
|
||||||
|
int _lineLength;
|
||||||
int _frameByteSize;
|
int _frameByteSize;
|
||||||
int _cropLeft;
|
|
||||||
int _cropRight;
|
|
||||||
int _cropTop;
|
|
||||||
int _cropBottom;
|
|
||||||
int _frameDecimation;
|
int _frameDecimation;
|
||||||
int _horizontalPixelDecimation;
|
|
||||||
int _verticalPixelDecimation;
|
|
||||||
int _noSignalCounterThreshold;
|
int _noSignalCounterThreshold;
|
||||||
|
|
||||||
ColorRgb _noSignalThresholdColor;
|
ColorRgb _noSignalThresholdColor;
|
||||||
|
|
||||||
VideoMode _mode3D;
|
|
||||||
|
|
||||||
int _currentFrame;
|
int _currentFrame;
|
||||||
int _noSignalCounter;
|
int _noSignalCounter;
|
||||||
|
|
||||||
QSocketNotifier * _streamNotifier;
|
QSocketNotifier * _streamNotifier;
|
||||||
|
|
||||||
|
ImageResampler _imageResampler;
|
||||||
};
|
};
|
||||||
|
39
include/grabber/X11Grabber.h
Normal file
39
include/grabber/X11Grabber.h
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
|
||||||
|
// Hyperion-utils includes
|
||||||
|
#include <utils/Image.h>
|
||||||
|
#include <utils/ColorRgb.h>
|
||||||
|
#include <utils/ImageResampler.h>
|
||||||
|
|
||||||
|
// X11 includes
|
||||||
|
#include <X11/Xlib.h>
|
||||||
|
|
||||||
|
class X11Grabber
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
|
||||||
|
X11Grabber(int cropLeft, int cropRight, int cropTop, int cropBottom, int horizontalPixelDecimation, int verticalPixelDecimation);
|
||||||
|
|
||||||
|
virtual ~X11Grabber();
|
||||||
|
|
||||||
|
int open();
|
||||||
|
|
||||||
|
Image<ColorRgb> & grab();
|
||||||
|
|
||||||
|
private:
|
||||||
|
ImageResampler _imageResampler;
|
||||||
|
|
||||||
|
int _cropLeft;
|
||||||
|
int _cropRight;
|
||||||
|
int _cropTop;
|
||||||
|
int _cropBottom;
|
||||||
|
|
||||||
|
/// Reference to the X11 display (nullptr if not opened)
|
||||||
|
Display * _x11Display;
|
||||||
|
|
||||||
|
unsigned _screenWidth;
|
||||||
|
unsigned _screenHeight;
|
||||||
|
|
||||||
|
Image<ColorRgb> _image;
|
||||||
|
|
||||||
|
int updateScreenDimensions();
|
||||||
|
};
|
102
include/protoserver/ProtoConnection.h
Normal file
102
include/protoserver/ProtoConnection.h
Normal file
@ -0,0 +1,102 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
// stl includes
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
// Qt includes
|
||||||
|
#include <QColor>
|
||||||
|
#include <QImage>
|
||||||
|
#include <QTcpSocket>
|
||||||
|
#include <QMap>
|
||||||
|
|
||||||
|
// hyperion util
|
||||||
|
#include <utils/Image.h>
|
||||||
|
#include <utils/ColorRgb.h>
|
||||||
|
|
||||||
|
// jsoncpp includes
|
||||||
|
#include <message.pb.h>
|
||||||
|
|
||||||
|
///
|
||||||
|
/// Connection class to setup an connection to the hyperion server and execute commands
|
||||||
|
///
|
||||||
|
class ProtoConnection
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
///
|
||||||
|
/// Constructor
|
||||||
|
///
|
||||||
|
/// @param address The address of the Hyperion server (for example "192.168.0.32:19444)
|
||||||
|
///
|
||||||
|
ProtoConnection(const std::string & address);
|
||||||
|
|
||||||
|
///
|
||||||
|
/// Destructor
|
||||||
|
///
|
||||||
|
~ProtoConnection();
|
||||||
|
|
||||||
|
/// Do not read reply messages from Hyperion if set to true
|
||||||
|
void setSkipReply(bool skip);
|
||||||
|
|
||||||
|
///
|
||||||
|
/// Set all leds to the specified color
|
||||||
|
///
|
||||||
|
/// @param color The color
|
||||||
|
/// @param priority The priority
|
||||||
|
/// @param duration The duration in milliseconds
|
||||||
|
///
|
||||||
|
void setColor(const ColorRgb & color, int priority, int duration = 1);
|
||||||
|
|
||||||
|
///
|
||||||
|
/// Set the leds according to the given image (assume the image is stretched to the display size)
|
||||||
|
///
|
||||||
|
/// @param image The image
|
||||||
|
/// @param priority The priority
|
||||||
|
/// @param duration The duration in milliseconds
|
||||||
|
///
|
||||||
|
void setImage(const Image<ColorRgb> & image, int priority, int duration = -1);
|
||||||
|
|
||||||
|
///
|
||||||
|
/// Clear the given priority channel
|
||||||
|
///
|
||||||
|
/// @param priority The priority
|
||||||
|
///
|
||||||
|
void clear(int priority);
|
||||||
|
|
||||||
|
///
|
||||||
|
/// Clear all priority channels
|
||||||
|
///
|
||||||
|
void clearAll();
|
||||||
|
|
||||||
|
private:
|
||||||
|
/// Try to connect to the Hyperion host
|
||||||
|
void connectToHost();
|
||||||
|
|
||||||
|
///
|
||||||
|
/// Send a command message and receive its reply
|
||||||
|
///
|
||||||
|
/// @param message The message to send
|
||||||
|
///
|
||||||
|
void sendMessage(const proto::HyperionRequest & message);
|
||||||
|
|
||||||
|
///
|
||||||
|
/// Parse a reply message
|
||||||
|
///
|
||||||
|
/// @param reply The received reply
|
||||||
|
///
|
||||||
|
/// @return true if the reply indicates success
|
||||||
|
///
|
||||||
|
bool parseReply(const proto::HyperionReply & reply);
|
||||||
|
|
||||||
|
private:
|
||||||
|
/// The TCP-Socket with the connection to the server
|
||||||
|
QTcpSocket _socket;
|
||||||
|
|
||||||
|
/// Host address
|
||||||
|
QString _host;
|
||||||
|
|
||||||
|
/// Host port
|
||||||
|
uint16_t _port;
|
||||||
|
|
||||||
|
/// Skip receiving reply messages from Hyperion if set
|
||||||
|
bool _skipReply;
|
||||||
|
};
|
34
include/protoserver/ProtoConnectionWrapper.h
Normal file
34
include/protoserver/ProtoConnectionWrapper.h
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
// Qt includes
|
||||||
|
#include <QObject>
|
||||||
|
|
||||||
|
// hyperion includes
|
||||||
|
#include <utils/Image.h>
|
||||||
|
#include <utils/ColorRgb.h>
|
||||||
|
|
||||||
|
// hyperion proto includes
|
||||||
|
#include "protoserver/ProtoConnection.h"
|
||||||
|
|
||||||
|
/// This class handles callbacks from the V4L2 grabber
|
||||||
|
class ProtoConnectionWrapper : public QObject
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
|
public:
|
||||||
|
ProtoConnectionWrapper(const std::string & address, int priority, int duration_ms, bool skipProtoReply);
|
||||||
|
virtual ~ProtoConnectionWrapper();
|
||||||
|
|
||||||
|
public slots:
|
||||||
|
/// Handle a single image
|
||||||
|
/// @param image The image to process
|
||||||
|
void receiveImage(const Image<ColorRgb> & image);
|
||||||
|
|
||||||
|
private:
|
||||||
|
/// Priority for calls to Hyperion
|
||||||
|
const int _priority;
|
||||||
|
|
||||||
|
/// Duration for color calls to Hyperion
|
||||||
|
const int _duration_ms;
|
||||||
|
|
||||||
|
/// Hyperion proto connection object
|
||||||
|
ProtoConnection _connection;
|
||||||
|
};
|
@ -96,11 +96,6 @@ public:
|
|||||||
return _height;
|
return _height;
|
||||||
}
|
}
|
||||||
|
|
||||||
uint8_t alpha(const unsigned pixel) const
|
|
||||||
{
|
|
||||||
return (_pixels + pixel)->red;
|
|
||||||
}
|
|
||||||
|
|
||||||
uint8_t red(const unsigned pixel) const
|
uint8_t red(const unsigned pixel) const
|
||||||
{
|
{
|
||||||
return (_pixels + pixel)->red;
|
return (_pixels + pixel)->red;
|
||||||
|
40
include/utils/ImageResampler.h
Normal file
40
include/utils/ImageResampler.h
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <utils/VideoMode.h>
|
||||||
|
#include <utils/PixelFormat.h>
|
||||||
|
#include <utils/Image.h>
|
||||||
|
#include <utils/ColorRgb.h>
|
||||||
|
|
||||||
|
class ImageResampler
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
ImageResampler();
|
||||||
|
~ImageResampler();
|
||||||
|
|
||||||
|
void setHorizontalPixelDecimation(int decimator);
|
||||||
|
|
||||||
|
void setVerticalPixelDecimation(int decimator);
|
||||||
|
|
||||||
|
void setCropping(int cropLeft,
|
||||||
|
int cropRight,
|
||||||
|
int cropTop,
|
||||||
|
int cropBottom);
|
||||||
|
|
||||||
|
void set3D(VideoMode mode);
|
||||||
|
|
||||||
|
void processImage(const uint8_t * data, int width, int height, int lineLength, PixelFormat pixelFormat,
|
||||||
|
Image<ColorRgb> & outputImage) const;
|
||||||
|
|
||||||
|
private:
|
||||||
|
static inline uint8_t clamp(int x);
|
||||||
|
static void yuv2rgb(uint8_t y, uint8_t u, uint8_t v, uint8_t & r, uint8_t & g, uint8_t & b);
|
||||||
|
|
||||||
|
private:
|
||||||
|
int _horizontalDecimation;
|
||||||
|
int _verticalDecimation;
|
||||||
|
int _cropLeft;
|
||||||
|
int _cropRight;
|
||||||
|
int _cropTop;
|
||||||
|
int _cropBottom;
|
||||||
|
VideoMode _videoMode;
|
||||||
|
};
|
@ -10,6 +10,7 @@ enum PixelFormat {
|
|||||||
PIXELFORMAT_YUYV,
|
PIXELFORMAT_YUYV,
|
||||||
PIXELFORMAT_UYVY,
|
PIXELFORMAT_UYVY,
|
||||||
PIXELFORMAT_RGB32,
|
PIXELFORMAT_RGB32,
|
||||||
|
PIXELFORMAT_BGR32,
|
||||||
PIXELFORMAT_NO_CHANGE
|
PIXELFORMAT_NO_CHANGE
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -30,6 +31,10 @@ inline PixelFormat parsePixelFormat(std::string pixelFormat)
|
|||||||
{
|
{
|
||||||
return PIXELFORMAT_RGB32;
|
return PIXELFORMAT_RGB32;
|
||||||
}
|
}
|
||||||
|
else if (pixelFormat == "bgr32")
|
||||||
|
{
|
||||||
|
return PIXELFORMAT_BGR32;
|
||||||
|
}
|
||||||
|
|
||||||
// return the default NO_CHANGE
|
// return the default NO_CHANGE
|
||||||
return PIXELFORMAT_NO_CHANGE;
|
return PIXELFORMAT_NO_CHANGE;
|
@ -6,7 +6,11 @@ SET(CURRENT_SOURCE_DIR ${CMAKE_SOURCE_DIR}/libsrc)
|
|||||||
add_subdirectory(hyperion)
|
add_subdirectory(hyperion)
|
||||||
add_subdirectory(blackborder)
|
add_subdirectory(blackborder)
|
||||||
add_subdirectory(jsonserver)
|
add_subdirectory(jsonserver)
|
||||||
add_subdirectory(protoserver)
|
|
||||||
|
if (ENABLE_PROTOBUF)
|
||||||
|
add_subdirectory(protoserver)
|
||||||
|
endif (ENABLE_PROTOBUF)
|
||||||
|
|
||||||
add_subdirectory(boblightserver)
|
add_subdirectory(boblightserver)
|
||||||
add_subdirectory(leddevice)
|
add_subdirectory(leddevice)
|
||||||
add_subdirectory(utils)
|
add_subdirectory(utils)
|
||||||
|
@ -1,4 +1,3 @@
|
|||||||
|
|
||||||
if (ENABLE_DISPMANX)
|
if (ENABLE_DISPMANX)
|
||||||
add_subdirectory(dispmanx)
|
add_subdirectory(dispmanx)
|
||||||
endif (ENABLE_DISPMANX)
|
endif (ENABLE_DISPMANX)
|
||||||
@ -6,3 +5,7 @@ endif (ENABLE_DISPMANX)
|
|||||||
if (ENABLE_V4L2)
|
if (ENABLE_V4L2)
|
||||||
add_subdirectory(v4l2)
|
add_subdirectory(v4l2)
|
||||||
endif (ENABLE_V4L2)
|
endif (ENABLE_V4L2)
|
||||||
|
|
||||||
|
if (ENABLE_X11)
|
||||||
|
add_subdirectory(x11)
|
||||||
|
endif()
|
||||||
|
@ -9,7 +9,6 @@ SET(V4L2_QT_HEADERS
|
|||||||
|
|
||||||
SET(V4L2_HEADERS
|
SET(V4L2_HEADERS
|
||||||
${CURRENT_HEADER_DIR}/VideoStandard.h
|
${CURRENT_HEADER_DIR}/VideoStandard.h
|
||||||
${CURRENT_HEADER_DIR}/PixelFormat.h
|
|
||||||
)
|
)
|
||||||
|
|
||||||
SET(V4L2_SOURCES
|
SET(V4L2_SOURCES
|
||||||
|
@ -18,24 +18,6 @@
|
|||||||
|
|
||||||
#define CLEAR(x) memset(&(x), 0, sizeof(x))
|
#define CLEAR(x) memset(&(x), 0, sizeof(x))
|
||||||
|
|
||||||
static inline uint8_t clamp(int x)
|
|
||||||
{
|
|
||||||
return (x<0) ? 0 : ((x>255) ? 255 : uint8_t(x));
|
|
||||||
}
|
|
||||||
|
|
||||||
static void yuv2rgb(uint8_t y, uint8_t u, uint8_t v, uint8_t & r, uint8_t & g, uint8_t & b)
|
|
||||||
{
|
|
||||||
// see: http://en.wikipedia.org/wiki/YUV#Y.27UV444_to_RGB888_conversion
|
|
||||||
int c = y - 16;
|
|
||||||
int d = u - 128;
|
|
||||||
int e = v - 128;
|
|
||||||
|
|
||||||
r = clamp((298 * c + 409 * e + 128) >> 8);
|
|
||||||
g = clamp((298 * c - 100 * d - 208 * e + 128) >> 8);
|
|
||||||
b = clamp((298 * c + 516 * d + 128) >> 8);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
V4L2Grabber::V4L2Grabber(const std::string & device,
|
V4L2Grabber::V4L2Grabber(const std::string & device,
|
||||||
int input,
|
int input,
|
||||||
VideoStandard videoStandard,
|
VideoStandard videoStandard,
|
||||||
@ -52,21 +34,19 @@ V4L2Grabber::V4L2Grabber(const std::string & device,
|
|||||||
_pixelFormat(pixelFormat),
|
_pixelFormat(pixelFormat),
|
||||||
_width(width),
|
_width(width),
|
||||||
_height(height),
|
_height(height),
|
||||||
|
_lineLength(-1),
|
||||||
_frameByteSize(-1),
|
_frameByteSize(-1),
|
||||||
_cropLeft(0),
|
|
||||||
_cropRight(0),
|
|
||||||
_cropTop(0),
|
|
||||||
_cropBottom(0),
|
|
||||||
_frameDecimation(std::max(1, frameDecimation)),
|
_frameDecimation(std::max(1, frameDecimation)),
|
||||||
_horizontalPixelDecimation(std::max(1, horizontalPixelDecimation)),
|
|
||||||
_verticalPixelDecimation(std::max(1, verticalPixelDecimation)),
|
|
||||||
_noSignalCounterThreshold(50),
|
_noSignalCounterThreshold(50),
|
||||||
_noSignalThresholdColor(ColorRgb{0,0,0}),
|
_noSignalThresholdColor(ColorRgb{0,0,0}),
|
||||||
_mode3D(VIDEO_2D),
|
|
||||||
_currentFrame(0),
|
_currentFrame(0),
|
||||||
_noSignalCounter(0),
|
_noSignalCounter(0),
|
||||||
_streamNotifier(nullptr)
|
_streamNotifier(nullptr),
|
||||||
|
_imageResampler()
|
||||||
{
|
{
|
||||||
|
_imageResampler.setHorizontalPixelDecimation(std::max(1, horizontalPixelDecimation));
|
||||||
|
_imageResampler.setVerticalPixelDecimation(std::max(1, verticalPixelDecimation));
|
||||||
|
|
||||||
open_device();
|
open_device();
|
||||||
init_device(videoStandard, input);
|
init_device(videoStandard, input);
|
||||||
}
|
}
|
||||||
@ -81,15 +61,12 @@ V4L2Grabber::~V4L2Grabber()
|
|||||||
|
|
||||||
void V4L2Grabber::setCropping(int cropLeft, int cropRight, int cropTop, int cropBottom)
|
void V4L2Grabber::setCropping(int cropLeft, int cropRight, int cropTop, int cropBottom)
|
||||||
{
|
{
|
||||||
_cropLeft = cropLeft;
|
_imageResampler.setCropping(cropLeft, cropRight, cropTop, cropBottom);
|
||||||
_cropRight = cropRight;
|
|
||||||
_cropTop = cropTop;
|
|
||||||
_cropBottom = cropBottom;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void V4L2Grabber::set3D(VideoMode mode)
|
void V4L2Grabber::set3D(VideoMode mode)
|
||||||
{
|
{
|
||||||
_mode3D = mode;
|
_imageResampler.set3D(mode);
|
||||||
}
|
}
|
||||||
|
|
||||||
void V4L2Grabber::setSignalThreshold(double redSignalThreshold, double greenSignalThreshold, double blueSignalThreshold, int noSignalCounterThreshold)
|
void V4L2Grabber::setSignalThreshold(double redSignalThreshold, double greenSignalThreshold, double blueSignalThreshold, int noSignalCounterThreshold)
|
||||||
@ -414,6 +391,9 @@ void V4L2Grabber::init_device(VideoStandard videoStandard, int input)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// set the line length
|
||||||
|
_lineLength = fmt.fmt.pix.bytesperline;
|
||||||
|
|
||||||
// set the settings
|
// set the settings
|
||||||
if (-1 == xioctl(VIDIOC_S_FMT, &fmt))
|
if (-1 == xioctl(VIDIOC_S_FMT, &fmt))
|
||||||
{
|
{
|
||||||
@ -688,66 +668,8 @@ bool V4L2Grabber::process_image(const void *p, int size)
|
|||||||
|
|
||||||
void V4L2Grabber::process_image(const uint8_t * data)
|
void V4L2Grabber::process_image(const uint8_t * data)
|
||||||
{
|
{
|
||||||
int width = _width;
|
Image<ColorRgb> image(0, 0);
|
||||||
int height = _height;
|
_imageResampler.processImage(data, _width, _height, _lineLength, _pixelFormat, image);
|
||||||
|
|
||||||
switch (_mode3D)
|
|
||||||
{
|
|
||||||
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<ColorRgb> 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)
|
|
||||||
{
|
|
||||||
ColorRgb & rgb = image(xDest, yDest);
|
|
||||||
|
|
||||||
switch (_pixelFormat)
|
|
||||||
{
|
|
||||||
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 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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// check signal (only in center of the resulting image, because some grabbers have noise values along the borders)
|
// check signal (only in center of the resulting image, because some grabbers have noise values along the borders)
|
||||||
bool noSignal = true;
|
bool noSignal = true;
|
||||||
|
37
libsrc/grabber/x11/CMakeLists.txt
Normal file
37
libsrc/grabber/x11/CMakeLists.txt
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
# Define the current source locations
|
||||||
|
SET(CURRENT_HEADER_DIR ${CMAKE_SOURCE_DIR}/include/grabber)
|
||||||
|
SET(CURRENT_SOURCE_DIR ${CMAKE_SOURCE_DIR}/libsrc/grabber/x11)
|
||||||
|
|
||||||
|
# Find X11
|
||||||
|
find_package(X11 REQUIRED)
|
||||||
|
|
||||||
|
include_directories(
|
||||||
|
${QT_INCLUDES}
|
||||||
|
${X11_INCLUDES}
|
||||||
|
)
|
||||||
|
|
||||||
|
SET(X11_QT_HEADERS
|
||||||
|
${CURRENT_HEADER_DIR}/X11Grabber.h
|
||||||
|
)
|
||||||
|
|
||||||
|
SET(X11_HEADERS
|
||||||
|
${CURRENT_HEADER_DIR}/X11Grabber.h
|
||||||
|
)
|
||||||
|
|
||||||
|
SET(X11_SOURCES
|
||||||
|
${CURRENT_SOURCE_DIR}/X11Grabber.cpp
|
||||||
|
)
|
||||||
|
|
||||||
|
QT4_WRAP_CPP(X11_HEADERS_MOC ${X11_QT_HEADERS})
|
||||||
|
|
||||||
|
add_library(x11-grabber
|
||||||
|
${X11_HEADERS}
|
||||||
|
${X11_SOURCES}
|
||||||
|
${X11_QT_HEADERS}
|
||||||
|
${X11_HEADERS_MOC}
|
||||||
|
)
|
||||||
|
|
||||||
|
target_link_libraries(x11-grabber
|
||||||
|
hyperion
|
||||||
|
${QT_LIBRARIES}
|
||||||
|
)
|
98
libsrc/grabber/x11/X11Grabber.cpp
Normal file
98
libsrc/grabber/x11/X11Grabber.cpp
Normal file
@ -0,0 +1,98 @@
|
|||||||
|
// STL includes
|
||||||
|
#include <iostream>
|
||||||
|
#include <cstdint>
|
||||||
|
|
||||||
|
// X11 includes
|
||||||
|
#include <X11/Xutil.h>
|
||||||
|
|
||||||
|
// X11Grabber includes
|
||||||
|
#include <grabber/X11Grabber.h>
|
||||||
|
|
||||||
|
X11Grabber::X11Grabber(int cropLeft, int cropRight, int cropTop, int cropBottom, int horizontalPixelDecimation, int verticalPixelDecimation) :
|
||||||
|
_imageResampler(),
|
||||||
|
_cropLeft(cropLeft),
|
||||||
|
_cropRight(cropRight),
|
||||||
|
_cropTop(cropTop),
|
||||||
|
_cropBottom(cropBottom),
|
||||||
|
_x11Display(nullptr),
|
||||||
|
_screenWidth(0),
|
||||||
|
_screenHeight(0),
|
||||||
|
_image(0,0)
|
||||||
|
{
|
||||||
|
_imageResampler.setHorizontalPixelDecimation(horizontalPixelDecimation);
|
||||||
|
_imageResampler.setVerticalPixelDecimation(verticalPixelDecimation);
|
||||||
|
_imageResampler.setCropping(0, 0, 0, 0); // cropping is performed by XGetImage
|
||||||
|
}
|
||||||
|
|
||||||
|
X11Grabber::~X11Grabber()
|
||||||
|
{
|
||||||
|
if (_x11Display != nullptr)
|
||||||
|
{
|
||||||
|
XCloseDisplay(_x11Display);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int X11Grabber::open()
|
||||||
|
{
|
||||||
|
const char * display_name = nullptr;
|
||||||
|
_x11Display = XOpenDisplay(display_name);
|
||||||
|
|
||||||
|
if (_x11Display == nullptr)
|
||||||
|
{
|
||||||
|
std::cerr << "Failed to open the default X11-display" << std::endl;
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
Image<ColorRgb> & X11Grabber::grab()
|
||||||
|
{
|
||||||
|
if (_x11Display == nullptr)
|
||||||
|
{
|
||||||
|
open();
|
||||||
|
}
|
||||||
|
|
||||||
|
updateScreenDimensions();
|
||||||
|
|
||||||
|
const unsigned croppedWidth = _screenWidth - _cropLeft - _cropRight;
|
||||||
|
const unsigned croppedHeight = _screenHeight - _cropTop - _cropBottom;
|
||||||
|
|
||||||
|
// Capture the current screen
|
||||||
|
XImage * xImage = XGetImage(_x11Display, DefaultRootWindow(_x11Display), _cropLeft, _cropTop, croppedWidth, croppedHeight, AllPlanes, ZPixmap);
|
||||||
|
if (xImage == nullptr)
|
||||||
|
{
|
||||||
|
std::cerr << "Grab failed" << std::endl;
|
||||||
|
return _image;
|
||||||
|
}
|
||||||
|
|
||||||
|
_imageResampler.processImage(reinterpret_cast<const uint8_t *>(xImage->data), xImage->width, xImage->height, xImage->bytes_per_line, PIXELFORMAT_BGR32, _image);
|
||||||
|
|
||||||
|
// Cleanup allocated resources of the X11 grab
|
||||||
|
XDestroyImage(xImage);
|
||||||
|
|
||||||
|
return _image;
|
||||||
|
}
|
||||||
|
|
||||||
|
int X11Grabber::updateScreenDimensions()
|
||||||
|
{
|
||||||
|
XWindowAttributes window_attributes_return;
|
||||||
|
const Status status = XGetWindowAttributes(_x11Display, DefaultRootWindow(_x11Display), &window_attributes_return);
|
||||||
|
if (status == 0)
|
||||||
|
{
|
||||||
|
std::cerr << "Failed to obtain window attributes" << std::endl;
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_screenWidth == unsigned(window_attributes_return.width) && _screenHeight == unsigned(window_attributes_return.height))
|
||||||
|
{
|
||||||
|
// No update required
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
std::cout << "Update of screen resolution: [" << _screenWidth << "x" << _screenHeight <<"] => ";
|
||||||
|
_screenWidth = window_attributes_return.width;
|
||||||
|
_screenHeight = window_attributes_return.height;
|
||||||
|
std::cout << "[" << _screenWidth << "x" << _screenHeight <<"]" << std::endl;
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
@ -243,8 +243,13 @@ LedDevice * Hyperion::createColorSmoothing(const Json::Value & smoothingConfig,
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
const unsigned updateDelay = smoothingConfig.get("updateDelay", Json::Value(0u)).asUInt();
|
||||||
std::cout << "Creating linear smoothing" << std::endl;
|
std::cout << "Creating linear smoothing" << std::endl;
|
||||||
return new LinearColorSmoothing(ledDevice, smoothingConfig["updateFrequency"].asDouble(), smoothingConfig["time_ms"].asInt());
|
return new LinearColorSmoothing(
|
||||||
|
ledDevice,
|
||||||
|
smoothingConfig["updateFrequency"].asDouble(),
|
||||||
|
smoothingConfig["time_ms"].asInt(),
|
||||||
|
updateDelay);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
@ -3,29 +3,38 @@
|
|||||||
|
|
||||||
#include "LinearColorSmoothing.h"
|
#include "LinearColorSmoothing.h"
|
||||||
|
|
||||||
LinearColorSmoothing::LinearColorSmoothing(LedDevice *ledDevice, double ledUpdateFrequency_hz, int settlingTime_ms) :
|
LinearColorSmoothing::LinearColorSmoothing(
|
||||||
|
LedDevice * ledDevice,
|
||||||
|
double ledUpdateFrequency_hz,
|
||||||
|
int settlingTime_ms,
|
||||||
|
unsigned updateDelay) :
|
||||||
QObject(),
|
QObject(),
|
||||||
LedDevice(),
|
LedDevice(),
|
||||||
_ledDevice(ledDevice),
|
_ledDevice(ledDevice),
|
||||||
_updateInterval(1000 / ledUpdateFrequency_hz),
|
_updateInterval(1000 / ledUpdateFrequency_hz),
|
||||||
_settlingTime(settlingTime_ms),
|
_settlingTime(settlingTime_ms),
|
||||||
_timer()
|
_timer(),
|
||||||
|
_outputDelay(updateDelay)
|
||||||
{
|
{
|
||||||
_timer.setSingleShot(false);
|
_timer.setSingleShot(false);
|
||||||
_timer.setInterval(_updateInterval);
|
_timer.setInterval(_updateInterval);
|
||||||
|
|
||||||
connect(&_timer, SIGNAL(timeout()), this, SLOT(updateLeds()));
|
connect(&_timer, SIGNAL(timeout()), this, SLOT(updateLeds()));
|
||||||
|
|
||||||
|
std::cout << "Created linear-smoothing(interval_ms=" << _updateInterval << ";settlingTime_ms=" << settlingTime_ms << ";updateDelay=" << _outputDelay << std::endl;
|
||||||
}
|
}
|
||||||
|
|
||||||
LinearColorSmoothing::~LinearColorSmoothing()
|
LinearColorSmoothing::~LinearColorSmoothing()
|
||||||
{
|
{
|
||||||
|
// Make sure to switch off the underlying led-device (because switchOff is no longer forwarded)
|
||||||
|
_ledDevice->switchOff();
|
||||||
delete _ledDevice;
|
delete _ledDevice;
|
||||||
}
|
}
|
||||||
|
|
||||||
int LinearColorSmoothing::write(const std::vector<ColorRgb> &ledValues)
|
int LinearColorSmoothing::write(const std::vector<ColorRgb> &ledValues)
|
||||||
{
|
{
|
||||||
// received a new target color
|
// received a new target color
|
||||||
if (_previousValues.size() == 0)
|
if (_previousValues.empty())
|
||||||
{
|
{
|
||||||
// not initialized yet
|
// not initialized yet
|
||||||
_targetTime = QDateTime::currentMSecsSinceEpoch() + _settlingTime;
|
_targetTime = QDateTime::currentMSecsSinceEpoch() + _settlingTime;
|
||||||
@ -46,17 +55,20 @@ int LinearColorSmoothing::write(const std::vector<ColorRgb> &ledValues)
|
|||||||
|
|
||||||
int LinearColorSmoothing::switchOff()
|
int LinearColorSmoothing::switchOff()
|
||||||
{
|
{
|
||||||
// stop smoothing filter
|
// We will keep updating the leds (but with pure-black)
|
||||||
_timer.stop();
|
|
||||||
|
|
||||||
// return to uninitialized state
|
// Clear the smoothing parameters
|
||||||
_previousValues.clear();
|
std::fill(_targetValues.begin(), _targetValues.end(), ColorRgb::BLACK);
|
||||||
_previousTime = 0;
|
|
||||||
_targetValues.clear();
|
|
||||||
_targetTime = 0;
|
_targetTime = 0;
|
||||||
|
|
||||||
// finally switch off all leds
|
// Erase the output-queue
|
||||||
return _ledDevice->switchOff();
|
for (unsigned i=0; i<_outputQueue.size(); ++i)
|
||||||
|
{
|
||||||
|
_outputQueue.push_back(_targetValues);
|
||||||
|
_outputQueue.pop_front();
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
void LinearColorSmoothing::updateLeds()
|
void LinearColorSmoothing::updateLeds()
|
||||||
@ -69,7 +81,7 @@ void LinearColorSmoothing::updateLeds()
|
|||||||
memcpy(_previousValues.data(), _targetValues.data(), _targetValues.size() * sizeof(ColorRgb));
|
memcpy(_previousValues.data(), _targetValues.data(), _targetValues.size() * sizeof(ColorRgb));
|
||||||
_previousTime = now;
|
_previousTime = now;
|
||||||
|
|
||||||
_ledDevice->write(_previousValues);
|
queueColors(_previousValues);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@ -86,6 +98,26 @@ void LinearColorSmoothing::updateLeds()
|
|||||||
}
|
}
|
||||||
_previousTime = now;
|
_previousTime = now;
|
||||||
|
|
||||||
_ledDevice->write(_previousValues);
|
queueColors(_previousValues);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void LinearColorSmoothing::queueColors(const std::vector<ColorRgb> & ledColors)
|
||||||
|
{
|
||||||
|
if (_outputDelay == 0)
|
||||||
|
{
|
||||||
|
// No output delay => immediate write
|
||||||
|
_ledDevice->write(ledColors);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Push the new colors in the delay-buffer
|
||||||
|
_outputQueue.push_back(ledColors);
|
||||||
|
// If the delay-buffer is filled pop the front and write to device
|
||||||
|
if (_outputQueue.size() > _outputDelay)
|
||||||
|
{
|
||||||
|
_ledDevice->write(_outputQueue.front());
|
||||||
|
_outputQueue.pop_front();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -23,7 +23,8 @@ public:
|
|||||||
/// @param LedDevice the led device
|
/// @param LedDevice the led device
|
||||||
/// @param LedUpdatFrequency The frequency at which the leds will be updated (Hz)
|
/// @param LedUpdatFrequency The frequency at which the leds will be updated (Hz)
|
||||||
/// @param settingTime The time after which the updated led values have been fully applied (sec)
|
/// @param settingTime The time after which the updated led values have been fully applied (sec)
|
||||||
LinearColorSmoothing(LedDevice *ledDevice, double ledUpdateFrequency, int settlingTime);
|
/// @param updateDelay The number of frames to delay outgoing led updates
|
||||||
|
LinearColorSmoothing(LedDevice *ledDevice, double ledUpdateFrequency, int settlingTime, unsigned updateDelay);
|
||||||
|
|
||||||
/// Destructor
|
/// Destructor
|
||||||
virtual ~LinearColorSmoothing();
|
virtual ~LinearColorSmoothing();
|
||||||
@ -43,6 +44,13 @@ private slots:
|
|||||||
void updateLeds();
|
void updateLeds();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
/**
|
||||||
|
* Pushes the colors into the output queue and popping the head to the led-device
|
||||||
|
*
|
||||||
|
* @param ledColors The colors to queue
|
||||||
|
*/
|
||||||
|
void queueColors(const std::vector<ColorRgb> & ledColors);
|
||||||
|
|
||||||
/// The led device
|
/// The led device
|
||||||
LedDevice * _ledDevice;
|
LedDevice * _ledDevice;
|
||||||
|
|
||||||
@ -66,4 +74,10 @@ private:
|
|||||||
|
|
||||||
/// The previously written led data
|
/// The previously written led data
|
||||||
std::vector<ColorRgb> _previousValues;
|
std::vector<ColorRgb> _previousValues;
|
||||||
|
|
||||||
|
/** The number of updates to keep in the output queue (delayed) before being output */
|
||||||
|
const unsigned _outputDelay;
|
||||||
|
/** The output queue */
|
||||||
|
std::list<std::vector<ColorRgb> > _outputQueue;
|
||||||
|
|
||||||
};
|
};
|
||||||
|
@ -10,6 +10,8 @@
|
|||||||
// Qt includes
|
// Qt includes
|
||||||
#include <QResource>
|
#include <QResource>
|
||||||
#include <QDateTime>
|
#include <QDateTime>
|
||||||
|
#include <QCryptographicHash>
|
||||||
|
#include <QHostInfo>
|
||||||
|
|
||||||
// hyperion util includes
|
// hyperion util includes
|
||||||
#include <hyperion/ImageProcessorFactory.h>
|
#include <hyperion/ImageProcessorFactory.h>
|
||||||
@ -25,7 +27,8 @@ JsonClientConnection::JsonClientConnection(QTcpSocket *socket, Hyperion * hyperi
|
|||||||
_socket(socket),
|
_socket(socket),
|
||||||
_imageProcessor(ImageProcessorFactory::getInstance().newImageProcessor()),
|
_imageProcessor(ImageProcessorFactory::getInstance().newImageProcessor()),
|
||||||
_hyperion(hyperion),
|
_hyperion(hyperion),
|
||||||
_receiveBuffer()
|
_receiveBuffer(),
|
||||||
|
_webSocketHandshakeDone(false)
|
||||||
{
|
{
|
||||||
// connect internal signals and slots
|
// connect internal signals and slots
|
||||||
connect(_socket, SIGNAL(disconnected()), this, SLOT(socketClosed()));
|
connect(_socket, SIGNAL(disconnected()), this, SLOT(socketClosed()));
|
||||||
@ -42,6 +45,19 @@ void JsonClientConnection::readData()
|
|||||||
{
|
{
|
||||||
_receiveBuffer += _socket->readAll();
|
_receiveBuffer += _socket->readAll();
|
||||||
|
|
||||||
|
if (_webSocketHandshakeDone)
|
||||||
|
{
|
||||||
|
// websocket mode, data frame
|
||||||
|
handleWebSocketFrame();
|
||||||
|
} else
|
||||||
|
{
|
||||||
|
// might be a handshake request or raw socket data
|
||||||
|
if(_receiveBuffer.contains("Upgrade: websocket"))
|
||||||
|
{
|
||||||
|
doWebSocketHandshake();
|
||||||
|
} else
|
||||||
|
{
|
||||||
|
// raw socket data, handling as usual
|
||||||
int bytes = _receiveBuffer.indexOf('\n') + 1;
|
int bytes = _receiveBuffer.indexOf('\n') + 1;
|
||||||
while(bytes > 0)
|
while(bytes > 0)
|
||||||
{
|
{
|
||||||
@ -57,10 +73,135 @@ void JsonClientConnection::readData()
|
|||||||
// try too look up '\n' again
|
// try too look up '\n' again
|
||||||
bytes = _receiveBuffer.indexOf('\n') + 1;
|
bytes = _receiveBuffer.indexOf('\n') + 1;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void JsonClientConnection::handleWebSocketFrame()
|
||||||
|
{
|
||||||
|
if ((_receiveBuffer.at(0) & 0x80) == 0x80)
|
||||||
|
{
|
||||||
|
// final bit found, frame complete
|
||||||
|
quint8 * maskKey = NULL;
|
||||||
|
quint8 opCode = _receiveBuffer.at(0) & 0x0F;
|
||||||
|
bool isMasked = (_receiveBuffer.at(1) & 0x80) == 0x80;
|
||||||
|
quint64 payloadLength = _receiveBuffer.at(1) & 0x7F;
|
||||||
|
quint32 index = 2;
|
||||||
|
|
||||||
|
switch (payloadLength)
|
||||||
|
{
|
||||||
|
case 126:
|
||||||
|
payloadLength = ((_receiveBuffer.at(2) << 8) & 0xFF00) | (_receiveBuffer.at(3) & 0xFF);
|
||||||
|
index += 2;
|
||||||
|
break;
|
||||||
|
case 127:
|
||||||
|
payloadLength = 0;
|
||||||
|
for (uint i=0; i < 8; i++) {
|
||||||
|
payloadLength |= ((quint64)(_receiveBuffer.at(index+i) & 0xFF)) << (8*(7-i));
|
||||||
|
}
|
||||||
|
index += 8;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isMasked)
|
||||||
|
{
|
||||||
|
// if the data is masked we need to get the key for unmasking
|
||||||
|
maskKey = new quint8[4];
|
||||||
|
for (uint i=0; i < 4; i++)
|
||||||
|
{
|
||||||
|
maskKey[i] = _receiveBuffer.at(index + i);
|
||||||
|
}
|
||||||
|
index += 4;
|
||||||
|
}
|
||||||
|
|
||||||
|
// check the type of data frame
|
||||||
|
switch (opCode)
|
||||||
|
{
|
||||||
|
case 0x01:
|
||||||
|
{
|
||||||
|
// frame contains text, extract it
|
||||||
|
QByteArray result = _receiveBuffer.mid(index, payloadLength);
|
||||||
|
_receiveBuffer.clear();
|
||||||
|
|
||||||
|
// unmask data if necessary
|
||||||
|
if (isMasked)
|
||||||
|
{
|
||||||
|
for (uint i=0; i < payloadLength; i++)
|
||||||
|
{
|
||||||
|
result[i] = (result[i] ^ maskKey[i % 4]);
|
||||||
|
}
|
||||||
|
if (maskKey != NULL)
|
||||||
|
{
|
||||||
|
delete[] maskKey;
|
||||||
|
maskKey = NULL;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
handleMessage(QString(result).toStdString());
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 0x08:
|
||||||
|
{
|
||||||
|
// close request, confirm
|
||||||
|
quint8 close[] = {0x88, 0};
|
||||||
|
_socket->write((const char*)close, 2);
|
||||||
|
_socket->flush();
|
||||||
|
_socket->close();
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 0x09:
|
||||||
|
{
|
||||||
|
// ping received, send pong
|
||||||
|
quint8 pong[] = {0x0A, 0};
|
||||||
|
_socket->write((const char*)pong, 2);
|
||||||
|
_socket->flush();
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
} else
|
||||||
|
{
|
||||||
|
std::cout << "Someone is sending very big messages over several frames... it's not supported yet" << std::endl;
|
||||||
|
quint8 close[] = {0x88, 0};
|
||||||
|
_socket->write((const char*)close, 2);
|
||||||
|
_socket->flush();
|
||||||
|
_socket->close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void JsonClientConnection::doWebSocketHandshake()
|
||||||
|
{
|
||||||
|
// http header, might not be a very reliable check...
|
||||||
|
std::cout << "Websocket handshake" << std::endl;
|
||||||
|
|
||||||
|
// get the key to prepare an answer
|
||||||
|
int start = _receiveBuffer.indexOf("Sec-WebSocket-Key") + 19;
|
||||||
|
std::string value(_receiveBuffer.mid(start, _receiveBuffer.indexOf("\r\n", start) - start).data());
|
||||||
|
_receiveBuffer.clear();
|
||||||
|
|
||||||
|
// must be always appended
|
||||||
|
value += "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
|
||||||
|
|
||||||
|
// generate sha1 hash
|
||||||
|
QByteArray hash = QCryptographicHash::hash(value.c_str(), QCryptographicHash::Sha1);
|
||||||
|
|
||||||
|
// prepare an answer
|
||||||
|
std::ostringstream h;
|
||||||
|
h << "HTTP/1.1 101 Switching Protocols\r\n" <<
|
||||||
|
"Upgrade: websocket\r\n" <<
|
||||||
|
"Connection: Upgrade\r\n" <<
|
||||||
|
"Sec-WebSocket-Accept: " << QString(hash.toBase64()).toStdString() << "\r\n\r\n";
|
||||||
|
|
||||||
|
_socket->write(h.str().c_str());
|
||||||
|
_socket->flush();
|
||||||
|
// we are in WebSocket mode, data frames should follow next
|
||||||
|
_webSocketHandshakeDone = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void JsonClientConnection::socketClosed()
|
void JsonClientConnection::socketClosed()
|
||||||
{
|
{
|
||||||
|
_webSocketHandshakeDone = false;
|
||||||
emit connectionClosed(this);
|
emit connectionClosed(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -206,6 +347,9 @@ void JsonClientConnection::handleServerInfoCommand(const Json::Value &)
|
|||||||
result["success"] = true;
|
result["success"] = true;
|
||||||
Json::Value & info = result["info"];
|
Json::Value & info = result["info"];
|
||||||
|
|
||||||
|
// add host name for remote clients
|
||||||
|
info["hostname"] = QHostInfo::localHostName().toStdString();
|
||||||
|
|
||||||
// collect priority information
|
// collect priority information
|
||||||
Json::Value & priorities = info["priorities"] = Json::Value(Json::arrayValue);
|
Json::Value & priorities = info["priorities"] = Json::Value(Json::arrayValue);
|
||||||
uint64_t now = QDateTime::currentMSecsSinceEpoch();
|
uint64_t now = QDateTime::currentMSecsSinceEpoch();
|
||||||
@ -362,7 +506,32 @@ void JsonClientConnection::sendMessage(const Json::Value &message)
|
|||||||
{
|
{
|
||||||
Json::FastWriter writer;
|
Json::FastWriter writer;
|
||||||
std::string serializedReply = writer.write(message);
|
std::string serializedReply = writer.write(message);
|
||||||
|
|
||||||
|
if (!_webSocketHandshakeDone)
|
||||||
|
{
|
||||||
|
// raw tcp socket mode
|
||||||
_socket->write(serializedReply.data(), serializedReply.length());
|
_socket->write(serializedReply.data(), serializedReply.length());
|
||||||
|
} else
|
||||||
|
{
|
||||||
|
// websocket mode
|
||||||
|
quint32 size = serializedReply.length();
|
||||||
|
|
||||||
|
// prepare data frame
|
||||||
|
QByteArray response;
|
||||||
|
response.append(0x81);
|
||||||
|
if (size > 125)
|
||||||
|
{
|
||||||
|
response.append(0x7E);
|
||||||
|
response.append((size >> 8) & 0xFF);
|
||||||
|
response.append(size & 0xFF);
|
||||||
|
} else {
|
||||||
|
response.append(size);
|
||||||
|
}
|
||||||
|
|
||||||
|
response.append(serializedReply.c_str(), serializedReply.length());
|
||||||
|
|
||||||
|
_socket->write(response.data(), response.length());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void JsonClientConnection::sendSuccessReply()
|
void JsonClientConnection::sendSuccessReply()
|
||||||
|
@ -137,6 +137,16 @@ private:
|
|||||||
///
|
///
|
||||||
void sendErrorReply(const std::string & error);
|
void sendErrorReply(const std::string & error);
|
||||||
|
|
||||||
|
///
|
||||||
|
/// Do handshake for a websocket connection
|
||||||
|
///
|
||||||
|
void doWebSocketHandshake();
|
||||||
|
|
||||||
|
///
|
||||||
|
/// Handle incoming websocket data frame
|
||||||
|
///
|
||||||
|
void handleWebSocketFrame();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
///
|
///
|
||||||
/// Check if a JSON messag is valid according to a given JSON schema
|
/// Check if a JSON messag is valid according to a given JSON schema
|
||||||
@ -161,4 +171,7 @@ private:
|
|||||||
|
|
||||||
/// The buffer used for reading data from the socket
|
/// The buffer used for reading data from the socket
|
||||||
QByteArray _receiveBuffer;
|
QByteArray _receiveBuffer;
|
||||||
|
|
||||||
|
/// used for WebSocket detection and connection handling
|
||||||
|
bool _webSocketHandshakeDone;
|
||||||
};
|
};
|
||||||
|
@ -57,6 +57,7 @@ if(ENABLE_SPIDEV)
|
|||||||
${CURRENT_SOURCE_DIR}/LedDeviceLpd8806.h
|
${CURRENT_SOURCE_DIR}/LedDeviceLpd8806.h
|
||||||
${CURRENT_SOURCE_DIR}/LedDeviceP9813.h
|
${CURRENT_SOURCE_DIR}/LedDeviceP9813.h
|
||||||
${CURRENT_SOURCE_DIR}/LedDeviceWs2801.h
|
${CURRENT_SOURCE_DIR}/LedDeviceWs2801.h
|
||||||
|
${CURRENT_SOURCE_DIR}/LedDeviceAPA102.h
|
||||||
)
|
)
|
||||||
SET(Leddevice_SOURCES
|
SET(Leddevice_SOURCES
|
||||||
${Leddevice_SOURCES}
|
${Leddevice_SOURCES}
|
||||||
@ -65,9 +66,21 @@ if(ENABLE_SPIDEV)
|
|||||||
${CURRENT_SOURCE_DIR}/LedDeviceLpd8806.cpp
|
${CURRENT_SOURCE_DIR}/LedDeviceLpd8806.cpp
|
||||||
${CURRENT_SOURCE_DIR}/LedDeviceP9813.cpp
|
${CURRENT_SOURCE_DIR}/LedDeviceP9813.cpp
|
||||||
${CURRENT_SOURCE_DIR}/LedDeviceWs2801.cpp
|
${CURRENT_SOURCE_DIR}/LedDeviceWs2801.cpp
|
||||||
|
${CURRENT_SOURCE_DIR}/LedDeviceAPA102.cpp
|
||||||
)
|
)
|
||||||
endif(ENABLE_SPIDEV)
|
endif(ENABLE_SPIDEV)
|
||||||
|
|
||||||
|
if(ENABLE_WS2812BPWM)
|
||||||
|
SET(Leddevice_HEADERS
|
||||||
|
${Leddevice_HEADERS}
|
||||||
|
${CURRENT_SOURCE_DIR}/LedDeviceWS2812b.h
|
||||||
|
)
|
||||||
|
SET(Leddevice_SOURCES
|
||||||
|
${Leddevice_SOURCES}
|
||||||
|
${CURRENT_SOURCE_DIR}/LedDeviceWS2812b.cpp
|
||||||
|
)
|
||||||
|
endif(ENABLE_WS2812BPWM)
|
||||||
|
|
||||||
if(ENABLE_TINKERFORGE)
|
if(ENABLE_TINKERFORGE)
|
||||||
SET(Leddevice_HEADERS
|
SET(Leddevice_HEADERS
|
||||||
${Leddevice_HEADERS}
|
${Leddevice_HEADERS}
|
||||||
|
42
libsrc/leddevice/LedDeviceAPA102.cpp
Normal file
42
libsrc/leddevice/LedDeviceAPA102.cpp
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
|
||||||
|
// STL includes
|
||||||
|
#include <cstring>
|
||||||
|
#include <cstdio>
|
||||||
|
#include <iostream>
|
||||||
|
|
||||||
|
// Linux includes
|
||||||
|
#include <fcntl.h>
|
||||||
|
#include <sys/ioctl.h>
|
||||||
|
|
||||||
|
// hyperion local includes
|
||||||
|
#include "LedDeviceAPA102.h"
|
||||||
|
|
||||||
|
LedDeviceAPA102::LedDeviceAPA102(const std::string& outputDevice, const unsigned baudrate) :
|
||||||
|
LedSpiDevice(outputDevice, baudrate, 500000),
|
||||||
|
_ledBuffer(0)
|
||||||
|
{
|
||||||
|
// empty
|
||||||
|
}
|
||||||
|
|
||||||
|
int LedDeviceAPA102::write(const std::vector<ColorRgb> &ledValues)
|
||||||
|
{
|
||||||
|
const unsigned int mLedCount = (ledValues.size() * 4) + 8;
|
||||||
|
if(_ledBuffer.size() != mLedCount){
|
||||||
|
_ledBuffer.resize(mLedCount, 0x00);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (unsigned iLed=1; iLed<=ledValues.size(); ++iLed) {
|
||||||
|
const ColorRgb& rgb = ledValues[iLed];
|
||||||
|
_ledBuffer[iLed*4] = 0xFF;
|
||||||
|
_ledBuffer[iLed*4+1] = rgb.red;
|
||||||
|
_ledBuffer[iLed*4+2] = rgb.green;
|
||||||
|
_ledBuffer[iLed*4+3] = rgb.blue;
|
||||||
|
}
|
||||||
|
|
||||||
|
return writeBytes(_ledBuffer.size(), _ledBuffer.data());
|
||||||
|
}
|
||||||
|
|
||||||
|
int LedDeviceAPA102::switchOff()
|
||||||
|
{
|
||||||
|
return write(std::vector<ColorRgb>(_ledBuffer.size(), ColorRgb{0,0,0}));
|
||||||
|
}
|
42
libsrc/leddevice/LedDeviceAPA102.h
Normal file
42
libsrc/leddevice/LedDeviceAPA102.h
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
// STL includes
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
// hyperion incluse
|
||||||
|
#include "LedSpiDevice.h"
|
||||||
|
|
||||||
|
///
|
||||||
|
/// Implementation of the LedDevice interface for writing to APA102 led device.
|
||||||
|
///
|
||||||
|
/// APA102 is
|
||||||
|
///
|
||||||
|
class LedDeviceAPA102 : public LedSpiDevice
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
///
|
||||||
|
/// Constructs the LedDevice for a string containing leds of the type APA102
|
||||||
|
///
|
||||||
|
/// @param outputDevice The name of the output device (eg '/etc/SpiDev.0.0')
|
||||||
|
/// @param baudrate The used baudrate for writing to the output device
|
||||||
|
///
|
||||||
|
LedDeviceAPA102(const std::string& outputDevice,
|
||||||
|
const unsigned baudrate);
|
||||||
|
|
||||||
|
///
|
||||||
|
/// 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<ColorRgb> &ledValues);
|
||||||
|
|
||||||
|
/// Switch the leds off
|
||||||
|
virtual int switchOff();
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
/// The buffer containing the packed RGB values
|
||||||
|
std::vector<uint8_t> _ledBuffer;
|
||||||
|
|
||||||
|
};
|
@ -14,6 +14,7 @@
|
|||||||
#include "LedDeviceLpd8806.h"
|
#include "LedDeviceLpd8806.h"
|
||||||
#include "LedDeviceP9813.h"
|
#include "LedDeviceP9813.h"
|
||||||
#include "LedDeviceWs2801.h"
|
#include "LedDeviceWs2801.h"
|
||||||
|
#include "LedDeviceAPA102.h"
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#ifdef ENABLE_TINKERFORGE
|
#ifdef ENABLE_TINKERFORGE
|
||||||
@ -31,6 +32,10 @@
|
|||||||
#include "LedDevicePhilipsHue.h"
|
#include "LedDevicePhilipsHue.h"
|
||||||
#include "LedDeviceTpm2.h"
|
#include "LedDeviceTpm2.h"
|
||||||
|
|
||||||
|
#ifdef ENABLE_WS2812BPWM
|
||||||
|
#include "LedDeviceWS2812b.h"
|
||||||
|
#endif
|
||||||
|
|
||||||
LedDevice * LedDeviceFactory::construct(const Json::Value & deviceConfig)
|
LedDevice * LedDeviceFactory::construct(const Json::Value & deviceConfig)
|
||||||
{
|
{
|
||||||
std::cout << "Device configuration: " << deviceConfig << std::endl;
|
std::cout << "Device configuration: " << deviceConfig << std::endl;
|
||||||
@ -82,6 +87,16 @@ LedDevice * LedDeviceFactory::construct(const Json::Value & deviceConfig)
|
|||||||
|
|
||||||
device = deviceP9813;
|
device = deviceP9813;
|
||||||
}
|
}
|
||||||
|
else if (type == "apa102")
|
||||||
|
{
|
||||||
|
const std::string output = deviceConfig["output"].asString();
|
||||||
|
const unsigned rate = deviceConfig["rate"].asInt();
|
||||||
|
|
||||||
|
LedDeviceAPA102* deviceAPA102 = new LedDeviceAPA102(output, rate);
|
||||||
|
deviceAPA102->open();
|
||||||
|
|
||||||
|
device = deviceAPA102;
|
||||||
|
}
|
||||||
else if (type == "ws2801" || type == "lightberry")
|
else if (type == "ws2801" || type == "lightberry")
|
||||||
{
|
{
|
||||||
const std::string output = deviceConfig["output"].asString();
|
const std::string output = deviceConfig["output"].asString();
|
||||||
@ -178,10 +193,17 @@ LedDevice * LedDeviceFactory::construct(const Json::Value & deviceConfig)
|
|||||||
const std::string output = deviceConfig["output"].asString();
|
const std::string output = deviceConfig["output"].asString();
|
||||||
const unsigned rate = deviceConfig["rate"].asInt();
|
const unsigned rate = deviceConfig["rate"].asInt();
|
||||||
|
|
||||||
LedDeviceTpm2* deviceTpm2 = new LedDeviceTpm2(output, rate);
|
LedDeviceTpm2 * deviceTpm2 = new LedDeviceTpm2(output, rate);
|
||||||
deviceTpm2->open();
|
deviceTpm2->open();
|
||||||
device = deviceTpm2;
|
device = deviceTpm2;
|
||||||
}
|
}
|
||||||
|
#ifdef ENABLE_WS2812BPWM
|
||||||
|
else if (type == "ws2812b")
|
||||||
|
{
|
||||||
|
LedDeviceWS2812b * ledDeviceWS2812b = new LedDeviceWS2812b();
|
||||||
|
device = ledDeviceWS2812b;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
std::cout << "Unable to create device " << type << std::endl;
|
std::cout << "Unable to create device " << type << std::endl;
|
||||||
|
883
libsrc/leddevice/LedDeviceWS2812b.cpp
Normal file
883
libsrc/leddevice/LedDeviceWS2812b.cpp
Normal file
@ -0,0 +1,883 @@
|
|||||||
|
// For license and other informations see LedDeviceWS2812b.h
|
||||||
|
// To activate: use led device "ws2812s" in the hyperion configuration
|
||||||
|
|
||||||
|
// STL includes
|
||||||
|
#include <cstring>
|
||||||
|
#include <cstdio>
|
||||||
|
#include <iostream>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
// Linux includes
|
||||||
|
#include <fcntl.h>
|
||||||
|
#include <stdarg.h>
|
||||||
|
#include <sys/mman.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
//#include <sys/types.h>
|
||||||
|
//#include <sys/ioctl.h>
|
||||||
|
|
||||||
|
#ifdef BENCHMARK
|
||||||
|
#include <time.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// hyperion local includes
|
||||||
|
#include "LedDeviceWS2812b.h"
|
||||||
|
|
||||||
|
// ==== Defines and Vars ====
|
||||||
|
|
||||||
|
// Base addresses for GPIO, PWM, PWM clock, and DMA controllers (physical, not bus!)
|
||||||
|
// These will be "memory mapped" into virtual RAM so that they can be written and read directly.
|
||||||
|
// -------------------------------------------------------------------------------------------------
|
||||||
|
#define DMA_BASE 0x20007000
|
||||||
|
#define DMA_LEN 0x24
|
||||||
|
#define PWM_BASE 0x2020C000
|
||||||
|
#define PWM_LEN 0x28
|
||||||
|
#define CLK_BASE 0x20101000
|
||||||
|
#define CLK_LEN 0xA8
|
||||||
|
#define GPIO_BASE 0x20200000
|
||||||
|
#define GPIO_LEN 0xB4
|
||||||
|
|
||||||
|
// GPIO
|
||||||
|
// -------------------------------------------------------------------------------------------------
|
||||||
|
#define GPFSEL0 0x20200000 // GPIO function select, pins 0-9 (bits 30-31 reserved)
|
||||||
|
#define GPFSEL1 0x20200004 // Pins 10-19
|
||||||
|
#define GPFSEL2 0x20200008 // Pins 20-29
|
||||||
|
#define GPFSEL3 0x2020000C // Pins 30-39
|
||||||
|
#define GPFSEL4 0x20200010 // Pins 40-49
|
||||||
|
#define GPFSEL5 0x20200014 // Pins 50-53
|
||||||
|
#define GPSET0 0x2020001C // Set (turn on) pin
|
||||||
|
#define GPCLR0 0x20200028 // Clear (turn off) pin
|
||||||
|
#define GPPUD 0x20200094 // Internal pullup/pulldown resistor control
|
||||||
|
#define GPPUDCLK0 0x20200098 // PUD clock for pins 0-31
|
||||||
|
#define GPPUDCLK1 0x2020009C // PUD clock for pins 32-53
|
||||||
|
|
||||||
|
// Memory offsets for the PWM clock register, which is undocumented! Please fix that, Broadcom!
|
||||||
|
// -------------------------------------------------------------------------------------------------
|
||||||
|
#define PWM_CLK_CNTL 40 // Control (on/off)
|
||||||
|
#define PWM_CLK_DIV 41 // Divisor (bits 11:0 are *quantized* floating part, 31:12 integer part)
|
||||||
|
|
||||||
|
// PWM Register Addresses (page 141)
|
||||||
|
// These are divided by 4 because the register offsets in the guide are in bytes (8 bits) but
|
||||||
|
// the pointers we use in this program are in words (32 bits). Buss' original defines are in
|
||||||
|
// word offsets, e.g. PWM_RNG1 was 4 and PWM_DAT1 was 5. This is functionally the same, but it
|
||||||
|
// matches the numbers supplied in the guide.
|
||||||
|
// -------------------------------------------------------------------------------------------------
|
||||||
|
#define PWM_CTL 0x00 // Control Register
|
||||||
|
#define PWM_STA (0x04 / 4) // Status Register
|
||||||
|
#define PWM_DMAC (0x08 / 4) // DMA Control Register
|
||||||
|
#define PWM_RNG1 (0x10 / 4) // Channel 1 Range
|
||||||
|
#define PWM_DAT1 (0x14 / 4) // Channel 1 Data
|
||||||
|
#define PWM_FIF1 (0x18 / 4) // FIFO (for both channels - bytes are interleaved if both active)
|
||||||
|
#define PWM_RNG2 (0x20 / 4) // Channel 2 Range
|
||||||
|
#define PWM_DAT2 (0x24 / 4) // Channel 2 Data
|
||||||
|
|
||||||
|
// PWM_CTL register bit offsets
|
||||||
|
// Note: Don't use MSEN1/2 for this purpose. It will screw things up.
|
||||||
|
// -------------------------------------------------------------------------------------------------
|
||||||
|
#define PWM_CTL_MSEN2 15 // Channel 2 - 0: Use PWM algorithm. 1: Use M/S (serial) algorithm.
|
||||||
|
#define PWM_CTL_USEF2 13 // Channel 2 - 0: Use PWM_DAT2. 1: Use FIFO.
|
||||||
|
#define PWM_CTL_POLA2 12 // Channel 2 - Invert output polarity (if set, 0=high and 1=low)
|
||||||
|
#define PWM_CTL_SBIT2 11 // Channel 2 - Silence bit (default line state when not transmitting)
|
||||||
|
#define PWM_CTL_RPTL2 10 // Channel 2 - Repeat last data in FIFO
|
||||||
|
#define PWM_CTL_MODE2 9 // Channel 2 - Mode. 0=PWM, 1=Serializer
|
||||||
|
#define PWM_CTL_PWEN2 8 // Channel 2 - Enable PWM
|
||||||
|
#define PWM_CTL_CLRF1 6 // Clear FIFO
|
||||||
|
#define PWM_CTL_MSEN1 7 // Channel 1 - 0: Use PWM algorithm. 1: Use M/S (serial) algorithm.
|
||||||
|
#define PWM_CTL_USEF1 5 // Channel 1 - 0: Use PWM_DAT1. 1: Use FIFO.
|
||||||
|
#define PWM_CTL_POLA1 4 // Channel 1 - Invert output polarity (if set, 0=high and 1=low)
|
||||||
|
#define PWM_CTL_SBIT1 3 // Channel 1 - Silence bit (default line state when not transmitting)
|
||||||
|
#define PWM_CTL_RPTL1 2 // Channel 1 - Repeat last data in FIFO
|
||||||
|
#define PWM_CTL_MODE1 1 // Channel 1 - Mode. 0=PWM, 1=Serializer
|
||||||
|
#define PWM_CTL_PWEN1 0 // Channel 1 - Enable PWM
|
||||||
|
|
||||||
|
// PWM_STA register bit offsets
|
||||||
|
// -------------------------------------------------------------------------------------------------
|
||||||
|
#define PWM_STA_STA4 12 // Channel 4 State
|
||||||
|
#define PWM_STA_STA3 11 // Channel 3 State
|
||||||
|
#define PWM_STA_STA2 10 // Channel 2 State
|
||||||
|
#define PWM_STA_STA1 9 // Channel 1 State
|
||||||
|
#define PWM_STA_BERR 8 // Bus Error
|
||||||
|
#define PWM_STA_GAPO4 7 // Gap Occurred on Channel 4
|
||||||
|
#define PWM_STA_GAPO3 6 // Gap Occurred on Channel 3
|
||||||
|
#define PWM_STA_GAPO2 5 // Gap Occurred on Channel 2
|
||||||
|
#define PWM_STA_GAPO1 4 // Gap Occurred on Channel 1
|
||||||
|
#define PWM_STA_RERR1 3 // FIFO Read Error
|
||||||
|
#define PWM_STA_WERR1 2 // FIFO Write Error
|
||||||
|
#define PWM_STA_EMPT1 1 // FIFO Empty
|
||||||
|
#define PWM_STA_FULL1 0 // FIFO Full
|
||||||
|
|
||||||
|
// PWM_DMAC bit offsets
|
||||||
|
// -------------------------------------------------------------------------------------------------
|
||||||
|
#define PWM_DMAC_ENAB 31 // 0: DMA Disabled. 1: DMA Enabled.
|
||||||
|
#define PWM_DMAC_PANIC 8 // Bits 15:8. Threshold for PANIC signal. Default 7.
|
||||||
|
#define PWM_DMAC_DREQ 0 // Bits 7:0. Threshold for DREQ signal. Default 7.
|
||||||
|
|
||||||
|
// PWM_RNG1, PWM_RNG2
|
||||||
|
// --------------------------------------------------------------------------------------------------
|
||||||
|
// Defines the transmission range. In PWM mode, evenly spaced pulses are sent within a period
|
||||||
|
// of length defined in these registers. In serial mode, serialized data is sent within the
|
||||||
|
// same period. The value is normally 32. If less, data will be truncated. If more, data will
|
||||||
|
// be padded with zeros.
|
||||||
|
|
||||||
|
// DAT1, DAT2
|
||||||
|
// --------------------------------------------------------------------------------------------------
|
||||||
|
// NOTE: These registers are not useful for our purposes - we will use the FIFO instead!
|
||||||
|
// Stores 32 bits of data to be sent when USEF1/USEF2 is 0. In PWM mode, defines how many
|
||||||
|
// pulses will be sent within the period specified in PWM_RNG1/PWM_RNG2. In serializer mode,
|
||||||
|
// defines a 32-bit word to be transmitted.
|
||||||
|
|
||||||
|
// FIF1
|
||||||
|
// --------------------------------------------------------------------------------------------------
|
||||||
|
// 32-bit-wide register used to "stuff" the FIFO, which has 16 32-bit words. (So, if you write
|
||||||
|
// it 16 times, it will fill the FIFO.)
|
||||||
|
// See also: PWM_STA_EMPT1 (FIFO empty)
|
||||||
|
// PWM_STA_FULL1 (FIFO full)
|
||||||
|
// PWM_CTL_CLRF1 (Clear FIFO)
|
||||||
|
|
||||||
|
// DMA
|
||||||
|
// --------------------------------------------------------------------------------------------------
|
||||||
|
// DMA registers (divided by four to convert form word to byte offsets, as with the PWM registers)
|
||||||
|
#define DMA_CS (0x00 / 4) // Control & Status register
|
||||||
|
#define DMA_CONBLK_AD (0x04 / 4) // Address of Control Block (must be 256-BYTE ALIGNED!!!)
|
||||||
|
#define DMA_TI (0x08 / 4) // Transfer Information (populated from CB)
|
||||||
|
#define DMA_SOURCE_AD (0x0C / 4) // Source address, populated from CB. Physical address.
|
||||||
|
#define DMA_DEST_AD (0x10 / 4) // Destination address, populated from CB. Bus address.
|
||||||
|
#define DMA_TXFR_LEN (0x14 / 4) // Transfer length, populated from CB
|
||||||
|
#define DMA_STRIDE (0x18 / 4) // Stride, populated from CB
|
||||||
|
#define DMA_NEXTCONBK (0x1C / 4) // Next control block address, populated from CB
|
||||||
|
#define DMA_DEBUG (0x20 / 4) // Debug settings
|
||||||
|
|
||||||
|
// DMA Control & Status register bit offsets
|
||||||
|
#define DMA_CS_RESET 31 // Reset the controller for this channel
|
||||||
|
#define DMA_CS_ABORT 30 // Set to abort transfer
|
||||||
|
#define DMA_CS_DISDEBUG 29 // Disable debug pause signal
|
||||||
|
#define DMA_CS_WAIT_FOR 28 // Wait for outstanding writes
|
||||||
|
#define DMA_CS_PANIC_PRI 20 // Panic priority (bits 23:20), default 7
|
||||||
|
#define DMA_CS_PRIORITY 16 // AXI priority level (bits 19:16), default 7
|
||||||
|
#define DMA_CS_ERROR 8 // Set when there's been an error
|
||||||
|
#define DMA_CS_WAITING_FOR 6 // Set when the channel's waiting for a write to be accepted
|
||||||
|
#define DMA_CS_DREQ_STOPS_DMA 5 // Set when the DMA is paused because DREQ is inactive
|
||||||
|
#define DMA_CS_PAUSED 4 // Set when the DMA is paused (active bit cleared, etc.)
|
||||||
|
#define DMA_CS_DREQ 3 // Set when DREQ line is high
|
||||||
|
#define DMA_CS_INT 2 // If INTEN is set, this will be set on CB transfer end
|
||||||
|
#define DMA_CS_END 1 // Set when the current control block is finished
|
||||||
|
#define DMA_CS_ACTIVE 0 // Enable DMA (CB_ADDR must not be 0)
|
||||||
|
// Default CS word
|
||||||
|
#define DMA_CS_CONFIGWORD (8 << DMA_CS_PANIC_PRI) | \
|
||||||
|
(8 << DMA_CS_PRIORITY) | \
|
||||||
|
(1 << DMA_CS_WAIT_FOR)
|
||||||
|
|
||||||
|
// DREQ lines (page 61, most DREQs omitted)
|
||||||
|
#define DMA_DREQ_ALWAYS 0
|
||||||
|
#define DMA_DREQ_PCM_TX 2
|
||||||
|
#define DMA_DREQ_PCM_RX 3
|
||||||
|
#define DMA_DREQ_PWM 5
|
||||||
|
#define DMA_DREQ_SPI_TX 6
|
||||||
|
#define DMA_DREQ_SPI_RX 7
|
||||||
|
#define DMA_DREQ_BSC_TX 8
|
||||||
|
#define DMA_DREQ_BSC_RX 9
|
||||||
|
|
||||||
|
// DMA Transfer Information register bit offsets
|
||||||
|
// We don't write DMA_TI directly. It's populated from the TI field in a control block.
|
||||||
|
#define DMA_TI_NO_WIDE_BURSTS 26 // Don't do wide writes in 2-beat bursts
|
||||||
|
#define DMA_TI_WAITS 21 // Wait this many cycles after end of each read/write
|
||||||
|
#define DMA_TI_PERMAP 16 // Peripheral # whose ready signal controls xfer rate (pwm=5)
|
||||||
|
#define DMA_TI_BURST_LENGTH 12 // Length of burst in words (bits 15:12)
|
||||||
|
#define DMA_TI_SRC_IGNORE 11 // Don't perform source reads (for fast cache fill)
|
||||||
|
#define DMA_TI_SRC_DREQ 10 // Peripheral in PERMAP gates source reads
|
||||||
|
#define DMA_TI_SRC_WIDTH 9 // Source transfer width - 0=32 bits, 1=128 bits
|
||||||
|
#define DMA_TI_SRC_INC 8 // Source address += SRC_WITH after each read
|
||||||
|
#define DMA_TI_DEST_IGNORE 7 // Don't perform destination writes
|
||||||
|
#define DMA_TI_DEST_DREQ 6 // Peripheral in PERMAP gates destination writes
|
||||||
|
#define DMA_TI_DEST_WIDTH 5 // Destination transfer width - 0=32 bits, 1=128 bits
|
||||||
|
#define DMA_TI_DEST_INC 4 // Dest address += DEST_WIDTH after each read
|
||||||
|
#define DMA_TI_WAIT_RESP 3 // Wait for write response
|
||||||
|
#define DMA_TI_TDMODE 1 // 2D striding mode
|
||||||
|
#define DMA_TI_INTEN 0 // Interrupt enable
|
||||||
|
// Default TI word
|
||||||
|
#define DMA_TI_CONFIGWORD (1 << DMA_TI_NO_WIDE_BURSTS) | \
|
||||||
|
(1 << DMA_TI_SRC_INC) | \
|
||||||
|
(1 << DMA_TI_DEST_DREQ) | \
|
||||||
|
(1 << DMA_TI_WAIT_RESP) | \
|
||||||
|
(1 << DMA_TI_INTEN) | \
|
||||||
|
(DMA_DREQ_PWM << DMA_TI_PERMAP)
|
||||||
|
|
||||||
|
// DMA Debug register bit offsets
|
||||||
|
#define DMA_DEBUG_LITE 28 // Whether the controller is "Lite"
|
||||||
|
#define DMA_DEBUG_VERSION 25 // DMA Version (bits 27:25)
|
||||||
|
#define DMA_DEBUG_DMA_STATE 16 // DMA State (bits 24:16)
|
||||||
|
#define DMA_DEBUG_DMA_ID 8 // DMA controller's AXI bus ID (bits 15:8)
|
||||||
|
#define DMA_DEBUG_OUTSTANDING_WRITES 4 // Outstanding writes (bits 7:4)
|
||||||
|
#define DMA_DEBUG_READ_ERROR 2 // Slave read response error (clear by setting)
|
||||||
|
#define DMA_DEBUG_FIFO_ERROR 1 // Operational read FIFO error (clear by setting)
|
||||||
|
#define DMA_DEBUG_READ_LAST_NOT_SET 0 // AXI bus read last signal not set (clear by setting)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#define PAGE_SIZE 4096 // Size of a RAM page to be allocated
|
||||||
|
#define PAGE_SHIFT 12 // This is used for address translation
|
||||||
|
#define NUM_PAGES ((sizeof(struct control_data_s) + PAGE_SIZE - 1) >> PAGE_SHIFT)
|
||||||
|
|
||||||
|
#define SETBIT(word, bit) word |= 1<<bit
|
||||||
|
#define CLRBIT(word, bit) word &= ~(1<<bit)
|
||||||
|
#define GETBIT(word, bit) word & (1 << bit) ? 1 : 0
|
||||||
|
#define true 1
|
||||||
|
#define false 0
|
||||||
|
|
||||||
|
// GPIO
|
||||||
|
#define INP_GPIO(g) *(gpio_reg+((g)/10)) &= ~(7<<(((g)%10)*3))
|
||||||
|
#define OUT_GPIO(g) *(gpio_reg+((g)/10)) |= (1<<(((g)%10)*3))
|
||||||
|
#define SET_GPIO_ALT(g,a) *(gpio_reg+(((g)/10))) |= (((a)<=3?(a)+4:(a)==4?3:2)<<(((g)%10)*3))
|
||||||
|
#define GPIO_SET *(gpio_reg+7) // sets bits which are 1 ignores bits which are 0
|
||||||
|
#define GPIO_CLR *(gpio_reg+10) // clears bits which are 1 ignores bits which are 0
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
LedDeviceWS2812b::LedDeviceWS2812b() :
|
||||||
|
LedDevice(),
|
||||||
|
mLedCount(0)
|
||||||
|
|
||||||
|
#ifdef BENCHMARK
|
||||||
|
,
|
||||||
|
runCount(0),
|
||||||
|
combinedNseconds(0),
|
||||||
|
shortestNseconds(2147483647)
|
||||||
|
#endif
|
||||||
|
|
||||||
|
{
|
||||||
|
//shortestNseconds = 2147483647;
|
||||||
|
// Init PWM generator and clear LED buffer
|
||||||
|
initHardware();
|
||||||
|
//clearLEDBuffer();
|
||||||
|
|
||||||
|
// init bit pattern, it is always 1X0
|
||||||
|
unsigned int wireBit = 0;
|
||||||
|
|
||||||
|
while ((wireBit + 3) < ((NUM_DATA_WORDS) * 4 * 8))
|
||||||
|
{
|
||||||
|
setPWMBit(wireBit++, 1);
|
||||||
|
setPWMBit(wireBit++, 0); // just init it with 0
|
||||||
|
setPWMBit(wireBit++, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
printf("WS2812b init finished \n");
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifdef WS2812_ASM_OPTI
|
||||||
|
|
||||||
|
// rotate register, used to move the 1 around :-)
|
||||||
|
static inline __attribute__((always_inline)) uint32_t arm_ror_imm(uint32_t v, uint32_t sh)
|
||||||
|
{
|
||||||
|
uint32_t d;
|
||||||
|
asm ("ROR %[Rd], %[Rm], %[Is]" : [Rd] "=r" (d) : [Rm] "r" (v), [Is] "r" (sh));
|
||||||
|
return d;
|
||||||
|
}
|
||||||
|
|
||||||
|
// rotate register, used to move the 1 around, add 1 to int counter on carry
|
||||||
|
static inline __attribute__((always_inline)) uint32_t arm_ror_imm_add_on_carry(uint32_t v, uint32_t sh, uint32_t inc)
|
||||||
|
{
|
||||||
|
uint32_t d;
|
||||||
|
asm ("RORS %[Rd], %[Rm], %[Is]\n\t"
|
||||||
|
"ADDCS %[Rd1], %[Rd1], #1"
|
||||||
|
: [Rd] "=r" (d), [Rd1] "+r" (inc): [Rm] "r" (v), [Is] "r" (sh));
|
||||||
|
return d;
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline __attribute__((always_inline)) uint32_t arm_ror(uint32_t v, uint32_t sh)
|
||||||
|
{
|
||||||
|
uint32_t d;
|
||||||
|
asm ("ROR %[Rd], %[Rm], %[Rs]" : [Rd] "=r" (d) : [Rm] "r" (v), [Rs] "r" (sh));
|
||||||
|
return d;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static inline __attribute__((always_inline)) uint32_t arm_Bit_Clear_imm(uint32_t v, uint32_t v2)
|
||||||
|
{
|
||||||
|
uint32_t d;
|
||||||
|
asm ("BIC %[Rd], %[Rm], %[Rs]" : [Rd] "=r" (d) : [Rm] "r" (v), [Rs] "r" (v2));
|
||||||
|
return d;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
int LedDeviceWS2812b::write(const std::vector<ColorRgb> &ledValues)
|
||||||
|
{
|
||||||
|
#ifdef BENCHMARK
|
||||||
|
timespec timeStart;
|
||||||
|
timespec timeEnd;
|
||||||
|
clock_gettime(CLOCK_PROCESS_CPUTIME_ID, &timeStart);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
mLedCount = ledValues.size();
|
||||||
|
|
||||||
|
// Read data from LEDBuffer[], translate it into wire format, and write to PWMWaveform
|
||||||
|
unsigned int colorBits = 0; // Holds the GRB color before conversion to wire bit pattern
|
||||||
|
unsigned int wireBit = 1; // Holds the current bit we will set in PWMWaveform, start with 1 and skip the other two for speed
|
||||||
|
|
||||||
|
// Copy PWM waveform to DMA's data buffer
|
||||||
|
//printf("Copying %d words to DMA data buffer\n", NUM_DATA_WORDS);
|
||||||
|
struct control_data_s *ctl = (struct control_data_s *)virtbase;
|
||||||
|
dma_cb_t *cbp = ctl->cb;
|
||||||
|
|
||||||
|
// 72 bits per pixel / 32 bits per word = 2.25 words per pixel
|
||||||
|
// Add 1 to make sure the PWM FIFO gets the message: "we're sending zeroes"
|
||||||
|
// Times 4 because DMA works in bytes, not words
|
||||||
|
cbp->length = ((mLedCount * 2.25) + 1) * 4;
|
||||||
|
if(cbp->length > NUM_DATA_WORDS * 4)
|
||||||
|
{
|
||||||
|
cbp->length = NUM_DATA_WORDS * 4;
|
||||||
|
mLedCount = (NUM_DATA_WORDS - 1) / 2.25;
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifdef WS2812_ASM_OPTI
|
||||||
|
unsigned int startbitPattern = 0x40000000; // = 0100 0000 0000 0000 0000 0000 0000 0000 pattern
|
||||||
|
#endif
|
||||||
|
|
||||||
|
|
||||||
|
for(size_t i=0; i<mLedCount; i++)
|
||||||
|
{
|
||||||
|
// Create bits necessary to represent one color triplet (in GRB, not RGB, order)
|
||||||
|
//printf("RGB: %d, %d, %d\n", ledValues[i].red, ledValues[i].green, ledValues[i].blue);
|
||||||
|
colorBits = ((unsigned int)ledValues[i].red << 8) | ((unsigned int)ledValues[i].green << 16) | ledValues[i].blue;
|
||||||
|
//printBinary(colorBits, 24);
|
||||||
|
//printf(" (binary, GRB order)\n");
|
||||||
|
|
||||||
|
// Iterate through color bits to get wire bits
|
||||||
|
for(int j=23; j>=0; j--) {
|
||||||
|
#ifdef WS2812_ASM_OPTI
|
||||||
|
// Fetch word the bit is in
|
||||||
|
unsigned int wordOffset = (int)(wireBit / 32);
|
||||||
|
wireBit +=3;
|
||||||
|
|
||||||
|
if (colorBits & (1 << j)) {
|
||||||
|
PWMWaveform[wordOffset] |= startbitPattern;
|
||||||
|
} else {
|
||||||
|
PWMWaveform[wordOffset] = arm_Bit_Clear_imm(PWMWaveform[wordOffset], startbitPattern);
|
||||||
|
}
|
||||||
|
|
||||||
|
startbitPattern = arm_ror_imm(startbitPattern, 3);
|
||||||
|
#else
|
||||||
|
unsigned char colorBit = (colorBits & (1 << j)) ? 1 : 0; // Holds current bit out of colorBits to be processed
|
||||||
|
setPWMBit(wireBit, colorBit);
|
||||||
|
wireBit +=3;
|
||||||
|
#endif
|
||||||
|
/* old code for better understanding
|
||||||
|
switch(colorBit) {
|
||||||
|
case 1:
|
||||||
|
//wireBits = 0b110; // High, High, Low
|
||||||
|
setPWMBit(wireBit++, 1);
|
||||||
|
setPWMBit(wireBit++, 1);
|
||||||
|
setPWMBit(wireBit++, 0);
|
||||||
|
break;
|
||||||
|
case 0:
|
||||||
|
//wireBits = 0b100; // High, Low, Low
|
||||||
|
setPWMBit(wireBit++, 1);
|
||||||
|
setPWMBit(wireBit++, 0);
|
||||||
|
setPWMBit(wireBit++, 0);
|
||||||
|
break;
|
||||||
|
}*/
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifdef WS2812_ASM_OPTI
|
||||||
|
// calculate the bits manually since it is not needed with asm
|
||||||
|
//wireBit += mLedCount * 24 *3;
|
||||||
|
//printf(" %d\n", wireBit);
|
||||||
|
#endif
|
||||||
|
//remove one to undo optimization
|
||||||
|
wireBit --;
|
||||||
|
|
||||||
|
#ifdef WS2812_ASM_OPTI
|
||||||
|
int rest = 32 - wireBit % 32; // 64: 32 - used Bits
|
||||||
|
startbitPattern = (1 << (rest-1)); // set new bitpattern to start at the benigining of one bit (3 bit in wave form)
|
||||||
|
rest += 32; // add one int extra for pwm
|
||||||
|
|
||||||
|
// printBinary(startbitPattern, 32);
|
||||||
|
// printf(" startbit\n");
|
||||||
|
|
||||||
|
unsigned int oldwireBitValue = wireBit;
|
||||||
|
unsigned int oldbitPattern = startbitPattern;
|
||||||
|
|
||||||
|
// zero rest of the 4 bytes / int so that output is 0 (no data is send)
|
||||||
|
for (int i = 0; i < rest; i += 3)
|
||||||
|
{
|
||||||
|
unsigned int wordOffset = (int)(wireBit / 32);
|
||||||
|
wireBit += 3;
|
||||||
|
PWMWaveform[wordOffset] = arm_Bit_Clear_imm(PWMWaveform[wordOffset], startbitPattern);
|
||||||
|
startbitPattern = arm_ror_imm(startbitPattern, 3);
|
||||||
|
}
|
||||||
|
|
||||||
|
#else
|
||||||
|
// fill up the bytes
|
||||||
|
int rest = 32 - wireBit % 32 + 32; // 64: 32 - used Bits + 32 (one int extra for pwm)
|
||||||
|
unsigned int oldwireBitValue = wireBit;
|
||||||
|
|
||||||
|
// zero rest of the 4 bytes / int so that output is 0 (no data is send)
|
||||||
|
for (int i = 0; i < rest; i += 3)
|
||||||
|
{
|
||||||
|
setPWMBit(wireBit, 0);
|
||||||
|
wireBit += 3;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
memcpy ( ctl->sample, PWMWaveform, cbp->length );
|
||||||
|
|
||||||
|
// Enable DMA and PWM engines, which should now send the data
|
||||||
|
startTransfer();
|
||||||
|
|
||||||
|
// restore bit pattern
|
||||||
|
wireBit = oldwireBitValue;
|
||||||
|
|
||||||
|
#ifdef WS2812_ASM_OPTI
|
||||||
|
startbitPattern = oldbitPattern;
|
||||||
|
for (int i = 0; i < rest; i += 3)
|
||||||
|
{
|
||||||
|
unsigned int wordOffset = (int)(wireBit / 32);
|
||||||
|
wireBit += 3;
|
||||||
|
PWMWaveform[wordOffset] |= startbitPattern;
|
||||||
|
startbitPattern = arm_ror_imm(startbitPattern, 3);
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
for (int i = 0; i < rest; i += 3)
|
||||||
|
{
|
||||||
|
setPWMBit(wireBit, 1);
|
||||||
|
wireBit += 3;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifdef BENCHMARK
|
||||||
|
clock_gettime(CLOCK_PROCESS_CPUTIME_ID, &timeEnd);
|
||||||
|
timespec result;
|
||||||
|
|
||||||
|
result.tv_sec = timeEnd.tv_sec - timeStart.tv_sec;
|
||||||
|
result.tv_nsec = timeEnd.tv_nsec - timeStart.tv_nsec;
|
||||||
|
if (result.tv_nsec < 0)
|
||||||
|
{
|
||||||
|
result.tv_nsec = 1e9 - result.tv_nsec;
|
||||||
|
result.tv_sec -= 1;
|
||||||
|
}
|
||||||
|
runCount ++;
|
||||||
|
combinedNseconds += result.tv_nsec;
|
||||||
|
shortestNseconds = result.tv_nsec < shortestNseconds ? result.tv_nsec : shortestNseconds;
|
||||||
|
#endif
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int LedDeviceWS2812b::switchOff()
|
||||||
|
{
|
||||||
|
return write(std::vector<ColorRgb>(mLedCount, ColorRgb{0,0,0}));
|
||||||
|
}
|
||||||
|
|
||||||
|
LedDeviceWS2812b::~LedDeviceWS2812b()
|
||||||
|
{
|
||||||
|
// Exit cleanly, freeing memory and stopping the DMA & PWM engines
|
||||||
|
terminate(0);
|
||||||
|
#ifdef BENCHMARK
|
||||||
|
printf("WS2812b Benchmark results: Runs %d - Avarage %lu (n) - Minimum %ld (n)\n",
|
||||||
|
runCount, (runCount > 0 ? combinedNseconds / runCount : 0), shortestNseconds);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// =================================================================================================
|
||||||
|
// ________ .__
|
||||||
|
// / _____/ ____ ____ ________________ | |
|
||||||
|
// / \ ____/ __ \ / \_/ __ \_ __ \__ \ | |
|
||||||
|
// \ \_\ \ ___/| | \ ___/| | \// __ \| |__
|
||||||
|
// \______ /\___ >___| /\___ >__| (____ /____/
|
||||||
|
// \/ \/ \/ \/ \/
|
||||||
|
// =================================================================================================
|
||||||
|
|
||||||
|
// Convenience functions
|
||||||
|
// --------------------------------------------------------------------------------------------------
|
||||||
|
// Print some bits of a binary number (2nd arg is how many bits)
|
||||||
|
void LedDeviceWS2812b::printBinary(unsigned int i, unsigned int bits)
|
||||||
|
{
|
||||||
|
int x;
|
||||||
|
for(x=bits-1; x>=0; x--)
|
||||||
|
{
|
||||||
|
printf("%d", (i & (1 << x)) ? 1 : 0);
|
||||||
|
if(x % 16 == 0 && x > 0)
|
||||||
|
{
|
||||||
|
printf(" ");
|
||||||
|
}
|
||||||
|
else if(x % 4 == 0 && x > 0)
|
||||||
|
{
|
||||||
|
printf(":");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reverse the bits in a word
|
||||||
|
unsigned int reverseWord(unsigned int word)
|
||||||
|
{
|
||||||
|
unsigned int output = 0;
|
||||||
|
//unsigned char bit;
|
||||||
|
int i;
|
||||||
|
for(i=0; i<32; i++)
|
||||||
|
{
|
||||||
|
output |= word & (1 << i) ? 1 : 0;
|
||||||
|
if(i<31)
|
||||||
|
{
|
||||||
|
output <<= 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return output;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Shutdown functions
|
||||||
|
// --------------------------------------------------------------------------------------------------
|
||||||
|
void LedDeviceWS2812b::terminate(int dummy) {
|
||||||
|
// Shut down the DMA controller
|
||||||
|
if(dma_reg)
|
||||||
|
{
|
||||||
|
CLRBIT(dma_reg[DMA_CS], DMA_CS_ACTIVE);
|
||||||
|
usleep(100);
|
||||||
|
SETBIT(dma_reg[DMA_CS], DMA_CS_RESET);
|
||||||
|
usleep(100);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Shut down PWM
|
||||||
|
if(pwm_reg)
|
||||||
|
{
|
||||||
|
CLRBIT(pwm_reg[PWM_CTL], PWM_CTL_PWEN1);
|
||||||
|
usleep(100);
|
||||||
|
pwm_reg[PWM_CTL] = (1 << PWM_CTL_CLRF1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Free the allocated memory
|
||||||
|
if(page_map != 0)
|
||||||
|
{
|
||||||
|
free(page_map);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void LedDeviceWS2812b::fatal(const char *fmt, ...)
|
||||||
|
{
|
||||||
|
va_list ap;
|
||||||
|
va_start(ap, fmt);
|
||||||
|
vfprintf(stderr, fmt, ap);
|
||||||
|
va_end(ap);
|
||||||
|
terminate(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Memory management
|
||||||
|
// --------------------------------------------------------------------------------------------------
|
||||||
|
// Translate from virtual address to physical
|
||||||
|
unsigned int LedDeviceWS2812b::mem_virt_to_phys(void *virt)
|
||||||
|
{
|
||||||
|
unsigned int offset = (uint8_t *)virt - virtbase;
|
||||||
|
return page_map[offset >> PAGE_SHIFT].physaddr + (offset % PAGE_SIZE);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Translate from physical address to virtual
|
||||||
|
unsigned int LedDeviceWS2812b::mem_phys_to_virt(uint32_t phys)
|
||||||
|
{
|
||||||
|
unsigned int pg_offset = phys & (PAGE_SIZE - 1);
|
||||||
|
unsigned int pg_addr = phys - pg_offset;
|
||||||
|
|
||||||
|
for (unsigned int i = 0; i < NUM_PAGES; i++)
|
||||||
|
{
|
||||||
|
if (page_map[i].physaddr == pg_addr)
|
||||||
|
{
|
||||||
|
return (uint32_t)virtbase + i * PAGE_SIZE + pg_offset;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fatal("Failed to reverse map phys addr %08x\n", phys);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Map a peripheral's IO memory into our virtual memory, so we can read/write it directly
|
||||||
|
void * LedDeviceWS2812b::map_peripheral(uint32_t base, uint32_t len)
|
||||||
|
{
|
||||||
|
int fd = open("/dev/mem", O_RDWR);
|
||||||
|
void * vaddr;
|
||||||
|
|
||||||
|
if (fd < 0)
|
||||||
|
{
|
||||||
|
fatal("Failed to open /dev/mem: %m\n");
|
||||||
|
}
|
||||||
|
vaddr = mmap(NULL, len, PROT_READ|PROT_WRITE, MAP_SHARED, fd, base);
|
||||||
|
if (vaddr == MAP_FAILED)
|
||||||
|
{
|
||||||
|
fatal("Failed to map peripheral at 0x%08x: %m\n", base);
|
||||||
|
}
|
||||||
|
close(fd);
|
||||||
|
|
||||||
|
return vaddr;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Zero out the PWM waveform buffer
|
||||||
|
void LedDeviceWS2812b::clearPWMBuffer()
|
||||||
|
{
|
||||||
|
memset(PWMWaveform, 0, NUM_DATA_WORDS * 4); // Times four because memset deals in bytes.
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set an individual bit in the PWM output array, accounting for word boundaries
|
||||||
|
// The (31 - bitIdx) is so that we write the data backwards, correcting its endianness
|
||||||
|
// This means getPWMBit will return something other than what was written, so it would be nice
|
||||||
|
// if the logic that calls this function would figure it out instead. (However, that's trickier)
|
||||||
|
void LedDeviceWS2812b::setPWMBit(unsigned int bitPos, unsigned char bit)
|
||||||
|
{
|
||||||
|
// Fetch word the bit is in
|
||||||
|
unsigned int wordOffset = (int)(bitPos / 32);
|
||||||
|
unsigned int bitIdx = bitPos - (wordOffset * 32);
|
||||||
|
|
||||||
|
switch(bit)
|
||||||
|
{
|
||||||
|
case 1:
|
||||||
|
PWMWaveform[wordOffset] |= (1 << (31 - bitIdx));
|
||||||
|
break;
|
||||||
|
case 0:
|
||||||
|
PWMWaveform[wordOffset] &= ~(1 << (31 - bitIdx));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ==== Init Hardware ====
|
||||||
|
void LedDeviceWS2812b::initHardware()
|
||||||
|
{
|
||||||
|
int pid;
|
||||||
|
int fd;
|
||||||
|
char pagemap_fn[64];
|
||||||
|
|
||||||
|
// Clear the PWM buffer
|
||||||
|
// ---------------------------------------------------------------
|
||||||
|
clearPWMBuffer();
|
||||||
|
|
||||||
|
// Set up peripheral access
|
||||||
|
// ---------------------------------------------------------------
|
||||||
|
dma_reg = (unsigned int *) map_peripheral(DMA_BASE, DMA_LEN);
|
||||||
|
dma_reg += 0x000;
|
||||||
|
pwm_reg = (unsigned int *) map_peripheral(PWM_BASE, PWM_LEN);
|
||||||
|
clk_reg = (unsigned int *) map_peripheral(CLK_BASE, CLK_LEN);
|
||||||
|
gpio_reg = (unsigned int *) map_peripheral(GPIO_BASE, GPIO_LEN);
|
||||||
|
|
||||||
|
|
||||||
|
// Set PWM alternate function for GPIO18
|
||||||
|
// ---------------------------------------------------------------
|
||||||
|
//gpio_reg[1] &= ~(7 << 24);
|
||||||
|
//usleep(100);
|
||||||
|
//gpio_reg[1] |= (2 << 24);
|
||||||
|
//usleep(100);
|
||||||
|
SET_GPIO_ALT(18, 5);
|
||||||
|
|
||||||
|
|
||||||
|
// Allocate memory for the DMA control block & data to be sent
|
||||||
|
// ---------------------------------------------------------------
|
||||||
|
virtbase = (uint8_t *) mmap(
|
||||||
|
NULL, // Address
|
||||||
|
NUM_PAGES * PAGE_SIZE, // Length
|
||||||
|
PROT_READ | PROT_WRITE, // Protection
|
||||||
|
MAP_SHARED | // Shared
|
||||||
|
MAP_ANONYMOUS | // Not file-based, init contents to 0
|
||||||
|
MAP_NORESERVE | // Don't reserve swap space
|
||||||
|
MAP_LOCKED, // Lock in RAM (don't swap)
|
||||||
|
-1, // File descriptor
|
||||||
|
0); // Offset
|
||||||
|
|
||||||
|
if (virtbase == MAP_FAILED)
|
||||||
|
{
|
||||||
|
fatal("Failed to mmap physical pages: %m\n");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((unsigned long)virtbase & (PAGE_SIZE-1))
|
||||||
|
{
|
||||||
|
fatal("Virtual address is not page aligned\n");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Allocate page map (pointers to the control block(s) and data for each CB
|
||||||
|
page_map = (page_map_t *) malloc(NUM_PAGES * sizeof(*page_map));
|
||||||
|
if (page_map == 0)
|
||||||
|
{
|
||||||
|
fatal("Failed to malloc page_map: %m\n");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Use /proc/self/pagemap to figure out the mapping between virtual and physical addresses
|
||||||
|
pid = getpid();
|
||||||
|
sprintf(pagemap_fn, "/proc/%d/pagemap", pid);
|
||||||
|
fd = open(pagemap_fn, O_RDONLY);
|
||||||
|
|
||||||
|
if (fd < 0)
|
||||||
|
{
|
||||||
|
fatal("Failed to open %s: %m\n", pagemap_fn);
|
||||||
|
}
|
||||||
|
|
||||||
|
off_t newOffset = (unsigned long)virtbase >> 9;
|
||||||
|
if (lseek(fd, newOffset, SEEK_SET) != newOffset)
|
||||||
|
{
|
||||||
|
fatal("Failed to seek on %s: %m\n", pagemap_fn);
|
||||||
|
}
|
||||||
|
|
||||||
|
printf("Page map: %d pages\n", NUM_PAGES);
|
||||||
|
for (unsigned int i = 0; i < NUM_PAGES; i++)
|
||||||
|
{
|
||||||
|
uint64_t pfn;
|
||||||
|
page_map[i].virtaddr = virtbase + i * PAGE_SIZE;
|
||||||
|
|
||||||
|
// Following line forces page to be allocated
|
||||||
|
// (Note: Copied directly from Hirst's code... page_map[i].virtaddr[0] was just set...?)
|
||||||
|
page_map[i].virtaddr[0] = 0;
|
||||||
|
|
||||||
|
if (read(fd, &pfn, sizeof(pfn)) != sizeof(pfn)) {
|
||||||
|
fatal("Failed to read %s: %m\n", pagemap_fn);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (((pfn >> 55) & 0xfbf) != 0x10c) { // pagemap bits: https://www.kernel.org/doc/Documentation/vm/pagemap.txt
|
||||||
|
fatal("Page %d not present (pfn 0x%016llx)\n", i, pfn);
|
||||||
|
}
|
||||||
|
|
||||||
|
page_map[i].physaddr = (unsigned int)pfn << PAGE_SHIFT | 0x40000000;
|
||||||
|
//printf("Page map #%2d: virtual %8p ==> physical 0x%08x [0x%016llx]\n", i, page_map[i].virtaddr, page_map[i].physaddr, pfn);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Set up control block
|
||||||
|
// ---------------------------------------------------------------
|
||||||
|
struct control_data_s *ctl = (struct control_data_s *)virtbase;
|
||||||
|
dma_cb_t *cbp = ctl->cb;
|
||||||
|
// FIXME: Change this to use DEFINEs
|
||||||
|
unsigned int phys_pwm_fifo_addr = 0x7e20c000 + 0x18;
|
||||||
|
|
||||||
|
// No wide bursts, source increment, dest DREQ on line 5, wait for response, enable interrupt
|
||||||
|
cbp->info = DMA_TI_CONFIGWORD;
|
||||||
|
|
||||||
|
// Source is our allocated memory
|
||||||
|
cbp->src = mem_virt_to_phys(ctl->sample);
|
||||||
|
|
||||||
|
// Destination is the PWM controller
|
||||||
|
cbp->dst = phys_pwm_fifo_addr;
|
||||||
|
|
||||||
|
// 72 bits per pixel / 32 bits per word = 2.25 words per pixel
|
||||||
|
// Add 1 to make sure the PWM FIFO gets the message: "we're sending zeroes"
|
||||||
|
// Times 4 because DMA works in bytes, not words
|
||||||
|
cbp->length = ((mLedCount * 2.25) + 1) * 4;
|
||||||
|
if(cbp->length > NUM_DATA_WORDS * 4)
|
||||||
|
{
|
||||||
|
cbp->length = NUM_DATA_WORDS * 4;
|
||||||
|
}
|
||||||
|
|
||||||
|
// We don't use striding
|
||||||
|
cbp->stride = 0;
|
||||||
|
|
||||||
|
// These are reserved
|
||||||
|
cbp->pad[0] = 0;
|
||||||
|
cbp->pad[1] = 0;
|
||||||
|
|
||||||
|
// Pointer to next block - 0 shuts down the DMA channel when transfer is complete
|
||||||
|
cbp->next = 0;
|
||||||
|
|
||||||
|
// Stop any existing DMA transfers
|
||||||
|
// ---------------------------------------------------------------
|
||||||
|
dma_reg[DMA_CS] |= (1 << DMA_CS_ABORT);
|
||||||
|
usleep(100);
|
||||||
|
dma_reg[DMA_CS] = (1 << DMA_CS_RESET);
|
||||||
|
usleep(100);
|
||||||
|
|
||||||
|
|
||||||
|
// PWM Clock
|
||||||
|
// ---------------------------------------------------------------
|
||||||
|
// Kill the clock
|
||||||
|
// FIXME: Change this to use a DEFINE
|
||||||
|
clk_reg[PWM_CLK_CNTL] = 0x5A000000 | (1 << 5);
|
||||||
|
usleep(100);
|
||||||
|
|
||||||
|
// Disable DMA requests
|
||||||
|
CLRBIT(pwm_reg[PWM_DMAC], PWM_DMAC_ENAB);
|
||||||
|
usleep(100);
|
||||||
|
|
||||||
|
// The fractional part is quantized to a range of 0-1024, so multiply the decimal part by 1024.
|
||||||
|
// E.g., 0.25 * 1024 = 256.
|
||||||
|
// So, if you want a divisor of 400.5, set idiv to 400 and fdiv to 512.
|
||||||
|
unsigned int idiv = 400;
|
||||||
|
unsigned short fdiv = 0; // Should be 16 bits, but the value must be <= 1024
|
||||||
|
clk_reg[PWM_CLK_DIV] = 0x5A000000 | (idiv << 12) | fdiv; // Set clock multiplier
|
||||||
|
usleep(100);
|
||||||
|
|
||||||
|
// Enable the clock. Next-to-last digit means "enable clock". Last digit is 1 (oscillator),
|
||||||
|
// 4 (PLLA), 5 (PLLC), or 6 (PLLD) (according to the docs) although PLLA doesn't seem to work.
|
||||||
|
// FIXME: Change this to use a DEFINE
|
||||||
|
clk_reg[PWM_CLK_CNTL] = 0x5A000015;
|
||||||
|
usleep(100);
|
||||||
|
|
||||||
|
|
||||||
|
// PWM
|
||||||
|
// ---------------------------------------------------------------
|
||||||
|
// Clear any preexisting crap from the control & status register
|
||||||
|
pwm_reg[PWM_CTL] = 0;
|
||||||
|
|
||||||
|
// Set transmission range (32 bytes, or 1 word)
|
||||||
|
// <32: Truncate. >32: Pad with SBIT1. As it happens, 32 is perfect.
|
||||||
|
pwm_reg[PWM_RNG1] = 32;
|
||||||
|
usleep(100);
|
||||||
|
|
||||||
|
// Send DMA requests to fill the FIFO
|
||||||
|
pwm_reg[PWM_DMAC] =
|
||||||
|
(1 << PWM_DMAC_ENAB) |
|
||||||
|
(8 << PWM_DMAC_PANIC) |
|
||||||
|
(8 << PWM_DMAC_DREQ);
|
||||||
|
usleep(1000);
|
||||||
|
|
||||||
|
// Clear the FIFO
|
||||||
|
SETBIT(pwm_reg[PWM_CTL], PWM_CTL_CLRF1);
|
||||||
|
usleep(100);
|
||||||
|
|
||||||
|
// Don't repeat last FIFO contents if it runs dry
|
||||||
|
CLRBIT(pwm_reg[PWM_CTL], PWM_CTL_RPTL1);
|
||||||
|
usleep(100);
|
||||||
|
|
||||||
|
// Silence (default) bit is 0
|
||||||
|
CLRBIT(pwm_reg[PWM_CTL], PWM_CTL_SBIT1);
|
||||||
|
usleep(100);
|
||||||
|
|
||||||
|
// Polarity = default (low = 0, high = 1)
|
||||||
|
CLRBIT(pwm_reg[PWM_CTL], PWM_CTL_POLA1);
|
||||||
|
usleep(100);
|
||||||
|
|
||||||
|
// Enable serializer mode
|
||||||
|
SETBIT(pwm_reg[PWM_CTL], PWM_CTL_MODE1);
|
||||||
|
usleep(100);
|
||||||
|
|
||||||
|
// Use FIFO rather than DAT1
|
||||||
|
SETBIT(pwm_reg[PWM_CTL], PWM_CTL_USEF1);
|
||||||
|
usleep(100);
|
||||||
|
|
||||||
|
// Disable MSEN1
|
||||||
|
CLRBIT(pwm_reg[PWM_CTL], PWM_CTL_MSEN1);
|
||||||
|
usleep(100);
|
||||||
|
|
||||||
|
|
||||||
|
// DMA
|
||||||
|
// ---------------------------------------------------------------
|
||||||
|
// Raise an interrupt when transfer is complete, which will set the INT flag in the CS register
|
||||||
|
SETBIT(dma_reg[DMA_CS], DMA_CS_INT);
|
||||||
|
usleep(100);
|
||||||
|
|
||||||
|
// Clear the END flag (by setting it - this is a "write 1 to clear", or W1C, bit)
|
||||||
|
SETBIT(dma_reg[DMA_CS], DMA_CS_END);
|
||||||
|
usleep(100);
|
||||||
|
|
||||||
|
// Send the physical address of the control block into the DMA controller
|
||||||
|
dma_reg[DMA_CONBLK_AD] = mem_virt_to_phys(ctl->cb);
|
||||||
|
usleep(100);
|
||||||
|
|
||||||
|
// Clear error flags, if any (these are also W1C bits)
|
||||||
|
// FIXME: Use a define instead of this
|
||||||
|
dma_reg[DMA_DEBUG] = 7;
|
||||||
|
usleep(100);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Begin the transfer
|
||||||
|
void LedDeviceWS2812b::startTransfer()
|
||||||
|
{
|
||||||
|
// Enable DMA
|
||||||
|
dma_reg[DMA_CONBLK_AD] = mem_virt_to_phys(((struct control_data_s *) virtbase)->cb);
|
||||||
|
dma_reg[DMA_CS] = DMA_CS_CONFIGWORD | (1 << DMA_CS_ACTIVE);
|
||||||
|
usleep(100);
|
||||||
|
|
||||||
|
// Enable PWM
|
||||||
|
SETBIT(pwm_reg[PWM_CTL], PWM_CTL_PWEN1);
|
||||||
|
}
|
218
libsrc/leddevice/LedDeviceWS2812b.h
Normal file
218
libsrc/leddevice/LedDeviceWS2812b.h
Normal file
@ -0,0 +1,218 @@
|
|||||||
|
#ifndef LEDDEVICEWS2812B_H_
|
||||||
|
#define LEDDEVICEWS2812B_H_
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
|
||||||
|
// Set tabs to 4 spaces.
|
||||||
|
|
||||||
|
// =================================================================================================
|
||||||
|
//
|
||||||
|
// __ __ _________________ ______ ____________ ____________________.__
|
||||||
|
// / \ / \/ _____/\_____ \ / __ \/_ \_____ \ \______ \______ \__|
|
||||||
|
// \ \/\/ /\_____ \ / ____/ > < | |/ ____/ | _/| ___/ |
|
||||||
|
// \ / / \/ \/ -- \| / \ | | \| | | |
|
||||||
|
// \__/\ / /_______ /\_______ \______ /|___\_______ \ |____|_ /|____| |__|
|
||||||
|
// \/ \/ \/ \/ \/ \/
|
||||||
|
//
|
||||||
|
// WS2812 NeoPixel driver
|
||||||
|
// Based on code by Richard G. Hirst and others
|
||||||
|
// Adapted for the WS2812 by 626Pilot, April/May 2014
|
||||||
|
// See: https://github.com/626Pilot/RaspberryPi-NeoPixel-WS2812
|
||||||
|
// Version: https://github.com/626Pilot/RaspberryPi-NeoPixel-WS2812/blob/1d43407d9e6eba19bff24330bc09a27963b55751/ws2812-RPi.c
|
||||||
|
// Huge ASCII art section labels are from http://patorjk.com/software/taag/
|
||||||
|
//
|
||||||
|
// LED driver adaptation by Kammerjaeger ()
|
||||||
|
// mostly code removed that was not needed
|
||||||
|
//
|
||||||
|
// License: GPL
|
||||||
|
//
|
||||||
|
// You are using this at your OWN RISK. I believe this software is reasonably safe to use (aside
|
||||||
|
// from the intrinsic risk to those who are photosensitive - see below), although I can't be certain
|
||||||
|
// that it won't trash your hardware or cause property damage.
|
||||||
|
//
|
||||||
|
// Speaking of risk, WS2812 pixels are bright enough to cause eye pain and (for all I know) possibly
|
||||||
|
// retina damage when run at full strength. It's a good idea to set the brightness at 0.2 or so for
|
||||||
|
// direct viewing (whether you're looking directly at the pixels or not), or to put some diffuse
|
||||||
|
// material between you and the LEDs.
|
||||||
|
//
|
||||||
|
// PHOTOSENSITIVITY WARNING:
|
||||||
|
// Patterns of light and darkness (stationary or moving), flashing lights, patterns and backgrounds
|
||||||
|
// on screens, and the like, may cause epilleptic seizures in some people. This is a danger EVEN IF
|
||||||
|
// THE PERSON (WHICH MAY BE *YOU*) HAS NEVER KNOWINGLY HAD A PHOTOSENSITIVE EPISODE BEFORE. It's up
|
||||||
|
// to you to learn the warning signs, but symptoms may include dizziness, nausea, vision changes,
|
||||||
|
// convlusions, disorientation, involuntary movements, and eye twitching. (This list is not
|
||||||
|
// necessarily exhaustive.)
|
||||||
|
//
|
||||||
|
// NEOPIXEL BEST PRACTICES: https://learn.adafruit.com/adafruit-neopixel-uberguide/best-practices
|
||||||
|
//
|
||||||
|
// Connections:
|
||||||
|
// Positive to Raspberry Pi's 3.3v, for better separation connect only ground and data directly
|
||||||
|
// (5v can be used then without a problem, at least it worked for me, Kammerjaeger)
|
||||||
|
// Negative to Raspberry Pi's ground
|
||||||
|
// Data to GPIO18 (Pin 12) (through a resistor, which you should know from the Best
|
||||||
|
// Practices guide!)
|
||||||
|
//
|
||||||
|
// Buy WS2812-based stuff from: http://adafruit.com
|
||||||
|
//
|
||||||
|
// To activate: use led device "ws2812s" in the hyperion configuration
|
||||||
|
// (it needs to be root so it can map the peripherals' registers)
|
||||||
|
//
|
||||||
|
// =================================================================================================
|
||||||
|
|
||||||
|
// This is for the WS2812 LEDs. It won't work with the older WS2811s, although it could be modified
|
||||||
|
// for that without too much trouble. Preliminary driver used Frank Buss' servo driver, but I moved
|
||||||
|
// to Richard Hirst's memory mapping/access model because his code already works with DMA, and has
|
||||||
|
// what I think is a slightly cleaner way of accessing the registers: register[name] rather than
|
||||||
|
// *(register + name).
|
||||||
|
|
||||||
|
// At the time of writing, there's a lot of confusing "PWM DMA" code revolving around simulating
|
||||||
|
// an FM signal. Usually this is done without properly initializing certain registers, which is
|
||||||
|
// OK for their purpose, but I needed to be able to transfer actual coherent data and have it wind
|
||||||
|
// up in a proper state once it was transferred. This has proven to be a somewhat painful task.
|
||||||
|
// The PWM controller likes to ignore the RPTL1 bit when the data is in a regular, repeating
|
||||||
|
// pattern. I'M NOT MAKING IT UP! It really does that. It's bizarre. There are lots of other
|
||||||
|
// strange irregularities as well, which had to be figured out through trial and error. It doesn't
|
||||||
|
// help that the BCM2835 ARM Peripherals manual contains outright errors and omissions!
|
||||||
|
|
||||||
|
// Many examples of this kind of code have magic numbers in them. If you don't know, a magic number
|
||||||
|
// is one that either lacks an obvious structure (e.g. 0x2020C000) or purpose. Please don't use
|
||||||
|
// that stuff in any code you release! All magic numbers found in reference code have been changed
|
||||||
|
// to DEFINEs. That way, instead of seeing some inscrutable number, you see (e.g.) PWM_CTL.
|
||||||
|
|
||||||
|
// References - BCM2835 ARM Peripherals:
|
||||||
|
// http://www.raspberrypi.org/wp-content/uploads/2012/02/BCM2835-ARM-Peripherals.pdf
|
||||||
|
//
|
||||||
|
// Raspberry Pi low-level peripherals:
|
||||||
|
// http://elinux.org/RPi_Low-level_peripherals
|
||||||
|
//
|
||||||
|
// Richard Hirst's nice, clean code:
|
||||||
|
// https://github.com/richardghirst/PiBits/blob/master/PiFmDma/PiFmDma.c
|
||||||
|
//
|
||||||
|
// PWM clock register:
|
||||||
|
// http://www.raspberrypi.org/forums/viewtopic.php?t=8467&p=124620
|
||||||
|
//
|
||||||
|
// Simple (because it's in assembly) PWM+DMA setup:
|
||||||
|
// https://github.com/mikedurso/rpi-projects/blob/master/asm-nyancat/rpi-nyancat.s
|
||||||
|
//
|
||||||
|
// Adafruit's NeoPixel driver:
|
||||||
|
// https://github.com/adafruit/Adafruit_NeoPixel/blob/master/Adafruit_NeoPixel.cpp
|
||||||
|
|
||||||
|
// Hyperion includes
|
||||||
|
#include <leddevice/LedDevice.h>
|
||||||
|
|
||||||
|
//#define BENCHMARK
|
||||||
|
#define WS2812_ASM_OPTI
|
||||||
|
|
||||||
|
// The page map contains pointers to memory that we will allocate below. It uses two pointers
|
||||||
|
// per address. This is because the software (this program) deals only in virtual addresses,
|
||||||
|
// whereas the DMA controller can only access RAM via physical address. (If that's not confusing
|
||||||
|
// enough, it writes to peripherals by their bus addresses.)
|
||||||
|
struct page_map_t
|
||||||
|
{
|
||||||
|
uint8_t *virtaddr;
|
||||||
|
uint32_t physaddr;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Control Block (CB) - this tells the DMA controller what to do.
|
||||||
|
struct dma_cb_t
|
||||||
|
{
|
||||||
|
unsigned info; // Transfer Information (TI)
|
||||||
|
unsigned src; // Source address (physical)
|
||||||
|
unsigned dst; // Destination address (bus)
|
||||||
|
unsigned length; // Length in bytes (not words!)
|
||||||
|
unsigned stride; // We don't care about this
|
||||||
|
unsigned next; // Pointer to next control block
|
||||||
|
unsigned pad[2]; // These are "reserved" (unused)
|
||||||
|
};
|
||||||
|
|
||||||
|
///
|
||||||
|
/// Implementation of the LedDevice interface for writing to Ws2801 led device.
|
||||||
|
///
|
||||||
|
class LedDeviceWS2812b : public LedDevice
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
///
|
||||||
|
/// Constructs the LedDevice for a string containing leds of the type WS2812
|
||||||
|
LedDeviceWS2812b();
|
||||||
|
|
||||||
|
~LedDeviceWS2812b();
|
||||||
|
///
|
||||||
|
/// 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<ColorRgb> &ledValues);
|
||||||
|
|
||||||
|
/// Switch the leds off
|
||||||
|
virtual int switchOff();
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
/// the number of leds (needed when switching off)
|
||||||
|
size_t mLedCount;
|
||||||
|
|
||||||
|
page_map_t *page_map; // This will hold the page map, which we'll allocate
|
||||||
|
uint8_t *virtbase; // Pointer to some virtual memory that will be allocated
|
||||||
|
|
||||||
|
volatile unsigned int *pwm_reg; // PWM controller register set
|
||||||
|
volatile unsigned int *clk_reg; // PWM clock manager register set
|
||||||
|
volatile unsigned int *dma_reg; // DMA controller register set
|
||||||
|
volatile unsigned int *gpio_reg; // GPIO pin controller register set
|
||||||
|
|
||||||
|
// Contains arrays of control blocks and their related samples.
|
||||||
|
// One pixel needs 72 bits (24 bits for the color * 3 to represent them on the wire).
|
||||||
|
// 768 words = 341.3 pixels
|
||||||
|
// 1024 words = 455.1 pixels
|
||||||
|
// The highest I can make this number is 1016. Any higher, and it will start copying garbage to the
|
||||||
|
// PWM controller. I think it might be because of the virtual->physical memory mapping not being
|
||||||
|
// contiguous, so *pointer+1016 isn't "next door" to *pointer+1017 for some weird reason.
|
||||||
|
// However, that's still enough for 451.5 color instructions! If someone has more pixels than that
|
||||||
|
// to control, they can figure it out. I tried Hirst's message of having one CB per word, which
|
||||||
|
// seems like it might fix that, but I couldn't figure it out.
|
||||||
|
#define NUM_DATA_WORDS 1016
|
||||||
|
struct control_data_s {
|
||||||
|
dma_cb_t cb[1];
|
||||||
|
uint32_t sample[NUM_DATA_WORDS];
|
||||||
|
};
|
||||||
|
|
||||||
|
//struct control_data_s *ctl;
|
||||||
|
|
||||||
|
// PWM waveform buffer (in words), 16 32-bit words are enough to hold 170 wire bits.
|
||||||
|
// That's OK if we only transmit from the FIFO, but for DMA, we will use a much larger size.
|
||||||
|
// 1024 (4096 bytes) should be enough for over 400 elements. It can be bumped up if you need more!
|
||||||
|
unsigned int PWMWaveform[NUM_DATA_WORDS];
|
||||||
|
|
||||||
|
void initHardware();
|
||||||
|
void startTransfer();
|
||||||
|
|
||||||
|
void clearPWMBuffer();
|
||||||
|
void setPWMBit(unsigned int bitPos, unsigned char bit);
|
||||||
|
|
||||||
|
unsigned int mem_phys_to_virt(uint32_t phys);
|
||||||
|
unsigned int mem_virt_to_phys(void *virt);
|
||||||
|
void terminate(int dummy);
|
||||||
|
void fatal(const char *fmt, ...);
|
||||||
|
void * map_peripheral(uint32_t base, uint32_t len);
|
||||||
|
void printBinary(unsigned int i, unsigned int bits);
|
||||||
|
|
||||||
|
#ifdef BENCHMARK
|
||||||
|
unsigned int runCount;
|
||||||
|
long combinedNseconds;
|
||||||
|
long shortestNseconds;
|
||||||
|
#endif
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#endif /* LEDDEVICEWS2812B_H_ */
|
@ -5,12 +5,15 @@ set(CURRENT_SOURCE_DIR ${CMAKE_SOURCE_DIR}/libsrc/protoserver)
|
|||||||
|
|
||||||
include_directories(
|
include_directories(
|
||||||
${CMAKE_CURRENT_BINARY_DIR}
|
${CMAKE_CURRENT_BINARY_DIR}
|
||||||
${PROTOBUF_INCLUDE_DIRS})
|
${PROTOBUF_INCLUDE_DIRS}
|
||||||
|
)
|
||||||
|
|
||||||
# Group the headers that go through the MOC compiler
|
# Group the headers that go through the MOC compiler
|
||||||
set(ProtoServer_QT_HEADERS
|
set(ProtoServer_QT_HEADERS
|
||||||
${CURRENT_HEADER_DIR}/ProtoServer.h
|
${CURRENT_HEADER_DIR}/ProtoServer.h
|
||||||
|
${CURRENT_HEADER_DIR}/ProtoConnection.h
|
||||||
${CURRENT_SOURCE_DIR}/ProtoClientConnection.h
|
${CURRENT_SOURCE_DIR}/ProtoClientConnection.h
|
||||||
|
${CURRENT_HEADER_DIR}/ProtoConnectionWrapper.h
|
||||||
)
|
)
|
||||||
|
|
||||||
set(ProtoServer_HEADERS
|
set(ProtoServer_HEADERS
|
||||||
@ -19,6 +22,8 @@ set(ProtoServer_HEADERS
|
|||||||
set(ProtoServer_SOURCES
|
set(ProtoServer_SOURCES
|
||||||
${CURRENT_SOURCE_DIR}/ProtoServer.cpp
|
${CURRENT_SOURCE_DIR}/ProtoServer.cpp
|
||||||
${CURRENT_SOURCE_DIR}/ProtoClientConnection.cpp
|
${CURRENT_SOURCE_DIR}/ProtoClientConnection.cpp
|
||||||
|
${CURRENT_SOURCE_DIR}/ProtoConnection.cpp
|
||||||
|
${CURRENT_SOURCE_DIR}/ProtoConnectionWrapper.cpp
|
||||||
)
|
)
|
||||||
|
|
||||||
set(ProtoServer_PROTOS
|
set(ProtoServer_PROTOS
|
||||||
@ -44,5 +49,6 @@ add_library(protoserver
|
|||||||
target_link_libraries(protoserver
|
target_link_libraries(protoserver
|
||||||
hyperion
|
hyperion
|
||||||
hyperion-utils
|
hyperion-utils
|
||||||
${PROTOBUF_LIBRARIES}
|
protobuf
|
||||||
${QT_LIBRARIES})
|
${QT_LIBRARIES}
|
||||||
|
)
|
||||||
|
188
libsrc/protoserver/ProtoConnection.cpp
Normal file
188
libsrc/protoserver/ProtoConnection.cpp
Normal file
@ -0,0 +1,188 @@
|
|||||||
|
// stl includes
|
||||||
|
#include <stdexcept>
|
||||||
|
|
||||||
|
// Qt includes
|
||||||
|
#include <QRgb>
|
||||||
|
|
||||||
|
// protoserver includes
|
||||||
|
#include "protoserver/ProtoConnection.h"
|
||||||
|
|
||||||
|
ProtoConnection::ProtoConnection(const std::string & a) :
|
||||||
|
_socket(),
|
||||||
|
_skipReply(false)
|
||||||
|
{
|
||||||
|
QString address(a.c_str());
|
||||||
|
QStringList parts = address.split(":");
|
||||||
|
if (parts.size() != 2)
|
||||||
|
{
|
||||||
|
throw std::runtime_error(QString("Wrong address: unable to parse address (%1)").arg(address).toStdString());
|
||||||
|
}
|
||||||
|
_host = parts[0];
|
||||||
|
|
||||||
|
bool ok;
|
||||||
|
_port = parts[1].toUShort(&ok);
|
||||||
|
if (!ok)
|
||||||
|
{
|
||||||
|
throw std::runtime_error(QString("Wrong address: Unable to parse the port number (%1)").arg(parts[1]).toStdString());
|
||||||
|
}
|
||||||
|
|
||||||
|
// try to connect to host
|
||||||
|
std::cout << "Connecting to Hyperion: " << _host.toStdString() << ":" << _port << std::endl;
|
||||||
|
connectToHost();
|
||||||
|
}
|
||||||
|
|
||||||
|
ProtoConnection::~ProtoConnection()
|
||||||
|
{
|
||||||
|
_socket.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
void ProtoConnection::setSkipReply(bool skip)
|
||||||
|
{
|
||||||
|
_skipReply = skip;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ProtoConnection::setColor(const ColorRgb & color, int priority, int duration)
|
||||||
|
{
|
||||||
|
proto::HyperionRequest request;
|
||||||
|
request.set_command(proto::HyperionRequest::COLOR);
|
||||||
|
proto::ColorRequest * colorRequest = request.MutableExtension(proto::ColorRequest::colorRequest);
|
||||||
|
colorRequest->set_rgbcolor((color.red << 16) | (color.green << 8) | color.blue);
|
||||||
|
colorRequest->set_priority(priority);
|
||||||
|
colorRequest->set_duration(duration);
|
||||||
|
|
||||||
|
// send command message
|
||||||
|
sendMessage(request);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ProtoConnection::setImage(const Image<ColorRgb> &image, int priority, int duration)
|
||||||
|
{
|
||||||
|
proto::HyperionRequest request;
|
||||||
|
request.set_command(proto::HyperionRequest::IMAGE);
|
||||||
|
proto::ImageRequest * imageRequest = request.MutableExtension(proto::ImageRequest::imageRequest);
|
||||||
|
imageRequest->set_imagedata(image.memptr(), image.width() * image.height() * 3);
|
||||||
|
imageRequest->set_imagewidth(image.width());
|
||||||
|
imageRequest->set_imageheight(image.height());
|
||||||
|
imageRequest->set_priority(priority);
|
||||||
|
imageRequest->set_duration(duration);
|
||||||
|
|
||||||
|
// send command message
|
||||||
|
sendMessage(request);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ProtoConnection::clear(int priority)
|
||||||
|
{
|
||||||
|
proto::HyperionRequest request;
|
||||||
|
request.set_command(proto::HyperionRequest::CLEAR);
|
||||||
|
proto::ClearRequest * clearRequest = request.MutableExtension(proto::ClearRequest::clearRequest);
|
||||||
|
clearRequest->set_priority(priority);
|
||||||
|
|
||||||
|
// send command message
|
||||||
|
sendMessage(request);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ProtoConnection::clearAll()
|
||||||
|
{
|
||||||
|
proto::HyperionRequest request;
|
||||||
|
request.set_command(proto::HyperionRequest::CLEARALL);
|
||||||
|
|
||||||
|
// send command message
|
||||||
|
sendMessage(request);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ProtoConnection::connectToHost()
|
||||||
|
{
|
||||||
|
_socket.connectToHost(_host, _port);
|
||||||
|
if (_socket.waitForConnected()) {
|
||||||
|
std::cout << "Connected to Hyperion host" << std::endl;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ProtoConnection::sendMessage(const proto::HyperionRequest &message)
|
||||||
|
{
|
||||||
|
if (_socket.state() == QAbstractSocket::UnconnectedState)
|
||||||
|
{
|
||||||
|
std::cout << "Currently disconnected: trying to connect to host" << std::endl;
|
||||||
|
connectToHost();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_socket.state() != QAbstractSocket::ConnectedState)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// We only get here if we are connected
|
||||||
|
|
||||||
|
// serialize message (FastWriter already appends a newline)
|
||||||
|
std::string serializedMessage = message.SerializeAsString();
|
||||||
|
|
||||||
|
int length = serializedMessage.size();
|
||||||
|
const uint8_t header[] = {
|
||||||
|
uint8_t((length >> 24) & 0xFF),
|
||||||
|
uint8_t((length >> 16) & 0xFF),
|
||||||
|
uint8_t((length >> 8) & 0xFF),
|
||||||
|
uint8_t((length ) & 0xFF)};
|
||||||
|
|
||||||
|
// write message
|
||||||
|
int count = 0;
|
||||||
|
count += _socket.write(reinterpret_cast<const char *>(header), 4);
|
||||||
|
count += _socket.write(reinterpret_cast<const char *>(serializedMessage.data()), length);
|
||||||
|
if (!_socket.waitForBytesWritten())
|
||||||
|
{
|
||||||
|
std::cerr << "Error while writing data to host" << std::endl;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!_skipReply)
|
||||||
|
{
|
||||||
|
// read reply data
|
||||||
|
QByteArray serializedReply;
|
||||||
|
length = -1;
|
||||||
|
while (length < 0 && serializedReply.size() < length+4)
|
||||||
|
{
|
||||||
|
// receive reply
|
||||||
|
if (!_socket.waitForReadyRead())
|
||||||
|
{
|
||||||
|
std::cerr << "Error while reading data from host" << std::endl;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
serializedReply += _socket.readAll();
|
||||||
|
|
||||||
|
if (length < 0 && serializedReply.size() >= 4)
|
||||||
|
{
|
||||||
|
// read the message size
|
||||||
|
length =
|
||||||
|
((serializedReply[0]<<24) & 0xFF000000) |
|
||||||
|
((serializedReply[1]<<16) & 0x00FF0000) |
|
||||||
|
((serializedReply[2]<< 8) & 0x0000FF00) |
|
||||||
|
((serializedReply[3] ) & 0x000000FF);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// parse reply data
|
||||||
|
proto::HyperionReply reply;
|
||||||
|
reply.ParseFromArray(serializedReply.constData()+4, length);
|
||||||
|
|
||||||
|
// parse reply message
|
||||||
|
parseReply(reply);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ProtoConnection::parseReply(const proto::HyperionReply &reply)
|
||||||
|
{
|
||||||
|
bool success = false;
|
||||||
|
|
||||||
|
if (!reply.success())
|
||||||
|
{
|
||||||
|
if (reply.has_error())
|
||||||
|
{
|
||||||
|
throw std::runtime_error("Error: " + reply.error());
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
throw std::runtime_error("Error: No error info");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return success;
|
||||||
|
}
|
19
libsrc/protoserver/ProtoConnectionWrapper.cpp
Normal file
19
libsrc/protoserver/ProtoConnectionWrapper.cpp
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
// protoserver includes
|
||||||
|
#include "protoserver/ProtoConnectionWrapper.h"
|
||||||
|
|
||||||
|
ProtoConnectionWrapper::ProtoConnectionWrapper(const std::string & address, int priority, int duration_ms, bool skipProtoReply) :
|
||||||
|
_priority(priority),
|
||||||
|
_duration_ms(duration_ms),
|
||||||
|
_connection(address)
|
||||||
|
{
|
||||||
|
_connection.setSkipReply(skipProtoReply);
|
||||||
|
}
|
||||||
|
|
||||||
|
ProtoConnectionWrapper::~ProtoConnectionWrapper()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
void ProtoConnectionWrapper::receiveImage(const Image<ColorRgb> & image)
|
||||||
|
{
|
||||||
|
_connection.setImage(image, _priority, _duration_ms);
|
||||||
|
}
|
@ -17,7 +17,7 @@ message HyperionRequest {
|
|||||||
|
|
||||||
message ColorRequest {
|
message ColorRequest {
|
||||||
extend HyperionRequest {
|
extend HyperionRequest {
|
||||||
required ColorRequest colorRequest = 10;
|
optional ColorRequest colorRequest = 10;
|
||||||
}
|
}
|
||||||
|
|
||||||
// priority to use when setting the color
|
// priority to use when setting the color
|
||||||
@ -32,7 +32,7 @@ message ColorRequest {
|
|||||||
|
|
||||||
message ImageRequest {
|
message ImageRequest {
|
||||||
extend HyperionRequest {
|
extend HyperionRequest {
|
||||||
required ImageRequest imageRequest = 11;
|
optional ImageRequest imageRequest = 11;
|
||||||
}
|
}
|
||||||
|
|
||||||
// priority to use when setting the image
|
// priority to use when setting the image
|
||||||
@ -53,7 +53,7 @@ message ImageRequest {
|
|||||||
|
|
||||||
message ClearRequest {
|
message ClearRequest {
|
||||||
extend HyperionRequest {
|
extend HyperionRequest {
|
||||||
required ClearRequest clearRequest = 12;
|
optional ClearRequest clearRequest = 12;
|
||||||
}
|
}
|
||||||
|
|
||||||
// priority which need to be cleared
|
// priority which need to be cleared
|
||||||
|
@ -13,6 +13,12 @@ add_library(hyperion-utils
|
|||||||
${CURRENT_HEADER_DIR}/Image.h
|
${CURRENT_HEADER_DIR}/Image.h
|
||||||
${CURRENT_HEADER_DIR}/Sleep.h
|
${CURRENT_HEADER_DIR}/Sleep.h
|
||||||
|
|
||||||
|
${CURRENT_HEADER_DIR}/PixelFormat.h
|
||||||
|
${CURRENT_HEADER_DIR}/VideoMode.h
|
||||||
|
|
||||||
|
${CURRENT_HEADER_DIR}/ImageResampler.h
|
||||||
|
${CURRENT_SOURCE_DIR}/ImageResampler.cpp
|
||||||
|
|
||||||
${CURRENT_HEADER_DIR}/HsvTransform.h
|
${CURRENT_HEADER_DIR}/HsvTransform.h
|
||||||
${CURRENT_SOURCE_DIR}/HsvTransform.cpp
|
${CURRENT_SOURCE_DIR}/HsvTransform.cpp
|
||||||
${CURRENT_HEADER_DIR}/RgbChannelTransform.h
|
${CURRENT_HEADER_DIR}/RgbChannelTransform.h
|
||||||
|
133
libsrc/utils/ImageResampler.cpp
Normal file
133
libsrc/utils/ImageResampler.cpp
Normal file
@ -0,0 +1,133 @@
|
|||||||
|
#include "utils/ImageResampler.h"
|
||||||
|
|
||||||
|
ImageResampler::ImageResampler() :
|
||||||
|
_horizontalDecimation(1),
|
||||||
|
_verticalDecimation(1),
|
||||||
|
_cropLeft(0),
|
||||||
|
_cropRight(0),
|
||||||
|
_cropTop(0),
|
||||||
|
_cropBottom(0),
|
||||||
|
_videoMode(VIDEO_2D)
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
ImageResampler::~ImageResampler()
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void ImageResampler::setHorizontalPixelDecimation(int decimator)
|
||||||
|
{
|
||||||
|
_horizontalDecimation = decimator;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ImageResampler::setVerticalPixelDecimation(int decimator)
|
||||||
|
{
|
||||||
|
_verticalDecimation = decimator;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ImageResampler::setCropping(int cropLeft, int cropRight, int cropTop, int cropBottom)
|
||||||
|
{
|
||||||
|
_cropLeft = cropLeft;
|
||||||
|
_cropRight = cropRight;
|
||||||
|
_cropTop = cropTop;
|
||||||
|
_cropBottom = cropBottom;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ImageResampler::set3D(VideoMode mode)
|
||||||
|
{
|
||||||
|
_videoMode = mode;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ImageResampler::processImage(const uint8_t * data, int width, int height, int lineLength, PixelFormat pixelFormat, Image<ColorRgb> &outputImage) const
|
||||||
|
{
|
||||||
|
int cropLeft = _cropLeft;
|
||||||
|
int cropRight = _cropRight;
|
||||||
|
int cropTop = _cropTop;
|
||||||
|
int cropBottom = _cropBottom;
|
||||||
|
|
||||||
|
// handle 3D mode
|
||||||
|
switch (_videoMode)
|
||||||
|
{
|
||||||
|
case VIDEO_3DSBS:
|
||||||
|
cropRight = width/2;
|
||||||
|
break;
|
||||||
|
case VIDEO_3DTAB:
|
||||||
|
cropBottom = height/2;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// calculate the output size
|
||||||
|
int outputWidth = (width - cropLeft - cropRight - _horizontalDecimation/2 + _horizontalDecimation - 1) / _horizontalDecimation;
|
||||||
|
int outputHeight = (height - cropTop - cropBottom - _verticalDecimation/2 + _verticalDecimation - 1) / _verticalDecimation;
|
||||||
|
outputImage.resize(outputWidth, outputHeight);
|
||||||
|
|
||||||
|
for (int yDest = 0, ySource = cropTop + _verticalDecimation/2; yDest < outputHeight; ySource += _verticalDecimation, ++yDest)
|
||||||
|
{
|
||||||
|
for (int xDest = 0, xSource = cropLeft + _horizontalDecimation/2; xDest < outputWidth; xSource += _horizontalDecimation, ++xDest)
|
||||||
|
{
|
||||||
|
ColorRgb & rgb = outputImage(xDest, yDest);
|
||||||
|
|
||||||
|
switch (pixelFormat)
|
||||||
|
{
|
||||||
|
case PIXELFORMAT_UYVY:
|
||||||
|
{
|
||||||
|
int index = lineLength * ySource + xSource * 2;
|
||||||
|
uint8_t y = data[index+1];
|
||||||
|
uint8_t u = ((xSource&1) == 0) ? data[index ] : data[index-2];
|
||||||
|
uint8_t v = ((xSource&1) == 0) ? data[index+2] : data[index ];
|
||||||
|
yuv2rgb(y, u, v, rgb.red, rgb.green, rgb.blue);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case PIXELFORMAT_YUYV:
|
||||||
|
{
|
||||||
|
int index = lineLength * ySource + xSource * 2;
|
||||||
|
uint8_t y = data[index];
|
||||||
|
uint8_t u = ((xSource&1) == 0) ? data[index+1] : data[index-1];
|
||||||
|
uint8_t v = ((xSource&1) == 0) ? data[index+3] : data[index+1];
|
||||||
|
yuv2rgb(y, u, v, rgb.red, rgb.green, rgb.blue);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case PIXELFORMAT_RGB32:
|
||||||
|
{
|
||||||
|
int index = lineLength * ySource + xSource * 4;
|
||||||
|
rgb.red = data[index ];
|
||||||
|
rgb.green = data[index+1];
|
||||||
|
rgb.blue = data[index+2];
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case PIXELFORMAT_BGR32:
|
||||||
|
{
|
||||||
|
int index = lineLength * ySource + xSource * 4;
|
||||||
|
rgb.blue = data[index ];
|
||||||
|
rgb.green = data[index+1];
|
||||||
|
rgb.red = data[index+2];
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case PIXELFORMAT_NO_CHANGE:
|
||||||
|
std::cerr << "Invalid pixel format given" << std::endl;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t ImageResampler::clamp(int x)
|
||||||
|
{
|
||||||
|
return (x<0) ? 0 : ((x>255) ? 255 : uint8_t(x));
|
||||||
|
}
|
||||||
|
|
||||||
|
void ImageResampler::yuv2rgb(uint8_t y, uint8_t u, uint8_t v, uint8_t &r, uint8_t &g, uint8_t &b)
|
||||||
|
{
|
||||||
|
// see: http://en.wikipedia.org/wiki/YUV#Y.27UV444_to_RGB888_conversion
|
||||||
|
int c = y - 16;
|
||||||
|
int d = u - 128;
|
||||||
|
int e = v - 128;
|
||||||
|
|
||||||
|
r = clamp((298 * c + 409 * e + 128) >> 8);
|
||||||
|
g = clamp((298 * c - 100 * d - 208 * e + 128) >> 8);
|
||||||
|
b = clamp((298 * c + 516 * d + 128) >> 8);
|
||||||
|
}
|
@ -1,5 +1,15 @@
|
|||||||
add_subdirectory(hyperiond)
|
add_subdirectory(hyperiond)
|
||||||
add_subdirectory(hyperion-remote)
|
add_subdirectory(hyperion-remote)
|
||||||
if (ENABLE_V4L2)
|
|
||||||
|
# The following clients depend on the protobuf library
|
||||||
|
if(ENABLE_PROTOBUF)
|
||||||
|
# Add the 'Video 4 Linux' grabber if it is enabled
|
||||||
|
if(ENABLE_V4L2)
|
||||||
add_subdirectory(hyperion-v4l2)
|
add_subdirectory(hyperion-v4l2)
|
||||||
endif (ENABLE_V4L2)
|
endif()
|
||||||
|
|
||||||
|
# Add the X11 grabber if it is enabled
|
||||||
|
if(ENABLE_X11)
|
||||||
|
add_subdirectory(hyperion-x11)
|
||||||
|
endif()
|
||||||
|
endif()
|
||||||
|
@ -2,9 +2,6 @@ cmake_minimum_required(VERSION 2.8)
|
|||||||
|
|
||||||
project(hyperion-remote)
|
project(hyperion-remote)
|
||||||
|
|
||||||
# add protocol buffers
|
|
||||||
find_package(Protobuf REQUIRED)
|
|
||||||
|
|
||||||
# find Qt4
|
# find Qt4
|
||||||
find_package(Qt4 REQUIRED QtCore QtGui QtNetwork)
|
find_package(Qt4 REQUIRED QtCore QtGui QtNetwork)
|
||||||
|
|
||||||
|
@ -2,44 +2,29 @@ cmake_minimum_required(VERSION 2.8)
|
|||||||
|
|
||||||
project(hyperion-v4l2)
|
project(hyperion-v4l2)
|
||||||
|
|
||||||
# add protocol buffers
|
|
||||||
find_package(Protobuf REQUIRED)
|
|
||||||
|
|
||||||
# find Qt4
|
# find Qt4
|
||||||
find_package(Qt4 REQUIRED QtCore QtGui QtNetwork)
|
find_package(Qt4 REQUIRED QtCore QtGui QtNetwork)
|
||||||
|
|
||||||
include_directories(
|
include_directories(
|
||||||
${CMAKE_CURRENT_BINARY_DIR}
|
${CMAKE_CURRENT_BINARY_DIR}/../../libsrc/protoserver
|
||||||
${PROTOBUF_INCLUDE_DIRS}
|
${PROTOBUF_INCLUDE_DIRS}
|
||||||
${QT_INCLUDES}
|
${QT_INCLUDES}
|
||||||
)
|
)
|
||||||
|
|
||||||
set(Hyperion_V4L2_QT_HEADERS
|
set(Hyperion_V4L2_QT_HEADERS
|
||||||
ImageHandler.h
|
|
||||||
ScreenshotHandler.h
|
ScreenshotHandler.h
|
||||||
)
|
)
|
||||||
|
|
||||||
set(Hyperion_V4L2_HEADERS
|
set(Hyperion_V4L2_HEADERS
|
||||||
VideoStandardParameter.h
|
VideoStandardParameter.h
|
||||||
PixelFormatParameter.h
|
PixelFormatParameter.h
|
||||||
ProtoConnection.h
|
|
||||||
)
|
)
|
||||||
|
|
||||||
set(Hyperion_V4L2_SOURCES
|
set(Hyperion_V4L2_SOURCES
|
||||||
hyperion-v4l2.cpp
|
hyperion-v4l2.cpp
|
||||||
ProtoConnection.cpp
|
|
||||||
ImageHandler.cpp
|
|
||||||
ScreenshotHandler.cpp
|
ScreenshotHandler.cpp
|
||||||
)
|
)
|
||||||
|
|
||||||
set(Hyperion_V4L2_PROTOS
|
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/../../libsrc/protoserver/message.proto
|
|
||||||
)
|
|
||||||
|
|
||||||
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})
|
QT4_WRAP_CPP(Hyperion_V4L2_MOC_SOURCES ${Hyperion_V4L2_QT_HEADERS})
|
||||||
|
|
||||||
add_executable(hyperion-v4l2
|
add_executable(hyperion-v4l2
|
||||||
@ -47,8 +32,6 @@ add_executable(hyperion-v4l2
|
|||||||
${Hyperion_V4L2_SOURCES}
|
${Hyperion_V4L2_SOURCES}
|
||||||
${Hyperion_V4L2_QT_HEADERS}
|
${Hyperion_V4L2_QT_HEADERS}
|
||||||
${Hyperion_V4L2_MOC_SOURCES}
|
${Hyperion_V4L2_MOC_SOURCES}
|
||||||
${Hyperion_V4L2_PROTO_SRCS}
|
|
||||||
${Hyperion_V4L2_PROTO_HDRS}
|
|
||||||
)
|
)
|
||||||
|
|
||||||
target_link_libraries(hyperion-v4l2
|
target_link_libraries(hyperion-v4l2
|
||||||
@ -56,7 +39,7 @@ target_link_libraries(hyperion-v4l2
|
|||||||
getoptPlusPlus
|
getoptPlusPlus
|
||||||
blackborder
|
blackborder
|
||||||
hyperion-utils
|
hyperion-utils
|
||||||
${PROTOBUF_LIBRARIES}
|
protoserver
|
||||||
pthread
|
pthread
|
||||||
${QT_LIBRARIES}
|
${QT_LIBRARIES}
|
||||||
)
|
)
|
||||||
|
@ -1,18 +0,0 @@
|
|||||||
// hyperion-v4l2 includes
|
|
||||||
#include "ImageHandler.h"
|
|
||||||
|
|
||||||
ImageHandler::ImageHandler(const std::string & address, int priority, bool skipProtoReply) :
|
|
||||||
_priority(priority),
|
|
||||||
_connection(address)
|
|
||||||
{
|
|
||||||
_connection.setSkipReply(skipProtoReply);
|
|
||||||
}
|
|
||||||
|
|
||||||
ImageHandler::~ImageHandler()
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
void ImageHandler::receiveImage(const Image<ColorRgb> & image)
|
|
||||||
{
|
|
||||||
_connection.setImage(image, _priority, 1000);
|
|
||||||
}
|
|
@ -1,31 +0,0 @@
|
|||||||
// Qt includes
|
|
||||||
#include <QObject>
|
|
||||||
|
|
||||||
// hyperion includes
|
|
||||||
#include <utils/Image.h>
|
|
||||||
#include <utils/ColorRgb.h>
|
|
||||||
|
|
||||||
// hyperion v4l2 includes
|
|
||||||
#include "ProtoConnection.h"
|
|
||||||
|
|
||||||
/// This class handles callbacks from the V4L2 grabber
|
|
||||||
class ImageHandler : public QObject
|
|
||||||
{
|
|
||||||
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<ColorRgb> & image);
|
|
||||||
|
|
||||||
private:
|
|
||||||
/// Priority for calls to Hyperion
|
|
||||||
const int _priority;
|
|
||||||
|
|
||||||
/// Hyperion proto connection object
|
|
||||||
ProtoConnection _connection;
|
|
||||||
};
|
|
@ -2,7 +2,7 @@
|
|||||||
#include <getoptPlusPlus/getoptpp.h>
|
#include <getoptPlusPlus/getoptpp.h>
|
||||||
|
|
||||||
// grabber includes
|
// grabber includes
|
||||||
#include <grabber/PixelFormat.h>
|
#include <utils/PixelFormat.h>
|
||||||
|
|
||||||
using namespace vlofgren;
|
using namespace vlofgren;
|
||||||
|
|
||||||
|
@ -1,188 +0,0 @@
|
|||||||
// stl includes
|
|
||||||
#include <stdexcept>
|
|
||||||
|
|
||||||
// Qt includes
|
|
||||||
#include <QRgb>
|
|
||||||
|
|
||||||
// hyperion-v4l2 includes
|
|
||||||
#include "ProtoConnection.h"
|
|
||||||
|
|
||||||
ProtoConnection::ProtoConnection(const std::string & a) :
|
|
||||||
_socket(),
|
|
||||||
_skipReply(false)
|
|
||||||
{
|
|
||||||
QString address(a.c_str());
|
|
||||||
QStringList parts = address.split(":");
|
|
||||||
if (parts.size() != 2)
|
|
||||||
{
|
|
||||||
throw std::runtime_error(QString("Wrong address: unable to parse address (%1)").arg(address).toStdString());
|
|
||||||
}
|
|
||||||
_host = parts[0];
|
|
||||||
|
|
||||||
bool ok;
|
|
||||||
_port = parts[1].toUShort(&ok);
|
|
||||||
if (!ok)
|
|
||||||
{
|
|
||||||
throw std::runtime_error(QString("Wrong address: Unable to parse the port number (%1)").arg(parts[1]).toStdString());
|
|
||||||
}
|
|
||||||
|
|
||||||
// try to connect to host
|
|
||||||
std::cout << "Connecting to Hyperion: " << _host.toStdString() << ":" << _port << std::endl;
|
|
||||||
connectToHost();
|
|
||||||
}
|
|
||||||
|
|
||||||
ProtoConnection::~ProtoConnection()
|
|
||||||
{
|
|
||||||
_socket.close();
|
|
||||||
}
|
|
||||||
|
|
||||||
void ProtoConnection::setSkipReply(bool skip)
|
|
||||||
{
|
|
||||||
_skipReply = skip;
|
|
||||||
}
|
|
||||||
|
|
||||||
void ProtoConnection::setColor(const ColorRgb & color, int priority, int duration)
|
|
||||||
{
|
|
||||||
proto::HyperionRequest request;
|
|
||||||
request.set_command(proto::HyperionRequest::COLOR);
|
|
||||||
proto::ColorRequest * colorRequest = request.MutableExtension(proto::ColorRequest::colorRequest);
|
|
||||||
colorRequest->set_rgbcolor((color.red << 16) | (color.green << 8) | color.blue);
|
|
||||||
colorRequest->set_priority(priority);
|
|
||||||
colorRequest->set_duration(duration);
|
|
||||||
|
|
||||||
// send command message
|
|
||||||
sendMessage(request);
|
|
||||||
}
|
|
||||||
|
|
||||||
void ProtoConnection::setImage(const Image<ColorRgb> &image, int priority, int duration)
|
|
||||||
{
|
|
||||||
proto::HyperionRequest request;
|
|
||||||
request.set_command(proto::HyperionRequest::IMAGE);
|
|
||||||
proto::ImageRequest * imageRequest = request.MutableExtension(proto::ImageRequest::imageRequest);
|
|
||||||
imageRequest->set_imagedata(image.memptr(), image.width() * image.height() * 3);
|
|
||||||
imageRequest->set_imagewidth(image.width());
|
|
||||||
imageRequest->set_imageheight(image.height());
|
|
||||||
imageRequest->set_priority(priority);
|
|
||||||
imageRequest->set_duration(duration);
|
|
||||||
|
|
||||||
// send command message
|
|
||||||
sendMessage(request);
|
|
||||||
}
|
|
||||||
|
|
||||||
void ProtoConnection::clear(int priority)
|
|
||||||
{
|
|
||||||
proto::HyperionRequest request;
|
|
||||||
request.set_command(proto::HyperionRequest::CLEAR);
|
|
||||||
proto::ClearRequest * clearRequest = request.MutableExtension(proto::ClearRequest::clearRequest);
|
|
||||||
clearRequest->set_priority(priority);
|
|
||||||
|
|
||||||
// send command message
|
|
||||||
sendMessage(request);
|
|
||||||
}
|
|
||||||
|
|
||||||
void ProtoConnection::clearAll()
|
|
||||||
{
|
|
||||||
proto::HyperionRequest request;
|
|
||||||
request.set_command(proto::HyperionRequest::CLEARALL);
|
|
||||||
|
|
||||||
// send command message
|
|
||||||
sendMessage(request);
|
|
||||||
}
|
|
||||||
|
|
||||||
void ProtoConnection::connectToHost()
|
|
||||||
{
|
|
||||||
_socket.connectToHost(_host, _port);
|
|
||||||
if (_socket.waitForConnected()) {
|
|
||||||
std::cout << "Connected to Hyperion host" << std::endl;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void ProtoConnection::sendMessage(const proto::HyperionRequest &message)
|
|
||||||
{
|
|
||||||
if (_socket.state() == QAbstractSocket::UnconnectedState)
|
|
||||||
{
|
|
||||||
std::cout << "Currently disconnected: trying to connect to host" << std::endl;
|
|
||||||
connectToHost();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (_socket.state() != QAbstractSocket::ConnectedState)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// We only get here if we are connected
|
|
||||||
|
|
||||||
// serialize message (FastWriter already appends a newline)
|
|
||||||
std::string serializedMessage = message.SerializeAsString();
|
|
||||||
|
|
||||||
int length = serializedMessage.size();
|
|
||||||
const uint8_t header[] = {
|
|
||||||
uint8_t((length >> 24) & 0xFF),
|
|
||||||
uint8_t((length >> 16) & 0xFF),
|
|
||||||
uint8_t((length >> 8) & 0xFF),
|
|
||||||
uint8_t((length ) & 0xFF)};
|
|
||||||
|
|
||||||
// write message
|
|
||||||
int count = 0;
|
|
||||||
count += _socket.write(reinterpret_cast<const char *>(header), 4);
|
|
||||||
count += _socket.write(reinterpret_cast<const char *>(serializedMessage.data()), length);
|
|
||||||
if (!_socket.waitForBytesWritten())
|
|
||||||
{
|
|
||||||
std::cerr << "Error while writing data to host" << std::endl;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!_skipReply)
|
|
||||||
{
|
|
||||||
// read reply data
|
|
||||||
QByteArray serializedReply;
|
|
||||||
length = -1;
|
|
||||||
while (length < 0 && serializedReply.size() < length+4)
|
|
||||||
{
|
|
||||||
// receive reply
|
|
||||||
if (!_socket.waitForReadyRead())
|
|
||||||
{
|
|
||||||
std::cerr << "Error while reading data from host" << std::endl;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
serializedReply += _socket.readAll();
|
|
||||||
|
|
||||||
if (length < 0 && serializedReply.size() >= 4)
|
|
||||||
{
|
|
||||||
// read the message size
|
|
||||||
length =
|
|
||||||
((serializedReply[0]<<24) & 0xFF000000) |
|
|
||||||
((serializedReply[1]<<16) & 0x00FF0000) |
|
|
||||||
((serializedReply[2]<< 8) & 0x0000FF00) |
|
|
||||||
((serializedReply[3] ) & 0x000000FF);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// parse reply data
|
|
||||||
proto::HyperionReply reply;
|
|
||||||
reply.ParseFromArray(serializedReply.constData()+4, length);
|
|
||||||
|
|
||||||
// parse reply message
|
|
||||||
parseReply(reply);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
bool ProtoConnection::parseReply(const proto::HyperionReply &reply)
|
|
||||||
{
|
|
||||||
bool success = false;
|
|
||||||
|
|
||||||
if (!reply.success())
|
|
||||||
{
|
|
||||||
if (reply.has_error())
|
|
||||||
{
|
|
||||||
throw std::runtime_error("Error: " + reply.error());
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
throw std::runtime_error("Error: No error info");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return success;
|
|
||||||
}
|
|
@ -1,102 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
// stl includes
|
|
||||||
#include <string>
|
|
||||||
|
|
||||||
// Qt includes
|
|
||||||
#include <QColor>
|
|
||||||
#include <QImage>
|
|
||||||
#include <QTcpSocket>
|
|
||||||
#include <QMap>
|
|
||||||
|
|
||||||
// hyperion util
|
|
||||||
#include <utils/Image.h>
|
|
||||||
#include <utils/ColorRgb.h>
|
|
||||||
|
|
||||||
// jsoncpp includes
|
|
||||||
#include <message.pb.h>
|
|
||||||
|
|
||||||
///
|
|
||||||
/// Connection class to setup an connection to the hyperion server and execute commands
|
|
||||||
///
|
|
||||||
class ProtoConnection
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
///
|
|
||||||
/// Constructor
|
|
||||||
///
|
|
||||||
/// @param address The address of the Hyperion server (for example "192.168.0.32:19444)
|
|
||||||
///
|
|
||||||
ProtoConnection(const std::string & address);
|
|
||||||
|
|
||||||
///
|
|
||||||
/// Destructor
|
|
||||||
///
|
|
||||||
~ProtoConnection();
|
|
||||||
|
|
||||||
/// Do not read reply messages from Hyperion if set to true
|
|
||||||
void setSkipReply(bool skip);
|
|
||||||
|
|
||||||
///
|
|
||||||
/// Set all leds to the specified color
|
|
||||||
///
|
|
||||||
/// @param color The color
|
|
||||||
/// @param priority The priority
|
|
||||||
/// @param duration The duration in milliseconds
|
|
||||||
///
|
|
||||||
void setColor(const ColorRgb & color, int priority, int duration = 1);
|
|
||||||
|
|
||||||
///
|
|
||||||
/// Set the leds according to the given image (assume the image is stretched to the display size)
|
|
||||||
///
|
|
||||||
/// @param image The image
|
|
||||||
/// @param priority The priority
|
|
||||||
/// @param duration The duration in milliseconds
|
|
||||||
///
|
|
||||||
void setImage(const Image<ColorRgb> & image, int priority, int duration = -1);
|
|
||||||
|
|
||||||
///
|
|
||||||
/// Clear the given priority channel
|
|
||||||
///
|
|
||||||
/// @param priority The priority
|
|
||||||
///
|
|
||||||
void clear(int priority);
|
|
||||||
|
|
||||||
///
|
|
||||||
/// Clear all priority channels
|
|
||||||
///
|
|
||||||
void clearAll();
|
|
||||||
|
|
||||||
private:
|
|
||||||
/// Try to connect to the Hyperion host
|
|
||||||
void connectToHost();
|
|
||||||
|
|
||||||
///
|
|
||||||
/// Send a command message and receive its reply
|
|
||||||
///
|
|
||||||
/// @param message The message to send
|
|
||||||
///
|
|
||||||
void sendMessage(const proto::HyperionRequest & message);
|
|
||||||
|
|
||||||
///
|
|
||||||
/// Parse a reply message
|
|
||||||
///
|
|
||||||
/// @param reply The received reply
|
|
||||||
///
|
|
||||||
/// @return true if the reply indicates success
|
|
||||||
///
|
|
||||||
bool parseReply(const proto::HyperionReply & reply);
|
|
||||||
|
|
||||||
private:
|
|
||||||
/// The TCP-Socket with the connection to the server
|
|
||||||
QTcpSocket _socket;
|
|
||||||
|
|
||||||
/// Host address
|
|
||||||
QString _host;
|
|
||||||
|
|
||||||
/// Host port
|
|
||||||
uint16_t _port;
|
|
||||||
|
|
||||||
/// Skip receiving reply messages from Hyperion if set
|
|
||||||
bool _skipReply;
|
|
||||||
};
|
|
@ -15,11 +15,13 @@
|
|||||||
// grabber includes
|
// grabber includes
|
||||||
#include "grabber/V4L2Grabber.h"
|
#include "grabber/V4L2Grabber.h"
|
||||||
|
|
||||||
|
// proto includes
|
||||||
|
#include "protoserver/ProtoConnection.h"
|
||||||
|
#include "protoserver/ProtoConnectionWrapper.h"
|
||||||
|
|
||||||
// hyperion-v4l2 includes
|
// hyperion-v4l2 includes
|
||||||
#include "ProtoConnection.h"
|
|
||||||
#include "VideoStandardParameter.h"
|
#include "VideoStandardParameter.h"
|
||||||
#include "PixelFormatParameter.h"
|
#include "PixelFormatParameter.h"
|
||||||
#include "ImageHandler.h"
|
|
||||||
#include "ScreenshotHandler.h"
|
#include "ScreenshotHandler.h"
|
||||||
|
|
||||||
using namespace vlofgren;
|
using namespace vlofgren;
|
||||||
@ -100,6 +102,7 @@ int main(int argc, char** argv)
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// cropping values if not defined
|
||||||
if (!argCropLeft.isSet()) argCropLeft.setDefault(argCropWidth.getValue());
|
if (!argCropLeft.isSet()) argCropLeft.setDefault(argCropWidth.getValue());
|
||||||
if (!argCropRight.isSet()) argCropRight.setDefault(argCropWidth.getValue());
|
if (!argCropRight.isSet()) argCropRight.setDefault(argCropWidth.getValue());
|
||||||
if (!argCropTop.isSet()) argCropTop.setDefault(argCropHeight.getValue());
|
if (!argCropTop.isSet()) argCropTop.setDefault(argCropHeight.getValue());
|
||||||
@ -152,7 +155,7 @@ int main(int argc, char** argv)
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
ImageHandler handler(argAddress.getValue(), argPriority.getValue(), argSkipReply.isSet());
|
ProtoConnectionWrapper handler(argAddress.getValue(), argPriority.getValue(), 1000, argSkipReply.isSet());
|
||||||
QObject::connect(&grabber, SIGNAL(newFrame(Image<ColorRgb>)), &handler, SLOT(receiveImage(Image<ColorRgb>)));
|
QObject::connect(&grabber, SIGNAL(newFrame(Image<ColorRgb>)), &handler, SLOT(receiveImage(Image<ColorRgb>)));
|
||||||
grabber.start();
|
grabber.start();
|
||||||
QCoreApplication::exec();
|
QCoreApplication::exec();
|
||||||
|
52
src/hyperion-x11/CMakeLists.txt
Normal file
52
src/hyperion-x11/CMakeLists.txt
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
# Configure minimum CMAKE version
|
||||||
|
cmake_minimum_required(VERSION 2.8)
|
||||||
|
|
||||||
|
# Set the project name
|
||||||
|
project(hyperion-x11)
|
||||||
|
|
||||||
|
# find Qt4
|
||||||
|
find_package(Qt4 REQUIRED QtCore QtGui QtNetwork)
|
||||||
|
|
||||||
|
# Find X11
|
||||||
|
find_package(X11 REQUIRED)
|
||||||
|
|
||||||
|
include_directories(
|
||||||
|
${CMAKE_CURRENT_BINARY_DIR}/../../libsrc/protoserver
|
||||||
|
${QT_INCLUDES}
|
||||||
|
${X11_INCLUDES}
|
||||||
|
${PROTOBUF_INCLUDE_DIRS}
|
||||||
|
)
|
||||||
|
|
||||||
|
set(Hyperion_X11_QT_HEADERS
|
||||||
|
X11Wrapper.h)
|
||||||
|
|
||||||
|
set(Hyperion_X11_HEADERS
|
||||||
|
)
|
||||||
|
|
||||||
|
set(Hyperion_X11_SOURCES
|
||||||
|
hyperion-x11.cpp
|
||||||
|
X11Wrapper.cpp
|
||||||
|
)
|
||||||
|
|
||||||
|
QT4_WRAP_CPP(Hyperion_X11_HEADERS_MOC ${Hyperion_X11_QT_HEADERS})
|
||||||
|
|
||||||
|
add_executable(hyperion-x11
|
||||||
|
${Hyperion_X11_HEADERS}
|
||||||
|
${Hyperion_X11_SOURCES}
|
||||||
|
${Hyperion_X11_HEADERS_MOC}
|
||||||
|
)
|
||||||
|
|
||||||
|
target_link_libraries(hyperion-x11
|
||||||
|
getoptPlusPlus
|
||||||
|
blackborder
|
||||||
|
hyperion-utils
|
||||||
|
protoserver
|
||||||
|
x11-grabber
|
||||||
|
${X11_LIBRARIES}
|
||||||
|
pthread
|
||||||
|
)
|
||||||
|
|
||||||
|
qt4_use_modules(hyperion-x11
|
||||||
|
Core
|
||||||
|
Gui
|
||||||
|
Network)
|
36
src/hyperion-x11/X11Wrapper.cpp
Normal file
36
src/hyperion-x11/X11Wrapper.cpp
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
|
||||||
|
// Hyperion-X11 includes
|
||||||
|
#include "X11Wrapper.h"
|
||||||
|
|
||||||
|
X11Wrapper::X11Wrapper(int grabInterval, int cropLeft, int cropRight, int cropTop, int cropBottom, int horizontalPixelDecimation, int verticalPixelDecimation) :
|
||||||
|
_timer(this),
|
||||||
|
_grabber(cropLeft, cropRight, cropTop, cropBottom, horizontalPixelDecimation, verticalPixelDecimation)
|
||||||
|
{
|
||||||
|
_timer.setSingleShot(false);
|
||||||
|
_timer.setInterval(grabInterval);
|
||||||
|
|
||||||
|
// Connect capturing to the timeout signal of the timer
|
||||||
|
connect(&_timer, SIGNAL(timeout()), this, SLOT(capture()));
|
||||||
|
}
|
||||||
|
|
||||||
|
const Image<ColorRgb> & X11Wrapper::getScreenshot()
|
||||||
|
{
|
||||||
|
const Image<ColorRgb> & screenshot = _grabber.grab();
|
||||||
|
return screenshot;
|
||||||
|
}
|
||||||
|
|
||||||
|
void X11Wrapper::start()
|
||||||
|
{
|
||||||
|
_timer.start();
|
||||||
|
}
|
||||||
|
|
||||||
|
void X11Wrapper::stop()
|
||||||
|
{
|
||||||
|
_timer.stop();
|
||||||
|
}
|
||||||
|
|
||||||
|
void X11Wrapper::capture()
|
||||||
|
{
|
||||||
|
const Image<ColorRgb> & screenshot = _grabber.grab();
|
||||||
|
emit sig_screenshot(screenshot);
|
||||||
|
}
|
39
src/hyperion-x11/X11Wrapper.h
Normal file
39
src/hyperion-x11/X11Wrapper.h
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
|
||||||
|
// QT includes
|
||||||
|
#include <QTimer>
|
||||||
|
|
||||||
|
// Hyperion-X11 includes
|
||||||
|
#include <grabber/X11Grabber.h>
|
||||||
|
|
||||||
|
class X11Wrapper : public QObject
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
public:
|
||||||
|
X11Wrapper(int grabInterval, int cropLeft, int cropRight, int cropTop, int cropBottom, int horizontalPixelDecimation, int verticalPixelDecimation);
|
||||||
|
|
||||||
|
const Image<ColorRgb> & getScreenshot();
|
||||||
|
|
||||||
|
///
|
||||||
|
/// Starts the timed capturing of screenshots
|
||||||
|
///
|
||||||
|
void start();
|
||||||
|
|
||||||
|
void stop();
|
||||||
|
|
||||||
|
signals:
|
||||||
|
void sig_screenshot(const Image<ColorRgb> & screenshot);
|
||||||
|
|
||||||
|
private slots:
|
||||||
|
///
|
||||||
|
/// Performs a single screenshot capture and publishes the capture screenshot on the screenshot
|
||||||
|
/// signal.
|
||||||
|
///
|
||||||
|
void capture();
|
||||||
|
|
||||||
|
private:
|
||||||
|
/// The QT timer to generate capture-publish events
|
||||||
|
QTimer _timer;
|
||||||
|
|
||||||
|
/// The grabber for creating screenshots
|
||||||
|
X11Grabber _grabber;
|
||||||
|
};
|
110
src/hyperion-x11/hyperion-x11.cpp
Normal file
110
src/hyperion-x11/hyperion-x11.cpp
Normal file
@ -0,0 +1,110 @@
|
|||||||
|
|
||||||
|
// QT includes
|
||||||
|
#include <QCoreApplication>
|
||||||
|
#include <QImage>
|
||||||
|
|
||||||
|
// getoptPlusPLus includes
|
||||||
|
#include <getoptPlusPlus/getoptpp.h>
|
||||||
|
|
||||||
|
#include "protoserver/ProtoConnectionWrapper.h"
|
||||||
|
#include "X11Wrapper.h"
|
||||||
|
|
||||||
|
using namespace vlofgren;
|
||||||
|
|
||||||
|
// save the image as screenshot
|
||||||
|
void saveScreenshot(const char * filename, const Image<ColorRgb> & image)
|
||||||
|
{
|
||||||
|
// store as PNG
|
||||||
|
QImage pngImage((const uint8_t *) image.memptr(), image.width(), image.height(), 3*image.width(), QImage::Format_RGB888);
|
||||||
|
pngImage.save(filename);
|
||||||
|
}
|
||||||
|
|
||||||
|
int main(int argc, char ** argv)
|
||||||
|
{
|
||||||
|
QCoreApplication app(argc, argv);
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// create the option parser and initialize all parameters
|
||||||
|
OptionsParser optionParser("X11 capture application for Hyperion");
|
||||||
|
ParameterSet & parameters = optionParser.getParameters();
|
||||||
|
|
||||||
|
IntParameter & argFps = parameters.add<IntParameter> ('f', "framerate", "Capture frame rate [default=10]");
|
||||||
|
IntParameter & argCropWidth = parameters.add<IntParameter> (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<IntParameter> (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<IntParameter> (0x0, "crop-left", "Number of pixels to crop from the left of the picture before decimation (overrides --crop-width)");
|
||||||
|
IntParameter & argCropRight = parameters.add<IntParameter> (0x0, "crop-right", "Number of pixels to crop from the right of the picture before decimation (overrides --crop-width)");
|
||||||
|
IntParameter & argCropTop = parameters.add<IntParameter> (0x0, "crop-top", "Number of pixels to crop from the top of the picture before decimation (overrides --crop-height)");
|
||||||
|
IntParameter & argCropBottom = parameters.add<IntParameter> (0x0, "crop-bottom", "Number of pixels to crop from the bottom of the picture before decimation (overrides --crop-height)");
|
||||||
|
IntParameter & argSizeDecimation = parameters.add<IntParameter> ('s', "size-decimator", "Decimation factor for the output size [default=8]");
|
||||||
|
SwitchParameter<> & argScreenshot = parameters.add<SwitchParameter<>> (0x0, "screenshot", "Take a single screenshot, save it to file and quit");
|
||||||
|
StringParameter & argAddress = parameters.add<StringParameter> ('a', "address", "Set the address of the hyperion server [default: 127.0.0.1:19445]");
|
||||||
|
IntParameter & argPriority = parameters.add<IntParameter> ('p', "priority", "Use the provided priority channel (the lower the number, the higher the priority) [default: 800]");
|
||||||
|
SwitchParameter<> & argSkipReply = parameters.add<SwitchParameter<>> (0x0, "skip-reply", "Do not receive and check reply messages from Hyperion");
|
||||||
|
SwitchParameter<> & argHelp = parameters.add<SwitchParameter<>> ('h', "help", "Show this help message and exit");
|
||||||
|
|
||||||
|
// set defaults
|
||||||
|
argFps.setDefault(10);
|
||||||
|
argCropWidth.setDefault(0);
|
||||||
|
argCropHeight.setDefault(0);
|
||||||
|
argSizeDecimation.setDefault(8);
|
||||||
|
argAddress.setDefault("127.0.0.1:19445");
|
||||||
|
argPriority.setDefault(800);
|
||||||
|
|
||||||
|
// parse all options
|
||||||
|
optionParser.parse(argc, const_cast<const char **>(argv));
|
||||||
|
|
||||||
|
// check if we need to display the usage. exit if we do.
|
||||||
|
if (argHelp.isSet())
|
||||||
|
{
|
||||||
|
optionParser.usage();
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// cropping values if not defined
|
||||||
|
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());
|
||||||
|
|
||||||
|
// Create the X11 grabbing stuff
|
||||||
|
int grabInterval = 1000 / argFps.getValue();
|
||||||
|
X11Wrapper x11Wrapper(
|
||||||
|
grabInterval,
|
||||||
|
argCropLeft.getValue(),
|
||||||
|
argCropRight.getValue(),
|
||||||
|
argCropTop.getValue(),
|
||||||
|
argCropBottom.getValue(),
|
||||||
|
argSizeDecimation.getValue(), // horizontal decimation
|
||||||
|
argSizeDecimation.getValue()); // vertical decimation
|
||||||
|
|
||||||
|
if (argScreenshot.isSet())
|
||||||
|
{
|
||||||
|
// Capture a single screenshot and finish
|
||||||
|
const Image<ColorRgb> & screenshot = x11Wrapper.getScreenshot();
|
||||||
|
saveScreenshot("screenshot.png", screenshot);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Create the Proto-connection with hyperiond
|
||||||
|
ProtoConnectionWrapper protoWrapper(argAddress.getValue(), argPriority.getValue(), 1000, argSkipReply.isSet());
|
||||||
|
|
||||||
|
// Connect the screen capturing to the proto processing
|
||||||
|
QObject::connect(&x11Wrapper, SIGNAL(sig_screenshot(const Image<ColorRgb> &)), &protoWrapper, SLOT(receiveImage(Image<ColorRgb>)));
|
||||||
|
|
||||||
|
// Start the capturing
|
||||||
|
x11Wrapper.start();
|
||||||
|
|
||||||
|
// Start the application
|
||||||
|
app.exec();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (const std::runtime_error & e)
|
||||||
|
{
|
||||||
|
// An error occured. Display error and quit
|
||||||
|
std::cerr << e.what() << std::endl;
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
@ -7,7 +7,6 @@ target_link_libraries(hyperiond
|
|||||||
xbmcvideochecker
|
xbmcvideochecker
|
||||||
effectengine
|
effectengine
|
||||||
jsonserver
|
jsonserver
|
||||||
protoserver
|
|
||||||
boblightserver
|
boblightserver
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -18,3 +17,7 @@ endif (ENABLE_DISPMANX)
|
|||||||
if (ENABLE_V4L2)
|
if (ENABLE_V4L2)
|
||||||
target_link_libraries(hyperiond v4l2-grabber)
|
target_link_libraries(hyperiond v4l2-grabber)
|
||||||
endif (ENABLE_V4L2)
|
endif (ENABLE_V4L2)
|
||||||
|
|
||||||
|
if (ENABLE_PROTOBUF)
|
||||||
|
target_link_libraries(hyperiond protoserver)
|
||||||
|
endif (ENABLE_PROTOBUF)
|
||||||
|
@ -36,8 +36,10 @@
|
|||||||
// JsonServer includes
|
// JsonServer includes
|
||||||
#include <jsonserver/JsonServer.h>
|
#include <jsonserver/JsonServer.h>
|
||||||
|
|
||||||
|
#ifdef ENABLE_PROTOBUF
|
||||||
// ProtoServer includes
|
// ProtoServer includes
|
||||||
#include <protoserver/ProtoServer.h>
|
#include <protoserver/ProtoServer.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
// BoblightServer includes
|
// BoblightServer includes
|
||||||
#include <boblightserver/BoblightServer.h>
|
#include <boblightserver/BoblightServer.h>
|
||||||
@ -113,6 +115,8 @@ int main(int argc, char** argv)
|
|||||||
const unsigned duration_ms = effectConfig["duration_ms"].asUInt();
|
const unsigned duration_ms = effectConfig["duration_ms"].asUInt();
|
||||||
const int priority = 0;
|
const int priority = 0;
|
||||||
|
|
||||||
|
hyperion.setColor(priority+1, ColorRgb::BLACK, duration_ms, false);
|
||||||
|
|
||||||
if (effectConfig.isMember("args"))
|
if (effectConfig.isMember("args"))
|
||||||
{
|
{
|
||||||
const Json::Value effectConfigArgs = effectConfig["args"];
|
const Json::Value effectConfigArgs = effectConfig["args"];
|
||||||
@ -124,11 +128,9 @@ int main(int argc, char** argv)
|
|||||||
{
|
{
|
||||||
std::cout << "Failed to start boot sequence: " << effectName << " with user-defined arguments" << std::endl;
|
std::cout << "Failed to start boot sequence: " << effectName << " with user-defined arguments" << std::endl;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
|
||||||
if (hyperion.setEffect(effectName, priority, duration_ms) == 0)
|
if (hyperion.setEffect(effectName, priority, duration_ms) == 0)
|
||||||
{
|
{
|
||||||
std::cout << "Boot sequence(" << effectName << ") created and started" << std::endl;
|
std::cout << "Boot sequence(" << effectName << ") created and started" << std::endl;
|
||||||
@ -233,6 +235,7 @@ int main(int argc, char** argv)
|
|||||||
std::cout << "Json server created and started on port " << jsonServer->getPort() << std::endl;
|
std::cout << "Json server created and started on port " << jsonServer->getPort() << std::endl;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#ifdef ENABLE_PROTOBUF
|
||||||
// Create Proto server if configuration is present
|
// Create Proto server if configuration is present
|
||||||
ProtoServer * protoServer = nullptr;
|
ProtoServer * protoServer = nullptr;
|
||||||
if (config.isMember("protoServer"))
|
if (config.isMember("protoServer"))
|
||||||
@ -241,6 +244,7 @@ int main(int argc, char** argv)
|
|||||||
protoServer = new ProtoServer(&hyperion, protoServerConfig["port"].asUInt());
|
protoServer = new ProtoServer(&hyperion, protoServerConfig["port"].asUInt());
|
||||||
std::cout << "Proto server created and started on port " << protoServer->getPort() << std::endl;
|
std::cout << "Proto server created and started on port " << protoServer->getPort() << std::endl;
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
// Create Boblight server if configuration is present
|
// Create Boblight server if configuration is present
|
||||||
BoblightServer * boblightServer = nullptr;
|
BoblightServer * boblightServer = nullptr;
|
||||||
@ -264,7 +268,9 @@ int main(int argc, char** argv)
|
|||||||
#endif
|
#endif
|
||||||
delete xbmcVideoChecker;
|
delete xbmcVideoChecker;
|
||||||
delete jsonServer;
|
delete jsonServer;
|
||||||
|
#ifdef ENABLE_PROTOBUF
|
||||||
delete protoServer;
|
delete protoServer;
|
||||||
|
#endif
|
||||||
delete boblightServer;
|
delete boblightServer;
|
||||||
|
|
||||||
// leave application
|
// leave application
|
||||||
|
@ -52,3 +52,11 @@ target_link_libraries(test_qregexp
|
|||||||
add_executable(test_qtscreenshot TestQtScreenshot.cpp)
|
add_executable(test_qtscreenshot TestQtScreenshot.cpp)
|
||||||
target_link_libraries(test_qtscreenshot
|
target_link_libraries(test_qtscreenshot
|
||||||
${QT_LIBRARIES})
|
${QT_LIBRARIES})
|
||||||
|
|
||||||
|
if(ENABLE_X11)
|
||||||
|
# Find X11
|
||||||
|
find_package(X11 REQUIRED)
|
||||||
|
|
||||||
|
add_executable(test_x11performance TestX11Performance.cpp)
|
||||||
|
target_link_libraries(test_x11performance ${X11_LIBRARIES} ${QT_LIBRARIES})
|
||||||
|
endif(ENABLE_X11)
|
||||||
|
@ -6,15 +6,67 @@
|
|||||||
#include <QApplication>
|
#include <QApplication>
|
||||||
#include <QDesktopWidget>
|
#include <QDesktopWidget>
|
||||||
#include <QPixmap>
|
#include <QPixmap>
|
||||||
|
#include <QFile>
|
||||||
|
#include <QRgb>
|
||||||
|
|
||||||
|
#include <QElapsedTimer>
|
||||||
|
|
||||||
|
// Utils includes
|
||||||
|
#include <utils/Image.h>
|
||||||
|
#include <utils/ColorRgb.h>
|
||||||
|
|
||||||
|
void createScreenshot(const int cropHorizontal, const int cropVertical, const int decimation, Image<ColorRgb> & image)
|
||||||
|
{
|
||||||
|
// Create the full size screenshot
|
||||||
|
const QRect screenSize = QApplication::desktop()->screenGeometry();
|
||||||
|
const int croppedWidth = screenSize.width() - 2*cropVertical;
|
||||||
|
const int croppedHeight = screenSize.height() - 2*cropHorizontal;
|
||||||
|
const QPixmap fullSizeScreenshot = QPixmap::grabWindow(QApplication::desktop()->winId(), cropVertical, cropHorizontal, croppedWidth, croppedHeight);
|
||||||
|
|
||||||
|
// Scale the screenshot to the required size
|
||||||
|
const int width = fullSizeScreenshot.width()/decimation;
|
||||||
|
const int height = fullSizeScreenshot.height()/decimation;
|
||||||
|
const QPixmap scaledScreenshot = fullSizeScreenshot.scaled(width, height, Qt::IgnoreAspectRatio, Qt::FastTransformation);
|
||||||
|
|
||||||
|
// Convert the QPixmap to QImage in order to get out RGB values
|
||||||
|
const QImage qImage = scaledScreenshot.toImage();
|
||||||
|
|
||||||
|
// Make sure that the output image has the right size
|
||||||
|
image.resize(width, height);
|
||||||
|
|
||||||
|
// Copy the data into the output image
|
||||||
|
for (int y=0; y<qImage.height(); ++y)
|
||||||
|
{
|
||||||
|
for (int x=0; x<qImage.width(); ++x)
|
||||||
|
{
|
||||||
|
// Get the pixel at [x;y] (format int32 #AARRGGBB)
|
||||||
|
const QRgb inPixel = qImage.pixel(x,y);
|
||||||
|
|
||||||
|
// Copy the color channels into the output pixel
|
||||||
|
ColorRgb & outPixel = image(x,y);
|
||||||
|
outPixel.red = (inPixel & 0x00ff0000) >> 16;
|
||||||
|
outPixel.green = (inPixel & 0x0000ff00) >> 8;
|
||||||
|
outPixel.blue = (inPixel & 0x000000ff);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
int main(int argc, char** argv)
|
int main(int argc, char** argv)
|
||||||
{
|
{
|
||||||
|
int decimation = 10;
|
||||||
|
|
||||||
QApplication app(argc, argv);
|
QApplication app(argc, argv);
|
||||||
|
QElapsedTimer timer;
|
||||||
|
|
||||||
QPixmap originalPixmap = QPixmap::grabWindow(QApplication::desktop()->winId());
|
Image<ColorRgb> screenshot(64,64);
|
||||||
|
|
||||||
std::cout << "Grabbed image: [" << originalPixmap.width() << "; " << originalPixmap.height() << "]" << std::endl;
|
int loopCnt = 100;
|
||||||
|
timer.start();
|
||||||
|
for (int i=0; i<loopCnt; ++i)
|
||||||
|
{
|
||||||
|
createScreenshot(0,0, decimation, screenshot);
|
||||||
|
}
|
||||||
|
std::cout << "Time required for single screenshot: " << timer.elapsed()/loopCnt << "ms" << std::endl;
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
139
test/TestX11Performance.cpp
Normal file
139
test/TestX11Performance.cpp
Normal file
@ -0,0 +1,139 @@
|
|||||||
|
|
||||||
|
// X11 includes
|
||||||
|
#include <X11/Xlib.h>
|
||||||
|
#include <X11/Xutil.h>
|
||||||
|
|
||||||
|
#include <QElapsedTimer>
|
||||||
|
|
||||||
|
#include <utils/Image.h>
|
||||||
|
#include <utils/ColorRgb.h>
|
||||||
|
|
||||||
|
void foo_1(int pixelDecimation)
|
||||||
|
{
|
||||||
|
int cropWidth = 0;
|
||||||
|
int cropHeight = 0;
|
||||||
|
|
||||||
|
Image<ColorRgb> image(64, 64);
|
||||||
|
|
||||||
|
/// Reference to the X11 display (nullptr if not opened)
|
||||||
|
Display * x11Display;
|
||||||
|
|
||||||
|
const char * display_name = nullptr;
|
||||||
|
x11Display = XOpenDisplay(display_name);
|
||||||
|
|
||||||
|
std::cout << "Opened display: " << x11Display << std::endl;
|
||||||
|
|
||||||
|
XWindowAttributes window_attributes_return;
|
||||||
|
XGetWindowAttributes(x11Display, DefaultRootWindow(x11Display), &window_attributes_return);
|
||||||
|
|
||||||
|
int screenWidth = window_attributes_return.width;
|
||||||
|
int screenHeight = window_attributes_return.height;
|
||||||
|
std::cout << "[" << screenWidth << "x" << screenHeight <<"]" << std::endl;
|
||||||
|
|
||||||
|
// Update the size of the buffer used to transfer the screenshot
|
||||||
|
int width = (screenWidth - 2 * cropWidth + pixelDecimation/2) / pixelDecimation;
|
||||||
|
int height = (screenHeight - 2 * cropHeight + pixelDecimation/2) / pixelDecimation;
|
||||||
|
image.resize(width, height);
|
||||||
|
|
||||||
|
const int croppedWidth = screenWidth - 2*cropWidth;
|
||||||
|
const int croppedHeight = screenHeight - 2*cropHeight;
|
||||||
|
|
||||||
|
QElapsedTimer timer;
|
||||||
|
timer.start();
|
||||||
|
|
||||||
|
XImage * xImage = XGetImage(x11Display, DefaultRootWindow(x11Display), cropWidth, cropHeight, croppedWidth, croppedHeight, AllPlanes, ZPixmap);
|
||||||
|
|
||||||
|
std::cout << "Captured image: " << xImage << std::endl;
|
||||||
|
|
||||||
|
// Copy the capture XImage to the local image (and apply required decimation)
|
||||||
|
ColorRgb * outputPtr = image.memptr();
|
||||||
|
for (int iY=(pixelDecimation/2); iY<croppedHeight; iY+=pixelDecimation)
|
||||||
|
{
|
||||||
|
for (int iX=(pixelDecimation/2); iX<croppedWidth; iX+=pixelDecimation)
|
||||||
|
{
|
||||||
|
// Extract the pixel from the X11-image
|
||||||
|
const uint32_t pixel = uint32_t(XGetPixel(xImage, iX, iY));
|
||||||
|
|
||||||
|
// Assign the color value
|
||||||
|
outputPtr->red = uint8_t((pixel >> 16) & 0xff);
|
||||||
|
outputPtr->green = uint8_t((pixel >> 8) & 0xff);
|
||||||
|
outputPtr->blue = uint8_t((pixel >> 0) & 0xff);
|
||||||
|
|
||||||
|
// Move to the next output pixel
|
||||||
|
++outputPtr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cleanup allocated resources of the X11 grab
|
||||||
|
XDestroyImage(xImage);
|
||||||
|
|
||||||
|
std::cout << "Time required: " << timer.elapsed() << " ms" << std::endl;
|
||||||
|
|
||||||
|
XCloseDisplay(x11Display);
|
||||||
|
}
|
||||||
|
|
||||||
|
void foo_2(int pixelDecimation)
|
||||||
|
{
|
||||||
|
int cropWidth = 0;
|
||||||
|
int cropHeight = 0;
|
||||||
|
|
||||||
|
Image<ColorRgb> image(64, 64);
|
||||||
|
|
||||||
|
/// Reference to the X11 display (nullptr if not opened)
|
||||||
|
Display * x11Display;
|
||||||
|
|
||||||
|
const char * display_name = nullptr;
|
||||||
|
x11Display = XOpenDisplay(display_name);
|
||||||
|
|
||||||
|
XWindowAttributes window_attributes_return;
|
||||||
|
XGetWindowAttributes(x11Display, DefaultRootWindow(x11Display), &window_attributes_return);
|
||||||
|
|
||||||
|
int screenWidth = window_attributes_return.width;
|
||||||
|
int screenHeight = window_attributes_return.height;
|
||||||
|
std::cout << "[" << screenWidth << "x" << screenHeight <<"]" << std::endl;
|
||||||
|
|
||||||
|
// Update the size of the buffer used to transfer the screenshot
|
||||||
|
int width = (screenWidth - 2 * cropWidth + pixelDecimation/2) / pixelDecimation;
|
||||||
|
int height = (screenHeight - 2 * cropHeight + pixelDecimation/2) / pixelDecimation;
|
||||||
|
image.resize(width, height);
|
||||||
|
|
||||||
|
const int croppedWidth = screenWidth - 2*cropWidth;
|
||||||
|
const int croppedHeight = screenHeight - 2*cropHeight;
|
||||||
|
|
||||||
|
QElapsedTimer timer;
|
||||||
|
timer.start();
|
||||||
|
|
||||||
|
// Copy the capture XImage to the local image (and apply required decimation)
|
||||||
|
ColorRgb * outputPtr = image.memptr();
|
||||||
|
for (int iY=(pixelDecimation/2); iY<croppedHeight; iY+=pixelDecimation)
|
||||||
|
{
|
||||||
|
for (int iX=(pixelDecimation/2); iX<croppedWidth; iX+=pixelDecimation)
|
||||||
|
{
|
||||||
|
XImage * xImage = XGetImage(x11Display, DefaultRootWindow(x11Display), iX, iY, 1, 1, AllPlanes, ZPixmap);
|
||||||
|
// Extract the pixel from the X11-image
|
||||||
|
const uint32_t pixel = uint32_t(XGetPixel(xImage, 0, 0));
|
||||||
|
|
||||||
|
// Assign the color value
|
||||||
|
outputPtr->red = uint8_t((pixel >> 16) & 0xff);
|
||||||
|
outputPtr->green = uint8_t((pixel >> 8) & 0xff);
|
||||||
|
outputPtr->blue = uint8_t((pixel >> 0) & 0xff);
|
||||||
|
|
||||||
|
// Move to the next output pixel
|
||||||
|
++outputPtr;
|
||||||
|
|
||||||
|
// Cleanup allocated resources of the X11 grab
|
||||||
|
XDestroyImage(xImage);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
std::cout << "Time required: " << timer.elapsed() << " ms" << std::endl;
|
||||||
|
|
||||||
|
|
||||||
|
XCloseDisplay(x11Display);
|
||||||
|
}
|
||||||
|
|
||||||
|
int main()
|
||||||
|
{
|
||||||
|
foo_1(10);
|
||||||
|
foo_2(10);
|
||||||
|
return 0;
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user