Driver Station Custom UDP (Python/C++)

I don’t think my plans to switch us back over to Python will work out but in case they do, I’m curious as to if anyone has some insight on an issue we were having regarding UDP DS communcation. (This has nothing to do with the robotpy libs; more of a general networking question). My dashboard is written in C++ and the robot code is in Python. Back in week 2, I’d have some times where I’d plug the robot in and everything would work fine and other times where the two wouldn’t talk to each other unless i restarted everything. I think it had something to do with the port getting blocked on the roboRIO side. Does this code have any issues in terms of networking theology

For the code itself its pretty simple, the python should act as a server and listen on port 5805- whenever it receives a connection it will expect the auton mode, defense, and position and will feed back exactly what it was sent. The return packet is than displayed to confirm that the data being communicated is valid.

The entry point for the C++ code is startThread()

Python (RIO side)


    def server_thread_run(self, som,thingy):
        DashComm.print("[DashComm] Starting Server Thread")
        
        import os
        import time
        self.clear()
        while 1:
            self.clear()
            try:
                self.socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
                DashComm.print("[DashComm] Attempting to obtain socket 5805...")
                self.socket.bind((self.TCP_IP, self.TCP_PORT))
                self.socket.settimeout(5)
                self.socket.listen(0)
                DashComm.print("[DashComm] Successfully obtained socket 5805!")
                try:
                    while 1:
                        self.dataValid = False
                        DashComm.print("[DashComm] Listening for a connection. . .")
                        conn, addr = self.socket.accept()
                        DashComm.print("[DashComm] Found a connection!")
                        while conn and addr:
                            DashComm.print("[DashComm] The connection is valid!")
                            conn.settimeout(3)
                            conn.send(bytes('c' + chr(t) + chr(d) + chr(p), 'ASCII'))
                            data = conn.recv(self.BUFFER_SIZE)
                            DashComm.print("Data Valid: {} {} {} {}".format(data[0], data[1], data[2], data[3]))
                            self.processData(data)
                            self.dataValid = True
                except Exception as ex:
                    self.dataValid = False
                    self.socket.close()
                    time.sleep(2)
            except:
                wpilib.DriverStation.reportError("RESTART THE ROBORIO IF YOU WANT TO USE DS AUTON!!!! SOCKET 5805 OCCUPIED", False)
                #print("Exception : " + ex)
                self.dataValid = False
            finally:
                self.socket.close()
                time.sleep(5)
            

C++ DriverStation (yes i know QTcpSocket has a really wierd API)

#include "tcpsocketconn.h"
#include <QTcpSocket>

TCPSocketConn::TCPSocketConn(QQuickItem *parent) : QQuickItem(parent)
{

}

void TCPSocketConn::start(){
    qDebug("Start!");
    if(this->_status != -1){
        qDebug("Attempted to start TCPSocketConnection when started? %d", this->_status);
        return;
    }
    _pSocket = new QTcpSocket( this );
    this->_defensePosition = this->_defensePosition & 0xF;
    this->_defenseType = this->_defenseType & 0xF;
    this->_autonMode = this->_autonMode & 0xF;
    qDebug("STart tH!!read");
    connect(_pSocket, SIGNAL(readyRead()), this, SLOT(readTcpData()));
    connect(_pSocket, SIGNAL(connected()), this, SLOT(connected()));
    connect(_pSocket, SIGNAL(disconnected()), this, SLOT(disconnected()));
    connect(_pSocket, SIGNAL(error(QAbstractSocket::SocketError)), this, SLOT(error(QAbstractSocket::SocketError)));

    _pSocket->connectToHost("roborio-1684-frc.local", 5805);

    qDebug("STart tH!!!read");

}

void TCPSocketConn::error(QAbstractSocket::SocketError e){
    qDebug("THREADE RROR");
   //_pSocket->reset();

    this->_defensePosition = this->_defensePosition & 0xF;
    this->_defenseType = this->_defenseType & 0xF;
    this->_autonMode = this->_autonMode & 0xF;
    _pSocket->abort();
    _pSocket->connectToHost("roborio-1684-frc.local", 5805);
}

void TCPSocketConn::startThread(){
    qDebug("STart tHread");
    this->start();
}

void TCPSocketConn::close(){
    qDebug("Close Connection");
}

