Skip to content

Ruida Driver - Support TCP Lightburn Bridge #202

@tatarize

Description

@tatarize

Some folks have a Lightburn Bridge, it's basically just a raspberry pi that connects to a short USB cord on one side, and wifi on the other. The only use of the network connection to the ruida is from the PI and it's just a simple relay.

class RuidaRelay(Relay):

    def __init__(self, *args):
        # super().__init__(cfg, status)
        self.version = (1, 0)
        self.type = RelayType.Ethernet
        self.buffer_size = 1024
        self.from_laser_port = 40200
        self.to_laser_port = 50200
        self.stop_lock = threading.Lock()
        self.stop = False
        self.type = RelayType.Ethernet
        self.laser_ip = "127.0.0.1"
        self.server_ip = '0.0.0.0'
        self.server_port = 5005

    def runConnection(self, sock):
        outSock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
        inSock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
        inSock.bind(('', self.from_laser_port))
        inSock.setblocking(0)
        serv, addr = sock.accept()
        self.status.ok(f"Connection from: {addr[0]}:{addr[1]}")
        serv.setsockopt(socket.SOL_TCP, socket.TCP_NODELAY, 1)
        serv.setsockopt(socket.SOL_SOCKET, socket.SO_SNDBUF, 4096)
        serv.setsockopt(socket.SOL_SOCKET, socket.SO_RCVBUF, 32768)
        packet = b''
        packetLen = 0
        lastLen = 0
        packetType = ord('L')
        ackValue = b''
        gotAck = True
        lastTime = time.time()
        while 1:
            readable, writable, exceptional = select.select([inSock, serv], [], [serv], 1.0)
            if serv in exceptional:
                self.status.error('Server socket error')
                break
            if gotAck:
                if serv in readable:
                    try:
                        if packetLen == 0:
                            data = serv.recv(3 - len(packet))
                        else:
                            data = serv.recv(packetLen - len(packet))
                    except Exception:
                        pass

                    if not data:
                        break
                    packet += data
                    if packetLen == 0:
                        if len(packet) == 3:
                            packetType = packet[0]
                            packetLen = (packet[1] << 8) + packet[2]
                            packet = serv.recv(packetLen)
                    if packetLen != 0:
                        if len(packet) == packetLen:
                            if packetType == ord('L'):
                                outSock.sendto(packet, (self.laser_ip, self.to_laser_port))
                                lastLen = packetLen
                                packetLen = 0
                                packet = b''
                                lastTime = time.time()
                                gotAck = False
                            else:
                                if packetType == ord('P'):
                                    data = b'P\x00\x02' + bytes(self.version)
                                    serv.send(data)
                                else:
                                    self.status.error(f"unhandled packet type {packetType}" + str(bytes(chr(packetType))))
            if inSock in readable:
                data, addr = inSock.recvfrom(self.buffer_size)
                if len(data) > 1 or lastLen <= 500:
                    hdr = bytes([packetType, len(data) >> 8, len(data) & 255])
                    serv.send(hdr)
                    serv.send(data)
                if len(data) == 1:
                    if len(ackValue) == 0:
                        ackValue = data
                        gotAck = True
                    else:
                        if ackValue[0] != data[0]:
                            self.status.warn('Non-ack received')
                            break
                        else:
                            gotAck = True
            if gotAck == False and time.time() - lastTime > 6:
                self.status.error('Laser timeout error')
                break

        serv.close()
        outSock.close()
        inSock.close()
        self.status.ok('Ruida command complete')

    def run(self):
        self.stop = False
        sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        sock.bind(("", self.server_port))
        print(f"Ruida relay started, laser IP: {self.laser_ip}")
        sock.setblocking(0)
        while True:
            sock.listen(1)
            gotConnect = False
            while True:
                readable, writable, exceptional = select.select([sock], [], [sock], 1.0)
                if sock in readable:
                    gotConnect = True
                    break
                if sock in exceptional:
                    break
                with self.stop_lock:
                    if self.stop:
                        break

            if gotConnect == False:
                break
            self.runConnection(sock)
            with self.stop_lock:
                if self.stop:
                    break

        self.status.info('Ruida relay stopped')

It does a few things like non-blocking connections and streaming protocol to improve throughput, but basically accepts two commands L and P. The L <2-byte length> <payload> just sends that data to the laser and the P command replies with the relay version 1.0 which is effectively the whole protocol. They do a similar thing for the camera code where F provides a frame from the camera and T provides a thumbnail. I'm guessing they don't just implement the MJPEG server because they don't have networked camera support. --- In any event:

Connect to port 5005.
Commands are L<length><Payload> and responses are in-kind. These will be sent across the UDP channel so they should have normal UDP checksum, and swizzle. And after sending a batch of data they quickly just disconnect from the TCP server.

This is another connection the would exist for Ruida and is pretty easy to implement. In MeerK40t, I am planning on implementing it pretty soon (since the driver's finally being implemented (I got a beta tester!), and the Ruida emulator thing I wrote into the program already does so.

Metadata

Metadata

Assignees

No one assigned

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions