[IMP] abstract communication with shuttle

This commit is contained in:
Alexandre Fayolle
2019-11-04 15:03:03 +01:00
committed by Hai Lang
parent 084c86b356
commit 06fa3da67f
3 changed files with 113 additions and 10 deletions

View File

@@ -71,7 +71,12 @@ class StockLocation(models.Model):
location.vertical_lift_shuttle_id = shuttle
def _hardware_vertical_lift_tray(self, cell_location=None):
"""Send instructions to the vertical lift hardware
payload = self._hardware_vertical_lift_tray_payload(cell_location)
res = self.vertical_lift_shuttle_id._hardware_send_message(payload)
return res
def _hardware_vertical_lift_tray_payload(self, cell_location=None):
"""Prepare the message to be sent to the vertical lift hardware
Private method, this is where the implementation actually happens.
Addons can add their instructions based on the hardware used for
@@ -120,9 +125,9 @@ class StockLocation(models.Model):
from_left,
from_bottom,
)
self.env.user.notify_info(
message=message, title=_("Lift Simulation")
)
return message
else:
return super()._hardware_vertical_lift_tray_payload(cell_location)
def fetch_vertical_lift_tray(self, cell_location=None):
"""Send instructions to the vertical lift hardware

View File

@@ -1,8 +1,13 @@
# Copyright 2019 Camptocamp SA
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
import logging
import socket
import ssl
from odoo import _, api, fields, models
_logger = logging.getLogger(__name__)
class VerticalLiftShuttle(models.Model):
_name = "vertical.lift.shuttle"
@@ -26,6 +31,13 @@ class VerticalLiftShuttle(models.Model):
hardware = fields.Selection(
selection="_selection_hardware", default="simulation", required=True
)
server = fields.Char(help="hostname or IP address of the server")
port = fields.Integer(
help="network port of the server on which to send the message"
)
use_tls = fields.Boolean(
help="set this if the server expects TLS wrapped communication"
)
_sql_constraints = [
(
@@ -64,6 +76,85 @@ class VerticalLiftShuttle(models.Model):
),
}
def _hardware_send_message(self, payload):
"""default implementation for message sending
If in hardware is 'simulation' then display a simple message.
Otherwise defaults to connecting to server:port using a TCP socket
(optionnally wrapped with TLS) and sending the payload, then waiting
for a response and disconnecting.
:param payload: a bytes object containing the payload
"""
self.ensure_one()
_logger.info('send %r', payload)
if self.hardware == "simulation":
self.env.user.notify_info(message=payload,
title=_("Lift Simulation"))
return True
else:
conn = self._hardware_get_server_connection()
try:
offset = 0
while True:
size = conn.send(payload[offset:])
offset += size
if offset >= len(payload) or not size:
break
response = self._hardware_recv_response(conn)
_logger.info('recv %r', response)
return self._check_server_response(payload, response)
finally:
self._hardware_release_server_connection(conn)
def _hardware_recv_response(self, conn):
"""Default implementation expects the remote server to close()
the socket after sending the reponse.
Override to match the protocol implemented by the hardware.
:param conn: a socket connected to the server
:return: the response sent by the server, as a bytes object
"""
response = b''
chunk = True
while chunk:
chunk = conn.recv(1024)
response += chunk
return response
def _check_server_response(self, payload, response):
"""Use this to check if the response is a success or a failure
:param payload: the payload sent
:param response: the response received
:return: True if the response is a succes, False otherwise
"""
return True
def _hardware_release_server_connection(self, conn):
conn.close()
def _hardware_get_server_connection(self):
"""This implementation will yield a new connection to the server
and close() it when exiting the context.
Override to match the communication protocol of your hardware"""
conn = socket.create_connection((self.server, self.port))
if self.use_tls:
ctx = ssl.create_default_context()
self._hardware_update_tls_context(ctx)
conntls = ctx.wrap_socket(conn, server_hostname=self.server)
return conntls
else:
return conn
def _hardware_update_tls_context(self, context):
"""Update the TLS context, e.g. to add a client certificate.
This method does nothing, override to match your communication
protocol."""
pass
def _operation_for_mode(self):
model = self._model_for_mode[self.mode]
record = self.env[model].search([("shuttle_id", "=", self.id)])

View File

@@ -58,12 +58,19 @@
<field name="model">vertical.lift.shuttle</field>
<field name="arch" type="xml">
<form string="Operations">
<group>
<field name="name"/>
<field name="mode"/>
<field name="location_id"/>
<field name="hardware"/>
</group>
<group name="main">
<group name="left">
<field name="name"/>
<field name="mode"/>
<field name="location_id"/>
<field name="hardware"/>
</group>
<group string="Network" name="network">
<field name="server"/>
<field name="port"/>
<field name="use_tls"/>
</group>
</group>
</form>
</field>
</record>