From a724fd1535ab045903875db685c73a3274f53912 Mon Sep 17 00:00:00 2001 From: redPanther Date: Wed, 14 Dec 2016 22:45:00 +0100 Subject: [PATCH] detect config is readonly, adalight and other stuff (#333) * update lightberry sketches update compilehowwto (windows disclaimer) some refactoring in main cmakelists + preparation for windows compile tune ada driver, set delayAfterConnect default to 1.5s because some arduino (e.g. mega r3) needs this set priority min/max for grabber/network services - prevent colliding prios between webui/background stuff and grabbers/net services * add check if config is writable. TODO do something usefull in webui * fix indention error * fix typo * fix webui can't write led config * typo * fix cmakelists * change methode of detecting linux --- CMakeLists.txt | 27 +- CompileHowto.md | 3 + assets/firmware/arduino/adalight/adalight.ino | 10 +- .../LightberryHDUSBAPA1021.1.ino | 273 ++++++++++++++++++ .../LightberryHDUSB1.1.ino} | 219 +++++++------- assets/firmware/arduino/tpm2/tpm2.ino | 133 +++++++++ assets/webconfig/content/leds.html | 2 +- assets/webconfig/js/content_index.js | 6 + assets/webconfig/js/content_leds.js | 5 +- effects/candle.py | 2 +- include/hyperion/Hyperion.h | 11 +- libsrc/hyperion/Hyperion.cpp | 16 +- libsrc/hyperion/hyperion.schema.json | 12 +- libsrc/jsonserver/JsonClientConnection.cpp | 1 + libsrc/leddevice/LedDeviceAdalight.cpp | 30 +- libsrc/leddevice/LedDeviceAdalight.h | 1 + libsrc/leddevice/LedDevicePhilipsHue.cpp | 4 +- libsrc/leddevice/LedDeviceTpm2.cpp | 6 +- libsrc/leddevice/schemas/schema-adalight.json | 2 +- src/hyperiond/hyperiond.cpp | 6 +- 20 files changed, 609 insertions(+), 160 deletions(-) create mode 100644 assets/firmware/arduino/adalight_lightberry_apa102/LightberryHDUSBAPA1021.1.ino rename assets/firmware/arduino/{adalightapa102/LightberryHDUSBAPA1021.1.ino => adalight_lightberry_ws2801/LightberryHDUSB1.1.ino} (60%) create mode 100644 assets/firmware/arduino/tpm2/tpm2.ino diff --git a/CMakeLists.txt b/CMakeLists.txt index b969f31f..fadeda3d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -19,21 +19,32 @@ SET ( HYPERION_VERSION_PATCH 0 ) SET ( DEFAULT_AMLOGIC OFF ) SET ( DEFAULT_DISPMANX OFF ) -SET ( DEFAULT_FB ON ) SET ( DEFAULT_OSX OFF ) SET ( DEFAULT_X11 OFF ) -SET ( DEFAULT_SPIDEV ON ) SET ( DEFAULT_WS281XPWM OFF ) -SET ( DEFAULT_V4L2 ON ) SET ( DEFAULT_USE_SHARED_AVAHI_LIBS OFF ) SET ( DEFAULT_USE_AVAHI_LIBS ON ) SET ( DEFAULT_USE_SYSTEM_PROTO_LIBS OFF ) SET ( DEFAULT_TESTS OFF ) +IF ( ${CMAKE_SYSTEM} MATCHES "Linux" ) + SET ( DEFAULT_V4L2 ON ) + SET ( DEFAULT_SPIDEV ON ) + SET ( DEFAULT_FB ON ) +ELSE() + SET ( DEFAULT_V4L2 OFF ) + SET ( DEFAULT_SPIDEV OFF ) + SET ( DEFAULT_FB OFF ) +ENDIF() + if (APPLE) SET( PLATFORM "osx") endif() +if (WIN32) + SET( PLATFORM "windows") +endif() + if ( NOT DEFINED PLATFORM ) if ( "${CMAKE_SYSTEM_PROCESSOR}" MATCHES "x86" ) SET( PLATFORM "x86") @@ -56,16 +67,12 @@ endif() message( STATUS "PLATFORM: ${PLATFORM}") + if ( "${PLATFORM}" MATCHES "osx" ) SET(CMAKE_PREFIX_PATH ${CMAKE_PREFIX_PATH} "/usr/local/opt/qt5" CACHE STRING "path to your QT5 files" ) include_directories("/opt/X11/include/") SET ( DEFAULT_OSX ON ) - SET ( DEFAULT_V4L2 OFF ) - SET ( DEFAULT_SPIDEV OFF ) - SET ( DEFAULT_FB OFF ) - SET ( DEFAULT_WS281XPWM OFF ) - SET ( DEFAULT_USE_AVAHI_LIBS OFF ) - SET ( DEFAULT_USE_SHARED_AVAHI_LIBS OFF ) + SET ( DEFAULT_USE_AVAHI_LIBS OFF ) elseif ( "${PLATFORM}" STREQUAL "rpi" ) SET ( DEFAULT_DISPMANX ON ) SET ( DEFAULT_WS281XPWM ON ) @@ -83,6 +90,8 @@ elseif ( "${PLATFORM}" MATCHES "x86" ) endif() elseif ( "${PLATFORM}" STREQUAL "imx6" ) SET ( DEFAULT_FB ON ) +elseif ( "${PLATFORM}" STREQUAL "windows" ) + MESSAGE( WARNING "Hyperion is not developed nor tested on MS Windows.") endif() # enable tests for -dev builds diff --git a/CompileHowto.md b/CompileHowto.md index 94a4d76b..14774cbf 100644 --- a/CompileHowto.md +++ b/CompileHowto.md @@ -7,6 +7,9 @@ sudo apt-get update sudo apt-get install git cmake build-essential qtbase5-dev libqt5serialport5-dev libusb-1.0-0-dev python-dev libxrender-dev libavahi-core-dev libavahi-compat-libdnssd-dev ``` +**ATTENTION Win10LinuxSubsystem** we do not (/we can't) support using hyperion in linux subsystem of MS Windows 10, albeit some users tested it with success. Keep in mind to disable +all linux specific led and grabber hardware via cmake. Because we use QT as framework in hyperion, serialport leds and network driven devices could work. + ## RPI Only when you build on the rapberry pi and include the dispmanx grabber (which is the default) you also need the firmware including headers installed. This downloads the firmware from the raspberrypi github diff --git a/assets/firmware/arduino/adalight/adalight.ino b/assets/firmware/arduino/adalight/adalight.ino index c9e6c1b0..d7ce571c 100644 --- a/assets/firmware/arduino/adalight/adalight.ino +++ b/assets/firmware/arduino/adalight/adalight.ino @@ -9,6 +9,8 @@ set following values to your needs **************************************/ +#define INITAL_LED_TEST_ENABLED true + // Number of leds in your strip. set to "1" and ANALOG_OUTPUT_ENABLED to "true" to activate analog only #define NUM_LEDS 100 @@ -92,7 +94,7 @@ void showColor(const CRGB& led) { // switch of digital and analog leds void switchOff() { - #if ANALOG_ONLY == false + #if NUM_LEDS > 1 || ANALOG_OUTPUT_ENABLED == false memset(leds, 0, NUM_LEDS * sizeof(struct CRGB)); FastLED.show(); #endif @@ -145,11 +147,13 @@ void setup() { FastLED.setDither ( DITHER_MODE ); // initial RGB flash + #if INITAL_LED_TEST_ENABLED == true showColor(CRGB(255, 0, 0)); delay(400); showColor(CRGB(0, 255, 0)); delay(400); showColor(CRGB(0, 0, 255)); delay(400); + #endif showColor(CRGB(0, 0, 0)); - + Serial.begin(serialRate); Serial.print("Ada\n"); // Send "Magic Word" string to host @@ -159,7 +163,7 @@ void setup() { // loop() is avoided as even that small bit of function overhead // has a measurable impact on this code's overall throughput. - while (true) { + for(;;) { // wait for first byte of Magic Word for (i = 0; i < sizeof prefix; ++i) { // If next byte is not in Magic Word, the start over diff --git a/assets/firmware/arduino/adalight_lightberry_apa102/LightberryHDUSBAPA1021.1.ino b/assets/firmware/arduino/adalight_lightberry_apa102/LightberryHDUSBAPA1021.1.ino new file mode 100644 index 00000000..000a8815 --- /dev/null +++ b/assets/firmware/arduino/adalight_lightberry_apa102/LightberryHDUSBAPA1021.1.ino @@ -0,0 +1,273 @@ +// Arduino "bridge" code between host computer and WS2801-based digital +// RGB LED pixels (e.g. Adafruit product ID #322). Intended for use +// with USB-native boards such as Teensy or Adafruit 32u4 Breakout; +// works on normal serial Arduinos, but throughput is severely limited. +// LED data is streamed, not buffered, making this suitable for larger +// installations (e.g. video wall, etc.) than could otherwise be held +// in the Arduino's limited RAM. + +// Some effort is put into avoiding buffer underruns (where the output +// side becomes starved of data). The WS2801 latch protocol, being +// delay-based, could be inadvertently triggered if the USB bus or CPU +// is swamped with other tasks. This code buffers incoming serial data +// and introduces intentional pauses if there's a threat of the buffer +// draining prematurely. The cost of this complexity is somewhat +// reduced throughput, the gain is that most visual glitches are +// avoided (though ultimately a function of the load on the USB bus and +// host CPU, and out of our control). + +// LED data and clock lines are connected to the Arduino's SPI output. +// On traditional Arduino boards, SPI data out is digital pin 11 and +// clock is digital pin 13. On both Teensy and the 32u4 Breakout, +// data out is pin B2, clock is B1. LEDs should be externally +// powered -- trying to run any more than just a few off the Arduino's +// 5V line is generally a Bad Idea. LED ground should also be +// connected to Arduino ground. + +// -------------------------------------------------------------------- +// This file is part of Adalight. + +// Adalight is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as +// published by the Free Software Foundation, either version 3 of +// the License, or (at your option) any later version. + +// Adalight is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. + +// You should have received a copy of the GNU Lesser General Public +// License along with Adalight. If not, see +// . +// -------------------------------------------------------------------- + +#include + +// LED pin for Adafruit 32u4 Breakout Board: +//#define LED_DDR DDRE +//#define LED_PORT PORTE +//#define LED_PIN _BV(PORTE6) +// LED pin for Teensy: +//#define LED_DDR DDRD +//#define LED_PORT PORTD +//#define LED_PIN _BV(PORTD6) +// LED pin for Arduino: +#define LED_DDR DDRB +#define LED_PORT PORTB +#define LED_PIN _BV(PORTB5) + +// A 'magic word' (along with LED count & checksum) precedes each block +// of LED data; this assists the microcontroller in syncing up with the +// host-side software and properly issuing the latch (host I/O is +// likely buffered, making usleep() unreliable for latch). You may see +// an initial glitchy frame or two until the two come into alignment. +// The magic word can be whatever sequence you like, but each character +// should be unique, and frequent pixel values like 0 and 255 are +// avoided -- fewer false positives. The host software will need to +// generate a compatible header: immediately following the magic word +// are three bytes: a 16-bit count of the number of LEDs (high byte +// first) followed by a simple checksum value (high byte XOR low byte +// XOR 0x55). LED data follows, 3 bytes per LED, in order R, G, B, +// where 0 = off and 255 = max brightness. + +static const uint8_t magic[] = {'A','d','a'}; +#define MAGICSIZE sizeof(magic) +#define HEADERSIZE (MAGICSIZE + 3) + +#define MODE_HEADER 0 +#define MODE_HOLD 1 +#define MODE_DATA 2 + +#define DATA_LED A5 +#define SPI_LED A3 + +// If no serial data is received for a while, the LEDs are shut off +// automatically. This avoids the annoying "stuck pixel" look when +// quitting LED display programs on the host computer. +static const unsigned long serialTimeout = 15000; // 15 seconds + +void setup() +{ + // Dirty trick: the circular buffer for serial data is 256 bytes, + // and the "in" and "out" indices are unsigned 8-bit types -- this + // much simplifies the cases where in/out need to "wrap around" the + // beginning/end of the buffer. Otherwise there'd be a ton of bit- + // masking and/or conditional code every time one of these indices + // needs to change, slowing things down tremendously. + uint8_t + buffer[256], + indexIn = 0, + indexOut = 0, + mode = MODE_HEADER, + hi, lo, chk, i, spiFlag; + int16_t + bytesBuffered = 0, + hold = 0, + c; + int32_t + bytesRemaining; + unsigned long + startTime, + lastByteTime, + lastAckTime, + t; + bool + data_in_led = false, + spi_out_led = false; + + LED_DDR |= LED_PIN; // Enable output for LED + LED_PORT &= ~LED_PIN; // LED off + pinMode(DATA_LED, OUTPUT); //data in led + pinMode(SPI_LED, OUTPUT); //data out led + + delay(5000); + + Serial.begin(115200); // Teensy/32u4 disregards baud rate; is OK! + + SPI.begin(); + SPI.setBitOrder(MSBFIRST); + SPI.setDataMode(SPI_MODE0); + SPI.setClockDivider(SPI_CLOCK_DIV16); // 1 MHz max, else flicker + + // Issue test pattern to LEDs on startup. This helps verify that + // wiring between the Arduino and LEDs is correct. Not knowing the + // actual number of LEDs connected, this sets all of them (well, up + // to the first 25,000, so as not to be TOO time consuming) to red, + // green, blue, then off. Once you're confident everything is working + // end-to-end, it's OK to comment this out and reprogram the Arduino. + uint8_t testcolor[] = { 0, 0, 0, 255, 0, 0 }; + for(int i=0; i<5; i++){ + for(SPDR = 0x00; !(SPSR & _BV(SPIF)); ); + } + for(char n=3; n>=0; n--) { + for(c=0; c<25000; c++) { + for(i=0; i<3; i++) { + for(SPDR = testcolor[n + i]; !(SPSR & _BV(SPIF)); ); + } + for(i=0; i<1; i++) { + for(SPDR = 0xFF; !(SPSR & _BV(SPIF)); ); + } + } + for(int i=0; i<16; i++){ + for(SPDR = 0x00; !(SPSR & _BV(SPIF)); ); + } + delay(1); // One millisecond pause = latch + digitalWrite(SPI_LED, spi_out_led = !spi_out_led); + } + + Serial.print("Ada\n"); // Send ACK string to host + + startTime = micros(); + lastByteTime = lastAckTime = millis(); + + // loop() is avoided as even that small bit of function overhead + // has a measurable impact on this code's overall throughput. + + for(;;) { + digitalWrite(DATA_LED, LOW); + digitalWrite(SPI_LED, LOW); + // Implementation is a simple finite-state machine. + // Regardless of mode, check for serial input each time: + t = millis(); + if((bytesBuffered < 256) && ((c = Serial.read()) >= 0)) { + buffer[indexIn++] = c; + bytesBuffered++; + lastByteTime = lastAckTime = t; // Reset timeout counters + } else { + // No data received. If this persists, send an ACK packet + // to host once every second to alert it to our presence. + if((t - lastAckTime) > 1000) { + Serial.print("Ada\n"); // Send ACK string to host + lastAckTime = t; // Reset counter + } + // If no data received for an extended time, turn off all LEDs. + if((t - lastByteTime) > serialTimeout) { + for(c=0; c<25000; c++) { + for(i=0; i<3; i++) { + for(SPDR = 0x00; !(SPSR & _BV(SPIF)); ); + } + for(i=0; i<1; i++) { + for(SPDR = 0xFF; !(SPSR & _BV(SPIF)); ); + } + } + delay(1); // One millisecond pause = latch + lastByteTime = t; // Reset counter + } + } + + switch(mode) { + + case MODE_HEADER: + + // In header-seeking mode. Is there enough data to check? + if(bytesBuffered >= HEADERSIZE) { + // Indeed. Check for a 'magic word' match. + for(i=0; (i 0) and multiply by 3 for R,G,B. + bytesRemaining = 4L * (256L * (long)hi + (long)lo) +4L + (256L *(long)hi + (long)lo +15)/16; + bytesBuffered -= 3; + spiFlag = 0; // No data out yet + mode = MODE_HOLD; // Proceed to latch wait mode + digitalWrite(DATA_LED, data_in_led = !data_in_led); + } else { + // Checksum didn't match; search resumes after magic word. + indexOut -= 3; // Rewind + } + } // else no header match. Resume at first mismatched byte. + bytesBuffered -= i; + } + break; + + case MODE_HOLD: + + // Ostensibly "waiting for the latch from the prior frame + // to complete" mode, but may also revert to this mode when + // underrun prevention necessitates a delay. + + if((micros() - startTime) < hold) break; // Still holding; keep buffering + + // Latch/delay complete. Advance to data-issuing mode... + LED_PORT &= ~LED_PIN; // LED off + mode = MODE_DATA; // ...and fall through (no break): + + case MODE_DATA: + digitalWrite(SPI_LED, spi_out_led = !spi_out_led); + while(spiFlag && !(SPSR & _BV(SPIF))); // Wait for prior byte + if(bytesRemaining > 0) { + if(bytesBuffered > 0) { + SPDR = buffer[indexOut++]; // Issue next byte + bytesBuffered--; + bytesRemaining--; + spiFlag = 1; + } + // If serial buffer is threatening to underrun, start + // introducing progressively longer pauses to allow more + // data to arrive (up to a point). + // if((bytesBuffered < 32) && (bytesRemaining > bytesBuffered)) { + // startTime = micros(); + // hold = 100 + (32 - bytesBuffered) * 10; + // mode = MODE_HOLD; +//} + } else { + // End of data -- issue latch: + startTime = micros(); + hold = 1000; // Latch duration = 1000 uS + LED_PORT |= LED_PIN; // LED on + mode = MODE_HEADER; // Begin next header search + } + } // end switch + } // end for(;;) +} + +void loop() +{ + // Not used. See note in setup() function. +} diff --git a/assets/firmware/arduino/adalightapa102/LightberryHDUSBAPA1021.1.ino b/assets/firmware/arduino/adalight_lightberry_ws2801/LightberryHDUSB1.1.ino similarity index 60% rename from assets/firmware/arduino/adalightapa102/LightberryHDUSBAPA1021.1.ino rename to assets/firmware/arduino/adalight_lightberry_ws2801/LightberryHDUSB1.1.ino index 603599c9..9dfb1da2 100644 --- a/assets/firmware/arduino/adalightapa102/LightberryHDUSBAPA1021.1.ino +++ b/assets/firmware/arduino/adalight_lightberry_ws2801/LightberryHDUSB1.1.ino @@ -71,7 +71,7 @@ // XOR 0x55). LED data follows, 3 bytes per LED, in order R, G, B, // where 0 = off and 255 = max brightness. -static const uint8_t magic[] = {'A', 'd', 'a'}; +static const uint8_t magic[] = {'A','d','a'}; #define MAGICSIZE sizeof(magic) #define HEADERSIZE (MAGICSIZE + 3) @@ -96,37 +96,39 @@ void setup() // masking and/or conditional code every time one of these indices // needs to change, slowing things down tremendously. uint8_t - buffer[256], - indexIn = 0, - indexOut = 0, - mode = MODE_HEADER, - hi, lo, chk, i, spiFlag; + buffer[256], + indexIn = 0, + indexOut = 0, + mode = MODE_HEADER, + hi, lo, chk, i, spiFlag; int16_t - bytesBuffered = 0, - hold = 0, - c; + bytesBuffered = 0, + hold = 0, + c; int32_t - bytesRemaining; + bytesRemaining; unsigned long - startTime, - lastByteTime, - lastAckTime, - t; - bool - data_in_led = false, - spi_out_led = false; - + startTime, + lastByteTime, + lastAckTime, + t; + bool + data_in_led = false, + spi_out_led = false; + LED_DDR |= LED_PIN; // Enable output for LED LED_PORT &= ~LED_PIN; // LED off pinMode(DATA_LED, OUTPUT); //data in led pinMode(SPI_LED, OUTPUT); //data out led + delay(5000); + Serial.begin(115200); // Teensy/32u4 disregards baud rate; is OK! SPI.begin(); SPI.setBitOrder(MSBFIRST); SPI.setDataMode(SPI_MODE0); - SPI.setClockDivider(SPI_CLOCK_DIV8); // 2Mhz + SPI.setClockDivider(SPI_CLOCK_DIV16); // 1 MHz max, else flicker // Issue test pattern to LEDs on startup. This helps verify that // wiring between the Arduino and LEDs is correct. Not knowing the @@ -135,23 +137,24 @@ void setup() // green, blue, then off. Once you're confident everything is working // end-to-end, it's OK to comment this out and reprogram the Arduino. uint8_t testcolor[] = { 0, 0, 0, 255, 0, 0 }; - for (char n = 3; n >= 0; n--) { - for (int i = 0; i < 4; i++) { //Start Frame - for (SPDR = 0x00; !(SPSR & _BV(SPIF)); ); - } - for (c = 0; c < 25000; c++) { - for (SPDR = 0xFF; !(SPSR & _BV(SPIF)); ); //Brightness byte - for (i = 0; i < 3; i++) { - for (SPDR = testcolor[n + i]; !(SPSR & _BV(SPIF)); ); //BGR + for(int i=0; i<5; i++){ + for(SPDR = 0x00; !(SPSR & _BV(SPIF)); ); + } + for(char n=3; n>=0; n--) { + for(c=0; c<25000; c++) { + for(i=0; i<3; i++) { + for(SPDR = testcolor[n + i]; !(SPSR & _BV(SPIF)); ); + } + for(i=0; i<1; i++) { + for(SPDR = 0xFF; !(SPSR & _BV(SPIF)); ); } } - for (int i = 0; i < 4; i++) { //Stop Frame - for (SPDR = 0xFF; !(SPSR & _BV(SPIF)); ); + for(int i=0; i<16; i++){ + for(SPDR = 0x00; !(SPSR & _BV(SPIF)); ); } - delay(1); // One millisecond pause = latch + digitalWrite(SPI_LED, spi_out_led = !spi_out_led); } - digitalWrite(SPI_LED, spi_out_led = !spi_out_led); Serial.print("Ada\n"); // Send ACK string to host @@ -161,109 +164,105 @@ void setup() // loop() is avoided as even that small bit of function overhead // has a measurable impact on this code's overall throughput. - for (;;) { + for(;;) { digitalWrite(DATA_LED, LOW); digitalWrite(SPI_LED, LOW); // Implementation is a simple finite-state machine. // Regardless of mode, check for serial input each time: t = millis(); - if ((bytesBuffered < 256) && ((c = Serial.read()) >= 0)) { + if((bytesBuffered < 256) && ((c = Serial.read()) >= 0)) { buffer[indexIn++] = c; bytesBuffered++; lastByteTime = lastAckTime = t; // Reset timeout counters } else { // No data received. If this persists, send an ACK packet // to host once every second to alert it to our presence. - if ((t - lastAckTime) > 1000) { + if((t - lastAckTime) > 1000) { Serial.print("Ada\n"); // Send ACK string to host lastAckTime = t; // Reset counter } // If no data received for an extended time, turn off all LEDs. - if ((t - lastByteTime) > serialTimeout) { - for (i = 0; i < 4; i++) { //Start Frame - for (SPDR = 0x00; !(SPSR & _BV(SPIF)); ); - } - for (c = 0; c < 25000; c++) { - for (SPDR = 0xFF; !(SPSR & _BV(SPIF)); ); //Brightness Byte - for (i = 0; i < 3; i++) { - for (SPDR = 0x00; !(SPSR & _BV(SPIF)); ); //BGR + if((t - lastByteTime) > serialTimeout) { + for(c=0; c<25000; c++) { + for(i=0; i<3; i++) { + for(SPDR = 0x00; !(SPSR & _BV(SPIF)); ); + } + for(i=0; i<1; i++) { + for(SPDR = 0xFF; !(SPSR & _BV(SPIF)); ); + } } - } - for (i = 0; i < 4; i++) { //Stop Frame - for (SPDR = 0xFF; !(SPSR & _BV(SPIF)); ); - } delay(1); // One millisecond pause = latch lastByteTime = t; // Reset counter } } - switch (mode) { + switch(mode) { - case MODE_HEADER: + case MODE_HEADER: - // In header-seeking mode. Is there enough data to check? - if (bytesBuffered >= HEADERSIZE) { - // Indeed. Check for a 'magic word' match. - for (i = 0; (i < MAGICSIZE) && (buffer[indexOut++] == magic[i++]);); - if (i == MAGICSIZE) { - // Magic word matches. Now how about the checksum? - hi = buffer[indexOut++]; - lo = buffer[indexOut++]; - chk = buffer[indexOut++]; - if (chk == (hi ^ lo ^ 0x55)) { - // Checksum looks valid. Get 16-bit LED count, add 1 - // (# LEDs is always > 0) and multiply by 3 for R,G,B. - bytesRemaining = 4L * (256L * (long)hi + (long)lo) + 4L + (256L * (long)hi + (long)lo + 15) / 16; - bytesBuffered -= 3; - spiFlag = 0; // No data out yet - mode = MODE_HOLD; // Proceed to latch wait mode - digitalWrite(DATA_LED, data_in_led = !data_in_led); - } else { - // Checksum didn't match; search resumes after magic word. - indexOut -= 3; // Rewind - } - } // else no header match. Resume at first mismatched byte. - bytesBuffered -= i; - } - break; - - case MODE_HOLD: - - // Ostensibly "waiting for the latch from the prior frame - // to complete" mode, but may also revert to this mode when - // underrun prevention necessitates a delay. - - if ((micros() - startTime) < hold) break; // Still holding; keep buffering - - // Latch/delay complete. Advance to data-issuing mode... - LED_PORT &= ~LED_PIN; // LED off - mode = MODE_DATA; // ...and fall through (no break): - - case MODE_DATA: - digitalWrite(SPI_LED, spi_out_led = !spi_out_led); - while (spiFlag && !(SPSR & _BV(SPIF))); // Wait for prior byte - if (bytesRemaining > 0) { - if (bytesBuffered > 0) { - SPDR = buffer[indexOut++]; // Issue next byte - bytesBuffered--; - bytesRemaining--; - spiFlag = 1; + // In header-seeking mode. Is there enough data to check? + if(bytesBuffered >= HEADERSIZE) { + // Indeed. Check for a 'magic word' match. + for(i=0; (i 0) and multiply by 3 for R,G,B. + bytesRemaining = 4L * (256L * (long)hi + (long)lo) +4L + (256L *(long)hi + (long)lo +15)/16; + bytesBuffered -= 3; + spiFlag = 0; // No data out yet + mode = MODE_HOLD; // Proceed to latch wait mode + digitalWrite(DATA_LED, data_in_led = !data_in_led); + } else { + // Checksum didn't match; search resumes after magic word. + indexOut -= 3; // Rewind } - // If serial buffer is threatening to underrun, start - // introducing progressively longer pauses to allow more - // data to arrive (up to a point). - if ((bytesBuffered < 32) && (bytesRemaining > bytesBuffered)) { - startTime = micros(); - hold = 100 + (32 - bytesBuffered) * 10; - mode = MODE_HOLD; - } - } else { - // End of data -- issue latch: - startTime = micros(); - hold = 1000; // Latch duration = 1000 uS - LED_PORT |= LED_PIN; // LED on - mode = MODE_HEADER; // Begin next header search + } // else no header match. Resume at first mismatched byte. + bytesBuffered -= i; + } + break; + + case MODE_HOLD: + + // Ostensibly "waiting for the latch from the prior frame + // to complete" mode, but may also revert to this mode when + // underrun prevention necessitates a delay. + + if((micros() - startTime) < hold) break; // Still holding; keep buffering + + // Latch/delay complete. Advance to data-issuing mode... + LED_PORT &= ~LED_PIN; // LED off + mode = MODE_DATA; // ...and fall through (no break): + + case MODE_DATA: + digitalWrite(SPI_LED, spi_out_led = !spi_out_led); + while(spiFlag && !(SPSR & _BV(SPIF))); // Wait for prior byte + if(bytesRemaining > 0) { + if(bytesBuffered > 0) { + SPDR = buffer[indexOut++]; // Issue next byte + bytesBuffered--; + bytesRemaining--; + spiFlag = 1; } + // If serial buffer is threatening to underrun, start + // introducing progressively longer pauses to allow more + // data to arrive (up to a point). + if((bytesBuffered < 32) && (bytesRemaining > bytesBuffered)) { + startTime = micros(); + hold = 100 + (32 - bytesBuffered) * 10; + mode = MODE_HOLD; + } + } else { + // End of data -- issue latch: + startTime = micros(); + hold = 1000; // Latch duration = 1000 uS + LED_PORT |= LED_PIN; // LED on + mode = MODE_HEADER; // Begin next header search + } } // end switch } // end for(;;) } @@ -271,4 +270,4 @@ void setup() void loop() { // Not used. See note in setup() function. -} +} diff --git a/assets/firmware/arduino/tpm2/tpm2.ino b/assets/firmware/arduino/tpm2/tpm2.ino new file mode 100644 index 00000000..55c95c46 --- /dev/null +++ b/assets/firmware/arduino/tpm2/tpm2.ino @@ -0,0 +1,133 @@ +/** + * This is a demo implemention how to use tpm2 protovol on arduino + * + * code is taken from: https://github.com/JonasVanGool/TPM2-ARDUINO + */ + + +#include + +#define START_BYTE 0xC9 +#define STOP_BYTE 0x36 +#define DATA_FRAME 0xDA +#define COMMAND 0xC0 +#define REQ_RESP 0xAA + +#define BAUDRATE 115200 + +#define DATA_PIN 12 +#define MAX_NR_LEDS 200 + +CRGB leds[MAX_NR_LEDS]; + +enum States { + ST_START, + ST_PACKET_TYPE, + ST_PAYLOAD_SIZE, + ST_USER_DATA, + ST_END +}; + +States activeState = ST_START; +uint8_t byteRead = 0; +uint8_t payloadHighByte = 0; +uint8_t payloadLowByte = 0; +int payloadSize = 0; +int bytesRead = 0; +int nrOfLeds = 0; + +boolean flag = false; +void setup() { + // Init leds + for (int i = 0; i < MAX_NR_LEDS; i++) + leds[i] = 0; + // Start serial device + Serial.begin(BAUDRATE); + // Set time out for readBytes function + Serial.setTimeout(100); + // init fastLED Library + LEDS.addLeds(leds, MAX_NR_LEDS); + // setting brightness to 50% brightness + LEDS.setBrightness(128); + // debug led + pinMode(13, OUTPUT); + + + for(;;) { + if (Serial.available() > 0) { + // process TPM2 protocol + switch (activeState) { + //------------------------------// + // START // + //------------------------------// + case ST_START: + // read incomming byte + byteRead = Serial.read(); + if (byteRead == START_BYTE) { + activeState = ST_PACKET_TYPE; + } + break; + //------------------------------// + // PACKET_TYPE // + //------------------------------// + case ST_PACKET_TYPE: + // read incomming byte + byteRead = Serial.read(); + if (byteRead == DATA_FRAME) { + activeState = ST_PAYLOAD_SIZE; + } else { + activeState = ST_START; + Serial.flush(); + } + break; + //------------------------------// + // PAYLOAD_SIZE // + //------------------------------// + case ST_PAYLOAD_SIZE: + payloadHighByte = Serial.read(); + while (Serial.available() == 0) {} + payloadLowByte = Serial.read(); + payloadSize = (payloadHighByte << 8) + payloadLowByte; + nrOfLeds = payloadSize / 3; + if (nrOfLeds <= MAX_NR_LEDS) { + activeState = ST_USER_DATA; + } else { + activeState = ST_START; + Serial.flush(); + } + break; + //------------------------------// + // USER_DATA // + //------------------------------// + case ST_USER_DATA: + bytesRead = Serial.readBytes((char *)leds, payloadSize); + LEDS.show(); + if (bytesRead == payloadSize) { + activeState = ST_END; + } else { + activeState = ST_START; + Serial.flush(); + } + break; + //------------------------------// + // END // + //------------------------------// + case ST_END: + // read incomming byte + byteRead = Serial.read(); + if (byteRead == STOP_BYTE) { + activeState = ST_START; + } else { + activeState = ST_START; + Serial.flush(); + } + break; + default: break; + } + } + + } +} + +void loop() { +} diff --git a/assets/webconfig/content/leds.html b/assets/webconfig/content/leds.html index b2a4a57e..cf72e4e3 100644 --- a/assets/webconfig/content/leds.html +++ b/assets/webconfig/content/leds.html @@ -101,7 +101,7 @@
diff --git a/assets/webconfig/js/content_index.js b/assets/webconfig/js/content_index.js index 0aaa0819..9bb3fabb 100644 --- a/assets/webconfig/js/content_index.js +++ b/assets/webconfig/js/content_index.js @@ -22,6 +22,12 @@ $(document).ready( function() { parsedServerInfoJSON = event.response; currentVersion = parsedServerInfoJSON.info.hyperion[0].version; cleanCurrentVersion = currentVersion.replace(/\./g, ''); + + // ToDo lock config menu and display appropriate message + if (! parsedServerInfoJSON.info.hyperion[0].config_writeable) + { + console.log("ATTENTION config is not writable"); + } if (parsedServerInfoJSON.info.hyperion[0].config_modified) $("#hyperion_reload_notify").fadeIn("fast"); diff --git a/assets/webconfig/js/content_leds.js b/assets/webconfig/js/content_leds.js index 457213bb..9c377fe9 100644 --- a/assets/webconfig/js/content_leds.js +++ b/assets/webconfig/js/content_leds.js @@ -472,11 +472,8 @@ $(document).ready(function() { // ------------------------------------------------------------------ $("#leds_custom_save").off().on("click", function() { - function createLedConfig(){ - var string = '{"leds" :'+$("#ledconfig").val()+'}'; - } if (validateText()) - requestWriteConfig(JSON.parse(createLedConfig())); + requestWriteConfig(JSON.parse('{"leds" :'+$("#ledconfig").val()+'}')); }); // ------------------------------------------------------------------ diff --git a/effects/candle.py b/effects/candle.py index 1be744c3..f0eeaf6f 100644 --- a/effects/candle.py +++ b/effects/candle.py @@ -30,7 +30,7 @@ if (candles == "list") and (type(ledlist) is str): if (i +#include #include #include #include @@ -84,7 +85,7 @@ public: /// void freeObjects(); - static Hyperion* initInstance(const QJsonObject& qjsonConfig, const std::string configFile); + static Hyperion* initInstance(const QJsonObject& qjsonConfig, const QString configFile); static Hyperion* getInstance(); /// @@ -140,7 +141,7 @@ public: /// get filename of configfile /// @return the current config filename - std::string getConfigFileName() { return _configFile; }; + std::string getConfigFileName() { return _configFile.toStdString(); }; /// register a input source to a priority channel /// @param name uniq name of input source @@ -180,6 +181,8 @@ public: bool configModified(); + bool configWriteable(); + public slots: /// /// Writes a single color to all the leds for the given time and priority @@ -314,7 +317,7 @@ private: /// /// @param[in] qjsonConfig The Json configuration /// - Hyperion(const QJsonObject& qjsonConfig, const std::string configFile); + Hyperion(const QJsonObject& qjsonConfig, const QString configFile); /// The specifiation of the led frame construction and picture integration LedString _ledString; @@ -348,7 +351,7 @@ private: const QJsonObject& _qjsonConfig; // the name of config file - std::string _configFile; + QString _configFile; /// The timer for handling priority channel timeouts QTimer _timer; diff --git a/libsrc/hyperion/Hyperion.cpp b/libsrc/hyperion/Hyperion.cpp index 98fef676..f1bd9146 100644 --- a/libsrc/hyperion/Hyperion.cpp +++ b/libsrc/hyperion/Hyperion.cpp @@ -12,6 +12,7 @@ #include #include #include +#include // hyperion include #include @@ -32,7 +33,7 @@ Hyperion* Hyperion::_hyperion = nullptr; -Hyperion* Hyperion::initInstance(const QJsonObject& qjsonConfig, const std::string configFile) // REMOVE jsonConfig variable when the conversion from jsonCPP to QtJSON is finished +Hyperion* Hyperion::initInstance(const QJsonObject& qjsonConfig, const QString configFile) // REMOVE jsonConfig variable when the conversion from jsonCPP to QtJSON is finished { if ( Hyperion::_hyperion != nullptr ) throw std::runtime_error("Hyperion::initInstance can be called only one time"); @@ -514,7 +515,7 @@ MessageForwarder * Hyperion::getForwarder() return _messageForwarder; } -Hyperion::Hyperion(const QJsonObject &qjsonConfig, const std::string configFile) +Hyperion::Hyperion(const QJsonObject &qjsonConfig, const QString configFile) : _ledString(createLedString(qjsonConfig["leds"], createColorOrder(qjsonConfig["device"].toObject()))) , _ledStringClone(createLedStringClone(qjsonConfig["leds"], createColorOrder(qjsonConfig["device"].toObject()))) , _muxer(_ledString.leds().size()) @@ -582,6 +583,7 @@ Hyperion::Hyperion(const QJsonObject &qjsonConfig, const std::string configFile) Debug(_log,"configured leds: %d hw leds: %d", getLedCount(), _hwLedCount); WarningIf(hwLedCount < getLedCount(), _log, "more leds configured than available. check 'ledCount' in 'device' section"); + WarningIf(!configWriteable(), _log, "Your config is not writeable - you won't be able to use the web ui for configuration."); // initialize hash of current config configModified(); @@ -618,7 +620,7 @@ unsigned Hyperion::getLedCount() const bool Hyperion::configModified() { bool isModified = false; - QFile f(_configFile.c_str()); + QFile f(_configFile); if (f.open(QFile::ReadOnly)) { QCryptographicHash hash(QCryptographicHash::Sha1); @@ -639,6 +641,14 @@ bool Hyperion::configModified() return isModified; } +bool Hyperion::configWriteable() +{ + QFile file(_configFile); + QFileInfo fileInfo(file); + return fileInfo.isWritable() && fileInfo.isReadable(); +} + + void Hyperion::registerPriority(const std::string name, const int priority) { Info(_log, "Register new input source named '%s' for priority channel '%d'", name.c_str(), priority ); diff --git a/libsrc/hyperion/hyperion.schema.json b/libsrc/hyperion/hyperion.schema.json index 656d5c83..e1ce0900 100644 --- a/libsrc/hyperion/hyperion.schema.json +++ b/libsrc/hyperion/hyperion.schema.json @@ -476,7 +476,8 @@ "priority" : { "type" : "integer", - "minimum" : 0, + "minimum" : 2, + "maximum" : 2000000000, "title" : "edt_conf_general_priority_title", "default" : 900, "propertyOrder" : 9 @@ -617,7 +618,8 @@ { "type" : "integer", "title" : "edt_conf_general_priority_title", - "minimum" : 0, + "minimum" : 2, + "maximum" : 2000000000, "default" : 890 }, "cropLeft" : @@ -974,7 +976,8 @@ { "type" : "integer", "title" : "edt_conf_general_priority_title", - "minimum" : 0, + "minimum" : 2, + "maximum" : 2000000000, "default" : 800, "propertyOrder" : 3 } @@ -1014,7 +1017,8 @@ { "type" : "integer", "title" : "edt_conf_general_priority_title", - "minimum" : 0, + "minimum" : 2, + "maximum" : 2000000000, "default" : 800, "propertyOrder" : 4 }, diff --git a/libsrc/jsonserver/JsonClientConnection.cpp b/libsrc/jsonserver/JsonClientConnection.cpp index b293c9fe..0709613d 100644 --- a/libsrc/jsonserver/JsonClientConnection.cpp +++ b/libsrc/jsonserver/JsonClientConnection.cpp @@ -841,6 +841,7 @@ void JsonClientConnection::handleServerInfoCommand(const QJsonObject&, const QSt ver["build"] = QString(HYPERION_BUILD_ID); ver["time"] = QString(__DATE__ " " __TIME__); ver["config_modified"] = _hyperion->configModified(); + ver["config_writeable"] = _hyperion->configWriteable(); hyperion.append(ver); info["hyperion"] = hyperion; diff --git a/libsrc/leddevice/LedDeviceAdalight.cpp b/libsrc/leddevice/LedDeviceAdalight.cpp index f005770f..f25e884d 100644 --- a/libsrc/leddevice/LedDeviceAdalight.cpp +++ b/libsrc/leddevice/LedDeviceAdalight.cpp @@ -2,6 +2,7 @@ LedDeviceAdalight::LedDeviceAdalight(const QJsonObject &deviceConfig) : ProviderRs232() + , _headerSize(6) , _ligthBerryAPA102Mode(false) { _deviceReady = init(deviceConfig); @@ -18,7 +19,6 @@ bool LedDeviceAdalight::init(const QJsonObject &deviceConfig) _ligthBerryAPA102Mode = deviceConfig["lightberry_apa102_mode"].toBool(false); // create ledBuffer - unsigned int bufferSize = 6; // 6 bytes header unsigned int totalLedCount = _ledCount; if (_ligthBerryAPA102Mode) @@ -26,25 +26,30 @@ bool LedDeviceAdalight::init(const QJsonObject &deviceConfig) const unsigned int startFrameSize = 4; const unsigned int bytesPerRGBLed = 4; const unsigned int endFrameSize = std::max(((_ledCount + 15) / 16), bytesPerRGBLed); - bufferSize += (_ledCount * bytesPerRGBLed) + startFrameSize + endFrameSize ; + _ledBuffer.resize(_headerSize + (_ledCount * bytesPerRGBLed) + startFrameSize + endFrameSize, 0x00); + + // init constant data values + for (signed iLed=1; iLed<=_ledCount; iLed++) + { + _ledBuffer[iLed*4+_headerSize] = 0xFF; + } + Debug( _log, "Adalight driver with activated LightBerry APA102 mode"); } else { totalLedCount -= 1; - bufferSize += _ledRGBCount; + _ledBuffer.resize(_headerSize + _ledRGBCount, 0x00); } - _ledBuffer.resize(bufferSize, 0x00); _ledBuffer[0] = 'A'; _ledBuffer[1] = 'd'; _ledBuffer[2] = 'a'; - _ledBuffer[3] = (((unsigned int)(totalLedCount)) >> 8) & 0xFF; // LED count high byte - _ledBuffer[4] = ((unsigned int)(totalLedCount)) & 0xFF; // LED count low byte + _ledBuffer[3] = (totalLedCount >> 8) & 0xFF; // LED count high byte + _ledBuffer[4] = totalLedCount & 0xFF; // LED count low byte _ledBuffer[5] = _ledBuffer[3] ^ _ledBuffer[4] ^ 0x55; // Checksum Debug( _log, "Adalight header for %d leds: %c%c%c 0x%02x 0x%02x 0x%02x", _ledCount, - _ledBuffer[0], _ledBuffer[1], _ledBuffer[2], _ledBuffer[3], _ledBuffer[4], _ledBuffer[5] - ); + _ledBuffer[0], _ledBuffer[1], _ledBuffer[2], _ledBuffer[3], _ledBuffer[4], _ledBuffer[5] ); return true; } @@ -56,15 +61,14 @@ int LedDeviceAdalight::write(const std::vector & ledValues) for (signed iLed=1; iLed<=_ledCount; iLed++) { const ColorRgb& rgb = ledValues[iLed-1]; - _ledBuffer[iLed*4+6] = 0xFF; - _ledBuffer[iLed*4+1+6] = rgb.red; - _ledBuffer[iLed*4+2+6] = rgb.green; - _ledBuffer[iLed*4+3+6] = rgb.blue; + _ledBuffer[iLed*4+7] = rgb.red; + _ledBuffer[iLed*4+8] = rgb.green; + _ledBuffer[iLed*4+9] = rgb.blue; } } else { - memcpy(6 + _ledBuffer.data(), ledValues.data(), ledValues.size() * 3); + memcpy(_headerSize + _ledBuffer.data(), ledValues.data(), ledValues.size() * 3); } return writeBytes(_ledBuffer.size(), _ledBuffer.data()); diff --git a/libsrc/leddevice/LedDeviceAdalight.h b/libsrc/leddevice/LedDeviceAdalight.h index 3e5120e4..765f4586 100644 --- a/libsrc/leddevice/LedDeviceAdalight.h +++ b/libsrc/leddevice/LedDeviceAdalight.h @@ -31,6 +31,7 @@ private: /// virtual int write(const std::vector & ledValues); + const short _headerSize; bool _ligthBerryAPA102Mode; }; diff --git a/libsrc/leddevice/LedDevicePhilipsHue.cpp b/libsrc/leddevice/LedDevicePhilipsHue.cpp index 8f34d8e7..077d8d45 100755 --- a/libsrc/leddevice/LedDevicePhilipsHue.cpp +++ b/libsrc/leddevice/LedDevicePhilipsHue.cpp @@ -369,7 +369,9 @@ void LedDevicePhilipsHue::saveStates(unsigned int nLights) if (error.error != QJsonParseError::NoError) { // Error occured, break loop. - Error(_log, "saveStates(nLights=%d): got invalid response from light %s.", nLights, getUrl(getRoute(lightIds.at(i))).toStdString().c_str()); + Error(_log, "saveStates(nLights=%d): got invalid response from light %s. (error:%s, offset:%d)", + nLights, getUrl(getRoute(lightIds.at(i))).toStdString().c_str(), error.errorString().toLocal8Bit().constData(), error.offset ); + break; } diff --git a/libsrc/leddevice/LedDeviceTpm2.cpp b/libsrc/leddevice/LedDeviceTpm2.cpp index abf6012c..24210089 100644 --- a/libsrc/leddevice/LedDeviceTpm2.cpp +++ b/libsrc/leddevice/LedDeviceTpm2.cpp @@ -16,11 +16,11 @@ bool LedDeviceTpm2::init(const QJsonObject &deviceConfig) { ProviderRs232::init(deviceConfig); - _ledBuffer.resize(5 + 3*_ledCount); + _ledBuffer.resize(5 + _ledRGBCount); _ledBuffer[0] = 0xC9; // block-start byte _ledBuffer[1] = 0xDA; // DATA frame - _ledBuffer[2] = ((3 * _ledCount) >> 8) & 0xFF; // frame size high byte - _ledBuffer[3] = (3 * _ledCount) & 0xFF; // frame size low byte + _ledBuffer[2] = (_ledRGBCount >> 8) & 0xFF; // frame size high byte + _ledBuffer[3] = _ledRGBCount & 0xFF; // frame size low byte _ledBuffer.back() = 0x36; // block-end byte return true; diff --git a/libsrc/leddevice/schemas/schema-adalight.json b/libsrc/leddevice/schemas/schema-adalight.json index b5ff4526..19946533 100644 --- a/libsrc/leddevice/schemas/schema-adalight.json +++ b/libsrc/leddevice/schemas/schema-adalight.json @@ -17,7 +17,7 @@ "delayAfterConnect": { "type": "integer", "title":"edt_dev_spec_delayAfterConnect_title", - "default": 250, + "default": 1500, "append" : "ms", "propertyOrder" : 3 }, diff --git a/src/hyperiond/hyperiond.cpp b/src/hyperiond/hyperiond.cpp index 577af4a4..04d931ef 100644 --- a/src/hyperiond/hyperiond.cpp +++ b/src/hyperiond/hyperiond.cpp @@ -69,12 +69,12 @@ HyperionDaemon::HyperionDaemon(QString configFile, QObject *parent) } else { - WarningIf(_qconfig.contains("logger"), Logger::getInstance("LOGGER"), "Logger settings overriden by command line argument"); + WarningIf(_qconfig.contains("logger"), Logger::getInstance("LOGGER"), "Logger settings overridden by command line argument"); } - _hyperion = Hyperion::initInstance(_qconfig, configFile.toStdString()); + _hyperion = Hyperion::initInstance(_qconfig, configFile); - Info(_log, "Hyperion initialised"); + Info(_log, "Hyperion initialized"); } HyperionDaemon::~HyperionDaemon()