skaarj1989/mWebSockets

Connection between mWebSockets server and python client using WSS

Perfectmistake opened this issue · 15 comments

I am currently trying to connect my mwebsocket server running on an Arduino Uno (ATmega328P) with a W5500 ethernet shield to a asyncio websockets client in python. The code for the Arduino server is shown below:

#include <WebSocketServer.h>
using namespace net;

// Enter a MAC address and IP address for your controller below.
// The IP address will be dependent on your local network:
byte mac[]{0xA8, 0x61, 0x0A, 0xAE, 0x69, 0x13};
IPAddress ip(198, 162, 1, 177);
IPAddress gateway(0,0,0,0);
IPAddress DNSserver(0,0,0,0);
IPAddress subnet(255,255,255,0);

constexpr uint16_t port = 80;
WebSocketServer wss{port};

void setup() {
  // You can use Ethernet.init(pin) to configure the CS pin
  
  Ethernet.init(10);  // Most Arduino shields
  //Ethernet.init(5);   // MKR ETH shield
  //Ethernet.init(0);   // Teensy 2.0
  //Ethernet.init(20);  // Teensy++ 2.0
  //Ethernet.init(15);  // ESP8266 with Adafruit Featherwing Ethernet
  //Ethernet.init(33);  // ESP32 with Adafruit Featherwing Ethernet

  // Open serial communications and wait for port to open:
  Serial.begin(9600);
  while (!Serial) {
    ; // wait for serial port to connect. Needed for native USB port only
  }
  Serial.println("Ethernet WebServer Example");

  // start the Ethernet connection and the server:
  Ethernet.begin(mac,ip,DNSserver,gateway,subnet);

  // Check for Ethernet hardware present
  if (Ethernet.hardwareStatus() == EthernetNoHardware) {
    Serial.println("Ethernet shield was not found.  Sorry, can't run without hardware. :(");
    while (true) {
      delay(1); // do nothing, no point running without Ethernet hardware
    }
  }
  if (Ethernet.linkStatus() == LinkOFF) {
    Serial.println("Ethernet cable is not connected.");
  }

  // start the server
  Serial.print("server is at ");
  Serial.println(Ethernet.localIP());

  wss.onConnection([](WebSocket & ws) {
    const auto protocol = ws.getProtocol(); 
    if (protocol) {
      Serial.print(F("Client protocol: "));
      Serial.println(protocol);
    }

    ws.onMessage([](WebSocket & ws, const WebSocket::DataType dataType,
    const char *message, uint16_t length) {
      switch (dataType) {
        case WebSocket::DataType::TEXT:
          Serial.print(F("Received: "));
          Serial.println(message);
          break;
        case WebSocket::DataType::BINARY:
          Serial.println(F("Received binary data"));
          break;
      }

      ws.send(dataType, message, length);
    });

    ws.onClose([](WebSocket &, const WebSocket::CloseCode, const char *,
    uint16_t) {
      Serial.println(F("Disconnected"));
    });

    Serial.print(F("New client: "));
    Serial.println(ws.getRemoteIP());

    const char message[] {"Hello from Arduino server!"};
    ws.send(WebSocket::DataType::TEXT, message, strlen(message));
  });

  wss.begin();
  Serial.println(Ethernet.localIP());
}

void loop() {
  wss.listen();
}

And the corresponding python code is shown below:

import asyncio
import websockets

async def test():
    async with websockets.connect( "wss://198.162.1.177:80/") as websocket:
        await websocket.send(str(1.001))
        response = await websocket.recv()
        print(response)
        
 
asyncio.get_event_loop().run_until_complete(test()) # run until test() is finished

My aim is to just receive the message "Hello from Arduino server!" every time the client queries the server. Currently, I have an SSL error that keeps appearing whenever I run the python script:

Traceback (most recent call last):
  File "websocketbasictest.py", line 11, in <module>
    asyncio.get_event_loop().run_until_complete(test()) # run until test() is finished
  File "/usr/lib64/python3.6/asyncio/base_events.py", line 488, in run_until_complete
    return future.result()
  File "websocketbasictest.py", line 5, in test
    async with websockets.connect( "wss://198.162.1.177:80/") as websocket:
  File "/usr/lib64/python3.6/site-packages/websockets/legacy/client.py", line 604, in __aenter__
    return await self
  File "/usr/lib64/python3.6/site-packages/websockets/legacy/client.py", line 622, in __await_impl__
    transport, protocol = await self._create_connection()
  File "/usr/lib64/python3.6/asyncio/base_events.py", line 824, in create_connection
    sock, protocol_factory, ssl, server_hostname)
  File "/usr/lib64/python3.6/asyncio/base_events.py", line 850, in _create_connection_transport
    yield from waiter
  File "/usr/lib64/python3.6/asyncio/sslproto.py", line 505, in data_received
    ssldata, appdata = self._sslpipe.feed_ssldata(data)
  File "/usr/lib64/python3.6/asyncio/sslproto.py", line 201, in feed_ssldata
    self._sslobj.do_handshake()
  File "/usr/lib64/python3.6/ssl.py", line 689, in do_handshake
    self._sslobj.do_handshake()
