including ArtNet feature from different Project

This commit is contained in:
2024-12-15 22:21:39 +01:00
parent 1cf86768c0
commit e4621f8ad8
10 changed files with 1012 additions and 10 deletions
+98
View File
@@ -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
+540
View File
@@ -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
+5
View File
@@ -0,0 +1,5 @@
idf_component_register(
SRC_DIRS "."
INCLUDE_DIRS "."
REQUIRES sockets
)
+21
View File
@@ -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
}
+4
View File
@@ -0,0 +1,4 @@
# ArtNet
Implementation is taken form `hideakitai` @ [github](https://github.com/hideakitai/ArtNet)
+140 -10
View File
@@ -35,6 +35,8 @@
#include "cliWrapper.hpp"
#include "udpSockets.hpp"
#include "tcpSockets.hpp"
#include "Queue.hpp"
#include "Artnet.h"
static const char *TAG = "Watch";
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);
}
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;
@@ -117,24 +148,123 @@ static void telnetSocketTask(int addrFamily) {
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() {
static int ipVersion = AF_INET;
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);
// initialise_mdns();
// netbiosns_init();
// netbiosns_set_name(CONFIG_EXAMPLE_MDNS_HOST_NAME);
// WebServer::init(FileSystem::getInstance());
// WebServer::getInstance(FileSystem::getInstance()).registerCallback(printSysState);
// auto &store = NvsApi::getInstance();
// auto broker = store.getValue("mqttBroker");
// if (broker != "") {
// MqttClient::init(broker);
// }
// auto logger = store.getValue("syslogDst");
// if (logger != "") {
// Syslog::setupTask(IpAdr{logger, 0});