Two way coms between photons tcp client and server

So I’m not sure I totally understand how the TCP code works and I’ve been having a little trouble getting two photons communication back and forth with each other using client.write(buff,len) and server.write(buff,len).

So I have a “console” photon connecting to a “dome” photon (the names are relevant to the project we’re working on). The console connects as a client to the dome and then booth should then send their names to each other. This is all I’m trying to get working right now. I have it functioning one way with server.write(buff,len) and client.read() in a loop taking out one byte at a time. The console prints the correct name “DOME” to the serial monitor.

The server code can’t seem to read the console’s name however, and I think this is just simple user error. I don’t really understand why there’s a client.write() function with no server.read() function to pair with it, so I’m just using client.read() in both cases. This worked for a single character but not for the buffers. The two files I’m using are below, can anyone point me in the right direction here?

console.ino

SYSTEM_THREAD(ENABLED)
//Here we declare our server and our client objects
TCPServer server = TCPServer(78);
TCPClient client;

//Here we have the IP address for the other photon we're communication with and
//it's port number.
byte dome_IP[4] = {192,168,11,101};
byte pod_IP[4]  = {192,168,11,102};
int dome_Port = 79;
int pod_Port  = 80;

//This is a value unique to this code to send out
const unsigned char name[7] = {'C','O','N','S','O','L','E'};
char other

Name[4] = {0};

//Here's the pin for the button we're using
int buttonPin = D0;
//User LED pin
int userLED = D7;

int i = 0;

//Array used for debouncing the switch
uint8_t debounce = 0b00000000;
//Holds the state of the userLED
bool userLED_state = false;
bool buttonState = false;

void setup() {
    //Turn off wifi so we can set up our static ip
    WiFi.off();
//Set static ip info
IPAddress myAddress(192,168,11,100);
IPAddress netmask(255,255,255,0);
IPAddress gateway(192,168,11,1);
IPAddress dns(192,168,11,1);
//Actually set the static ip
WiFi.setStaticIP(myAddress, netmask, gateway, dns);
//Tell the photon to use the static ip
WiFi.useStaticIP();
//Reconnect to wifi
WiFi.on();
WiFi.connect();

//Start listening for clients
server.begin();

//Start up serial so we can communicate various commands
Serial.begin(9600);
//Hold still and check wifi until we're connected over serial
while(!Serial.available()) Particle.process();
Serial.println("Sup guys! I'm the console!");

//Print out wifi credentials, just to make sure everything is ok
Serial.print("Local IP: ");
Serial.println(WiFi.localIP());
Serial.print("Subnet Mask: ");
Serial.println(WiFi.subnetMask());
Serial.print("gateway IP: ");
Serial.println(WiFi.gatewayIP());
Serial.print("SSID: ");
Serial.println(WiFi.SSID());

//Some pinMode calls to initialize our GPIO
pinMode(buttonPin, INPUT);
pinMode(userLED, OUTPUT);
digitalWrite(userLED, LOW);
}

void loop() {
    //Runs for maintenance
    /*Particle.process();*/

//If we're connected to a client, which we should be most of the time, send
//a value to the client to make sure we're connected. If not try to connect
//to a client.
/*if(client.connected())
{
    Serial.println("Oh hey there!");
    server.write(buttonState);
}
else
{
    Serial.println("And nobody came....");
    client = server.available();
}*/

if(client.connected())
{
  Serial.println("Hey dome!");
  if(client.available())
  {
    client.write(name,7);
    Serial.print("From the dome... ");
    while(client.available())
    {
      otherName[i] = client.read();
      i++;
    }
    i = 0;
    for(i=0;i<4;i++)
    {
      Serial.print(otherName[i]);
    }
    Serial.print("\t");
  }
}
else
{
  client.connect(dome_IP,dome_Port);
  /*Serial.println("dome?");*/
}

//Here we read
debounce = debounce<<1;
debounce |= digitalRead(buttonPin);
/*Serial.print("debounce: ");
Serial.print(debounce,BIN);*/
if(debounce == 0b10000000)
{
  buttonState = true;
}
else if(debounce == 0b01111111)
{
  buttonState = false;
}

/*Serial.print("\tUser LED state: ");
Serial.println(userLED_state);*/
digitalWrite(userLED, buttonState);
delay(100);
}

