Forum Rule: Always post complete source code & details to reproduce any issue!
Page 1 of 3 1 2 3 LastLast
Results 1 to 25 of 72

Thread: New lwIP-based Ethernet library for Teensy 4.1

  1. #1
    Senior Member
    Join Date
    Mar 2017
    Location
    Oakland, CA, USA
    Posts
    316

    New lwIP-based Ethernet library for Teensy 4.1

    Hello, all. I went ahead and made an lwIP-based Ethernet library for the Teensy 4.1. Some details about it:

    • This currently is not a complete drop-in replacement for the Arduino-style Ethernet API. (But it’s reasonably close.) The README details the differences. I haven't yet decided the direction I want to take, because I don't love the global variable-style approach, plus there are some things that are underspecified. This may end up being something completely different, or it may be similar; I haven't decided yet. Heck, maybe I look at how mbed does it...
    • The polling function is set up internally via EventResponder to run inside yield(); no timers are used.
    • I haven't tested it with the Arduino IDE; it probably won't work. You need to use PlatformIO for now.
    • Use QNEthernet.h as the main include file.
    • I use namespaces.
    • lwIP version is 2.1.2.
    • main.cpp is a hodge-podge testing playground from which you can glean information about how to use the API. It's very similar to the Arduino API. You'll note mDNS and an OSC parser, useful with the TouchOSC app — try it. Also note that you don't actually need to include any of the lwip headers; I'm just using them for testing inside some callbacks.


    My current to do list:

    • Tune lwIP. I could use some help with this. (@manitou, I know you've already done some tuning; I point to this in the README.)
    • See if there's a way to improve DHCP response time. In my testing, it usually takes about 10-12 seconds to get an IP address.
    • Decide how to restructure the internals, either for something different or to make it even more compatible with the Arduino API. For example, right now, EthernetClient is not copyable, only movable, having to do with how I made the insides mostly client-managed instead of server-managed.
    • I haven't settled on a name.


    I got this to a point where all my basic testing works. This project is still very much a work in progress, though, aren't they all?

    Last point for now: Thank you CrashReporter and everyone who was involved with making that work!

    Here's the link: https://github.com/ssilverman/QNEthernet
    Last edited by shawn; 08-29-2021 at 02:44 AM.

  2. #2
    Senior Member
    Join Date
    Mar 2017
    Location
    Oakland, CA, USA
    Posts
    316
    Version 0.3.0 (tagged as "v0.3.0" in the repo) has:

    • Revamped and centralized connection management.
    • Works as an Arduino library.
    • Bug and kink fixes.
    • Update to "it's really v2.1.2 this time" lwIP.


    Link: https://github.com/ssilverman/QNEthe...ses/tag/v0.3.0

    I'm working on a "How To", but here's a few things you can do to adapt your code:

    1. Change #include <Ethernet.h> to #include <QNEthernet.h>. Note that this include already includes the header for EthernetUDP, so you can remove any #include <EthernetUdp.h>.
    2. Just below that, add: using namespace qindesign::network;
    3. You likely don't want or need to set/choose your own MAC address, so just call Ethernet.begin() with no arguments. This version uses DHCP. The three-argument version (IP, subnet mask, gateway) sets those parameters instead of using DHCP. If you really want to set your own MAC address, for now, consult the code.
    4. It may take 10-15 seconds to get a DHCP address (or whatever it is), so wait for a little bit until Ethernet.localIP() isn't INADDR_NONE. You could use an elapsedMillis with delays of, say, 10ms, until there's an address or the elapsed time reaches a maximum (eg. 15000ms).
    5. Ethernet.hardwareStatus() always returns zero and Ethernet.linkStatus() returns a bool (i.e. not that EthernetLinkStatus enum).
    6. Most other things should be the same.
    Last edited by shawn; 08-31-2021 at 06:43 AM.

  3. #3
    Senior Member
    Join Date
    Apr 2014
    Location
    Germany
    Posts
    9,345
    This is great.

  4. #4
    Senior Member
    Join Date
    Mar 2017
    Location
    Oakland, CA, USA
    Posts
    316
    I just released v0.4.0. The changes:

    • Updated to lwIP v2.1.3-rc1.
    • Moved the global objects (Ethernet and MDNS) into the same namespace as everything else. This means the "using namespace" approach is probably the easiest option.
    • Fixed UDP multicast: it wasn't joining the IGMP group or checking the address correctly (byte ordering issue).
    • Can now add TXT record items to mDNS services.
    • New Ethernet::waitForLocalIP(timeout) function to make it easier to wait for DHCP-assigned addresses.
    • Added the ability to re-announce mDNS services. I'm not certain if it's supposed happen, but the services disappear after around the TTL duration with no refresh. I don't know if this is my fault for not understanding something, or a limitation in the stack.
    • Updated the README with new instructions and notes.


    https://github.com/ssilverman/QNEthe...ses/tag/v0.4.0

  5. #5
    Member
    Join Date
    Jan 2020
    Location
    Toronto, Canada
    Posts
    72
    This is very good. I've modified and tested with several examples, and they are working smoothly with DHCP. Static IP still not working yet, will fix later.

    I've created a PR for the library to add several examples, so that it's much easier for anyone to start using the library.

    Add example for QNEthernet #1

  6. #6
    Senior Member
    Join Date
    Mar 2017
    Location
    Oakland, CA, USA
    Posts
    316
    Thank you for these examples. I'll see what's up with static IP...

    Update: I just tested static IP and it seems to work fine. But now I'm just realizing that you may have meant the examples and not necessarily the main code...
    Last edited by shawn; 09-03-2021 at 05:37 AM.

  7. #7
    Quote Originally Posted by shawn View Post
    I just released v0.4.0. The changes:
    shawn, thanks very much for this work. I have a question about loopServer(). It contains the clause shown below (printf was my addition). When the program starts, all elements of clients[] are 0, so this loop prints "Client X stop" for all 8 clients, on every pass. What is this code meant to do? If (!clients[i]) is true, i.e (clients[i]==0), what does it mean to call clients[i].stop()? Seems like something is missing.

    Code:
      // stop any clients which disconnect
      for (int i = 0; i < 8; i++) {
        if (!clients[i]) {
          clients[i].stop();
          Serial.printf("Client %d stop()\n", i);
        }
      }
    EDIT: I'm using the FNET utility "fbench.exe" to connect and send packets to the T41. I assume this application is disconnecting after sending the specified number of packets, but the server application in the T41 never reports a disconnect. The result is that I can connect/send until all of the clients[] have been used, and then the server application will no longer accept connections.
    Last edited by joepasquariello; 09-03-2021 at 05:13 PM.

  8. #8
    Senior Member
    Join Date
    Mar 2017
    Location
    Oakland, CA, USA
    Posts
    316
    Hi, Joe. That test code in test/main.cpp was adapted from all the Arduino test code here: https://www.arduino.cc/en/Reference/Ethernet

    I just wanted to make sure the library worked as expected with existing code, merely as a starting point. I'm not the fondest of how the example code is structured (or most Arduino-style code, for that matter) and it is not how I'd write my own network programs or examples. You can consider this code obsolete and not a good example. Maybe I should remove it if it's causing confusion.

    At some point in the near future, I'll be adding better examples.

  9. #9
    Senior Member
    Join Date
    Mar 2017
    Location
    Oakland, CA, USA
    Posts
    316
    Joe, I'll add another point: The code feels wrong because why would you stop a client whose Boolean value returns false? In fact, a client returning a Boolean isn't specified very well and is actually contradictory in the examples from that Arduino link above (https://www.arduino.cc/en/Reference/Ethernet). I've chosen to return `true` if connected and `false` otherwise; I'm not sure how other libraries do it, or how it's even supposed to be done, other than the fact that it exists in the Arduino API.

    In summary, I don't like that code and it really should be `if (clients[i])` and not `if (!clients[i])`, per how my library works.

  10. #10
    Senior Member
    Join Date
    Mar 2017
    Location
    Oakland, CA, USA
    Posts
    316
    Joe, one more thing: The original example actually has `clients[i] && !clients[i].connected()`, and I think I was experimenting, but that code is confusing in any case. I think they may have meant "active and no data pending". (Same for this line: `while (clients[i] && clients[i].available() > 0)`)

    In any case, don't use the code in `test/main.cpp`.

  11. #11
    Quote Originally Posted by shawn View Post
    At some point in the near future, I'll be adding better examples.
    Thanks, Shawn. I started with the TCP server example because that's one of my use cases. I'm pretty much an Ethernet beginner, but I'll try to figure out (google?) how lwip detects and handles a TCP client disconnect.

  12. #12
    Senior Member
    Join Date
    Mar 2017
    Location
    Oakland, CA, USA
    Posts
    316
    Joe, can you tell me more about your use case? Different uses demand different styles of API usage and state maintenance. For example, an HTTP server could be done by having itself manage all the connections (eg. accept()-style), but if you choose to use that "server.available()" approach, the HTTP server would instead need to manage state a certain way.

    [Note: I updated test/main.cpp with some notes and a slightly changed loopServer().]

    For client disconnects, lwIP sends an OK and a null pbuf to the receiver callback (I’m using callbacks internally; there’s an option to not use them). QNEthernet handles this case and disconnects the client state, but also stores remaining data in the case of errors.

  13. #13
    Quote Originally Posted by shawn View Post
    Joe, can you tell me more about your use case?
    My use case is a simple command/control interface via TCP. The device (T41) would listen/accept connections, then read, process, and reply to commands and data requests from those connections. The only thing missing from the loopServer example is to detect and clean up when a client disconnects, so that connection "slot" is available for another client.

  14. #14
    Senior Member
    Join Date
    Mar 2017
    Location
    Oakland, CA, USA
    Posts
    316
    Are you always expecting a fixed number of bytes or an arbitrary number of bytes (eg. controlled by, say, a “length” field or two)?

  15. #15
    Senior Member
    Join Date
    Mar 2017
    Location
    Oakland, CA, USA
    Posts
    316
    One of the things I don’t like about the “server.available()” API is that it’s not complete. There’s no defined way to map the client object you receive to state being managed by your program. The object itself may be a copy, or it may be “moved” (in the C++ sense).

    Same goes with storing received client objects from “server.accept()”. There’s copies vs. references vs. “moves” to worry about, and there’s a few gotchas in C++ if these aren’t handled properly.

    Maybe I’ll add something… EthernetClient::id() perhaps?

  16. #16
    The packet content and number of fields is arbitrary. Right now, I'm not trying to parse packets or build a real application. I'm just trying to test the capability to accept connections from clients, echo packets back to the client(s) for as long as they are active, and detect when they disconnect. Is it possible for the server to detect when a client disconnects, or is a timeout on receive the best one can do? I have to say I'm very confused about the Arduino approach to Ethernet, as opposed to directly using a socket API.

  17. #17
    Senior Member
    Join Date
    Mar 2017
    Location
    Oakland, CA, USA
    Posts
    316
    Here's a survey of how connections (aka "EthernetClient") work (at least with QNEthernet):

    1. `connected()`: Returns whether connected OR data is still available (or both)
    2. `operator bool`: Returns whether connected (at least in QNEthernet)
    3. `available()`: Returns the amount data is available, whether the connection is closed or not
    4. `read`: Reads data if there's data available, whether the connection's closed or not


    Connections will be closed automatically if the client shuts down a connection, and QNEthernet will properly handle the state such that the API behaves as expected. In addition, if a client closes a connection, any buffered data will still be available via the client API. If it were up to me, I'd have swapped the meaning of "operator bool" and "connected()", but see the above list as a guide.

    Some options:

    1. Keep checking `connected()` (or "operator bool") and `available()`/`read` to keep reading data. The data will run out when the connection is closed and after all the buffers are empty. The calls to `connected()` (or "operator bool") will indicate connection status (plus data available in the case of `connected()` or just connection state in the case of "operator bool").
    2. Same as the above, but without one of the two connection-status calls (`connected()` or "operator bool"). The data will just run out after connection-closed and after the buffers are empty.


    Does this help clarify things?
    Last edited by shawn; 09-03-2021 at 08:31 PM.

  18. #18
    Yes, that is helpful. I didn't realize that "clients[i]" and "!clients[i]" were uses of "operator bool", as opposed to testing for value 0.

    EDIT: I'm finding that the example program does mostly what I expected if I simply replace read() with read(buf,size) and replace Serial.write() with Serial.printf(nbytes read). When I do that, I get reasonable behavior with respect to elements of clients[] being "re-used".

    Thanks for all your help.
    Last edited by joepasquariello; 09-03-2021 at 09:24 PM.

  19. #19
    Senior Member
    Join Date
    Mar 2017
    Location
    Oakland, CA, USA
    Posts
    316
    Here's a quickie example of how a server could process a continuous stream of fixed-width messages from multiple clients:

    Code:
    // SPDX-FileCopyrightText: (c) 2021 Shawn Silverman <shawn@pobox.com>
    // SPDX-License-Identifier: MIT
    
    // FixedWidthServer demonstrates how to serve a protocol having a continuous
    // stream of fixed-size messages from multiple clients.
    // This file is part of the QNEthernet library.
    
    // C++ includes
    #include <algorithm>
    #include <utility>
    #include <vector>
    
    #include <QNEthernet.h>
    
    using namespace qindesign::network;
    
    constexpr uint32_t kDHCPTimeout = 10000;  // 10 seconds
    constexpr uint16_t kServerPort = 5000;
    constexpr int kMessageSize = 10;  // Pretend the protocol specifies 10 bytes
    
    // Keeps track of state for a single client.
    struct ClientState {
      ClientState(EthernetClient client)
          : client(std::move(client)) {}
    
      EthernetClient client;
      int bufSize = 0;  // Keeps track of how many bytes have been read
      uint8_t buf[kMessageSize];
      bool closed = false;
    };
    
    // Keeps track of what and where belong to whom.
    std::vector<ClientState> clients;
    
    // The server.
    EthernetServer server{kServerPort};
    
    void setup() {
      Serial.begin(115200);
      while (!Serial && millis() < 4000) {
        // Wait for Serial to initialize
      }
      Serial.println("Starting...");
    
      uint8_t mac[6];
      Ethernet.macAddress(mac);
      Serial.printf("MAC = %02x:%02x:%02x:%02x:%02x:%02x\n",
                    mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]);
    
      Serial.println("Starting Ethernet with DHCP...");
      Ethernet.begin();
      Ethernet.waitForLocalIP(kDHCPTimeout);
      if (Ethernet.localIP() == INADDR_NONE) {
        Serial.println("Failed to get IP address from DHCP");
      } else {
        IPAddress ip = Ethernet.localIP();
        Serial.printf("    Local IP    = %u.%u.%u.%u\n",
                      ip[0], ip[1], ip[2], ip[3]);
        ip = Ethernet.subnetMask();
        Serial.printf("    Subnet mask = %u.%u.%u.%u\n",
                      ip[0], ip[1], ip[2], ip[3]);
        ip = Ethernet.gatewayIP();
        Serial.printf("    Gateway     = %u.%u.%u.%u\n",
                      ip[0], ip[1], ip[2], ip[3]);
        ip = Ethernet.dnsServerIP();
        Serial.printf("    DNS         = %u.%u.%u.%u\n",
                      ip[0], ip[1], ip[2], ip[3]);
      }
    
      // Start the server
      Serial.printf("Listening for clients on port %u...\n", kServerPort);
      server.begin();
    }
    
    // Process one message. This implementation simply prints to Serial.
    //
    // We could pass just the buffer, but we're passing the whole state here so
    // you know which client it's from.
    void processMessage(ClientState &state) {
      Serial.print("Message: ");
      Serial.write(state.buf, kMessageSize);
      Serial.println();
    }
    
    void loop() {
      EthernetClient client = server.accept();
      if (client) {
        Serial.print("Client connected: ");
        Serial.println(client.remoteIP());
        clients.emplace_back(std::move(client));
        Serial.printf("Client count: %u\n", clients.size());
      }
    
      // Process data from each client
      for (ClientState &state : clients) {  // Use a reference
        if (!state.client.connected()) {
          state.closed = true;
          continue;
        }
    
        int avail = state.client.available();
        if (avail > 0) {
          int toRead = std::min(kMessageSize - state.bufSize, avail);
          state.bufSize += state.client.read(&state.buf[state.bufSize], toRead);
          if (state.bufSize >= kMessageSize) {
            processMessage(state);
            state.bufSize = 0;
          }
        }
      }
    
      // Clean up all the closed clients
      size_t size = clients.size();
      clients.erase(std::remove_if(clients.begin(), clients.end(),
                                   [](const auto &state) { return state.closed; }),
                    clients.end());
      if (clients.size() != size) {
        Serial.printf("Client count: %u\n", clients.size());
      }
    }
    I might write another example that does arbitrary size, but for now, you could extrapolate from here.

  20. #20
    Thanks, Shawn. I can see that you're using an STL vector, adding clients on connect and removing on disconnect, so you only iterate over clients that are connected. I'll do something like that.

  21. #21
    Member
    Join Date
    Jan 2020
    Location
    Toronto, Canada
    Posts
    72
    Quote Originally Posted by shawn View Post
    Thank you for these examples. I'll see what's up with static IP...

    Update: I just tested static IP and it seems to work fine. But now I'm just realizing that you may have meant the examples and not necessarily the main code...
    The static IP issue in the examples has been fixed.

    The call to Ethernet.setDNSServerIP(mydnsServer) must be placed after Ethernet.begin(myIP, myNetmask, myGW) for the DNS server IP to be valid. Otherwise, EthernetClient::connect() will always return false

  22. #22
    Member
    Join Date
    Jan 2020
    Location
    Toronto, Canada
    Posts
    72
    EthernetWebServer Library now support QNEthernet on Teensy 4.1.

    A new release will be published in this weekend

    Click image for larger version. 

Name:	Selection_023.png 
Views:	16 
Size:	25.3 KB 
ID:	25756

  23. #23
    Member
    Join Date
    Jan 2020
    Location
    Toronto, Canada
    Posts
    72
    EthernetWebServer v1.6.0 has been released to support this QNEthernet library.

    The following is debug terminal output when running example MQTTClient_Auth on Teensy 4.1 using QNEthernet Library

    Code:
    Start MQTTClient_Auth on TEENSY 4.1 using QNEthernet
    EthernetWebServer v1.6.0
    [EWS] =========== USE_QN_ETHERNET ===========
    Initialize Ethernet using static IP => IP Address = 192.168.2.222
    Attempting MQTT connection to broker.emqx.io...connected
    Message Send : MQTT_Pub => Hello from MQTTClient_Auth on TEENSY 4.1 using QNEthernet
    Message arrived [MQTT_Pub] Hello from MQTTClient_Auth on TEENSY 4.1 using QNEthernet
    Message Send : MQTT_Pub => Hello from MQTTClient_Auth on TEENSY 4.1 using QNEthernet
    Message arrived [MQTT_Pub] Hello from MQTTClient_Auth on TEENSY 4.1 using QNEthernet

  24. #24
    Member
    Join Date
    Jan 2020
    Location
    Toronto, Canada
    Posts
    72
    The QNEthernet, using lwip, is really a great library, and much better than the other alternative. Adding its support to other libraries will be very easy and smooth.


    EthernetWebServer_SSL Library now supports QNEthernet on Teensy 4.1



    Click image for larger version. 

Name:	AdvancedWebServer_QNEthernet.png 
Views:	19 
Size:	26.1 KB 
ID:	25757

    The following is debug terminal output when running example MQTTClient_SSL on Teensy 4.1 using QNEthernet Library

    Code:
    Starting MQTTClient_SSL on TEENSY 4.1 using QNEthernet
    EthernetWebServer_SSL v1.6.0
    [ETHERNET_WEBSERVER] =========== USE_QN_ETHERNET ===========
    Initialize Ethernet using static IP => IP Address = 192.168.2.222
    Attempting MQTTS connection to broker.emqx.io...connected
    Message Send : MQTT_Pub => Hello from MQTTClient_SSL on TEENSY 4.1
    Message arrived [MQTT_Pub] Hello from MQTTClient_SSL on TEENSY 4.1
    Message Send : MQTT_Pub => Hello from MQTTClient_SSL on TEENSY 4.1
    Message arrived [MQTT_Pub] Hello from MQTTClient_SSL on TEENSY 4.1
    Message Send : MQTT_Pub => Hello from MQTTClient_SSL on TEENSY 4.1
    Message arrived [MQTT_Pub] Hello from MQTTClient_SSL on TEENSY 4.1

  25. #25
    Senior Member
    Join Date
    Mar 2017
    Location
    Oakland, CA, USA
    Posts
    316
    Here's a quickie example I'm calling "LengthWidthServer" that shows how a server can process a continuous stream of messages from multiple clients, where each message has a length byte:

    Code:
    // SPDX-FileCopyrightText: (c) 2021 Shawn Silverman <shawn@pobox.com>
    // SPDX-License-Identifier: MIT
    
    // LengthWidthServer demonstrates how to serve a protocol having a continuous
    // stream of messages from multiple clients, where each message starts with a
    // one-byte length field. This is similar to the FixedWidthServer example, but
    // the data stream indicates the size of each message. It shows a simple
    // version of how to use states when parsing, when the position in the client
    // stream is arbitrary.
    // 
    // This file is part of the QNEthernet library.
    
    // C++ includes
    #include <algorithm>
    #include <utility>
    #include <vector>
    
    #include <QNEthernet.h>
    
    using namespace qindesign::network;
    
    constexpr uint32_t kDHCPTimeout = 10000;  // 10 seconds
    constexpr uint16_t kServerPort = 5000;
    
    // Where are we with message parsing?
    enum class MessageParseState {
      kStart,  // Starting state
      kValue,  // Reading the value
    };
    
    // Keeps track of state for a single client.
    struct ClientState {
      ClientState(EthernetClient client)
          : client(std::move(client)) {
        reset();
      }
    
      EthernetClient client;
      bool closed = false;
    
      MessageParseState parseState;
      int messageSize;   // The current message size
      int bufSize;       // Keeps track of how many bytes have been read
      uint8_t buf[255];  // Do the easy thing and allocate the maximum possible
    
      // Reset the client state.
      void reset() {
        messageSize = 0;
        bufSize = 0;
        parseState = MessageParseState::kStart;
      }
    };
    
    // Keeps track of what and where belong to whom.
    std::vector<ClientState> clients;
    
    // The server.
    EthernetServer server{kServerPort};
    
    void setup() {
      Serial.begin(115200);
      while (!Serial && millis() < 4000) {
        // Wait for Serial to initialize
      }
      Serial.println("Starting...");
    
      uint8_t mac[6];
      Ethernet.macAddress(mac);
      Serial.printf("MAC = %02x:%02x:%02x:%02x:%02x:%02x\n",
                    mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]);
    
      Serial.println("Starting Ethernet with DHCP...");
      Ethernet.begin();
      Ethernet.waitForLocalIP(kDHCPTimeout);
      if (Ethernet.localIP() == INADDR_NONE) {
        Serial.println("Failed to get IP address from DHCP");
      } else {
        IPAddress ip = Ethernet.localIP();
        Serial.printf("    Local IP    = %u.%u.%u.%u\n",
                      ip[0], ip[1], ip[2], ip[3]);
        ip = Ethernet.subnetMask();
        Serial.printf("    Subnet mask = %u.%u.%u.%u\n",
                      ip[0], ip[1], ip[2], ip[3]);
        ip = Ethernet.gatewayIP();
        Serial.printf("    Gateway     = %u.%u.%u.%u\n",
                      ip[0], ip[1], ip[2], ip[3]);
        ip = Ethernet.dnsServerIP();
        Serial.printf("    DNS         = %u.%u.%u.%u\n",
                      ip[0], ip[1], ip[2], ip[3]);
      }
    
      // Start the server
      Serial.printf("Listening for clients on port %u...\n", kServerPort);
      server.begin();
    }
    
    // Process one message. This implementation simply prints to Serial, escaping
    // some characters.
    //
    // We could pass just the buffer, but we're passing the whole state here so
    // we know which client it's from.
    void processMessage(const ClientState &state) {
      Serial.printf("Message [%d]: ", state.messageSize);
      for (int i = 0; i < state.messageSize; i++) {
        uint8_t b = state.buf[i];
        if (b < 0x20) {
          switch (b) {
            case '\a': Serial.print("\\q"); break;
            case '\b': Serial.print("\\b"); break;
            case '\t': Serial.print("\\t"); break;
            case '\n': Serial.print("\\n"); break;
            case '\v': Serial.print("\\v"); break;
            case '\f': Serial.print("\\f"); break;
            case '\r': Serial.print("\\r"); break;
            case '\\': Serial.print("\\\\"); break;
            default:
              Serial.printf("\\x%x%x", (b >> 4) & 0x0f, b & 0x0f);
          }
        } else if (0x7f <= b && b < 0xa0) {
          Serial.printf("\\x%x%x", (b >> 4) & 0x0f, b & 0x0f);      
        } else {
          Serial.write(b);
        }
      }
      Serial.println();
    }
    
    void loop() {
      EthernetClient client = server.accept();
      if (client) {
        Serial.print("Client connected: ");
        Serial.println(client.remoteIP());
        clients.emplace_back(std::move(client));
        Serial.printf("Client count: %u\n", clients.size());
      }
    
      // Process data from each client
      for (ClientState &state : clients) {  // Use a reference
        if (!state.client.connected()) {
          state.closed = true;
          continue;
        }
    
        int avail = state.client.available();
        while (avail > 0) {
          switch (state.parseState) {
            case MessageParseState::kStart:
              state.messageSize = state.client.read();
              avail--;
              state.parseState = MessageParseState::kValue;
              break;
    
            case MessageParseState::kValue: {
              int read = std::min(state.messageSize - state.bufSize, avail);
              read = state.client.read(&state.buf[state.bufSize], read);
              state.bufSize += read;
              avail -= read;
              if (state.bufSize >= state.messageSize) {
                processMessage(state);
                state.messageSize = 0;
                state.bufSize = 0;
                state.parseState = MessageParseState::kStart;
              }
              break;
            }
    
            default:
              break;  // Shouldn't happen because we put in all the states
          }
        }
      }
    
      // Clean up all the closed clients
      size_t size = clients.size();
      clients.erase(std::remove_if(clients.begin(), clients.end(),
                                   [](const auto &state) { return state.closed; }),
                    clients.end());
      if (clients.size() != size) {
        Serial.printf("Client count: %u\n", clients.size());
      }
    }

Posting Permissions

  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts
  •