WebSocket Client

Hi.
I have temperature sensors that have an API using WebSocket protocol (wss).
I imported the library https://pypi.org/project/websocket-client/ to create a WebSocket client.

My script for establishing a connection:

import websocket

ws = websocket.WebSocket()
ws.connect("wss://echo.websocket.org")
ws.send("Hello, Server")
print(ws.recv())

If I use “ws://” connection created, but if I use “wss://” i get error:

Traceback (most recent call last):
  File "<input>", line 4, in <module>
  File "C:\Users\User\.ignition\cache\gwlocalhost_8088\C0\pylib\websocket\_core.py", line 248, in connect
    self.sock, addrs = connect(url, self.sock_opt, proxy_info(**options),
  File "C:\Users\User\.ignition\cache\gwlocalhost_8088\C0\pylib\websocket\_http.py", line 126, in connect
    sock = _ssl_socket(sock, options.sslopt, hostname)
  File "C:\Users\User\.ignition\cache\gwlocalhost_8088\C0\pylib\websocket\_http.py", line 266, in _ssl_socket
    sock = ssl.wrap_socket(sock, **sslopt)
  File "C:\Users\User\.ignition\cache\gwlocalhost_8088\C0\pylib\_socket.py", line 382, in handle_exception
    return method_or_function(*args, **kwargs)
  File "C:\Users\User\.ignition\cache\gwlocalhost_8088\C0\pylib\_socket.py", line 382, in handle_exception
    return method_or_function(*args, **kwargs)
  File "C:\Users\User\.ignition\cache\gwlocalhost_8088\C0\pylib\ssl.py", line 904, in wrap_socket
    return SSLSocket(
  File "C:\Users\User\.ignition\cache\gwlocalhost_8088\C0\pylib\ssl.py", line 575, in __init__
    self.do_handshake()
  File "C:\Users\User\.ignition\cache\gwlocalhost_8088\C0\pylib\ssl.py", line 691, in do_handshake
    self._sock._handle_channel_future(self._handshake_future, "SSL handshake")
  File "C:\Users\User\.ignition\cache\gwlocalhost_8088\C0\pylib\_socket.py", line 384, in handle_exception
    raise _map_exception(jlx)
_socket.SSLError: [Errno 1] certificate verify failed (java.security.cert.CertificateException: certificate verify failed)

Please tell me what I can do to fix this.
Maybe there are some other tools for working with websocket in Ignition.
On the forum I read that you can use the jetty java library, But I never worked with this.
I would be grateful for any help.

1 Like

It’s failing because wss:// (vs ws://) means use SSL/TLS, and it appears whatever server you’re connected to doesn’t have a trustworthy certificate.

Do you know if it’s signed by a real CA or if it’s an internal CA or self-signed cert?

You should be able to tell Ignition to trust it by adding it to the supplemental certs folder and restarting the gateway. https://docs.inductiveautomation.com/display/DOC81/Adding+Security+Certificates+into+Keystores

I dont know if it’s signed by a real CA or if it’s an internal CA or self-signed cert.
But if i execute this script in cmd it works.

Thanks Kevin
I will try to get a certificate of trust, and do as you said. I’ll let you know if everything works out.

If you’re really getting that error for wss://echo.websocket.org and not just your sensor then I’m not really sure why it’s not working because that should be a real cert from Lets Encrypt…

Let us know if adding the certificate makes it work or not either way.

Hi.
I found out that the sensors I have are using Lets Encrypt to generate https certificates.
I getting that error for wss://echo.websocket.org and not just my sensor.

You’re basically on your own re: documentation, but the Java HTTP client available via the system.net.httpClient scripting function has the ability to connect to websockets; it’d be something like:
system.net.httpClient().javaClient.newWebSocketBuilder(), at which point you’ll want to refer to the JDK for documention.

Hi.
Thanks for the answer, Paul.
I figured out the Jython implementation using system.net.httpClient (). JavaClient.newWebSocketBuilder ().
I wrote a script like this:


from java.net.http import HttpClient;
from java.net.http import WebSocket;
from java.net import URI;


class listener(WebSocket.Listener):

	def onOpen(self, websocket):
		#print websocket.getSubprotocol()
		print "Event OnOpen"
		#print type(websocket)
		
		websocket.sendText("Hi", True)
		print "Sended"
		websocket.request(1)

	def onText(self, websocket, data, last):
		print "Event OnText"
		print "Response from server: " + data
		websocket.request(1)
		
client = HttpClient.newHttpClient()

client.newWebSocketBuilder().buildAsync(URI.create("ws://echo.websocket.org"),  listener());

If I execute this script in cmd it works.

But if I execute this script in Ignition Script Console I get an error:

__main__:6: RuntimeWarning: PyTableCode.call caught a Throwable that is not an Exception:
java.lang.VerifyError: (class: org/python/proxies/__main__$listener$13, method: super__onText signature: (Ljava/net/http/WebSocket;Ljava/lang/CharSequence;Z)Ljava/util/concurrent/CompletionStage;) Illegal use of nonvirtual function call
Jython internals might be in a bad state now that can cause deadlocks later on.
See http://bugs.jython.org/issue2536 for details.
Java Traceback:
Traceback (most recent call last):
  File "<input>", line 6, in <module>
java.lang.VerifyError: (class: org/python/proxies/__main__$listener$13, method: super__onText signature: (Ljava/net/http/WebSocket;Ljava/lang/CharSequence;Z)Ljava/util/concurrent/CompletionStage;) Illegal use of nonvirtual function call

	at java.base/java.lang.Class.getDeclaredMethods0(Native Method)

	at java.base/java.lang.Class.privateGetDeclaredMethods(Unknown Source)

	at java.base/java.lang.Class.privateGetPublicMethods(Unknown Source)

	at java.base/java.lang.Class.getMethods(Unknown Source)

	at org.python.core.PyJavaType.init(PyJavaType.java:291)

	at org.python.core.PyType.createType(PyType.java:1573)

	at org.python.core.PyType.addFromClass(PyType.java:1508)

	at org.python.core.PyType.fromClass(PyType.java:1609)

	at org.python.core.PyType.setupProxy(PyType.java:827)

	at org.python.core.PyType.newType(PyType.java:257)

	at org.python.core.PyType.type___new__(PyType.java:202)

	at org.python.core.PyType$exposed___new__.new_impl(Unknown Source)

	at org.python.core.PyType.invokeNew(PyType.java:636)

	at org.python.core.PyType.type___call__(PyType.java:1873)

	at org.python.core.PyType.__call__(PyType.java:1863)

	at org.python.core.PyObject.__call__(PyObject.java:515)

	at org.python.core.Py.makeClass(Py.java:2107)

	at org.python.core.Py.makeClass(Py.java:2068)

	at org.python.core.Py.makeClass(Py.java:2060)

	at org.python.pycode._pyx65.f$0(<input>:24)

	at org.python.pycode._pyx65.call_function(<input>)

	at org.python.core.PyTableCode.call(PyTableCode.java:171)

	at org.python.core.PyCode.call(PyCode.java:18)

	at org.python.core.Py.runCode(Py.java:1614)

	at org.python.core.Py.exec(Py.java:1658)

	at org.python.util.PythonInterpreter.exec(PythonInterpreter.java:276)

	at org.python.util.InteractiveInterpreter.runcode(InteractiveInterpreter.java:131)

	at com.inductiveautomation.ignition.designer.gui.tools.jythonconsole.JythonConsole$ConsoleWorker.doInBackground(JythonConsole.java:605)

	at com.inductiveautomation.ignition.designer.gui.tools.jythonconsole.JythonConsole$ConsoleWorker.doInBackground(JythonConsole.java:593)

	at java.desktop/javax.swing.SwingWorker$1.call(Unknown Source)

	at java.base/java.util.concurrent.FutureTask.run(Unknown Source)

	at java.desktop/javax.swing.SwingWorker.run(Unknown Source)

	at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(Unknown Source)

	at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(Unknown Source)

	at java.base/java.lang.Thread.run(Unknown Source)

java.lang.VerifyError: java.lang.VerifyError: (class: org/python/proxies/__main__$listener$13, method: super__onText signature: (Ljava/net/http/WebSocket;Ljava/lang/CharSequence;Z)Ljava/util/concurrent/CompletionStage;) Illegal use of nonvirtual function call


	at org.python.core.Py.JavaError(Py.java:552)

	at org.python.core.PyTableCode.call(PyTableCode.java:180)

	at org.python.core.PyCode.call(PyCode.java:18)

	at org.python.core.Py.runCode(Py.java:1614)

	at org.python.core.Py.exec(Py.java:1658)

	at org.python.util.PythonInterpreter.exec(PythonInterpreter.java:276)

	at org.python.util.InteractiveInterpreter.runcode(InteractiveInterpreter.java:131)

	at com.inductiveautomation.ignition.designer.gui.tools.jythonconsole.JythonConsole$ConsoleWorker.doInBackground(JythonConsole.java:605)

	at com.inductiveautomation.ignition.designer.gui.tools.jythonconsole.JythonConsole$ConsoleWorker.doInBackground(JythonConsole.java:593)

	at java.desktop/javax.swing.SwingWorker$1.call(Unknown Source)

	at java.base/java.util.concurrent.FutureTask.run(Unknown Source)

	at java.desktop/javax.swing.SwingWorker.run(Unknown Source)

	at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(Unknown Source)

	at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(Unknown Source)

	at java.base/java.lang.Thread.run(Unknown Source)

Caused by: java.lang.VerifyError: (class: org/python/proxies/__main__$listener$13, method: super__onText signature: (Ljava/net/http/WebSocket;Ljava/lang/CharSequence;Z)Ljava/util/concurrent/CompletionStage;) Illegal use of nonvirtual function call

	at java.base/java.lang.Class.getDeclaredMethods0(Native Method)

	at java.base/java.lang.Class.privateGetDeclaredMethods(Unknown Source)

	at java.base/java.lang.Class.privateGetPublicMethods(Unknown Source)

	at java.base/java.lang.Class.getMethods(Unknown Source)

	at org.python.core.PyJavaType.init(PyJavaType.java:291)

	at org.python.core.PyType.createType(PyType.java:1573)

	at org.python.core.PyType.addFromClass(PyType.java:1508)

	at org.python.core.PyType.fromClass(PyType.java:1609)

	at org.python.core.PyType.setupProxy(PyType.java:827)

	at org.python.core.PyType.newType(PyType.java:257)

	at org.python.core.PyType.type___new__(PyType.java:202)

	at org.python.core.PyType$exposed___new__.new_impl(Unknown Source)

	at org.python.core.PyType.invokeNew(PyType.java:636)

	at org.python.core.PyType.type___call__(PyType.java:1873)

	at org.python.core.PyType.__call__(PyType.java:1863)

	at org.python.core.PyObject.__call__(PyObject.java:515)

	at org.python.core.Py.makeClass(Py.java:2107)

	at org.python.core.Py.makeClass(Py.java:2068)

	at org.python.core.Py.makeClass(Py.java:2060)

	at org.python.pycode._pyx65.f$0(<input>:24)

	at org.python.pycode._pyx65.call_function(<input>)

	at org.python.core.PyTableCode.call(PyTableCode.java:171)

	... 13 more

Traceback (most recent call last):
  File "<input>", line 6, in <module>
java.lang.VerifyError: (class: org/python/proxies/__main__$listener$13, method: super__onText signature: (Ljava/net/http/WebSocket;Ljava/lang/CharSequence;Z)Ljava/util/concurrent/CompletionStage;) Illegal use of nonvirtual function call

	at java.base/java.lang.Class.getDeclaredMethods0(Native Method)

	at java.base/java.lang.Class.privateGetDeclaredMethods(Unknown Source)

	at java.base/java.lang.Class.privateGetPublicMethods(Unknown Source)

	at java.base/java.lang.Class.getMethods(Unknown Source)

	at org.python.core.PyJavaType.init(PyJavaType.java:291)

	at org.python.core.PyType.createType(PyType.java:1573)

	at org.python.core.PyType.addFromClass(PyType.java:1508)

	at org.python.core.PyType.fromClass(PyType.java:1609)

	at org.python.core.PyType.setupProxy(PyType.java:827)

	at org.python.core.PyType.newType(PyType.java:257)

	at org.python.core.PyType.type___new__(PyType.java:202)

	at org.python.core.PyType$exposed___new__.new_impl(Unknown Source)

	at org.python.core.PyType.invokeNew(PyType.java:636)

	at org.python.core.PyType.type___call__(PyType.java:1873)

	at org.python.core.PyType.__call__(PyType.java:1863)

	at org.python.core.PyObject.__call__(PyObject.java:515)

	at org.python.core.Py.makeClass(Py.java:2107)

	at org.python.core.Py.makeClass(Py.java:2068)

	at org.python.core.Py.makeClass(Py.java:2060)

	at org.python.pycode._pyx65.f$0(<input>:24)

	at org.python.pycode._pyx65.call_function(<input>)

	at org.python.core.PyTableCode.call(PyTableCode.java:171)

	at org.python.core.PyCode.call(PyCode.java:18)

	at org.python.core.Py.runCode(Py.java:1614)

	at org.python.core.Py.exec(Py.java:1658)

	at org.python.util.PythonInterpreter.exec(PythonInterpreter.java:276)

	at org.python.util.InteractiveInterpreter.runcode(InteractiveInterpreter.java:131)

	at com.inductiveautomation.ignition.designer.gui.tools.jythonconsole.JythonConsole$ConsoleWorker.doInBackground(JythonConsole.java:605)

	at com.inductiveautomation.ignition.designer.gui.tools.jythonconsole.JythonConsole$ConsoleWorker.doInBackground(JythonConsole.java:593)

	at java.desktop/javax.swing.SwingWorker$1.call(Unknown Source)

	at java.base/java.util.concurrent.FutureTask.run(Unknown Source)

	at java.desktop/javax.swing.SwingWorker.run(Unknown Source)

	at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(Unknown Source)

	at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(Unknown Source)

	at java.base/java.lang.Thread.run(Unknown Source)

java.lang.VerifyError: java.lang.VerifyError: (class: org/python/proxies/__main__$listener$13, method: super__onText signature: (Ljava/net/http/WebSocket;Ljava/lang/CharSequence;Z)Ljava/util/concurrent/CompletionStage;) Illegal use of nonvirtual function call

1 Like

At the end of your onText implementation, Jython is trying to automatically call the default implementation of onText from the interface…but Jython predates default/virtual methods on Java interfaces, so it appears to be causing some confusion in Jython’s internals. You could try manually inserting a call to the interface method? return WebSocket.Listener.onText(self, websocket, data, last) at the end of your onText call? Or try manually return None at the end of your impl, to try to prevent the default impl from being called.

Hi Paul.
I don’t quite understand what I have to do.
If I don’t define the onText method, the error is thrown as well.
Could you explain to me in more detail what I can try to do?

Try manually inserting a return None at the end of your onText implementation. It looks like Jython is trying to automatically call the default method on the Websocket.Listener interface (default methods on interfaces is a Java 8 change), and getting confused (probably because Jython predates Java 8 by a number of years).

IE:

class listener(WebSocket.Listener):
	def onOpen(self, websocket):
		#print websocket.getSubprotocol()
		print "Event OnOpen"
		#print type(websocket)
		
		websocket.sendText("Hi", True)
		print "Sended"
		websocket.request(1)

	def onText(self, websocket, data, last):
		print "Event OnText"
		print "Response from server: " + data
		websocket.request(1)
		return None

Hi.
Thanks, Paul.
OU.
I’ve tried it, but it doesn’t work. (the same error occurs)
The error occurs during initialization: class listener (WebSocket.Listener):
and if I do not declare the onText () method, the error will remain

It’s a Jython bug. https://bugs.jython.org/issue2403
Unfortunately, I don’t think there’s any workaround you can do as an end user; we need to update Ignition’s Jython to 2.7.2. I’ve bumped the internal ticket for that effort in priority.

Clear.
It’s a pity.
Thanks for the help, Paul.
We will wait for the update.)

@jacob curious if you ever got this working.

Thanks,

Nick

The linked bug with Jython should be fixed in 8.1.8, where we updated our bundled Jython to 2.7.2. The rest of Jacob’s code looked sound, at least at a first glance.

1 Like

Cool, I plan on giving it a try today. Will share the results.

Cheers,

Nick

1 Like

I finally got a chance to test this today but I'm getting stuck, the output from this:

client.newWebSocketBuilder().buildAsync(URI.create(baseUrl+"ws/v2/sql/execute"),  listener())

Is this

jdk.internal.net.http.common.MinimalFuture@6a28325[Completed exceptionally: java.lang.IllegalArgumentException: invalid URI scheme: https]

Of course not expecting anyone to solve this for me, just posting here in case anyone has experience they can share, or in case this helps someone down the line.

We are using and https url.

Thanks,

Nick

Try using “wss://“ instead of “https://“.

That worked to fix that error. I have tested this API with the google web socket tested so I know its good.

At the end of the following code I get this message, I feel like I need to have something after the web socket builder that is currently not there. At present I never see the onOpen method executed.

jdk.internal.net.http.common.MinimalFuture@59ff0c80[Not completed] (id=939)

# Login
r = c.post(baseUrl + "login", data=params)
authToken = r.text

queryParams = {
	"token": authToken,
	"stats": 5000,
	"sql": query,
	"live": False
}

class listener(WebSocket.Listener):
	def onOpen(self, websocket):
		print("Event OnOpen")
		websocket.sendText(queryParams, True)
		print "Sent"
		websocket.request(1)
	
	def onText(self, websocket, data, last):
		print "Event OnText"
		print "Response from server: " + data
		websocket.request(1)
		return None
		
uri = URI.create(baseUrl.replace("https", "wss") + "ws/v2/sql/execute") 		
client = HttpClient.newHttpClient()		
client.newWebSocketBuilder().buildAsync(uri,  listener())

Thanks,

Nick

You are discarding the return value from .buildAsync(). Don’t do that. You need it to .get() the finished websocket object.