And here’s dome.ino

SYSTEM_THREAD(ENABLED)
//Here we declare our server and our client objects
TCPServer server = TCPServer(79);
TCPClient client;

//Here we have the IP address for the other photon we're communication with and
//it's port number.
byte console_IP[4] = {192,168,11,100};
byte Pod_IP[4]     = {192,168,11,102};
int console_Port = 78;
int pod_Port     = 80;

//This is a value unique to this code to send out
const unsigned char name[4] = {'D','O','M','E'};
char otherName[7] = {0};

//Here's the pin for the button we're using
int buttonPin = D0;
//User LED pin
int userLED = D7;

int i = 0;

//Array used for debouncing the switch
uint8_t debounce = 0b00000000;
//Holds the state of the userLED
bool userLED_state = false;
bool buttonState = false;

void setup() {
    //Turn off wifi so we can set up our static ip
    WiFi.off();
    //Set static ip info
    IPAddress myAddress(192,168,11,101);
    IPAddress netmask(255,255,255,0);
    IPAddress gateway(192,168,11,1);
    IPAddress dns(192,168,11,1);
    //Actually set the static ip
    WiFi.setStaticIP(myAddress, netmask, gateway, dns);
    //Tell the photon to use the static ip
    WiFi.useStaticIP();
    //Reconnect to wifi
    WiFi.on();
    WiFi.connect();

//Start listening for clients
server.begin();

//Start up serial so we can communicate various commands
Serial.begin(9600);
//Hold still and check wifi until we're connected over serial
while(!Serial.available()) Particle.process();
Serial.println("Sup guys! I'm the dome!");

//Print out wifi credentials, just to make sure everything is ok
Serial.print("Local IP: ");
Serial.println(WiFi.localIP());
Serial.print("Subnet Mask: ");
Serial.println(WiFi.subnetMask());
Serial.print("gateway IP: ");
Serial.println(WiFi.gatewayIP());
Serial.print("SSID: ");
Serial.println(WiFi.SSID());

//Some pinMode calls to initialize our GPIO
pinMode(buttonPin, INPUT);
pinMode(userLED, OUTPUT);
digitalWrite(userLED, LOW);
}

void loop() {
    //Runs for maintenance
    /*Particle.process();*/
//If we're connected to a client, which we should be most of the time, send
//a value to the client to make sure we're connected. If not try to connect
//to a client.
if(client.connected())
{
    Serial.println("Oh hey there!");
    server.write(name,4);
    while(client.available());
    {
      otherName[i] = client.read();
      i++;
    }
    i = 0;
    Serial.print("And I got a...");
    for(i=0;i<7;i++)
    {
      Serial.print(otherName[i]);
    }
    Serial.print("\t");

}
else
{
    /*Serial.println("And nobody came....");*/
    client = server.available();
}

//Here we read
debounce = debounce<<1;
debounce |= digitalRead(buttonPin);
/*Serial.print("debounce: ");
Serial.print(debounce,BIN);*/
if(debounce == 0b10000000)
{
  buttonState = true;
}
else if(debounce == 0b01111111)
{
  buttonState = false;
}

/*Serial.print("\tUser LED state: ");
Serial.println(userLED_state);*/
digitalWrite(userLED, buttonState);
delay(100);
}

First some general things to “correct” and then retry your test.

Since you are initially intending to not really connect to the cloud, it’d be better to use SYSTEM_MODE(SEMI_AUTOMATIC) and not only rely on SYSTEM_THREAD(ENABLED).
In AUTOMATIC mode, the system thread will still attempt to connect and you’ll just pull the WiFi out underneath it.

