118 lines
4.6 KiB
Python
118 lines
4.6 KiB
Python
|
import json
|
||
|
import logging
|
||
|
import pprint
|
||
|
import time
|
||
|
import urllib.parse
|
||
|
import urllib3
|
||
|
import websocket
|
||
|
|
||
|
from threading import Thread
|
||
|
|
||
|
from odoo.addons.hw_drivers import main
|
||
|
from odoo.addons.hw_drivers.tools import helpers
|
||
|
|
||
|
_logger = logging.getLogger(__name__)
|
||
|
websocket.enableTrace(True, level=logging.getLevelName(_logger.getEffectiveLevel()))
|
||
|
|
||
|
def send_to_controller(device_type, params):
|
||
|
"""
|
||
|
Confirm the operation's completion by sending a response back to the Odoo server
|
||
|
"""
|
||
|
routes = {
|
||
|
"printer": "/iot/printer/status",
|
||
|
}
|
||
|
params['iot_mac'] = helpers.get_mac_address()
|
||
|
server_url = helpers.get_odoo_server_url() + routes[device_type]
|
||
|
try:
|
||
|
urllib3.disable_warnings()
|
||
|
http = urllib3.PoolManager(cert_reqs='CERT_NONE')
|
||
|
http.request(
|
||
|
'POST',
|
||
|
server_url,
|
||
|
body=json.dumps({'params': params}).encode('utf8'),
|
||
|
headers={
|
||
|
'Content-type': 'application/json',
|
||
|
'Accept': 'text/plain',
|
||
|
},
|
||
|
)
|
||
|
except Exception:
|
||
|
_logger.exception('Could not reach confirmation status URL: %s', server_url)
|
||
|
|
||
|
|
||
|
def on_message(ws, messages):
|
||
|
"""
|
||
|
Synchronously handle messages received by the websocket.
|
||
|
"""
|
||
|
messages = json.loads(messages)
|
||
|
_logger.debug("websocket received a message: %s", pprint.pformat(messages))
|
||
|
iot_mac = helpers.get_mac_address()
|
||
|
for message in messages:
|
||
|
message_type = message['message']['type']
|
||
|
if message_type == 'iot_action':
|
||
|
payload = message['message']['payload']
|
||
|
if iot_mac in payload['iotDevice']['iotIdentifiers']:
|
||
|
for device in payload['iotDevice']['identifiers']:
|
||
|
device_identifier = device['identifier']
|
||
|
if device_identifier in main.iot_devices:
|
||
|
start_operation_time = time.perf_counter()
|
||
|
_logger.debug("device '%s' action started with: %s", device_identifier, pprint.pformat(payload))
|
||
|
main.iot_devices[device_identifier].action(payload)
|
||
|
_logger.info("device '%s' action finished - %.*f", device_identifier, 3, time.perf_counter() - start_operation_time)
|
||
|
else:
|
||
|
# likely intended as IoT share the same channel
|
||
|
_logger.debug("message ignored due to different iot mac: %s", iot_mac)
|
||
|
elif message_type != 'print_confirmation': # intended to be ignored
|
||
|
_logger.warning("message type not supported: %s", message_type)
|
||
|
|
||
|
|
||
|
def on_error(ws, error):
|
||
|
_logger.error("websocket received an error: %s", error)
|
||
|
|
||
|
|
||
|
def on_close(ws, close_status_code, close_msg):
|
||
|
_logger.debug("websocket closed with status: %s", close_status_code)
|
||
|
|
||
|
|
||
|
class WebsocketClient(Thread):
|
||
|
iot_channel = ""
|
||
|
|
||
|
def on_open(self, ws):
|
||
|
"""
|
||
|
When the client is setup, this function send a message to subscribe to the iot websocket channel
|
||
|
"""
|
||
|
ws.send(
|
||
|
json.dumps({'event_name': 'subscribe', 'data': {'channels': [self.iot_channel], 'last': 0, 'mac_address': helpers.get_mac_address()}})
|
||
|
)
|
||
|
|
||
|
def __init__(self, url):
|
||
|
url_parsed = urllib.parse.urlsplit(url)
|
||
|
scheme = url_parsed.scheme.replace("http", "ws", 1)
|
||
|
self.url = urllib.parse.urlunsplit((scheme, url_parsed.netloc, 'websocket', '', ''))
|
||
|
Thread.__init__(self)
|
||
|
|
||
|
def run(self):
|
||
|
self.ws = websocket.WebSocketApp(self.url,
|
||
|
header={"User-Agent": "OdooIoTBox/1.0"},
|
||
|
on_open=self.on_open, on_message=on_message,
|
||
|
on_error=on_error, on_close=on_close)
|
||
|
|
||
|
# The IoT synchronised servers can stop in 2 ways that we need to handle:
|
||
|
# A. Gracefully:
|
||
|
# In this case a disconnection signal is sent to the IoT-box
|
||
|
# The websocket is properly closed, but it needs to be established a new connection when
|
||
|
# the server will be back.
|
||
|
#
|
||
|
# B. Forced/killed:
|
||
|
# In this case there is no disconnection signal received
|
||
|
#
|
||
|
# This will also happen with the graceful quit as `reconnect` will trigger if the server
|
||
|
# is offline while attempting the new connection
|
||
|
while True:
|
||
|
try:
|
||
|
run_res = self.ws.run_forever(reconnect=10)
|
||
|
_logger.debug("websocket run_forever return with %s", run_res)
|
||
|
except Exception:
|
||
|
_logger.exception("An unexpected exception happened when running the websocket")
|
||
|
_logger.debug('websocket will try to restart in 10 seconds')
|
||
|
time.sleep(10)
|