diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index b21855f..bfacece 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -385,3 +385,52 @@ jobs: - name: FastLED - source-url: https://github.com/JAndrassy/EthernetENC.git verbose: true + + build-multi: + name: "Build Test (Multiple): ${{matrix.board.arch}}:${{matrix.board.name}}" + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + board: + - vendor: esp8266 + arch: esp8266 + name: generic + - vendor: esp32 + arch: esp32 + name: esp32 + - vendor: esp32 + arch: esp32 + name: esp32s3 + - vendor: esp32 + arch: esp32 + name: esp32c3 + include: + - index: https://arduino.esp8266.com/stable/package_esp8266com_index.json + board: + vendor: esp8266 + - index: https://raw.githubusercontent.com/espressif/arduino-esp32/gh-pages/package_esp32_index.json + board: + vendor: esp32 + steps: + - uses: actions/checkout@v4 + - uses: arduino/arduino-lint-action@v1 + with: + library-manager: update + - name: compile example sketchs + uses: arduino/compile-sketches@v1 + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + fqbn: ${{matrix.board.vendor}}:${{matrix.board.arch}}:${{matrix.board.name}} + platforms: | + - name: ${{matrix.board.vendor}}:${{matrix.board.arch}} + source-url: ${{matrix.index}} + sketch-paths: | + - examples/Multiple + libraries: | + - source-path: ./ + - name: ArxContainer + - name: ArxTypeTraits + - name: WiFi + - name: Ethernet + verbose: true diff --git a/Artnet/Manager.h b/Artnet/Manager.h index 9559870..f5116ee 100644 --- a/Artnet/Manager.h +++ b/Artnet/Manager.h @@ -1,32 +1,28 @@ #pragma once #ifndef ARTNET_MANAGER_H +#define ARTNET_MANAGER_H #include "Common.h" #include "Receiver.h" #include "Sender.h" +#include "ManagerTraits.h" namespace art_net { template -class Manager : public Sender_, public Receiver_ +class Manager : public IManager, public Sender_, public Receiver_ { S stream; public: - void begin(uint16_t recv_port = DEFAULT_PORT) + void begin(uint16_t port = DEFAULT_PORT) override { - this->stream.begin(recv_port); + this->stream.begin(port); this->Sender_::attach(this->stream); this->Receiver_::attach(this->stream); } - - void parse() - { - this->Receiver_::parse(); - } }; - } // namespace art_net #endif // ARTNET_MANAGER_H diff --git a/Artnet/ManagerTraits.h b/Artnet/ManagerTraits.h new file mode 100644 index 0000000..2042f42 --- /dev/null +++ b/Artnet/ManagerTraits.h @@ -0,0 +1,20 @@ +#pragma once +#ifndef ARTNET_MANAGER_TRAITS_H +#define ARTNET_MANAGER_TRAITS_H + +#include "ReceiverTraits.h" +#include "SenderTraits.h" + +namespace art_net { + +struct IManager : virtual ISender_, virtual IReceiver_ +{ + virtual ~IManager() = default; + virtual void begin(uint16_t port = DEFAULT_PORT) = 0; +}; + +} // namespace art_net + +using ArtnetInterface = art_net::IManager; + +#endif // ARTNET_MANAGER_TRAITS_H diff --git a/Artnet/Receiver.h b/Artnet/Receiver.h index 415a21f..55118f7 100644 --- a/Artnet/Receiver.h +++ b/Artnet/Receiver.h @@ -8,6 +8,7 @@ #include "ArtPollReply.h" #include "ArtTrigger.h" #include "ArtSync.h" +#include "ReceiverTraits.h" namespace art_net { @@ -22,7 +23,11 @@ static NoPrint no_log; } // namespace template +#ifndef ARDUINO_ARCH_AVR +class Receiver_ : virtual IReceiver_ +#else class Receiver_ +#endif { S *stream; Array packet; @@ -140,9 +145,7 @@ class Receiver_ } // 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> + void subscribeArtDmxUniverse(uint8_t net, uint8_t subnet, uint8_t universe, const ArtDmxCallback& func) { if (net > 0x7F) { this->logger->println(F("net should be less than 0x7F")); @@ -161,42 +164,33 @@ class Receiver_ } // subscribe artdmx packet for specified universe (15 bit) - template - auto subscribeArtDmxUniverse(uint16_t universe, const Fn &func) - -> std::enable_if_t::value> + void subscribeArtDmxUniverse(uint16_t universe, const ArtDmxCallback& func) { - this->callback_art_dmx_universes.insert(std::make_pair(universe, arx::function_traits::cast(func))); + this->callback_art_dmx_universes.insert(std::make_pair(universe, func)); } // subscribe artnzs packet for specified universe (15 bit) - template - auto subscribeArtNzsUniverse(uint16_t universe, const Fn &func) - -> std::enable_if_t::value> + void subscribeArtNzsUniverse(uint16_t universe, const ArtNzsCallback& func) { - this->callback_art_nzs_universes.insert(std::make_pair(universe, arx::function_traits::cast(func))); + this->callback_art_nzs_universes.insert(std::make_pair(universe, func)); } // subscribe artdmx packet for all universes - template - auto subscribeArtDmx(const Fn &func) - -> std::enable_if_t::value> + void subscribeArtDmx(const ArtDmxCallback& func) { - this->callback_art_dmx = arx::function_traits::cast(func); + this->callback_art_dmx = func; } // subscribe other packets - template - auto subscribeArtSync(const Fn &func) - -> std::enable_if_t::value> + void subscribeArtSync(const ArtSyncCallback& func) { - this->callback_art_sync = arx::function_traits::cast(func); + this->callback_art_sync = func; } - template - auto subscribeArtTrigger(const Fn &func) - -> std::enable_if_t::value> + // subscribe art_trigger packet + void subscribeArtTrigger(const ArtTriggerCallback& func) { - this->callback_art_trigger = arx::function_traits::cast(func); + this->callback_art_trigger = func; } void unsubscribeArtDmxUniverse(uint8_t net, uint8_t subnet, uint8_t universe) @@ -424,7 +418,11 @@ class Receiver_ }; template +#ifndef ARDUINO_ARCH_AVR +class Receiver : public IReceiver, public Receiver_ +#else class Receiver : public Receiver_ +#endif { S stream; diff --git a/Artnet/ReceiverTraits.h b/Artnet/ReceiverTraits.h index c951c8d..a09c3b3 100644 --- a/Artnet/ReceiverTraits.h +++ b/Artnet/ReceiverTraits.h @@ -2,6 +2,13 @@ #ifndef ARTNET_RECEIVER_TRAITS_H #define ARTNET_RECEIVER_TRAITS_H +#include "Common.h" +#include "ArtDmx.h" +#include "ArtNzs.h" +#include "ArtPollReply.h" +#include "ArtTrigger.h" +#include "ArtSync.h" + namespace art_net { template struct LocalIP; @@ -25,6 +32,10 @@ template bool isNetworkReady(T&& x) { return IsNetworkReady>::get(std::forward(x)); } +template +bool isNetworkReady(T&& x) { + return IsNetworkReady>::isNetworkReady(std::forward(x)); +} template IPAddress getLocalIP(); @@ -35,6 +46,70 @@ void getMacAddress(uint8_t mac[6]); template bool isNetworkReady(); +struct IReceiver_ +{ + virtual ~IReceiver_() = default; + + virtual OpCode parse() = 0; + // subscribe artdmx packet for specified net, subnet, and universe + virtual void subscribeArtDmxUniverse(uint8_t net, uint8_t subnet, uint8_t universe, const ArtDmxCallback& func) = 0; + // subscribe artdmx packet for specified universe (15 bit) + virtual void subscribeArtDmxUniverse(uint16_t universe, const ArtDmxCallback& func) = 0; + // subscribe artnzs packet for specified universe (15 bit) + virtual void subscribeArtNzsUniverse(uint16_t universe, const ArtNzsCallback& func) = 0; + // subscribe artdmx packet for all universes + virtual void subscribeArtDmx(const ArtDmxCallback& func) = 0; + // subscribe other packets + virtual void subscribeArtSync(const ArtSyncCallback& func) = 0; + // subscribe art_trigger packet + virtual void subscribeArtTrigger(const ArtTriggerCallback& func) = 0; + + virtual void unsubscribeArtDmxUniverse(uint8_t net, uint8_t subnet, uint8_t universe) = 0; + virtual void unsubscribeArtDmxUniverse(uint16_t universe) = 0; + virtual void unsubscribeArtDmxUniverses() = 0; + virtual void unsubscribeArtDmx() = 0; + virtual void unsubscribeArtNzsUniverse(uint16_t universe) = 0; + virtual void unsubscribeArtSync() = 0; + virtual void unsubscribeArtTrigger() = 0; + +#ifdef FASTLED_VERSION + virtual void forwardArtDmxDataToFastLED(uint8_t net, uint8_t subnet, uint8_t universe, CRGB* leds, uint16_t num) = 0; + virtual void forwardArtDmxDataToFastLED(uint16_t universe, CRGB* leds, uint16_t num) = 0; +#endif + + // https://art-net.org.uk/how-it-works/discovery-packets/artpollreply/ + virtual void setArtPollReplyConfigOem(uint16_t oem) = 0; + virtual void setArtPollReplyConfigEstaMan(uint16_t esta_man) = 0; + virtual void setArtPollReplyConfigStatus1(uint8_t status1) = 0; + virtual void setArtPollReplyConfigStatus2(uint8_t status2) = 0; + virtual void setArtPollReplyConfigShortName(const String &short_name) = 0; + virtual void setArtPollReplyConfigLongName(const String &long_name) = 0; + virtual void setArtPollReplyConfigNodeReport(const String &node_report) = 0; + virtual void setArtPollReplyConfigSwIn(size_t index, uint8_t sw_in) = 0; + virtual void setArtPollReplyConfigSwIn(uint8_t sw_in[4]) = 0; + virtual void setArtPollReplyConfigSwIn(uint8_t sw_in_0, uint8_t sw_in_1, uint8_t sw_in_2, uint8_t sw_in_3) = 0; + virtual 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] + ) = 0; + virtual void setArtPollReplyConfig(const ArtPollReplyConfig &cfg) = 0; + virtual void setLogger(Print* logger) = 0; +}; + +struct IReceiver : virtual IReceiver_ +{ + virtual ~IReceiver() = default; + virtual void begin(uint16_t recv_port = DEFAULT_PORT) = 0; +}; + } // namespace art_net +using ArtnetReceiverInterface = art_net::IReceiver; + #endif // ARTNET_RECEIVER_TRAITS_H diff --git a/Artnet/Sender.h b/Artnet/Sender.h index 7fb3fc5..f198a7e 100644 --- a/Artnet/Sender.h +++ b/Artnet/Sender.h @@ -7,11 +7,16 @@ #include "ArtNzs.h" #include "ArtTrigger.h" #include "ArtSync.h" +#include "SenderTraits.h" namespace art_net { template +#ifndef ARDUINO_ARCH_AVR +class Sender_ : virtual ISender_ +#else class Sender_ +#endif { S* stream; Array packet; @@ -189,14 +194,18 @@ class Sender_ }; template +#ifndef ARDUINO_ARCH_AVR +class Sender : public ISender, public Sender_ +#else class Sender : public Sender_ +#endif { S stream; public: - void begin() + void begin(uint16_t send_port = DEFAULT_PORT) { - this->stream.begin(DEFAULT_PORT); + this->stream.begin(send_port); this->Sender_::attach(this->stream); } }; diff --git a/Artnet/SenderTraits.h b/Artnet/SenderTraits.h new file mode 100644 index 0000000..a2469f5 --- /dev/null +++ b/Artnet/SenderTraits.h @@ -0,0 +1,52 @@ +#pragma once +#ifndef ARTNET_SENDER_TRAITS_H +#define ARTNET_SENDER_TRAITS_H + +namespace art_net { + +struct ISender_ +{ + virtual ~ISender_() = default; + + // streaming artdmx packet + virtual void setArtDmxData(const uint8_t* const data, uint16_t size) = 0; + virtual void setArtDmxData(uint16_t ch, uint8_t data) = 0; + + virtual void streamArtDmxTo(const String& ip, uint16_t universe15bit) = 0; + virtual void streamArtDmxTo(const String& ip, uint8_t net, uint8_t subnet, uint8_t universe) = 0; + virtual void streamArtDmxTo(const String& ip, uint8_t net, uint8_t subnet, uint8_t universe, uint8_t physical) = 0; + + // streaming artnzs packet + virtual void setArtNzsData(const uint8_t* const data, uint16_t size) = 0; + virtual void setArtNzsData(uint16_t ch, uint8_t data) = 0; + + virtual void streamArtNzsTo(const String& ip, uint16_t universe15bit) = 0; + virtual void streamArtNzsTo(const String& ip, uint8_t net, uint8_t subnet, uint8_t universe) = 0; + virtual void streamArtNzsTo(const String& ip, uint8_t net, uint8_t subnet, uint8_t universe, uint8_t start_code) = 0; + + // one-line artdmx sender + virtual void sendArtDmx(const String& ip, uint16_t universe15bit, const uint8_t* const data, uint16_t size) = 0; + virtual void sendArtDmx(const String& ip, uint8_t net, uint8_t subnet, uint8_t universe, const uint8_t* const data, uint16_t size) = 0; + virtual void sendArtDmx(const String& ip, uint8_t net, uint8_t subnet, uint8_t universe, uint8_t physical, const uint8_t *data, uint16_t size) = 0; + + // one-line artnzs sender + virtual void sendArtNzs(const String& ip, uint16_t universe15bit, const uint8_t* const data, uint16_t size) = 0; + virtual void sendArtNzs(const String& ip, uint8_t net, uint8_t subnet, uint8_t universe, const uint8_t* const data, uint16_t size) = 0; + virtual void sendArtNzs(const String& ip, uint8_t net, uint8_t subnet, uint8_t universe, uint8_t start_code, const uint8_t *data, uint16_t size) = 0; + + virtual void sendArtTrigger(const String& ip, uint16_t oem = 0, uint8_t key = 0, uint8_t subkey = 0, const uint8_t *payload = nullptr, uint16_t size = 512) = 0; + + virtual void sendArtSync(const String& ip) = 0; +}; + +struct ISender : virtual ISender_ +{ + virtual ~ISender() = default; + virtual void begin(uint16_t send_port = DEFAULT_PORT) = 0; +}; + +} // namespace art_net + +using ArtnetSenderInterface = art_net::ISender; + +#endif // ARTNET_SENDER_TRAITS_H diff --git a/README.md b/README.md index 3d4f7ca..223d66f 100644 --- a/README.md +++ b/README.md @@ -28,6 +28,7 @@ - ETH (ESP32) - NativeEthernet (Teensy 4.1) - Supports a lot of boards which can use Ethernet or WiFi +- Supports using multiple network interfaces in the same sketch - Multiple receiver callbacks depending on universe - Mutilple destination streaming with sender - One-line send to desired destination @@ -84,7 +85,7 @@ Where `{interface}` is one of the following depending on the interface you want | ETH (ESP32) | ArtnetETH.h | ArtnetETH | | NativeEthernet| ArtnetNativeEther.h | ArtnetNativeEther | -Also, you can use multiple interfaces in the same sketch. +You can use multiple interfaces in the same sketch if your platform supports using multiple network interfaces simultaneously without conflicts (e.g., ESP32 can use both WiFi and Ethernet at the same time, but Raspberry Pi Pico W cannot). See [examples/Multiple](examples/Multiple) for more details. ### ArtnetReceiver diff --git a/examples/Multiple/send_receive/send_receive.ino b/examples/Multiple/send_receive/send_receive.ino new file mode 100644 index 0000000..292124e --- /dev/null +++ b/examples/Multiple/send_receive/send_receive.ino @@ -0,0 +1,101 @@ +#include +#include + +// WiFi stuff +const char* ssid = "your-ssid"; +const char* pwd = "your-password"; +const IPAddress ip_wifi(192, 168, 1, 201); +const IPAddress gateway(192, 168, 1, 1); +const IPAddress subnet(255, 255, 255, 0); +// Ethernet stuff +const IPAddress ip_ether(192, 168, 0, 201); +uint8_t mac[] = {0x01, 0x23, 0x45, 0x67, 0x89, 0xAB}; + +ArtnetEther artnet_ether; +ArtnetWiFi artnet_wifi; +const String target_ip = "192.168.1.200"; +uint8_t universe = 1; // 0 - 15 + +const uint16_t size = 512; +uint8_t data[size]; +uint8_t value = 0; + +// if Artnet packet comes to this universe, this function is called +void onArtDmxUniverse(const uint8_t *data, uint16_t size, const ArtDmxMetadata& metadata, const ArtNetRemoteInfo& remote) +{ + Serial.print("lambda : artnet data from "); + Serial.print(remote.ip); + Serial.print(":"); + Serial.print(remote.port); + Serial.print(", 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 +void onArtDmx(const uint8_t *data, uint16_t size, const ArtDmxMetadata& metadata, const ArtNetRemoteInfo& remote) +{ + Serial.print("received ArtNet data from "); + Serial.print(remote.ip); + Serial.print(":"); + Serial.print(remote.port); + Serial.print(", net = "); + Serial.print(metadata.net); + Serial.print(", subnet = "); + Serial.print(metadata.subnet); + Serial.print(", universe = "); + Serial.print(metadata.universe); + Serial.print(", sequence = "); + Serial.print(metadata.sequence); + Serial.print(", size = "); + Serial.print(size); + Serial.println(")"); +}; + +void setup() { + Serial.begin(115200); + + // WiFi stuff + WiFi.begin(ssid, pwd); + WiFi.config(ip_wifi, gateway, subnet); + while (WiFi.status() != WL_CONNECTED) { + Serial.print("."); + delay(500); + } + Serial.print("WiFi connected, IP = "); + Serial.println(WiFi.localIP()); + // ArtnetWiFi + artnet_wifi.begin(); + artnet_wifi.subscribeArtDmxUniverse(universe, onArtDmxUniverse); + artnet_wifi.subscribeArtDmx(onArtDmx); + + // Ethernet stuff + Ethernet.begin(mac, ip_ether); + // ArtnetEther + artnet_ether.begin(); + artnet_ether.subscribeArtDmxUniverse(universe, onArtDmxUniverse); + artnet_ether.subscribeArtDmx(onArtDmx); +} + +void loop() { + // check if artnet packet has come and execute callback + artnet_wifi.parse(); + artnet_ether.parse(); + + value = (millis() / 4) % 256; + memset(data, value, size); + + artnet_wifi.setArtDmxData(data, size); + artnet_ether.setArtDmxData(data, size); + + // automatically send set data in 40fps + artnet_wifi.streamArtDmxTo(target_ip, universe); + artnet_ether.streamArtDmxTo(target_ip, universe); +} diff --git a/examples/Multiple/switch_interface/switch_interface.ino b/examples/Multiple/switch_interface/switch_interface.ino new file mode 100644 index 0000000..20afd48 --- /dev/null +++ b/examples/Multiple/switch_interface/switch_interface.ino @@ -0,0 +1,109 @@ +#include +#include + +// WiFi stuff +const char* ssid = "your-ssid"; +const char* pwd = "your-password"; +const IPAddress ip_wifi(192, 168, 1, 201); +const IPAddress gateway(192, 168, 1, 1); +const IPAddress subnet(255, 255, 255, 0); +// Ethernet stuff +const IPAddress ip_ether(192, 168, 0, 201); +uint8_t mac[] = {0x01, 0x23, 0x45, 0x67, 0x89, 0xAB}; + +// To switch between ArtnetWiFi and ArtnetEther at runtime, use pointer to the interface +// std::unique_ptr artnet; // if you only need sender functions +// std::unique_ptr artnet; // if you only need receiver functions +std::unique_ptr artnet; // if you need both sender and receiver functions +const String target_ip = "192.168.1.200"; +uint8_t universe = 1; // 0 - 15 + +const uint16_t size = 512; +uint8_t data[size]; +uint8_t value = 0; + +// Emulate runtime switching +bool use_wifi = true; + +// if Artnet packet comes to this universe, this function is called +void onArtDmxUniverse(const uint8_t *data, uint16_t size, const ArtDmxMetadata& metadata, const ArtNetRemoteInfo& remote) +{ + Serial.print("lambda : artnet data from "); + Serial.print(remote.ip); + Serial.print(":"); + Serial.print(remote.port); + Serial.print(", 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 +void onArtDmx(const uint8_t *data, uint16_t size, const ArtDmxMetadata& metadata, const ArtNetRemoteInfo& remote) +{ + Serial.print("received ArtNet data from "); + Serial.print(remote.ip); + Serial.print(":"); + Serial.print(remote.port); + Serial.print(", net = "); + Serial.print(metadata.net); + Serial.print(", subnet = "); + Serial.print(metadata.subnet); + Serial.print(", universe = "); + Serial.print(metadata.universe); + Serial.print(", sequence = "); + Serial.print(metadata.sequence); + Serial.print(", size = "); + Serial.print(size); + Serial.println(")"); +}; + +void setup() { + Serial.begin(115200); + + if (use_wifi) { + Serial.println("Use WiFi for ArtNet"); + // WiFi stuff + WiFi.begin(ssid, pwd); + WiFi.config(ip_wifi, gateway, subnet); + while (WiFi.status() != WL_CONNECTED) { + Serial.print("."); + delay(500); + } + Serial.print("WiFi connected, IP = "); + Serial.println(WiFi.localIP()); + // ArtnetWiFi + artnet = std::make_unique(); + artnet->begin(); + artnet->subscribeArtDmxUniverse(universe, onArtDmxUniverse); + artnet->subscribeArtDmx(onArtDmx); + } else { + Serial.println("Use Ethernet for ArtNet"); + // Ethernet stuff + Ethernet.begin(mac, ip_ether); + // ArtnetEther + artnet = std::make_unique(); + artnet->begin(); + artnet->subscribeArtDmxUniverse(universe, onArtDmxUniverse); + artnet->subscribeArtDmx(onArtDmx); + } +} + +void loop() { + // check if artnet packet has come and execute callback + artnet->parse(); + + value = (millis() / 4) % 256; + memset(data, value, size); + + artnet->setArtDmxData(data, size); + + // automatically send set data in 40fps + artnet->streamArtDmxTo(target_ip, universe); +} diff --git a/library.json b/library.json index 35200f8..8fbcf1d 100644 --- a/library.json +++ b/library.json @@ -11,12 +11,12 @@ "url": "https://github.com/hideakitai", "maintainer": true }, - "version": "0.8.3", + "version": "0.9.0", "license": "MIT", "frameworks": "*", "platforms": "*", "dependencies": { "hideakitai/ArxContainer": ">=0.6.0", - "hideakitai/ArxTypeTraits": "*" + "hideakitai/ArxTypeTraits": ">=0.3.2" } } diff --git a/library.properties b/library.properties index b3a97b3..8c3b95a 100644 --- a/library.properties +++ b/library.properties @@ -1,5 +1,5 @@ name=ArtNet -version=0.8.3 +version=0.9.0 author=hideakitai maintainer=hideakitai sentence=Art-Net Sender/Receiver for Arduino (Ethernet, WiFi) @@ -7,4 +7,4 @@ paragraph=Art-Net Sender/Receiver for Arduino (Ethernet, WiFi) category=Communication url=https://github.com/hideakitai/ArtNet architectures=* -depends=ArxContainer (>=0.6.0), ArxTypeTraits +depends=ArxContainer (>=0.6.0), ArxTypeTraits (>=0.3.2)