Other Controller get disconnected when sending any command to the Eth. Gateway (v2.0)
-
Yep, comments are often useful when we are not that lazy to include them :-D
BTW I've found a topic telling about modifying the Eth library to get the Source IP, and being then able to support multiple clients. I don't know if it really helps.
Also found this where the guy says that W5100 supports 4 sockets (so max 4 clients) but the Eth lib can NOT distinguish them if they are on the same port (might be a a workaround, to open 4 port instead of one, ie 5003,5004,5005,5006). He's finally submitted a patch against the Eth lib to allow 4 different sockets on the same port.... But the more interesting seems to be a comment by "Gene" who has posted complete sketch showing how to connect multiple telnet clients . Here is his code:
/* exampleMultiTelnetServer.ino */ /* * Created: May 21, 2015 * * Created by Gene Reeves to demonstrate how the W5100 eithenet handles multiple connections * using a single server object instance. * * once the sketch is loaded on your arduino and you see the start up banner display in the * Serial Monitor, fire up your favorite telnet client and connect a few instances. * * from the serial monitor you can send text to one or all clients. Use '*" for the ID to send * to all, otherwise use the ID you wish to send to, using this format to send, * "ID:TEDXT_TO_SEND". That should make everything as clear as mud :-) * * Dependencies: * * uses LinkedList class from here https://github.com/ivanseidel/LinkedList * to store list of connected clients. * * uses StringStream class from here https://gist.github.com/cmaglie/5883185 * Not sure why this was not a part of the StringObject to start with.. * * uses elapsedMillis class from here http://playground.arduino.cc/Code/ElapsedMillis * much improved version (for Teensy brand mcu's) here https://www.pjrc.com/teensy/td_timing_elaspedMillis.html * Unless I am mistaken, I beleive this code was originated by Paul Stoffregen of www.pjrc.com * If you are not famialar with Paul or his Teensy microcontroller, you would do yourself a * solid to spend some time reviewing his code and you can't beat the Teensy 3.1 for * price/performance/support!!! * Teensy 3.1 - 32bit-ARM mcu @ 72MHz (overclockable to 168MHz) w/ 256K flash / 64K ram / USB * for under $20. https://www.pjrc.com/teensy/index.html * * BTW, I am in no way affilaited with Paul or PJRC, I just think he built an awesome mcu. * */ #include <SPI.h> #include <LinkedList.h> #include <elapsedMillis.h> #include <StringStream.h> #include <Arduino.h> #include <EthernetUdp.h> #include <EthernetServer.h> #include <EthernetClient.h> #include <Ethernet.h> #include <Dns.h> #include <Dhcp.h> #include <IPAddress.h> #include <Server.h> #include <Client.h> /* DEFINES */ /*****************************************************/ /* Change the following to suite your environment. */ #define TELNET_SERVER_PORT 23 #define TELNET_SERVER_MAC 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xED #define TELNET_SERVER_IP 192, 168, 1, 177 #define TELNET_SERVER_GATEWAY 192, 168, 1, 1 #define TELNET_SERVER_NETMASK 255, 255, 255, 0 #define SERIAL_BAUD 115200 #define WELCOME_BANNER_STRING "Welcome to MultiTelnetServer.\r\nType 'exit' to disconnect\r\n" #define GOODBYE_BANNER_STRING "Come Back Again, Goodbye.." #define APP_BANNER_STRING "\r\n\r\n\tMultiTelnetServer Example\r\n This example application strives to\r\ndemonstrate using the Wiznet W5x00\r\nEthernet Shield's Arduino library usage\r\nwhen serving mulitple concurrent\r\nconnects using a single 'server object'\r\ninstance." #define SERVER_LISTENING_STRING "\tServer Listening on" /******************************************************/ /* * This is required to add RemoteIP, RemotePort support to the EthernetClient class * I stole it from here http://forum.arduino.cc/index.php?topic=210857.0 */ //namespace ethernet_fix //{ #include <utility/w5100.h> template<typename Tag, typename Tag::type M> struct AccessMember{ friend typename Tag::type get(Tag){ return M; } }; struct EthernetClient_Socket{ typedef uint8_t EthernetClient::*type; friend type get(EthernetClient_Socket); }; template struct AccessMember < EthernetClient_Socket, &EthernetClient::_sock > ; IPAddress RemoteIP(EthernetClient &c){ byte remoteIP[4]; W5100.readSnDIPR(c.*get(EthernetClient_Socket()), remoteIP); return (remoteIP); } uint16_t RemotePort(EthernetClient &c){ return W5100.readSnDPORT(c.*get(EthernetClient_Socket())); } //} // namespace "ethernet_fix" /* * EthernetClientEx - Because there is still no RemoteIP or RemotePort * properties built in to th current EthernetClient!! * */ class EthernetClientEx : public EthernetClient { protected: uint8_t _sock; // hack to get access to socket # public: EthernetClientEx(const EthernetClient &orig) : EthernetClient(orig) {} IPAddress remoteIP() { return RemoteIP((EthernetClient &)(*this)); } int16_t remotePort() { return RemotePort((EthernetClient &)(*this)); } bool isSameSock(const EthernetClientEx &c) { return (_sock == c._sock); } }; /* * ClientItem is class to wrap EthernetClient for storage on linked list * we will use it to add a ts (millis at connection time) and an index. * */ class ClientItem { public: unsigned long timestamp; int index; size_t recv_cnt; String recv_buffer; EthernetClientEx *client; ClientItem() { timestamp = millis(); index = -1; client = (EthernetClientEx *)false; } virtual ~ClientItem() { delete client; /* release memory */} unsigned long elapsed(void) { unsigned long ts = millis(); return (ts - timestamp); } }; /* * ClientList is linkedlist for storing connected clients. * For now, we'll just use it to store our clients, but should * expand this close to include testing all clients for disconnects, * testing all clients for pending recv data and sending data * to all connected clients. */ class LinkedClientList : public LinkedList<ClientItem*> //ClientList; { public: //EthernetServer *server; ClientItem *getClientItem(int idx) { for (ListNode<ClientItem *> *n = root;n;n=n->next) { if (n->data->index == idx) return n->data; } return (ClientItem *)0; } bool exists(const EthernetClientEx &ece) { for (ListNode<ClientItem *> *n = root; n; n = n->next) if (n->data->client->isSameSock(ece)) return true; return false; } void drop(int idx) { ClientItem *cli; int real_idx = -1; int ci = 0; for (ListNode<ClientItem *> *n = root; n; n = n->next) if (n->data->index != idx) ci++; else { cli = n->data; real_idx = ci; break; } if (real_idx != -1) { if (cli->client->connected()) cli->client->stop(); cli->index = -1; remove(real_idx); } } size_t send(const String &s, int idx) { size_t ret = 0; ClientItem * cli = getClientItem(idx); if (cli) { ret = cli->client->print(s); cli->client->flush(); } return ret; } size_t send2All(const String &s) { size_t ret = 0; for (ListNode<ClientItem *> *n = root; n; n = n->next) { ret += n->data->client->print(s); n->data->client->flush(); } return ret; } }; LinkedClientList ClientList; // Enter a MAC address and IP address for your controller below. // The IP address will be dependent on your local network. // gateway and subnet are optional: byte mac[] = { TELNET_SERVER_MAC }; IPAddress ip(TELNET_SERVER_IP); IPAddress gateway(TELNET_SERVER_GATEWAY); IPAddress subnet(TELNET_SERVER_NETMASK); // buffer to hold input from console StringStream con_buffer(""); // telnet defaults to port 23 EthernetServer server(TELNET_SERVER_PORT); /* forward's */ void server_check_new_connections(void); void server_check_client_recv(void); int server_accept(EthernetClientEx *enetcli); bool clientExists(const EthernetClientEx &c); bool test_for_cmd(const String &itm, const String &src); size_t console_recv(void); void proc_line(StringStream &ss); size_t client_recv(ClientItem *cli); void client_disconnecting(ClientItem *cli); void client_disconnected(ClientItem *cli); size_t send_to(int idx, const String &str); size_t send_to_all(const String &str); /*********************************************************/ void setup() { /* add setup code here */ // reserve some space for input buffer con_buffer.reserve(256); // Open serial communications and wait for port to open: Serial.begin(SERIAL_BAUD); delay(3000); // 3 second delay for Serial to connect. pinMode(10, OUTPUT); digitalWrite(10, HIGH); pinMode(4, OUTPUT); digitalWrite(4, HIGH); // initialize the ethernet device Ethernet.begin(mac, ip, gateway, subnet); // start listening for clients server.begin(); // Print "App Banner" and "Server Listening" notice. Serial.println(F(APP_BANNER_STRING)); Serial.println(F(SERVER_LISTENING_STRING)); Serial.print(F("\t\t")); Serial.print(ip); Serial.print(':'); Serial.println(TELNET_SERVER_PORT); Serial.println(F("\r\n\r\n")); } void loop() { /* add main program code here */ // check for any new clients server_check_new_connections(); // give up a timeslice yield(); // check to see if any of the clients // have recv data pending if (ClientList.size() > 0) server_check_client_recv(); // give up a timeslice yield(); // check to see if console has any // pending recv data. if (Serial.available()) console_recv(); // give up a timeslice yield(); } /*********************************************************/ size_t console_recv(void) { size_t ret = 0; int c = 0; while ((c = Serial.read()) >= 0) { if (c == 10) { proc_line(con_buffer); con_buffer = String(""); con_buffer.begin(); continue; } if (c != 13) { con_buffer += (char)c; ret++; } } return ret; } void proc_line(StringStream &ss) { // Console input should be in the for of // "ID:string_literal" // where: // ID is client index to send to or "*" (all) // string_literal is string to send // // check to ensure 2nd char is a ":" if ((ss.length() < 3) || (ss.charAt(1) != ':')) { Serial.print(F("Invalid Console Input \"")); Serial.print(ss); Serial.print(F("\"\r\nPlease use the following format:\r\n\t")); Serial.println(F("\"ID:string_literal\"\r\n")); Serial.flush(); return; } int idx = -1; Serial.print(F(" first char is '")); Serial.print(ss.charAt(0)); Serial.print(F("', ")); if (ss.charAt(0) != '*') { idx = ss.parseInt(); } Serial.print(F("idx=")); Serial.print(idx); Serial.print(F(", indexOf(':')=")); Serial.println(ss.indexOf(':')); ss = ss.substring(1 + ss.indexOf(':')); ss += F("\r\n"); if (idx < 0) ClientList.send2All(ss); //send_to_all(ss); else ClientList.send(ss, idx); } size_t console_print_connected_count(void) { size_t ret = Serial.print(F("\t\t")) + Serial.print(ClientList.size()) + Serial.println(F(" connected client(s).\r\n")); Serial.flush(); return ret; } bool test_for_cmd(const String &itm, const String &src) { String tst(src); tst.toLowerCase(); return tst.startsWith(itm); } void server_check_new_connections(void) { EthernetClient obj = server.available(); if (obj) { // convert to EthernetClientEx EthernetClientEx new_client(obj); // is it a new connection? if (!ClientList.exists(new_client)) { // accept new connection server_accept((EthernetClientEx *)&new_client); // send welcome banner new_client.println(F(WELCOME_BANNER_STRING)); new_client.flush(); } } } int server_accept(EthernetClientEx *enetcli) { // add itm to list ClientItem *itm = new ClientItem(); itm->timestamp = millis(); itm->index = ClientList.size(); itm->client = new EthernetClientEx((*enetcli)); ClientList.add(itm); // print notice on console Serial.print(F("Accepted new connection (")); Serial.print(itm->index); Serial.print(F(") from ")); Serial.print(enetcli->remoteIP()); Serial.print(':'); Serial.println(enetcli->remotePort()); Serial.println(); Serial.flush(); // print connected count console_print_connected_count(); // return items index return itm->index; } void server_check_client_recv(void) { if (ClientList.size() == 0) return; ClientItem *itm; for (int idx = ClientList.size() - 1; idx >= 0; idx--) { itm = ClientList.get(idx); if (itm->client->connected()==0) { client_disconnected(itm); continue; } if (itm->client->available()) { size_t rc = client_recv(itm); if (rc > 0) { // echo to Serial port Serial.print(F("Client #")); Serial.print(itm->index); Serial.print(F(" sent \"")); Serial.print(itm->recv_buffer); Serial.println('\"'); Serial.flush(); } // rc will == 0 if nothing received // rc will == -1 if client entered "exit" } yield(); } } size_t client_recv(ClientItem *cli) { int c; EthernetClientEx *cIn = (EthernetClientEx *)cli->client; cli->recv_buffer = "\0"; cli->recv_cnt = 0; while ((c = cIn->read()) >= 0) { cli->recv_buffer += (char)c; cli->recv_cnt++; } // check to see if they typed "exit" if (test_for_cmd(String(F("exit")), cli->recv_buffer)) { client_disconnecting(cli); delay(1); // give up timeslice cli->client->stop(); Serial.print(F("called client->stop() for #")); Serial.println(cli->index); Serial.flush(); return -1; } return cli->recv_cnt; } void client_disconnecting(ClientItem *cli) { Serial.print(F("Client #")); Serial.print(cli->index); Serial.println(F(" is disconnecting.")); // send Goodbye Banner send_to(cli->index, String(F(GOODBYE_BANNER_STRING))); Serial.flush(); delay(1); cli->client->stop(); } void client_disconnected(ClientItem *cli) { // print notice on console Serial.print(F("Client #")); Serial.print(cli->index); Serial.print(F(" was connected for ")); Serial.print(cli->elapsed() / 1000); Serial.print(F(" seconds, and has disconnected.")); Serial.flush(); // remove from list of clients ClientList.drop(cli->index); (*cli) = (*(ClientItem *)(unsigned long)0); // print connected count console_print_connected_count(); // this causes seg fault ?? //delete cli; } size_t send_to(int idx, const String &str) { return ClientList.send(str, idx); } size_t send_to_all(const String &str) { return ClientList.send2All(str); }Bingo ? Sounds good?
Again, sorry my C++ knowledge is very limited...
HTH
-
Happy if i could have helped you to save you some time to find a way for implementing a solution.
Let us know when you'd made some progress ;-)
-
BTW would you mind if I opened an issue at GH about this, linking to this topic, to help you and others developpers to have a reminder at the right place.
And maybe another C++ hero would see it, and start working on it! :-p
-
-
@soif I'm no good at C++ so I can't help there :(
I was wondering if you have had your Ethernet gateway crash at all while connecting two controllers? I have been experimenting with Domoticz (my primary controller is Vera) and my Ethernet gateway crashes about every other day. I usually have to power cycle the gateway to get it to come back (sometimes the reset button works). I had previously run the MYSController for many days without issue (but that was when I was on 1.4.1). Just curious if you are getting the same results since you have a similar setup to me. What is the other controller you are trying to connect?
My setup is a little different from yours as I'm using Node-red on a Pi to connect to the Ethernet gateway (Domoticz thinks it's looking at a serial connection so I haven't noticed the disconnect messages like you).
-
My GW don't crash while the second controller get a connection, it just throw away the previous connection.
I've tested this with MYS v1.51, 1.54 and 2.0beta.My two controllers are :
- Domoticz (primary)
- a simple telenet connection from the terminal
You can check this by having one controller connected, then from a terminal console type :
telnet IP_OF_THE_GW 5003It will launch a telnet session an you will see the GW messages scrolling, but when you send any characters (ie just hit return), boom, the first connected controller is immediately disconnected.
-
My GW don't crash while the second controller get a connection, it just throw away the previous connection.
I've tested this with MYS v1.51, 1.54 and 2.0beta.My two controllers are :
- Domoticz (primary)
- a simple telenet connection from the terminal
You can check this by having one controller connected, then from a terminal console type :
telnet IP_OF_THE_GW 5003It will launch a telnet session an you will see the GW messages scrolling, but when you send any characters (ie just hit return), boom, the first connected controller is immediately disconnected.
@soif said:
My GW don't crash while the second controller get a connection, it just throw away the previous connection.
Interesting. My Vera and Node-red must constantly be disconnecting each other. This could be a great clue as to what's going on with my gateway crashing. The strange thing is the myscontroller worked so well before...