Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Ruida Driver - Support TCP Lightburn Bridge #202

Open
tatarize opened this issue Dec 23, 2023 · 0 comments
Open

Ruida Driver - Support TCP Lightburn Bridge #202

tatarize opened this issue Dec 23, 2023 · 0 comments

Comments

@tatarize
Copy link
Contributor

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

2 participants