Module code.core.webSocketClient
Author: Jonathan Rotter
This module is required for motor2020 and surface2020 and thus needs to be on both motor pi and surface pi. It handles the websocket stuff for both those programs.
How to use: start( 'motor' or 'surface', func ) if motor, func should take one string arg if surface, func should return a string func is a reference to a function, so NO parentheses next to it don't call it, pass the reference to the function itself
Required 3rd-party libraries:
autobahn
twisted
Expand source code
#!/usr/bin/env python3.4
'''Author: Jonathan Rotter
This module is required for motor2020 and surface2020
and thus needs to be on both motor pi and surface pi.
It handles the websocket stuff for both those programs.
How to use:
start( 'motor' or 'surface', func )
if motor, func should take one string arg
if surface, func should return a string
func is a reference to a function, so NO parentheses next to it
don't call it, pass the reference to the function itself
Required 3rd-party libraries:
`autobahn`
`twisted`
'''
# websocket stuff
from autobahn.twisted.websocket import \
WebSocketClientProtocol, WebSocketClientFactory
import sys
# asynchronous stuff
from twisted.python import log
from twisted.internet import task, reactor
IP = '127.0.0.1'
'''ip is localhost, does not need to be changed'''
PORT = 8008
'''needs the be the same here as in server2020'''
# frequency = 1/timeout
TIMEOUT = 0.1
'''time between surface2020 being run in seconds'''
class ClientProtocol(WebSocketClientProtocol):
'''
Determines how the client will communicate with the server
'''
def onConnect(self, response):
'''
Called by the client factory when
connecting
'''
print("Server connected: {0}".format(response.peer))
# remember this connection
self.factory.register(self)
def onConnecting(self, transport_details):
'''
Called by the client factory when
connecting
'''
print("Connecting; transport details: {}".format(transport_details))
return None
def onOpen(self):
'''
Called by the client factory when
the connection is open
'''
print("WebSocket connection open.")
def onMessage(self, payload, isBinary):
'''
Called by the client factory when
receiving a message
Args:
payload (bytes): A bytes-like object
isBinary (bool): Almost always true
'''
# validate that the type is motor2020 or miniROV
# surface2020 shouldn't receive data
if self.factory.clientType in ['motor', 'miniROV']:
# received instructions!
# decode bytes to string
txt = payload.decode()
# call the given function, passing the
# payload/message as an argument
self.factory.func(txt)
else:
# fail fast for debugging purposes
raise ValueError('Only motor pi / Mini ROV should receive data')
def onClose(self, wasClean, code, reason):
'''
Called by the client factory when closing
'''
print("WebSocket connection closed: {0}".format(reason))
# remove the remembered connection
self.factory.unregister(self)
class ClientFactory(WebSocketClientFactory):
'''
Determines how connections deal with each other
'''
def __init__(self, url, clientType, func):
'''
Initializes the class
Args:
url (str): The url in the format "ws://127.0.0.1:8008"
clientType (str): Example: "/motor"
func (function): The handler function
'''
WebSocketClientFactory.__init__(self, url)
self.connections = []
self.clientType = clientType
self.func = func
self.connectionRefusedCount = 0
def register(self, client):
'''
Called in `ClientProtocol.onConnect`
Remember this connection
'''
if client not in self.connections:
self.connections.append(client)
def unregister(self, client):
'''
Called in `ClientProtocol.onClose`
Forget this connection
'''
if client in self.connections:
self.connections.remove(client)
def broadcast(self):
'''
Send a message to all other connections
Only for surface pi
'''
if not len(self.connections):
return # no connections
if self.clientType != 'surface':
raise ValueError('Only surface py should broadcast data')
# get data by calling the given function
txt = self.func()
for c in self.connections:
# send that data to the connected clients
c.sendMessage(txt.encode())
def clientConnectionFailed(self, connector, error):
'''
Called automatically when a connection
fails. Tells the reactor (Twisted's event manager)
to try to reconnect in 3 seconds
'''
self.connectionRefusedCount += 1
for _ in range(2):
# clears two lines in the console
print('\r\033[K\033[A', end='')
print(self.connectionRefusedCount, error.getErrorMessage())
# try to connect again in 3 seconds
reactor.callLater(3, connectTCP, self)
def connectTCP(factory):
'''Tries to connect to the given IP and Port number'''
global IP, PORT
# connect to the ip and port
reactor.connectTCP(IP, PORT, factory)
def start(clientType, func, ip=None):
'''
Called by motor2020 or surface2020
to use this module
Args:
clientType (str): Specifies whether it is motor pi or surface pi
func (function): The handler for messages
ip (str, optional): The ip address
'''
global IP
if ip:
# set the default ip (localhost) to the given ip if it is specified
IP = ip
# display debug info on stdout
log.startLogging(sys.stdout)
# new factory object
factory = ClientFactory(
u'ws://{}:{}/{}'.format(IP, PORT, clientType),
clientType,
func
)
factory.protocol = ClientProtocol
# reactor.connectTCP(IP, PORT, factory)
connectTCP(factory)
if clientType == 'surface':
# if this us type surface, start a loop where every TIMEOUT number of seconds
# it runs the given function to get the data and send it off
l = task.LoopingCall(factory.broadcast) # only for surface
l.start(TIMEOUT)
# start the code
reactor.run()
if __name__ == '__main__':
# not to be run as the main module
raise Exception('This code is only to be imported!!!')
Global variables
var IP-
ip is localhost, does not need to be changed
var PORT-
needs the be the same here as in server2020
var TIMEOUT-
time between surface2020 being run in seconds
Functions
def connectTCP(factory)-
Tries to connect to the given IP and Port number
Expand source code
def connectTCP(factory): '''Tries to connect to the given IP and Port number''' global IP, PORT # connect to the ip and port reactor.connectTCP(IP, PORT, factory) def start(clientType, func, ip=None)-
Called by motor2020 or surface2020 to use this module
Args
clientType:str- Specifies whether it is motor pi or surface pi
func:function- The handler for messages
ip:str, optional- The ip address
Expand source code
def start(clientType, func, ip=None): ''' Called by motor2020 or surface2020 to use this module Args: clientType (str): Specifies whether it is motor pi or surface pi func (function): The handler for messages ip (str, optional): The ip address ''' global IP if ip: # set the default ip (localhost) to the given ip if it is specified IP = ip # display debug info on stdout log.startLogging(sys.stdout) # new factory object factory = ClientFactory( u'ws://{}:{}/{}'.format(IP, PORT, clientType), clientType, func ) factory.protocol = ClientProtocol # reactor.connectTCP(IP, PORT, factory) connectTCP(factory) if clientType == 'surface': # if this us type surface, start a loop where every TIMEOUT number of seconds # it runs the given function to get the data and send it off l = task.LoopingCall(factory.broadcast) # only for surface l.start(TIMEOUT) # start the code reactor.run()
Classes
class ClientFactory (url, clientType, func)-
Determines how connections deal with each other
Initializes the class
Args
url:str- The url in the format "ws://127.0.0.1:8008"
clientType:str- Example: "/motor"
func:function- The handler function
Expand source code
class ClientFactory(WebSocketClientFactory): ''' Determines how connections deal with each other ''' def __init__(self, url, clientType, func): ''' Initializes the class Args: url (str): The url in the format "ws://127.0.0.1:8008" clientType (str): Example: "/motor" func (function): The handler function ''' WebSocketClientFactory.__init__(self, url) self.connections = [] self.clientType = clientType self.func = func self.connectionRefusedCount = 0 def register(self, client): ''' Called in `ClientProtocol.onConnect` Remember this connection ''' if client not in self.connections: self.connections.append(client) def unregister(self, client): ''' Called in `ClientProtocol.onClose` Forget this connection ''' if client in self.connections: self.connections.remove(client) def broadcast(self): ''' Send a message to all other connections Only for surface pi ''' if not len(self.connections): return # no connections if self.clientType != 'surface': raise ValueError('Only surface py should broadcast data') # get data by calling the given function txt = self.func() for c in self.connections: # send that data to the connected clients c.sendMessage(txt.encode()) def clientConnectionFailed(self, connector, error): ''' Called automatically when a connection fails. Tells the reactor (Twisted's event manager) to try to reconnect in 3 seconds ''' self.connectionRefusedCount += 1 for _ in range(2): # clears two lines in the console print('\r\033[K\033[A', end='') print(self.connectionRefusedCount, error.getErrorMessage()) # try to connect again in 3 seconds reactor.callLater(3, connectTCP, self)Ancestors
- autobahn.twisted.websocket.WebSocketClientFactory
- autobahn.twisted.websocket.WebSocketAdapterFactory
- autobahn.websocket.protocol.WebSocketClientFactory
- autobahn.websocket.protocol.WebSocketFactory
- twisted.internet.protocol.ClientFactory
- twisted.internet.protocol.Factory
Methods
def broadcast(self)-
Send a message to all other connections Only for surface pi
Expand source code
def broadcast(self): ''' Send a message to all other connections Only for surface pi ''' if not len(self.connections): return # no connections if self.clientType != 'surface': raise ValueError('Only surface py should broadcast data') # get data by calling the given function txt = self.func() for c in self.connections: # send that data to the connected clients c.sendMessage(txt.encode()) def clientConnectionFailed(self, connector, error)-
Called automatically when a connection fails. Tells the reactor (Twisted's event manager) to try to reconnect in 3 seconds
Expand source code
def clientConnectionFailed(self, connector, error): ''' Called automatically when a connection fails. Tells the reactor (Twisted's event manager) to try to reconnect in 3 seconds ''' self.connectionRefusedCount += 1 for _ in range(2): # clears two lines in the console print('\r\033[K\033[A', end='') print(self.connectionRefusedCount, error.getErrorMessage()) # try to connect again in 3 seconds reactor.callLater(3, connectTCP, self) def register(self, client)-
Called in
ClientProtocol.onConnect()Remember this connectionExpand source code
def register(self, client): ''' Called in `ClientProtocol.onConnect` Remember this connection ''' if client not in self.connections: self.connections.append(client) def unregister(self, client)-
Called in
ClientProtocol.onClose()Forget this connectionExpand source code
def unregister(self, client): ''' Called in `ClientProtocol.onClose` Forget this connection ''' if client in self.connections: self.connections.remove(client)
class ClientProtocol-
Determines how the client will communicate with the server
Expand source code
class ClientProtocol(WebSocketClientProtocol): ''' Determines how the client will communicate with the server ''' def onConnect(self, response): ''' Called by the client factory when connecting ''' print("Server connected: {0}".format(response.peer)) # remember this connection self.factory.register(self) def onConnecting(self, transport_details): ''' Called by the client factory when connecting ''' print("Connecting; transport details: {}".format(transport_details)) return None def onOpen(self): ''' Called by the client factory when the connection is open ''' print("WebSocket connection open.") def onMessage(self, payload, isBinary): ''' Called by the client factory when receiving a message Args: payload (bytes): A bytes-like object isBinary (bool): Almost always true ''' # validate that the type is motor2020 or miniROV # surface2020 shouldn't receive data if self.factory.clientType in ['motor', 'miniROV']: # received instructions! # decode bytes to string txt = payload.decode() # call the given function, passing the # payload/message as an argument self.factory.func(txt) else: # fail fast for debugging purposes raise ValueError('Only motor pi / Mini ROV should receive data') def onClose(self, wasClean, code, reason): ''' Called by the client factory when closing ''' print("WebSocket connection closed: {0}".format(reason)) # remove the remembered connection self.factory.unregister(self)Ancestors
- autobahn.twisted.websocket.WebSocketClientProtocol
- autobahn.twisted.websocket.WebSocketAdapterProtocol
- twisted.internet.protocol.Protocol
- twisted.internet.protocol.BaseProtocol
- autobahn.websocket.protocol.WebSocketClientProtocol
- autobahn.websocket.protocol.WebSocketProtocol
- autobahn.util.ObservableMixin
Methods
def onClose(self, wasClean, code, reason)-
Called by the client factory when closing
Expand source code
def onClose(self, wasClean, code, reason): ''' Called by the client factory when closing ''' print("WebSocket connection closed: {0}".format(reason)) # remove the remembered connection self.factory.unregister(self) def onConnect(self, response)-
Called by the client factory when connecting
Expand source code
def onConnect(self, response): ''' Called by the client factory when connecting ''' print("Server connected: {0}".format(response.peer)) # remember this connection self.factory.register(self) def onConnecting(self, transport_details)-
Called by the client factory when connecting
Expand source code
def onConnecting(self, transport_details): ''' Called by the client factory when connecting ''' print("Connecting; transport details: {}".format(transport_details)) return None def onMessage(self, payload, isBinary)-
Called by the client factory when receiving a message
Args
payload:bytes- A bytes-like object
isBinary:bool- Almost always true
Expand source code
def onMessage(self, payload, isBinary): ''' Called by the client factory when receiving a message Args: payload (bytes): A bytes-like object isBinary (bool): Almost always true ''' # validate that the type is motor2020 or miniROV # surface2020 shouldn't receive data if self.factory.clientType in ['motor', 'miniROV']: # received instructions! # decode bytes to string txt = payload.decode() # call the given function, passing the # payload/message as an argument self.factory.func(txt) else: # fail fast for debugging purposes raise ValueError('Only motor pi / Mini ROV should receive data') def onOpen(self)-
Called by the client factory when the connection is open
Expand source code
def onOpen(self): ''' Called by the client factory when the connection is open ''' print("WebSocket connection open.")