ArtNet icon indicating copy to clipboard operation
ArtNet copied to clipboard

Choosing between WiFi and Ethernet during runtime

Open baender opened this issue 1 year ago • 9 comments

This issue is similar to #85 but I was not able to solve it the way it was suggested.

Situation:

The user shall be able to choose between Art-Net via WiFi or Ethernet during runtime.

Initially I thought, I could pass the interface of choice to a generalized Artnet class (e.g an artnet.setInterface() method) . I realized, that this is not possible. Therefore, I tried to have one instance of each class using only one of the two.

When including ArtnetWiFi.h AND ArtnetEther.h I get a compiler error, when parsing Artnet (ethernet). That is not surprising since the library documentation explicitly says NOT to include both.

Here is my minimal example:

#include <ArtnetWiFi.h>
#include <ArtnetEther.h>

ArtnetWiFiReceiver artnetWiFi;
ArtnetReceiver artnetEthernet;

bool useWiFi;


void receiveArtNetWiFiCallback(const uint8_t *data, uint16_t size, const ArtDmxMetadata &metadata, const ArtNetRemoteInfo &remote) {
  // do nothing
}

void receiveArtNetEthernetCallback(const uint8_t *data, uint16_t size, const ArtDmxMetadata &metadata, const ArtNetRemoteInfo &remote) {
  // do nothing
}


void setup() {
  useWiFi = false;  // defined during runtime

  if (useWiFi) {
    WiFi.begin("ssid", "password");
    artnetWiFi.begin();
    artnetWiFi.subscribeArtDmxUniverse(0, receiveArtNetWiFiCallback);
  } else {
    byte mac[] = {0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xED};
    Ethernet.begin(mac);
    artnetEthernet.begin();
    artnetEthernet.subscribeArtDmxUniverse(0, receiveArtNetEthernetCallback);
  }
}


void loop() {
  if (useWiFi) {
    artnetWiFi.parse();
  } else {
    artnetEthernet.parse();  // Triggers the compiler error
  }
}

These are the compiler errors

.pio/libdeps/arduino_nano_esp32/ArtNet/Artnet/Receiver.h:378:25: error: no matching function for call to 'art_net::Receiver_<EthernetUDP>::localIP()'

.pio/libdeps/arduino_nano_esp32/ArtNet/Artnet/Receiver.h:380:9: error: no matching function for call to 'art_net::Receiver_<EthernetUDP>::macAddress(uint8_t [6])'

I wonder if there is any way I can support both interfaces on runtime.

Information to my setup: Board: Arduino Nano ESP32 ArtNet: 0.8.0 Ethernet: 2.0.2

baender avatar Dec 19 '24 11:12 baender

Making multiple interfaces available would be possible, but this is not currently planned.

Out of curiosity, in what situations would both Ethernet and WiFi interfaces be needed?

hideakitai avatar Dec 22 '24 17:12 hideakitai

The one could be the Backup of the other ohne if for example wired would fail you could use WiFi till you fix wired again

Petzl026 avatar Dec 22 '24 17:12 Petzl026

