diff --git a/CLC-qthing/CMakeLists.txt b/CLC-qthing/CMakeLists.txt new file mode 100644 index 0000000000000000000000000000000000000000..2dd04d521bbadac7a320499c479725e8470367a5 --- /dev/null +++ b/CLC-qthing/CMakeLists.txt @@ -0,0 +1,11 @@ +idf_component_register( + SRC_DIRS + "." + + INCLUDE_DIRS + "." + + REQUIRES + main +) +component_compile_options(-std=c++17) diff --git a/CLC-qthing/Controller.cpp b/CLC-qthing/Controller.cpp index 30495beab4bca082dc437d435cfd59e34b0b0dbd..dbc5ebb63c2a3ffe5bfb4652fb951a02c8d23eda 100644 --- a/CLC-qthing/Controller.cpp +++ b/CLC-qthing/Controller.cpp @@ -1,25 +1,27 @@ #include "CyanLightControl.hpp" -#include "qthing.h" -#include "mqtt_common.h" +#include <qthing> +#include <qthing/mqtt_common.hpp> #include <string> #include <cstdio> #include <cstdlib> -#include <function> #include <algorithm> +#include <functional> #include "esp_err.h" #include "esp_log.h" #include "driver/ledc.h" +using namespace qthing; + const char *TAG = "CLC"; const std::string delimiter = ":"; -float CyanLight::bytes2float(uint8_t *bytes) { +static float bytes2float(const char *bytes) { float f; memcpy(&f, bytes, sizeof(f)); @@ -28,12 +30,12 @@ float CyanLight::bytes2float(uint8_t *bytes) { } -CyanLight::Controller::Controller(uint8_t networkChannel, uint8_t channelsConfigured) : networkChannel{networkChannel} { +CyanLight::Controller::Controller(uint16_t baseChannel, uint8_t channelsConfigured) : baseChannel{baseChannel} { - this->channelsConfigured = std::max(channelsConfigured, CyanLight::MAX_CHANNELS); + this->channelsConfigured = std::min(channelsConfigured, CyanLight::MAX_CHANNELS); this->channels = new CyanLight::PWMChannel*[this->channelsConfigured]; - //this->channels = (CyanLight::PWMChannel **) malloc(this->channelsConfigured * sizeof(CyanLight::PWMChannel*)); + this->timer_cfg.duty_resolution = (ledc_timer_bit_t)this->resolution; this->timer_cfg.freq_hz = this->frequency; @@ -46,22 +48,32 @@ CyanLight::Controller::Controller(uint8_t networkChannel, uint8_t channelsConfig ledc_fade_func_install(0); - for (uint8_t i = 0; i < channelsConfigured; i++) { + for (uint8_t i = 0; i < this->channelsConfigured; i++) { this->channels[i] = new CyanLight::PWMChannel(i, CyanLight::channelGPIOS[i]); - char topic[256]; - snprintf(topic, sizeof(topic), DEVICE_NAMESPACE "CyanLight/pwm/$%i", i); - - add_message_callback(topic, [&, i](std::string message) { - - this->setChannel(i, strtof(message.c_str(), NULL)); + char topic[32]; + snprintf(topic, sizeof(topic), "pwm/$%i", i); - // float f = strtof(message.c_str(), NULL); - // ESP_LOGW(TAG, "i: %d msg: %s f: %f", i, message.c_str(), f); + add_message_callback(this->genDeviceTopic(topic), [&, i](std::string message) { + this->setPWM(i, strtof(message.c_str(), NULL)); + this->callPacketCallback(); }); } + std::string header("fxCyanF"); + uint8_t headerLength = header.length(); + + qthing::addUDPPacketCallback(header, [&, headerLength](udpPacket packet) { + this->handleUnicast(packet.payload + headerLength, packet.length - headerLength); + }); + + + std::function<void(std::string)> setCh = [&](std::string message) { + long int ch = strtol(message.c_str(), NULL, 0); + this->setBaseChannel(ch); + }; + std::function<void(std::string)> setFrq = [&](std::string message) { long int frq = strtol(message.c_str(), NULL, 0); this->setFrequency(frq); @@ -95,6 +107,14 @@ CyanLight::Controller::Controller(uint8_t networkChannel, uint8_t channelsConfig }; + std::function<void(std::string)> getBCh = [&](std::string ignored) { + this->publishBaseChannel(); + }; + + std::function<void(std::string)> getChs = [&](std::string ignored) { + this->publishChannelCount(); + }; + std::function<void(std::string)> getFrq = [&](std::string ignored) { this->publishFrequency(); }; @@ -109,34 +129,82 @@ CyanLight::Controller::Controller(uint8_t networkChannel, uint8_t channelsConfig // device-local setters - add_message_callback(DEVICE_NAMESPACE "CyanLight/frqres/set", setFrqRes); - add_message_callback(DEVICE_NAMESPACE "CyanLight/frequency/set", setFrq); - add_message_callback(DEVICE_NAMESPACE "CyanLight/resolution/set", setRes); + add_message_callback(this->genDeviceTopic("frqres/set"), setFrqRes); + add_message_callback(this->genDeviceTopic("channel/set"), setCh); + add_message_callback(this->genDeviceTopic("frequency/set"), setFrq); + add_message_callback(this->genDeviceTopic("resolution/set"), setRes); // device-lokal getters - add_message_callback(DEVICE_NAMESPACE "CyanLight/frequency/get", getFrq); - add_message_callback(DEVICE_NAMESPACE "CyanLight/resolution/get", getRes); - add_message_callback(DEVICE_NAMESPACE "CyanLight/frqres/get", getFrqRes); + add_message_callback(this->genDeviceTopic("frequency/get"), getFrq); + add_message_callback(this->genDeviceTopic("resolution/get"), getRes); + add_message_callback(this->genDeviceTopic("frqres/get"), getFrqRes); + add_message_callback(this->genDeviceTopic("channel/get"), getBCh); + add_message_callback(this->genDeviceTopic("channelCnt/get"), getChs); // global setters - add_message_callback("service/CyanLight/frqres/set", setFrqRes); - add_message_callback("service/CyanLight/frequency/set", setFrq); - add_message_callback("service/CyanLight/resolution/set", setRes); + add_message_callback(this->genServiceTopic("frqres/set"), setFrqRes); + add_message_callback(this->genServiceTopic("channel/set"), setCh); + add_message_callback(this->genServiceTopic("frequency/set"), setFrq); + add_message_callback(this->genServiceTopic("resolution/set"), setRes); // global getters - add_message_callback("service/CyanLight/frequency/get", getFrq); - add_message_callback("service/CyanLight/resolution/get", getRes); - add_message_callback("service/CyanLight/frqres/get", getFrqRes); + add_message_callback(this->genServiceTopic("frequency/get"), getFrq); + add_message_callback(this->genServiceTopic("resolution/get"), getRes); + add_message_callback(this->genServiceTopic("frqres/get"), getFrqRes); + add_message_callback(this->genServiceTopic("channel/get"), getBCh); + add_message_callback(this->genServiceTopic("channelCnt/get"), getChs); +} + + +bool CyanLight::Controller::handleUnicast(const char *data, std::size_t length) { + std::size_t size = this->getChannelCount() * sizeof(float); + + int32_t diff = length - size; + if (diff < 0) { + ESP_LOGE(TAG, "Invalid data length[ %i ]: Received ΔB = %i bytes too few", length, -diff); + return false; + } + + for (uint8_t ch = 0; ch < this->getChannelCount(); ch++) { + float f = bytes2float(data + ch * sizeof(float)); + this->setPWM(ch, f); + } + + this->callPacketCallback(); + + return true; +} + +bool CyanLight::Controller::handleBroadcast(const char *data, std::size_t length) { + std::size_t size = this->getChannelCount() * sizeof(float); + std::size_t offset = this->getBaseChannel() * sizeof(float); + + int32_t diff = length - offset - size; + if (diff < 0) { // TODO: test thoroughly! + ESP_LOGE(TAG, "Invalid data length[ %i ]: Received ΔB = %i bytes too few", length, -diff); + return false; + } + + for (uint8_t ch = 0; ch < this->getChannelCount(); ch++) { + float f = bytes2float(data + offset + ch * sizeof(float)); + this->setPWM(ch, f); + } + + this->callPacketCallback(); + + return true; } -void CyanLight::Controller::setChannel(uint8_t channel, float value) { +void CyanLight::Controller::setPWM(uint8_t channel, float value) { if (channel >= this->channelsConfigured) { ESP_LOGW(TAG, "ChannelID out of range: channel[ %i ]", channel); return; } + value = this->gammaCorrector(value); + if (value < 0.0f) value = 0.0f; if (value > 1.0f) value = 1.0f; @@ -147,6 +215,11 @@ void CyanLight::Controller::setChannel(uint8_t channel, float value) { } +void CyanLight::Controller::setGammaCorrector(CyanLight::GammaCorrector gammaCorrector) { + this->gammaCorrector = gammaCorrector; +} + + bool CyanLight::Controller::setFrqRes(uint32_t frq_hz, uint8_t res_bits) { this->timer_cfg.duty_resolution = (ledc_timer_bit_t)res_bits; @@ -167,6 +240,19 @@ bool CyanLight::Controller::setFrqRes(uint32_t frq_hz, uint8_t res_bits) { } } + +void CyanLight::Controller::setBaseChannel(uint16_t baseChannel) { + this->baseChannel = baseChannel; +} + +uint16_t CyanLight::Controller::getBaseChannel() { + return this->baseChannel; +} + +uint8_t CyanLight::Controller::getChannelCount() { + return this->channelsConfigured; +} + bool CyanLight::Controller::setFrequency(uint32_t frq_hz) { return this->setFrqRes(frq_hz, this->resolution); } @@ -183,21 +269,52 @@ uint8_t CyanLight::Controller::getResolution() { return this->resolution; } +void CyanLight::Controller::setPacketHandledCallback(CyanLight::PacketHandledCallback callback) { + this->packetCallback = callback; +} + + +std::string CyanLight::Controller::genDeviceTopic(const char *suffix) { + return std::string(DEVICE_NAMESPACE + "fxCyanF/") + std::string(suffix); +} + +std::string CyanLight::Controller::genServiceTopic(const char *suffix) { + return std::string("service/fxCyanF/") + std::string(suffix); +} + + +void CyanLight::Controller::callPacketCallback() { + this->packetCallback(); +} + + +void CyanLight::Controller::publishBaseChannel() { + char tmp[16]; + snprintf(tmp, sizeof(tmp), "%i", this->getBaseChannel()); + publish_message(DEVICE_NAMESPACE + "CyanLight/channel", tmp); +} + +void CyanLight::Controller::publishChannelCount() { + char tmp[16]; + snprintf(tmp, sizeof(tmp), "%i", this->getChannelCount()); + publish_message(DEVICE_NAMESPACE + "CyanLight/channelCnt", tmp); +} + void CyanLight::Controller::publishFrequency() { char tmp[16]; snprintf(tmp, sizeof(tmp), "%i", this->getFrequency()); - publish_message(DEVICE_NAMESPACE "CyanLight/frequency", tmp); + publish_message(DEVICE_NAMESPACE + "CyanLight/frequency", tmp); } void CyanLight::Controller::publishResolution() { char tmp[16]; snprintf(tmp, sizeof(tmp), "%i", this->getResolution()); - publish_message(DEVICE_NAMESPACE "CyanLight/resolution", tmp); + publish_message(DEVICE_NAMESPACE + "CyanLight/resolution", tmp); } void CyanLight::Controller::publishFrqRes() { char tmp[32]; snprintf(tmp, sizeof(tmp), "%i%s%i", this->getFrequency(), delimiter.c_str(), this->getResolution()); - publish_message(DEVICE_NAMESPACE "CyanLight/frqres", tmp); + publish_message(DEVICE_NAMESPACE + "CyanLight/frqres", tmp); } diff --git a/CLC-qthing/CyanLightControl.hpp b/CLC-qthing/CyanLightControl.hpp index 1afdd3e46a5071892355c90f08c725f02a5cbc8a..650b598a777c0dd58529fb904c8d537dcf1a8502 100644 --- a/CLC-qthing/CyanLightControl.hpp +++ b/CLC-qthing/CyanLightControl.hpp @@ -15,11 +15,21 @@ namespace CyanLight { const uint8_t channelGPIOS[MAX_CHANNELS] = { 27, 16, 17, 18, 19, 21 }; + typedef std::function<void()> PacketHandledCallback; + typedef std::function<float(float)> GammaCorrector; + class Controller { public: - Controller(uint8_t networkChannel, uint8_t channelsConfigured = 1); + Controller(uint16_t baseChannel, uint8_t channelsConfigured = 1); + + void setPWM(uint8_t channel, float value); + + void setGammaCorrector(GammaCorrector gammaCorrector); - void setChannel(uint8_t channel, float value); + uint8_t getChannelCount(); + + void setBaseChannel(uint16_t baseChannel); + uint16_t getBaseChannel(); bool setFrequency(uint32_t frq_hz); uint32_t getFrequency(); @@ -29,19 +39,34 @@ namespace CyanLight { bool setFrqRes(uint32_t frq_hz, uint8_t res_bits); + void setPacketHandledCallback(PacketHandledCallback callback); + + bool handleUnicast(const char *data, std::size_t length); + bool handleBroadcast(const char *data, std::size_t length); + + std::string genDeviceTopic(const char *suffix); + std::string genServiceTopic(const char *suffix); + void publishFrqRes(); void publishFrequency(); void publishResolution(); + void publishBaseChannel(); + void publishChannelCount(); + private: uint32_t frequency = 1000; // Hz - uint8_t resolution = 10; // bits + uint8_t resolution = 10; // bits - uint8_t networkChannel; + uint16_t baseChannel; uint8_t channelsConfigured; PWMChannel **channels; ledc_timer_config_t timer_cfg; + + GammaCorrector gammaCorrector = [](float f) { return f*f; }; + + PacketHandledCallback packetCallback = []() {}; + void callPacketCallback(); }; - float bytes2float(uint8_t *bytes); } diff --git a/CLC-qthing/PWMChannel.hpp b/CLC-qthing/PWMChannel.hpp index 125c7d086e8b0a30b4bc9e3ef7f4e36f9c9294a4..61b57f13a8fb4f42ffba31edc1672a7f7fe05975 100644 --- a/CLC-qthing/PWMChannel.hpp +++ b/CLC-qthing/PWMChannel.hpp @@ -1,6 +1,6 @@ #pragma once -#include <qthing.h> +#include <qthing> #include "driver/ledc.h" diff --git a/CLC-qthing/device_main.cpp b/CLC-qthing/device_main.cpp index e4eeb514109b5df8e5dedcc43b8dbbfee9ead109..04681872833be6092356cf822b16cbd97566c8fc 100644 --- a/CLC-qthing/device_main.cpp +++ b/CLC-qthing/device_main.cpp @@ -1,18 +1,23 @@ -#include <qthing.h> +#include <qthing> +#include "esp_log.h" #include "driver/gpio.h" +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" + + #include "CyanLightControl.hpp" -CyanLight::Controller ctrl(0, 3); +CyanLight::Controller ctrl(0, 1); void device_main() { // ground the pin beneath our channel 0 IO - uint8_t GND = 23; + /* uint8_t GND = 23; gpio_config_t conf; @@ -23,12 +28,31 @@ void device_main() { conf.pull_down_en = GPIO_PULLDOWN_DISABLE; gpio_config(&conf); - gpio_set_level((gpio_num_t)GND, 0); + gpio_set_level((gpio_num_t)GND, 0); */ + + + qthing::Config cfg; + cfg.apply(); + qthing::enable_wifi(); + return; + auto delay = 10 / portTICK_PERIOD_MS; + float limit = 50.0f; + while (true) { + for (uint8_t i = 0; i < limit; i++) { + float pwm = i / limit; + ctrl.setPWM(0, pwm*pwm); + vTaskDelay(delay); + } - enable_wlan(); + for (uint8_t i = 0; i < limit; i++) { + float pwm = i / limit; + ctrl.setPWM(0, 1.0f - pwm*pwm); + vTaskDelay(delay); + } + } } diff --git a/CLC-qthing/floatOut.py b/CLC-qthing/floatOut.py index 96cd7967aaa398a9c740147c89d908450cead8d3..0b995f6822ad8b3ce41a5e798c5d95f01cac378f 100755 --- a/CLC-qthing/floatOut.py +++ b/CLC-qthing/floatOut.py @@ -1,16 +1,53 @@ #!/usr/bin/env nix-shell #!nix-shell -i python -p python3 +import time import struct +from socket import * -value = 13.37 +port = 4213 +hosts = ['192.168.0.115'] +fps = 60 +step = 1 / 32 + + +s = socket(AF_INET, SOCK_DGRAM) def f2b(f): return bytearray(struct.pack("f", f)) -ba = f2b(value) -print(ba) -print([ "0x%02x" % b for b in ba ]) +x = 0.0 +goUp = True + +while True: + + if goUp: + x += step + else: + x -= step + + if x > 1: + x = 1.0 + goUp = not goUp + + if x < 0: + x = 0.0 + goUp = not goUp + + + data = f2b(x) + + for _ in range(5): + data += f2b(0.0) + + data = bytearray(b'fxCyanF') + data + + for host in hosts: + try: + s.sendto(data, (host, port)) + except: + pass + time.sleep(1 / fps)