Choosing between WiFi and Ethernet during runtime
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
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?
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
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?
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: @.***>
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.
+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.
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!
Neat, maybe PR that?
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.
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
Artnetobjects for a given interface (this is in contrast to, for example, Arduino'sSerial, where the library defines theSerialobject and then the programmer acts on it, rather than the programmer creating an object of classSerialto then interact with) - Each
Artnetinstance contains an associatedUDPmember, 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
UDPmember for WiFi and theUDPmember for ETH need to not be able to "see" each other, essentially meaning that all references toUDPneed to be contained in a seperate.cppfile for the respective interface (eitherArtnetWiFi.cpporArtnetETH.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
.hfile). Uh oh. - This is where we get problems:
- We need a
UDPmember for each interface so that eachArtnetinstance still has its ownUDP. - This member has to be declared in the
.hfile, with the class. - But declaring them in the
.hfile makes them conflict if we try to use multiple interfaces at once. Oh no!
- We need a
Some options
Thankfully, there are a few ways around this:
- We could do like
Serialdoes and have a unique, static object for each interface. (that way theUDPobject can be statically defined in the.cppfile, instead of being a member of theArtnetclass) (this is essentially what I did previously) - Multiple
Artnetobjects, but only oneUDPper interface. Multiple instances ofArtneton the same interface will have to share. (this is the same as the above , just applied at a different level of the inheritance tree) - Use
newto dynamically allocate aUDPobject for eachArtnetinstance at runtime - Some shenanigans with preprocessor macros. Basically would use a combination of macros and templates so that the syntax for creating an
Artnetobject would become the syntax for:- Creating a new UDP object for the relevant interface with a name based on the line number on which it it created.
- Creating a new
Artnetobject (with noUDPmember) - Associating the
UDPobject with theArtnetobject
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
Artnetobjects when they all share the sameUDPon the backend - May have conflicts w/ one
UDPbeing shared by multipleArtnets. 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
newfeels 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 @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.
Awesome! @hideakitai Giving it a try, it's not happy with redefinitions when including both ArtnetWiFi.h and ArtnetETH.h:
Ah, sorry, I’ve already fixed the rebase mistake. Please try the latest main branch. https://github.com/hideakitai/ArtNet
Ah, that's a different issue. ESP32, right? How about using Ethernet instead of ETH?
ESP32C3, no Ethernet available, only ETH on this platform, and yeah I'm on the latest commit
OK, thank you. I will fix it later
@kilrah Fixed. Please try the latest main branch
Now builds but I get a link error:
Hmm
- Can you build examples/Multiple/send_receive_eth/send_receive_eth.ino?
- Have you cleaned the build outputs before re-compiling the project?
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 =====================================
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.