mirror of
https://github.com/OCA/stock-logistics-warehouse.git
synced 2025-01-21 14:27:28 +02:00
[IMP] abstract communication with shuttle
This commit is contained in:
committed by
Guewen Baconnier
parent
fc995a9410
commit
82e5c5b087
@@ -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
|
||||
|
||||
@@ -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)])
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -11,10 +11,40 @@ class StockLocation(models.Model):
|
||||
_inherit = 'stock.location'
|
||||
|
||||
def _hardware_kardex_prepare_payload(self, cell_location=None):
|
||||
return ""
|
||||
message_template = ("{code}|{hostId}|{addr}|{carrier}|{carrierNext}|"
|
||||
"{x}|{y}|{boxType}|{Q}|{order}|{part}|{desc}|\r\n")
|
||||
shuttle = self.vertical_lift_shuttle_id
|
||||
if shuttle.mode == "pick":
|
||||
code = "1"
|
||||
elif shuttle.mode == "put":
|
||||
code = "2"
|
||||
elif shuttle.mode == "inventory":
|
||||
code = "5"
|
||||
else:
|
||||
code = "61" # ping
|
||||
if cell_location:
|
||||
x, y = cell_location.tray_cell_center_position()
|
||||
else:
|
||||
x, y = '', ''
|
||||
subst = {
|
||||
'code': code,
|
||||
'hostId': 'odoo',
|
||||
'addr': shuttle.name,
|
||||
'carrier': self.name,
|
||||
'carrierNext': '',
|
||||
'x': x,
|
||||
'y': y,
|
||||
'boxType': '',
|
||||
'Q': '',
|
||||
'order': '',
|
||||
'part': '',
|
||||
'desc': '',
|
||||
}
|
||||
payload = message_template.format(subst)
|
||||
return payload.encode('iso-8859-1', 'replace')
|
||||
|
||||
def _hardware_vertical_lift_tray(self, cell_location=None):
|
||||
"""Send instructions to the vertical lift hardware
|
||||
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
|
||||
@@ -53,4 +83,4 @@ class StockLocation(models.Model):
|
||||
payload = self._hardware_kardex_prepare_payload()
|
||||
_logger.debug("Sending to kardex: {}", payload)
|
||||
# TODO implement the communication with kardex
|
||||
super()._hardware_vertical_lift_tray(cell_location=cell_location)
|
||||
super()._hardware_vertical_lift_tray_payload(cell_location=cell_location)
|
||||
|
||||
@@ -12,3 +12,20 @@ class VerticalLiftShuttle(models.Model):
|
||||
values = super()._selection_hardware()
|
||||
values += [('kardex', 'Kardex')]
|
||||
return values
|
||||
|
||||
def _hardware_recv_response(self, conn):
|
||||
# the implementation uses messages delimited with \r\n
|
||||
response = b''
|
||||
chunk = True
|
||||
while chunk:
|
||||
chunk = conn.recv(1)
|
||||
response += chunk
|
||||
if response.endswith(b'\r\n'):
|
||||
break
|
||||
return response
|
||||
|
||||
def _check_server_response(self, payload, response):
|
||||
payload = payload.decode('iso-8859-1')
|
||||
response = response.decode('iso-8859-1')
|
||||
code, sep, remaining = response.partition('|')
|
||||
return code == "0"
|
||||
|
||||
Reference in New Issue
Block a user