Next, after calling WiFi.connect() you should waitUntil(WiFi.ready) before you proceede to use the WiFi connection.

For what reason does your console actually feature its own TCPServer?
You said the console attaches itself to the dome as client, but who’d attache itselft to the console server?

BTW: A TCPServer always uses its internal TCPClient object to do the talking.

Also setting the static IPs is only required once, since this setting will be stored in non-volatile flash memory.

1 Like

Thanks for the reply! I made those quick changes and I got the same result. It is nice that with SYSTEM_MODE(SEMI_AUTOMATIC) I’m not waiting for the cloud connection though, so thanks! Also I did mean to add the wait until wifi is ready code before, it was mentioned to me in another thread, I just completely forgot.

The reason I have the console as a server as well is that I thought this would be necessary for two way communications, to have both be servers and clients connected to each other. That way I could use server.write() and client.read() two ways. I think that was born of me not having any clue how this TCP library works though.

So the TCPServer has a built in TCPClient object, which explains why there’s no server.read() since it would use its internal client.read() right? So client.write() on the console should send the buffer to the server, which on the server end I read with serverName.read(), which will call the method inherited from the TCPClient object? I’m still not sure I understand but I think I’m getting there. Thanks again for the help!

1 Like

There is at least one issue with your DOME code here

    while(client.available());  // <--- see this semicolon, this actually ends the loop, 
    {                           // so this block only ever gets executed once
      otherName[i] = client.read();
      i++;
    }

BTW: If you have access to your AP/router setup, you could save yourself the hassle of setting a static IP, if you set a static IP at the router, tied to the devices MAC address.

Oh my god I cannot believe I missed that semicolon. This is not the first time I spent forever looking for a bug that stupid. I am so embarrassed right now :stuck_out_tongue:

I fixed that and things are working better. The console now reads DOME every cycle and prints it out over the serial monitor, however I’m still not getting anything on the serial monitor for the dome. I’m still not all that confident in how the TCP libraries work. Did my explanation before make any sense?

The name of the server object in the dome code is just named server, and assumed from the internal TCPClient you mentioned that server.read() would work in that case but that didn’t compile citing a lack of the read() member in the server class.

Have a play with this slightly reworked dome code an telnet

TCPServer server(79);
TCPClient client;

const char name[12] = {"DOME"};

void setup() {
/*
    WiFi.off();
    IPAddress netmask(255,255,255,0);
    IPAddress gateway(192,168,1,1);
    IPAddress dns(192,168,1,1);
    WiFi.setStaticIP(dome_IP, netmask, gateway, dns);
    WiFi.useStaticIP();
*/
    WiFi.on();
    WiFi.connect();

    waitUntil(WiFi.ready);
    
    server.begin();

    Serial.begin(115200);

    while(!Serial.available()) Particle.process();
    Serial.print("Sup guys! I'm the ");
    Serial.println(name);

    Serial.print("Local IP: ");
    Serial.println(WiFi.localIP());
    Serial.print("Subnet Mask: ");
    Serial.println(WiFi.subnetMask());
    Serial.print("gateway IP: ");
    Serial.println(WiFi.gatewayIP());
    Serial.print("SSID: ");
    Serial.println(WiFi.SSID());

    pinMode(buttonPin, INPUT);
    pinMode(userLED, OUTPUT);
    digitalWrite(userLED, LOW);
}

void loop() {
    char x;
    
    if(!client.connected())
    {
        client = server.available();
        if (client.connected())
        {
          client.remoteIP().printTo(Serial);  // prints the IP of the remote client
          client.print(" Hi, I am ");
          client.write((uint8_t*)name, strlen(name)+1);
          client.println();
        }
    }
    else
    {
        while(client.available())
        {
            Serial.write(x = client.read());
            if (x == '?')
            {
              client.print("Hi, I am ");
              client.write((uint8_t*)name, strlen(name)+1);
              client.println();
              client.stop();
            }
        }
    }
}

