I’ve got a photon set up as a TCPServer listening for connections on 10.87.0.180 port 90. This works fine if I’m on the same LAN as the photon and I connect to the photon via a browser. The photon responds with a web page and everything is great. However, if I forward port 90 to the photon and try to connect from the WAN side, I get some strange behavior. In chrome I get ERR_EMPTY_RESPONSE, and in Safari I get ‘Safari Cannot open the page because the network connection was lost.’
I have various other webservers running behind this router, ports forwarded the same way. This is unique to the photon. I have also tried other ports. Additionally, this site is connected to another site via an always-on site-to-site VPN and I get the same failure from the far end of the VPN when trying to connect to this photon, however, I have hundreds of servers on either end of this VPN all working fine, no matter which end of the VPN you connect to them from.
This seems to be a routing problem, as if the photon just doesn’t like the NAT.
Anyone have any success forwarding ports to a photon being used as a TCPServer or have any advice?
// This #include statement was automatically added by the Particle IDE.
#include <NCD2Relay.h>
NCD2Relay relayController;
// Our remote control buttons
int buttonPin1 = A0;
int buttonPin2 = A1;
int buttonPin3 = D2;
int buttonPin4 = D3;
int buttonPin5 = D4;
int buttonPin6 = D5;
int buttonPin7 = D6;
int buttonPin8 = D7;
TCPClient webClient;
TCPServer webServer = TCPServer(90);
char myIpAddress[24];
String readString;
long relayDuration = 0;
long relayStartTime = 0;
void setup() {
//Turn off wifi so we can set up our static ip
WiFi.off();
//Set static ip info
IPAddress myAddress(10,87,0,180);
IPAddress netmask(255,255,252,0);
IPAddress gateway(10,87,0,1);
IPAddress dns(10,87,0,1);
//Actually set the static ip
WiFi.setStaticIP(myAddress, netmask, gateway, dns);
//Tell the photon to use the static ip
WiFi.useStaticIP();
WiFi.on();
WiFi.connect();
//pins for RF remote inputs
pinMode( A0 , INPUT_PULLUP); //remote pin 1
pinMode( A1 , INPUT_PULLUP); //remote pin 2
pinMode( D2 , INPUT_PULLUP); //remote pin 3
pinMode( D3 , INPUT_PULLUP); //remote pin 4
pinMode( D4 , INPUT_PULLUP); //remote pin 5
pinMode( D5 , INPUT_PULLUP); //remote pin 6
pinMode( D6 , INPUT_PULLUP); //remote pin 7
pinMode( D7 , INPUT_PULLUP); //remote pin 8
relayController.setAddress(1, 1, 1); // set the address for the relay board
Particle.variable("ipAddress", myIpAddress, STRING); // make it so we can read the IP address variable of the photon in the particle console
IPAddress myIp = WiFi.localIP(); // get the IP of the photon which is handed out via DHCP
sprintf(myIpAddress, "%d.%d.%d.%d", myIp[0], myIp[1], myIp[2], myIp[3]); //Set the myIpAddress variable
webServer.begin(); //start the webserver
}
void loop() {
//Listen for button presses on the RF remote. If one of these buttons is pressed, feed the chickens the appropriate number of servings using the feedChickens function
//REMOTE BUTTON 1
if(digitalRead( buttonPin1 ) == HIGH){
feedChickens(1);
}
//REMOTE BUTTON 2
if(digitalRead( buttonPin2 ) == HIGH){
feedChickens(2);
}
//REMOTE BUTTON 3
if(digitalRead( buttonPin3 ) == HIGH){
feedChickens(3);
}
//REMOTE BUTTON 4
if(digitalRead( buttonPin4 ) == HIGH){
feedChickens(4);
}
//REMOTE BUTTON 5
if(digitalRead( buttonPin5 ) == HIGH){
feedChickens(5);
}
//REMOTE BUTTON 6
if(digitalRead( buttonPin6 ) == HIGH){
feedChickens(6);
}
//REMOTE BUTTON 7
if(digitalRead( buttonPin7 ) == HIGH){
feedChickens(7);
}
//REMOTE BUTTON 8
if(digitalRead( buttonPin8 ) == HIGH){
feedChickens(8);
}
//let's look at the http requests now
if (webClient.connected() && webClient.available()) {
while (webClient.connected()) {
if (webClient.available()) {
char c = webClient.read();
//read char by char HTTP request
if (readString.length() < 100) {
//store characters to string
readString += c;
//Serial.print(c);
}
//if HTTP request has ended
if (c == '\n') {
serveWebpage();
//look for arguments and feed the chickens appropriately similar to what we did with the RF buttons.
if (readString.indexOf("?button1") >0){
feedChickens(1);
}
if (readString.indexOf("?button2") >0){
feedChickens(2);
}
if (readString.indexOf("?button3") >0){
feedChickens(3);
}
if (readString.indexOf("?button4") >0){
feedChickens(4);
}
if (readString.indexOf("?button5") >0){
feedChickens(5);
}
if (readString.indexOf("?button6") >0){
feedChickens(6);
}
if (readString.indexOf("?button7") >0){
feedChickens(7);
}
if (readString.indexOf("?button8") >0){
feedChickens(8);
}
//clear the readString variable so it's empty for the next go around
readString = "";
}
}
}
}
else {
webClient = webServer.available();
}
//check to see if it's time to close that relay! What we are doing here is turning the relay off if the feeding duration has elapsed
if ((millis() - relayStartTime) > relayDuration) {
relayController.turnOffRelay(1);
}
}
//This function builds the response for the web page which will be displayed to the user in the browser.
void serveWebpage() {
webClient.println("HTTP/1.1 200 OK");
webClient.println("Content-Type: text/html");
webClient.println();
webClient.println("<HTML>");
webClient.println("<HEAD>");
webClient.println("<link rel=\"shortcut icon\" href=\"http://joedidit.com/projects/chickenfeeder/favicon.ico\" type=\"image/x-icon\" />");
webClient.println("<link rel=\"apple-touch-icon\" sizes=\"114×114\" href=\"http://joedidit.com/projects/chickenfeeder/apple-touch-icon-114x114.png\" />");
webClient.println("<link rel=\"apple-touch-icon\" sizes=\"72×72\" href=\"http://joedidit.com/projects/chickenfeeder/apple-touch-icon-72x72.png\" />");
webClient.println("<link rel=\"apple-touch-icon\" href=\"http://joedidit.com/projects/chickenfeeder/apple-touch-icon.png\" />");
webClient.println("<meta name='apple-mobile-web-app-capable' content='yes' />");
webClient.println("<meta name='apple-mobile-web-app-status-bar-style' content='black' />");
webClient.println("<style type=\"text/css\">");
webClient.println("body{ margin:60px 0px; padding:0px; text-align:center;");
webClient.println("}");
webClient.println("h1 { text-align: center; font-family:Arial, \"Trebuchet MS\", Helvetica, ");
webClient.println("sans-serif;");
webClient.println("}");
webClient.println("h2 { text-align: center; font-family:Arial, \"Trebuchet MS\", Helvetica, ");
webClient.println("sans-serif;");
webClient.println("}");
webClient.println("a { text-decoration:none; width:75px; height:50px; border-color:black; ");
webClient.println("border-top:2px solid; border-bottom:2px solid; border-right:2px solid; ");
webClient.println("border-left:2px solid; border-radius:10px 10px 10px; ");
webClient.println("-o-border-radius:10px 10px 10px; -webkit-border-radius:10px 10px 10px; ");
webClient.println("font-family:\"Trebuchet MS\",Arial, Helvetica, sans-serif; ");
webClient.println("-moz-border-radius:10px 10px 10px; background-color:#293F5E; ");
webClient.println("padding:8px; text-align:center;");
webClient.println("}");
webClient.println(".buttonRed { text-decoration:none; width:90px; height:50px; border-color:black; ");
webClient.println("border-top:2px solid; border-bottom:2px solid; border-right:2px solid; ");
webClient.println("border-left:2px solid; border-radius:10px 10px 10px; ");
webClient.println("-o-border-radius:10px 10px 10px; -webkit-border-radius:10px 10px 10px; ");
webClient.println("font-size: 28px;");
webClient.println("font-family:\"Trebuchet MS\",Arial, Helvetica, sans-serif; ");
webClient.println("-moz-border-radius:10px 10px 10px; background-color:#EB190F; ");
webClient.println("padding:40px; text-align:center;");
webClient.println("}");
webClient.println(".buttonYellow { text-decoration:none; width:90px; height:50px; border-color:black; ");
webClient.println("border-top:2px solid; border-bottom:2px solid; border-right:2px solid; ");
webClient.println("border-left:2px solid; border-radius:10px 10px 10px; ");
webClient.println("-o-border-radius:10px 10px 10px; -webkit-border-radius:10px 10px 10px; ");
webClient.println("font-family:\"Trebuchet MS\",Arial, Helvetica, sans-serif; ");
webClient.println("font-size: 28px;");
webClient.println("-moz-border-radius:10px 10px 10px; background-color:#D1CC22; ");
webClient.println("padding:40px; text-align:center;");
webClient.println("}");
webClient.println(".buttonGreen { text-decoration:none; width:90px; height:50px; border-color:black; ");
webClient.println("border-top:2px solid; border-bottom:2px solid; border-right:2px solid; ");
webClient.println("border-left:2px solid; border-radius:10px 10px 10px; ");
webClient.println("-o-border-radius:10px 10px 10px; -webkit-border-radius:10px 10px 10px; ");
webClient.println("font-family:\"Trebuchet MS\",Arial, Helvetica, sans-serif; ");
webClient.println("font-size: 28px;");
webClient.println("-moz-border-radius:10px 10px 10px; background-color:#11D51A; ");
webClient.println("padding:40px; text-align:center;");
webClient.println("}");
webClient.println(".buttonBlue { text-decoration:none; width:90px; height:50px; border-color:black; ");
webClient.println("border-top:2px solid; border-bottom:2px solid; border-right:2px solid; ");
webClient.println("border-left:2px solid; border-radius:10px 10px 10px; ");
webClient.println("-o-border-radius:10px 10px 10px; -webkit-border-radius:10px 10px 10px; ");
webClient.println("font-family:\"Trebuchet MS\",Arial, Helvetica, sans-serif; ");
webClient.println("font-size: 28px;");
webClient.println("-moz-border-radius:10px 10px 10px; background-color:#157EFC; ");
webClient.println("padding:40px; text-align:center;");
webClient.println("}");
webClient.println("a:link {color:white;} /* unvisited link */ a:visited {color:white;} /* ");
webClient.println("visited link */ a:hover {color:white;} /* mouse over link */ a:active ");
webClient.println("{color:white;} /* selected link */");
webClient.println("</style>");
webClient.println("<TITLE>Chicken Feeder</TITLE>");
webClient.println("</HEAD>");
webClient.println("<BODY>");
webClient.println("<H1>Chicken Feeder</H1>");
webClient.println("<hr />");
webClient.println("<br />");
webClient.println("<br />");
webClient.println("<br />");
webClient.println("<br />");
webClient.println("<br />");
webClient.println("<br />");
webClient.println("<br />");
webClient.println("<br />");
webClient.println("<br />");
webClient.println("<br />");
webClient.println("<br />");
webClient.println("<br />");
webClient.println("<br />");
webClient.println("<br />");
webClient.println("<br />");
webClient.println("<br />");
webClient.println("<br />");
webClient.println("<br />");
webClient.println("<br />");
webClient.println("<br />");
webClient.println("<br />");
webClient.println("<br />");
webClient.println("<a class=\"buttonRed\" href=\"/?button7\"\">7 Servings</a>");
webClient.println("<a class=\"buttonBlue\" href=\"/?button8\"\">8 Servings</a><br />");
webClient.println("<br />");
webClient.println("<br />");
webClient.println("<br />");
webClient.println("<br />");
webClient.println("<br />");
webClient.println("<br />");
webClient.println("<br />");
webClient.println("<a class=\"buttonRed\" href=\"/?button5\"\">5 Servings</a>");
webClient.println("<a class=\"buttonBlue\" href=\"/?button6\"\">6 Servings</a><br />");
webClient.println("<br />");
webClient.println("<br />");
webClient.println("<br />");
webClient.println("<br />");
webClient.println("<br />");
webClient.println("<br />");
webClient.println("<br />");
webClient.println("<a class=\"buttonYellow\" href=\"/?button3\"\">3 Servings</a>");
webClient.println("<a class=\"buttonGreen\" href=\"/?button4\"\">4 Servings</a><br />");
webClient.println("<br />");
webClient.println("<br />");
webClient.println("<br />");
webClient.println("<br />");
webClient.println("<br />");
webClient.println("<br />");
webClient.println("<br />");
webClient.println("<a class=\"buttonYellow\" href=\"/?button1\"\">1 Servings</a>");
webClient.println("<a class=\"buttonGreen\" href=\"/?button2\"\">2 Servings</a><br />");
webClient.println("<br />");
webClient.println("</BODY>");
webClient.println("</HTML>");
delay(1000);
//stopping webClient
webClient.stop();
delay(100);
}
//This function converts the number of servings into the number of milliseconds the motor needs to run
void feedChickens(long servings) {
relayController.turnOnRelay(1); //turn on the relay for the motor
relayDuration = servings * 3000; //sset the amount of time that should pass before turning the relay off.
relayStartTime = millis(); //set the start time to now.
char publishString[40]; //create a char so we can publisn to the console
if(Particle.connected() == true){ //only run the next lines if we are connected to the particle cloud
sprintf(publishString, "%d Servings", servings); //build the publish string
Particle.publish("Dispensing Feed", publishString); //publish to the cloud
}
}
//a strtofloat string we use to convert strings to floats
float strToFloat(String str) {
char carray[str.length() + 1]; // determine size of array
str.toCharArray(carray, sizeof(carray)); // put str into an array
return atof(carray);
}