#pragma once #ifndef ARTNET_RECEIVER_H #define ARTNET_RECEIVER_H #include "Common.h" #include "ArtDmx.h" #include "ArtNzs.h" #include "ArtPollReply.h" #include "ArtTrigger.h" #include "ArtSync.h" namespace art_net { namespace { struct NoPrint : public Print { size_t write(uint8_t) override { return 0; } }; static NoPrint no_log; } // namespace template class Receiver_ { S *stream; Array packet; art_dmx::CallbackMap callback_art_dmx_universes; art_dmx::CallbackType callback_art_dmx; art_nzs::CallbackMap callback_art_nzs_universes; art_sync::CallbackType callback_art_sync; art_trigger::CallbackType callback_art_trigger; ArtPollReplyConfig art_poll_reply_config; Print *logger {&no_log}; public: #if ARX_HAVE_LIBSTDCPLUSPLUS >= 201103L // Have libstdc++11 #else Receiver_() { this->packet.resize(PACKET_SIZE); } #endif OpCode parse() { #ifdef ARTNET_ENABLE_WIFI if (!isNetworkReady()) { return OpCode::NoPacket; } #endif size_t size = this->stream->parsePacket(); if (size == 0) { return OpCode::NoPacket; } this->logger->print(F("Packet received: size = ")); this->logger->println(size); if (size > PACKET_SIZE) { this->logger->print(F("Packet size is unexpectedly too large: ")); this->logger->println(size); size = PACKET_SIZE; } this->stream->read(this->packet.data(), size); if (!checkID()) { this->logger->println(F("Packet ID is not Art-Net")); return OpCode::ParseFailed; } RemoteInfo remote_info; remote_info.ip = this->stream->S::remoteIP(); remote_info.port = (uint16_t)this->stream->S::remotePort(); OpCode op_code = OpCode::Unsupported; OpCode received_op_code = static_cast(this->getOpCode()); switch (received_op_code) { case OpCode::Dmx: { art_dmx::Metadata metadata = art_dmx::generateMetadataFrom(this->packet.data()); if (this->callback_art_dmx) { this->callback_art_dmx(this->getArtDmxData(), size - HEADER_SIZE, metadata, remote_info); } for (auto& cb : this->callback_art_dmx_universes) { if (this->getArtDmxUniverse15bit() == cb.first) { cb.second(this->getArtDmxData(), size - HEADER_SIZE, metadata, remote_info); } } op_code = OpCode::Dmx; break; } case OpCode::Nzs: { art_nzs::Metadata metadata = art_nzs::generateMetadataFrom(this->packet.data()); for (auto& cb : this->callback_art_nzs_universes) { if (this->getArtDmxUniverse15bit() == cb.first) { cb.second(this->getArtDmxData(), size - HEADER_SIZE, metadata, remote_info); } } op_code = OpCode::Nzs; break; } case OpCode::Poll: { this->sendArtPollReply(remote_info); op_code = OpCode::Poll; break; } case OpCode::Trigger: { if (this->callback_art_trigger) { ArtTriggerMetadata metadata = { .oem = this->getArtTriggerOEM(), .key = this->getArtTriggerKey(), .sub_key = this->getArtTriggerSubKey(), .payload = this->getArtTriggerPayload(), .size = static_cast(size - art_trigger::PAYLOAD), }; this->callback_art_trigger(metadata, remote_info); } op_code = OpCode::Trigger; break; } case OpCode::Sync: { if (this->callback_art_sync) { this->callback_art_sync(remote_info); } op_code = OpCode::Sync; break; } default: { this->logger->print(F("Unsupported OpCode: ")); this->logger->println(this->getOpCode(), HEX); op_code = OpCode::Unsupported; break; } } this->stream->flush(); return op_code; } // subscribe artdmx packet for specified net, subnet, and universe template auto subscribeArtDmxUniverse(uint8_t net, uint8_t subnet, uint8_t universe, const Fn &func) -> std::enable_if_t::value> { if (net > 0x7F) { this->logger->println(F("net should be less than 0x7F")); return; } if (subnet > 0xF) { this->logger->println(F("subnet should be less than 0xF")); return; } if (universe > 0xF) { this->logger->println(F("universe should be less than 0xF")); return; } uint16_t u = ((uint16_t)net << 8) | ((uint16_t)subnet << 4) | (uint16_t)universe; this->subscribeArtDmxUniverse(u, func); } // subscribe artdmx packet for specified universe (15 bit) template auto subscribeArtDmxUniverse(uint16_t universe, const Fn &func) -> std::enable_if_t::value> { this->callback_art_dmx_universes.insert(std::make_pair(universe, arx::function_traits::cast(func))); } // subscribe artnzs packet for specified universe (15 bit) template auto subscribeArtNzsUniverse(uint16_t universe, const Fn &func) -> std::enable_if_t::value> { this->callback_art_nzs_universes.insert(std::make_pair(universe, arx::function_traits::cast(func))); } // subscribe artdmx packet for all universes template auto subscribeArtDmx(const Fn &func) -> std::enable_if_t::value> { this->callback_art_dmx = arx::function_traits::cast(func); } // subscribe other packets template auto subscribeArtSync(const Fn &func) -> std::enable_if_t::value> { this->callback_art_sync = arx::function_traits::cast(func); } template auto subscribeArtTrigger(const Fn &func) -> std::enable_if_t::value> { this->callback_art_trigger = arx::function_traits::cast(func); } void unsubscribeArtDmxUniverse(uint8_t net, uint8_t subnet, uint8_t universe) { uint16_t u = ((uint16_t)net << 8) | ((uint16_t)subnet << 4) | (uint16_t)universe; this->unsubscribe(u); } void unsubscribeArtDmxUniverse(uint16_t universe) { auto it = this->callback_art_dmx_universes.find(universe); if (it != this->callback_art_dmx_universes.end()) { this->callback_art_dmx_universes.erase(it); } } void unsubscribeArtDmxUniverses() { this->callback_art_dmx_universes.clear(); } void unsubscribeArtDmx() { this->callback_art_dmx = nullptr; } void unsubscribeArtNzsUniverse(uint16_t universe) { auto it = this->callback_art_nzs_universes.find(universe); if (it != this->callback_art_nzs_universes.end()) { this->callback_art_nzs_universes.erase(it); } } void unsubscribeArtSync() { this->callback_art_sync = nullptr; } void unsubscribeArtTrigger() { this->callback_art_trigger = nullptr; } #ifdef FASTLED_VERSION void forwardArtDmxDataToFastLED(uint8_t net, uint8_t subnet, uint8_t universe, CRGB* leds, uint16_t num) { uint16_t u = ((uint16_t)net << 8) | ((uint16_t)subnet << 4) | (uint16_t)universe; this->forwardArtDmxDataToFastLED(u, leds, num); } void forwardArtDmxDataToFastLED(uint16_t universe, CRGB* leds, uint16_t num) { this->subscribeArtDmxUniverse(universe, [this, leds, num](const uint8_t* data, const uint16_t size, const ArtDmxMetadata &, const RemoteInfo &) { size_t n; if (num <= size / 3) { // OK: requested number of LEDs is less than received data size n = num; } else { n = size / 3; this->logger->println(F("WARN: ArtNet packet size is less than requested LED numbers to forward")); this->logger->print(F(" requested: ")); this->logger->print(num * 3); this->logger->print(F(" received : ")); this->logger->println(size); } for (size_t pixel = 0; pixel < n; ++pixel) { size_t idx = pixel * 3; leds[pixel].r = data[idx + 0]; leds[pixel].g = data[idx + 1]; leds[pixel].b = data[idx + 2]; } }); } #endif // https://art-net.org.uk/how-it-works/discovery-packets/artpollreply/ void setArtPollReplyConfigOem(uint16_t oem) { this->art_poll_reply_config.oem = oem; } void setArtPollReplyConfigEstaMan(uint16_t esta_man) { this->art_poll_reply_config.esta_man = esta_man; } void setArtPollReplyConfigStatus1(uint8_t status1) { this->art_poll_reply_config.status1 = status1; } void setArtPollReplyConfigStatus2(uint8_t status2) { this->art_poll_reply_config.status2 = status2; } void setArtPollReplyConfigShortName(const String &short_name) { this->art_poll_reply_config.short_name = short_name; } void setArtPollReplyConfigLongName(const String &long_name) { this->art_poll_reply_config.long_name = long_name; } void setArtPollReplyConfigNodeReport(const String &node_report) { this->art_poll_reply_config.node_report = node_report; } void setArtPollReplyConfigSwIn(size_t index, uint8_t sw_in) { if (index < 4) { this->art_poll_reply_config.sw_in[index] = sw_in; } } void setArtPollReplyConfigSwIn(uint8_t sw_in[4]) { for (size_t i = 0; i < 4; ++i) { this->art_poll_reply_config.sw_in[i] = sw_in[i]; } } void setArtPollReplyConfigSwIn(uint8_t sw_in_0, uint8_t sw_in_1, uint8_t sw_in_2, uint8_t sw_in_3) { this->setArtPollReplyConfigSwIn(0, sw_in_0); this->setArtPollReplyConfigSwIn(1, sw_in_1); this->setArtPollReplyConfigSwIn(2, sw_in_2); this->setArtPollReplyConfigSwIn(3, sw_in_3); } void setArtPollReplyConfig( uint16_t oem, uint16_t esta_man, uint8_t status1, uint8_t status2, const String &short_name, const String &long_name, const String &node_report, uint8_t sw_in[4] ) { this->setArtPollReplyConfigOem(oem); this->setArtPollReplyConfigEstaMan(esta_man); this->setArtPollReplyConfigStatus1(status1); this->setArtPollReplyConfigStatus2(status2); this->setArtPollReplyConfigShortName(short_name); this->setArtPollReplyConfigLongName(long_name); this->setArtPollReplyConfigNodeReport(node_report); this->setArtPollReplyConfigSwIn(sw_in); } void setArtPollReplyConfig(const ArtPollReplyConfig &cfg) { this->art_poll_reply_config = cfg; } void setLogger(Print* logger) { this->logger = logger; } protected: void attach(S& s) { this->stream = &s; } private: bool checkID() const { const char* idptr = reinterpret_cast(this->packet.data()); return !strcmp(ARTNET_ID, idptr); } uint16_t getOpCode() const { return (this->packet[art_dmx::OP_CODE_H] << 8) | this->packet[art_dmx::OP_CODE_L]; } uint16_t getArtDmxUniverse15bit() const { return (this->packet[art_dmx::NET] << 8) | this->packet[art_dmx::SUBUNI]; } const uint8_t *getArtDmxData() const { return &(this->packet[art_dmx::DATA]); } void sendArtPollReply(const RemoteInfo &remote) { const IPAddress my_ip = this->localIP(); uint8_t my_mac[6]; this->macAddress(my_mac); arx::stdx::map universes; for (const auto &cb_pair : this->callback_art_dmx_universes) { universes[cb_pair.first] = true; } for (const auto &cb_pair : this->callback_art_nzs_universes) { universes[cb_pair.first] = true; } // if no universe is subscribed, send reply for universe 0 if (universes.empty()) { universes[0] = true; } for (const auto &u_pair : universes) { art_poll_reply::Packet reply = art_poll_reply::generatePacketFrom(my_ip, my_mac, u_pair.first, this->art_poll_reply_config); this->stream->beginPacket(remote.ip, DEFAULT_PORT); this->stream->write(reply.b, sizeof(art_poll_reply::Packet)); this->stream->endPacket(); } } uint16_t getArtTriggerOEM() const { return (this->packet[art_trigger::OEM_H] << 8) | this->packet[art_trigger::OEM_L]; } uint8_t getArtTriggerKey() const { return this->packet[art_trigger::KEY]; } uint8_t getArtTriggerSubKey() const { return this->packet[art_trigger::SUB_KEY]; } const uint8_t *getArtTriggerPayload() const { return this->packet.data() + art_trigger::PAYLOAD; } IPAddress localIP() { switch (WiFi.getMode()) { case WIFI_AP: return WiFi.softAPIP(); break; case WIFI_STA: return WiFi.localIP(); break; default: return ETH.localIP(); break; } } IPAddress subnetMask() { switch (WiFi.getMode()) { case WIFI_AP: return WiFi.softAPSubnetMask(); break; case WIFI_STA: return WiFi.subnetMask(); break; default: return ETH.subnetMask(); break; } } void macAddress(uint8_t* mac) { switch (WiFi.getMode()) { case WIFI_AP: WiFi.softAPmacAddress(mac); break; case WIFI_STA: WiFi.macAddress(mac); break; default: ETH.macAddress(mac); //Ethernet.MACAddress(mac); break; } } }; template class Receiver : public Receiver_ { S stream; public: void begin(uint16_t recv_port = DEFAULT_PORT) { this->stream.begin(recv_port); this->Receiver_::attach(this->stream); } }; } // namespace art_net #endif // ARTNET_RECEIVER_H