mirror of
https://github.com/nymea/nymea-plugins.git
synced 2026-10-22 14:33:25 +02:00
Add firmware and first working version
This commit is contained in:
parent
5f3331e1e9
commit
45925556e4
39
ws2812fx/firmware/include/README
Normal file
39
ws2812fx/firmware/include/README
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
|
||||||
|
This directory is intended for project header files.
|
||||||
|
|
||||||
|
A header file is a file containing C declarations and macro definitions
|
||||||
|
to be shared between several project source files. You request the use of a
|
||||||
|
header file in your project source file (C, C++, etc) located in `src` folder
|
||||||
|
by including it, with the C preprocessing directive `#include'.
|
||||||
|
|
||||||
|
```src/main.c
|
||||||
|
|
||||||
|
#include "header.h"
|
||||||
|
|
||||||
|
int main (void)
|
||||||
|
{
|
||||||
|
...
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Including a header file produces the same results as copying the header file
|
||||||
|
into each source file that needs it. Such copying would be time-consuming
|
||||||
|
and error-prone. With a header file, the related declarations appear
|
||||||
|
in only one place. If they need to be changed, they can be changed in one
|
||||||
|
place, and programs that include the header file will automatically use the
|
||||||
|
new version when next recompiled. The header file eliminates the labor of
|
||||||
|
finding and changing all the copies as well as the risk that a failure to
|
||||||
|
find one copy will result in inconsistencies within a program.
|
||||||
|
|
||||||
|
In C, the usual convention is to give header files names that end with `.h'.
|
||||||
|
It is most portable to use only letters, digits, dashes, and underscores in
|
||||||
|
header file names, and at most one dot.
|
||||||
|
|
||||||
|
Read more about using header files in official GCC documentation:
|
||||||
|
|
||||||
|
* Include Syntax
|
||||||
|
* Include Operation
|
||||||
|
* Once-Only Headers
|
||||||
|
* Computed Includes
|
||||||
|
|
||||||
|
https://gcc.gnu.org/onlinedocs/cpp/Header-Files.html
|
||||||
1
ws2812fx/firmware/lib/Adafruit_NeoPixel
Submodule
1
ws2812fx/firmware/lib/Adafruit_NeoPixel
Submodule
@ -0,0 +1 @@
|
|||||||
|
Subproject commit 8595ee3f5880d5096d71551c621d7a2d3818f974
|
||||||
46
ws2812fx/firmware/lib/README
Normal file
46
ws2812fx/firmware/lib/README
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
|
||||||
|
This directory is intended for project specific (private) libraries.
|
||||||
|
PlatformIO will compile them to static libraries and link into executable file.
|
||||||
|
|
||||||
|
The source code of each library should be placed in a an own separate directory
|
||||||
|
("lib/your_library_name/[here are source files]").
|
||||||
|
|
||||||
|
For example, see a structure of the following two libraries `Foo` and `Bar`:
|
||||||
|
|
||||||
|
|--lib
|
||||||
|
| |
|
||||||
|
| |--Bar
|
||||||
|
| | |--docs
|
||||||
|
| | |--examples
|
||||||
|
| | |--src
|
||||||
|
| | |- Bar.c
|
||||||
|
| | |- Bar.h
|
||||||
|
| | |- library.json (optional, custom build options, etc) https://docs.platformio.org/page/librarymanager/config.html
|
||||||
|
| |
|
||||||
|
| |--Foo
|
||||||
|
| | |- Foo.c
|
||||||
|
| | |- Foo.h
|
||||||
|
| |
|
||||||
|
| |- README --> THIS FILE
|
||||||
|
|
|
||||||
|
|- platformio.ini
|
||||||
|
|--src
|
||||||
|
|- main.c
|
||||||
|
|
||||||
|
and a contents of `src/main.c`:
|
||||||
|
```
|
||||||
|
#include <Foo.h>
|
||||||
|
#include <Bar.h>
|
||||||
|
|
||||||
|
int main (void)
|
||||||
|
{
|
||||||
|
...
|
||||||
|
}
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
PlatformIO Library Dependency Finder will find automatically dependent
|
||||||
|
libraries scanning project source files.
|
||||||
|
|
||||||
|
More information about PlatformIO Library Dependency Finder
|
||||||
|
- https://docs.platformio.org/page/librarymanager/ldf.html
|
||||||
1
ws2812fx/firmware/lib/WS2812FX
Submodule
1
ws2812fx/firmware/lib/WS2812FX
Submodule
@ -0,0 +1 @@
|
|||||||
|
Subproject commit 4774866a176eb4ff474f712ce63a06404385e911
|
||||||
14
ws2812fx/firmware/platformio.ini
Normal file
14
ws2812fx/firmware/platformio.ini
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
; PlatformIO Project Configuration File
|
||||||
|
;
|
||||||
|
; Build options: build flags, source filter
|
||||||
|
; Upload options: custom upload port, speed and extra flags
|
||||||
|
; Library options: dependencies, extra library storages
|
||||||
|
; Advanced options: extra scripting
|
||||||
|
;
|
||||||
|
; Please visit documentation for the other options and examples
|
||||||
|
; https://docs.platformio.org/page/projectconf.html
|
||||||
|
|
||||||
|
[env:uno]
|
||||||
|
platform = atmelavr
|
||||||
|
board = uno
|
||||||
|
framework = arduino
|
||||||
20
ws2812fx/firmware/src/main.cpp
Normal file
20
ws2812fx/firmware/src/main.cpp
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
#include <Arduino.h>
|
||||||
|
|
||||||
|
#include "nymealight.h"
|
||||||
|
|
||||||
|
// TEST
|
||||||
|
NymeaLight *light = nullptr;
|
||||||
|
|
||||||
|
void setup() {
|
||||||
|
// Get rid of unused warning from lib
|
||||||
|
(void)&_modes;
|
||||||
|
|
||||||
|
light = new NymeaLight(Serial, new WS2812FX(10, 13, NEO_GRB + NEO_KHZ800));
|
||||||
|
light->init();
|
||||||
|
}
|
||||||
|
|
||||||
|
void loop() {
|
||||||
|
light->process();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
330
ws2812fx/firmware/src/nymealight.cpp
Normal file
330
ws2812fx/firmware/src/nymealight.cpp
Normal file
@ -0,0 +1,330 @@
|
|||||||
|
#include "nymealight.h"
|
||||||
|
|
||||||
|
NymeaLight::NymeaLight(WS2812FX *strip) :
|
||||||
|
m_strip(strip)
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
NymeaLight::NymeaLight(SoftwareSerial *serial, WS2812FX *strip) :
|
||||||
|
m_softwareSerial(serial),
|
||||||
|
m_strip(strip)
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
NymeaLight::NymeaLight(HardwareSerial &serial, WS2812FX *strip) :
|
||||||
|
m_hardwareSerial(&serial),
|
||||||
|
m_strip(strip)
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
WS2812FX *NymeaLight::strip() const
|
||||||
|
{
|
||||||
|
return m_strip;
|
||||||
|
}
|
||||||
|
|
||||||
|
void NymeaLight::init()
|
||||||
|
{
|
||||||
|
// Get rid of unused warning from lib
|
||||||
|
(void)&_modes;
|
||||||
|
|
||||||
|
// Initialize serial port
|
||||||
|
if (m_hardwareSerial) {
|
||||||
|
m_hardwareSerial->begin(115200);
|
||||||
|
} else if (m_softwareSerial) {
|
||||||
|
m_softwareSerial->begin(115200);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initialize strip
|
||||||
|
m_strip->init();
|
||||||
|
if (m_power) {
|
||||||
|
m_strip->setBrightness(m_brightness);
|
||||||
|
} else {
|
||||||
|
m_strip->setBrightness(0);
|
||||||
|
}
|
||||||
|
m_strip->setSpeed(m_speed);
|
||||||
|
m_strip->setColor(0xff, 0xff, 0xff);
|
||||||
|
m_strip->setMode(FX_MODE_STATIC);
|
||||||
|
m_strip->start();
|
||||||
|
|
||||||
|
m_previouseTime = millis();
|
||||||
|
sendReadyNotification();
|
||||||
|
}
|
||||||
|
|
||||||
|
void NymeaLight::process()
|
||||||
|
{
|
||||||
|
// Read incoming data if there is any
|
||||||
|
readData();
|
||||||
|
|
||||||
|
//doAnimations();
|
||||||
|
|
||||||
|
// Perform service for WS2812FX
|
||||||
|
m_strip->service();
|
||||||
|
}
|
||||||
|
|
||||||
|
void NymeaLight::doAnimations()
|
||||||
|
{
|
||||||
|
// Perform animation steps every 50 ms
|
||||||
|
uint32_t currentTimestamp = millis();
|
||||||
|
if (m_previouseTime - currentTimestamp > 50000) {
|
||||||
|
//debugPrint("Tick");
|
||||||
|
m_previouseTime = currentTimestamp;
|
||||||
|
|
||||||
|
if (m_brightnessFadeDuration > 0 && m_brightnessTargetValue != m_strip->getBrightness()) {
|
||||||
|
// Calculate step for brightness animation
|
||||||
|
if (m_brightnessStartValue < m_brightnessTargetValue) {
|
||||||
|
m_strip->setBrightness(m_strip->getBrightness() - 1);
|
||||||
|
} else {
|
||||||
|
m_strip->setBrightness(m_strip->getBrightness() + 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void NymeaLight::debugPrint(const char message[])
|
||||||
|
{
|
||||||
|
streamByte(SlipProtocolEnd, true);
|
||||||
|
streamByte(NotificationDebugMessage);
|
||||||
|
streamByte(m_notificationId);
|
||||||
|
m_notificationId++;
|
||||||
|
for (size_t i = 0; i < strlen(message); i++) {
|
||||||
|
streamByte(message[i]);
|
||||||
|
}
|
||||||
|
streamByte(SlipProtocolEnd, true);
|
||||||
|
flushSerial();
|
||||||
|
}
|
||||||
|
|
||||||
|
void NymeaLight::flushSerial()
|
||||||
|
{
|
||||||
|
if (m_hardwareSerial) {
|
||||||
|
m_hardwareSerial->flush();
|
||||||
|
} else if (m_softwareSerial) {
|
||||||
|
m_softwareSerial->flush();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void NymeaLight::sendReadyNotification()
|
||||||
|
{
|
||||||
|
streamByte(SlipProtocolEnd, true);
|
||||||
|
streamByte(NotificationReady);
|
||||||
|
streamByte(m_notificationId);
|
||||||
|
m_notificationId++;
|
||||||
|
streamByte(SlipProtocolEnd, true);
|
||||||
|
flushSerial();
|
||||||
|
}
|
||||||
|
|
||||||
|
void NymeaLight::readData()
|
||||||
|
{
|
||||||
|
if (m_hardwareSerial) {
|
||||||
|
while (m_hardwareSerial->available()) {
|
||||||
|
uint8_t receivedByte = m_hardwareSerial->read();
|
||||||
|
processReceivedByte(receivedByte);
|
||||||
|
}
|
||||||
|
} else if (m_softwareSerial) {
|
||||||
|
while (m_softwareSerial->available()) {
|
||||||
|
uint8_t receivedByte = m_softwareSerial->read();
|
||||||
|
processReceivedByte(receivedByte);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void NymeaLight::processData(uint8_t buffer[], uint8_t length)
|
||||||
|
{
|
||||||
|
uint8_t command = buffer[0];
|
||||||
|
uint8_t requestId = buffer[1];
|
||||||
|
|
||||||
|
switch (command) {
|
||||||
|
case CommandGetStatus: {
|
||||||
|
if (length != 2) {
|
||||||
|
sendResponse(command, requestId, StatusInvalidPlayload);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
sendResponse(command, requestId, StatusSuccess);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case CommandSetPower: {
|
||||||
|
if (length != 5) {
|
||||||
|
sendResponse(command, requestId, StatusInvalidPlayload);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// uint16_t fadeDuration = (buffer[2] << 8 ) | buffer[3];
|
||||||
|
uint8_t powerInt = buffer[4];
|
||||||
|
if (powerInt == 0) {
|
||||||
|
m_power = false;
|
||||||
|
} else {
|
||||||
|
m_power = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (m_power) {
|
||||||
|
m_strip->setBrightness(m_brightness);
|
||||||
|
} else {
|
||||||
|
m_strip->setBrightness(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: animate brightness with fade duration
|
||||||
|
|
||||||
|
sendResponse(command, requestId, StatusSuccess);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case CommandSetColor: {
|
||||||
|
if (length != 7) {
|
||||||
|
sendResponse(command, requestId, StatusInvalidPlayload);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
//uint32_t currentColor = m_strip->getColor();
|
||||||
|
//uint32_t targetColor = ((uint32_t) << 16) | ((uint32_t)buffer[5] << 8) | buffer[6];
|
||||||
|
// uint16_t fadeDuration = (buffer[2] << 8 ) | buffer[3];
|
||||||
|
uint8_t red = buffer[4];
|
||||||
|
uint8_t green = buffer[5];
|
||||||
|
uint8_t blue = buffer[6];
|
||||||
|
m_strip->setColor(red, green, blue);
|
||||||
|
delayMicroseconds(1000);
|
||||||
|
sendResponse(command, requestId, StatusSuccess);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case CommandSetBrightness: {
|
||||||
|
if (length != 5) {
|
||||||
|
sendResponse(command, requestId, StatusInvalidPlayload);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
//uint16_t fadeDuration = (buffer[2] << 8 ) | buffer[3];
|
||||||
|
// m_brightnessStartValue = m_strip->getBrightness();
|
||||||
|
|
||||||
|
m_brightness = buffer[4];
|
||||||
|
if (m_power) {
|
||||||
|
m_strip->setBrightness(m_brightness);
|
||||||
|
}
|
||||||
|
|
||||||
|
delayMicroseconds(1000);
|
||||||
|
sendResponse(command, requestId, StatusSuccess);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case CommandSetSpeed: {
|
||||||
|
if (length != 6) {
|
||||||
|
sendResponse(command, requestId, StatusInvalidPlayload);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// uint16_t fadeDuration = (buffer[2] << 8 ) | buffer[3];
|
||||||
|
uint16_t speed = (buffer[4] << 8 ) | buffer[5];
|
||||||
|
m_strip->setSpeed(speed);
|
||||||
|
delayMicroseconds(1000);
|
||||||
|
sendResponse(command, requestId, StatusSuccess);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case CommandSetEffect: {
|
||||||
|
if (length != 3) {
|
||||||
|
sendResponse(command, requestId, StatusInvalidPlayload);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
m_strip->setMode(buffer[2]);
|
||||||
|
delayMicroseconds(1000);
|
||||||
|
sendResponse(command, requestId, StatusSuccess);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case CommandCustom:
|
||||||
|
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
sendResponse(command, requestId, StatusInvalidCommand);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
m_strip->show();
|
||||||
|
m_strip->service();
|
||||||
|
}
|
||||||
|
|
||||||
|
void NymeaLight::sendResponse(uint8_t command, uint8_t requestId, Status status)
|
||||||
|
{
|
||||||
|
streamByte(SlipProtocolEnd, true);
|
||||||
|
streamByte(command);
|
||||||
|
streamByte(requestId);
|
||||||
|
streamByte(status);
|
||||||
|
streamByte(SlipProtocolEnd, true);
|
||||||
|
|
||||||
|
flushSerial();
|
||||||
|
}
|
||||||
|
|
||||||
|
void NymeaLight::processReceivedByte(uint8_t receivedByte)
|
||||||
|
{
|
||||||
|
if (m_protocolEscaping) {
|
||||||
|
switch (receivedByte) {
|
||||||
|
case SlipProtocolTransposedEnd:
|
||||||
|
m_buffer[m_bufferIndex++] = SlipProtocolEnd;
|
||||||
|
m_protocolEscaping = false;
|
||||||
|
break;
|
||||||
|
case SlipProtocolTransposedEsc:
|
||||||
|
m_buffer[m_bufferIndex++] = SlipProtocolEsc;
|
||||||
|
m_protocolEscaping = false;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
// SLIP protocol violation...received escape, but it is not an escaped byte
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (receivedByte) {
|
||||||
|
case SlipProtocolEnd:
|
||||||
|
// We are done with this package, process it and reset the buffer
|
||||||
|
if (m_bufferIndex > 0) {
|
||||||
|
processData(m_buffer, m_bufferIndex);
|
||||||
|
}
|
||||||
|
m_bufferIndex = 0;
|
||||||
|
m_protocolEscaping = false;
|
||||||
|
break;
|
||||||
|
case SlipProtocolEsc:
|
||||||
|
// The next byte will be escaped, lets wait for it
|
||||||
|
m_protocolEscaping = true;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
// Nothing special, just add to buffer
|
||||||
|
m_buffer[m_bufferIndex++] = receivedByte;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void NymeaLight::streamByte(uint8_t dataByte, boolean specialCharacter)
|
||||||
|
{
|
||||||
|
// If this is a special character, write it without escaping
|
||||||
|
if (specialCharacter) {
|
||||||
|
writeByte(dataByte);
|
||||||
|
} else {
|
||||||
|
switch (dataByte) {
|
||||||
|
case SlipProtocolEnd:
|
||||||
|
writeByte(SlipProtocolEsc);
|
||||||
|
writeByte(SlipProtocolTransposedEnd);
|
||||||
|
break;
|
||||||
|
case SlipProtocolEsc:
|
||||||
|
writeByte(SlipProtocolEsc);
|
||||||
|
writeByte(SlipProtocolTransposedEsc);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
writeByte(dataByte);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void NymeaLight::writeByte(uint8_t dataByte)
|
||||||
|
{
|
||||||
|
if (m_hardwareSerial) {
|
||||||
|
m_hardwareSerial->write(dataByte);
|
||||||
|
} else if (m_softwareSerial) {
|
||||||
|
m_softwareSerial->write(dataByte);
|
||||||
|
}
|
||||||
|
}
|
||||||
166
ws2812fx/firmware/src/nymealight.h
Normal file
166
ws2812fx/firmware/src/nymealight.h
Normal file
@ -0,0 +1,166 @@
|
|||||||
|
|
||||||
|
/*
|
||||||
|
SLIP transfere: https://tools.ietf.org/html/rfc1055
|
||||||
|
|
||||||
|
Request package format:
|
||||||
|
--------------------------
|
||||||
|
uint8 : command (< 0xF0)
|
||||||
|
uint8 : requestId
|
||||||
|
uint8[] : payload (dynamic size, max: 253)
|
||||||
|
|
||||||
|
|
||||||
|
Response format:
|
||||||
|
--------------------------
|
||||||
|
uint8 : command (< 0xF0)
|
||||||
|
uint8 : requestId (same as request)
|
||||||
|
uint8 : status
|
||||||
|
uint8[] : payload (optional, max: 253)
|
||||||
|
|
||||||
|
|
||||||
|
Notification package format:
|
||||||
|
--------------------------
|
||||||
|
uint8 : command (>= 0xF0)
|
||||||
|
uint8 : notificationId
|
||||||
|
uint8[] : payload (dynamic size, max: 253)
|
||||||
|
|
||||||
|
|
||||||
|
Commands:
|
||||||
|
--------------------------
|
||||||
|
0x00: get ready status
|
||||||
|
payload: empty
|
||||||
|
|
||||||
|
|
||||||
|
0x01: set power
|
||||||
|
payload:
|
||||||
|
uint16 : fade duration in ms
|
||||||
|
uint8 : power (0x00 = off, 0x01 = on)
|
||||||
|
|
||||||
|
|
||||||
|
0x02: set color
|
||||||
|
payload:
|
||||||
|
uint16 : fade duration in ms
|
||||||
|
uint8 : red
|
||||||
|
uint8 : green
|
||||||
|
uint8 : blue
|
||||||
|
|
||||||
|
|
||||||
|
0x03: set brightness
|
||||||
|
payload:
|
||||||
|
uint16 : fade duration in ms
|
||||||
|
uint8 : brightness (0-255)
|
||||||
|
|
||||||
|
|
||||||
|
0x04: set speed
|
||||||
|
payload:
|
||||||
|
uint16 : fade duration in ms
|
||||||
|
uint16 : speed in ms (big endian)
|
||||||
|
|
||||||
|
|
||||||
|
0x05: set effect
|
||||||
|
payload:
|
||||||
|
uint8 : effect, see effect list from WS2812FX
|
||||||
|
|
||||||
|
|
||||||
|
0xef: custom command
|
||||||
|
The payload can be defined free and custom functionality can be implemented
|
||||||
|
|
||||||
|
|
||||||
|
Notifications:
|
||||||
|
--------------------------
|
||||||
|
0xF0: Status ready notification
|
||||||
|
payload: empty
|
||||||
|
|
||||||
|
|
||||||
|
0xF1: Debug message notification
|
||||||
|
payload: debug message characters
|
||||||
|
|
||||||
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <Arduino.h>
|
||||||
|
#include <SoftwareSerial.h>
|
||||||
|
#include <WS2812FX.h>
|
||||||
|
|
||||||
|
class NymeaLight
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
enum Command {
|
||||||
|
CommandGetStatus = 0x00,
|
||||||
|
CommandSetPower = 0x01,
|
||||||
|
CommandSetColor = 0x02,
|
||||||
|
CommandSetBrightness = 0x03,
|
||||||
|
CommandSetSpeed = 0x04,
|
||||||
|
CommandSetEffect = 0x05,
|
||||||
|
CommandCustom = 0xEF
|
||||||
|
};
|
||||||
|
|
||||||
|
enum Notification {
|
||||||
|
NotificationReady = 0xF0,
|
||||||
|
NotificationDebugMessage = 0xF1
|
||||||
|
};
|
||||||
|
|
||||||
|
enum Status {
|
||||||
|
StatusSuccess = 0x00,
|
||||||
|
StatusInvalidProtocol = 0x01,
|
||||||
|
StatusInvalidCommand = 0x02,
|
||||||
|
StatusInvalidPlayload = 0x03,
|
||||||
|
StatusUnknownError = 0xff
|
||||||
|
};
|
||||||
|
|
||||||
|
NymeaLight(WS2812FX *strip);
|
||||||
|
NymeaLight(SoftwareSerial *serial, WS2812FX *strip);
|
||||||
|
NymeaLight(HardwareSerial &serial, WS2812FX *strip);
|
||||||
|
|
||||||
|
WS2812FX *strip() const;
|
||||||
|
|
||||||
|
void init();
|
||||||
|
void process();
|
||||||
|
|
||||||
|
private:
|
||||||
|
enum SlipProtocol {
|
||||||
|
SlipProtocolEnd = 0xC0,
|
||||||
|
SlipProtocolEsc = 0xDB,
|
||||||
|
SlipProtocolTransposedEnd = 0xDC,
|
||||||
|
SlipProtocolTransposedEsc = 0xDD
|
||||||
|
};
|
||||||
|
|
||||||
|
HardwareSerial *m_hardwareSerial = nullptr;
|
||||||
|
SoftwareSerial *m_softwareSerial = nullptr;
|
||||||
|
WS2812FX *m_strip = nullptr;
|
||||||
|
uint8_t m_notificationId = 0;
|
||||||
|
|
||||||
|
// Light states
|
||||||
|
bool m_power = false;
|
||||||
|
uint8_t m_brightness = 0xff;
|
||||||
|
uint32_t m_color = 0x00ffffff;
|
||||||
|
uint8_t m_effect = FX_MODE_STATIC;
|
||||||
|
uint16_t m_speed = 2000;
|
||||||
|
|
||||||
|
// UART read
|
||||||
|
uint8_t m_buffer[255];
|
||||||
|
uint8_t m_bufferIndex = 0;
|
||||||
|
boolean m_protocolEscaping = false;
|
||||||
|
|
||||||
|
// Animations
|
||||||
|
uint32_t m_previouseTime = 0;
|
||||||
|
|
||||||
|
// Brightness animation
|
||||||
|
uint32_t m_brightnessProgress = 0; // us
|
||||||
|
uint8_t m_brightnessStartValue = 0;
|
||||||
|
uint8_t m_brightnessTargetValue = 0;
|
||||||
|
uint8_t m_brightnessFadeDuration = 0; //ms
|
||||||
|
|
||||||
|
void doAnimations();
|
||||||
|
void debugPrint(const char message[]);
|
||||||
|
void flushSerial();
|
||||||
|
void sendReadyNotification();
|
||||||
|
|
||||||
|
protected:
|
||||||
|
virtual void readData();
|
||||||
|
virtual void processReceivedByte(uint8_t receivedByte);
|
||||||
|
virtual void processData(uint8_t buffer[], uint8_t length);
|
||||||
|
virtual void sendResponse(uint8_t command, uint8_t requestId, Status status);
|
||||||
|
virtual void streamByte(uint8_t dataByte, boolean specialCharacter = false);
|
||||||
|
virtual void writeByte(uint8_t dataByte);
|
||||||
|
|
||||||
|
};
|
||||||
11
ws2812fx/firmware/test/README
Normal file
11
ws2812fx/firmware/test/README
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
|
||||||
|
This directory is intended for PlatformIO Unit Testing and project tests.
|
||||||
|
|
||||||
|
Unit Testing is a software testing method by which individual units of
|
||||||
|
source code, sets of one or more MCU program modules together with associated
|
||||||
|
control data, usage procedures, and operating procedures, are tested to
|
||||||
|
determine whether they are fit for use. Unit testing finds problems early
|
||||||
|
in the development cycle.
|
||||||
|
|
||||||
|
More information about PlatformIO Unit Testing:
|
||||||
|
- https://docs.platformio.org/page/plus/unit-testing.html
|
||||||
@ -28,22 +28,7 @@
|
|||||||
*
|
*
|
||||||
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
|
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
|
||||||
|
|
||||||
/*!
|
|
||||||
\page ws2812fx.html
|
|
||||||
\title WS2812FX Control
|
|
||||||
\brief Plug-In to control WS2812FX over USB
|
|
||||||
|
|
||||||
\ingroup plugins
|
|
||||||
\ingroup nymea-plugins
|
|
||||||
|
|
||||||
\chapter Plugin properties
|
|
||||||
Following JSON file contains the definition and the description of all available \l{ThingClass}{DeviceClasses}
|
|
||||||
and \l{Vendor}{Vendors} of this \l{DevicePlugin}.
|
|
||||||
|
|
||||||
For more details how to read this JSON file please check out the documentation for \l{The plugin JSON File}.
|
|
||||||
s
|
|
||||||
\quotefile plugins/deviceplugins/ws2812fx/devicepluginws2812fx.json
|
|
||||||
*/
|
|
||||||
#include <QColor>
|
#include <QColor>
|
||||||
#include <QRgb>
|
#include <QRgb>
|
||||||
|
|
||||||
@ -60,34 +45,46 @@ void IntegrationPluginWs2812fx::setupThing(ThingSetupInfo *info)
|
|||||||
{
|
{
|
||||||
Thing *thing = info->thing();
|
Thing *thing = info->thing();
|
||||||
|
|
||||||
QString interface = thing->paramValue(ws2812fxThingSerialPortParamTypeId).toString();
|
if (thing->thingClassId() == nymeaLightSerialThingClassId) {
|
||||||
|
QString interface = thing->paramValue(nymeaLightSerialThingSerialPortParamTypeId).toString();
|
||||||
|
|
||||||
if (m_usedInterfaces.contains(interface)) {
|
if (m_usedInterfaces.contains(interface)) {
|
||||||
info->finish(Thing::ThingErrorHardwareNotAvailable, QT_TR_NOOP("This serial port is already used."));
|
info->finish(Thing::ThingErrorHardwareNotAvailable, QT_TR_NOOP("This serial port is already in use."));
|
||||||
return;
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
NymeaLightSerialInterface *lightInterface = new NymeaLightSerialInterface(interface, thing);
|
||||||
|
NymeaLight *light = new NymeaLight(lightInterface, this);
|
||||||
|
lightInterface->setParent(light);
|
||||||
|
m_usedInterfaces.append(interface);
|
||||||
|
m_lights.insert(thing, light);
|
||||||
|
|
||||||
|
|
||||||
|
connect(light, &NymeaLight::availableChanged, thing, [=](bool available){
|
||||||
|
qCDebug(dcWs2812fx()) << thing << "available changed" << available;
|
||||||
|
thing->setStateValue(nymeaLightSerialConnectedStateTypeId, available);
|
||||||
|
|
||||||
|
if (available) {
|
||||||
|
// Set the light to the current states
|
||||||
|
light->setPower(thing->stateValue(nymeaLightSerialPowerStateTypeId).toBool());
|
||||||
|
light->setBrightness(thing->stateValue(nymeaLightSerialBrightnessStateTypeId).toUInt());
|
||||||
|
light->setColor(thing->stateValue(nymeaLightSerialColorStateTypeId).value<QColor>());
|
||||||
|
light->setEffect(thing->stateValue(nymeaLightSerialEffectModeStateTypeId).toUInt());
|
||||||
|
light->setSpeed(thing->stateValue(nymeaLightSerialSpeedStateTypeId).toUInt());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
info->finish(Thing::ThingErrorNoError);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
NymeaLightSerialInterface *lightInterface = new NymeaLightSerialInterface(interface, thing);
|
void IntegrationPluginWs2812fx::postSetupThing(Thing *thing)
|
||||||
NymeaLight *light = new NymeaLight(lightInterface, this);
|
{
|
||||||
lightInterface->setParent(light);
|
if (thing->thingClassId() == nymeaLightSerialThingClassId) {
|
||||||
|
NymeaLight *light = m_lights.value(thing);
|
||||||
if (!lightInterface->open()) {
|
light->enable();
|
||||||
qCWarning(dcWs2812fx()) << "Could not open interface" << interface;
|
|
||||||
light->deleteLater();
|
|
||||||
info->finish(Thing::ThingErrorHardwareFailure, QT_TR_NOOP("Error opening serial port."));
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
connect(light, &NymeaLight::availableChanged, thing, [=](bool available){
|
|
||||||
qCDebug(dcWs2812fx()) << thing << "available changed" << available;
|
|
||||||
thing->setStateValue(ws2812fxConnectedStateTypeId, available);
|
|
||||||
});
|
|
||||||
|
|
||||||
qCDebug(dcWs2812fx()) << "Setup successfully serial port" << interface;
|
|
||||||
thing->setStateValue(ws2812fxConnectedStateTypeId, true);
|
|
||||||
m_usedInterfaces.append(interface);
|
|
||||||
m_lights.insert(thing, light);
|
|
||||||
info->finish(Thing::ThingErrorNoError);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -96,15 +93,14 @@ void IntegrationPluginWs2812fx::discoverThings(ThingDiscoveryInfo *info)
|
|||||||
// Create the list of available serial interfaces
|
// Create the list of available serial interfaces
|
||||||
|
|
||||||
Q_FOREACH(QSerialPortInfo port, QSerialPortInfo::availablePorts()) {
|
Q_FOREACH(QSerialPortInfo port, QSerialPortInfo::availablePorts()) {
|
||||||
|
|
||||||
qCDebug(dcWs2812fx()) << "Found serial port:" << port.portName();
|
qCDebug(dcWs2812fx()) << "Found serial port:" << port.portName();
|
||||||
QString description = port.manufacturer() + " " + port.description();
|
QString description = port.systemLocation() + " " + port.manufacturer() + " " + port.description();
|
||||||
ThingDescriptor descriptor(info->thingClassId(), port.portName(), description);
|
ThingDescriptor descriptor(info->thingClassId(), QT_TR_NOOP("Nymea light"), description);
|
||||||
foreach (Thing *existingThing, myThings().filterByParam(ws2812fxThingSerialPortParamTypeId, port.portName())) {
|
foreach (Thing *existingThing, myThings().filterByParam(nymeaLightSerialThingSerialPortParamTypeId, port.systemLocation())) {
|
||||||
descriptor.setThingId(existingThing->id());
|
descriptor.setThingId(existingThing->id());
|
||||||
}
|
}
|
||||||
ParamList parameters;
|
ParamList parameters;
|
||||||
parameters.append(Param(ws2812fxThingSerialPortParamTypeId, port.portName()));
|
parameters.append(Param(nymeaLightSerialThingSerialPortParamTypeId, port.systemLocation()));
|
||||||
descriptor.setParams(parameters);
|
descriptor.setParams(parameters);
|
||||||
info->addThingDescriptor(descriptor);
|
info->addThingDescriptor(descriptor);
|
||||||
}
|
}
|
||||||
@ -122,16 +118,11 @@ void IntegrationPluginWs2812fx::executeAction(ThingActionInfo *info)
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (action.actionTypeId() == ws2812fxPowerActionTypeId) {
|
if (action.actionTypeId() == nymeaLightSerialPowerActionTypeId) {
|
||||||
bool power = action.param(ws2812fxPowerActionPowerParamTypeId).value().toBool();
|
bool power = action.param(nymeaLightSerialPowerActionPowerParamTypeId).value().toBool();
|
||||||
quint8 brightness = 0;
|
|
||||||
if (power) {
|
|
||||||
quint8 brightnessPercentage = thing->stateValue(ws2812fxBrightnessStateTypeId).toUInt();
|
|
||||||
brightness = qRound(255.0 * brightnessPercentage / 100);
|
|
||||||
}
|
|
||||||
|
|
||||||
qCDebug(dcWs2812fx()) << "Set power" << power;
|
qCDebug(dcWs2812fx()) << "Set power" << power;
|
||||||
NymeaLightInterfaceReply *reply = light->setBrightness(brightness, 500);
|
NymeaLightInterfaceReply *reply = light->setPower(power, 500);
|
||||||
connect(info, &ThingActionInfo::aborted, reply, &NymeaLightInterfaceReply::finished);
|
connect(info, &ThingActionInfo::aborted, reply, &NymeaLightInterfaceReply::finished);
|
||||||
connect(reply, &NymeaLightInterfaceReply::finished, this, [=](){
|
connect(reply, &NymeaLightInterfaceReply::finished, this, [=](){
|
||||||
if (reply->status() != NymeaLightInterface::StatusSuccess) {
|
if (reply->status() != NymeaLightInterface::StatusSuccess) {
|
||||||
@ -140,11 +131,11 @@ void IntegrationPluginWs2812fx::executeAction(ThingActionInfo *info)
|
|||||||
}
|
}
|
||||||
|
|
||||||
qCDebug(dcWs2812fx()) << "Set power finished successfully" << power;
|
qCDebug(dcWs2812fx()) << "Set power finished successfully" << power;
|
||||||
thing->setStateValue(ws2812fxPowerStateTypeId, power);
|
thing->setStateValue(nymeaLightSerialPowerStateTypeId, power);
|
||||||
info->finish(Thing::ThingErrorNoError);
|
info->finish(Thing::ThingErrorNoError);
|
||||||
});
|
});
|
||||||
} else if (action.actionTypeId() == ws2812fxColorActionTypeId) {
|
} else if (action.actionTypeId() == nymeaLightSerialColorActionTypeId) {
|
||||||
QColor color = action.param(ws2812fxColorActionColorParamTypeId).value().value<QColor>();
|
QColor color = action.param(nymeaLightSerialColorActionColorParamTypeId).value().value<QColor>();
|
||||||
qCDebug(dcWs2812fx()) << "Set color to" << color.name(QColor::HexRgb);
|
qCDebug(dcWs2812fx()) << "Set color to" << color.name(QColor::HexRgb);
|
||||||
NymeaLightInterfaceReply *reply = light->setColor(color);
|
NymeaLightInterfaceReply *reply = light->setColor(color);
|
||||||
connect(info, &ThingActionInfo::aborted, reply, &NymeaLightInterfaceReply::finished);
|
connect(info, &ThingActionInfo::aborted, reply, &NymeaLightInterfaceReply::finished);
|
||||||
@ -154,12 +145,12 @@ void IntegrationPluginWs2812fx::executeAction(ThingActionInfo *info)
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
qCDebug(dcWs2812fx()) << "Set color finished successfully" << color.name(QColor::HexRgb);
|
qCDebug(dcWs2812fx()) << "Set color finished successfully" << color.name(QColor::HexRgb);
|
||||||
thing->setStateValue(ws2812fxColorStateTypeId, color);
|
thing->setStateValue(nymeaLightSerialColorStateTypeId, color);
|
||||||
info->finish(Thing::ThingErrorNoError);
|
info->finish(Thing::ThingErrorNoError);
|
||||||
});
|
});
|
||||||
} else if (action.actionTypeId() == ws2812fxColorTemperatureActionTypeId) {
|
} else if (action.actionTypeId() == nymeaLightSerialColorTemperatureActionTypeId) {
|
||||||
// minValue 153, maxValue 500
|
// minValue 153, maxValue 500
|
||||||
uint colorTemperature = action.param(ws2812fxColorTemperatureActionColorTemperatureParamTypeId).value().toDouble();
|
uint colorTemperature = action.param(nymeaLightSerialColorTemperatureActionColorTemperatureParamTypeId).value().toDouble();
|
||||||
QColor color;
|
QColor color;
|
||||||
color.setRgb(255, 255, qRound((255.00 - (((colorTemperature - 153.00) / 347.00)) * 255.00)));
|
color.setRgb(255, 255, qRound((255.00 - (((colorTemperature - 153.00) / 347.00)) * 255.00)));
|
||||||
|
|
||||||
@ -173,11 +164,11 @@ void IntegrationPluginWs2812fx::executeAction(ThingActionInfo *info)
|
|||||||
}
|
}
|
||||||
|
|
||||||
qCDebug(dcWs2812fx()) << "Set color temperature finished successfully" << colorTemperature;
|
qCDebug(dcWs2812fx()) << "Set color temperature finished successfully" << colorTemperature;
|
||||||
thing->setStateValue(ws2812fxColorTemperatureStateTypeId, colorTemperature);
|
thing->setStateValue(nymeaLightSerialColorTemperatureStateTypeId, colorTemperature);
|
||||||
info->finish(Thing::ThingErrorNoError);
|
info->finish(Thing::ThingErrorNoError);
|
||||||
});
|
});
|
||||||
} else if (action.actionTypeId() == ws2812fxBrightnessActionTypeId) {
|
} else if (action.actionTypeId() == nymeaLightSerialBrightnessActionTypeId) {
|
||||||
quint8 brightnessPercentage = action.param(ws2812fxBrightnessActionBrightnessParamTypeId).value().toUInt();
|
quint8 brightnessPercentage = action.param(nymeaLightSerialBrightnessActionBrightnessParamTypeId).value().toUInt();
|
||||||
quint8 brightness = qRound(255.0 * brightnessPercentage / 100);
|
quint8 brightness = qRound(255.0 * brightnessPercentage / 100);
|
||||||
qCDebug(dcWs2812fx()) << "Set brightness to" << brightnessPercentage << brightness;
|
qCDebug(dcWs2812fx()) << "Set brightness to" << brightnessPercentage << brightness;
|
||||||
NymeaLightInterfaceReply *reply = light->setBrightness(brightness, 1000);
|
NymeaLightInterfaceReply *reply = light->setBrightness(brightness, 1000);
|
||||||
@ -189,11 +180,11 @@ void IntegrationPluginWs2812fx::executeAction(ThingActionInfo *info)
|
|||||||
}
|
}
|
||||||
|
|
||||||
qCDebug(dcWs2812fx()) << "Set brightness finished successfully" << brightness;
|
qCDebug(dcWs2812fx()) << "Set brightness finished successfully" << brightness;
|
||||||
thing->setStateValue(ws2812fxBrightnessStateTypeId, brightnessPercentage);
|
thing->setStateValue(nymeaLightSerialBrightnessStateTypeId, brightnessPercentage);
|
||||||
info->finish(Thing::ThingErrorNoError);
|
info->finish(Thing::ThingErrorNoError);
|
||||||
});
|
});
|
||||||
} else if (action.actionTypeId() == ws2812fxSpeedActionTypeId) {
|
} else if (action.actionTypeId() == nymeaLightSerialSpeedActionTypeId) {
|
||||||
quint16 speedPercentage = action.param(ws2812fxSpeedActionSpeedParamTypeId).value().toUInt();
|
quint16 speedPercentage = action.param(nymeaLightSerialSpeedActionSpeedParamTypeId).value().toUInt();
|
||||||
quint16 speed = 2000 - (speedPercentage * 20);
|
quint16 speed = 2000 - (speedPercentage * 20);
|
||||||
|
|
||||||
qCDebug(dcWs2812fx()) << "Set speed" << speedPercentage << "%" << speed << "ms";
|
qCDebug(dcWs2812fx()) << "Set speed" << speedPercentage << "%" << speed << "ms";
|
||||||
@ -206,11 +197,11 @@ void IntegrationPluginWs2812fx::executeAction(ThingActionInfo *info)
|
|||||||
}
|
}
|
||||||
|
|
||||||
qCDebug(dcWs2812fx()) << "Set speed finished successfully" << speedPercentage << "%" << speed << "ms";
|
qCDebug(dcWs2812fx()) << "Set speed finished successfully" << speedPercentage << "%" << speed << "ms";
|
||||||
thing->setStateValue(ws2812fxSpeedStateTypeId, speedPercentage);
|
thing->setStateValue(nymeaLightSerialSpeedStateTypeId, speedPercentage);
|
||||||
info->finish(Thing::ThingErrorNoError);
|
info->finish(Thing::ThingErrorNoError);
|
||||||
});
|
});
|
||||||
} else if (action.actionTypeId() == ws2812fxEffectModeActionTypeId) {
|
} else if (action.actionTypeId() == nymeaLightSerialEffectModeActionTypeId) {
|
||||||
QString effectMode = action.param(ws2812fxEffectModeActionEffectModeParamTypeId).value().toString();
|
QString effectMode = action.param(nymeaLightSerialEffectModeActionEffectModeParamTypeId).value().toString();
|
||||||
quint8 mode = FX_MODE_STATIC;
|
quint8 mode = FX_MODE_STATIC;
|
||||||
if (effectMode == "Static") {
|
if (effectMode == "Static") {
|
||||||
mode = FX_MODE_STATIC;
|
mode = FX_MODE_STATIC;
|
||||||
@ -340,7 +331,7 @@ void IntegrationPluginWs2812fx::executeAction(ThingActionInfo *info)
|
|||||||
}
|
}
|
||||||
|
|
||||||
qCDebug(dcWs2812fx()) << "Set mode finished successfully" << effectMode << mode;
|
qCDebug(dcWs2812fx()) << "Set mode finished successfully" << effectMode << mode;
|
||||||
thing->setStateValue(ws2812fxEffectModeStateTypeId, effectMode);
|
thing->setStateValue(nymeaLightSerialEffectModeStateTypeId, effectMode);
|
||||||
info->finish(Thing::ThingErrorNoError);
|
info->finish(Thing::ThingErrorNoError);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -349,9 +340,8 @@ void IntegrationPluginWs2812fx::executeAction(ThingActionInfo *info)
|
|||||||
|
|
||||||
void IntegrationPluginWs2812fx::thingRemoved(Thing *thing)
|
void IntegrationPluginWs2812fx::thingRemoved(Thing *thing)
|
||||||
{
|
{
|
||||||
if (thing->thingClassId() == ws2812fxThingClassId) {
|
if (thing->thingClassId() == nymeaLightSerialThingClassId) {
|
||||||
|
m_usedInterfaces.removeAll(thing->paramValue(nymeaLightSerialThingSerialPortParamTypeId).toString());
|
||||||
m_usedInterfaces.removeAll(thing->paramValue(ws2812fxThingSerialPortParamTypeId).toString());
|
|
||||||
NymeaLight *light = m_lights.take(thing);
|
NymeaLight *light = m_lights.take(thing);
|
||||||
light->deleteLater();
|
light->deleteLater();
|
||||||
}
|
}
|
||||||
|
|||||||
@ -111,6 +111,7 @@ public:
|
|||||||
explicit IntegrationPluginWs2812fx();
|
explicit IntegrationPluginWs2812fx();
|
||||||
|
|
||||||
void setupThing(ThingSetupInfo *info) override;
|
void setupThing(ThingSetupInfo *info) override;
|
||||||
|
void postSetupThing(Thing *thing) override;
|
||||||
void thingRemoved(Thing *thing) override;
|
void thingRemoved(Thing *thing) override;
|
||||||
void discoverThings(ThingDiscoveryInfo *info) override;
|
void discoverThings(ThingDiscoveryInfo *info) override;
|
||||||
void executeAction(ThingActionInfo *info) override;
|
void executeAction(ThingActionInfo *info) override;
|
||||||
|
|||||||
@ -10,8 +10,8 @@
|
|||||||
"thingClasses": [
|
"thingClasses": [
|
||||||
{
|
{
|
||||||
"id": "24364e36-b199-4e35-b468-c44da58c009c",
|
"id": "24364e36-b199-4e35-b468-c44da58c009c",
|
||||||
"name": "ws2812fx",
|
"name": "nymeaLightSerial",
|
||||||
"displayName": "WS2812FX",
|
"displayName": "Serial nymea light",
|
||||||
"createMethods": ["user", "discovery"],
|
"createMethods": ["user", "discovery"],
|
||||||
"interfaces": ["colorlight", "connectable"],
|
"interfaces": ["colorlight", "connectable"],
|
||||||
"paramTypes": [
|
"paramTypes": [
|
||||||
@ -21,7 +21,7 @@
|
|||||||
"displayName": "Serial port",
|
"displayName": "Serial port",
|
||||||
"type": "QString",
|
"type": "QString",
|
||||||
"inputType": "TextLine",
|
"inputType": "TextLine",
|
||||||
"defaultValue": "ttyAMC0"
|
"defaultValue": "/dev/ttyAMC0"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"stateTypes": [
|
"stateTypes": [
|
||||||
@ -63,7 +63,7 @@
|
|||||||
"displayNameEvent": "color changed",
|
"displayNameEvent": "color changed",
|
||||||
"displayNameAction": "Set color",
|
"displayNameAction": "Set color",
|
||||||
"type": "QColor",
|
"type": "QColor",
|
||||||
"defaultValue": "#000000",
|
"defaultValue": "#FFFFFF",
|
||||||
"writable": true
|
"writable": true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -74,7 +74,7 @@
|
|||||||
"displayNameAction": "Set brigtness",
|
"displayNameAction": "Set brigtness",
|
||||||
"type": "int",
|
"type": "int",
|
||||||
"unit": "Percentage",
|
"unit": "Percentage",
|
||||||
"defaultValue": 0,
|
"defaultValue": 100,
|
||||||
"minValue": 0,
|
"minValue": 0,
|
||||||
"maxValue": 100,
|
"maxValue": 100,
|
||||||
"writable": true
|
"writable": true
|
||||||
@ -87,7 +87,7 @@
|
|||||||
"displayNameAction": "Set speed",
|
"displayNameAction": "Set speed",
|
||||||
"type": "int",
|
"type": "int",
|
||||||
"unit": "Percentage",
|
"unit": "Percentage",
|
||||||
"defaultValue": 0,
|
"defaultValue": 50,
|
||||||
"minValue": 0,
|
"minValue": 0,
|
||||||
"maxValue": 100,
|
"maxValue": 100,
|
||||||
"writable": true
|
"writable": true
|
||||||
|
|||||||
@ -7,12 +7,36 @@ NymeaLight::NymeaLight(NymeaLightInterface *interface, QObject *parent) :
|
|||||||
QObject(parent),
|
QObject(parent),
|
||||||
m_interface(interface)
|
m_interface(interface)
|
||||||
{
|
{
|
||||||
|
connect(m_interface, &NymeaLightInterface::dataReceived, this, &NymeaLight::onDataReceived);
|
||||||
connect(m_interface, &NymeaLightInterface::availableChanged, this, [=](bool available){
|
connect(m_interface, &NymeaLightInterface::availableChanged, this, [=](bool available){
|
||||||
qCDebug(dcWs2812fx()) << "Interface available changed" << available;
|
m_interfaceAvailable = available;
|
||||||
emit availableChanged(available);
|
if (m_interfaceAvailable) {
|
||||||
|
qCDebug(dcWs2812fx()) << "Nymea light interface is now available. Start polling status of the light controller...";
|
||||||
|
m_pollStatusRetryCount = 0;
|
||||||
|
pollStatus();
|
||||||
|
} else {
|
||||||
|
m_ready = false;
|
||||||
|
m_requestId = 0;
|
||||||
|
emit availableChanged(false);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
connect(m_interface, &NymeaLightInterface::dataReceived, this, &NymeaLight::onDataReceived);
|
}
|
||||||
|
|
||||||
|
NymeaLightInterfaceReply *NymeaLight::setPower(bool power, quint16 fadeDuration)
|
||||||
|
{
|
||||||
|
// Build the request
|
||||||
|
QByteArray requestData;
|
||||||
|
QDataStream stream(&requestData, QIODevice::WriteOnly);
|
||||||
|
stream << static_cast<quint8>(NymeaLightInterface::CommandSetPower);
|
||||||
|
stream << m_requestId++;
|
||||||
|
stream << fadeDuration;
|
||||||
|
stream << static_cast<quint8>(power ? 0x01 : 0x00);
|
||||||
|
|
||||||
|
NymeaLightInterfaceReply *reply = createReply(requestData);
|
||||||
|
m_pendingRequests.enqueue(reply);
|
||||||
|
sendNextRequest();
|
||||||
|
return reply;
|
||||||
}
|
}
|
||||||
|
|
||||||
NymeaLightInterfaceReply *NymeaLight::setColor(const QColor &color, quint16 fadeDuration)
|
NymeaLightInterfaceReply *NymeaLight::setColor(const QColor &color, quint16 fadeDuration)
|
||||||
@ -82,7 +106,19 @@ NymeaLightInterfaceReply *NymeaLight::setEffect(quint8 effect)
|
|||||||
|
|
||||||
bool NymeaLight::available() const
|
bool NymeaLight::available() const
|
||||||
{
|
{
|
||||||
return m_interface->available();
|
return m_interfaceAvailable && m_ready;
|
||||||
|
}
|
||||||
|
|
||||||
|
void NymeaLight::enable()
|
||||||
|
{
|
||||||
|
m_interface->open();
|
||||||
|
qCDebug(dcWs2812fx()) << "Nymea light enabled";
|
||||||
|
}
|
||||||
|
|
||||||
|
void NymeaLight::disable()
|
||||||
|
{
|
||||||
|
m_interface->close();
|
||||||
|
qCDebug(dcWs2812fx()) << "Nymea light disabled";
|
||||||
}
|
}
|
||||||
|
|
||||||
NymeaLightInterfaceReply *NymeaLight::createReply(const QByteArray &requestData)
|
NymeaLightInterfaceReply *NymeaLight::createReply(const QByteArray &requestData)
|
||||||
@ -114,36 +150,93 @@ void NymeaLight::sendNextRequest()
|
|||||||
m_currentReply->m_timer->start();
|
m_currentReply->m_timer->start();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void NymeaLight::pollStatus()
|
||||||
|
{
|
||||||
|
// Request ready state from controller
|
||||||
|
NymeaLightInterfaceReply *reply = getStatus();
|
||||||
|
connect(reply, &NymeaLightInterfaceReply::finished, this, [=](){
|
||||||
|
if (reply->status() == NymeaLightInterface::StatusSuccess) {
|
||||||
|
qCDebug(dcWs2812fx()) << "Get status request finished successfully. The firmware is ready to operate.";
|
||||||
|
m_ready = true;
|
||||||
|
m_pollStatusRetryCount = 0;
|
||||||
|
emit availableChanged(true);
|
||||||
|
} else {
|
||||||
|
m_pollStatusRetryCount++;
|
||||||
|
if (m_pollStatusRetryCount >= m_pollStatusRetryLimit) {
|
||||||
|
qCWarning(dcWs2812fx()) << "Firmware did not respond to get status request after" << m_pollStatusRetryCount << "attempts. Giving up.";
|
||||||
|
m_ready = false;
|
||||||
|
} else {
|
||||||
|
if (!m_ready && m_interfaceAvailable) {
|
||||||
|
qCDebug(dcWs2812fx()) << "Get status request finished with error" << reply->status() << "Retry" << m_pollStatusRetryCount << "/" << m_pollStatusRetryLimit;
|
||||||
|
pollStatus();
|
||||||
|
} else {
|
||||||
|
qCDebug(dcWs2812fx()) << "Get status request finished with error, but that's ok since we received the ready notification." << reply->status();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
NymeaLightInterfaceReply *NymeaLight::getStatus()
|
||||||
|
{
|
||||||
|
qCDebug(dcWs2812fx()) << "Request status of nymea light";
|
||||||
|
QByteArray requestData;
|
||||||
|
QDataStream stream(&requestData, QIODevice::WriteOnly);
|
||||||
|
stream << static_cast<quint8>(NymeaLightInterface::CommandGetStatus);
|
||||||
|
stream << m_requestId++;
|
||||||
|
|
||||||
|
NymeaLightInterfaceReply *reply = createReply(requestData);
|
||||||
|
m_pendingRequests.enqueue(reply);
|
||||||
|
sendNextRequest();
|
||||||
|
return reply;
|
||||||
|
}
|
||||||
|
|
||||||
void NymeaLight::onDataReceived(const QByteArray &data)
|
void NymeaLight::onDataReceived(const QByteArray &data)
|
||||||
{
|
{
|
||||||
Q_ASSERT(data.length() >= 3);
|
Q_ASSERT(data.length() >= 3);
|
||||||
|
|
||||||
NymeaLightInterface::Command command = static_cast<NymeaLightInterface::Command>(static_cast<quint8>((data.at(0))));
|
quint8 commandInt = static_cast<quint8>((data.at(0)));
|
||||||
quint8 requestId = static_cast<quint8>(data.at(1));
|
quint8 requestId = static_cast<quint8>(data.at(1));
|
||||||
//qCDebug(dcWs2812fx()) << "Recived data" << command << requestId << data.toHex();
|
|
||||||
|
|
||||||
if (command == NymeaLightInterface::CommandDebug) {
|
qCDebug(dcWs2812fx()) << "Recived data" << commandInt << requestId << data.toHex();
|
||||||
qCDebug(dcWs2812fx()) << "Firmware debug:" << QString::fromUtf8(data.right(data.length() - 2));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
NymeaLightInterface::Status status = static_cast<NymeaLightInterface::Status>(data.at(2));
|
// Check if command or notification
|
||||||
|
if (commandInt < 0xF0) {
|
||||||
|
NymeaLightInterface::Command command = static_cast<NymeaLightInterface::Command>(commandInt);
|
||||||
|
|
||||||
if (m_currentReply) {
|
NymeaLightInterface::Status status = static_cast<NymeaLightInterface::Status>(data.at(2));
|
||||||
if (m_currentReply->command() == command && m_currentReply->requestId() == requestId) {
|
|
||||||
m_currentReply->m_timer->stop();
|
|
||||||
m_currentReply->m_status = status;
|
|
||||||
|
|
||||||
if (status != NymeaLightInterface::StatusSuccess) {
|
if (m_currentReply) {
|
||||||
qCWarning(dcWs2812fx()) << "Request finished with error" << command << m_currentReply->requestId() << status;
|
if (m_currentReply->command() == command && m_currentReply->requestId() == requestId) {
|
||||||
} else {
|
m_currentReply->m_timer->stop();
|
||||||
qCDebug(dcWs2812fx()) << "Request finished" << command << m_currentReply->requestId() << status;
|
m_currentReply->m_status = status;
|
||||||
|
|
||||||
|
if (status != NymeaLightInterface::StatusSuccess) {
|
||||||
|
qCWarning(dcWs2812fx()) << "Request finished with error" << command << m_currentReply->requestId() << status;
|
||||||
|
} else {
|
||||||
|
qCDebug(dcWs2812fx()) << "Request finished" << command << m_currentReply->requestId() << status;
|
||||||
|
}
|
||||||
|
|
||||||
|
emit m_currentReply->finished();
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
emit m_currentReply->finished();
|
qCWarning(dcWs2812fx()) << "Received unhandled command response data" << data.toHex();
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
qCWarning(dcWs2812fx()) << "Received unhandled data" << data.toHex();
|
NymeaLightInterface::Notification notification = static_cast<NymeaLightInterface::Notification>(commandInt);
|
||||||
|
switch (notification) {
|
||||||
|
case NymeaLightInterface::NotificationReady:
|
||||||
|
qCDebug(dcWs2812fx()) << "Controller ready notification received";
|
||||||
|
m_ready = true;
|
||||||
|
emit availableChanged(true);
|
||||||
|
break;
|
||||||
|
case NymeaLightInterface::NotificationDebugMessage:
|
||||||
|
qCDebug(dcWs2812fx()) << "Firmware debug:" << QString::fromUtf8(data.right(data.length() - 2));
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
qCWarning(dcWs2812fx()) << "Unhandled notification received" << data.toHex();
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -15,6 +15,7 @@ public:
|
|||||||
|
|
||||||
// Set the color. If fade duration is 0, the color will be set immediatly,
|
// Set the color. If fade duration is 0, the color will be set immediatly,
|
||||||
// otherwise it will fade to the color with the given fade duration
|
// otherwise it will fade to the color with the given fade duration
|
||||||
|
NymeaLightInterfaceReply *setPower(bool power, quint16 fadeDuration = 0);
|
||||||
NymeaLightInterfaceReply *setColor(const QColor &color, quint16 fadeDuration = 0);
|
NymeaLightInterfaceReply *setColor(const QColor &color, quint16 fadeDuration = 0);
|
||||||
NymeaLightInterfaceReply *setBrightness(quint8 brightness, quint16 fadeDuration = 0);
|
NymeaLightInterfaceReply *setBrightness(quint8 brightness, quint16 fadeDuration = 0);
|
||||||
NymeaLightInterfaceReply *setSpeed(quint16 speed, quint16 fadeDuration = 0);
|
NymeaLightInterfaceReply *setSpeed(quint16 speed, quint16 fadeDuration = 0);
|
||||||
@ -22,9 +23,17 @@ public:
|
|||||||
|
|
||||||
bool available() const;
|
bool available() const;
|
||||||
|
|
||||||
|
public slots:
|
||||||
|
void enable();
|
||||||
|
void disable();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
NymeaLightInterface *m_interface = nullptr;
|
NymeaLightInterface *m_interface = nullptr;
|
||||||
quint8 m_requestId = 0;
|
quint8 m_requestId = 0;
|
||||||
|
bool m_interfaceAvailable = false;
|
||||||
|
bool m_ready = false;
|
||||||
|
int m_pollStatusRetryCount = 0;
|
||||||
|
int m_pollStatusRetryLimit = 5;
|
||||||
|
|
||||||
NymeaLightInterfaceReply *m_currentReply = nullptr;
|
NymeaLightInterfaceReply *m_currentReply = nullptr;
|
||||||
QQueue<NymeaLightInterfaceReply *> m_pendingRequests;
|
QQueue<NymeaLightInterfaceReply *> m_pendingRequests;
|
||||||
@ -32,6 +41,11 @@ private:
|
|||||||
NymeaLightInterfaceReply *createReply(const QByteArray &requestData);
|
NymeaLightInterfaceReply *createReply(const QByteArray &requestData);
|
||||||
void sendNextRequest();
|
void sendNextRequest();
|
||||||
|
|
||||||
|
|
||||||
|
void pollStatus();
|
||||||
|
NymeaLightInterfaceReply *getStatus();
|
||||||
|
|
||||||
|
|
||||||
private slots:
|
private slots:
|
||||||
void onDataReceived(const QByteArray &data);
|
void onDataReceived(const QByteArray &data);
|
||||||
|
|
||||||
|
|||||||
@ -1,6 +0,0 @@
|
|||||||
#include "nymealightinterface.h"
|
|
||||||
|
|
||||||
NymeaLightInterface::NymeaLightInterface(QObject *parent) : QObject(parent)
|
|
||||||
{
|
|
||||||
|
|
||||||
}
|
|
||||||
@ -9,15 +9,22 @@ class NymeaLightInterface : public QObject
|
|||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
public:
|
public:
|
||||||
enum Command {
|
enum Command {
|
||||||
CommandSetColor = 0x00,
|
CommandGetStatus = 0x00,
|
||||||
CommandSetBrightness = 0x01,
|
CommandSetPower = 0x01,
|
||||||
CommandSetSpeed = 0x02,
|
CommandSetColor = 0x02,
|
||||||
CommandSetEffect = 0x03,
|
CommandSetBrightness = 0x03,
|
||||||
CommandDebug = 0xFE,
|
CommandSetSpeed = 0x04,
|
||||||
CommandCustom = 0xFF
|
CommandSetEffect = 0x05,
|
||||||
|
CommandCustom = 0xEF
|
||||||
};
|
};
|
||||||
Q_ENUM(Command)
|
Q_ENUM(Command)
|
||||||
|
|
||||||
|
enum Notification {
|
||||||
|
NotificationReady = 0xF0,
|
||||||
|
NotificationDebugMessage = 0xF1
|
||||||
|
};
|
||||||
|
Q_ENUM(Notification)
|
||||||
|
|
||||||
enum Status {
|
enum Status {
|
||||||
StatusSuccess = 0x00,
|
StatusSuccess = 0x00,
|
||||||
StatusInvalidProtocol = 0x01,
|
StatusInvalidProtocol = 0x01,
|
||||||
@ -34,7 +41,8 @@ public:
|
|||||||
};
|
};
|
||||||
Q_ENUM(Mode)
|
Q_ENUM(Mode)
|
||||||
|
|
||||||
explicit NymeaLightInterface(QObject *parent = nullptr);
|
inline explicit NymeaLightInterface(QObject *parent = nullptr) : QObject(parent) { };
|
||||||
|
virtual ~NymeaLightInterface() = default;
|
||||||
|
|
||||||
virtual bool open() = 0;
|
virtual bool open() = 0;
|
||||||
virtual void close() = 0;
|
virtual void close() = 0;
|
||||||
|
|||||||
@ -4,9 +4,10 @@
|
|||||||
#include <QDataStream>
|
#include <QDataStream>
|
||||||
|
|
||||||
NymeaLightSerialInterface::NymeaLightSerialInterface(const QString &name, QObject *parent) :
|
NymeaLightSerialInterface::NymeaLightSerialInterface(const QString &name, QObject *parent) :
|
||||||
NymeaLightInterface(parent)
|
NymeaLightInterface(parent),
|
||||||
|
m_serialPortName(name)
|
||||||
{
|
{
|
||||||
m_serialPort = new QSerialPort(name, this);
|
m_serialPort = new QSerialPort(m_serialPortName, this);
|
||||||
m_serialPort->setBaudRate(115200);
|
m_serialPort->setBaudRate(115200);
|
||||||
m_serialPort->setDataBits(QSerialPort::DataBits::Data8);
|
m_serialPort->setDataBits(QSerialPort::DataBits::Data8);
|
||||||
m_serialPort->setParity(QSerialPort::Parity::NoParity);
|
m_serialPort->setParity(QSerialPort::Parity::NoParity);
|
||||||
@ -33,8 +34,26 @@ NymeaLightSerialInterface::NymeaLightSerialInterface(const QString &name, QObjec
|
|||||||
|
|
||||||
bool NymeaLightSerialInterface::open()
|
bool NymeaLightSerialInterface::open()
|
||||||
{
|
{
|
||||||
|
bool serialPortFound = false;
|
||||||
|
foreach(const QSerialPortInfo &serialPortInfo, QSerialPortInfo::availablePorts()) {
|
||||||
|
if (serialPortInfo.systemLocation() == m_serialPortName) {
|
||||||
|
serialPortFound = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Prevent repeating warnings...
|
||||||
|
if (!serialPortFound) {
|
||||||
|
if (!m_reconnectTimer->isActive()) {
|
||||||
|
m_reconnectTimer->start();
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// The serial port is available...lt's try to open it
|
||||||
if (!m_serialPort->open(QIODevice::ReadWrite)) {
|
if (!m_serialPort->open(QIODevice::ReadWrite)) {
|
||||||
qCWarning(dcWs2812fx()) << "Could not open serial port" << m_serialPort->portName() << m_serialPort->errorString();
|
qCWarning(dcWs2812fx()) << "Could not open serial port" << m_serialPort->portName() << m_serialPort->errorString();
|
||||||
|
m_reconnectTimer->start();
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -130,7 +149,7 @@ void NymeaLightSerialInterface::onReadyRead()
|
|||||||
void NymeaLightSerialInterface::onSerialError(QSerialPort::SerialPortError error)
|
void NymeaLightSerialInterface::onSerialError(QSerialPort::SerialPortError error)
|
||||||
{
|
{
|
||||||
if (error != QSerialPort::NoError && m_serialPort->isOpen()) {
|
if (error != QSerialPort::NoError && m_serialPort->isOpen()) {
|
||||||
qCCritical(dcWs2812fx()) << "Serial port error:" << error << m_serialPort->errorString();
|
qCWarning(dcWs2812fx()) << "Serial port error:" << error << m_serialPort->errorString();
|
||||||
m_reconnectTimer->start();
|
m_reconnectTimer->start();
|
||||||
m_serialPort->close();
|
m_serialPort->close();
|
||||||
emit availableChanged(false);
|
emit availableChanged(false);
|
||||||
|
|||||||
@ -1,9 +1,10 @@
|
|||||||
#ifndef NYMEALIGHTSERIALINTERFACE_H
|
#ifndef NYMEALIGHTSERIALINTERFACE_H
|
||||||
#define NYMEALIGHTSERIALINTERFACE_H
|
#define NYMEALIGHTSERIALINTERFACE_H
|
||||||
|
|
||||||
|
#include <QTimer>
|
||||||
#include <QObject>
|
#include <QObject>
|
||||||
#include <QSerialPort>
|
#include <QSerialPort>
|
||||||
#include <QTimer>
|
#include <QSerialPortInfo>
|
||||||
|
|
||||||
#include "nymealightinterface.h"
|
#include "nymealightinterface.h"
|
||||||
|
|
||||||
@ -12,10 +13,12 @@ class NymeaLightSerialInterface : public NymeaLightInterface
|
|||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
public:
|
public:
|
||||||
explicit NymeaLightSerialInterface(const QString &name, QObject *parent = nullptr);
|
explicit NymeaLightSerialInterface(const QString &name, QObject *parent = nullptr);
|
||||||
|
~NymeaLightSerialInterface() override = default;
|
||||||
|
|
||||||
bool open() override;
|
bool open() override;
|
||||||
void close() override;
|
void close() override;
|
||||||
bool available() override;
|
bool available() override;
|
||||||
|
|
||||||
void sendData(const QByteArray &data) override;
|
void sendData(const QByteArray &data) override;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
@ -26,6 +29,7 @@ private:
|
|||||||
SlipProtocolTransposedEsc = 0xDD
|
SlipProtocolTransposedEsc = 0xDD
|
||||||
};
|
};
|
||||||
|
|
||||||
|
QString m_serialPortName;
|
||||||
QTimer *m_reconnectTimer = nullptr;
|
QTimer *m_reconnectTimer = nullptr;
|
||||||
QSerialPort *m_serialPort = nullptr;
|
QSerialPort *m_serialPort = nullptr;
|
||||||
QByteArray m_buffer;
|
QByteArray m_buffer;
|
||||||
|
|||||||
@ -7,7 +7,6 @@ TARGET = $$qtLibraryTarget(nymea_integrationpluginws2812fx)
|
|||||||
SOURCES += \
|
SOURCES += \
|
||||||
integrationpluginws2812fx.cpp \
|
integrationpluginws2812fx.cpp \
|
||||||
nymealight.cpp \
|
nymealight.cpp \
|
||||||
nymealightinterface.cpp \
|
|
||||||
nymealightserialinterface.cpp
|
nymealightserialinterface.cpp
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user