Open a serial monitor, hit any key see the IP of the device and type

// insert whatever IP you'll get reported
telnet 192.168.11.xxx 79

and send data to the dome, it’ll print it to serial and when you hit ‘?’ will terminate the session.
Maybe this clears things up a bit.

The client is actually the part that does the talking in both directions.
The server is mainly someone hanging around and waiting for a client to call - like “the little lady” from the telephone switch board (as you might have seen in old B/W movies :wink: )
Once she had connected the caller with the called extension all the talking was doen without her and the two clients on either device are like the two sockets the lady from the board just blugged her patch cables in to connect them.

1 Like

Ok I think I get it now. The server functions are just to set things up, and once that’s the case I can just use all client functions. That makes a lot more sense.

I’ve tried using telnet to connect to the dome a few times but I’m having trouble. I’m using PuTTY, and I’ve made sure I’m entering the right IP and port number. I’m probably doing something very simple very wrong, maybe there’s a semicolon after the IP address that’s getting me :stuck_out_tongue:

1 Like

If, then it’d be a colon (:) not a semicolon (;) (but in my PuTTY I have a dedicated port field, so no separator needed)

Are you using a TelNet connection in PuTTY?
Is your PC connected to the correct net/subnet to see the device?


BTW: server.write() is more than just a client.write() although in your case it’d have the same effect.
client.write() sends the data to that client while server.write() would send the same data to all clients.
Having only one client it has just the same effect, but an “imaginary” server.read() would get hit from all clients causing a lot of confusion :wink:

So the problem was that the laptop I’m on decided to default to the school network and not the router sitting next to it the photons were on. It happens on occasion and I always forget to double check that.

Ok so I’ve played with your code, I think I got a good grasp of it, and now I have two photons streaming data back and forth! I think I have what I need to get the rest of this working now, thanks so much for the help I really do appreciate it! Your code was immensely helpful! I’ll make sure to stick around in the hardware category and see if I can pay anything forward.

If you don’t mind though, I have a few more small questions about the code. You wrote name[12] = {"DOME"}, why isn’t the length of the buffer four? Also I’m assume that the len argument needs to count from 1 not 0 hence the plus one in the client.write((uint8_t*)name, strlen(name)+1) call correct?

1 Like

The reason for choosing 12 is to unify the code for reusability with your other devices that have longer names and make it a multiple of four since this is easier for the 32bit µC as all variables always start at a four-byte boundary and not using all the bytes to the next four byte boundary would be a waste (neardy, isn’t it :sunglasses:).
BTW it would need to be 5 at least since you have DOME pluse the zero-terminator that is required to properly end a C string.
And this also answers the count question: In order to transfer a valid string including zero-terminator you need string len (four characters) plus one for the terminator.
This has nothing to do with the array indexes really, but these would be as follows: ([0] = D, [1] = O, [2] = M, [3] = E, [4] = ’ \0’).

Oh ok that makes a lot of sense. It’s just making sure we declare 3 full registers of memory. Doing that makes the code more efficient since the next variable doesn’t get declared on the second half of that same third register right? By this same token, would it be more efficient to keep arrays of 4 uint8_ts so that we hit the 4 byte mark, or does that not really matter? I’d imagine that could be the kind of thing the compiler would spot and take care of for us.

The thing is not that a subsequent variable would be placed on a “wrong” position, but since all variables are always (unless explicitly instructed via __attibute__((packed))) located on four-byte-boundaries, we’d “lose” some bytes.

e.g. (I think it’s actually back to front, but for the clarity)

uint8_t a;    // located at 0x8000000
// three byte gap lost
uint8_t x[2]; // starting at 0x8000004
// two byte gap lost
uint8_t b;    // located at 0x8000008 

So when we already have an array, why not use all the bytes it occupies anyway.

Oh ok, I get it. So it ends up being mostly a “we might as well” type of thing, because we’re allocating that memory regardless. Thanks a lot for the all the help again, I really appreciate it!

1 Like