0

First time on StackExchange as a questioner! Hope I get this right. Apols if this is TLDR.

For thoes of you familiar with the TinyG SPJS you will know my context. For others: There is a program created by "somebody" called the SPJS(Serial Port Json Server) here It has a websocket interface. Among other things it primarially manages 2 things

  1. The Serial port traffic to and from the TinyG CNC controller. It manages integrity (no loss of data) and the buffering of commands to ensure a constant supply of G Code commands to the TinyG to ensure smooth cnc motion control.
  2. Multiple pc CAM clients in the form of pub sub websocket interface.

My Problem is 2 fold:

  1. Does anyone knows where to find the SPJS protocol I would really appreciate it?

  2. I want to use Python to write my own client. However I run into an issue at the connect:

import asyncio

import websockets as ws

async def WebsocSPJSConnection():
   uri = "ws://localhost:8989"
   async with  ws.connect(uri) as webs:
      print(webs.recv())

if name == "main": asyncio.run(WebsocSPJSConnection())

I know the server sees the connection attempt and a failure on the SPJS side, however the client just times out:

2022/10/25 12:07:48 cayenn.go:438: TCP Received  GET / HTTP/1.1
Host: localhost:8988
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Key: Q3SDgBW/JHBXDE6ctEdC/g==
Sec-WebSocket-Version: 13
Sec-WebSocket-Extensions: permessage-deflate; client_max_window_bits
User-Agent: Python/3.9 websockets/10.3

  from  [::1]:60677
Checking if from me [::1]<>10.71.193.1412022/10/25 12:07:48 cayenn.go:475: Err unmarshalling TCP inbound message from device. err: <nil>

Wireshark shows: Wireshark Trace

It seems the SPJS is getting an unmarshalled message from my client. However this is a connect command, I send nothing to the SPJS other than a connect command above and . Any ideas what is going on here?

Note The Javascript server in another client seems to connect correctly. And if I make a dummy python server on port 8988 it all works fine.

Melticus
  • 11
  • 2
  • I've had a doh moment. I figured it out. And as with all problems the root of it was embarrassingly trival. I was using the wrong uri. it should be "ws://localhost:8989/ws". As penance I will post my code when I am finished for others to be able to use. – Melticus Oct 25 '22 at 17:43

1 Answers1

0

As promised. Here for my penance is code which connects to TinyG SPJS and opens com4 on SPJS to connect to the TinyG board. Forgive, this is a snippet of a bigger code but is standalone.

The .kv file:

    CNCMainScreenBoxLayout:

<CNCMainScreenBoxLayout>:
    orientation: "horizontal"
    spacing: "20dp"
    BoxLayout:
        orientation: "vertical"
        Label:
            id: Status_Label
            size_hint: 1,0.09
            text: root.StatusText
            #on_update: root.on_Update()

    Button:
        background_color: (0, 0, 0, 0)
        size_hint: .4,1
        on_press: root.on_Click_Emergency_pressed()
        on_release: root.on_Click_Emergency_released()
        Image:
            id: EmergencyStop_Image
            source: "images\EmergencyStop_released.png"
            size: self.parent.size
            background: None
            y: self.parent.y
            x: self.parent.x
            allow_stretch: True

And the python file:

from kivy.uix.widget import Widget
from kivy.app import App
from kivy.lang import Builder
from kivy.properties import StringProperty, ObjectProperty
from kivy.clock import Clock
from kivy.logger import Logger
from kivy.uix.boxlayout import BoxLayout
import websocket
import _thread
import json
from kivy.support import install_twisted_reactor
install_twisted_reactor()

class CNCMainScreenBoxLayout(BoxLayout):
    StatusText = StringProperty("")

    def __init__(self, **kwargs):
        super().__init__(**kwargs)

    def Set_Status(self,displaytxt):
        self.StatusText = displaytxt

    def Get_URI(self):
        return "ws://localhost:8989/ws"

    def on_Click_Emergency_pressed(self):
        self.ids.EmergencyStop_Image.source = "images\EmergencyStop_Pressed.png"
        self.StatusText = "EMERGENCY STOP"

    def on_Click_Emergency_released(self):
        self.ids.EmergencyStop_Image.source = "images\EmergencyStop_Released.png"

class KivyWebSocket(websocket.WebSocketApp):
    def __init__(self, *args, **kwargs):
        super(KivyWebSocket, self).__init__(*args, **kwargs)
        self.logger = Logger
        self.logger.info('WebSocket: logger initialized')

class WebSocketTest(App):
    ws = None
    MainScreen = None
    url = None
    layout = ObjectProperty(None)
    StoredSettingsStore = None

    def __init__(self, **kwargs):
        super(WebSocketTest, self).__init__(**kwargs)
        self.MainScreen = Builder.load_file("CNCViewSO.kv")
        self.layout = CNCMainScreenBoxLayout()
        self.url = "ws://localhost:8989/ws"
        self.ws = KivyWebSocket(self.url,
                           on_message=self.on_ws_message,
                           on_error=self.on_ws_error,
                           on_open=self.on_ws_open,
                           on_close=self.on_ws_close, )

        app = App.get_running_app()
        Clock.schedule_once(app.ws_connection)
        self.logger = Logger
        self.logger.info('App: initiallzed')

    def build(self):
        ##self.layout = CNCMainScreenBoxLayout()
        self.logger.info('Build: initiallzed')
        return self.layout

    def on_ws_message(self, ws, message):
        #self.layout.the_btn.text = message
        decoded = json.loads(message)
        #print(decoded)
        for keys in decoded:
            #print(keys)
            if keys == "Version":
                print("Version is the version of the SPJS\r\n\t" + decoded[keys])
                self.layout.StatusText = "Connected to SPJS Version " + decoded[keys] + " at " + str(self.url)
            elif keys == "Hostname":
                print("Hostname is the hostname of the computer the SPJS is running on\r\n\t" + decoded[keys])
            elif keys == "Commands":
                print("Commands you can send to the server")
                for cmd in decoded[keys]:
                    print("\tcommand --> \t", str(cmd))
                self.ws_send_ComConnect("Com4")
            elif keys == "Cmd":
                print(keys)

            else:
                print("Unhandled key: " + keys)


    def on_ws_error(self, ws, error):
        self.logger.info('WebSocket: [ERROR]  {}'.format(error))

    def ws_connection(self, dt, **kwargs):
        # start a new thread connected to the web socket

        _thread.start_new_thread(self.ws.run_forever, ())

    def ws_send_ComConnect(self,comPort):
        Comstring = "Open " + comPort + " 115200 tinyg"
        self.ws.send(Comstring)

    def on_ws_open(self, ws):
        pass
        """def run(*args):
            for i in range(1, 13):
                time.sleep(1)
                ws.send('version')
            time.sleep(10)
            ws.close()
        _thread.start_new_thread(run, ())"""

    def on_ws_close(self, ws):
        self.layout.StatusText = '### closed ###'

# Press the green button in the gutter to run the script.
if __name__ == '__main__':

    WebSocketTest().run()

To Run you will need to create an images file in ./images for the buttons. Here are the button files:

emergencystop_released.png emergencystop_pressed.png

Melticus
  • 11
  • 2