including ArtNet feature from different Project
This commit is contained in:
@@ -0,0 +1,98 @@
|
|||||||
|
#pragma once
|
||||||
|
#ifndef ARDUINO_ARTNET_H
|
||||||
|
#define ARDUINO_ARTNET_H
|
||||||
|
|
||||||
|
// Spec (Art-Net 4) : http://artisticlicence.com/WebSiteMaster/User%20Guides/art-net.pdf
|
||||||
|
// Packet Summary : https://art-net.org.uk/structure/packet-summary-2/
|
||||||
|
// Packet Definition : https://art-net.org.uk/structure/streaming-packets/artdmx-packet-definition/
|
||||||
|
|
||||||
|
|
||||||
|
namespace arx { // others
|
||||||
|
|
||||||
|
template <class AlwaysVoid, template<class...> class Check, class... T>
|
||||||
|
struct is_detected_impl : std::false_type {};
|
||||||
|
template <template <class...> class Check, class... T>
|
||||||
|
struct is_detected_impl <std::void_t<Check<T...>>, Check, T...> : std::true_type {};
|
||||||
|
template <template <class...> class Check, class... T>
|
||||||
|
using is_detected = is_detected_impl<void, Check, T...>;
|
||||||
|
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
struct is_callable {
|
||||||
|
template <typename U, decltype(&U::operator()) = &U::operator()>
|
||||||
|
struct checker {};
|
||||||
|
template <typename U> static std::true_type test(checker<U> *);
|
||||||
|
template <typename> static std::false_type test(...);
|
||||||
|
static constexpr bool value = decltype(test<T>(nullptr))::value;
|
||||||
|
};
|
||||||
|
template <typename R, typename ... Arguments>
|
||||||
|
struct is_callable<R(*)(Arguments ...)> {
|
||||||
|
static constexpr bool value = true;
|
||||||
|
};
|
||||||
|
template <typename R, typename ... Arguments>
|
||||||
|
struct is_callable<R(*&)(Arguments ...)> {
|
||||||
|
static constexpr bool value = true;
|
||||||
|
};
|
||||||
|
template <typename R, typename ... Arguments>
|
||||||
|
struct is_callable<R(&)(Arguments ...)> {
|
||||||
|
static constexpr bool value = true;
|
||||||
|
};
|
||||||
|
template <typename R, typename ... Arguments>
|
||||||
|
struct is_callable<R(Arguments ...)> {
|
||||||
|
static constexpr bool value = true;
|
||||||
|
};
|
||||||
|
template <typename R, typename ... Arguments>
|
||||||
|
struct is_callable<std::function<R(Arguments ...)>> {
|
||||||
|
static constexpr bool value = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
namespace detail {
|
||||||
|
template <typename ret, typename ... arguments>
|
||||||
|
struct function_traits {
|
||||||
|
static constexpr size_t arity = sizeof...(arguments);
|
||||||
|
using result_type = ret;
|
||||||
|
using arguments_types_tuple = std::tuple<arguments ...>;
|
||||||
|
// template <size_t index>
|
||||||
|
// using argument_type = type_at<index, arguments ...>;
|
||||||
|
using function_type = std::function<ret(arguments ...)>;
|
||||||
|
template <typename function_t>
|
||||||
|
static constexpr function_type cast(function_t f) {
|
||||||
|
return static_cast<function_type>(f);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
template <typename T>
|
||||||
|
struct function_traits : public function_traits<decltype(&T::operator())> {};
|
||||||
|
template <typename class_type, typename ret, typename ... arguments>
|
||||||
|
struct function_traits<ret(class_type::*)(arguments ...) const>
|
||||||
|
: detail::function_traits<ret, arguments ...> {};
|
||||||
|
|
||||||
|
template <typename class_type, typename ret, typename ... arguments>
|
||||||
|
struct function_traits<ret(class_type::*)(arguments ...)>
|
||||||
|
: detail::function_traits<ret, arguments ...> {};
|
||||||
|
|
||||||
|
template <typename ret, typename ... arguments>
|
||||||
|
struct function_traits<ret(*)(arguments ...)>
|
||||||
|
: detail::function_traits<ret, arguments ...> {};
|
||||||
|
|
||||||
|
template <typename ret, typename ... arguments>
|
||||||
|
struct function_traits<ret(*&)(arguments ...)>
|
||||||
|
: detail::function_traits<ret, arguments ...> {};
|
||||||
|
|
||||||
|
template <typename ret, typename ... arguments>
|
||||||
|
struct function_traits<ret(arguments ...)>
|
||||||
|
: detail::function_traits<ret, arguments ...> {};
|
||||||
|
|
||||||
|
template <typename ret, typename ... arguments>
|
||||||
|
struct function_traits<std::function<ret(arguments ...)>>
|
||||||
|
: detail::function_traits<ret, arguments ...> {};
|
||||||
|
|
||||||
|
} // namespace arx
|
||||||
|
|
||||||
|
#include "ArtnetCommon.h"
|
||||||
|
using Artnet = arx::artnet::Manager;
|
||||||
|
using ArtnetSender = arx::artnet::Sender;
|
||||||
|
using ArtnetReceiver = arx::artnet::Receiver;
|
||||||
|
|
||||||
|
#endif // ARDUINO_ARTNET_H
|
||||||
@@ -0,0 +1,540 @@
|
|||||||
|
|
||||||
|
#pragma once
|
||||||
|
#ifndef ARTNET_COMMON_H
|
||||||
|
#define ARTNET_COMMON_H
|
||||||
|
|
||||||
|
#include "udpSockets.hpp"
|
||||||
|
#include <functional>
|
||||||
|
#include <map>
|
||||||
|
#include <string.h>
|
||||||
|
#include <string>
|
||||||
|
#include <algorithm>
|
||||||
|
#include <Task.hpp>
|
||||||
|
|
||||||
|
namespace arx {
|
||||||
|
namespace artnet {
|
||||||
|
// Packet Summary : https://art-net.org.uk/structure/packet-summary-2/
|
||||||
|
// Packet Definition : https://art-net.org.uk/structure/streaming-packets/artdmx-packet-definition/
|
||||||
|
|
||||||
|
struct Target { // DMX Universe Port-Address
|
||||||
|
uint32_t universe : 4;
|
||||||
|
uint32_t subnet : 4;
|
||||||
|
uint32_t net : 7;
|
||||||
|
};
|
||||||
|
|
||||||
|
enum class OpCode : uint16_t {
|
||||||
|
// Device Discovery
|
||||||
|
Poll = 0x2000,
|
||||||
|
PollReply = 0x2100,
|
||||||
|
// Device Configuration
|
||||||
|
Address = 0x6000,
|
||||||
|
Input = 0x7000,
|
||||||
|
IpProg = 0xF800,
|
||||||
|
IpProgReply = 0xF900,
|
||||||
|
Command = 0x2400,
|
||||||
|
// Streaming Control
|
||||||
|
Dmx = 0x5000,
|
||||||
|
Nzs = 0x5100,
|
||||||
|
Sync = 0x5200,
|
||||||
|
// RDM
|
||||||
|
TodRequest = 0x8000,
|
||||||
|
TodData = 0x8100,
|
||||||
|
TodControl = 0x8200,
|
||||||
|
Rdm = 0x8300,
|
||||||
|
RdmSub = 0x8400,
|
||||||
|
// Time-Keeping
|
||||||
|
TimeCode = 0x9700,
|
||||||
|
TimeSync = 0x9800,
|
||||||
|
// Triggering
|
||||||
|
Trigger = 0x9900,
|
||||||
|
// Diagnostics
|
||||||
|
DiagData = 0x2300,
|
||||||
|
// File Transfer
|
||||||
|
FirmwareMaster = 0xF200,
|
||||||
|
FirmwareReply = 0xF300,
|
||||||
|
Directory = 0x9A00,
|
||||||
|
DirectoryReply = 0x9B00,
|
||||||
|
FileTnMaster = 0xF400,
|
||||||
|
FileFnMaster = 0xF500,
|
||||||
|
FileFnReply = 0xF600,
|
||||||
|
// N/A
|
||||||
|
NA = 0x0000,
|
||||||
|
};
|
||||||
|
|
||||||
|
constexpr uint16_t OPC(OpCode op) {
|
||||||
|
return static_cast<uint16_t>(op);
|
||||||
|
}
|
||||||
|
|
||||||
|
enum class Index : uint16_t {
|
||||||
|
ID = 0,
|
||||||
|
OP_CODE_L = 8,
|
||||||
|
OP_CODE_H = 9,
|
||||||
|
PROTOCOL_VER_H = 10,
|
||||||
|
PROTOCOL_VER_L = 11,
|
||||||
|
SEQUENCE = 12,
|
||||||
|
PHYSICAL = 13,
|
||||||
|
SUBUNI = 14,
|
||||||
|
NET = 15,
|
||||||
|
LENGTH_H = 16,
|
||||||
|
LENGTH_L = 17,
|
||||||
|
DATA = 18
|
||||||
|
};
|
||||||
|
|
||||||
|
constexpr uint16_t IDX(Index i) {
|
||||||
|
return static_cast<uint16_t>(i);
|
||||||
|
}
|
||||||
|
|
||||||
|
constexpr uint16_t DEFAULT_PORT{0x1936}; // 6454
|
||||||
|
constexpr uint16_t HEADER_SIZE{18};
|
||||||
|
constexpr uint16_t PACKET_SIZE{530};
|
||||||
|
constexpr uint16_t PROTOCOL_VER{0x0014};
|
||||||
|
constexpr uint8_t ID_LENGTH{8};
|
||||||
|
constexpr char ID[ID_LENGTH]{"Art-Net"};
|
||||||
|
constexpr int DEFAULT_FPS_IN_TICKS {40 / portTICK_PERIOD_MS};
|
||||||
|
|
||||||
|
static constexpr uint8_t NUM_PIXELS_PER_UNIV{170};
|
||||||
|
|
||||||
|
using CallbackAllType =
|
||||||
|
std::function<void(const uint32_t universe, const uint8_t *data, const uint16_t size)>;
|
||||||
|
using CallbackType = std::function<void(const uint8_t *data, const uint16_t size)>;
|
||||||
|
|
||||||
|
template <uint16_t SIZE> using Array = std::array<uint8_t, SIZE>;
|
||||||
|
using IntervalMap = std::map<uint32_t, uint32_t>;
|
||||||
|
using CallbackMap = std::map<uint32_t, CallbackType>;
|
||||||
|
using namespace std;
|
||||||
|
|
||||||
|
union ArtPollReply {
|
||||||
|
struct {
|
||||||
|
uint8_t id[8];
|
||||||
|
uint8_t op_code_l;
|
||||||
|
uint8_t op_code_h;
|
||||||
|
uint8_t ip[4];
|
||||||
|
uint8_t port_l;
|
||||||
|
uint8_t port_h;
|
||||||
|
uint8_t ver_h;
|
||||||
|
uint8_t ver_l;
|
||||||
|
uint8_t net_sw;
|
||||||
|
uint8_t sub_sw;
|
||||||
|
uint8_t oem_h;
|
||||||
|
uint8_t oem_l;
|
||||||
|
uint8_t ubea_ver;
|
||||||
|
uint8_t status_1;
|
||||||
|
uint8_t esta_man_l;
|
||||||
|
uint8_t esta_man_h;
|
||||||
|
uint8_t short_name[18];
|
||||||
|
uint8_t long_name[64];
|
||||||
|
uint8_t node_report[64];
|
||||||
|
uint8_t num_ports_h;
|
||||||
|
uint8_t num_ports_l;
|
||||||
|
uint8_t port_types[4];
|
||||||
|
uint8_t good_input[4];
|
||||||
|
uint8_t good_output[4];
|
||||||
|
uint8_t sw_in[4];
|
||||||
|
uint8_t sw_out[4];
|
||||||
|
uint8_t sw_video;
|
||||||
|
uint8_t sw_macro;
|
||||||
|
uint8_t sw_remote;
|
||||||
|
uint8_t spare[3];
|
||||||
|
uint8_t style;
|
||||||
|
uint8_t mac[6];
|
||||||
|
uint8_t bindIp[4];
|
||||||
|
uint8_t bindIndex;
|
||||||
|
uint8_t status_2;
|
||||||
|
uint8_t filler[26];
|
||||||
|
};
|
||||||
|
std::array<uint8_t, 239> b;
|
||||||
|
};
|
||||||
|
|
||||||
|
class Sender {
|
||||||
|
Array<PACKET_SIZE> packet;
|
||||||
|
uint8_t target_net {0};
|
||||||
|
uint8_t target_subnet {0};
|
||||||
|
uint8_t target_universe {0};
|
||||||
|
uint8_t seq {0};
|
||||||
|
uint8_t phy {0};
|
||||||
|
|
||||||
|
IntervalMap intervals;
|
||||||
|
UdpSocket& stream;
|
||||||
|
|
||||||
|
public:
|
||||||
|
Sender(UdpSocket & socket) : stream(socket) {
|
||||||
|
for (size_t i = 0; i < ID_LENGTH; i++) packet[IDX(Index::ID) + i] =
|
||||||
|
static_cast<uint8_t>(ID[i]); packet[IDX(Index::OP_CODE_H)] = (OPC(OpCode::Dmx) >> 8) &
|
||||||
|
0x00FF; packet[IDX(Index::OP_CODE_L)] = (OPC(OpCode::Dmx) >> 0) & 0x00FF;
|
||||||
|
packet[IDX(Index::PROTOCOL_VER_H)] = (PROTOCOL_VER >> 8) & 0x00FF;
|
||||||
|
packet[IDX(Index::PROTOCOL_VER_L)] = (PROTOCOL_VER >> 0) & 0x00FF;
|
||||||
|
}
|
||||||
|
virtual ~Sender() {}
|
||||||
|
|
||||||
|
// streaming packet
|
||||||
|
void streamingData(const uint8_t* const data, const uint16_t size) {
|
||||||
|
memcpy((&packet[IDX(Index::DATA)]), data, size);
|
||||||
|
}
|
||||||
|
void streamingData(const uint16_t ch, const uint8_t data) {
|
||||||
|
packet[IDX(Index::DATA) + ch] = data;
|
||||||
|
}
|
||||||
|
|
||||||
|
void streaming(const std::string& ip, const uint32_t universe) {
|
||||||
|
|
||||||
|
uint32_t now = fos::Timing::getTickCount();
|
||||||
|
if (intervals.find(universe) == intervals.end()) {
|
||||||
|
intervals.insert(make_pair(universe, now));
|
||||||
|
}
|
||||||
|
if (now >= intervals[universe] + DEFAULT_FPS_IN_TICKS) {
|
||||||
|
setUniverse(universe);
|
||||||
|
sendPacket(ip);
|
||||||
|
intervals[universe] = now;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
void streaming(const std::string& ip, const uint8_t net, const uint8_t subnet, const uint8_t
|
||||||
|
universe) {
|
||||||
|
uint32_t u = ((uint32_t)net << 8) | ((uint32_t)subnet << 4) | (uint32_t)universe;
|
||||||
|
streaming(ip, u);
|
||||||
|
}
|
||||||
|
|
||||||
|
// one-line sender
|
||||||
|
void send(const std::string& ip, const uint32_t universe, const uint8_t* const data, const
|
||||||
|
uint16_t size) {
|
||||||
|
setUniverse(universe);
|
||||||
|
streamingData(data, size);
|
||||||
|
sendPacket(ip);
|
||||||
|
}
|
||||||
|
void send(
|
||||||
|
const std::string& ip,
|
||||||
|
const uint8_t net,
|
||||||
|
const uint8_t subnet,
|
||||||
|
const uint8_t universe,
|
||||||
|
const uint8_t* const data,
|
||||||
|
const uint16_t size) {
|
||||||
|
setUniverse(net, subnet, universe);
|
||||||
|
streamingData(data, size);
|
||||||
|
sendPacket(ip);
|
||||||
|
}
|
||||||
|
|
||||||
|
void physical(const uint8_t i) {
|
||||||
|
phy = std::clamp<uint8_t>(i, 0, 3);
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t sequence() const {
|
||||||
|
return seq;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected:
|
||||||
|
void sendPacket(const std::string& ip) {
|
||||||
|
packet[IDX(Index::SEQUENCE)] = seq++;
|
||||||
|
packet[IDX(Index::PHYSICAL)] = phy;
|
||||||
|
packet[IDX(Index::NET)] = target_net;
|
||||||
|
packet[IDX(Index::SUBUNI)] = (target_subnet << 4) | target_universe;
|
||||||
|
packet[IDX(Index::LENGTH_H)] = (512 >> 8) & 0xFF;
|
||||||
|
packet[IDX(Index::LENGTH_L)] = (512 >> 0) & 0xFF;
|
||||||
|
|
||||||
|
if (ip == "")
|
||||||
|
{
|
||||||
|
stream.write(packet.data(), packet.size());
|
||||||
|
} else {
|
||||||
|
auto ipAddr = IpAdr(ip, DEFAULT_PORT);
|
||||||
|
stream.write(packet.data(), packet.size(), &ipAddr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void setUniverse(const uint32_t universe) {
|
||||||
|
target_net = (universe >> 8) & 0x7F;
|
||||||
|
target_subnet = (universe >> 4) & 0x0F;
|
||||||
|
target_universe = universe & 0x0F;
|
||||||
|
}
|
||||||
|
void setUniverse(const uint8_t net, const uint8_t subnet, const uint8_t universe) {
|
||||||
|
target_net = net & 0x7F;
|
||||||
|
target_subnet = subnet & 0x0F;
|
||||||
|
target_universe = universe & 0x0F;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
class Receiver {
|
||||||
|
Array<PACKET_SIZE> packet;
|
||||||
|
uint8_t net_switch; // net of universe
|
||||||
|
uint8_t sub_switch; // subnet of universe
|
||||||
|
std::string short_name{"Custom ArtNet"};
|
||||||
|
std::string long_name{"Arduino ArtNet Adaption by gustice"};
|
||||||
|
std::string node_report{""};
|
||||||
|
CallbackMap callbacks;
|
||||||
|
CallbackAllType callback_all;
|
||||||
|
UdpSocket &stream;
|
||||||
|
bool b_verbose{false};
|
||||||
|
|
||||||
|
public:
|
||||||
|
Receiver(UdpSocket &socket, const uint8_t subscribe_net = 0, const uint8_t subscribe_subnet = 0)
|
||||||
|
: stream(socket) {
|
||||||
|
if (subscribe_net > 128) {
|
||||||
|
ESP_LOGE("ARX", "Net must be < 128");
|
||||||
|
} else {
|
||||||
|
net_switch = subscribe_net;
|
||||||
|
}
|
||||||
|
if (subscribe_subnet > 16) {
|
||||||
|
ESP_LOGE("ARX", "Subnet must be < 16");
|
||||||
|
} else {
|
||||||
|
sub_switch = subscribe_subnet;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
virtual ~Receiver() {}
|
||||||
|
|
||||||
|
OpCode parse() {
|
||||||
|
auto data = stream.read();
|
||||||
|
|
||||||
|
if (data.size() <= 0)
|
||||||
|
return OpCode::NA;
|
||||||
|
uint8_t *d = &data[0];
|
||||||
|
|
||||||
|
OpCode op_code = OpCode::NA;
|
||||||
|
if (checkID(d)) {
|
||||||
|
switch (opcode(d)) {
|
||||||
|
case OPC(OpCode::Dmx): {
|
||||||
|
memcpy(packet.data(), d, data.size());
|
||||||
|
if (callback_all)
|
||||||
|
callback_all(universe15bit(), dataPtr(), data.size() - HEADER_SIZE);
|
||||||
|
for (auto &c : callbacks) {
|
||||||
|
if (universe15bit() == c.first)
|
||||||
|
c.second(dataPtr(), data.size() - HEADER_SIZE);
|
||||||
|
}
|
||||||
|
op_code = OpCode::Dmx;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case OPC(OpCode::Poll): {
|
||||||
|
pollReply();
|
||||||
|
op_code = OpCode::Poll;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
default: {
|
||||||
|
if (b_verbose) {
|
||||||
|
ESP_LOGW("ARX", "Unsupported OpCode: %d", opcode(d));
|
||||||
|
}
|
||||||
|
op_code = OpCode::NA;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return op_code;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline std::string id() const {
|
||||||
|
std::string str;
|
||||||
|
for (uint8_t i = 0; i < ID_LENGTH; ++i)
|
||||||
|
str += packet[IDX(Index::ID) + i];
|
||||||
|
return str;
|
||||||
|
}
|
||||||
|
inline uint16_t opcode() const {
|
||||||
|
return (packet[IDX(Index::OP_CODE_H)] << 8) | packet[IDX(Index::OP_CODE_L)];
|
||||||
|
}
|
||||||
|
inline uint16_t opcode(const uint8_t *p) const {
|
||||||
|
return (p[IDX(Index::OP_CODE_H)] << 8) | p[IDX(Index::OP_CODE_L)];
|
||||||
|
}
|
||||||
|
inline uint16_t version() const {
|
||||||
|
return (packet[IDX(Index::PROTOCOL_VER_H)] << 8) | packet[IDX(Index::PROTOCOL_VER_L)];
|
||||||
|
}
|
||||||
|
inline uint8_t sequence() const {
|
||||||
|
return packet[IDX(Index::SEQUENCE)];
|
||||||
|
}
|
||||||
|
inline uint8_t physical() const {
|
||||||
|
return packet[IDX(Index::PHYSICAL)];
|
||||||
|
}
|
||||||
|
uint8_t net() const {
|
||||||
|
return packet[IDX(Index::NET)] & 0x7F;
|
||||||
|
}
|
||||||
|
uint8_t subnet() const {
|
||||||
|
return (packet[IDX(Index::SUBUNI)] >> 4) & 0x0F;
|
||||||
|
}
|
||||||
|
inline uint8_t universe() const {
|
||||||
|
return packet[IDX(Index::SUBUNI)] & 0x0F;
|
||||||
|
}
|
||||||
|
inline uint16_t universe15bit() const {
|
||||||
|
return (packet[IDX(Index::NET)] << 8) | packet[IDX(Index::SUBUNI)];
|
||||||
|
}
|
||||||
|
inline uint16_t length() const {
|
||||||
|
return (packet[IDX(Index::LENGTH_H)] << 8) | packet[IDX(Index::LENGTH_L)];
|
||||||
|
}
|
||||||
|
inline uint16_t size() const {
|
||||||
|
return length();
|
||||||
|
}
|
||||||
|
inline uint8_t *dataPtr() {
|
||||||
|
return &(packet[HEADER_SIZE]);
|
||||||
|
}
|
||||||
|
inline uint8_t dataPtr(const uint16_t i) const {
|
||||||
|
return packet[HEADER_SIZE + i];
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename Fn>
|
||||||
|
inline auto subscribe(const uint8_t universe, Fn &&func)
|
||||||
|
-> std::enable_if_t<arx::is_callable<Fn>::value> {
|
||||||
|
if (callbacks.size() >= 4) {
|
||||||
|
if (b_verbose) {
|
||||||
|
ESP_LOGE("ARX", "too many callbacks");
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (universe > 0xF) {
|
||||||
|
if (b_verbose) {
|
||||||
|
ESP_LOGE("ARX", "universe out of bounds");
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
} else {
|
||||||
|
uint32_t u =
|
||||||
|
((uint32_t)net_switch << 8) | ((uint32_t)sub_switch << 4) | (uint32_t)universe;
|
||||||
|
callbacks.insert(make_pair(u, arx::function_traits<Fn>::cast(func)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
template <typename Fn> inline auto subscribe(const uint8_t universe, Fn *func) {
|
||||||
|
if (callbacks.size() >= 4) {
|
||||||
|
if (b_verbose) {
|
||||||
|
ESP_LOGE("ARX", "too many callbacks");
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (universe > 0xF) {
|
||||||
|
if (b_verbose) {
|
||||||
|
ESP_LOGE("ARX", "universe out of bounds");
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
uint32_t u =
|
||||||
|
((uint32_t)net_switch << 8) | ((uint32_t)sub_switch << 4) | (uint32_t)universe;
|
||||||
|
callbacks.insert(make_pair(u, arx::function_traits<Fn>::cast(func)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
template <typename Fn>
|
||||||
|
inline auto subscribe15bit(const uint16_t universe, Fn &&func)
|
||||||
|
-> std::enable_if_t<arx::is_callable<Fn>::value> {
|
||||||
|
if (callbacks.size() >= 4) {
|
||||||
|
if (b_verbose) {
|
||||||
|
ESP_LOGE("ARX", "too many callbacks");
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
callbacks.insert(make_pair(universe, arx::function_traits<Fn>::cast(func)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
template <typename Fn> inline auto subscribe15bit(const uint16_t universe, Fn *func) {
|
||||||
|
if (callbacks.size() >= 4) {
|
||||||
|
if (b_verbose) {
|
||||||
|
ESP_LOGE("ARX", "too many callbacks");
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
callbacks.insert(make_pair(universe, arx::function_traits<Fn>::cast(func)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
template <typename F>
|
||||||
|
inline auto subscribe(F &&func) -> std::enable_if_t<arx::is_callable<F>::value> {
|
||||||
|
callback_all = arx::function_traits<F>::cast(func);
|
||||||
|
}
|
||||||
|
template <typename F> inline auto subscribe(F *func) {
|
||||||
|
callback_all = arx::function_traits<F>::cast(func);
|
||||||
|
}
|
||||||
|
|
||||||
|
inline void unsubscribe(const uint8_t universe) {
|
||||||
|
auto it = callbacks.find(universe);
|
||||||
|
if (it != callbacks.end())
|
||||||
|
callbacks.erase(it);
|
||||||
|
}
|
||||||
|
inline void unsubscribe() {
|
||||||
|
callback_all = nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline void clear_subscribers() {
|
||||||
|
unsubscribe();
|
||||||
|
callbacks.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
void nodereport(const std::string nr) {
|
||||||
|
node_report = nr;
|
||||||
|
}
|
||||||
|
|
||||||
|
void verbose(const bool b) {
|
||||||
|
b_verbose = b;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected:
|
||||||
|
private:
|
||||||
|
bool checkID() const {
|
||||||
|
const char *idptr = reinterpret_cast<const char *>(packet.data());
|
||||||
|
return !strcmp(ID, idptr);
|
||||||
|
}
|
||||||
|
bool checkID(const uint8_t *p) const {
|
||||||
|
const char *idptr = reinterpret_cast<const char *>(p);
|
||||||
|
return !strcmp(ID, idptr);
|
||||||
|
}
|
||||||
|
|
||||||
|
void pollReply() {
|
||||||
|
ArtPollReply r;
|
||||||
|
for (size_t i = 0; i < ID_LENGTH; i++)
|
||||||
|
r.id[i] = static_cast<uint8_t>(ID[i]);
|
||||||
|
r.op_code_l = ((uint16_t)OpCode::PollReply >> 0) & 0x00FF;
|
||||||
|
r.op_code_h = ((uint16_t)OpCode::PollReply >> 8) & 0x00FF;
|
||||||
|
IpInfo ipInfo = stream.getIpInfo();
|
||||||
|
Ip my_ip = ipInfo.address;
|
||||||
|
for (size_t i = 0; i < 4; ++i)
|
||||||
|
r.ip[i] = my_ip[i];
|
||||||
|
r.port_l = (DEFAULT_PORT >> 0) & 0xFF;
|
||||||
|
r.port_h = (DEFAULT_PORT >> 8) & 0xFF;
|
||||||
|
r.ver_h = (PROTOCOL_VER >> 8) & 0x00FF;
|
||||||
|
r.ver_l = (PROTOCOL_VER >> 0) & 0x00FF;
|
||||||
|
r.oem_h = 0; // https://github.com/tobiasebsen/ArtNode/blob/master/src/Art-NetOemCodes.h
|
||||||
|
r.oem_l = 0xFF; // OemUnknown
|
||||||
|
r.ubea_ver = 0; // UBEA not programmed
|
||||||
|
r.status_1 = 0x00; // Unknown / Normal
|
||||||
|
r.esta_man_l = 0; // No EATA manufacture code
|
||||||
|
r.esta_man_h = 0; // No ESTA manufacture code
|
||||||
|
memset(r.short_name, 0, 18);
|
||||||
|
memset(r.long_name, 0, 64);
|
||||||
|
memset(r.node_report, 0, 64);
|
||||||
|
memcpy(r.short_name, short_name.c_str(), short_name.length());
|
||||||
|
memcpy(r.long_name, long_name.c_str(), long_name.length());
|
||||||
|
memcpy(r.node_report, node_report.c_str(), node_report.length());
|
||||||
|
r.num_ports_h = 0; // Reserved
|
||||||
|
r.num_ports_l = callbacks.size(); // This library implements only 4 port
|
||||||
|
memset(r.port_types, 0, 4);
|
||||||
|
memset(r.sw_in, 0, 4);
|
||||||
|
memset(r.sw_out, 0, 4);
|
||||||
|
memset(r.good_input, 0, 4);
|
||||||
|
memset(r.good_output, 0, 4);
|
||||||
|
r.net_sw = net_switch & 0x7F;
|
||||||
|
r.sub_sw = sub_switch & 0x0F;
|
||||||
|
|
||||||
|
size_t i = 0;
|
||||||
|
for (const auto &pair : callbacks) {
|
||||||
|
// r.port_types[i] = 0xC0; // I/O available by DMX512 // @todo Bug@ liveShowSoftware
|
||||||
|
r.port_types[i] = 0x80; // Output available by DMX512
|
||||||
|
r.good_input[i] = 0x80; // Data received without error
|
||||||
|
r.good_output[i] = 0x80; // Data transmitted without error
|
||||||
|
r.sw_in[i] = i;// dummy: input port is flexible
|
||||||
|
r.sw_out[i] = pair.first & 0x0F;
|
||||||
|
if (++i >= 4)
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
r.sw_video = 0; // Video display shows local data
|
||||||
|
r.sw_macro = 0; // No support for macro key inputs
|
||||||
|
r.sw_remote = 0; // No support for remote trigger inputs
|
||||||
|
memset(r.spare, 0x00, 3);
|
||||||
|
r.style = 0x00; // A DMX to / from Art-Net device
|
||||||
|
std::copy(ipInfo.macAddress.asArray().begin(), ipInfo.macAddress.asArray().end(), std::end(r.mac));
|
||||||
|
for (size_t i = 0; i < 4; ++i)
|
||||||
|
r.bindIp[i] = my_ip[i];
|
||||||
|
r.bindIndex = 0;
|
||||||
|
r.status_2 = 0x0C; // features: 15bitAddr | DHCP
|
||||||
|
memset(r.filler, 0x00, 26);
|
||||||
|
Ip bc = ipInfo.getBradcastAddress();
|
||||||
|
IpAdr broadCast(bc.asWord(), DEFAULT_PORT);
|
||||||
|
stream.write(r.b.data(), r.b.size(), &broadCast); // on sending station
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
class Manager : public Sender, public Receiver {
|
||||||
|
public:
|
||||||
|
Manager(UdpSocket &socket) : Sender(socket), Receiver(socket) {
|
||||||
|
|
||||||
|
}
|
||||||
|
void parse() {
|
||||||
|
this->Receiver::parse();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
} // namespace artnet
|
||||||
|
} // namespace arx
|
||||||
|
|
||||||
|
#endif // ARTNET_COMMON_H
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
idf_component_register(
|
||||||
|
SRC_DIRS "."
|
||||||
|
INCLUDE_DIRS "."
|
||||||
|
REQUIRES sockets
|
||||||
|
)
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2019 Hideaki Tai
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
||||||
@@ -0,0 +1,54 @@
|
|||||||
|
#include <FastLED.h> // include FastLED *before* Artnet
|
||||||
|
|
||||||
|
// Please include ArtnetEther.h to use Artnet on the platform
|
||||||
|
// which can use both WiFi and Ethernet
|
||||||
|
#include <ArtnetEther.h>
|
||||||
|
// this is also valid for other platforms which can use only Ethernet
|
||||||
|
// #include <Artnet.h>
|
||||||
|
|
||||||
|
// Ethernet stuff
|
||||||
|
const IPAddress ip(192, 168, 0, 201);
|
||||||
|
uint8_t mac[] = {0x01, 0x23, 0x45, 0x67, 0x89, 0xAB};
|
||||||
|
|
||||||
|
ArtnetReceiver artnet;
|
||||||
|
uint8_t universe = 1; // 0 - 15
|
||||||
|
|
||||||
|
// FastLED
|
||||||
|
#define NUM_LEDS 1
|
||||||
|
CRGB leds[NUM_LEDS];
|
||||||
|
const uint8_t PIN_LED_DATA = 3;
|
||||||
|
|
||||||
|
void setup() {
|
||||||
|
Serial.begin(115200);
|
||||||
|
delay(2000);
|
||||||
|
|
||||||
|
FastLED.addLeds<NEOPIXEL, PIN_LED_DATA>(leds, NUM_LEDS);
|
||||||
|
|
||||||
|
Ethernet.begin(mac, ip);
|
||||||
|
artnet.begin();
|
||||||
|
// artnet.subscribe_net(0); // optionally you can change
|
||||||
|
// artnet.subscribe_subnet(0); // optionally you can change
|
||||||
|
|
||||||
|
// if Artnet packet comes to this universe, forward them to fastled directly
|
||||||
|
artnet.forward(universe, leds, NUM_LEDS);
|
||||||
|
|
||||||
|
// this can be achieved manually as follows
|
||||||
|
// if Artnet packet comes to this universe, this function is called
|
||||||
|
// artnet.subscribe(universe, [&](uint8_t* data, uint16_t size)
|
||||||
|
// {
|
||||||
|
// // set led
|
||||||
|
// // artnet data size per packet is 512 max
|
||||||
|
// // so there is max 170 pixel per packet (per universe)
|
||||||
|
// for (size_t pixel = 0; pixel < NUM_LEDS; ++pixel) {
|
||||||
|
// size_t idx = pixel * 3;
|
||||||
|
// leds[pixel].r = data[idx + 0];
|
||||||
|
// leds[pixel].g = data[idx + 1];
|
||||||
|
// leds[pixel].b = data[idx + 2];
|
||||||
|
// }
|
||||||
|
// });
|
||||||
|
}
|
||||||
|
|
||||||
|
void loop() {
|
||||||
|
artnet.parse(); // check if artnet packet has come and execute callback
|
||||||
|
FastLED.show();
|
||||||
|
}
|
||||||
@@ -0,0 +1,47 @@
|
|||||||
|
// Please include ArtnetEther.h to use Artnet on the platform
|
||||||
|
// which can use both WiFi and Ethernet
|
||||||
|
#include <ArtnetEther.h>
|
||||||
|
// this is also valid for other platforms which can use only Ethernet
|
||||||
|
// #include <Artnet.h>
|
||||||
|
|
||||||
|
// Ethernet stuff
|
||||||
|
const IPAddress ip(192, 168, 0, 201);
|
||||||
|
uint8_t mac[] = {0x01, 0x23, 0x45, 0x67, 0x89, 0xAB};
|
||||||
|
|
||||||
|
ArtnetReceiver artnet;
|
||||||
|
uint32_t universe1 = 1; // 0 - 15
|
||||||
|
uint32_t universe2 = 2; // 0 - 15
|
||||||
|
|
||||||
|
void callback(const uint8_t* data, const uint16_t size) {
|
||||||
|
// you can also use pre-defined callbacks
|
||||||
|
}
|
||||||
|
|
||||||
|
void setup() {
|
||||||
|
Serial.begin(115200);
|
||||||
|
|
||||||
|
Ethernet.begin(mac, ip);
|
||||||
|
artnet.begin();
|
||||||
|
// artnet.subscribe_net(0); // optionally you can change
|
||||||
|
// artnet.subscribe_subnet(0); // optionally you can change
|
||||||
|
|
||||||
|
// if Artnet packet comes to this universe, this function is called
|
||||||
|
artnet.subscribe(universe1, [&](const uint8_t* data, const uint16_t size) {
|
||||||
|
Serial.print("artnet data (universe : ");
|
||||||
|
Serial.print(universe1);
|
||||||
|
Serial.print(", size = ");
|
||||||
|
Serial.print(size);
|
||||||
|
Serial.print(") :");
|
||||||
|
for (size_t i = 0; i < size; ++i) {
|
||||||
|
Serial.print(data[i]);
|
||||||
|
Serial.print(",");
|
||||||
|
}
|
||||||
|
Serial.println();
|
||||||
|
});
|
||||||
|
|
||||||
|
// you can also use pre-defined callbacks
|
||||||
|
artnet.subscribe(universe2, callback);
|
||||||
|
}
|
||||||
|
|
||||||
|
void loop() {
|
||||||
|
artnet.parse(); // check if artnet packet has come and execute callback
|
||||||
|
}
|
||||||
@@ -0,0 +1,65 @@
|
|||||||
|
#ifdef __AVR__
|
||||||
|
#warning THIS EXAMPLE MAY USE TOO MUCH MEMORY FOR AVR. WE RECOMMEND TO USE SENDER OR RECEIVER ONLY.
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// Please include ArtnetEther.h to use Artnet on the platform
|
||||||
|
// which can use both WiFi and Ethernet
|
||||||
|
#include <ArtnetEther.h>
|
||||||
|
// this is also valid for other platforms which can use only Ethernet
|
||||||
|
// #include <Artnet.h>
|
||||||
|
|
||||||
|
// Ethernet stuff
|
||||||
|
const IPAddress ip(192, 168, 0, 201);
|
||||||
|
uint8_t mac[] = {0x01, 0x23, 0x45, 0x67, 0x89, 0xAB};
|
||||||
|
|
||||||
|
Artnet artnet;
|
||||||
|
const String target_ip = "192.168.0.200";
|
||||||
|
uint8_t universe = 1; // 0 - 15
|
||||||
|
|
||||||
|
const uint16_t size = 512;
|
||||||
|
uint8_t data[size];
|
||||||
|
uint8_t value = 0;
|
||||||
|
|
||||||
|
void setup() {
|
||||||
|
Serial.begin(115200);
|
||||||
|
delay(2000);
|
||||||
|
|
||||||
|
Ethernet.begin(mac, ip);
|
||||||
|
artnet.begin();
|
||||||
|
// artnet.begin(net, subnet); // optionally you can change
|
||||||
|
|
||||||
|
Serial.println("set subscriber");
|
||||||
|
|
||||||
|
// if Artnet packet comes to this universe, this function is called
|
||||||
|
artnet.subscribe(universe, [](const uint8_t* data, const uint16_t size) {
|
||||||
|
Serial.print("artnet data (universe : ");
|
||||||
|
Serial.print(universe);
|
||||||
|
Serial.print(", size = ");
|
||||||
|
Serial.print(size);
|
||||||
|
Serial.print(") :");
|
||||||
|
for (size_t i = 0; i < size; ++i) {
|
||||||
|
Serial.print(data[i]);
|
||||||
|
Serial.print(",");
|
||||||
|
}
|
||||||
|
Serial.println();
|
||||||
|
});
|
||||||
|
|
||||||
|
// if Artnet packet comes, this function is called to every universe
|
||||||
|
artnet.subscribe([&](const uint32_t univ, const uint8_t* data, const uint16_t size) {
|
||||||
|
Serial.print("ArtNet data has come to universe: ");
|
||||||
|
Serial.println(univ);
|
||||||
|
});
|
||||||
|
|
||||||
|
Serial.println("start");
|
||||||
|
}
|
||||||
|
|
||||||
|
void loop() {
|
||||||
|
artnet.parse(); // check if artnet packet has come and execute callback
|
||||||
|
|
||||||
|
value = (millis() / 4) % 256;
|
||||||
|
memset(data, value, size);
|
||||||
|
|
||||||
|
artnet.streaming_data(data, size);
|
||||||
|
artnet.streaming(target_ip, universe); // automatically send set data in 40fps
|
||||||
|
// artnet.streaming(target_ip, net, subnet, univ); // or you can set net, subnet, and universe
|
||||||
|
}
|
||||||
@@ -0,0 +1,38 @@
|
|||||||
|
// Please include ArtnetEther.h to use Artnet on the platform
|
||||||
|
// which can use both WiFi and Ethernet
|
||||||
|
#include <ArtnetEther.h>
|
||||||
|
// this is also valid for other platforms which can use only Ethernet
|
||||||
|
// #include <Artnet.h>
|
||||||
|
|
||||||
|
// Ethernet stuff
|
||||||
|
const IPAddress ip(192, 168, 0, 201);
|
||||||
|
uint8_t mac[] = {0x01, 0x23, 0x45, 0x67, 0x89, 0xAB};
|
||||||
|
|
||||||
|
ArtnetSender artnet;
|
||||||
|
const String target_ip = "192.168.0.200";
|
||||||
|
uint32_t universe = 1;
|
||||||
|
|
||||||
|
const uint16_t size = 512;
|
||||||
|
uint8_t data[size];
|
||||||
|
uint8_t value = 0;
|
||||||
|
|
||||||
|
void setup() {
|
||||||
|
Serial.begin(115200);
|
||||||
|
delay(2000);
|
||||||
|
|
||||||
|
Ethernet.begin(mac, ip);
|
||||||
|
artnet.begin();
|
||||||
|
// artnet.begin(net, subnet); // optionally you can change
|
||||||
|
|
||||||
|
Serial.println("start");
|
||||||
|
Serial.println(UDP_TX_PACKET_MAX_SIZE);
|
||||||
|
}
|
||||||
|
|
||||||
|
void loop() {
|
||||||
|
value = (millis() / 4) % 256;
|
||||||
|
memset(data, value, size);
|
||||||
|
|
||||||
|
artnet.streaming_data(data, size);
|
||||||
|
artnet.streaming(target_ip, universe); // automatically send set data in 40fps
|
||||||
|
// artnet.streaming(target_ip, net, subnet, univ); // or you can set net, subnet, and universe
|
||||||
|
}
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
# ArtNet
|
||||||
|
|
||||||
|
Implementation is taken form `hideakitai` @ [github](https://github.com/hideakitai/ArtNet)
|
||||||
|
|
||||||
+140
-10
@@ -35,6 +35,8 @@
|
|||||||
#include "cliWrapper.hpp"
|
#include "cliWrapper.hpp"
|
||||||
#include "udpSockets.hpp"
|
#include "udpSockets.hpp"
|
||||||
#include "tcpSockets.hpp"
|
#include "tcpSockets.hpp"
|
||||||
|
#include "Queue.hpp"
|
||||||
|
#include "Artnet.h"
|
||||||
|
|
||||||
static const char *TAG = "Watch";
|
static const char *TAG = "Watch";
|
||||||
static esp_eth_handle_t s_eth_handle = NULL;
|
static esp_eth_handle_t s_eth_handle = NULL;
|
||||||
@@ -44,6 +46,35 @@ extern "C" { // This switch allows the ROS C-implementation to find this main
|
|||||||
void app_main(void);
|
void app_main(void);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
struct Color {
|
||||||
|
uint8_t r;
|
||||||
|
uint8_t g;
|
||||||
|
uint8_t b;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct DeviceState {
|
||||||
|
int jumps;
|
||||||
|
int pictogram;
|
||||||
|
int profile;
|
||||||
|
float speed = 1.0; // Rad/sec
|
||||||
|
float accel = 1.0; // Rad/sec^2
|
||||||
|
Color color{16, 16, 16};
|
||||||
|
};
|
||||||
|
|
||||||
|
DeviceState activeState;
|
||||||
|
enum class MoveType {
|
||||||
|
Step,
|
||||||
|
AllPictograms,
|
||||||
|
Arbitrary,
|
||||||
|
};
|
||||||
|
struct MoveCommand {
|
||||||
|
MoveType type;
|
||||||
|
uint8_t deltaPic;
|
||||||
|
uint8_t profile;
|
||||||
|
Color color;
|
||||||
|
};
|
||||||
|
static fos::Queue<MoveCommand> commandQueue(8);
|
||||||
|
|
||||||
|
|
||||||
std::unique_ptr<Cli> shellPtr;
|
std::unique_ptr<Cli> shellPtr;
|
||||||
|
|
||||||
@@ -117,24 +148,123 @@ static void telnetSocketTask(int addrFamily) {
|
|||||||
vTaskDelete(NULL);
|
vTaskDelete(NULL);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
constexpr uint32_t DmxUniverse = 0;
|
||||||
|
constexpr int DmxChannelCount = 9; //
|
||||||
|
|
||||||
|
uint8_t scaledRgbChannel(uint8_t value, float scale) {
|
||||||
|
return static_cast<uint8_t>(value * scale);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Callback for ArtNet DMX-socket. Does not send data only updates the image
|
||||||
|
*
|
||||||
|
* @param data Pointer to dmx-values
|
||||||
|
* @param size length of dmx-values
|
||||||
|
*/
|
||||||
|
static void UniverseCallback(const uint8_t *data, const uint16_t size) {
|
||||||
|
int len = std::min(static_cast<int>(size), DmxChannelCount);
|
||||||
|
ESP_LOGD(TAG, "Got DMX");
|
||||||
|
ESP_LOG_BUFFER_HEXDUMP("DMX", data, len, ESP_LOG_DEBUG);
|
||||||
|
Syslog::logv(Syslog::Severity::Debug, "Got DMX: r%0x g%0x b%0x i%0x trig%i j%i p%i a%i w%i",
|
||||||
|
data[0], data[1], data[2], data[3], data[4], data[5], data[6], data[7], data[8]);
|
||||||
|
|
||||||
|
float intens = (float)data[3] / UINT8_MAX;
|
||||||
|
// activeState.color = Color{
|
||||||
|
// scaledRgbChannel(data[0], intens),
|
||||||
|
// scaledRgbChannel(data[1], intens),
|
||||||
|
// scaledRgbChannel(data[2], intens),
|
||||||
|
// 0};
|
||||||
|
|
||||||
|
static uint8_t lastTrig = 0;
|
||||||
|
uint8_t thisTrig = data[4];
|
||||||
|
if (thisTrig == lastTrig) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
lastTrig = thisTrig;
|
||||||
|
uint8_t jumps = data[5];
|
||||||
|
activeState.jumps = jumps;
|
||||||
|
uint8_t profile = data[6];
|
||||||
|
uint8_t accel = data[7];
|
||||||
|
uint8_t velo = data[8];
|
||||||
|
activeState.accel = (float)accel / 100 + 0.5; // min .5 / max 3
|
||||||
|
activeState.speed = (float)velo / 100 + 0.5; // min .5 / max 3
|
||||||
|
activeState.profile = profile;
|
||||||
|
|
||||||
|
Syslog::logv(Syslog::Severity::Notice, "Command detected step=%i prof=%i accel=%.1f velo=%.1f", jumps, profile, activeState.accel, activeState.speed);
|
||||||
|
commandQueue.enqueue(std::make_unique<MoveCommand>(MoveType::Step, jumps, profile, activeState.color));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief DMX-Socket for ArtNet TCP/IP
|
||||||
|
*
|
||||||
|
* @details Tested with [Q Light Controller+](https://www.qlcplus.org/)
|
||||||
|
* @param args ignored task argument
|
||||||
|
*/
|
||||||
|
static void dmxSocketSenderTask(UdpSocket &socket) {
|
||||||
|
try {
|
||||||
|
ArtnetSender artnet(socket);
|
||||||
|
std::array<uint8_t, 16> data;
|
||||||
|
while (socket.isActive()) {
|
||||||
|
data[0]++;
|
||||||
|
artnet.send("", 0, data.data(), data.size());
|
||||||
|
fos::Task::delayMs(500);
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (const std::exception &e) {
|
||||||
|
ESP_LOGE("UDP", "%s", e.what());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief DMX-Socket for ArtNet TCP/IP
|
||||||
|
*
|
||||||
|
* @details Tested with [Q Light Controller+](https://www.qlcplus.org/)
|
||||||
|
* @param args ignored task argument
|
||||||
|
*/
|
||||||
|
static void dmxSocketTask(int addrFamily) {
|
||||||
|
|
||||||
|
ESP_LOGI(TAG, "Starting ArtNet task");
|
||||||
|
constexpr uint16_t ArtNetPort = arx::artnet::DEFAULT_PORT;
|
||||||
|
IpAdr address = addrFamily == AF_INET ? IpAdr(INADDR_ANY, ArtNetPort) : IpAdr(ArtNetPort);
|
||||||
|
|
||||||
|
try {
|
||||||
|
while (true) {
|
||||||
|
UdpSocket socket(address);
|
||||||
|
socket.bindTo();
|
||||||
|
|
||||||
|
ArtnetReceiver artnet(socket);
|
||||||
|
artnet.subscribe(DmxUniverse, UniverseCallback);
|
||||||
|
artnet.parse();
|
||||||
|
// auto msg = std::make_unique<Display::Message>(Display::Message::Type::UpdateInfo, "DMX-IP-Mode");
|
||||||
|
// displayEvents.enqueue(std::move(msg));
|
||||||
|
// deviceState.setNewState(DeviceState::Mode::Remote);
|
||||||
|
// xTaskCreate(valueRefresherTask, "valueRefresher", 4096, nullptr, 5, NULL);
|
||||||
|
// static TaskT<UdpSocket &> dmxSender(dmxSocketSenderTask, socket, "dmxSender", 4096);
|
||||||
|
|
||||||
|
// parse artnet as long socket is active, then restart socket
|
||||||
|
while (socket.isActive()) {
|
||||||
|
ESP_LOGD(TAG, "Waiting for data");
|
||||||
|
artnet.parse();
|
||||||
|
// socket.write(data);
|
||||||
|
}
|
||||||
|
// deviceState.fallbackToLast();
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (const std::exception &e) {
|
||||||
|
ESP_LOGE("UDP", "%s", e.what());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void startServerTasks() {
|
void startServerTasks() {
|
||||||
static int ipVersion = AF_INET;
|
static int ipVersion = AF_INET;
|
||||||
static fos::TaskT<int> telnetSocket(telnetSocketTask, ipVersion, "tcpServer", 4096);
|
static fos::TaskT<int> telnetSocket(telnetSocketTask, ipVersion, "tcpServer", 4096);
|
||||||
// static TaskT<int> dmxNode(dmxSocketTask, ipVersion, "dmxNode", 4096);
|
static fos::TaskT<int> dmxNode(dmxSocketTask, ipVersion, "dmxNode", 4096);
|
||||||
// static Task otaUpdate(SystemFunctions::OtaTaskEntry, "otaTask", 8192);
|
// static Task otaUpdate(SystemFunctions::OtaTaskEntry, "otaTask", 8192);
|
||||||
|
|
||||||
// initialise_mdns();
|
// initialise_mdns();
|
||||||
// netbiosns_init();
|
// netbiosns_init();
|
||||||
// netbiosns_set_name(CONFIG_EXAMPLE_MDNS_HOST_NAME);
|
// netbiosns_set_name(CONFIG_EXAMPLE_MDNS_HOST_NAME);
|
||||||
// WebServer::init(FileSystem::getInstance());
|
|
||||||
// WebServer::getInstance(FileSystem::getInstance()).registerCallback(printSysState);
|
|
||||||
|
|
||||||
// auto &store = NvsApi::getInstance();
|
// auto &store = NvsApi::getInstance();
|
||||||
// auto broker = store.getValue("mqttBroker");
|
|
||||||
// if (broker != "") {
|
|
||||||
// MqttClient::init(broker);
|
|
||||||
// }
|
|
||||||
|
|
||||||
// auto logger = store.getValue("syslogDst");
|
// auto logger = store.getValue("syslogDst");
|
||||||
// if (logger != "") {
|
// if (logger != "") {
|
||||||
// Syslog::setupTask(IpAdr{logger, 0});
|
// Syslog::setupTask(IpAdr{logger, 0});
|
||||||
|
|||||||
Reference in New Issue
Block a user