void TCPSocketConn::readTcpData(){
    //qDebug("Read TP DaTA");
    QByteArray arr = _pSocket->readAll();
    QByteArray r;
    r[0] = 99;
    r[1] = this->_autonMode & 0xF;
    r[2] = this->_defenseType & 0xF;
    r[3] = this->_defensePosition & 0xF;

    //qDebug("Broadcast data %d %d %d", r[1], r[2], r[3]);

    qDebug("Values: %d %d %d %d
",(int) arr[0], (int) arr[1], (int) arr[2], (int) arr[3]);
    if((int)arr[0] == 99){
        this->_autonMode =       ((int)arr[1]) << 4 + (this->_autonMode & 0xF);
        this->_defenseType =     ((int)arr[2]) << 4 + (this->_defenseType & 0xF);
        //qDebug("How is defense type %d %d %d", (int)arr[2], (int)arr[2] << 4, this->_defenseType);
        this->_defensePosition = ((int)arr[3]) << 4 + (this->_defensePosition & 0xF);
    }

    _pSocket->write(r);
}

void TCPSocketConn::disconnected(){
    qDebug("disconnected");
    //_pSocket->reset();

    this->_defensePosition = this->_defensePosition & 0xF;
    this->_defenseType = this->_defenseType & 0xF;
    this->_autonMode = this->_autonMode & 0xF;
    _pSocket->connectToHost("roborio-1684-frc.local", 5805);
}

void TCPSocketConn::connected(){
    qDebug("Connected");
}

QString TCPSocketConn::url(){
    return this->_url;
}

void TCPSocketConn::url(QString s){
    this->_url = s;

    if(this->_status == -1){
        this->startThread();
    }
}


QString TCPSocketConn::message(){
    return this->_lastMessage;
}

int TCPSocketConn::status(){
    return this->_status;
}

//the least significant byte is what the user requests
//the most significant byte is what the robot thinks it is
int TCPSocketConn::defensePosition(){

    return (this->_defensePosition & 0xF0) >> 4;
}

int TCPSocketConn::defenseType(){
    return (this->_defenseType & 0xF0) >> 4;
}

int TCPSocketConn::autonMode(){
    return (this->_autonMode & 0xF0) >> 4;
}

void TCPSocketConn::defensePosition(int x){
    this->_defensePosition = (this->_defensePosition & 0xF0) + (x & 0x0F);
}

void TCPSocketConn::defenseType(int x){
    this->_defenseType = (this->_defenseType & 0xF0) + (x & 0x0F);
}

void TCPSocketConn::autonMode(int x){
    this->_autonMode = (this->_autonMode & 0xF0) + (x & 0x0F);
}




void TCPSocketConn::a_3(QString d){

}
void TCPSocketConn::a_4(int s){

}

I know we had troubles connecting to the robot in our first event, I suspect because of mDNS issues. Our DS would connect, but networktables wouldn’t. Switched to static IPs + dashboard hack, no issues.

The reason I say mDNS is that it seems like once it gets resolved, it doesn’t seem to actually cache the values. A second query to a name takes just as long as the first, and it’s often a few seconds. I haven’t done any deep analysis on it, but I’m tired of it so I just use static IPs.

Unlikely that it was an mDNS issue since the DS was connected fine. From my experience working with other teams with mDNS, it was usually an all or nothing scenario (33, for example, ALWAYS worked in the pits and NEVER worked on the field. This dashboard is sometimes working in the pits and was never tested on the field)

If the process is killed when deploying code, could the old running code still have that port occupied?

That was not my team’s experience. The DS would connect fine, but the pynetworktables2js + IP camera would not connect.

In theory, no. In practice, I know I’ve seen it happen on OSX if you don’t specify SO_REUSEADDR because (presumably?) it doesn’t immediately close the socket. I feel like I’ve seen similar behavior on Windows…

On the field or in the pits? IP cameras don’t work with mDNS on the field (and I’ve had rough experiences with nettables, not specific to python)

Sorry, by IP camera I meant mjpg-streamer, so it was hosted on the robot.

And this was on the field, the pits worked marginally better.

Yeah anything remotely custom needs static IP to work… (honestly I’ve switched a few teams to static IP just because their default usb camera code wasn’t working)