Hi,
I want to control an LED/relay from an external webserver with an Arduino Nano and W5500 Ethernet chip in realtime (latency for button click on website ---> LED toggle should be lower than 1sec). The task is simple with UDP but my Arduino is behind a NAT/router with dynamic IP and port forwarding is not possible so I can't send UDP packets from my external webserver to Arduino.
Arduino <---> NAT/router <---> webserver
To solve this, I establish a TCP connection from Arduino to the webserver. It's a Python webserver and I handle the connection in its own thread. When someone presses the button to toggle the LED, the webserver just appends a command ('0' or '1') to my TCP handler thread that sends the command via established TCP connection to my Arduino.
Problem: it works sometimes for many minutes/hours then the TCP connection stucks. Arduino thinks the connection is established and I also don't get a timeout Exception or something on my Python thread but no data is received/transmitted.
Has someone an idea how I can solve this problem?
This is my Arduino code:
#include <SPI.h>
#include <Ethernet2.h>
#include "utility/w5500.h"
// Enter a MAC address for your controller below.
// Newer Ethernet shields have a MAC address printed on a sticker on the shield
byte mac[] = {
0x00, 0xAA, 0xBB, 0xCC, 0xDE, 0x02
};
unsigned int TCPPort = 8080;
IPAddress server(192, 168, 100, 28);
EthernetClient client;
// status LED
const int pin_led_tcp = 4;
// relais pins
const int pin_relais1 = 5;
void setup() {
// Open serial communications and wait for port to open:
Serial.begin(9600);
// init and turn off relais
pinMode(pin_relais1, OUTPUT);
digitalWrite(pin_relais1, HIGH);
// init and turn off status LED
pinMode(pin_led_tcp, OUTPUT);
digitalWrite(pin_led_tcp, LOW);
// start the Ethernet connection:
if (Ethernet.begin(mac) == 0) {
Serial.println("Failed to configure Ethernet using DHCP");
// no point in carrying on, so do nothing forevermore:
for (;;);
}
// connect TCP
connectTCP();
}
void loop() {
uint8_t status = client.status();
if(status != SnSR::ESTABLISHED){
Serial.println("Connection lost...");
digitalWrite(pin_led_tcp, LOW);
Serial.println(status, HEX);
for(size_t i=0; i<MAX_SOCK_NUM; i++){
Serial.print("state ");
Serial.print(i);
Serial.print(": ");
Serial.println(Ethernet._state[i]);
}
client.stop();
//reconnect
connectTCP();
} else {
// turn LED on if Arduino is connected
digitalWrite(pin_led_tcp, HIGH);
}
char cmd = -1;
if (client.available()) {
cmd = client.read();
}
// execute command
switch (cmd) {
case '1':
Serial.println("Turn lights on...");
digitalWrite(pin_relais1, LOW);
break;
case '0':
Serial.println("Turn lights off...");
digitalWrite(pin_relais1, HIGH);
break;
case 'p': // ping
Serial.println("got ping");
client.write('p');
break;
case -1: // no command
break;
default:
Serial.println("unknown command");
Serial.println(cmd);
break;
}
}
bool connectTCP(){
// if you get a connection, report back via serial:
if (client.connect(server, TCPPort)) {
Serial.println("connected");
return true;
} else {
// if you didn't get a connection to the server:
Serial.println("connection failed");
}
return false;
}
And this is my Python TCP handler:
class ArduinoSocket(object):
def __init__(self, port, logger):
self.port = port
self.logger = logger
self.sock = self.__create_socket()
self.running = True
self.commands = []
self.send_timeout = 5.0
# start loop
self.thread = Thread(target=self.loop)
self.thread.start()
def __create_socket(self):
self.logger.debug("Create new socket")
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.settimeout(2.0)
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
"""Set TCP keepalive on an open socket.
It activates after 1 second (after_idle_sec) of idleness,
then sends a keepalive ping once every 3 seconds (interval_sec),
and closes the connection after 5 failed ping (max_fails), or 15 seconds
"""
#sock.setsockopt(socket.SOL_SOCKET, socket.SO_KEEPALIVE, 1)
#sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_KEEPIDLE, 1)
#sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_KEEPINTVL, 3)
#sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_KEEPCNT, 5)
sock.bind(("0.0.0.0", self.port))
sock.listen(8)
return sock
def loop(self):
clientsocket = None
last_ping = time.time()
while self.running:
if clientsocket is None:
try:
(clientsocket, address) = self.sock.accept()
self.logger.debug("Got connection to {}".format(address))
except IOError, e:
self.logger.error("IOError {}".format(e))
continue
except socket.timeout:
self.logger.error("Timeout {}".format(e))
continue
except Exception as e:
self.logger.error("Unknown error {}".format(e))
if len(self.commands) > 0 and clientsocket is not None:
try:
t, cmd = self.commands[0]
if time.time() - t < self.send_timeout:
self.logger.debug("send command {}".format(cmd))
clientsocket.send(str(cmd))
self.commands.pop(0)
except IOError as e:
self.logger.error("Clientsocket.send() IOError {}".format(e))
if clientsocket is not None:
clientsocket.close()
clientsocket = None
except Exception as e:
self.logger.error("Clientsocket.send() Unknown error {}".format(e))
if clientsocket is not None:
clientsocket.close()
clientsocket = None
elif time.time() - last_ping > 5:
try:
if clientsocket is not None:
self.logger.debug("send ping")
clientsocket.send("p")
last_ping = time.time()
else:
self.logger.debug("no clientsocket to send ping")
except IOError as e:
self.logger.error("Clientsocket.send() IOError {}".format(e))
if clientsocket is not None:
clientsocket.close()
clientsocket = None
except Exception as e:
self.logger.error("Clientsocket.send() Unknown error {}".format(e))
if clientsocket is not None:
clientsocket.close()
clientsocket = None
def send(self, cmd):
self.commands.append((time.time(), cmd))