using cmake to build project and submodules
This commit is contained in:
149
RpiLedBars/backend/.clang-format
Normal file
149
RpiLedBars/backend/.clang-format
Normal file
@@ -0,0 +1,149 @@
|
||||
---
|
||||
Language: Cpp
|
||||
# BasedOnStyle: LLVM
|
||||
AccessModifierOffset: -2
|
||||
AlignAfterOpenBracket: Align
|
||||
AlignConsecutiveMacros: false
|
||||
AlignConsecutiveAssignments: false
|
||||
AlignConsecutiveBitFields: false
|
||||
AlignConsecutiveDeclarations: false
|
||||
AlignEscapedNewlines: Right
|
||||
AlignOperands: Align
|
||||
AlignTrailingComments: true
|
||||
AllowAllArgumentsOnNextLine: true
|
||||
AllowAllConstructorInitializersOnNextLine: true
|
||||
AllowAllParametersOfDeclarationOnNextLine: true
|
||||
AllowShortEnumsOnASingleLine: true
|
||||
AllowShortBlocksOnASingleLine: Never
|
||||
AllowShortCaseLabelsOnASingleLine: false
|
||||
AllowShortFunctionsOnASingleLine: All
|
||||
AllowShortLambdasOnASingleLine: All
|
||||
AllowShortIfStatementsOnASingleLine: Never
|
||||
AllowShortLoopsOnASingleLine: false
|
||||
AlwaysBreakAfterDefinitionReturnType: None
|
||||
AlwaysBreakAfterReturnType: None
|
||||
AlwaysBreakBeforeMultilineStrings: false
|
||||
AlwaysBreakTemplateDeclarations: MultiLine
|
||||
BinPackArguments: true
|
||||
BinPackParameters: true
|
||||
BraceWrapping:
|
||||
AfterCaseLabel: false
|
||||
AfterClass: false
|
||||
AfterControlStatement: Never
|
||||
AfterEnum: false
|
||||
AfterFunction: false
|
||||
AfterNamespace: false
|
||||
AfterObjCDeclaration: false
|
||||
AfterStruct: false
|
||||
AfterUnion: false
|
||||
AfterExternBlock: false
|
||||
BeforeCatch: false
|
||||
BeforeElse: false
|
||||
BeforeLambdaBody: false
|
||||
BeforeWhile: false
|
||||
IndentBraces: false
|
||||
SplitEmptyFunction: true
|
||||
SplitEmptyRecord: true
|
||||
SplitEmptyNamespace: true
|
||||
BreakBeforeBinaryOperators: None
|
||||
BreakBeforeBraces: Attach
|
||||
BreakBeforeInheritanceComma: false
|
||||
BreakInheritanceList: BeforeColon
|
||||
BreakBeforeTernaryOperators: true
|
||||
BreakConstructorInitializersBeforeComma: false
|
||||
BreakConstructorInitializers: BeforeColon
|
||||
BreakAfterJavaFieldAnnotations: false
|
||||
BreakStringLiterals: true
|
||||
ColumnLimit: 100
|
||||
CommentPragmas: '^ IWYU pragma:'
|
||||
CompactNamespaces: false
|
||||
ConstructorInitializerAllOnOneLineOrOnePerLine: false
|
||||
ConstructorInitializerIndentWidth: 4
|
||||
ContinuationIndentWidth: 4
|
||||
Cpp11BracedListStyle: true
|
||||
DeriveLineEnding: true
|
||||
DerivePointerAlignment: false
|
||||
DisableFormat: false
|
||||
ExperimentalAutoDetectBinPacking: false
|
||||
FixNamespaceComments: true
|
||||
ForEachMacros:
|
||||
- foreach
|
||||
- Q_FOREACH
|
||||
- BOOST_FOREACH
|
||||
IncludeBlocks: Preserve
|
||||
IncludeCategories:
|
||||
- Regex: '^"(llvm|llvm-c|clang|clang-c)/'
|
||||
Priority: 2
|
||||
SortPriority: 0
|
||||
- Regex: '^(<|"(gtest|gmock|isl|json)/)'
|
||||
Priority: 3
|
||||
SortPriority: 0
|
||||
- Regex: '.*'
|
||||
Priority: 1
|
||||
SortPriority: 0
|
||||
IncludeIsMainRegex: '(Test)?$'
|
||||
IncludeIsMainSourceRegex: ''
|
||||
IndentCaseLabels: false
|
||||
IndentCaseBlocks: false
|
||||
IndentGotoLabels: true
|
||||
IndentPPDirectives: None
|
||||
IndentExternBlock: AfterExternBlock
|
||||
IndentWidth: 2
|
||||
IndentWrappedFunctionNames: false
|
||||
InsertTrailingCommas: None
|
||||
JavaScriptQuotes: Leave
|
||||
JavaScriptWrapImports: true
|
||||
KeepEmptyLinesAtTheStartOfBlocks: true
|
||||
MacroBlockBegin: ''
|
||||
MacroBlockEnd: ''
|
||||
MaxEmptyLinesToKeep: 1
|
||||
NamespaceIndentation: None
|
||||
ObjCBinPackProtocolList: Auto
|
||||
ObjCBlockIndentWidth: 2
|
||||
ObjCBreakBeforeNestedBlockParam: true
|
||||
ObjCSpaceAfterProperty: false
|
||||
ObjCSpaceBeforeProtocolList: true
|
||||
PenaltyBreakAssignment: 2
|
||||
PenaltyBreakBeforeFirstCallParameter: 19
|
||||
PenaltyBreakComment: 300
|
||||
PenaltyBreakFirstLessLess: 120
|
||||
PenaltyBreakString: 1000
|
||||
PenaltyBreakTemplateDeclaration: 10
|
||||
PenaltyExcessCharacter: 1000000
|
||||
PenaltyReturnTypeOnItsOwnLine: 60
|
||||
PointerAlignment: Right
|
||||
ReflowComments: true
|
||||
SortIncludes: true
|
||||
SortUsingDeclarations: true
|
||||
SpaceAfterCStyleCast: false
|
||||
SpaceAfterLogicalNot: false
|
||||
SpaceAfterTemplateKeyword: true
|
||||
SpaceBeforeAssignmentOperators: true
|
||||
SpaceBeforeCpp11BracedList: false
|
||||
SpaceBeforeCtorInitializerColon: true
|
||||
SpaceBeforeInheritanceColon: true
|
||||
SpaceBeforeParens: ControlStatements
|
||||
SpaceBeforeRangeBasedForLoopColon: true
|
||||
SpaceInEmptyBlock: false
|
||||
SpaceInEmptyParentheses: false
|
||||
SpacesBeforeTrailingComments: 1
|
||||
SpacesInAngles: false
|
||||
SpacesInConditionalStatement: false
|
||||
SpacesInContainerLiterals: true
|
||||
SpacesInCStyleCastParentheses: false
|
||||
SpacesInParentheses: false
|
||||
SpacesInSquareBrackets: false
|
||||
SpaceBeforeSquareBrackets: false
|
||||
Standard: Latest
|
||||
StatementMacros:
|
||||
- Q_UNUSED
|
||||
- QT_REQUIRE_VERSION
|
||||
TabWidth: 8
|
||||
UseCRLF: false
|
||||
UseTab: Never
|
||||
WhitespaceSensitiveMacros:
|
||||
- STRINGIZE
|
||||
- PP_STRINGIZE
|
||||
- BOOST_PP_STRINGIZE
|
||||
...
|
||||
|
14
RpiLedBars/backend/.gitignore
vendored
Normal file
14
RpiLedBars/backend/.gitignore
vendored
Normal file
@@ -0,0 +1,14 @@
|
||||
bin
|
||||
obj
|
||||
CMakeLists.txt.user
|
||||
CMakeCache.txt
|
||||
CMakeFiles
|
||||
CMakeScripts
|
||||
Testing
|
||||
Makefile
|
||||
cmake_install.cmake
|
||||
install_manifest.txt
|
||||
compile_commands.json
|
||||
CTestTestfile.cmake
|
||||
_deps
|
||||
build
|
10
RpiLedBars/backend/CMakeLists.txt
Normal file
10
RpiLedBars/backend/CMakeLists.txt
Normal file
@@ -0,0 +1,10 @@
|
||||
cmake_minimum_required(VERSION 3.12)
|
||||
|
||||
# set the project name
|
||||
project(RpiLedBars VERSION 0.5 LANGUAGES C)
|
||||
|
||||
set(CMAKE_C_STANDARD 99)
|
||||
|
||||
add_subdirectory(libs)
|
||||
|
||||
add_subdirectory(src)
|
87
RpiLedBars/backend/README.md
Normal file
87
RpiLedBars/backend/README.md
Normal file
@@ -0,0 +1,87 @@
|
||||
# RpiLedBars
|
||||
|
||||
LedBars version for raspberry pi
|
||||
|
||||
## Contains
|
||||
* a python test program (main.py)
|
||||
* a C program using :
|
||||
* ws2812 with dma/smi from Jeremy P Bentham - https://iosoft.blog/category/neopixel/
|
||||
* artnet from Stephan Ruloff - https://github.com/rstephan/ArtnetnodeWifi
|
||||
|
||||
## Install
|
||||
|
||||
### I2S Microphone
|
||||
|
||||
#### Install dependencies
|
||||
~~~bash
|
||||
sudo apt install python3-pip
|
||||
sudo pip3 install --upgrade adafruit-python-shell
|
||||
~~~
|
||||
|
||||
#### Build and install module
|
||||
~~~bash
|
||||
cd /tmp
|
||||
sudo wget https://raw.githubusercontent.com/adafruit/Raspberry-Pi-Installer-Scripts/master/i2smic.py
|
||||
sudo python3 i2smic.py
|
||||
~~~
|
||||
|
||||
After a kernel update this step as to be executed again (after reboot -> depends on `uname`)
|
||||
|
||||
#### Test microphone
|
||||
|
||||
Plug the microphone
|
||||
~~~bash
|
||||
sudo reboot
|
||||
arecord -l
|
||||
arecord -D plughw:1 -c1 -r 48000 -f S32_LE -t wav -V mono -v file.wav
|
||||
~~~
|
||||
|
||||
#### Control record volume
|
||||
~~~bash
|
||||
cp res/.asoundrc ~/.asoundrc
|
||||
~~~
|
||||
|
||||
* Intall i2s mems microphone - https://learn.adafruit.com/adafruit-i2s-mems-microphone-breakout/raspberry-pi-wiring-test
|
||||
* Configuring alsa - https://makersportal.com/blog/recording-stereo-audio-on-a-raspberry-pi
|
||||
* Alsa API - http://www.equalarea.com/paul/alsa-audio.html
|
||||
|
||||
### Project depenencies
|
||||
|
||||
~~~bash
|
||||
sudo apt install libasound2-dev wiringpi
|
||||
~~~
|
||||
|
||||
#### Cava
|
||||
|
||||
~~~bash
|
||||
sudo apt install libfftw3-dev libasound2-dev libtool automake
|
||||
cd cava_
|
||||
./autogen.sh
|
||||
./configure
|
||||
make
|
||||
sudo make install
|
||||
~~~
|
||||
|
||||
### Access point
|
||||
|
||||
~~~bash
|
||||
grep "Access point conf" $(find /etc -type f)
|
||||
~~~
|
||||
|
||||
* https://learn.sparkfun.com/tutorials/setting-up-a-raspberry-pi-3-as-an-access-point/all
|
||||
|
||||
## Biblio:
|
||||
|
||||
### Art-Net
|
||||
* Protocol Spec :
|
||||
* https://art-net.org.uk/how-it-works/
|
||||
* https://artisticlicence.com/WebSiteMaster/User%20Guides/art-net.pdf
|
||||
* ESP32 library from Stephan Ruloff - https://github.com/rstephan/ArtnetnodeWifi
|
||||
|
||||
### ws28xx rgb leds
|
||||
* ws2812 datasheet - https://cdn-shop.adafruit.com/datasheets/WS2812.pdf
|
||||
* ws2815 datasheet - https://pdf1.alldatasheet.com/datasheet-pdf/view/1134588/WORLDSEMI/WS2815B.html
|
||||
* ws2812 with dma/smi from Jeremy P Bentham :
|
||||
* ws2812 - https://iosoft.blog/2020/09/29/raspberry-pi-multi-channel-ws2812/
|
||||
* dma - https://iosoft.blog/2020/05/25/raspberry-pi-dma-programming/
|
||||
* smi - https://iosoft.blog/2020/07/16/raspberry-pi-smi/
|
24
RpiLedBars/backend/cava_config
Normal file
24
RpiLedBars/backend/cava_config
Normal file
@@ -0,0 +1,24 @@
|
||||
[general]
|
||||
framerate = 60
|
||||
autosens = 0
|
||||
sensitivity = 200
|
||||
bars = 128
|
||||
|
||||
[input]
|
||||
method = alsa
|
||||
source = plughw:1
|
||||
|
||||
[output]
|
||||
method = raw
|
||||
channels = mono
|
||||
mono_option = left
|
||||
data_format = ascii
|
||||
ascii_max_range=65535
|
||||
bit_format = 16bit
|
||||
|
||||
[smoothing]
|
||||
integral = 77
|
||||
monstercat = 0
|
||||
waves = 0
|
||||
gravity = 0
|
||||
|
25
RpiLedBars/backend/cava_config_namedpipe
Normal file
25
RpiLedBars/backend/cava_config_namedpipe
Normal file
@@ -0,0 +1,25 @@
|
||||
[general]
|
||||
framerate = 60
|
||||
autosens = 0
|
||||
sensitivity = 200
|
||||
bars = 128
|
||||
|
||||
[input]
|
||||
method = alsa
|
||||
source = plughw:1
|
||||
|
||||
[output]
|
||||
method = raw
|
||||
channels = mono
|
||||
mono_option = left
|
||||
raw_target = /tmp/cava_output
|
||||
data_format = ascii
|
||||
ascii_max_range=65535
|
||||
bit_format = 16bit
|
||||
|
||||
[smoothing]
|
||||
integral = 0
|
||||
monstercat = 1
|
||||
waves = 0
|
||||
gravity = 0
|
||||
|
25
RpiLedBars/backend/cava_test
Normal file
25
RpiLedBars/backend/cava_test
Normal file
@@ -0,0 +1,25 @@
|
||||
[general]
|
||||
framerate = 60
|
||||
autosens = 0
|
||||
sensitivity = 200
|
||||
bars = 20
|
||||
|
||||
[input]
|
||||
method = alsa
|
||||
source = plughw:1
|
||||
|
||||
[output]
|
||||
method = raw
|
||||
channels = mono
|
||||
mono_option = left
|
||||
;raw_target = /tmp/cava_output
|
||||
data_format = ascii
|
||||
ascii_max_range=65535
|
||||
bit_format = 16bit
|
||||
|
||||
[smoothing]
|
||||
integral = 0
|
||||
monstercat = 0
|
||||
waves = 0
|
||||
gravity = 0
|
||||
|
5
RpiLedBars/backend/install.sh
Executable file
5
RpiLedBars/backend/install.sh
Executable file
@@ -0,0 +1,5 @@
|
||||
# make
|
||||
cp ./bin/pixled bin/service_pixled
|
||||
sudo -s bash -c "cp pixled.service /etc/systemd/system; systemctl daemon-reload"
|
||||
# sudo -s bash -c "systemctl enable pixled"
|
||||
sudo -s bash -c "systemctl restart pixled"
|
14
RpiLedBars/backend/libs/CMakeLists.txt
Normal file
14
RpiLedBars/backend/libs/CMakeLists.txt
Normal file
@@ -0,0 +1,14 @@
|
||||
include(FetchContent)
|
||||
|
||||
FetchContent_Declare(
|
||||
logc
|
||||
GIT_REPOSITORY "https://github.com/Tropicananass/log.c.git"
|
||||
)
|
||||
|
||||
FetchContent_Declare(
|
||||
ws
|
||||
GIT_REPOSITORY "https://github.com/Tropicananass/wsServer.git"
|
||||
)
|
||||
|
||||
# add the log.c and ws libraries
|
||||
FetchContent_MakeAvailable(logc ws)
|
12
RpiLedBars/backend/pixled.service
Normal file
12
RpiLedBars/backend/pixled.service
Normal file
@@ -0,0 +1,12 @@
|
||||
[Unit]
|
||||
Description=pixled
|
||||
After=network.target
|
||||
|
||||
[Service]
|
||||
ExecStart=pixled -n 60 -d 2
|
||||
Restart=always
|
||||
User=root
|
||||
Group=root
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
3
RpiLedBars/backend/sgdb.sh
Executable file
3
RpiLedBars/backend/sgdb.sh
Executable file
@@ -0,0 +1,3 @@
|
||||
#! /bin/bash
|
||||
|
||||
sudo /usr/bin/gdb "$@"
|
14
RpiLedBars/backend/src/CMakeLists.txt
Normal file
14
RpiLedBars/backend/src/CMakeLists.txt
Normal file
@@ -0,0 +1,14 @@
|
||||
|
||||
add_subdirectory(tasks)
|
||||
add_subdirectory(drivers)
|
||||
|
||||
# add the executable
|
||||
add_executable(${PROJECT_NAME}
|
||||
rpi_midi_controller.c
|
||||
rpi_param.c
|
||||
rpi_pattern.c
|
||||
main.c)
|
||||
|
||||
target_link_libraries(${PROJECT_NAME} PRIVATE m asound)
|
||||
target_link_libraries(${PROJECT_NAME} PRIVATE logc)
|
||||
target_link_libraries(${PROJECT_NAME} PRIVATE ${PROJECT_NAME}_tasks ${PROJECT_NAME}_drivers)
|
14
RpiLedBars/backend/src/drivers/CMakeLists.txt
Normal file
14
RpiLedBars/backend/src/drivers/CMakeLists.txt
Normal file
@@ -0,0 +1,14 @@
|
||||
# add the executable
|
||||
add_library(${PROJECT_NAME}_drivers
|
||||
common.c
|
||||
dma/rpi_dma.c
|
||||
dma/rpi_videocore.c
|
||||
gpio/rpi_gpio.c
|
||||
leddriver/rpi_leddriver.c
|
||||
selector/rpi_selector.c
|
||||
smi/rpi_smi.c)
|
||||
|
||||
target_link_libraries(${PROJECT_NAME}_drivers PRIVATE wiringPi)
|
||||
target_link_libraries(${PROJECT_NAME}_drivers PRIVATE logc)
|
||||
|
||||
target_include_directories(${PROJECT_NAME}_drivers PUBLIC dma gpio leddriver selector smi)
|
56
RpiLedBars/backend/src/drivers/common.c
Normal file
56
RpiLedBars/backend/src/drivers/common.c
Normal file
@@ -0,0 +1,56 @@
|
||||
#include "common.h"
|
||||
|
||||
#include <fcntl.h>
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <sys/mman.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include "log.h"
|
||||
|
||||
// Use mmap to obtain virtual address, given physical
|
||||
void *map_periph(MEM_MAP *mp, void *phys, int size) {
|
||||
mp->phys = phys;
|
||||
mp->size = PAGE_ROUNDUP(size);
|
||||
mp->bus = (void *)((uint32_t)phys - PHYS_REG_BASE + BUS_REG_BASE);
|
||||
mp->virt = map_segment(phys, mp->size);
|
||||
return (mp->virt);
|
||||
}
|
||||
|
||||
// Free mapped peripheral or memory
|
||||
void unmap_periph_mem(MEM_MAP *mp) {
|
||||
if (mp) {
|
||||
unmap_segment(mp->virt, mp->size);
|
||||
}
|
||||
}
|
||||
|
||||
// ----- VIRTUAL MEMORY -----
|
||||
|
||||
// Get virtual memory segment for peripheral regs or physical mem
|
||||
void *map_segment(void *addr, int size) {
|
||||
int fd;
|
||||
void *mem;
|
||||
|
||||
size = PAGE_ROUNDUP(size);
|
||||
if ((fd = open("/dev/mem", O_RDWR | O_SYNC | O_CLOEXEC)) < 0) {
|
||||
log_fatal("can't open /dev/mem, run using sudo");
|
||||
exit(-EXIT_FAILURE);
|
||||
}
|
||||
|
||||
mem = mmap(0, size, PROT_WRITE | PROT_READ, MAP_SHARED, fd, (uint32_t)addr);
|
||||
close(fd);
|
||||
log_info("Map %p -> %p", (void *)addr, mem);
|
||||
if (mem == MAP_FAILED) {
|
||||
log_fatal("can't map memory");
|
||||
exit(-EXIT_FAILURE);
|
||||
}
|
||||
return (mem);
|
||||
}
|
||||
|
||||
// Free mapped memory
|
||||
void unmap_segment(void *mem, int size) {
|
||||
if (mem) {
|
||||
munmap(mem, PAGE_ROUNDUP(size));
|
||||
}
|
||||
}
|
50
RpiLedBars/backend/src/drivers/common.h
Normal file
50
RpiLedBars/backend/src/drivers/common.h
Normal file
@@ -0,0 +1,50 @@
|
||||
#if !defined(__COMMON_H__)
|
||||
#define __COMMON_H__
|
||||
|
||||
// Location of peripheral registers in physical memory
|
||||
#define PHYS_REG_BASE PI_23_REG_BASE
|
||||
#define PI_01_REG_BASE 0x20000000 // Pi Zero or 1
|
||||
#define PI_23_REG_BASE 0x3F000000 // Pi 2 or 3
|
||||
#define PI_4_REG_BASE 0xFE000000 // Pi 4
|
||||
|
||||
#define CLOCK_HZ 250000000 // Pi 2 - 4
|
||||
//#define CLOCK_HZ 400000000 // Pi Zero
|
||||
|
||||
// Location of peripheral registers in bus memory
|
||||
#define BUS_REG_BASE 0x7E000000
|
||||
|
||||
// Get virtual 8 and 32-bit pointers to register
|
||||
#define REG8(m, x) ((volatile uint8_t *)((uint32_t)(m.virt) + (uint32_t)(x)))
|
||||
#define REG32(m, x) ((volatile uint32_t *)((uint32_t)(m.virt) + (uint32_t)(x)))
|
||||
// Get bus address of register
|
||||
#define REG_BUS_ADDR(m, x) ((uint32_t)(m.bus) + (uint32_t)(x))
|
||||
// Convert uncached memory virtual address to bus address
|
||||
#define MEM_BUS_ADDR(mp, a) ((uint32_t)a - (uint32_t)mp->virt + (uint32_t)mp->bus)
|
||||
// Convert bus address to physical address (for mmap)
|
||||
#define BUS_PHYS_ADDR(a) ((void *)((uint32_t)(a) & ~0xC0000000))
|
||||
|
||||
// Size of memory page
|
||||
#define PAGE_SIZE 0x1000
|
||||
// Round up to nearest page
|
||||
#define PAGE_ROUNDUP(n) ((n) % PAGE_SIZE == 0 ? (n) : ((n) + PAGE_SIZE) & ~(PAGE_SIZE - 1))
|
||||
|
||||
// Structure for mapped peripheral or memory
|
||||
typedef struct {
|
||||
int fd, // File descriptor
|
||||
h, // Memory handle
|
||||
size; // Memory size
|
||||
void *bus, // Bus address
|
||||
*virt, // Virtual address
|
||||
*phys; // Physical address
|
||||
} MEM_MAP;
|
||||
|
||||
// Use mmap to obtain virtual address, given physical
|
||||
void *map_periph(MEM_MAP *mp, void *phys, int size);
|
||||
|
||||
// Free mapped peripheral or memory
|
||||
void unmap_periph_mem(MEM_MAP *mp);
|
||||
|
||||
void *map_segment(void *addr, int size);
|
||||
void unmap_segment(void *addr, int size);
|
||||
|
||||
#endif // __COMMON_H__
|
90
RpiLedBars/backend/src/drivers/dma/rpi_dma.c
Normal file
90
RpiLedBars/backend/src/drivers/dma/rpi_dma.c
Normal file
@@ -0,0 +1,90 @@
|
||||
#include "rpi_dma.h"
|
||||
|
||||
#include <stdio.h>
|
||||
|
||||
#include "rpi_videocore.h"
|
||||
|
||||
// DMA channels and data requests
|
||||
#define DMA_SMI_DREQ 4
|
||||
#define DMA_PWM_DREQ 5
|
||||
#define DMA_SPI_TX_DREQ 6
|
||||
#define DMA_SPI_RX_DREQ 7
|
||||
#define DMA_BASE (PHYS_REG_BASE + 0x007000)
|
||||
// DMA register addresses offset by 0x100 * chan_num
|
||||
#define DMA_CS 0x00
|
||||
#define DMA_CONBLK_AD 0x04
|
||||
#define DMA_TI 0x08
|
||||
#define DMA_SRCE_AD 0x0c
|
||||
#define DMA_DEST_AD 0x10
|
||||
#define DMA_TXFR_LEN 0x14
|
||||
#define DMA_STRIDE 0x18
|
||||
#define DMA_NEXTCONBK 0x1c
|
||||
#define DMA_DEBUG 0x20
|
||||
#define DMA_REG(ch, r) ((r) == DMA_ENABLE ? DMA_ENABLE : (ch)*0x100 + (r))
|
||||
#define DMA_ENABLE 0xff0
|
||||
// DMA register values
|
||||
#define DMA_WAIT_RESP (1 << 3)
|
||||
#define DMA_CB_DEST_INC (1 << 4)
|
||||
#define DMA_DEST_DREQ (1 << 6)
|
||||
#define DMA_CB_SRCE_INC (1 << 8)
|
||||
#define DMA_SRCE_DREQ (1 << 10)
|
||||
#define DMA_PRIORITY(n) ((n) << 16)
|
||||
|
||||
// Virtual memory pointers to acceess GPIO, DMA and PWM from user space
|
||||
MEM_MAP dma_regs;
|
||||
|
||||
char *dma_regstrs[] = {"DMA CS", "CB_AD", "TI", "SRCE_AD", "DEST_AD",
|
||||
"TFR_LEN", "STRIDE", "NEXT_CB", "DEBUG", ""};
|
||||
|
||||
void dma_setup(MEM_MAP *mp, int chan, int nsamp, uint8_t **txdata, int offset, uint32_t dest_ad) {
|
||||
map_periph(&dma_regs, (void *)DMA_BASE, PAGE_SIZE);
|
||||
|
||||
DMA_CB *cbs = mp->virt;
|
||||
|
||||
*txdata = (uint8_t *)(cbs + offset);
|
||||
enable_dma(chan);
|
||||
cbs[0].ti = DMA_DEST_DREQ | (DMA_SMI_DREQ << 16) | DMA_CB_SRCE_INC | DMA_WAIT_RESP;
|
||||
cbs[0].tfr_len = nsamp;
|
||||
cbs[0].srce_ad = MEM_BUS_ADDR(mp, *txdata);
|
||||
cbs[0].dest_ad = dest_ad;
|
||||
}
|
||||
|
||||
void dma_close() { unmap_periph_mem(&dma_regs); }
|
||||
|
||||
// Enable and reset DMA
|
||||
void enable_dma(int chan) {
|
||||
*REG32(dma_regs, DMA_ENABLE) |= (1 << chan);
|
||||
*REG32(dma_regs, DMA_REG(chan, DMA_CS)) = 1 << 31;
|
||||
}
|
||||
|
||||
// Start DMA, given first control block
|
||||
void start_dma(MEM_MAP *mp, int chan, DMA_CB *cbp, uint32_t csval) {
|
||||
*REG32(dma_regs, DMA_REG(chan, DMA_CONBLK_AD)) = MEM_BUS_ADDR(mp, cbp);
|
||||
*REG32(dma_regs, DMA_REG(chan, DMA_CS)) = 2; // Clear 'end' flag
|
||||
*REG32(dma_regs, DMA_REG(chan, DMA_DEBUG)) = 7; // Clear error bits
|
||||
*REG32(dma_regs, DMA_REG(chan, DMA_CS)) = 1 | csval; // Start DMA
|
||||
}
|
||||
|
||||
// Return remaining transfer length
|
||||
uint32_t dma_transfer_len(int chan) { return (*REG32(dma_regs, DMA_REG(chan, DMA_TXFR_LEN))); }
|
||||
|
||||
// Check if DMA is active
|
||||
uint32_t dma_active(int chan) { return ((*REG32(dma_regs, DMA_REG(chan, DMA_CS))) & 1); }
|
||||
|
||||
// Halt current DMA operation by resetting controller
|
||||
void stop_dma(int chan) {
|
||||
if (dma_regs.virt)
|
||||
*REG32(dma_regs, DMA_REG(chan, DMA_CS)) = 1 << 31;
|
||||
}
|
||||
|
||||
// Display DMA registers
|
||||
void disp_dma(int chan) {
|
||||
volatile uint32_t *p = REG32(dma_regs, DMA_REG(chan, DMA_CS));
|
||||
int i = 0;
|
||||
|
||||
while (dma_regstrs[i][0]) {
|
||||
printf("%-7s %08X ", dma_regstrs[i++], *p++);
|
||||
if (i % 5 == 0 || dma_regstrs[i][0] == 0)
|
||||
printf("\n");
|
||||
}
|
||||
}
|
33
RpiLedBars/backend/src/drivers/dma/rpi_dma.h
Normal file
33
RpiLedBars/backend/src/drivers/dma/rpi_dma.h
Normal file
@@ -0,0 +1,33 @@
|
||||
#if !defined(__RPI_DMA_H__)
|
||||
#define __RPI_DMA_H__
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#include "../common.h"
|
||||
|
||||
#define DMA_CHAN_A 10
|
||||
#define DMA_CHAN_B 11
|
||||
|
||||
// DMA control block (must be 32-byte aligned)
|
||||
typedef struct {
|
||||
uint32_t ti, // Transfer info
|
||||
srce_ad, // Source address
|
||||
dest_ad, // Destination address
|
||||
tfr_len, // Transfer length
|
||||
stride, // Transfer stride
|
||||
next_cb, // Next control block
|
||||
debug, // Debug register, zero in control block
|
||||
unused;
|
||||
} DMA_CB __attribute__((aligned(32)));
|
||||
|
||||
void dma_setup(MEM_MAP *mp, int chan, int nsamp, uint8_t **txdata, int offset, uint32_t dest_ad);
|
||||
void dma_close();
|
||||
|
||||
void enable_dma(int chan);
|
||||
void start_dma(MEM_MAP *mp, int chan, DMA_CB *cbp, uint32_t csval);
|
||||
uint32_t dma_transfer_len(int chan);
|
||||
uint32_t dma_active(int chan);
|
||||
void stop_dma(int chan);
|
||||
void disp_dma(int chan);
|
||||
|
||||
#endif // __RPI_DMA_H__
|
128
RpiLedBars/backend/src/drivers/dma/rpi_videocore.c
Normal file
128
RpiLedBars/backend/src/drivers/dma/rpi_videocore.c
Normal file
@@ -0,0 +1,128 @@
|
||||
#include "rpi_videocore.h"
|
||||
|
||||
#include <fcntl.h>
|
||||
#include <stdio.h>
|
||||
#include <sys/ioctl.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include "log.h"
|
||||
|
||||
// Mailbox command/response structure
|
||||
typedef struct {
|
||||
uint32_t len, // Overall length (bytes)
|
||||
req, // Zero for request, 1<<31 for response
|
||||
tag, // Command number
|
||||
blen, // Buffer length (bytes)
|
||||
dlen; // Data length (bytes)
|
||||
uint32_t uints[32 - 5]; // Data (108 bytes maximum)
|
||||
} VC_MSG __attribute__((aligned(16)));
|
||||
|
||||
void disp_vc_msg(VC_MSG *msgp);
|
||||
|
||||
int open_mbox(void);
|
||||
void close_mbox(int fd);
|
||||
uint32_t msg_mbox(int fd, VC_MSG *msgp);
|
||||
void *map_uncached_mem(MEM_MAP *mp, int size);
|
||||
|
||||
void videocore_setup(MEM_MAP *mp, int size) { map_uncached_mem(mp, size); }
|
||||
void videocore_close(MEM_MAP *mp) {
|
||||
unmap_periph_mem(mp);
|
||||
if (mp->fd) {
|
||||
unlock_vc_mem(mp->fd, mp->h);
|
||||
free_vc_mem(mp->fd, mp->h);
|
||||
close_mbox(mp->fd);
|
||||
}
|
||||
}
|
||||
|
||||
// Allocate uncached memory, get bus & phys addresses
|
||||
void *map_uncached_mem(MEM_MAP *mp, int size) {
|
||||
void *ret;
|
||||
mp->size = PAGE_ROUNDUP(size);
|
||||
mp->fd = open_mbox();
|
||||
ret = (mp->h = alloc_vc_mem(mp->fd, mp->size, DMA_MEM_FLAGS)) > 0 &&
|
||||
(mp->bus = lock_vc_mem(mp->fd, mp->h)) != 0 &&
|
||||
(mp->virt = map_segment(BUS_PHYS_ADDR(mp->bus), mp->size)) != 0
|
||||
? mp->virt
|
||||
: 0;
|
||||
log_info("VC mem handle %u, phys %p, virt %p", mp->h, mp->bus, mp->virt);
|
||||
return (ret);
|
||||
}
|
||||
|
||||
// Allocate memory on PAGE_SIZE boundary, return handle
|
||||
uint32_t alloc_vc_mem(int fd, uint32_t size, VC_ALLOC_FLAGS flags) {
|
||||
VC_MSG msg = {
|
||||
.tag = 0x3000c, .blen = 12, .dlen = 12, .uints = {PAGE_ROUNDUP(size), PAGE_SIZE, flags}};
|
||||
return (msg_mbox(fd, &msg));
|
||||
}
|
||||
// Lock allocated memory, return bus address
|
||||
void *lock_vc_mem(int fd, int h) {
|
||||
VC_MSG msg = {.tag = 0x3000d, .blen = 4, .dlen = 4, .uints = {h}};
|
||||
return (h ? (void *)msg_mbox(fd, &msg) : 0);
|
||||
}
|
||||
// Unlock allocated memory
|
||||
uint32_t unlock_vc_mem(int fd, int h) {
|
||||
VC_MSG msg = {.tag = 0x3000e, .blen = 4, .dlen = 4, .uints = {h}};
|
||||
return (h ? msg_mbox(fd, &msg) : 0);
|
||||
}
|
||||
// Free memory
|
||||
uint32_t free_vc_mem(int fd, int h) {
|
||||
VC_MSG msg = {.tag = 0x3000f, .blen = 4, .dlen = 4, .uints = {h}};
|
||||
return (h ? msg_mbox(fd, &msg) : 0);
|
||||
}
|
||||
uint32_t set_vc_clock(int fd, int id, uint32_t freq) {
|
||||
VC_MSG msg1 = {.tag = 0x38001, .blen = 8, .dlen = 8, .uints = {id, 1}};
|
||||
VC_MSG msg2 = {.tag = 0x38002, .blen = 12, .dlen = 12, .uints = {id, freq, 0}};
|
||||
msg_mbox(fd, &msg1);
|
||||
disp_vc_msg(&msg1);
|
||||
msg_mbox(fd, &msg2);
|
||||
disp_vc_msg(&msg2);
|
||||
return (0);
|
||||
}
|
||||
|
||||
// Display mailbox message
|
||||
void disp_vc_msg(VC_MSG *msgp) {
|
||||
int i;
|
||||
|
||||
printf("VC msg len=%X, req=%X, tag=%X, blen=%x, dlen=%x, data ", msgp->len, msgp->req, msgp->tag,
|
||||
msgp->blen, msgp->dlen);
|
||||
for (i = 0; i < msgp->blen / 4; i++)
|
||||
printf("%08X ", msgp->uints[i]);
|
||||
printf("\n");
|
||||
}
|
||||
|
||||
// Open mailbox interface, return file descriptor
|
||||
int open_mbox(void) {
|
||||
int fd;
|
||||
|
||||
if ((fd = open("/dev/vcio", 0)) < 0)
|
||||
log_error("can't open VC mailbox");
|
||||
return (fd);
|
||||
}
|
||||
// Close mailbox interface
|
||||
void close_mbox(int fd) {
|
||||
if (fd >= 0)
|
||||
close(fd);
|
||||
}
|
||||
|
||||
// Send message to mailbox, return first response int, 0 if error
|
||||
uint32_t msg_mbox(int fd, VC_MSG *msgp) {
|
||||
uint32_t ret = 0, i;
|
||||
|
||||
for (i = msgp->dlen / 4; i <= msgp->blen / 4; i += 4)
|
||||
msgp->uints[i++] = 0;
|
||||
msgp->len = (msgp->blen + 6) * 4;
|
||||
msgp->req = 0;
|
||||
if (ioctl(fd, _IOWR(100, 0, void *), msgp) < 0) {
|
||||
log_error("VC IOCTL failed");
|
||||
} else if ((msgp->req & 0x80000000) == 0) {
|
||||
log_error("VC IOCTL error");
|
||||
} else if (msgp->req == 0x80000001) {
|
||||
log_error("VC IOCTL partial error");
|
||||
} else {
|
||||
ret = msgp->uints[0];
|
||||
}
|
||||
#if DEBUG
|
||||
disp_vc_msg(msgp);
|
||||
#endif
|
||||
return (ret);
|
||||
}
|
33
RpiLedBars/backend/src/drivers/dma/rpi_videocore.h
Normal file
33
RpiLedBars/backend/src/drivers/dma/rpi_videocore.h
Normal file
@@ -0,0 +1,33 @@
|
||||
#if !defined(__RPI_VIDEOCORE_H__)
|
||||
#define __RPI_VIDEOCORE_H__
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#include "../common.h"
|
||||
|
||||
// Videocore mailbox memory allocation flags, see:
|
||||
// https://github.com/raspberrypi/firmware/wiki/Mailbox-property-interface
|
||||
typedef enum {
|
||||
MEM_FLAG_DISCARDABLE = 1 << 0, // can be resized to 0 at any time. Use for cached data
|
||||
MEM_FLAG_NORMAL = 0 << 2, // normal allocating alias. Don't use from ARM
|
||||
MEM_FLAG_DIRECT = 1 << 2, // 0xC alias uncached
|
||||
MEM_FLAG_COHERENT = 2 << 2, // 0x8 alias. Non-allocating in L2 but coherent
|
||||
MEM_FLAG_ZERO = 1 << 4, // initialise buffer to all zeros
|
||||
MEM_FLAG_NO_INIT = 1 << 5, // don't initialise (default is initialise to all ones)
|
||||
MEM_FLAG_HINT_PERMALOCK = 1 << 6, // Likely to be locked for long periods of time
|
||||
MEM_FLAG_L1_NONALLOCATING = (MEM_FLAG_DIRECT | MEM_FLAG_COHERENT) // Allocating in L2
|
||||
} VC_ALLOC_FLAGS;
|
||||
|
||||
// VC flags for unchached DMA memory
|
||||
#define DMA_MEM_FLAGS (MEM_FLAG_DIRECT | MEM_FLAG_ZERO)
|
||||
|
||||
void videocore_setup(MEM_MAP *mp, int size);
|
||||
void videocore_close(MEM_MAP *mp);
|
||||
|
||||
uint32_t alloc_vc_mem(int fd, uint32_t size, VC_ALLOC_FLAGS flags);
|
||||
void *lock_vc_mem(int fd, int h);
|
||||
uint32_t unlock_vc_mem(int fd, int h);
|
||||
uint32_t free_vc_mem(int fd, int h);
|
||||
uint32_t set_vc_clock(int fd, int id, uint32_t freq);
|
||||
|
||||
#endif // __RPI_VIDEOCORE_H__
|
88
RpiLedBars/backend/src/drivers/gpio/rpi_gpio.c
Normal file
88
RpiLedBars/backend/src/drivers/gpio/rpi_gpio.c
Normal file
@@ -0,0 +1,88 @@
|
||||
#include "rpi_gpio.h"
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include "../common.h"
|
||||
|
||||
// GPIO register definitions
|
||||
#define GPIO_BASE (PHYS_REG_BASE + 0x200000)
|
||||
#define GPIO_MODE0 0x00
|
||||
#define GPIO_SET0 0x1c
|
||||
#define GPIO_CLR0 0x28
|
||||
#define GPIO_LEV0 0x34
|
||||
#define GPIO_GPPUD 0x94
|
||||
#define GPIO_GPPUDCLK0 0x98
|
||||
|
||||
#define GPIO_MODE_STRS "IN", "OUT", "ALT5", "ALT4", "ALT0", "ALT1", "ALT2", "ALT3"
|
||||
|
||||
bool isInitialized = false;
|
||||
|
||||
// Virtual memory pointers to acceess GPIO, DMA and PWM from user space
|
||||
MEM_MAP gpio_regs;
|
||||
|
||||
char *gpio_mode_strs[] = {GPIO_MODE_STRS};
|
||||
|
||||
// definitions
|
||||
|
||||
void gpio_setup() {
|
||||
if (!isInitialized) {
|
||||
map_periph(&gpio_regs, (void *)GPIO_BASE, PAGE_SIZE);
|
||||
isInitialized = true;
|
||||
}
|
||||
}
|
||||
|
||||
void gpio_close() {
|
||||
if (isInitialized) {
|
||||
unmap_periph_mem(&gpio_regs);
|
||||
isInitialized = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Set input or output with pullups
|
||||
void gpio_set(int pin, int mode, int pull) {
|
||||
gpio_mode(pin, mode);
|
||||
gpio_pull(pin, pull);
|
||||
}
|
||||
// Set I/P pullup or pulldown
|
||||
void gpio_pull(int pin, int pull) {
|
||||
volatile uint32_t *reg = REG32(gpio_regs, GPIO_GPPUDCLK0) + pin / 32;
|
||||
|
||||
*REG32(gpio_regs, GPIO_GPPUD) = pull;
|
||||
usleep(2);
|
||||
*reg = pin << (pin % 32);
|
||||
usleep(2);
|
||||
*REG32(gpio_regs, GPIO_GPPUD) = 0;
|
||||
*reg = 0;
|
||||
}
|
||||
|
||||
// Set input or output
|
||||
void gpio_mode(int pin, int mode) {
|
||||
if (gpio_regs.virt) {
|
||||
volatile uint32_t *reg = REG32(gpio_regs, GPIO_MODE0) + pin / 10, shift = (pin % 10) * 3;
|
||||
*reg = (*reg & ~(7 << shift)) | (mode << shift);
|
||||
}
|
||||
}
|
||||
|
||||
// Set an O/P pin
|
||||
void gpio_out(int pin, int val) {
|
||||
volatile uint32_t *reg = REG32(gpio_regs, val ? GPIO_SET0 : GPIO_CLR0) + pin / 32;
|
||||
*reg = 1 << (pin % 32);
|
||||
}
|
||||
|
||||
// Get an I/P pin value
|
||||
uint8_t gpio_in(int pin) {
|
||||
volatile uint32_t *reg = REG32(gpio_regs, GPIO_LEV0) + pin / 32;
|
||||
return (((*reg) >> (pin % 32)) & 1);
|
||||
}
|
||||
|
||||
// Display the values in a GPIO mode register
|
||||
void disp_mode_vals(uint32_t mode) {
|
||||
int i;
|
||||
|
||||
for (i = 0; i < 10; i++)
|
||||
printf("%u:%-4s ", i, gpio_mode_strs[(mode >> (i * 3)) & 7]);
|
||||
printf("\n");
|
||||
}
|
26
RpiLedBars/backend/src/drivers/gpio/rpi_gpio.h
Normal file
26
RpiLedBars/backend/src/drivers/gpio/rpi_gpio.h
Normal file
@@ -0,0 +1,26 @@
|
||||
#if !defined(__RPI_GPIO_H__)
|
||||
#define __RPI_GPIO_H__
|
||||
|
||||
// GPIO I/O definitions
|
||||
#define GPIO_IN 0
|
||||
#define GPIO_OUT 1
|
||||
#define GPIO_ALT0 4
|
||||
#define GPIO_ALT1 5
|
||||
#define GPIO_ALT2 6
|
||||
#define GPIO_ALT3 7
|
||||
#define GPIO_ALT4 3
|
||||
#define GPIO_ALT5 2
|
||||
|
||||
#define GPIO_NOPULL 0
|
||||
#define GPIO_PULLDN 1
|
||||
#define GPIO_PULLUP 2
|
||||
|
||||
void gpio_setup();
|
||||
|
||||
void gpio_close();
|
||||
|
||||
void gpio_pull(int pin, int pull);
|
||||
|
||||
void gpio_mode(int pin, int mode);
|
||||
|
||||
#endif // __RPI_GPIO_H__
|
210
RpiLedBars/backend/src/drivers/leddriver/rpi_leddriver.c
Normal file
210
RpiLedBars/backend/src/drivers/leddriver/rpi_leddriver.c
Normal file
@@ -0,0 +1,210 @@
|
||||
#include "rpi_leddriver.h"
|
||||
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include "../../rpi_param.h"
|
||||
#include "../common.h"
|
||||
|
||||
#include "../dma/rpi_dma.h"
|
||||
#include "../dma/rpi_videocore.h"
|
||||
#include "../gpio/rpi_gpio.h"
|
||||
#include "../smi/rpi_smi.h"
|
||||
|
||||
#if PHYS_REG_BASE == PI_4_REG_BASE // Timings for RPi v4 (1.5 GHz)
|
||||
#define SMI_TIMING 10, 15, 30, 15 // 400 ns cycle time
|
||||
#else // Timings for RPi v0-3 (1 GHz)
|
||||
#define SMI_TIMING 10, 10, 20, 10 // 400 ns cycle time
|
||||
#endif
|
||||
|
||||
#define TX_TEST 0 // If non-zero, use dummy Tx data
|
||||
#define LED_NBITS 24 // Number of data bits per LED
|
||||
#define LED_PREBITS 4 // Number of zero bits before LED data
|
||||
#define LED_POSTBITS 4 // Number of zero bits after LED data
|
||||
#define BIT_NPULSES 3 // Number of O/P pulses per LED bit
|
||||
|
||||
// Length of data for 1 row (1 LED on each channel)
|
||||
#define LED_DLEN (LED_NBITS * BIT_NPULSES)
|
||||
|
||||
// Transmit data type, 8 or 16 bits
|
||||
#if LED_NCHANS > 8
|
||||
#define TXDATA_T uint16_t
|
||||
#else
|
||||
#define TXDATA_T uint8_t
|
||||
#endif
|
||||
|
||||
// Ofset into Tx data buffer, given LED number in chan
|
||||
#define LED_TX_OSET(n) (LED_PREBITS + (LED_DLEN * (n)))
|
||||
|
||||
// Size of data buffers & NV memory, given number of LEDs per chan
|
||||
#define TX_BUFF_LEN(n) (LED_TX_OSET(n) + LED_POSTBITS)
|
||||
#define TX_BUFF_SIZE(n) (TX_BUFF_LEN(n) * sizeof(TXDATA_T))
|
||||
#define VC_MEM_SIZE (PAGE_SIZE + TX_BUFF_SIZE(CHAN_MAXLEDS))
|
||||
|
||||
/* Global */
|
||||
MEM_MAP vc_mem;
|
||||
TXDATA_T *txdata;
|
||||
TXDATA_T tx_buffer[TX_BUFF_LEN(CHAN_MAXLEDS)];
|
||||
|
||||
void swap_bytes();
|
||||
|
||||
void leddriver_setup() {
|
||||
videocore_setup(&vc_mem, VC_MEM_SIZE);
|
||||
gpio_setup();
|
||||
smi_setup(LED_NCHANS, SMI_TIMING, &vc_mem, TX_BUFF_LEN(CHAN_MAXLEDS), &txdata);
|
||||
}
|
||||
|
||||
void leddriver_close() {
|
||||
videocore_close(&vc_mem);
|
||||
smi_close(LED_NCHANS);
|
||||
gpio_close();
|
||||
}
|
||||
|
||||
void set_color(uint32_t rgb, int index) {
|
||||
int msk;
|
||||
TXDATA_T *txd = &(tx_buffer[LED_TX_OSET(index)]);
|
||||
|
||||
// For each bit of the 24-bit RGB values..
|
||||
for (size_t n = 0; n < LED_NBITS; n++) {
|
||||
// Mask to convert RGB to GRB, M.S bit first
|
||||
msk = n == 0 ? 0x800000 : n == 8 ? 0x8000 : n == 16 ? 0x80 : msk >> 1;
|
||||
// 1st byte or word is a high pulse on all lines
|
||||
txd[0] = (TXDATA_T)0xffff;
|
||||
// 2nd has high or low bits from data
|
||||
// 3rd is a low pulse
|
||||
txd[1] = txd[2] = 0;
|
||||
if (rgb & msk) {
|
||||
txd[1] = (TXDATA_T)0xffff;
|
||||
}
|
||||
txd += BIT_NPULSES;
|
||||
}
|
||||
}
|
||||
|
||||
// Set Tx data for 8 or 16 chans, 1 LED per chan, given 1 RGB val per chan
|
||||
// Logic 1 is 0.8us high, 0.4 us low, logic 0 is 0.4us high, 0.8us low
|
||||
void rgb_txdata(int *rgbs, int index) {
|
||||
int i, n, msk;
|
||||
TXDATA_T *txd = &(tx_buffer[LED_TX_OSET(index)]);
|
||||
|
||||
// For each bit of the 24-bit RGB values..
|
||||
for (n = 0; n < LED_NBITS; n++) {
|
||||
// Mask to convert RGB to GRB, M.S bit first
|
||||
msk = n == 0 ? 0x800000 : n == 8 ? 0x8000 : n == 16 ? 0x80 : msk >> 1;
|
||||
// 1st byte or word is a high pulse on all lines
|
||||
txd[0] = (TXDATA_T)0xffff;
|
||||
// 2nd has high or low bits from data
|
||||
// 3rd is a low pulse
|
||||
txd[1] = txd[2] = 0;
|
||||
for (i = 0; i < LED_NCHANS; i++) {
|
||||
if (rgbs[i] & msk)
|
||||
txd[1] |= (1 << i);
|
||||
}
|
||||
txd += BIT_NPULSES;
|
||||
}
|
||||
}
|
||||
|
||||
void leddriver_refresh() {
|
||||
#if LED_NCHANS <= 8
|
||||
swap_bytes();
|
||||
#endif
|
||||
|
||||
while (dma_active(DMA_CHAN_A)) {
|
||||
usleep(10);
|
||||
}
|
||||
|
||||
memcpy(txdata, tx_buffer, TX_BUFF_SIZE(CHAN_MAXLEDS));
|
||||
enable_dma(DMA_CHAN_A);
|
||||
start_smi(&vc_mem);
|
||||
usleep(10);
|
||||
}
|
||||
|
||||
// Swap adjacent bytes in transmit data
|
||||
void swap_bytes() {
|
||||
uint16_t *wp = (uint16_t *)tx_buffer;
|
||||
int len = TX_BUFF_SIZE(CHAN_MAXLEDS);
|
||||
|
||||
len = (len + 1) / 2;
|
||||
while (len-- > 0) {
|
||||
*wp = __builtin_bswap16(*wp);
|
||||
wp++;
|
||||
}
|
||||
}
|
||||
|
||||
/* source :
|
||||
* https://github.com/adafruit/Adafruit_NeoPixel/blob/216ccdbff399750f5b02d4cc804c598399e39713/Adafruit_NeoPixel.cpp#L2414
|
||||
*/
|
||||
uint32_t ColorHSV(uint16_t hue, uint8_t sat, uint8_t val) {
|
||||
|
||||
uint8_t r, g, b;
|
||||
|
||||
// Remap 0-65535 to 0-1529. Pure red is CENTERED on the 64K rollover;
|
||||
// 0 is not the start of pure red, but the midpoint...a few values above
|
||||
// zero and a few below 65536 all yield pure red (similarly, 32768 is the
|
||||
// midpoint, not start, of pure cyan). The 8-bit RGB hexcone (256 values
|
||||
// each for red, green, blue) really only allows for 1530 distinct hues
|
||||
// (not 1536, more on that below), but the full unsigned 16-bit type was
|
||||
// chosen for hue so that one's code can easily handle a contiguous color
|
||||
// wheel by allowing hue to roll over in either direction.
|
||||
hue = (hue * 1530L + 32768) / 65536;
|
||||
// Because red is centered on the rollover point (the +32768 above,
|
||||
// essentially a fixed-point +0.5), the above actually yields 0 to 1530,
|
||||
// where 0 and 1530 would yield the same thing. Rather than apply a
|
||||
// costly modulo operator, 1530 is handled as a special case below.
|
||||
|
||||
// So you'd think that the color "hexcone" (the thing that ramps from
|
||||
// pure red, to pure yellow, to pure green and so forth back to red,
|
||||
// yielding six slices), and with each color component having 256
|
||||
// possible values (0-255), might have 1536 possible items (6*256),
|
||||
// but in reality there's 1530. This is because the last element in
|
||||
// each 256-element slice is equal to the first element of the next
|
||||
// slice, and keeping those in there this would create small
|
||||
// discontinuities in the color wheel. So the last element of each
|
||||
// slice is dropped...we regard only elements 0-254, with item 255
|
||||
// being picked up as element 0 of the next slice. Like this:
|
||||
// Red to not-quite-pure-yellow is: 255, 0, 0 to 255, 254, 0
|
||||
// Pure yellow to not-quite-pure-green is: 255, 255, 0 to 1, 255, 0
|
||||
// Pure green to not-quite-pure-cyan is: 0, 255, 0 to 0, 255, 254
|
||||
// and so forth. Hence, 1530 distinct hues (0 to 1529), and hence why
|
||||
// the constants below are not the multiples of 256 you might expect.
|
||||
|
||||
// Convert hue to R,G,B (nested ifs faster than divide+mod+switch):
|
||||
if (hue < 510) { // Red to Green-1
|
||||
b = 0;
|
||||
if (hue < 255) { // Red to Yellow-1
|
||||
r = 255;
|
||||
g = hue; // g = 0 to 254
|
||||
} else { // Yellow to Green-1
|
||||
r = 510 - hue; // r = 255 to 1
|
||||
g = 255;
|
||||
}
|
||||
} else if (hue < 1020) { // Green to Blue-1
|
||||
r = 0;
|
||||
if (hue < 765) { // Green to Cyan-1
|
||||
g = 255;
|
||||
b = hue - 510; // b = 0 to 254
|
||||
} else { // Cyan to Blue-1
|
||||
g = 1020 - hue; // g = 255 to 1
|
||||
b = 255;
|
||||
}
|
||||
} else if (hue < 1530) { // Blue to Red-1
|
||||
g = 0;
|
||||
if (hue < 1275) { // Blue to Magenta-1
|
||||
r = hue - 1020; // r = 0 to 254
|
||||
b = 255;
|
||||
} else { // Magenta to Red-1
|
||||
r = 255;
|
||||
b = 1530 - hue; // b = 255 to 1
|
||||
}
|
||||
} else { // Last 0.5 Red (quicker than % operator)
|
||||
r = 255;
|
||||
g = b = 0;
|
||||
}
|
||||
|
||||
// Apply saturation and value to R,G,B, pack into 32-bit result:
|
||||
uint32_t v1 = 1 + val; // 1 to 256; allows >>8 instead of /255
|
||||
uint16_t s1 = 1 + sat; // 1 to 256; same reason
|
||||
uint8_t s2 = 255 - sat; // 255 to 0
|
||||
return ((((((r * s1) >> 8) + s2) * v1) & 0xff00) << 8) |
|
||||
(((((g * s1) >> 8) + s2) * v1) & 0xff00) | (((((b * s1) >> 8) + s2) * v1) >> 8);
|
||||
}
|
18
RpiLedBars/backend/src/drivers/leddriver/rpi_leddriver.h
Normal file
18
RpiLedBars/backend/src/drivers/leddriver/rpi_leddriver.h
Normal file
@@ -0,0 +1,18 @@
|
||||
#if !defined(__RPI_LEDDRIVER_H__)
|
||||
#define __RPI_LEDDRIVER_H__
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
void leddriver_setup();
|
||||
|
||||
void leddriver_close();
|
||||
|
||||
void set_color(uint32_t rgb, int index);
|
||||
|
||||
void rgb_txdata(int *rgbs, int index);
|
||||
|
||||
void leddriver_refresh();
|
||||
|
||||
uint32_t ColorHSV(uint16_t hue, uint8_t sat, uint8_t val);
|
||||
|
||||
#endif // __RPI_LEDDRIVER_H__
|
65
RpiLedBars/backend/src/drivers/selector/rpi_selector.c
Normal file
65
RpiLedBars/backend/src/drivers/selector/rpi_selector.c
Normal file
@@ -0,0 +1,65 @@
|
||||
/** @file rpi_selector.c
|
||||
* @brief This module is used to manage selector on gpio
|
||||
*/
|
||||
|
||||
/***************************************************************************************************
|
||||
* Includes
|
||||
**************************************************************************************************/
|
||||
#include "rpi_selector.h"
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <wiringPi.h>
|
||||
|
||||
/***************************************************************************************************
|
||||
* Preprocessor Constants and Macros
|
||||
**************************************************************************************************/
|
||||
|
||||
/***************************************************************************************************
|
||||
* Type and Contant Definitions
|
||||
**************************************************************************************************/
|
||||
unsigned const selectorPinNumber = 4;
|
||||
/* TODO use GPIO function from ../gpio/gipo.h (same pin number) */
|
||||
int selectorPins[4] = {26, 27, 6, 5};
|
||||
char modeStr[] = "0 | 0 | 0 | 0 \n";
|
||||
|
||||
/***************************************************************************************************
|
||||
* Persistent Variables
|
||||
**************************************************************************************************/
|
||||
|
||||
/***************************************************************************************************
|
||||
* Internal Function Prototypes
|
||||
**************************************************************************************************/
|
||||
|
||||
/***************************************************************************************************
|
||||
* External Function Definitions
|
||||
**************************************************************************************************/
|
||||
void selector_setup() {
|
||||
wiringPiSetupGpio();
|
||||
|
||||
for (size_t i = 0; i < selectorPinNumber; ++i) {
|
||||
pinMode(selectorPins[i], INPUT);
|
||||
pullUpDnControl(selectorPins[i], PUD_DOWN);
|
||||
}
|
||||
}
|
||||
|
||||
int selector_get_position() {
|
||||
int newPosition = -1;
|
||||
for (size_t i = 0; i < selectorPinNumber; ++i) {
|
||||
if (digitalRead(selectorPins[i])) {
|
||||
newPosition = i;
|
||||
}
|
||||
}
|
||||
return newPosition;
|
||||
}
|
||||
|
||||
char *selector_tostr() {
|
||||
for (size_t i = 0; i < selectorPinNumber; ++i) {
|
||||
modeStr[i * 4] = digitalRead(selectorPins[i]) ? '1' : '0';
|
||||
}
|
||||
return modeStr;
|
||||
}
|
||||
|
||||
/***************************************************************************************************
|
||||
* Internal Function Definitions
|
||||
**************************************************************************************************/
|
42
RpiLedBars/backend/src/drivers/selector/rpi_selector.h
Normal file
42
RpiLedBars/backend/src/drivers/selector/rpi_selector.h
Normal file
@@ -0,0 +1,42 @@
|
||||
/** @file rpi_selector.h
|
||||
* @brief This module is used to manage selector on gpio
|
||||
*/
|
||||
#if !defined(__RPI_SELECTOR_H__)
|
||||
#define __RPI_SELECTOR_H__
|
||||
|
||||
/***************************************************************************************************
|
||||
* Includes
|
||||
**************************************************************************************************/
|
||||
#include <stdbool.h>
|
||||
|
||||
/***************************************************************************************************
|
||||
* Preprocessor Constants and Macros
|
||||
**************************************************************************************************/
|
||||
|
||||
/***************************************************************************************************
|
||||
* Type and Contant Definitions
|
||||
**************************************************************************************************/
|
||||
|
||||
/***************************************************************************************************
|
||||
* Global Variables
|
||||
**************************************************************************************************/
|
||||
|
||||
/***************************************************************************************************
|
||||
* External Function Prototypes
|
||||
**************************************************************************************************/
|
||||
/**
|
||||
* @brief setup the selector module
|
||||
**/
|
||||
void selector_setup();
|
||||
|
||||
/**
|
||||
* @brief get the selector position
|
||||
**/
|
||||
int selector_get_position();
|
||||
|
||||
/**
|
||||
* @brief Get string with selector debug info
|
||||
**/
|
||||
char *selector_tostr();
|
||||
|
||||
#endif /* __RPI_SELECTOR_H__ */
|
223
RpiLedBars/backend/src/drivers/smi/rpi_smi.c
Normal file
223
RpiLedBars/backend/src/drivers/smi/rpi_smi.c
Normal file
@@ -0,0 +1,223 @@
|
||||
#include "rpi_smi.h"
|
||||
|
||||
#include <unistd.h>
|
||||
|
||||
#include "../dma/rpi_dma.h"
|
||||
#include "../gpio/rpi_gpio.h"
|
||||
|
||||
// GPIO first pin
|
||||
#define SMI_SD0_PIN 8
|
||||
|
||||
// Data widths
|
||||
#define SMI_8_BITS 0
|
||||
#define SMI_16_BITS 1
|
||||
#define SMI_18_BITS 2
|
||||
#define SMI_9_BITS 3
|
||||
|
||||
// Clock registers and values
|
||||
#define CLK_BASE (PHYS_REG_BASE + 0x101000)
|
||||
// #define CLK_PWM_CTL 0xa0
|
||||
// #define CLK_PWM_DIV 0xa4
|
||||
#define CLK_SMI_CTL 0xb0
|
||||
#define CLK_SMI_DIV 0xb4
|
||||
#define CLK_PASSWD 0x5a000000
|
||||
#define PWM_CLOCK_ID 0xa
|
||||
|
||||
// DMA request threshold
|
||||
#define REQUEST_THRESH 2
|
||||
|
||||
// Register definitions
|
||||
#define SMI_BASE (PHYS_REG_BASE + 0x600000)
|
||||
#define SMI_CS 0x00 // Control & status
|
||||
#define SMI_L 0x04 // Transfer length
|
||||
#define SMI_A 0x08 // Address
|
||||
#define SMI_D 0x0c // Data
|
||||
#define SMI_DSR0 0x10 // Read settings device 0
|
||||
#define SMI_DSW0 0x14 // Write settings device 0
|
||||
#define SMI_DSR1 0x18 // Read settings device 1
|
||||
#define SMI_DSW1 0x1c // Write settings device 1
|
||||
#define SMI_DSR2 0x20 // Read settings device 2
|
||||
#define SMI_DSW2 0x24 // Write settings device 2
|
||||
#define SMI_DSR3 0x28 // Read settings device 3
|
||||
#define SMI_DSW3 0x2c // Write settings device 3
|
||||
#define SMI_DMC 0x30 // DMA control
|
||||
#define SMI_DCS 0x34 // Direct control/status
|
||||
#define SMI_DCA 0x38 // Direct address
|
||||
#define SMI_DCD 0x3c // Direct data
|
||||
#define SMI_FD 0x40 // FIFO debug
|
||||
#define SMI_REGLEN (SMI_FD * 4)
|
||||
|
||||
// Union of 32-bit value with register bitfields
|
||||
#define REG_DEF(name, fields) \
|
||||
typedef union { \
|
||||
struct { \
|
||||
volatile uint32_t fields; \
|
||||
}; \
|
||||
volatile uint32_t value; \
|
||||
} name
|
||||
|
||||
// Control and status register
|
||||
#define SMI_CS_FIELDS \
|
||||
enable: \
|
||||
1, done : 1, active : 1, start : 1, clear : 1, write : 1, _x1 : 2, teen : 1, intd : 1, intt : 1, \
|
||||
intr : 1, pvmode : 1, seterr : 1, pxldat : 1, edreq : 1, _x2 : 8, _x3 : 1, aferr : 1, \
|
||||
txw : 1, rxr : 1, txd : 1, rxd : 1, txe : 1, rxf : 1
|
||||
REG_DEF(SMI_CS_REG, SMI_CS_FIELDS);
|
||||
|
||||
// Data length register
|
||||
#define SMI_L_FIELDS \
|
||||
len: \
|
||||
32
|
||||
REG_DEF(SMI_L_REG, SMI_L_FIELDS);
|
||||
|
||||
// Address & device number
|
||||
#define SMI_A_FIELDS \
|
||||
addr: \
|
||||
6, _x1 : 2, dev : 2
|
||||
REG_DEF(SMI_A_REG, SMI_A_FIELDS);
|
||||
|
||||
// Data FIFO
|
||||
#define SMI_D_FIELDS \
|
||||
data: \
|
||||
32
|
||||
REG_DEF(SMI_D_REG, SMI_D_FIELDS);
|
||||
|
||||
// DMA control register
|
||||
#define SMI_DMC_FIELDS \
|
||||
reqw: \
|
||||
6, reqr : 6, panicw : 6, panicr : 6, dmap : 1, _x1 : 3, dmaen : 1
|
||||
REG_DEF(SMI_DMC_REG, SMI_DMC_FIELDS);
|
||||
|
||||
// Device settings: read (1 of 4)
|
||||
#define SMI_DSR_FIELDS \
|
||||
rstrobe: \
|
||||
7, rdreq : 1, rpace : 7, rpaceall : 1, rhold : 6, fsetup : 1, mode68 : 1, rsetup : 6, rwidth : 2
|
||||
REG_DEF(SMI_DSR_REG, SMI_DSR_FIELDS);
|
||||
|
||||
// Device settings: write (1 of 4)
|
||||
#define SMI_DSW_FIELDS \
|
||||
wstrobe: \
|
||||
7, wdreq : 1, wpace : 7, wpaceall : 1, whold : 6, wswap : 1, wformat : 1, wsetup : 6, wwidth : 2
|
||||
REG_DEF(SMI_DSW_REG, SMI_DSW_FIELDS);
|
||||
|
||||
// Direct control register
|
||||
#define SMI_DCS_FIELDS \
|
||||
enable: \
|
||||
1, start : 1, done : 1, write : 1
|
||||
REG_DEF(SMI_DCS_REG, SMI_DCS_FIELDS);
|
||||
|
||||
// Direct control address & device number
|
||||
#define SMI_DCA_FIELDS \
|
||||
addr: \
|
||||
6, _x1 : 2, dev : 2
|
||||
REG_DEF(SMI_DCA_REG, SMI_DCA_FIELDS);
|
||||
|
||||
// Direct control data
|
||||
#define SMI_DCD_FIELDS \
|
||||
data: \
|
||||
32
|
||||
REG_DEF(SMI_DCD_REG, SMI_DCD_FIELDS);
|
||||
|
||||
// Debug register
|
||||
#define SMI_FLVL_FIELDS \
|
||||
fcnt: \
|
||||
6, _x1 : 2, flvl : 6
|
||||
REG_DEF(SMI_FLVL_REG, SMI_FLVL_FIELDS);
|
||||
|
||||
// Pointers to SMI registers
|
||||
volatile SMI_CS_REG *smi_cs;
|
||||
volatile SMI_L_REG *smi_l;
|
||||
volatile SMI_A_REG *smi_a;
|
||||
volatile SMI_D_REG *smi_d;
|
||||
volatile SMI_DMC_REG *smi_dmc;
|
||||
volatile SMI_DSR_REG *smi_dsr;
|
||||
volatile SMI_DSW_REG *smi_dsw;
|
||||
volatile SMI_DCS_REG *smi_dcs;
|
||||
volatile SMI_DCA_REG *smi_dca;
|
||||
volatile SMI_DCD_REG *smi_dcd;
|
||||
|
||||
MEM_MAP smi_regs, clk_regs;
|
||||
|
||||
void setup_smi_dma(MEM_MAP *mp, int nsamp, uint8_t **txdata, int len);
|
||||
|
||||
void smi_setup(int channels, int ns, int setup, int strobe, int hold, MEM_MAP *mp, int nsamp,
|
||||
uint8_t **txdata) {
|
||||
map_periph(&smi_regs, (void *)SMI_BASE, PAGE_SIZE);
|
||||
map_periph(&clk_regs, (void *)CLK_BASE, PAGE_SIZE);
|
||||
|
||||
int width = channels > 8 ? SMI_16_BITS : SMI_8_BITS;
|
||||
int i, divi = ns / 2;
|
||||
|
||||
smi_cs = (SMI_CS_REG *)REG32(smi_regs, SMI_CS);
|
||||
smi_l = (SMI_L_REG *)REG32(smi_regs, SMI_L);
|
||||
smi_a = (SMI_A_REG *)REG32(smi_regs, SMI_A);
|
||||
smi_d = (SMI_D_REG *)REG32(smi_regs, SMI_D);
|
||||
smi_dmc = (SMI_DMC_REG *)REG32(smi_regs, SMI_DMC);
|
||||
smi_dsr = (SMI_DSR_REG *)REG32(smi_regs, SMI_DSR0);
|
||||
smi_dsw = (SMI_DSW_REG *)REG32(smi_regs, SMI_DSW0);
|
||||
smi_dcs = (SMI_DCS_REG *)REG32(smi_regs, SMI_DCS);
|
||||
smi_dca = (SMI_DCA_REG *)REG32(smi_regs, SMI_DCA);
|
||||
smi_dcd = (SMI_DCD_REG *)REG32(smi_regs, SMI_DCD);
|
||||
smi_cs->value = smi_l->value = smi_a->value = 0;
|
||||
smi_dsr->value = smi_dsw->value = smi_dcs->value = smi_dca->value = 0;
|
||||
if (*REG32(clk_regs, CLK_SMI_DIV) != divi << 12) {
|
||||
*REG32(clk_regs, CLK_SMI_CTL) = CLK_PASSWD | (1 << 5);
|
||||
usleep(10);
|
||||
while (*REG32(clk_regs, CLK_SMI_CTL) & (1 << 7))
|
||||
;
|
||||
usleep(10);
|
||||
*REG32(clk_regs, CLK_SMI_DIV) = CLK_PASSWD | (divi << 12);
|
||||
usleep(10);
|
||||
*REG32(clk_regs, CLK_SMI_CTL) = CLK_PASSWD | 6 | (1 << 4);
|
||||
usleep(10);
|
||||
while ((*REG32(clk_regs, CLK_SMI_CTL) & (1 << 7)) == 0)
|
||||
;
|
||||
usleep(100);
|
||||
}
|
||||
if (smi_cs->seterr)
|
||||
smi_cs->seterr = 1;
|
||||
smi_dsr->rsetup = smi_dsw->wsetup = setup;
|
||||
smi_dsr->rstrobe = smi_dsw->wstrobe = strobe;
|
||||
smi_dsr->rhold = smi_dsw->whold = hold;
|
||||
smi_dmc->panicr = smi_dmc->panicw = 8;
|
||||
smi_dmc->reqr = smi_dmc->reqw = REQUEST_THRESH;
|
||||
smi_dsr->rwidth = smi_dsw->wwidth = width;
|
||||
for (i = 0; i < channels; i++)
|
||||
gpio_mode(SMI_SD0_PIN + i, GPIO_ALT1);
|
||||
|
||||
setup_smi_dma(mp, nsamp, txdata, width + 1);
|
||||
}
|
||||
|
||||
void smi_close(int channels) {
|
||||
for (size_t i = 0; i < channels; ++i)
|
||||
gpio_mode(SMI_SD0_PIN + i, GPIO_IN);
|
||||
if (smi_regs.virt) {
|
||||
*REG32(smi_regs, SMI_CS) = 0;
|
||||
}
|
||||
stop_dma(DMA_CHAN_A);
|
||||
unmap_periph_mem(&clk_regs);
|
||||
unmap_periph_mem(&smi_regs);
|
||||
dma_close();
|
||||
}
|
||||
|
||||
// Start SMI DMA transfers
|
||||
void start_smi(MEM_MAP *mp) {
|
||||
DMA_CB *cbs = mp->virt;
|
||||
|
||||
start_dma(mp, DMA_CHAN_A, &cbs[0], 0);
|
||||
smi_cs->start = 1;
|
||||
}
|
||||
|
||||
// private
|
||||
|
||||
// Set up SMI transfers using DMA
|
||||
void setup_smi_dma(MEM_MAP *mp, int nsamp, uint8_t **txdata, int len) {
|
||||
smi_dmc->dmaen = 1;
|
||||
smi_cs->enable = 1;
|
||||
smi_cs->clear = 1;
|
||||
smi_cs->pxldat = 1;
|
||||
smi_l->len = nsamp * len;
|
||||
smi_cs->write = 1;
|
||||
|
||||
dma_setup(mp, DMA_CHAN_A, nsamp, txdata, len, REG_BUS_ADDR(smi_regs, SMI_D));
|
||||
}
|
14
RpiLedBars/backend/src/drivers/smi/rpi_smi.h
Normal file
14
RpiLedBars/backend/src/drivers/smi/rpi_smi.h
Normal file
@@ -0,0 +1,14 @@
|
||||
#if !defined(__RPI_SMI_H__)
|
||||
#define __RPI_SMI_H__
|
||||
|
||||
#include "../common.h"
|
||||
#include <stdint.h>
|
||||
|
||||
void smi_setup(int channels, int ns, int setup, int strobe, int hold, MEM_MAP *mp, int nsamp,
|
||||
uint8_t **txdata);
|
||||
|
||||
void smi_close(int channels);
|
||||
|
||||
void start_smi(MEM_MAP *mp);
|
||||
|
||||
#endif // __RPI_SMI_H__
|
371
RpiLedBars/backend/src/main.c
Normal file
371
RpiLedBars/backend/src/main.c
Normal file
@@ -0,0 +1,371 @@
|
||||
/** @file main.c
|
||||
* @brief This is the main exectution program
|
||||
*/
|
||||
|
||||
/***************************************************************************************************
|
||||
* Includes
|
||||
**************************************************************************************************/
|
||||
#include <ctype.h>
|
||||
#include <errno.h>
|
||||
#include <pthread.h>
|
||||
#include <signal.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <time.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include "log.h"
|
||||
|
||||
#include "drivers/leddriver/rpi_leddriver.h"
|
||||
|
||||
#include "artnet.h"
|
||||
#include "cava.h"
|
||||
#include "selector.h"
|
||||
#include "websocket.h"
|
||||
|
||||
#include "rpi_midi_controller.h"
|
||||
#include "rpi_param.h"
|
||||
#include "rpi_pattern.h"
|
||||
|
||||
/***************************************************************************************************
|
||||
* Preprocessor Constants and Macros
|
||||
**************************************************************************************************/
|
||||
|
||||
/***************************************************************************************************
|
||||
* Type and Contant Definitions
|
||||
**************************************************************************************************/
|
||||
|
||||
/***************************************************************************************************
|
||||
* Persistent Variables
|
||||
**************************************************************************************************/
|
||||
/* Command-line parameters */
|
||||
bool IsTestMode = false;
|
||||
int logLevel = 2;
|
||||
|
||||
int previousMode = -1;
|
||||
|
||||
unsigned long mainLoopCycle = 0;
|
||||
|
||||
pthread_mutex_t logLockMutex = PTHREAD_MUTEX_INITIALIZER;
|
||||
|
||||
/***************************************************************************************************
|
||||
* Internal Function Prototypes
|
||||
**************************************************************************************************/
|
||||
|
||||
void parseCommandLineArgs(int argc, char const *argv[]);
|
||||
|
||||
void terminate(int sig);
|
||||
|
||||
void manage_tasks(int previousMode, int currentMode);
|
||||
|
||||
void execute_task(int mode);
|
||||
|
||||
void execute_test_mode();
|
||||
|
||||
void execute_artnet_mode();
|
||||
|
||||
void execute_autonomous_mode();
|
||||
|
||||
void execute_manual_mode();
|
||||
|
||||
void adjust_loop(struct timespec const *loopStart);
|
||||
|
||||
void log_lock_helper(bool lock, void *udata);
|
||||
|
||||
/***************************************************************************************************
|
||||
* External Function Definitions
|
||||
**************************************************************************************************/
|
||||
|
||||
int main(int argc, char const *argv[]) {
|
||||
// setup
|
||||
signal(SIGINT, terminate);
|
||||
|
||||
struct sched_param sp;
|
||||
sp.sched_priority = 32;
|
||||
if (pthread_setschedparam(pthread_self(), SCHED_FIFO, &sp)) {
|
||||
fprintf(stderr, "WARNING: Failed to set stepper thread to real-time priority: %s\n",
|
||||
strerror(errno));
|
||||
}
|
||||
|
||||
log_set_lock(log_lock_helper, &logLockMutex);
|
||||
|
||||
param_setup();
|
||||
midi_controller_setup();
|
||||
leddriver_setup();
|
||||
websocket_start();
|
||||
selector_start();
|
||||
|
||||
parseCommandLineArgs(argc, argv);
|
||||
|
||||
// loop
|
||||
while (1) {
|
||||
struct timespec loopStart;
|
||||
int mode;
|
||||
|
||||
clock_gettime(CLOCK_MONOTONIC, &loopStart);
|
||||
if (IsTestMode) {
|
||||
if (mainLoopCycle % (180 * 3) < 180) {
|
||||
mode = 0;
|
||||
} else if (mainLoopCycle % (180 * 3) < 180 * 2) {
|
||||
mode = 1;
|
||||
} else {
|
||||
mode = 2;
|
||||
}
|
||||
} else {
|
||||
mode = param_access->pixled.mode;
|
||||
}
|
||||
/* todo thread ? */
|
||||
midi_controller_execute();
|
||||
|
||||
if (mode != -1) {
|
||||
if (mode != previousMode) {
|
||||
log_info("swtching to mode : %d", mode);
|
||||
manage_tasks(previousMode, mode);
|
||||
} else {
|
||||
execute_task(mode);
|
||||
}
|
||||
previousMode = mode;
|
||||
}
|
||||
adjust_loop(&loopStart);
|
||||
}
|
||||
}
|
||||
|
||||
/***************************************************************************************************
|
||||
* Internal Function Definitions
|
||||
**************************************************************************************************/
|
||||
|
||||
void parseCommandLineArgs(int argc, char const *argv[]) {
|
||||
int args = 0;
|
||||
|
||||
while (argc > ++args) // Process command-line args
|
||||
{
|
||||
if (argv[args][0] == '-') {
|
||||
switch (toupper(argv[args][1])) {
|
||||
case 'N': // -N: number of LEDs per channel
|
||||
if (args >= argc - 1) {
|
||||
log_error("no numeric value");
|
||||
exit(-EXIT_FAILURE);
|
||||
} else {
|
||||
param_access->pixled.chanLedCount = atoi(argv[++args]);
|
||||
}
|
||||
break;
|
||||
case 'D': // -D: debug level
|
||||
if (args >= argc - 1) {
|
||||
log_error("no debug level");
|
||||
exit(-EXIT_FAILURE);
|
||||
} else {
|
||||
logLevel = atoi(argv[++args]);
|
||||
log_set_level(logLevel);
|
||||
}
|
||||
break;
|
||||
case 'T': // -T: test mode
|
||||
IsTestMode = true;
|
||||
break;
|
||||
default: // Otherwise error
|
||||
log_error("Unrecognised option '%c'\n", argv[args][1]);
|
||||
fprintf(stderr, "Options:\n"
|
||||
" -t Test mode (flash LEDs)\n"
|
||||
" -n num number of LEDs per channel\n"
|
||||
" -d lvl debug level\n");
|
||||
exit(-EXIT_FAILURE);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void terminate(int sig) {
|
||||
manage_tasks(previousMode, -1);
|
||||
leddriver_close();
|
||||
log_info("Goodbye !");
|
||||
exit(EXIT_SUCCESS);
|
||||
}
|
||||
|
||||
void manage_tasks(int previousMode, int currentMode) {
|
||||
/* stop previous bg task */
|
||||
switch (previousMode) {
|
||||
case 1:
|
||||
artnet_stop();
|
||||
break;
|
||||
case 2:
|
||||
case 3:
|
||||
cava_stop();
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
/* start new bg task */
|
||||
switch (currentMode) {
|
||||
case 1:
|
||||
artnet_start();
|
||||
break;
|
||||
case 2:
|
||||
case 3:
|
||||
cava_start();
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void execute_task(int mode) {
|
||||
switch (mode) {
|
||||
case 0:
|
||||
// mode test
|
||||
execute_test_mode();
|
||||
break;
|
||||
case 1:
|
||||
// artnet mode
|
||||
execute_artnet_mode();
|
||||
break;
|
||||
case 2:
|
||||
execute_autonomous_mode();
|
||||
break;
|
||||
case 3:
|
||||
// manual mode
|
||||
execute_manual_mode();
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Pointer to uncached Tx data buffer
|
||||
// TXDATA_T tx_buffer[TX_BUFF_LEN(CHAN_MAXLEDS)]; // Tx buffer for assembling data
|
||||
|
||||
void execute_test_mode() {
|
||||
// RGB values for test mode (1 value for each of 16 channels)
|
||||
uint32_t on_rgbs[] = {0x5f0000, 0x005f00, 0x00005f, 0x5f5f00, 0x5f005f, 0x005f5f, 0x5f5f5f};
|
||||
uint32_t off_rgbs = 0x000000;
|
||||
|
||||
static int i = 0, offset = 0;
|
||||
|
||||
for (size_t ledIndex = 0; ledIndex < param_access->pixled.chanLedCount; ++ledIndex) {
|
||||
set_color(ledIndex <= offset % param_access->pixled.chanLedCount ? on_rgbs[i] * .5 : off_rgbs,
|
||||
ledIndex);
|
||||
}
|
||||
|
||||
leddriver_refresh();
|
||||
|
||||
if (offset < param_access->pixled.chanLedCount) {
|
||||
++offset;
|
||||
} else {
|
||||
offset = 0;
|
||||
if (i < 7) {
|
||||
++i;
|
||||
} else {
|
||||
i = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// RGB data
|
||||
int rgb_data[CHAN_MAXLEDS][LED_NCHANS];
|
||||
|
||||
void execute_artnet_mode() {
|
||||
uint8_t *dmxData;
|
||||
|
||||
for (size_t ledBar = 0; ledBar < LED_NCHANS; ledBar++) {
|
||||
if (artnet_get_dmx_data(ledBar, &dmxData) == 0) {
|
||||
for (size_t i = 0; i < param_access->pixled.chanLedCount; ++i) {
|
||||
uint8_t *rgb = dmxData + (i * 3);
|
||||
rgb_data[i][ledBar] = (rgb[0] << 16) | (rgb[1] << 8) | rgb[2];
|
||||
rgb_txdata(rgb_data[i], i);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
leddriver_refresh();
|
||||
}
|
||||
|
||||
void execute_autonomous_mode() {
|
||||
int ret;
|
||||
uint16_t *buffer;
|
||||
|
||||
if ((ret = cava_get_buffer(&buffer)) == 0) {
|
||||
switch (param_access->auton.pattern) {
|
||||
case 0:
|
||||
bounce_led_and_color(buffer, CAVA_BAR_NUMBER);
|
||||
break;
|
||||
case 1:
|
||||
bounce_led(buffer, CAVA_BAR_NUMBER);
|
||||
break;
|
||||
case 2:
|
||||
bounce_led_and_travel(buffer, CAVA_BAR_NUMBER);
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
unsigned baseLed[LED_NCHANS] = {0};
|
||||
|
||||
void execute_manual_mode() {
|
||||
int ret;
|
||||
uint16_t *buffer;
|
||||
|
||||
if ((ret = cava_get_buffer(&buffer)) == 0) {
|
||||
for (size_t ledBarIndex = 0; ledBarIndex < LED_NCHANS; ++ledBarIndex) {
|
||||
uint16_t barMax = 0;
|
||||
// uint16_t hueInterval = UINT16_MAX - (param_access->ledbar[ledBarIndex].hueInterval -
|
||||
// param_access->ledbar[ledBarIndex].hueBase);
|
||||
for (size_t cavaBar = 0; cavaBar < CAVA_BAR_NUMBER / LED_NCHANS; ++cavaBar) {
|
||||
unsigned barIndex = ledBarIndex * CAVA_BAR_NUMBER / LED_NCHANS + cavaBar;
|
||||
if (barMax < buffer[barIndex]) {
|
||||
barMax = buffer[barIndex];
|
||||
}
|
||||
}
|
||||
barMax *= param_access->auton.sensitivity * param_access->ledbar[ledBarIndex].sensitivity;
|
||||
unsigned ledToLight = barMax * param_access->pixled.chanLedCount / 2 / UINT16_MAX;
|
||||
unsigned long hueShift =
|
||||
(long)barMax * (long)param_access->ledbar[ledBarIndex].hueInterval / UINT16_MAX;
|
||||
uint16_t hue = param_access->ledbar[ledBarIndex].hueBase - hueShift;
|
||||
uint32_t color = ColorHSV(hue, 255, param_access->ledbar[ledBarIndex].luminosity);
|
||||
|
||||
for (size_t i = 0; i < ledToLight; ++i) {
|
||||
rgb_data[i][ledBarIndex] = color;
|
||||
rgb_txdata(rgb_data[i], i);
|
||||
}
|
||||
for (size_t i = ledToLight; i < param_access->pixled.chanLedCount; ++i) {
|
||||
rgb_data[i][ledBarIndex] = 0x000000;
|
||||
rgb_txdata(rgb_data[i], i);
|
||||
}
|
||||
}
|
||||
|
||||
leddriver_refresh();
|
||||
}
|
||||
}
|
||||
|
||||
void adjust_loop(struct timespec const *loopStart) {
|
||||
struct timespec loopEnd;
|
||||
long elapsedTimeUs, remainingTimeUs;
|
||||
|
||||
clock_gettime(CLOCK_MONOTONIC, &loopEnd);
|
||||
elapsedTimeUs = (loopEnd.tv_nsec - loopStart->tv_nsec) / 1E3 +
|
||||
((unsigned)(loopEnd.tv_sec - loopStart->tv_sec)) * 1E6;
|
||||
remainingTimeUs = 16000 - elapsedTimeUs;
|
||||
|
||||
if (remainingTimeUs >= 0) {
|
||||
log_trace("cycle %lu, loop remaining time %ld", mainLoopCycle, remainingTimeUs);
|
||||
usleep(remainingTimeUs);
|
||||
} else {
|
||||
log_warn("loop overlap by %06ldus", -remainingTimeUs);
|
||||
log_info("loop start %ld.%09lds - loop end %ld.%09lds", loopStart->tv_sec, loopStart->tv_nsec,
|
||||
loopEnd.tv_sec, loopEnd.tv_nsec);
|
||||
}
|
||||
++mainLoopCycle;
|
||||
}
|
||||
|
||||
void log_lock_helper(bool lock, void *udata) {
|
||||
pthread_mutex_t *pLogLockMutex = (pthread_mutex_t *)(udata);
|
||||
if (lock) {
|
||||
pthread_mutex_lock(pLogLockMutex);
|
||||
} else {
|
||||
pthread_mutex_unlock(pLogLockMutex);
|
||||
}
|
||||
}
|
240
RpiLedBars/backend/src/rpi_midi_controller.c
Normal file
240
RpiLedBars/backend/src/rpi_midi_controller.c
Normal file
@@ -0,0 +1,240 @@
|
||||
/** @file .c
|
||||
* @brief This module
|
||||
*/
|
||||
|
||||
/***************************************************************************************************
|
||||
* Includes
|
||||
**************************************************************************************************/
|
||||
|
||||
#include "rpi_midi_controller.h"
|
||||
|
||||
#include <alsa/asoundlib.h>
|
||||
|
||||
#include "log.h"
|
||||
|
||||
#include "rpi_param.h"
|
||||
|
||||
/***************************************************************************************************
|
||||
* Preprocessor Constants and Macros
|
||||
**************************************************************************************************/
|
||||
|
||||
#define C3 48
|
||||
#define Gd3 56
|
||||
|
||||
/***************************************************************************************************
|
||||
* Type and Contant Definitions
|
||||
**************************************************************************************************/
|
||||
|
||||
/***************************************************************************************************
|
||||
* Persistent Variables
|
||||
**************************************************************************************************/
|
||||
|
||||
/**
|
||||
* Boolean for interrupting the main thread loop
|
||||
*/
|
||||
static snd_seq_t *seq_handle;
|
||||
|
||||
/**
|
||||
* Boolean for interrupting the main thread loop
|
||||
*/
|
||||
static int sys_port;
|
||||
|
||||
/**
|
||||
* Boolean for interrupting the main thread loop
|
||||
*/
|
||||
static int in_port;
|
||||
|
||||
/* state machine */
|
||||
int destChannel = 0xf;
|
||||
|
||||
/***************************************************************************************************
|
||||
* Internal Function Prototypes
|
||||
**************************************************************************************************/
|
||||
|
||||
/**
|
||||
* @brief
|
||||
*/
|
||||
void subscribe_system_port();
|
||||
|
||||
/**
|
||||
* @brief
|
||||
*
|
||||
* @param controller
|
||||
*/
|
||||
void subscribe_midi_controller(snd_seq_addr_t controller);
|
||||
|
||||
/**
|
||||
* @brief
|
||||
*
|
||||
* @param ev
|
||||
*/
|
||||
void handle_system_port_events(snd_seq_event_t *ev);
|
||||
|
||||
/**
|
||||
* @brief
|
||||
*
|
||||
* @param ev
|
||||
*/
|
||||
void handle_in_port_events(snd_seq_event_t *ev);
|
||||
|
||||
/***************************************************************************************************
|
||||
* External Function Definitions
|
||||
**************************************************************************************************/
|
||||
|
||||
void midi_controller_setup() {
|
||||
if (snd_seq_open(&seq_handle, "default", SND_SEQ_OPEN_INPUT, SND_SEQ_NONBLOCK) != 0) {
|
||||
SNDERR("snd_seq_open");
|
||||
}
|
||||
|
||||
if (snd_seq_set_client_name(seq_handle, "Midi Listener") != 0) {
|
||||
SNDERR("snd_seq_set_client_name");
|
||||
}
|
||||
|
||||
sys_port = snd_seq_create_simple_port(seq_handle, "sys:in",
|
||||
SND_SEQ_PORT_CAP_WRITE | SND_SEQ_PORT_CAP_SUBS_WRITE,
|
||||
SND_SEQ_PORT_TYPE_APPLICATION);
|
||||
|
||||
if (sys_port < 0) {
|
||||
SNDERR("snd_seq_create_simple_port");
|
||||
}
|
||||
|
||||
subscribe_system_port();
|
||||
|
||||
in_port = snd_seq_create_simple_port(seq_handle, "listen:in",
|
||||
SND_SEQ_PORT_CAP_WRITE | SND_SEQ_PORT_CAP_SUBS_WRITE,
|
||||
SND_SEQ_PORT_TYPE_APPLICATION);
|
||||
|
||||
if (in_port < 0) {
|
||||
SNDERR("snd_seq_create_simple_port");
|
||||
}
|
||||
|
||||
snd_seq_addr_t controller = {.client = 24, .port = 0};
|
||||
subscribe_midi_controller(controller);
|
||||
}
|
||||
|
||||
void midi_controller_execute() {
|
||||
snd_seq_event_t *ev = NULL;
|
||||
int ret;
|
||||
while ((ret = snd_seq_event_input(seq_handle, &ev)) > 0) {
|
||||
if (ev->dest.port == sys_port) {
|
||||
handle_system_port_events(ev);
|
||||
} else if (ev->dest.port == in_port) {
|
||||
handle_in_port_events(ev);
|
||||
} else {
|
||||
log_warn("Unkonwn midi dest port %d", ev->dest.port);
|
||||
}
|
||||
}
|
||||
if (ret < 0 && ret != -EAGAIN) {
|
||||
SNDERR("snd_seq_event_input");
|
||||
}
|
||||
}
|
||||
|
||||
void midi_controller_close() {}
|
||||
|
||||
/***************************************************************************************************
|
||||
* Internal Function Definitions
|
||||
**************************************************************************************************/
|
||||
|
||||
void subscribe_system_port() {
|
||||
snd_seq_addr_t sender = {.client = 0, .port = SND_SEQ_PORT_SYSTEM_ANNOUNCE};
|
||||
snd_seq_connect_from(seq_handle, sys_port, sender.client, sender.port);
|
||||
}
|
||||
|
||||
void subscribe_midi_controller(snd_seq_addr_t controller) {
|
||||
snd_seq_connect_from(seq_handle, in_port, controller.client, controller.port);
|
||||
}
|
||||
|
||||
void handle_system_port_events(snd_seq_event_t *ev) {
|
||||
if (ev->type == SND_SEQ_EVENT_PORT_START) {
|
||||
snd_seq_addr_t *newport = (snd_seq_addr_t *)&ev->data;
|
||||
if (newport->client != snd_seq_client_id(seq_handle)) {
|
||||
log_info("New port %d:%d", newport->client, newport->port);
|
||||
subscribe_midi_controller(*newport);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void handle_in_port_events(snd_seq_event_t *ev) {
|
||||
switch (ev->type) {
|
||||
case SND_SEQ_EVENT_PGMCHANGE:
|
||||
if (ev->data.control.value < LED_NCHANS) {
|
||||
if (ev->data.control.value != destChannel) {
|
||||
destChannel = ev->data.control.value;
|
||||
log_info("Control param channel %d", ev->data.control.value);
|
||||
}
|
||||
} else if (destChannel != GLOBAL_CHANNEL) {
|
||||
log_info("Control global param");
|
||||
destChannel = GLOBAL_CHANNEL;
|
||||
}
|
||||
break;
|
||||
|
||||
case SND_SEQ_EVENT_NOTEON:
|
||||
if (ev->data.note.note == C3) {
|
||||
if (destChannel != GLOBAL_CHANNEL) {
|
||||
destChannel = GLOBAL_CHANNEL;
|
||||
log_info("Control global param");
|
||||
}
|
||||
} else if (ev->data.note.note >= Gd3) {
|
||||
set_pattern(ev->data.note.note - Gd3);
|
||||
} else {
|
||||
log_debug("NOTEON : ch %#04x - note %d - vel %d", ev->data.note.channel, ev->data.note.note,
|
||||
ev->data.note.velocity);
|
||||
}
|
||||
break;
|
||||
|
||||
case SND_SEQ_EVENT_NOTEOFF:
|
||||
break;
|
||||
|
||||
case SND_SEQ_EVENT_CONTROLLER:
|
||||
switch (ev->data.control.param) {
|
||||
/* Pots */
|
||||
case 1:
|
||||
set_sensitivity(destChannel, ev->data.control.value);
|
||||
break;
|
||||
case 2:
|
||||
set_luminosity(destChannel, ev->data.control.value);
|
||||
break;
|
||||
case 3:
|
||||
set_hue_base(destChannel, ev->data.control.value);
|
||||
break;
|
||||
case 4:
|
||||
set_hue_interval(destChannel, ev->data.control.value);
|
||||
break;
|
||||
case 5:
|
||||
set_gravity(ev->data.control.value);
|
||||
break;
|
||||
case 6:
|
||||
set_saturation(destChannel, ev->data.control.value);
|
||||
break;
|
||||
case 7:
|
||||
set_hue_auto_shift_interval(destChannel, ev->data.control.value);
|
||||
break;
|
||||
case 8:
|
||||
set_hue_auto_shift_speed(destChannel, ev->data.control.value);
|
||||
break;
|
||||
|
||||
/* Pads in CC */
|
||||
case 26:
|
||||
set_hue_auto_shift(ev->data.control.value != 0);
|
||||
break;
|
||||
|
||||
default:
|
||||
log_debug("CONTROLLER : ch %#04x - param %u - value %d", ev->data.control.channel,
|
||||
ev->data.control.param, ev->data.control.value);
|
||||
break;
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
log_debug("ev %02d : %#010x - %#010x - %#010x", ev->type, ev->data.raw32.d[0],
|
||||
ev->data.raw32.d[1], ev->data.raw32.d[2]);
|
||||
break;
|
||||
}
|
||||
if (ev->type == SND_SEQ_EVENT_PORT_START) {
|
||||
snd_seq_addr_t *newport = (snd_seq_addr_t *)&ev->data;
|
||||
if (newport->client != snd_seq_client_id(seq_handle)) {
|
||||
log_debug("New port %d:%d", newport->client, newport->port);
|
||||
subscribe_midi_controller(*newport);
|
||||
}
|
||||
}
|
||||
}
|
42
RpiLedBars/backend/src/rpi_midi_controller.h
Normal file
42
RpiLedBars/backend/src/rpi_midi_controller.h
Normal file
@@ -0,0 +1,42 @@
|
||||
/** @file .h
|
||||
* @brief This module
|
||||
*/
|
||||
#if !defined(__RPI_MIDI_CONTROLLER_H__)
|
||||
#define __RPI_MIDI_CONTROLLER_H__
|
||||
|
||||
/***************************************************************************************************
|
||||
* Includes
|
||||
**************************************************************************************************/
|
||||
|
||||
/***************************************************************************************************
|
||||
* Preprocessor Constants and Macros
|
||||
**************************************************************************************************/
|
||||
|
||||
/***************************************************************************************************
|
||||
* Type and Contant Definitions
|
||||
**************************************************************************************************/
|
||||
|
||||
/***************************************************************************************************
|
||||
* Global Variables
|
||||
**************************************************************************************************/
|
||||
|
||||
/***************************************************************************************************
|
||||
* External Function Prototypes
|
||||
**************************************************************************************************/
|
||||
|
||||
/**
|
||||
* @brief
|
||||
*/
|
||||
void midi_controller_setup();
|
||||
|
||||
/**
|
||||
* @brief
|
||||
*/
|
||||
void midi_controller_execute();
|
||||
|
||||
/**
|
||||
* @brief
|
||||
*/
|
||||
void midi_controller_close();
|
||||
|
||||
#endif /* __RPI_MIDI_CONTROLLER_H__ */
|
202
RpiLedBars/backend/src/rpi_param.c
Normal file
202
RpiLedBars/backend/src/rpi_param.c
Normal file
@@ -0,0 +1,202 @@
|
||||
/** @file .c
|
||||
* @brief This module
|
||||
*/
|
||||
|
||||
/***************************************************************************************************
|
||||
* Includes
|
||||
**************************************************************************************************/
|
||||
|
||||
#include "rpi_param.h"
|
||||
|
||||
#include <math.h>
|
||||
#include <pthread.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#include "log.h"
|
||||
|
||||
#include "drivers/selector/rpi_selector.h"
|
||||
#include "websocket.h"
|
||||
|
||||
/***************************************************************************************************
|
||||
* Preprocessor Constants and Macros
|
||||
**************************************************************************************************/
|
||||
|
||||
/***************************************************************************************************
|
||||
* Type and Contant Definitions
|
||||
**************************************************************************************************/
|
||||
|
||||
pixled_param_t const dummyPixledParam = {.mode = 0, .chanLedCount = 0};
|
||||
auton_param_t const dummyAutonParam = {
|
||||
.sensitivity = 10, .gravity = 80, .pattern = 0, .isHueAutoShiftEnabled = true};
|
||||
ledbar_param_t const dummyLedbarParam = {.sensitivity = 1.,
|
||||
.hueBase = 121,
|
||||
.hueInterval = -100,
|
||||
.hueAutoShift = 0,
|
||||
.hueAutoShiftInterval = 0,
|
||||
.hueAutoShiftSpeed = 1.,
|
||||
.isIncreasing = false,
|
||||
.luminosity = 20,
|
||||
.saturation = 180};
|
||||
|
||||
/***************************************************************************************************
|
||||
* Persistent Variables
|
||||
**************************************************************************************************/
|
||||
|
||||
param_t param;
|
||||
|
||||
pthread_mutex_t paramLockMutex = PTHREAD_MUTEX_INITIALIZER;
|
||||
|
||||
/***************************************************************************************************
|
||||
* Internal Function Prototypes
|
||||
**************************************************************************************************/
|
||||
|
||||
/***************************************************************************************************
|
||||
* External Function Definitions
|
||||
**************************************************************************************************/
|
||||
|
||||
void param_setup() {
|
||||
param.pixled = dummyPixledParam;
|
||||
param.auton = dummyAutonParam;
|
||||
for (size_t i = 0; i < LED_NCHANS; ++i) {
|
||||
param.ledbar[i] = dummyLedbarParam;
|
||||
}
|
||||
param_access = ¶m;
|
||||
}
|
||||
|
||||
void set_mode(unsigned int mode) {
|
||||
if (mode != param.pixled.mode) {
|
||||
pthread_mutex_lock(¶mLockMutex);
|
||||
param.pixled.mode = mode;
|
||||
log_debug("Mode : %d", mode);
|
||||
websocket_send_mode(mode);
|
||||
pthread_mutex_unlock(¶mLockMutex);
|
||||
}
|
||||
}
|
||||
|
||||
void set_pattern(unsigned int pattern) {
|
||||
if (pattern != param.auton.pattern) {
|
||||
pthread_mutex_lock(¶mLockMutex);
|
||||
param.auton.pattern = pattern;
|
||||
log_debug("Pattern : %d", pattern);
|
||||
pthread_mutex_unlock(¶mLockMutex);
|
||||
}
|
||||
}
|
||||
|
||||
void set_gravity(int8_t gravity8) {
|
||||
param.auton.gravity = gravity8 * 100 / INT8_MAX;
|
||||
log_debug("Gravity : %d", param.auton.gravity);
|
||||
}
|
||||
|
||||
void set_hue_auto_shift(bool HasToBeEnabled) {
|
||||
if (HasToBeEnabled != param.auton.isHueAutoShiftEnabled) {
|
||||
param.auton.isHueAutoShiftEnabled = HasToBeEnabled;
|
||||
log_debug("Hue auto shift : %s", HasToBeEnabled ? "Enabled" : "Disabled");
|
||||
if (!HasToBeEnabled) {
|
||||
for (size_t i = 0; i < LED_NCHANS; ++i) {
|
||||
param.ledbar[i].hueAutoShift = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void set_sensitivity(int channel, int8_t sensitivity8) {
|
||||
float sensitivity = pow(1.065, sensitivity8 - INT8_MAX / 2);
|
||||
if (channel >= LED_NCHANS) {
|
||||
param.auton.sensitivity = sensitivity;
|
||||
} else {
|
||||
param.ledbar[channel].sensitivity = sensitivity;
|
||||
}
|
||||
log_debug("Sensitivity : %f", sensitivity);
|
||||
}
|
||||
|
||||
void set_hue_base(int channel, int8_t hueBase8) {
|
||||
uint16_t hueBase = hueBase8 * HUEBASE_MAX / INT8_MAX;
|
||||
if (channel < LED_NCHANS) {
|
||||
param.ledbar[channel].hueBase = hueBase;
|
||||
} else {
|
||||
for (size_t i = 0; i < LED_NCHANS; ++i) {
|
||||
param.ledbar[i].hueBase = hueBase;
|
||||
}
|
||||
}
|
||||
log_debug("Hue base : %u", hueBase);
|
||||
}
|
||||
|
||||
void set_hue_interval(int channel, int8_t hueInterval8) {
|
||||
int16_t hueInterval = hueInterval8 - ((INT8_MAX + 1) / 2);
|
||||
if (-30 <= hueInterval && hueInterval <= 30) {
|
||||
hueInterval = hueInterval * (HUEINTERVAL_MAX + 1) / 30;
|
||||
} else if (hueInterval <= -30) {
|
||||
hueInterval = 22 * (hueInterval + 30) - HUEINTERVAL_MAX;
|
||||
} else {
|
||||
hueInterval = 22 * (hueInterval - 30) + HUEINTERVAL_MAX;
|
||||
}
|
||||
|
||||
if (channel < LED_NCHANS) {
|
||||
param.ledbar[channel].hueInterval = hueInterval;
|
||||
} else {
|
||||
for (size_t i = 0; i < LED_NCHANS; ++i) {
|
||||
param.ledbar[i].hueInterval = hueInterval;
|
||||
}
|
||||
}
|
||||
log_debug("Hue interval [%2d] : %d", channel, hueInterval);
|
||||
}
|
||||
|
||||
void set_hue_auto_shift_interval(int channel, int8_t hueInterval8) {
|
||||
int16_t hueInterval = hueInterval8 - ((INT8_MAX + 1) / 2);
|
||||
if (-30 <= hueInterval && hueInterval <= 30) {
|
||||
hueInterval = hueInterval * (HUEINTERVAL_MAX + 1) / 30;
|
||||
} else if (hueInterval <= -30) {
|
||||
hueInterval = 22 * (hueInterval + 30) - HUEINTERVAL_MAX;
|
||||
} else {
|
||||
hueInterval = 22 * (hueInterval - 30) + HUEINTERVAL_MAX;
|
||||
}
|
||||
|
||||
if (channel < LED_NCHANS) {
|
||||
param.ledbar[channel].hueAutoShiftInterval = hueInterval;
|
||||
} else {
|
||||
for (size_t i = 0; i < LED_NCHANS; ++i) {
|
||||
param.ledbar[i].hueAutoShiftInterval = hueInterval;
|
||||
}
|
||||
}
|
||||
log_debug("Hue auto shift interval [%2d] : %d", channel, hueInterval);
|
||||
}
|
||||
|
||||
void set_hue_auto_shift_speed(int channel, int8_t hueSpeed8) {
|
||||
float hueSpeed = pow(1.065, hueSpeed8 - INT8_MAX / 2);
|
||||
if (channel < LED_NCHANS) {
|
||||
param.ledbar[channel].hueAutoShiftSpeed = hueSpeed;
|
||||
} else {
|
||||
for (size_t i = 0; i < LED_NCHANS; ++i) {
|
||||
param.ledbar[i].hueAutoShiftSpeed = hueSpeed;
|
||||
}
|
||||
}
|
||||
log_debug("Hue auto shift speed [%2d] : %d", channel, hueSpeed);
|
||||
}
|
||||
|
||||
void set_luminosity(int channel, int8_t luminosity8) {
|
||||
uint8_t luminosity = luminosity8 * UINT8_MAX / INT8_MAX;
|
||||
if (channel < LED_NCHANS) {
|
||||
param.ledbar[channel].luminosity = luminosity;
|
||||
} else {
|
||||
for (size_t i = 0; i < LED_NCHANS; ++i) {
|
||||
param.ledbar[i].luminosity = luminosity;
|
||||
}
|
||||
}
|
||||
log_debug("Luminosity : %u", luminosity);
|
||||
}
|
||||
|
||||
void set_saturation(int channel, int8_t saturation8) {
|
||||
uint8_t saturation = saturation8 * UINT8_MAX / INT8_MAX;
|
||||
if (channel < LED_NCHANS) {
|
||||
param.ledbar[channel].saturation = saturation;
|
||||
} else {
|
||||
for (size_t i = 0; i < LED_NCHANS; ++i) {
|
||||
param.ledbar[i].saturation = saturation;
|
||||
}
|
||||
}
|
||||
log_debug("Saturation : %u", saturation);
|
||||
}
|
||||
|
||||
/***************************************************************************************************
|
||||
* Internal Function Definitions
|
||||
**************************************************************************************************/
|
144
RpiLedBars/backend/src/rpi_param.h
Normal file
144
RpiLedBars/backend/src/rpi_param.h
Normal file
@@ -0,0 +1,144 @@
|
||||
/** @file .h
|
||||
* @brief This module
|
||||
*/
|
||||
|
||||
#if !defined(__RPI_PARAM_H__)
|
||||
#define __RPI_PARAM_H__
|
||||
|
||||
/***************************************************************************************************
|
||||
* Includes
|
||||
**************************************************************************************************/
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
|
||||
/***************************************************************************************************
|
||||
* Preprocessor Constants and Macros
|
||||
**************************************************************************************************/
|
||||
|
||||
#define CHAN_MAXLEDS 60 // Maximum number of LEDs per channel
|
||||
#define LED_NCHANS 8 // Number of LED channels (8 or 16)
|
||||
|
||||
#define GLOBAL_CHANNEL 15
|
||||
|
||||
#define HUE_MAX 359
|
||||
#define HUEBASE_MIN 0
|
||||
#define HUEBASE_MAX HUE_MAX
|
||||
#define HUEINTERVAL_MIN -HUE_MAX
|
||||
#define HUEINTERVAL_MAX HUE_MAX
|
||||
#define LUMINOSITY_MIN 0
|
||||
#define LUMINOSITY_MAX 255
|
||||
#define SATURATION_MIN 0
|
||||
#define SATURATION_MAX 255
|
||||
|
||||
/***************************************************************************************************
|
||||
* Type and Contant Definitions
|
||||
**************************************************************************************************/
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
typedef struct {
|
||||
int mode;
|
||||
unsigned int chanLedCount;
|
||||
} pixled_param_t;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
typedef struct {
|
||||
float sensitivity;
|
||||
unsigned int gravity;
|
||||
unsigned int pattern;
|
||||
bool isHueAutoShiftEnabled;
|
||||
} auton_param_t;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
typedef struct {
|
||||
float sensitivity;
|
||||
unsigned int hueBase;
|
||||
int hueInterval;
|
||||
unsigned int hueAutoShift;
|
||||
int hueAutoShiftInterval;
|
||||
float hueAutoShiftSpeed;
|
||||
bool isIncreasing;
|
||||
unsigned int luminosity;
|
||||
unsigned int saturation;
|
||||
} ledbar_param_t;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
typedef struct {
|
||||
pixled_param_t pixled;
|
||||
auton_param_t auton;
|
||||
ledbar_param_t ledbar[LED_NCHANS];
|
||||
} param_t;
|
||||
|
||||
/***************************************************************************************************
|
||||
* Global Variables
|
||||
**************************************************************************************************/
|
||||
|
||||
param_t *param_access;
|
||||
|
||||
/***************************************************************************************************
|
||||
* External Function Prototypes
|
||||
**************************************************************************************************/
|
||||
|
||||
/**
|
||||
* @brief
|
||||
*/
|
||||
void param_setup();
|
||||
|
||||
/**
|
||||
* @brief
|
||||
*
|
||||
* @param[in] mode
|
||||
*/
|
||||
void set_mode(unsigned int mode);
|
||||
|
||||
/**
|
||||
* @brief
|
||||
*
|
||||
* @param[in] pattern
|
||||
*/
|
||||
void set_pattern(unsigned int pattern);
|
||||
|
||||
/**
|
||||
* @brief
|
||||
*
|
||||
* @param[in] HasToBeEnabled
|
||||
*/
|
||||
void set_hue_auto_shift(bool HasToBeEnabled);
|
||||
|
||||
/**
|
||||
* @brief
|
||||
*
|
||||
* @param[in] gravity8
|
||||
*/
|
||||
void set_gravity(int8_t gravity8);
|
||||
|
||||
/**
|
||||
* @brief
|
||||
*
|
||||
* @param[in] channel
|
||||
*
|
||||
* @param[in] sensitivity8
|
||||
*/
|
||||
void set_sensitivity(int channel, int8_t sensitivity8);
|
||||
|
||||
void set_hue_base(int channel, int8_t hueBase8);
|
||||
|
||||
void set_hue_interval(int channel, int8_t hueInterval8);
|
||||
|
||||
void set_hue_auto_shift_interval(int channel, int8_t hueInterval8);
|
||||
|
||||
void set_hue_auto_shift_speed(int channel, int8_t hueSpeed8);
|
||||
|
||||
void set_luminosity(int channel, int8_t luminosity8);
|
||||
|
||||
void set_saturation(int channel, int8_t saturation8);
|
||||
|
||||
#endif /* __RPI_PARAM_H__ */
|
259
RpiLedBars/backend/src/rpi_pattern.c
Normal file
259
RpiLedBars/backend/src/rpi_pattern.c
Normal file
@@ -0,0 +1,259 @@
|
||||
/** @file .c
|
||||
* @brief This module
|
||||
*/
|
||||
|
||||
/***************************************************************************************************
|
||||
* Includes
|
||||
**************************************************************************************************/
|
||||
|
||||
#include "rpi_pattern.h"
|
||||
|
||||
#include <stdlib.h>
|
||||
|
||||
#include "log.h"
|
||||
|
||||
#include "drivers/leddriver/rpi_leddriver.h"
|
||||
#include "rpi_param.h"
|
||||
|
||||
/***************************************************************************************************
|
||||
* Preprocessor Constants and Macros
|
||||
**************************************************************************************************/
|
||||
|
||||
/***************************************************************************************************
|
||||
* Type and Contant Definitions
|
||||
**************************************************************************************************/
|
||||
|
||||
/***************************************************************************************************
|
||||
* Persistent Variables
|
||||
**************************************************************************************************/
|
||||
|
||||
/**
|
||||
* Index to channel translattion
|
||||
*/
|
||||
int channelTranslation[LED_NCHANS] = {0, 1, 2, 3, 4, 6, 7, 5};
|
||||
|
||||
/**
|
||||
* RGB data buffer. Persistency is not needed but, as of it size, it must be declared in heap
|
||||
*/
|
||||
int rgbData[CHAN_MAXLEDS][LED_NCHANS];
|
||||
|
||||
/**
|
||||
* Number of led previously lighted per led bar
|
||||
*/
|
||||
int lightedLedCountArray[LED_NCHANS];
|
||||
|
||||
/**
|
||||
* Index of first previously lighted per led bar
|
||||
*/
|
||||
int baseLedIndexArray[LED_NCHANS];
|
||||
|
||||
/***************************************************************************************************
|
||||
* Internal Function Prototypes
|
||||
**************************************************************************************************/
|
||||
|
||||
uint16_t get_max_on_interval(uint16_t *array, unsigned int length, size_t intervalMin,
|
||||
size_t intervalSize);
|
||||
|
||||
uint16_t apply_sensitivity(uint16_t value, unsigned int valueIndex);
|
||||
|
||||
unsigned int get_led_to_light_count(uint16_t barValue, unsigned int barIndex);
|
||||
|
||||
unsigned int get_first_led_to_light(unsigned int barIndex);
|
||||
|
||||
uint32_t get_hsv_color(unsigned int barIndex, float shiftRatio);
|
||||
|
||||
void increment_autoshift(unsigned int barIndex);
|
||||
|
||||
/***************************************************************************************************
|
||||
* External Function Definitions
|
||||
**************************************************************************************************/
|
||||
|
||||
void bounce_led_and_color(uint16_t *spectrumArray, unsigned int spectrumLength) {
|
||||
for (size_t ledBarIndex = 0; ledBarIndex < LED_NCHANS; ++ledBarIndex) {
|
||||
size_t intervalSize = spectrumLength / LED_NCHANS;
|
||||
uint16_t barMax = 0;
|
||||
unsigned int ledToLightCount = 0;
|
||||
|
||||
barMax = get_max_on_interval(spectrumArray, spectrumLength, ledBarIndex * intervalSize,
|
||||
intervalSize);
|
||||
barMax = apply_sensitivity(barMax, ledBarIndex);
|
||||
ledToLightCount = get_led_to_light_count(barMax, ledBarIndex);
|
||||
|
||||
uint32_t color = get_hsv_color(ledBarIndex, (ledToLightCount - 1) /
|
||||
(float)param_access->pixled.chanLedCount);
|
||||
|
||||
for (size_t i = 0; i < ledToLightCount; ++i) {
|
||||
rgbData[i][ledBarIndex] = color;
|
||||
rgb_txdata(rgbData[i], i);
|
||||
}
|
||||
for (size_t i = ledToLightCount; i < param_access->pixled.chanLedCount; ++i) {
|
||||
rgbData[i][ledBarIndex] = 0x000000;
|
||||
rgb_txdata(rgbData[i], i);
|
||||
}
|
||||
|
||||
increment_autoshift(ledBarIndex);
|
||||
}
|
||||
leddriver_refresh();
|
||||
}
|
||||
|
||||
void bounce_led(uint16_t *spectrumArray, unsigned int spectrumLength) {
|
||||
for (size_t ledBarIndex = 0; ledBarIndex < LED_NCHANS; ++ledBarIndex) {
|
||||
size_t intervalSize = spectrumLength / LED_NCHANS;
|
||||
uint16_t barMax = 0;
|
||||
unsigned int ledToLightCount = 0;
|
||||
|
||||
barMax = get_max_on_interval(spectrumArray, spectrumLength, ledBarIndex * intervalSize,
|
||||
intervalSize);
|
||||
barMax = apply_sensitivity(barMax, ledBarIndex);
|
||||
ledToLightCount = get_led_to_light_count(barMax, ledBarIndex);
|
||||
|
||||
for (size_t i = 0; i < ledToLightCount; ++i) {
|
||||
|
||||
uint32_t color = get_hsv_color(ledBarIndex, i / (float)param_access->pixled.chanLedCount);
|
||||
rgbData[i][ledBarIndex] = color;
|
||||
rgb_txdata(rgbData[i], i);
|
||||
}
|
||||
for (size_t i = ledToLightCount; i < param_access->pixled.chanLedCount; ++i) {
|
||||
rgbData[i][ledBarIndex] = 0x000000;
|
||||
rgb_txdata(rgbData[i], i);
|
||||
}
|
||||
|
||||
increment_autoshift(ledBarIndex);
|
||||
}
|
||||
|
||||
leddriver_refresh();
|
||||
}
|
||||
|
||||
void bounce_led_and_travel(uint16_t *spectrumArray, unsigned int spectrumLength) {
|
||||
for (size_t ledBarIndex = 0; ledBarIndex < LED_NCHANS; ++ledBarIndex) {
|
||||
size_t intervalSize = spectrumLength / LED_NCHANS;
|
||||
uint16_t barMax = 0;
|
||||
unsigned int ledToLightCount = 0, firstLedToLight = get_first_led_to_light(ledBarIndex);
|
||||
uint32_t color = 0;
|
||||
|
||||
barMax = get_max_on_interval(spectrumArray, spectrumLength, ledBarIndex * intervalSize,
|
||||
intervalSize);
|
||||
barMax = apply_sensitivity(barMax, ledBarIndex) / 4;
|
||||
ledToLightCount = get_led_to_light_count(barMax, ledBarIndex) + 1;
|
||||
|
||||
if (!param_access->auton.isHueAutoShiftEnabled) {
|
||||
color = get_hsv_color(ledBarIndex,
|
||||
(ledToLightCount - 1) * 4L / (float)param_access->pixled.chanLedCount);
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < ledToLightCount; ++i) {
|
||||
unsigned int ledIndex = firstLedToLight + i;
|
||||
ledIndex = ledIndex < param_access->pixled.chanLedCount
|
||||
? ledIndex
|
||||
: ledIndex - param_access->pixled.chanLedCount;
|
||||
if (param_access->auton.isHueAutoShiftEnabled) {
|
||||
color = get_hsv_color(ledBarIndex, ledIndex / (float)param_access->pixled.chanLedCount);
|
||||
}
|
||||
rgbData[ledIndex][ledBarIndex] = color;
|
||||
rgb_txdata(rgbData[ledIndex], ledIndex);
|
||||
}
|
||||
for (size_t i = ledToLightCount; i < param_access->pixled.chanLedCount; ++i) {
|
||||
unsigned int ledIndex = firstLedToLight + i;
|
||||
ledIndex = ledIndex < param_access->pixled.chanLedCount
|
||||
? ledIndex
|
||||
: ledIndex - param_access->pixled.chanLedCount;
|
||||
rgbData[ledIndex][ledBarIndex] = 0x000000;
|
||||
rgb_txdata(rgbData[ledIndex], ledIndex);
|
||||
}
|
||||
|
||||
increment_autoshift(ledBarIndex);
|
||||
}
|
||||
|
||||
leddriver_refresh();
|
||||
}
|
||||
|
||||
/***************************************************************************************************
|
||||
* Internal Function Definitions
|
||||
**************************************************************************************************/
|
||||
|
||||
uint16_t get_max_on_interval(uint16_t *array, unsigned int length, size_t intervalMin,
|
||||
size_t intervalSize) {
|
||||
uint16_t barMax = 0;
|
||||
for (size_t i = 0; i < intervalSize; ++i) {
|
||||
unsigned barIndex = intervalMin + i;
|
||||
if (barMax < array[barIndex]) {
|
||||
barMax = array[barIndex];
|
||||
}
|
||||
}
|
||||
return barMax;
|
||||
}
|
||||
|
||||
uint16_t apply_sensitivity(uint16_t value, unsigned int valueIndex) {
|
||||
return value * param_access->auton.sensitivity * param_access->ledbar[valueIndex].sensitivity;
|
||||
}
|
||||
|
||||
unsigned int get_led_to_light_count(uint16_t barValue, unsigned int barIndex) {
|
||||
unsigned int nbLightedLed = lightedLedCountArray[barIndex];
|
||||
unsigned int ledToLightCount = barValue * param_access->pixled.chanLedCount / UINT16_MAX;
|
||||
|
||||
if (ledToLightCount < nbLightedLed) {
|
||||
/* apply gravity */
|
||||
ledToLightCount = ((ledToLightCount * (100 - param_access->auton.gravity)) +
|
||||
(nbLightedLed * param_access->auton.gravity)) /
|
||||
100;
|
||||
}
|
||||
lightedLedCountArray[barIndex] = ledToLightCount;
|
||||
return ledToLightCount;
|
||||
}
|
||||
|
||||
unsigned int get_first_led_to_light(unsigned int barIndex) {
|
||||
unsigned int firstLed = baseLedIndexArray[barIndex];
|
||||
if (lightedLedCountArray[barIndex] != 0) {
|
||||
firstLed += lightedLedCountArray[barIndex] / 4 + 1;
|
||||
firstLed = firstLed < param_access->pixled.chanLedCount
|
||||
? firstLed
|
||||
: firstLed - param_access->pixled.chanLedCount;
|
||||
}
|
||||
baseLedIndexArray[barIndex] = firstLed;
|
||||
return firstLed;
|
||||
}
|
||||
|
||||
uint32_t get_hsv_color(unsigned int barIndex, float shiftRatio) {
|
||||
int hueShift = shiftRatio * param_access->ledbar[barIndex].hueInterval;
|
||||
uint16_t hue = param_access->ledbar[barIndex].hueBase +
|
||||
(param_access->ledbar[barIndex].hueAutoShift) + hueShift;
|
||||
// log_trace("hueAutoShift : %010x, hueShift : %d, hue %u",
|
||||
// param_access->ledbar[barIndex].hueAutoShift, hueShift, hue);
|
||||
uint32_t color =
|
||||
ColorHSV(hue * UINT16_MAX / HUEBASE_MAX, param_access->ledbar[barIndex].saturation,
|
||||
param_access->ledbar[barIndex].luminosity);
|
||||
return color;
|
||||
}
|
||||
|
||||
void increment_autoshift(unsigned int barIndex) {
|
||||
static float increment[LED_NCHANS];
|
||||
if (param_access->auton.isHueAutoShiftEnabled) {
|
||||
int const hueInterval = param_access->ledbar[barIndex].hueAutoShiftInterval;
|
||||
int hueAutoShift = param_access->ledbar[barIndex].hueAutoShift;
|
||||
if ((hueInterval > 0 && param_access->ledbar[barIndex].isIncreasing) ||
|
||||
(hueInterval < 0 && !param_access->ledbar[barIndex].isIncreasing)) {
|
||||
increment[barIndex] += param_access->ledbar[barIndex].hueAutoShiftSpeed;
|
||||
} else if (hueInterval != 0) {
|
||||
increment[barIndex] -= param_access->ledbar[barIndex].hueAutoShiftSpeed;
|
||||
}
|
||||
|
||||
if (abs(increment[barIndex]) > 1) {
|
||||
hueAutoShift += increment[barIndex];
|
||||
increment[barIndex] = increment[barIndex] - (int)increment[barIndex];
|
||||
}
|
||||
|
||||
if ((hueInterval > 0 && hueAutoShift < 0) || (hueInterval < 0 && hueAutoShift > 0)) {
|
||||
hueAutoShift = -hueAutoShift;
|
||||
param_access->ledbar[barIndex].isIncreasing = !param_access->ledbar[barIndex].isIncreasing;
|
||||
} else if ((hueInterval > 0 && hueAutoShift > hueInterval) ||
|
||||
(hueInterval < 0 && hueAutoShift < hueInterval)) {
|
||||
hueAutoShift = hueInterval;
|
||||
param_access->ledbar[barIndex].isIncreasing = !param_access->ledbar[barIndex].isIncreasing;
|
||||
}
|
||||
if (barIndex == 0) {
|
||||
log_trace("hueInterval : %d, hueAutoShift : %d, increase : %s", hueInterval, hueAutoShift,
|
||||
param_access->ledbar[barIndex].isIncreasing ? "yes" : "no");
|
||||
}
|
||||
param_access->ledbar[barIndex].hueAutoShift = hueAutoShift;
|
||||
}
|
||||
}
|
57
RpiLedBars/backend/src/rpi_pattern.h
Normal file
57
RpiLedBars/backend/src/rpi_pattern.h
Normal file
@@ -0,0 +1,57 @@
|
||||
/** @file .h
|
||||
* @brief This module
|
||||
*/
|
||||
|
||||
#if !defined(__RPI_PATTERN_H__)
|
||||
#define __RPI_PATTERN_H__
|
||||
|
||||
/***************************************************************************************************
|
||||
* Includes
|
||||
**************************************************************************************************/
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
/***************************************************************************************************
|
||||
* Preprocessor Constants and Macros
|
||||
**************************************************************************************************/
|
||||
|
||||
/***************************************************************************************************
|
||||
* Type and Contant Definitions
|
||||
**************************************************************************************************/
|
||||
|
||||
/***************************************************************************************************
|
||||
* Global Variables
|
||||
**************************************************************************************************/
|
||||
|
||||
/***************************************************************************************************
|
||||
* External Function Prototypes
|
||||
**************************************************************************************************/
|
||||
|
||||
/**
|
||||
* @brief
|
||||
*
|
||||
* @param[in] spectrumArray array of value from spectral analysis
|
||||
*
|
||||
* @param[in] spectrumLength length of spectrum array
|
||||
*/
|
||||
void bounce_led_and_color(uint16_t *spectrumArray, unsigned int spectrumLength);
|
||||
|
||||
/**
|
||||
* @brief
|
||||
*
|
||||
* @param[in] spectrumArray array of value from spectral analysis
|
||||
*
|
||||
* @param[in] spectrumLength length of spectrum array
|
||||
*/
|
||||
void bounce_led(uint16_t *spectrumArray, unsigned int spectrumLength);
|
||||
|
||||
/**
|
||||
* @brief
|
||||
*
|
||||
* @param[in] spectrumArray array of value from spectral analysis
|
||||
*
|
||||
* @param[in] spectrumLength length of spectrum array
|
||||
*/
|
||||
void bounce_led_and_travel(uint16_t *spectrumArray, unsigned int spectrumLength);
|
||||
|
||||
#endif /* __RPI_PATTERN_H__ */
|
11
RpiLedBars/backend/src/tasks/CMakeLists.txt
Normal file
11
RpiLedBars/backend/src/tasks/CMakeLists.txt
Normal file
@@ -0,0 +1,11 @@
|
||||
# add the executable
|
||||
add_library(${PROJECT_NAME}_tasks
|
||||
artnet/artnet.c
|
||||
cava/cava.c
|
||||
selector/selector.c
|
||||
websocket/websocket.c)
|
||||
|
||||
target_link_libraries(${PROJECT_NAME}_tasks PRIVATE pthread)
|
||||
target_link_libraries(${PROJECT_NAME}_tasks PRIVATE logc ws)
|
||||
|
||||
target_include_directories(${PROJECT_NAME}_tasks PUBLIC includes)
|
198
RpiLedBars/backend/src/tasks/artnet/artnet.c
Normal file
198
RpiLedBars/backend/src/tasks/artnet/artnet.c
Normal file
@@ -0,0 +1,198 @@
|
||||
/** @file rpi_artnet.c
|
||||
* @brief This module contains continuous tasks for artnet node communications.
|
||||
*
|
||||
* This is the implementation file.
|
||||
*/
|
||||
|
||||
/***************************************************************************************************
|
||||
* Includes
|
||||
**************************************************************************************************/
|
||||
#include "artnet.h"
|
||||
|
||||
#include <arpa/inet.h>
|
||||
#include <errno.h>
|
||||
#include <fcntl.h>
|
||||
#include <poll.h>
|
||||
#include <pthread.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <sys/socket.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include "artnet_packets.h"
|
||||
#include "artnet_utils.h"
|
||||
#include "log.h"
|
||||
|
||||
/***************************************************************************************************
|
||||
* Preprocessor Constants and Macros
|
||||
**************************************************************************************************/
|
||||
|
||||
/***************************************************************************************************
|
||||
* Type and Contant Definitions
|
||||
**************************************************************************************************/
|
||||
|
||||
/***************************************************************************************************
|
||||
* Persistent Variables
|
||||
**************************************************************************************************/
|
||||
|
||||
/**
|
||||
* Boolean for interrupting the main thread loop
|
||||
*/
|
||||
bool isUdpListenerRunning = false;
|
||||
/**
|
||||
* Task thread identifier
|
||||
*/
|
||||
pthread_t udpListener;
|
||||
/**
|
||||
* Buffer for storing artnet dmx data received
|
||||
*/
|
||||
uint8_t artDmxBufferArray[16][512];
|
||||
|
||||
/***************************************************************************************************
|
||||
* Internal Function Prototypes
|
||||
**************************************************************************************************/
|
||||
|
||||
/**
|
||||
* @brief This function is used to thread main execution function
|
||||
*
|
||||
* @param arg not used.
|
||||
*
|
||||
* @return NULL.
|
||||
*/
|
||||
static void *artnet_udp_handler(void *arg);
|
||||
|
||||
/**
|
||||
* @brief This function is used to send an artnet poll reply packet
|
||||
*
|
||||
* @param[in] fdUdpSocket The UDP socket file descriptor.
|
||||
*
|
||||
* @param[in] senderAddr The adress of poll emiter.
|
||||
*/
|
||||
static void artnet_send_poll_reply(int fdUdpSocket, struct sockaddr_in senderAddr);
|
||||
|
||||
/***************************************************************************************************
|
||||
* External Function Definitions
|
||||
**************************************************************************************************/
|
||||
|
||||
void artnet_start() {
|
||||
isUdpListenerRunning = true;
|
||||
|
||||
if (pthread_create(&udpListener, NULL, artnet_udp_handler, NULL) < 0) {
|
||||
log_error("pthread_create: %s", strerror(errno));
|
||||
}
|
||||
}
|
||||
|
||||
void artnet_stop() {
|
||||
isUdpListenerRunning = false;
|
||||
|
||||
if (pthread_join(udpListener, NULL) != 0) {
|
||||
log_error("pthread_join: %s", strerror(errno));
|
||||
}
|
||||
}
|
||||
|
||||
int artnet_get_dmx_data(unsigned int univerve, uint8_t *dmxData[]) {
|
||||
if (univerve > 8) {
|
||||
log_error("Universe %d out of bounds %d\n", univerve, 16);
|
||||
*dmxData = NULL;
|
||||
return -1;
|
||||
}
|
||||
*dmxData = artDmxBufferArray[univerve];
|
||||
log_trace("%d;%d;%d;%d", (*dmxData)[0], (*dmxData)[1], (*dmxData)[2], (*dmxData)[3]);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/***************************************************************************************************
|
||||
* Internal Function Definitions
|
||||
**************************************************************************************************/
|
||||
|
||||
static void *artnet_udp_handler(void *arg) {
|
||||
int fdUdpSocket = -1;
|
||||
struct pollfd pollFdArray[1];
|
||||
int timeoutMs;
|
||||
int flags;
|
||||
char buffer[ARTNET_MAX_BUFFER];
|
||||
struct sockaddr_in serverAddr, srcAddr;
|
||||
socklen_t srcLen = sizeof(struct sockaddr_in);
|
||||
|
||||
/* Create UDP socket */
|
||||
fdUdpSocket = socket(PF_INET, SOCK_DGRAM, 0);
|
||||
if (fdUdpSocket < 0) {
|
||||
log_error("Opening socket failed: %s", strerror(errno));
|
||||
}
|
||||
|
||||
/* Set non-blocking socket */
|
||||
flags = fcntl(fdUdpSocket, F_GETFL, 0);
|
||||
fcntl(fdUdpSocket, F_SETFL, flags | O_NONBLOCK);
|
||||
|
||||
/* pollfd structure and timeout */
|
||||
memset(pollFdArray, 0, sizeof(pollFdArray));
|
||||
pollFdArray[0].fd = fdUdpSocket;
|
||||
pollFdArray[0].events = POLLIN;
|
||||
timeoutMs = 10;
|
||||
|
||||
/* Configure settings in address struct */
|
||||
serverAddr.sin_family = AF_INET;
|
||||
serverAddr.sin_port = htons(ARTNET_PORT);
|
||||
serverAddr.sin_addr.s_addr = inet_addr("0.0.0.0");
|
||||
memset(serverAddr.sin_zero, '\0', sizeof(serverAddr.sin_zero));
|
||||
|
||||
/* Bind socket with address struct */
|
||||
bind(fdUdpSocket, (struct sockaddr *)&serverAddr, sizeof(serverAddr));
|
||||
|
||||
while (isUdpListenerRunning) {
|
||||
|
||||
int pollReturn;
|
||||
if ((pollReturn = poll(pollFdArray, 1, timeoutMs)) == 1) {
|
||||
ssize_t bufferLen =
|
||||
recvfrom(fdUdpSocket, buffer, ARTNET_MAX_BUFFER, 0, (struct sockaddr *)&srcAddr, &srcLen);
|
||||
|
||||
if (bufferLen <= ARTNET_MAX_BUFFER && bufferLen > sizeof(artnetHeader_t)) {
|
||||
artnetHeader_t *artnetHeader = (artnetHeader_t *)buffer;
|
||||
|
||||
if (memcmp(artnetHeader->id, ARTNET_ID, sizeof(ARTNET_ID)) == 0) {
|
||||
switch (artnetHeader->opCode) {
|
||||
case OpDmx:
|
||||
if (bufferLen >= 20) {
|
||||
artDmx_t *artDmx = (artDmx_t *)buffer;
|
||||
uint16_t dmxLength = (artDmx->lengthHi << 8) | artDmx->lengthLo;
|
||||
uint8_t *artDmxBuffer = artDmxBufferArray[artDmx->subUni & 0x00ff];
|
||||
if (dmxLength <= 512) {
|
||||
// store for later use
|
||||
memcpy(artDmxBuffer, artDmx->data, dmxLength);
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case OpPoll:
|
||||
artnet_send_poll_reply(fdUdpSocket, srcAddr);
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} else if (pollReturn < 0) {
|
||||
log_error("error polling %d: %s", fdUdpSocket, strerror(errno));
|
||||
}
|
||||
}
|
||||
|
||||
close(fdUdpSocket);
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static void artnet_send_poll_reply(int fdUdpSocket, struct sockaddr_in senderAddr) {
|
||||
/* Configure settings in address struct */
|
||||
senderAddr.sin_family = AF_INET;
|
||||
senderAddr.sin_port = htons(ARTNET_PORT);
|
||||
memset(senderAddr.sin_zero, '\0', sizeof(senderAddr.sin_zero));
|
||||
|
||||
if (sendto(fdUdpSocket, (uint8_t *)&artPollReply, sizeof(artPollReply_t), 0,
|
||||
(struct sockaddr *)&senderAddr, sizeof(senderAddr)) < 0) {
|
||||
log_error("%s: sending poll reply to \"%s:%d\": %s", inet_ntoa(senderAddr.sin_addr),
|
||||
senderAddr.sin_port, strerror(errno));
|
||||
}
|
||||
}
|
128
RpiLedBars/backend/src/tasks/artnet/artnet_op_codes.h
Normal file
128
RpiLedBars/backend/src/tasks/artnet/artnet_op_codes.h
Normal file
@@ -0,0 +1,128 @@
|
||||
#if !defined(__RPI_ARTNET_OP_CODES_H__)
|
||||
#define __RPI_ARTNET_OP_CODES_H__
|
||||
|
||||
/* List of hex values and discriptions of Opcodes */
|
||||
|
||||
/* This is an ArtPoll packet, no other data is contained in this UDP packet */
|
||||
#define OpPoll 0x2000
|
||||
|
||||
/* This is an ArtPollReply Packet. It contains device status information. */
|
||||
#define OpPollReply 0x2100
|
||||
|
||||
/* Diagnostics and data logging packet. */
|
||||
#define OpDiagData 0x2300
|
||||
|
||||
/* Used to send text based parameter commands. */
|
||||
#define OpCommand 0x2400
|
||||
|
||||
/* This is an ArtDmx data packet. It contains zero start code DMX512 information for a single
|
||||
* Universe. */
|
||||
#define OpOutput 0x5000
|
||||
|
||||
/* This is an ArtDmx data packet. It contains zero start code DMX512 information for a single
|
||||
* Universe. */
|
||||
#define OpDmx 0x5000
|
||||
|
||||
/* This is an ArtNzs data packet. It contains non-zero start code (except RDM) DMX512 information
|
||||
* for a single Universe. */
|
||||
#define OpNzs 0x5100
|
||||
|
||||
/* This is an ArtAddress packet. It contains remote programming information for a Node. */
|
||||
#define OpAddress 0x6000
|
||||
|
||||
/* This is an ArtInput packet. It contains enable – disable data for DMX inputs. */
|
||||
#define OpInput 0x7000
|
||||
|
||||
/* This is an ArtTodRequest packet. It is used to request a Table of Devices (ToD) for RDM
|
||||
* discovery. */
|
||||
#define OpTodRequest 0x8000
|
||||
|
||||
/* This is an ArtTodData packet. It is used to send a Table of Devices (ToD) for RDM discovery. */
|
||||
#define OpTodData 0x8100
|
||||
|
||||
/* This is an ArtTodControl packet. It is used to send RDM discovery control messages. */
|
||||
#define OpTodControl 0x8200
|
||||
|
||||
/* This is an ArtRdm packet. It is used to send all non discovery RDM messages. */
|
||||
#define OpRdm 0x8300
|
||||
|
||||
/* This is an ArtRdmSub packet. It is used to send compressed, RDM Sub-Device data. */
|
||||
#define OpRdmSub 0x8400
|
||||
|
||||
/* This is an ArtVideoSetup packet. It contains video screen setup information for nodes that
|
||||
* implement the extended video features. */
|
||||
#define OpVideoSetup 0xa010
|
||||
|
||||
/* This is an ArtVideoPalette packet. It contains colour palette setup information for nodes that
|
||||
* implement the extended video features. */
|
||||
#define OpVideoPalette 0xa020
|
||||
|
||||
/* This is an ArtVideoData packet. It contains display data for nodes that implement the extended
|
||||
* video features. */
|
||||
#define OpVideoData 0xa040
|
||||
|
||||
/* This is an ArtMacMaster packet. It is used to program the Node’s MAC address, Oem device type and
|
||||
* ESTA manufacturer code. This is for factory initialisation of a Node. It is not to be used by
|
||||
* applications. */
|
||||
#define OpMacMaster 0xf000
|
||||
|
||||
/* This is an ArtMacSlave packet. It is returned by the node to acknowledge receipt of an
|
||||
* ArtMacMaster packet. */
|
||||
#define OpMacSlave 0xf100
|
||||
|
||||
/* This is an ArtFirmwareMaster packet. It is used to upload new firmware or firmware extensions to
|
||||
* the Node. */
|
||||
#define OpFirmwareMaster 0xf200
|
||||
|
||||
/* This is an ArtFirmwareReply packet. It is returned by the node to acknowledge receipt of an
|
||||
* ArtFirmwareMaster packet or ArtFileTnMaster packet. */
|
||||
#define OpFirmwareReply 0xf300
|
||||
|
||||
/* Uploads user file to node. */
|
||||
#define OpFileTnMaster 0xf400
|
||||
|
||||
/* Downloads user file from node. */
|
||||
#define OpFileFnMaster 0xf500
|
||||
|
||||
/* Node acknowledge for downloads. */
|
||||
#define OpFileFnReply 0xf600
|
||||
|
||||
/* This is an ArtIpProg packet. It is used to reprogramme the IP, Mask and Port address of the Node.
|
||||
*/
|
||||
#define OpIpProg 0xf800
|
||||
|
||||
/* This is an ArtIpProgReply packet. It is returned by the node to acknowledge receipt of an
|
||||
* ArtIpProg packet. */
|
||||
#define OpIpProgReply 0xf900
|
||||
|
||||
/* This is an ArtMedia packet. It is Unicast by a Media Server and acted upon by a Controller. */
|
||||
#define OpMedia 0x9000
|
||||
|
||||
/* This is an ArtMediaPatch packet. It is Unicast by a Controller and acted upon by a Media Server.
|
||||
*/
|
||||
#define OpMediaPatch 0x9100
|
||||
|
||||
/* This is an ArtMediaControl packet. It is Unicast by a Controller and acted upon by a Media
|
||||
* Server. */
|
||||
#define OpMediaControl 0x9200
|
||||
|
||||
/* This is an ArtMediaControlReply packet. It is Unicast by a Media Server and acted upon by a
|
||||
* Controller. */
|
||||
#define OpMediaContrlReply 0x9300
|
||||
|
||||
/* This is an ArtTimeCode packet. It is used to transport time code over the network. */
|
||||
#define OpTimeCode 0x9700
|
||||
|
||||
/* Used to synchronise real time date and clock */
|
||||
#define OpTimeSync 0x9800
|
||||
|
||||
/* Used to send trigger macros */
|
||||
#define OpTrigger 0x9900
|
||||
|
||||
/* Requests a node's file list */
|
||||
#define OpDirectory 0x9a00
|
||||
|
||||
/* Replies to OpDirectory with file list */
|
||||
#define OpDirectoryReply 0x9b00
|
||||
|
||||
#endif // __RPI_ARTNET_OP_CODES_H__
|
178
RpiLedBars/backend/src/tasks/artnet/artnet_packets.h
Normal file
178
RpiLedBars/backend/src/tasks/artnet/artnet_packets.h
Normal file
@@ -0,0 +1,178 @@
|
||||
#if !defined(__RPI_ARTNET_PACKETS_H__)
|
||||
#define __RPI_ARTNET_PACKETS_H__
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#include "artnet_op_codes.h"
|
||||
#include "artnet_utils.h"
|
||||
|
||||
typedef struct {
|
||||
char id[8];
|
||||
uint16_t opCode;
|
||||
} __attribute__((__packed__)) artnetHeader_t;
|
||||
|
||||
typedef struct {
|
||||
artnetHeader_t artnetHeader;
|
||||
uint16_t protVer;
|
||||
uint8_t sequence;
|
||||
uint8_t physical;
|
||||
uint8_t subUni;
|
||||
uint8_t net;
|
||||
uint8_t lengthHi;
|
||||
uint8_t lengthLo;
|
||||
uint8_t data[512];
|
||||
} __attribute__((__packed__)) artDmx_t;
|
||||
|
||||
typedef struct {
|
||||
char ID[8];
|
||||
uint16_t OpCode;
|
||||
uint8_t IPAddr[4];
|
||||
uint16_t Port;
|
||||
uint16_t VersionInfo; // The node's current FIRMWARE VERS
|
||||
// Bits 14-8 of the 15 bit universe number are encoded into the bottom 7 bits of this field.
|
||||
uint8_t NetSwitch;
|
||||
// This is used in combination with SubSwitch and Swin[] or Swout[] to produce the full universe
|
||||
// address. Bits 7-4 of the 15 bit universe number are encoded into the bottom 4 bits of this
|
||||
// field.
|
||||
uint8_t SubSwitch;
|
||||
uint16_t Oem; // Manufacturer code, bit 15 set if
|
||||
// extended features avail
|
||||
uint8_t UbeaVersion; // Firmware version of UBEA
|
||||
uint8_t Status;
|
||||
// bit 0 = 0 UBEA not present
|
||||
// bit 0 = 1 UBEA present
|
||||
// bit 1 = 0 Not capable of RDM (Uni-directional DMX)
|
||||
// bit 1 = 1 Capable of RDM (Bi-directional DMX)
|
||||
// bit 2 = 0 Booted from flash (normal boot)
|
||||
// bit 2 = 1 Booted from ROM (possible error condition)
|
||||
// bit 3 = Not used
|
||||
// bit 54 = 00 Universe programming authority unknown
|
||||
// bit 54 = 01 Universe programming authority set by front panel controls
|
||||
// bit 54 = 10 Universe programming authority set by network
|
||||
// bit 76 = 00 Indicators Normal
|
||||
// bit 76 = 01 Indicators Locate
|
||||
// bit 76 = 10 Indicators Mute
|
||||
uint8_t EstaMan[2]; // ESTA manufacturer id, lo byte
|
||||
char ShortName[18]; // short name defaults to IP
|
||||
char LongName[64];
|
||||
// Text feedback of Node status or errors also used for debug info
|
||||
char NodeReport[64];
|
||||
|
||||
uint8_t NumPortsHi; // 0
|
||||
uint8_t NumPortsLo; // 4 If num i/p ports is dif to output ports, return biggest
|
||||
|
||||
uint8_t PortTypes[4];
|
||||
// bit 7 is output
|
||||
// bit 6 is input
|
||||
// bits 0-5 are protocol number (0= DMX, 1=MIDI)
|
||||
// for DMX-Hub ={0xc0,0xc0,0xc0,0xc0};
|
||||
|
||||
uint8_t GoodInput[4];
|
||||
// bit 7 is data received
|
||||
// bit 6 is data includes test packets
|
||||
// bit 5 is data includes SIP's
|
||||
// bit 4 is data includes text
|
||||
// bit 3 set is input is disabled
|
||||
// bit 2 is receive errors
|
||||
// bit 1-0 not used, transmitted as zero.
|
||||
// Don't test for zero!
|
||||
|
||||
uint8_t GoodOutput[4];
|
||||
// bit 7 is data is transmitting
|
||||
// bit 6 is data includes test packets
|
||||
// bit 5 is data includes SIP's
|
||||
// bit 4 is data includes text
|
||||
// bit 3 output is merging data.
|
||||
// bit 2 set if DMX output short detected on power up
|
||||
// bit 1 set if DMX output merge mode is LTP
|
||||
// bit 0 not used, transmitted as zero.
|
||||
|
||||
uint8_t SwIn[4];
|
||||
// Bits 3-0 of the 15 bit universe number are encoded into the low nibble
|
||||
// This is used in combination with SubSwitch and NetSwitch to produce the full universe address.
|
||||
// THIS IS FOR INPUT - ART-NET or DMX
|
||||
// NB ON ART-NET II THESE 4 UNIVERSES WILL BE UNICAST TO.
|
||||
|
||||
uint8_t SwOut[4];
|
||||
// Bits 3-0 of the 15 bit universe number are encoded into the low nibble
|
||||
// This is used in combination with SubSwitch and NetSwitch to produce the full universe address.
|
||||
// data belongs
|
||||
// THIS IS FOR OUTPUT - ART-NET or DMX.
|
||||
// NB ON ART-NET II THESE 4 UNIVERSES WILL BE UNICAST TO.
|
||||
|
||||
uint8_t SwVideo;
|
||||
// Low nibble is the value of the video
|
||||
// output channel
|
||||
|
||||
uint8_t SwMacro;
|
||||
// Bit 0 is Macro input 1
|
||||
// Bit 7 is Macro input 8
|
||||
|
||||
uint8_t SwRemote;
|
||||
// Bit 0 is Remote input 1
|
||||
// Bit 7 is Remote input 8
|
||||
|
||||
uint8_t Spare1; // Spare, currently zero
|
||||
uint8_t Spare2; // Spare, currently zero
|
||||
uint8_t Spare3; // Spare, currently zero
|
||||
uint8_t Style; // Set to Style code to describe type of equipment
|
||||
|
||||
uint8_t Mac[6]; // Mac Address, zero if info not available
|
||||
|
||||
uint8_t BindIp[4]; // If this unit is part of a larger or modular product, this is the IP of the
|
||||
// root device.
|
||||
uint8_t BindIndex; // Set to zero if no binding, otherwise this number represents the order of
|
||||
// bound devices. A lower number means closer to root device.
|
||||
|
||||
uint8_t Status2;
|
||||
// bit 0 = 0 Node does not support web browser
|
||||
// bit 0 = 1 Node supports web browser configuration
|
||||
|
||||
// bit 1 = 0 Node's IP address is manually configured
|
||||
// bit 1 = 1 Node's IP address is DHCP configured
|
||||
|
||||
// bit 2 = 0 Node is not DHCP capable
|
||||
// bit 2 = 1 Node is DHCP capable
|
||||
|
||||
// bit 2-7 not implemented, transmit as zero
|
||||
|
||||
uint8_t Filler[26]; // Filler bytes, currently zero.
|
||||
} artPollReply_t;
|
||||
|
||||
static artPollReply_t const artPollReply = {
|
||||
.ID = "Art-Net",
|
||||
.OpCode = OpPollReply,
|
||||
.IPAddr = {0},
|
||||
.Port = ARTNET_PORT,
|
||||
.VersionInfo = 1,
|
||||
.NetSwitch = 0,
|
||||
.SubSwitch = 0,
|
||||
.Oem = 0x0190,
|
||||
.UbeaVersion = 0,
|
||||
.Status = 0,
|
||||
.EstaMan = {0, 0},
|
||||
.ShortName = "Tropicananass",
|
||||
.LongName = "Tropicananass Artnetnode",
|
||||
.NodeReport = {0},
|
||||
.NumPortsHi = 0,
|
||||
.NumPortsLo = 1,
|
||||
.PortTypes = {0x80, 0, 0, 0},
|
||||
.GoodInput = {0},
|
||||
.GoodOutput = {0},
|
||||
.SwIn = {0},
|
||||
.SwOut = {0},
|
||||
.SwVideo = 0,
|
||||
.SwMacro = 0,
|
||||
.SwRemote = 0,
|
||||
.Spare1 = 0,
|
||||
.Spare2 = 0,
|
||||
.Spare3 = 0,
|
||||
.Style = 0,
|
||||
.Mac = {0},
|
||||
.BindIp = {0},
|
||||
.BindIndex = 0,
|
||||
.Status2 = 0b00000000,
|
||||
.Filler = {0},
|
||||
};
|
||||
|
||||
#endif // __RPI_ARTNET_PACKETS_H__
|
25
RpiLedBars/backend/src/tasks/artnet/artnet_utils.h
Normal file
25
RpiLedBars/backend/src/tasks/artnet/artnet_utils.h
Normal file
@@ -0,0 +1,25 @@
|
||||
#if !defined(__RPI_ARTNET_UTILS_H__)
|
||||
#define __RPI_ARTNET_UTILS_H__
|
||||
|
||||
#define ARTNET_PORT 0x1936
|
||||
|
||||
// Buffers
|
||||
#define ARTNET_MAX_BUFFER 530
|
||||
#define DMX_MAX_BUFFER 512
|
||||
|
||||
// Packet constants
|
||||
#define ARTNET_ID "Art-Net"
|
||||
#define ARTNET_DMX_START_LOC 18
|
||||
|
||||
// Packet confines
|
||||
#define ARTNET_SHORT_NAME_MAX_LENGTH 17
|
||||
#define ARTNET_LONG_NAME_MAX_LENGTH 63
|
||||
|
||||
// DMX settings
|
||||
#define DMX_MAX_OUTPUTS 4
|
||||
#define DMX_MS_BETWEEN_TICKS 25
|
||||
|
||||
// RDM
|
||||
#define DMX_RDM_STARTCODE 0xCC
|
||||
|
||||
#endif // __RPI_ARTNET_UTILS_H__
|
225
RpiLedBars/backend/src/tasks/cava/cava.c
Normal file
225
RpiLedBars/backend/src/tasks/cava/cava.c
Normal file
@@ -0,0 +1,225 @@
|
||||
/** @file rpi_cava.h
|
||||
* @brief This module contains continuous tasks for cava (spectrum analyzer) comunication
|
||||
*/
|
||||
|
||||
/***************************************************************************************************
|
||||
* Includes
|
||||
**************************************************************************************************/
|
||||
#include "cava.h"
|
||||
|
||||
#include <errno.h>
|
||||
#include <fcntl.h>
|
||||
#include <poll.h>
|
||||
#include <pthread.h>
|
||||
#include <signal.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
#include <wait.h>
|
||||
|
||||
#include "log.h"
|
||||
|
||||
/***************************************************************************************************
|
||||
* Preprocessor Constants and Macros
|
||||
**************************************************************************************************/
|
||||
|
||||
/**
|
||||
* Constant for Maximum char in a line (128 number of (5 char + 1 delim) + \n )
|
||||
*/
|
||||
#define MAXLINECHAR CAVA_BAR_NUMBER *(5 + 1) + 1
|
||||
|
||||
/***************************************************************************************************
|
||||
* Type and Contant Definitions
|
||||
**************************************************************************************************/
|
||||
|
||||
/***************************************************************************************************
|
||||
* Persistent Variables
|
||||
**************************************************************************************************/
|
||||
|
||||
/**
|
||||
* Cava process id
|
||||
*/
|
||||
pid_t cavaPid;
|
||||
/**
|
||||
* Boolean for interrupting the main thread loop
|
||||
*/
|
||||
bool isFifoReaderRunning = false;
|
||||
/**
|
||||
* Task thread identifier
|
||||
*/
|
||||
pthread_t fifoReader;
|
||||
|
||||
int fdCavaInput;
|
||||
char lineBuffer[MAXLINECHAR];
|
||||
uint16_t buffer[128 + 2];
|
||||
|
||||
/***************************************************************************************************
|
||||
* Internal Function Prototypes
|
||||
**************************************************************************************************/
|
||||
|
||||
/**
|
||||
* @brief This function is used to thread main execution function
|
||||
*
|
||||
* @param arg not used.
|
||||
*
|
||||
* @return NULL.
|
||||
*/
|
||||
static void *fifo_to_buffer(void *arg);
|
||||
|
||||
static int start_cava_process();
|
||||
|
||||
static void stop_cava_process();
|
||||
|
||||
/***************************************************************************************************
|
||||
* External Function Definitions
|
||||
**************************************************************************************************/
|
||||
|
||||
void cava_start() {
|
||||
isFifoReaderRunning = true;
|
||||
pthread_create(&fifoReader, NULL, fifo_to_buffer, NULL);
|
||||
}
|
||||
|
||||
void cava_stop() {
|
||||
isFifoReaderRunning = false;
|
||||
if (pthread_join(fifoReader, NULL) != 0) {
|
||||
log_error("pthread_join: %s", strerror(errno));
|
||||
}
|
||||
}
|
||||
|
||||
int cava_get_buffer(uint16_t **buffer_dst) {
|
||||
*buffer_dst = buffer;
|
||||
log_trace("%d;%d;%d;%d", buffer[0], buffer[1], buffer[2], buffer[3]);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/***************************************************************************************************
|
||||
* Internal Function Definitions
|
||||
**************************************************************************************************/
|
||||
|
||||
static void *fifo_to_buffer(void *arg) {
|
||||
struct pollfd fds[1];
|
||||
int timeoutMs;
|
||||
int ret;
|
||||
size_t valueIndex = 0, charOffset = 0;
|
||||
char strValue[6] = "0\0";
|
||||
bool hasToBeDiscarded = true;
|
||||
|
||||
if ((fdCavaInput = start_cava_process()) < 0) {
|
||||
log_error("y'a un truc qui a pas marché");
|
||||
}
|
||||
|
||||
memset(fds, 0, sizeof(fds));
|
||||
fds[0].fd = fdCavaInput;
|
||||
fds[0].events = POLLIN;
|
||||
timeoutMs = 10;
|
||||
|
||||
while (isFifoReaderRunning) {
|
||||
|
||||
if ((ret = poll(fds, 1, timeoutMs)) == 1) {
|
||||
int nread;
|
||||
nread = read(fdCavaInput, lineBuffer, 128 + 1);
|
||||
|
||||
if (nread >= 0) {
|
||||
for (size_t i = 0; i < nread; ++i) {
|
||||
char current = lineBuffer[i];
|
||||
|
||||
if (hasToBeDiscarded) {
|
||||
if (current == '\n') {
|
||||
charOffset = 0;
|
||||
strValue[charOffset] = '\0';
|
||||
valueIndex = 0;
|
||||
hasToBeDiscarded = false;
|
||||
}
|
||||
|
||||
} else {
|
||||
if ('0' <= current && current <= '9') {
|
||||
strValue[charOffset++] = current;
|
||||
} else if (current == '\n' || current == ';') {
|
||||
strValue[charOffset] = '\0';
|
||||
charOffset = 0;
|
||||
buffer[valueIndex++] = atoi(strValue);
|
||||
|
||||
if (current == '\n' || valueIndex > 129) {
|
||||
valueIndex = 0;
|
||||
|
||||
if (valueIndex > 129) {
|
||||
log_warn("Buffer overflow, \\n missed, discarding next");
|
||||
hasToBeDiscarded = true;
|
||||
}
|
||||
}
|
||||
|
||||
} else {
|
||||
log_warn("Unexpected char %d [%c]\n", current, current);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (errno != EAGAIN) {
|
||||
log_error("read: %s", strerror(errno));
|
||||
}
|
||||
}
|
||||
} else if (ret < 0) {
|
||||
log_error("polling %d: %s\n", fdCavaInput, strerror(errno));
|
||||
}
|
||||
}
|
||||
|
||||
stop_cava_process();
|
||||
close(fdCavaInput);
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static int start_cava_process() {
|
||||
int fdCavaPipe[2];
|
||||
|
||||
if (pipe(fdCavaPipe) < 0) {
|
||||
log_error("Cava pipe failure: %s", strerror(errno));
|
||||
return -EXIT_FAILURE;
|
||||
}
|
||||
|
||||
if ((cavaPid = fork()) < 0) {
|
||||
log_error("Cava fork failure: %s", strerror(errno));
|
||||
exit(-EXIT_FAILURE);
|
||||
}
|
||||
|
||||
if (cavaPid == 0) {
|
||||
/* Child process*/
|
||||
// int fdLogOut;
|
||||
char *args[] = {"cava", "-p", "/home/pi/LedBars/RpiLedBars/cava_config", NULL};
|
||||
|
||||
/* Close reading end of the pipe */
|
||||
close(fdCavaPipe[0]);
|
||||
/* Dup writing end of the pipe in place of stdout */
|
||||
dup2(fdCavaPipe[1], STDOUT_FILENO);
|
||||
/* Close writing end of the pipe */
|
||||
close(fdCavaPipe[1]);
|
||||
|
||||
/* Open / create a log file for cava */
|
||||
// fdLogOut = open("/dev/null", O_WRONLY, NULL);
|
||||
/* Dup file in place of stderr */
|
||||
// dup2(fdLogOut, STDERR_FILENO);
|
||||
|
||||
execvp(args[0], args);
|
||||
log_error("Cava execvp failure or return: %s", strerror(errno));
|
||||
exit(-EXIT_FAILURE);
|
||||
} else {
|
||||
int flags;
|
||||
/* Close writing end of the pipe */
|
||||
close(fdCavaPipe[1]);
|
||||
/* Set reading end of the pipe non-blocking */
|
||||
flags = fcntl(fdCavaPipe[0], F_GETFL, 0);
|
||||
fcntl(fdCavaPipe[0], F_SETFL, flags | O_NONBLOCK);
|
||||
/* Return reading end of the pipe */
|
||||
return fdCavaPipe[0];
|
||||
}
|
||||
|
||||
/* Unreachable */
|
||||
return -1;
|
||||
}
|
||||
|
||||
static void stop_cava_process() {
|
||||
kill(cavaPid, SIGTERM);
|
||||
waitpid(0, NULL, WNOHANG);
|
||||
}
|
50
RpiLedBars/backend/src/tasks/includes/artnet.h
Normal file
50
RpiLedBars/backend/src/tasks/includes/artnet.h
Normal file
@@ -0,0 +1,50 @@
|
||||
/** @file artnet.h
|
||||
* @brief This module contains continuous tasks for artnet node communications.
|
||||
*
|
||||
* This is the header file for the definition of services to allow managing thread acting as artnet
|
||||
* node.
|
||||
*/
|
||||
#if !defined(__ARTNET_H__)
|
||||
#define __ARTNET_H__
|
||||
|
||||
/***************************************************************************************************
|
||||
* Includes
|
||||
**************************************************************************************************/
|
||||
#include <stdint.h>
|
||||
|
||||
/***************************************************************************************************
|
||||
* Preprocessor Constants and Macros
|
||||
**************************************************************************************************/
|
||||
|
||||
/***************************************************************************************************
|
||||
* Type and Contant Definitions
|
||||
**************************************************************************************************/
|
||||
|
||||
/***************************************************************************************************
|
||||
* Global Variables
|
||||
**************************************************************************************************/
|
||||
|
||||
/***************************************************************************************************
|
||||
* External Function Prototypes
|
||||
**************************************************************************************************/
|
||||
|
||||
/**
|
||||
* @brief Start artnet node module
|
||||
**/
|
||||
void artnet_start();
|
||||
|
||||
/**
|
||||
* @brief Stop artnet node module
|
||||
*/
|
||||
void artnet_stop();
|
||||
|
||||
/**
|
||||
* @brief Get last DMX data received for the specified universe
|
||||
*
|
||||
* @param[in] univerve The universe to get data from
|
||||
*
|
||||
* @param[out] dmxData The pointer to the DMX data array
|
||||
*/
|
||||
int artnet_get_dmx_data(unsigned int univerve, uint8_t *dmxData[]);
|
||||
|
||||
#endif /* __ARTNET_H__ */
|
37
RpiLedBars/backend/src/tasks/includes/cava.h
Normal file
37
RpiLedBars/backend/src/tasks/includes/cava.h
Normal file
@@ -0,0 +1,37 @@
|
||||
/** @file cava.h
|
||||
* @brief This module contains continuous tasks for cava (spectrum analyzer) comunication
|
||||
*/
|
||||
#if !defined(__CAVA_H__)
|
||||
#define __CAVA_H__
|
||||
|
||||
/***************************************************************************************************
|
||||
* Includes
|
||||
**************************************************************************************************/
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
/***************************************************************************************************
|
||||
* Preprocessor Constants and Macros
|
||||
**************************************************************************************************/
|
||||
|
||||
#define CAVA_BAR_NUMBER 128
|
||||
|
||||
/***************************************************************************************************
|
||||
* Type and Contant Definitions
|
||||
**************************************************************************************************/
|
||||
|
||||
/***************************************************************************************************
|
||||
* Global Variables
|
||||
**************************************************************************************************/
|
||||
|
||||
/***************************************************************************************************
|
||||
* External Function Prototypes
|
||||
**************************************************************************************************/
|
||||
|
||||
int cava_get_buffer(uint16_t **buffer_dst);
|
||||
|
||||
void cava_start();
|
||||
|
||||
void cava_stop();
|
||||
|
||||
#endif /* __CAVA_H__ */
|
34
RpiLedBars/backend/src/tasks/includes/selector.h
Normal file
34
RpiLedBars/backend/src/tasks/includes/selector.h
Normal file
@@ -0,0 +1,34 @@
|
||||
/** @file selector.h
|
||||
* @brief This module contains continuous tasks for cava (spectrum analyzer) comunication
|
||||
*/
|
||||
#if !defined(__SELECTOR_TASK_H__)
|
||||
#define __SELECTOR_TASK_H__
|
||||
|
||||
/***************************************************************************************************
|
||||
* Includes
|
||||
**************************************************************************************************/
|
||||
|
||||
/***************************************************************************************************
|
||||
* Preprocessor Constants and Macros
|
||||
**************************************************************************************************/
|
||||
|
||||
/***************************************************************************************************
|
||||
* Type and Contant Definitions
|
||||
**************************************************************************************************/
|
||||
|
||||
/***************************************************************************************************
|
||||
* Global Variables
|
||||
**************************************************************************************************/
|
||||
|
||||
/***************************************************************************************************
|
||||
* External Function Prototypes
|
||||
**************************************************************************************************/
|
||||
|
||||
/**
|
||||
* @brief Start selector listener module
|
||||
**/
|
||||
void selector_start();
|
||||
|
||||
void selector_stop();
|
||||
|
||||
#endif /* __SELECTOR_TASK_H__ */
|
43
RpiLedBars/backend/src/tasks/includes/websocket.h
Normal file
43
RpiLedBars/backend/src/tasks/includes/websocket.h
Normal file
@@ -0,0 +1,43 @@
|
||||
/** @file websocket.h
|
||||
* @brief This module
|
||||
*/
|
||||
|
||||
#if !defined(__WEBSOCKET_H__)
|
||||
#define __WEBSOCKET_H__
|
||||
|
||||
/***************************************************************************************************
|
||||
* Includes
|
||||
**************************************************************************************************/
|
||||
|
||||
/***************************************************************************************************
|
||||
* Preprocessor Constants and Macros
|
||||
**************************************************************************************************/
|
||||
|
||||
/***************************************************************************************************
|
||||
* Type and Contant Definitions
|
||||
**************************************************************************************************/
|
||||
|
||||
/***************************************************************************************************
|
||||
* Global Variables
|
||||
**************************************************************************************************/
|
||||
|
||||
/***************************************************************************************************
|
||||
* External Function Prototypes
|
||||
**************************************************************************************************/
|
||||
|
||||
/**
|
||||
* @brief Start websocket module
|
||||
**/
|
||||
void websocket_start();
|
||||
|
||||
/**
|
||||
* @brief Start websocket module
|
||||
**/
|
||||
void websocket_stop();
|
||||
|
||||
/**
|
||||
* @brief Send mode to websocket client
|
||||
**/
|
||||
void websocket_send_mode(int mode);
|
||||
|
||||
#endif /* __WEBSOCKET_H__ */
|
87
RpiLedBars/backend/src/tasks/selector/selector.c
Normal file
87
RpiLedBars/backend/src/tasks/selector/selector.c
Normal file
@@ -0,0 +1,87 @@
|
||||
/** @file selector.c
|
||||
* @brief This module contains continuous tasks for cava (spectrum analyzer) comunication
|
||||
*/
|
||||
|
||||
/***************************************************************************************************
|
||||
* Includes
|
||||
**************************************************************************************************/
|
||||
#include "selector.h"
|
||||
|
||||
#include <errno.h>
|
||||
#include <pthread.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include "../../drivers/selector/rpi_selector.h"
|
||||
#include "../../rpi_param.h"
|
||||
|
||||
#include "log.h"
|
||||
|
||||
/***************************************************************************************************
|
||||
* Preprocessor Constants and Macros
|
||||
**************************************************************************************************/
|
||||
|
||||
#define LISTENER_INTERVAL 500000
|
||||
|
||||
/***************************************************************************************************
|
||||
* Type and Contant Definitions
|
||||
**************************************************************************************************/
|
||||
|
||||
/***************************************************************************************************
|
||||
* Persistent Variables
|
||||
**************************************************************************************************/
|
||||
|
||||
pthread_t selectorListener;
|
||||
/**
|
||||
* Boolean for interrupting the main thread loop
|
||||
*/
|
||||
bool isSelectorListenerRunning = false;
|
||||
|
||||
/***************************************************************************************************
|
||||
* Internal Function Prototypes
|
||||
**************************************************************************************************/
|
||||
|
||||
/**
|
||||
* @brief This function is used to thread main execution function
|
||||
*
|
||||
* @param arg not used.
|
||||
*
|
||||
* @return NULL.
|
||||
*/
|
||||
static void *listen_to_selector(void *arg);
|
||||
|
||||
/***************************************************************************************************
|
||||
* External Function Definitions
|
||||
**************************************************************************************************/
|
||||
|
||||
void selector_start() {
|
||||
isSelectorListenerRunning = true;
|
||||
pthread_create(&selectorListener, NULL, listen_to_selector, NULL);
|
||||
}
|
||||
|
||||
void selector_stop() {
|
||||
isSelectorListenerRunning = false;
|
||||
if (pthread_join(selectorListener, NULL) != 0) {
|
||||
log_error("pthread_join: %s", strerror(errno));
|
||||
}
|
||||
}
|
||||
|
||||
/***************************************************************************************************
|
||||
* Internal Function Definitions
|
||||
**************************************************************************************************/
|
||||
|
||||
static void *listen_to_selector(void *arg) {
|
||||
int previousPosition;
|
||||
selector_setup();
|
||||
|
||||
while (isSelectorListenerRunning) {
|
||||
int newPosition = selector_get_position();
|
||||
if (newPosition != -1 && newPosition != previousPosition) {
|
||||
set_mode(newPosition);
|
||||
previousPosition = newPosition;
|
||||
}
|
||||
usleep(LISTENER_INTERVAL);
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
194
RpiLedBars/backend/src/tasks/websocket/websocket.c
Normal file
194
RpiLedBars/backend/src/tasks/websocket/websocket.c
Normal file
@@ -0,0 +1,194 @@
|
||||
/** @file websocket.c
|
||||
* @brief This module
|
||||
*/
|
||||
|
||||
/***************************************************************************************************
|
||||
* Includes
|
||||
**************************************************************************************************/
|
||||
|
||||
#include "websocket.h"
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "log.h"
|
||||
#include "ws.h"
|
||||
|
||||
#include "../../rpi_param.h"
|
||||
|
||||
/***************************************************************************************************
|
||||
* Preprocessor Constants and Macros
|
||||
**************************************************************************************************/
|
||||
|
||||
#define MAX_TOKEN 10
|
||||
|
||||
/***************************************************************************************************
|
||||
* Type and Contant Definitions
|
||||
**************************************************************************************************/
|
||||
|
||||
/***************************************************************************************************
|
||||
* Persistent Variables
|
||||
**************************************************************************************************/
|
||||
|
||||
int client_fd = -1;
|
||||
|
||||
/***************************************************************************************************
|
||||
* Internal Function Prototypes
|
||||
**************************************************************************************************/
|
||||
/**
|
||||
* @brief Called when a client connects to the server.
|
||||
*
|
||||
* @param fd File Descriptor belonging to the client. The @p fd parameter
|
||||
* is used in order to send messages and retrieve informations
|
||||
* about the client.
|
||||
*/
|
||||
void onopen(int fd);
|
||||
|
||||
/**
|
||||
* @brief Called when a client disconnects to the server.
|
||||
*
|
||||
* @param fd File Descriptor belonging to the client. The @p fd parameter
|
||||
* is used in order to send messages and retrieve informations
|
||||
* about the client.
|
||||
*/
|
||||
void onclose(int fd);
|
||||
|
||||
/**
|
||||
* @brief Called when a client connects to the server.
|
||||
*
|
||||
* @param fd File Descriptor belonging to the client. The
|
||||
* @p fd parameter is used in order to send messages and
|
||||
* retrieve informations about the client.
|
||||
*
|
||||
* @param msg Received message, this message can be a text
|
||||
* or binary message.
|
||||
*
|
||||
* @param size Message size (in bytes).
|
||||
*
|
||||
* @param type Message type.
|
||||
*/
|
||||
void onmessage(int fd, const unsigned char *msg, uint64_t size, int type);
|
||||
|
||||
void mode_command_handler(char *payload);
|
||||
|
||||
void pattern_command_handler(char *payload);
|
||||
|
||||
void color_command_handler(char *payload);
|
||||
|
||||
/***************************************************************************************************
|
||||
* External Function Definitions
|
||||
**************************************************************************************************/
|
||||
|
||||
void websocket_start() {
|
||||
struct ws_events evs;
|
||||
evs.onopen = &onopen;
|
||||
evs.onclose = &onclose;
|
||||
evs.onmessage = &onmessage;
|
||||
ws_socket(&evs, 8080, 1);
|
||||
}
|
||||
|
||||
void websocket_stop() {}
|
||||
|
||||
void websocket_send_mode(int mode) {
|
||||
char msg[] = "m:0";
|
||||
msg[2] = '0' + mode;
|
||||
if (client_fd != -1) {
|
||||
log_debug("Sending mode \"%s\"", msg);
|
||||
ws_sendframe_txt(client_fd, msg, true);
|
||||
}
|
||||
}
|
||||
|
||||
/***************************************************************************************************
|
||||
* Internal Function Definitions
|
||||
**************************************************************************************************/
|
||||
|
||||
void onopen(int fd) {
|
||||
char *cli;
|
||||
cli = ws_getaddress(fd);
|
||||
log_debug("Connection opened, client: %d | addr: %s", fd, cli);
|
||||
free(cli);
|
||||
client_fd = fd;
|
||||
}
|
||||
|
||||
void onclose(int fd) {
|
||||
char *cli;
|
||||
cli = ws_getaddress(fd);
|
||||
log_debug("Connection closed, client: %d | addr: %s", fd, cli);
|
||||
free(cli);
|
||||
client_fd = -1;
|
||||
}
|
||||
|
||||
void onmessage(int fd, const unsigned char *msg, uint64_t size, int type) {
|
||||
char msgStr[size + 1];
|
||||
char *command, *payload;
|
||||
char *cli = ws_getaddress(fd);
|
||||
log_trace("Received message: %s (size: %" PRId64 ", type: %d), from: %s/%d", msg, size, type, cli,
|
||||
fd);
|
||||
|
||||
strcpy(msgStr, (char *)msg);
|
||||
command = strtok(msgStr, ":");
|
||||
if (command != NULL) {
|
||||
payload = strtok(NULL, ":");
|
||||
if (payload != NULL && strlen(command) == 1) {
|
||||
switch (command[0]) {
|
||||
case 'm':
|
||||
mode_command_handler(payload);
|
||||
break;
|
||||
case 'p':
|
||||
pattern_command_handler(payload);
|
||||
break;
|
||||
case 'c':
|
||||
color_command_handler(payload);
|
||||
break;
|
||||
|
||||
default:
|
||||
log_warn("Unkown command in \"%s\"", msgStr);
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
log_warn("Empty payload in \"%s\"", msgStr);
|
||||
}
|
||||
} else {
|
||||
log_warn("No command found in \"%s\"", msgStr);
|
||||
}
|
||||
|
||||
free(cli);
|
||||
}
|
||||
|
||||
void mode_command_handler(char *payload) {
|
||||
int newMode = atoi(payload);
|
||||
if (0 <= newMode && newMode <= 3) {
|
||||
set_mode(newMode);
|
||||
} else {
|
||||
log_warn("Unknown mode : %s", payload);
|
||||
}
|
||||
}
|
||||
|
||||
void pattern_command_handler(char *payload) {
|
||||
int newPattern = atoi(payload);
|
||||
if (0 <= newPattern && newPattern <= 3) {
|
||||
set_pattern(newPattern);
|
||||
} else {
|
||||
log_warn("Unknown pattern : %s", payload);
|
||||
}
|
||||
}
|
||||
|
||||
void color_command_handler(char *payload) {
|
||||
int i = 0;
|
||||
char *tokenArray[MAX_TOKEN] = {NULL};
|
||||
tokenArray[i] = strtok(payload, ",");
|
||||
while (tokenArray[i] && i < MAX_TOKEN - 1) {
|
||||
tokenArray[++i] = strtok(NULL, ",");
|
||||
}
|
||||
log_debug("tokens[%d]", i);
|
||||
for (size_t n = 0; n < i; ++n) {
|
||||
log_debug(" - %s", tokenArray[n]);
|
||||
}
|
||||
if (atoi(tokenArray[0]) == 0) {
|
||||
set_hue_base(LED_NCHANS, atoi(tokenArray[1]) * INT8_MAX / HUE_MAX);
|
||||
set_saturation(LED_NCHANS, atoi(tokenArray[2]) * INT8_MAX / 100);
|
||||
set_luminosity(LED_NCHANS, atoi(tokenArray[3]) * INT8_MAX / 100);
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user