Is it difficult to switch Ethernet/WiFi in the preprocessor (e.g., #ifdef) and rewrite the program again? Do you want to make it so that it is impossible to rewrite, and if you start the program while pressing some physical button, it will switch to Ethernet/WiFi?

hideakitai avatar Dec 22 '24 18:12 hideakitai

Hi hideakitai,

I am working on an Artnet-device I want to give to a user. The user should be able to use the device without setting up an environment and programming the device. Instead they should be able to use a graphical user interface (display and rotary encoder or web interface) to configure the device.

The user might not have WiFi available or is worried about interference and would rather choose cable (Ethernet).

On the other hand, the device might need to work without cable (e.g. battery powered) and only WiFi is an option.

I want the user to be able to choose between the two Interfaces depending on their needs. But again, without the need of programming the device only via a user interface (during runtime).

Am 22. Dezember 2024 19:46:46 MEZ schrieb Hideaki Tai @.***>:

Is it difficult to switch Ethernet/WiFi in the preprocessor (e.g., #ifdef) and rewrite the program again? Do you want to make it so that it is impossible to rewrite, and if you start the program while pressing some physical button, it will switch to Ethernet/WiFi?

-- Reply to this email directly or view it on GitHub: https://github.com/hideakitai/ArtNet/issues/128#issuecomment-2558554691 You are receiving this because you authored the thread.

Message ID: @.***>

baender avatar Dec 22 '24 22:12 baender

Okay, I see several requests to use both Wi-Fi and Ethernet. I don't know when I can support this, as I am working on it in my spare time, but I will look into improving the ability to use multiple backends simultaneously. Of course, pull requests are welcome.

hideakitai avatar Dec 23 '24 02:12 hideakitai

+1 for runtime selection. For a "production" setup I'd of course always use ethernet, but it'd be convenient to be able to switch the same device to wifi for config/testing without having to go dig out NICs/switch/cables.

kilrah avatar Feb 23 '25 20:02 kilrah

Here's the way I've allowed for runtime switching between Wifi/Ethernet:

Create an ArtnetProtocol class defining pure virtual functions for interacting with artnet (ArtnetProtocol.h) Create an ArtnetWifiProtocol class inheriting from and overriding ArtnetProtocol (ArtnetWifiProtocol.h) Create an ArtnetEthernetProtocol class also inheriting from and overriding ArtnetProtocol (ArtnetEthernetProtocol.h)

  • Note that none of these header files should include the Artnet library headers! They only contain their respective class declarations!

In ArtnetWifiProtocol.cpp, include <ArtnetWiFi.h> and use it to implement ArtnetProtocolWifi In ArtnetEthernetProtocol.cpp, include<ArtnetETH.h> (or whichever flavour of ethernet is applicable) and use it to implement ArtnetProtocolEthernet

In the main application: Define an instance of ArtnetWifiProtocol for interacting w/ artnet over wifi Define an instance of ArtnetEthernetProtocol for interacting w/ artnet over ethernet Define an ArtnetProtocol* for interacting w/ whichever implementation you choose at runtime (if ArtnetProtocol* == &ArtnetWifiProtocol use Wifi, if ArtnetProtocol* == &ArtnetEthernetProtocol use Ethernet)

Huzzah!

thirstyice avatar Jun 22 '25 23:06 thirstyice

Neat, maybe PR that?

kilrah avatar Jun 23 '25 17:06 kilrah

I feel like adding an interface on top of the existing library functions probably isn't the best solution for inclusion in the library itself as it's basically an entirely separate entity, but it may be possible to re-write the library to use a similar structure. I'm not sure how much that would affect the current API though. I'll look into it when I have time.

thirstyice avatar Jun 25 '25 00:06 thirstyice

Okay, so I did some research and here's what I've got:

With the way the library works currently there are a couple of hurdles to implementing the kind of runtime switching I described previously.

Backstory

First, some backstory about the way this library works currently:

  • You can define multiple Artnet objects for a given interface (this is in contrast to, for example, Arduino's Serial, where the library defines the Serial object and then the programmer acts on it, rather than the programmer creating an object of class Serial to then interact with)
  • Each Artnet instance contains an associated UDP member, which is unique to that object and specific to the applicable interface (WiFi, Ethernet, etc)

Challenges

So, here are the challenges (I'll be referring to WiFi and ETH for the interfaces, but the concepts are transferable to basically any combination of interfaces):

  • In order for the runtime switching to work, the UDP member for WiFi and the UDP member for ETH need to not be able to "see" each other, essentially meaning that all references to UDP need to be contained in a seperate .cpp file for the respective interface (either ArtnetWiFi.cpp or ArtnetETH.cpp). So far so good.
  • C++ requires that all members of classes, even private ones, are listed in the class declaration (the part that goes in the .h file). Uh oh.
  • This is where we get problems:
    1. We need a UDP member for each interface so that each Artnet instance still has its own UDP.
    2. This member has to be declared in the .h file, with the class.
    3. But declaring them in the .h file makes them conflict if we try to use multiple interfaces at once. Oh no!

Some options

Thankfully, there are a few ways around this:

  1. We could do like Serial does and have a unique, static object for each interface. (that way the UDP object can be statically defined in the .cpp file, instead of being a member of the Artnet class) (this is essentially what I did previously)
  2. Multiple Artnet objects, but only one UDP per interface. Multiple instances of Artnet on the same interface will have to share. (this is the same as the above , just applied at a different level of the inheritance tree)
  3. Use new to dynamically allocate a UDP object for each Artnet instance at runtime
  4. Some shenanigans with preprocessor macros. Basically would use a combination of macros and templates so that the syntax for creating an Artnet object would become the syntax for:
    1. Creating a new UDP object for the relevant interface with a name based on the line number on which it it created.
    2. Creating a new Artnet object (with no UDP member)
    3. Associating the UDP object with the Artnet object

Pros + Cons

Here's my estimate of some of the pros + cons of each option:

1. Do like Serial

Pros

  • Familiar concept to Arduino users
  • Simple to use, implement, and maintain
  • Functionality matches the majority of use cases

Cons

  • Would require changes to existing programs
  • Difficult to have Artnet on multiple ports on the same interface

2. Shared UDP per interface

Pros

  • Probably could be implemented w/ minimal changes to existing code
  • Not too bad to implement + maintain

Cons

  • Difficult to have Artnet on multiple ports on the same interface, and that wouldn't be obvious without delving into the code
  • (At least w/ option 1 the inability to have multiple ports is fairly intuitive)
  • Feels kind of pointless to have multiple Artnet objects when they all share the same UDP on the backend
  • May have conflicts w/ one UDP being shared by multiple Artnets. More research would be required, and even then there may be edge cases

3. new

Pros

  • No changes to existing code. API remains identical, functionality remains identical (other than the addition of runtime interface selection ofc)

Cons

  • Manual memory management - eww?
  • Gotta keep track of object creation and destruction (on the backend) to avoid memory leaks
  • Sometimes using new feels like... philosophically wrong (even when it's actually the right solution)

4. Preprocessor shenanigans

Pros

  • If it works, no change to existing code. API remains identical, functionality remains identical. Syntax highlighting would be different though

Cons

  • If it works
  • I feel like the fact that this option has "shenanigans" in the title is self explanatory
  • Preprocessor macros can be really hard to debug / maintain
  • Super opaque in terms of what's actually going on under the hood
  • The more I think about it the more I think this wouldn't even work

Conclusion

So yeah, that's my assessment. I'm very tired (writing this before bed), so I hope I was at least somewhat coherent.

@hideakitai let me know how (if) you'd like me to proceed. Personally I'd probably go for either option 1: Make like Serial (if keeping the library code as simple as possible is preferred) or option 3: Use new (if minimal changes to existing user's code is preferred).

Let me know what you think!

thirstyice avatar Sep 16 '25 06:09 thirstyice

@thirstyice @baender @kilrah Hi, I'm sorry for not getting back to you sooner. There were multiple technical issues to support this feature; I've implemented them. I used dynamic dispatch to switch the network interfaces (WiFi or Ethernet). Please take a look at the PRs above for more details.

An example of switching network interfaces can be found here. Please try it, and if you have any problems, please reopen this issue and let me know.

hideakitai avatar Oct 16 '25 18:10 hideakitai

Awesome! @hideakitai Giving it a try, it's not happy with redefinitions when including both ArtnetWiFi.h and ArtnetETH.h:

Image

kilrah avatar Oct 17 '25 12:10 kilrah

Ah, sorry, I’ve already fixed the rebase mistake. Please try the latest main branch. https://github.com/hideakitai/ArtNet

hideakitai avatar Oct 17 '25 12:10 hideakitai

Ah, that's a different issue. ESP32, right? How about using Ethernet instead of ETH?

hideakitai avatar Oct 17 '25 13:10 hideakitai

ESP32C3, no Ethernet available, only ETH on this platform, and yeah I'm on the latest commit

kilrah avatar Oct 17 '25 13:10 kilrah

OK, thank you. I will fix it later

hideakitai avatar Oct 17 '25 13:10 hideakitai

@kilrah Fixed. Please try the latest main branch

hideakitai avatar Oct 17 '25 14:10 hideakitai

Now builds but I get a link error:

Image

kilrah avatar Oct 17 '25 15:10 kilrah

Hmm

  • Can you build examples/Multiple/send_receive_eth/send_receive_eth.ino?
  • Have you cleaned the build outputs before re-compiling the project?

hideakitai avatar Oct 17 '25 15:10 hideakitai

I attempted to build send_receive_eth.ino on the ESP32-C3 using PlatformIO. It seems to link successfully, but I wonder why.

$ pio ci --board esp32-c3-devkitm-1 --project-option="framework=arduino" --project-option="platform=https://github.com/pioarduino/platform-espressif32/releases/download/53.03.11/platform-espressif32.zip" --project-option="lib_deps=https://github.com/hideakitai/ArtNet.git"  send_receive_eth.ino
...
Archiving .pio/build/esp32-c3-devkitm-1/libFrameworkArduino.a
Indexing .pio/build/esp32-c3-devkitm-1/libFrameworkArduino.a
Linking .pio/build/esp32-c3-devkitm-1/firmware.elf
Retrieving maximum program size .pio/build/esp32-c3-devkitm-1/firmware.elf
Checking size .pio/build/esp32-c3-devkitm-1/firmware.elf
Advanced Memory Usage is available via "PlatformIO Home > Project Inspect"
RAM:   [=         ]  12.3% (used 40148 bytes from 327680 bytes)
Flash: [=======   ]  73.1% (used 958110 bytes from 1310720 bytes)
Building .pio/build/esp32-c3-devkitm-1/firmware.bin
esptool.py v4.8.5
Creating esp32c3 image...
Merged 2 ELF sections
Successfully created esp32c3 image.
===================================== [SUCCESS] Took 10.93 seconds =====================================

hideakitai avatar Oct 17 '25 16:10 hideakitai

Sorry, found the issue, I had #include <ArtnetETH.h> getting into another source file than the main through a shared header, removing that builds fine.

kilrah avatar Oct 17 '25 17:10 kilrah