ssl.SSLError: [SSL: WRONG_VERSION_NUMBER] wrong version number (_ssl.c:852)

Is there a mismatch between the communications protocol here? Is there something missing that would allow this connection to take place?

Sorry, you can't create secure WebSocket server with Arduino Uno.
use ws:// instead of wss://

Ok, using ws://, I now obtain a different error message, it seems like the handshake protocol cannot be established:

Traceback (most recent call last):
  File "websocketbasictest.py", line 11, in <module>
    asyncio.get_event_loop().run_until_complete(test()) # run until
test() is finished
  File "/usr/lib64/python3.6/asyncio/base_events.py", line 488, in
run_until_complete
    return future.result()
  File "websocketbasictest.py", line 5, in test
    async with websockets.connect( "ws://198.162.1.177:80/") as websocket:
  File "/usr/lib64/python3.6/site-packages/websockets/legacy/client.py",
line 604, in __aenter__
    return await self
  File "/usr/lib64/python3.6/site-packages/websockets/legacy/client.py",
line 634, in __await_impl__
    extra_headers=protocol.extra_headers,
  File "/usr/lib64/python3.6/site-packages/websockets/legacy/client.py",
line 397, in handshake
    response_headers, available_subprotocols
  File "/usr/lib64/python3.6/site-packages/websockets/legacy/client.py",
line 302, in process_subprotocol
    raise InvalidHandshake("no subprotocols supported")
websockets.exceptions.InvalidHandshake: no subprotocols supported

Does the WebSocket Server include a header or something similar that must be taken into account when creating a python client?

Show the debug output of your server

How do I do that?

In config.h, uncomment this:

//#define _DEBUG
//#define _DUMP_HANDSHAKE
//#define _DUMP_FRAME_DATA
//#define _DUMP_HEADER

Then reupload the sketch, try to connect your python client to the Arduino server and show the Serial Monitor output.

Here is what I get in the Serial Monitor:

11:22:16.017 -> [Line #0] GET / HTTP/1.1
11:22:16.050 -> [Line #1] Host: 198.162.1.177
11:22:16.084 -> [Line #2] Upgrade: websocket
11:22:16.117 -> [Line #3] Connection: Upgrade
11:22:16.117 -> [Line #4] Sec-WebSocket-Key: 2psSNoLZdaLVuWeXU117vw==
11:22:16.183 -> [Line #5] Sec-WebSocket-Version: 13
11:22:16.216 -> [Line #6] Sec-WebSocket-Extensions: permessage-deflate;
client_max_window_bits
11:22:16.316 -> [Line #7] User-Agent: Python/3.6 websockets/9.0.1
11:22:16.349 -> [Line #8]
11:22:16.382 -> New client: 198.162.1.21
11:22:16.382 -> TX FRAME : OPCODE=1, FIN=True, RSV=0, PAYLOAD-LEN=26,
MASK=None
11:22:16.449 -> Hello from Arduino server!
11:22:16.482 -> TX BYTES = 28

Your client has been accepted and should receive: Hello from Arduino server

InvalidHandshake("no subprotocols supported")
This is weird because you did not request any subprotocol.

Well from the terminal output posted earlier, its clear that this message is not printing to the terminal, as expected. Do you have any idea why this error could be appearing?

Is it possible for your python client to print a response handshake from a server?
I have literally no experience in python, but found this

import logging
logger = logging.getLogger('websockets')
logger.setLevel(logging.INFO)
logger.addHandler(logging.StreamHandler())

After getting the full log from the client: I get this:

client - state = CONNECTING
client - event = connection_made(<_SelectorSocketTransport fd=6
read=idle write=<idle, bufsize=0>>)
client > GET / HTTP/1.1
client > Headers([('Host', '198.162.1.177'), ('Upgrade', 'websocket'),
('Connection', 'Upgrade'), ('Sec-WebSocket-Key',
'tYLsa0ez8lI7yz0GS6ApyQ=='), ('Sec-WebSocket-Version', '13'),
('Sec-WebSocket-Extensions', 'permessage-deflate;
client_max_window_bits'), ('User-Agent', 'Python/3.6 websockets/9.0.1')])
client - event = data_received(<2 bytes>)
client - event = data_received(<2 bytes>)
client - event = data_received(<1 bytes>)
client - event = data_received(<1 bytes>)
client - event = data_received(<1 bytes>)
client - event = data_received(<3 bytes>)
client - event = data_received(<1 bytes>)
client - event = data_received(<1 bytes>)
client - event = data_received(<1 bytes>)
client - event = data_received(<2 bytes>)
client - event = data_received(<1 bytes>)
client - event = data_received(<1 bytes>)
client - event = data_received(<1 bytes>)
client - event = data_received(<1 bytes>)
client - event = data_received(<1 bytes>)
client - event = data_received(<3 bytes>)
client - event = data_received(<1 bytes>)
client - event = data_received(<1 bytes>)
client - event = data_received(<1 bytes>)
client - event = data_received(<1 bytes>)
client - event = data_received(<1 bytes>)
client - event = data_received(<2 bytes>)
client - event = data_received(<1 bytes>)
client - event = data_received(<1 bytes>)
client - event = data_received(<2 bytes>)
client - event = data_received(<3 bytes>)
client - event = data_received(<1 bytes>)
client - event = data_received(<1 bytes>)
client - event = data_received(<1 bytes>)
client - event = data_received(<1 bytes>)
client - event = data_received(<2 bytes>)
client - event = data_received(<1 bytes>)
client - event = data_received(<1 bytes>)
client - event = data_received(<2 bytes>)
client - event = data_received(<1 bytes>)
client - event = data_received(<1 bytes>)
client - event = data_received(<2 bytes>)
client - event = data_received(<1 bytes>)
client - event = data_received(<1 bytes>)
client - event = data_received(<1 bytes>)
client - event = data_received(<1 bytes>)
client - event = data_received(<1 bytes>)
client - event = data_received(<1 bytes>)
client - event = data_received(<2 bytes>)
client - event = data_received(<3 bytes>)
client - event = data_received(<1 bytes>)
client - event = data_received(<2 bytes>)
client - event = data_received(<1 bytes>)
client - event = data_received(<1 bytes>)
client - event = data_received(<1 bytes>)
client - event = data_received(<1 bytes>)
client - event = data_received(<1 bytes>)
client - event = data_received(<1 bytes>)
client - event = data_received(<3 bytes>)
client - event = data_received(<1 bytes>)
client - event = data_received(<1 bytes>)
client - event = data_received(<1 bytes>)
client - event = data_received(<1 bytes>)
client - event = data_received(<3 bytes>)
client - event = data_received(<1 bytes>)
client - event = data_received(<1 bytes>)
client - event = data_received(<1 bytes>)
client - event = data_received(<1 bytes>)
client - event = data_received(<3 bytes>)
client - event = data_received(<1 bytes>)
client - event = data_received(<1 bytes>)
client - event = data_received(<1 bytes>)
client - event = data_received(<1 bytes>)
client - event = data_received(<1 bytes>)
client - event = data_received(<1 bytes>)
client - event = data_received(<2 bytes>)
client - event = data_received(<1 bytes>)
client - event = data_received(<1 bytes>)
client - event = data_received(<2 bytes>)
client - event = data_received(<2 bytes>)
client - event = data_received(<50 bytes>)
client - event = data_received(<2 bytes>)
client - event = data_received(<26 bytes>)
client - event = data_received(<2 bytes>)
client < HTTP/1.1 101 Switching Protocols
client < Headers([('X-Powered-By', 'mWebSockets'), ('Upgrade',
'websocket'), ('Connection', 'Upgrade'), ('Sec-WebSocket-Accept',
'qoy9WDfSV+a85R93WXPXUe40LWk='), ('Sec-WebSocket-Protocol', '')])
client ! failing CONNECTING WebSocket connection with code 1006
client x half-closing TCP connection
client - event = data_received(<2 bytes>)
client - event = data_received(<26 bytes>)
client - event = eof_received()
client - event = connection_lost(None)
client - state = CLOSED
client x code = 1006, reason = [no reason]

Ok, I know what's wrong. For quick workaround, change this conditional to:

protocol[0] != '\0'

Fantastic!! this now works as expected! One small thing before I close this issue, how would I change the message sent from being text to being data (ie a floating point number). I have tried this:

    const char message[] {0.1};
    ws.send(WebSocket::DataType::BINARY, message, strlen(message));

But I only get:

b' '

in the terminal, whereas I would like 0.1 to appear.

const char message[]{"0.1"};

Well what about the case where I want to send some value that has been defined previously? for example:


    float value;
    value = 0.1;
    const char message[] {value};
    ws.send(WebSocket::DataType::BINARY, message, strlen(message));

This is out of scope of this issue.
Use sprintf, and read this to save yourself from pain.