Merge PR #2407 into 15.0

Signed-off-by pedrobaeza
pull/2638/head
OCA-git-bot 2023-10-02 06:33:46 +00:00
commit 3bd50c5e23
22 changed files with 1267 additions and 0 deletions

View File

@ -0,0 +1 @@
../../../../web_widget_remote_measure

View File

@ -0,0 +1,6 @@
import setuptools
setuptools.setup(
setup_requires=['setuptools-odoo'],
odoo_addon=True,
)

View File

@ -0,0 +1,130 @@
============================
Remote Measure Devices Input
============================
.. !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
!! This file is generated by oca-gen-addon-readme !!
!! changes will be overwritten. !!
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
.. |badge1| image:: https://img.shields.io/badge/maturity-Beta-yellow.png
:target: https://odoo-community.org/page/development-status
:alt: Beta
.. |badge2| image:: https://img.shields.io/badge/licence-AGPL--3-blue.png
:target: http://www.gnu.org/licenses/agpl-3.0-standalone.html
:alt: License: AGPL-3
.. |badge3| image:: https://img.shields.io/badge/github-OCA%2Fweb-lightgray.png?logo=github
:target: https://github.com/OCA/web/tree/15.0/web_widget_remote_measure
:alt: OCA/web
.. |badge4| image:: https://img.shields.io/badge/weblate-Translate%20me-F47D42.png
:target: https://translation.odoo-community.org/projects/web-15-0/web-15-0-web_widget_remote_measure
:alt: Translate me on Weblate
.. |badge5| image:: https://img.shields.io/badge/runbot-Try%20me-875A7B.png
:target: https://runbot.odoo-community.org/runbot/162/15.0
:alt: Try me on Runbot
|badge1| |badge2| |badge3| |badge4| |badge5|
This module allows to input data from remote devices in your network. Currently, only
websockets devices are supported, but it can be extended for any protocol like
Webservices.
Other modules can extend this one in order to use the widget.
**Table of contents**
.. contents::
:local:
Configuration
=============
To configure your remote devices:
#. Go to *Settings > Technical > Devices > Remote devices*
#. Create a new one configuring the required info.
#. If the devices has an special port, set it up in the host data: e.g.: 10.1.1.2:3210
Usage
=====
The remote device has to be in the users network so their web clients can reach them.
In order to test a device you can:
#. Go to *Settings > Technical > Devices > Remote devices*
#. In the Kanban view you'll wich devices can be reached as they'll have a green dot in
their card.
#. Go to one of those and click *Edit*.
#. You can start measuring from the remote device in the *Test measure* field.
On the technical side, you can use the widget in your own `Float``. You'll need to
provide an uom field so records that aren't in that UoM don't measure from the device.
.. code:: xml
<field name="float_field" widget="remote_measure" options="{'remote_device_field': 'measure_device_id', 'uom_field': 'uom_id'}" />
Known issues / Roadmap
======================
Current support:
- Websockets connection
- F501 protocol on continuous message stream.
But this is a commonground to add:
- Other connection interfaces like Webservices APIs
- Other device protocols.
- Active device controls, la Tare, resets, etc.
Bug Tracker
===========
Bugs are tracked on `GitHub Issues <https://github.com/OCA/web/issues>`_.
In case of trouble, please check there if your issue has already been reported.
If you spotted it first, help us smashing it by providing a detailed and welcomed
`feedback <https://github.com/OCA/web/issues/new?body=module:%20web_widget_remote_measure%0Aversion:%2015.0%0A%0A**Steps%20to%20reproduce**%0A-%20...%0A%0A**Current%20behavior**%0A%0A**Expected%20behavior**>`_.
Do not contact contributors directly about support or help with technical issues.
Credits
=======
Authors
~~~~~~~
* Tecnativa
Contributors
~~~~~~~~~~~~
* `Tecnativa <https://www.tecnativa.com>`_:
* David Vidal
Maintainers
~~~~~~~~~~~
This module is maintained by the OCA.
.. image:: https://odoo-community.org/logo.png
:alt: Odoo Community Association
:target: https://odoo-community.org
OCA, or the Odoo Community Association, is a nonprofit organization whose
mission is to support the collaborative development of Odoo features and
promote its widespread use.
.. |maintainer-chienandalu| image:: https://github.com/chienandalu.png?size=40px
:target: https://github.com/chienandalu
:alt: chienandalu
Current `maintainer <https://odoo-community.org/page/maintainer-role>`__:
|maintainer-chienandalu|
This module is part of the `OCA/web <https://github.com/OCA/web/tree/15.0/web_widget_remote_measure>`_ project on GitHub.
You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute.

View File

@ -0,0 +1 @@
from . import models

View File

@ -0,0 +1,25 @@
# Copyright 2023 Tecnativa - David Vidal
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
{
"name": "Remote Measure Devices Input",
"summary": "Allows to connect to remote devices to record measures",
"version": "15.0.1.0.0",
"author": "Tecnativa, Odoo Community Association (OCA)",
"website": "https://github.com/OCA/web",
"maintainers": ["chienandalu"],
"license": "AGPL-3",
"category": "Stock",
"depends": ["web", "uom"],
"data": [
"views/remote_measure_device_views.xml",
"views/res_users_views.xml",
"security/ir.model.access.csv",
],
"assets": {
"web.assets_backend": [
"web_widget_remote_measure/static/src/**/*.js",
"web_widget_remote_measure/static/src/**/*.scss",
],
"web.assets_qweb": ["web_widget_remote_measure/static/src/**/*.xml"],
},
}

View File

@ -0,0 +1,2 @@
from . import remote_measure_device
from . import res_users

View File

@ -0,0 +1,32 @@
# Copyright 2023 Tecnativa - David Vidal
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
from odoo import fields, models
class RemoteMeasureDevice(models.Model):
_name = "remote.measure.device"
_description = "Remote measure device"
active = fields.Boolean(default=True)
name = fields.Char(required=True)
uom_id = fields.Many2one(
string="Unit of measure",
comodel_name="uom.uom",
required=True,
)
uom_category_id = fields.Many2one(related="uom_id.category_id")
uom_factor = fields.Float(related="uom_id.factor")
protocol = fields.Selection(
selection=[("f501", "Scale F501")],
help="Operating protocol",
required=True,
)
connection_mode = fields.Selection(
selection=[
("websockets", "Web Sockets"),
("webservices", "Web Services"),
],
required=True,
)
host = fields.Char(required=True)
test_measure = fields.Float(default=0.0)

View File

@ -0,0 +1,12 @@
# Copyright 2023 Tecnativa - David Vidal
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
from odoo import fields, models
class ResUsers(models.Model):
_inherit = "res.users"
remote_measure_device_id = fields.Many2one(
comodel_name="remote.measure.device",
help="Default remote measure device for this user",
)

View File

@ -0,0 +1,5 @@
To configure your remote devices:
#. Go to *Settings > Technical > Devices > Remote devices*
#. Create a new one configuring the required info.
#. If the devices has an special port, set it up in the host data: e.g.: 10.1.1.2:3210

View File

@ -0,0 +1,3 @@
* `Tecnativa <https://www.tecnativa.com>`_:
* David Vidal

View File

@ -0,0 +1,5 @@
This module allows to input data from remote devices in your network. Currently, only
websockets devices are supported, but it can be extended for any protocol like
Webservices.
Other modules can extend this one in order to use the widget.

View File

@ -0,0 +1,10 @@
Current support:
- Websockets connection
- F501 protocol on continuous message stream.
But this is a commonground to add:
- Other connection interfaces like Webservices APIs
- Other device protocols.
- Active device controls, la Tare, resets, etc.

View File

@ -0,0 +1,16 @@
The remote device has to be in the users network so their web clients can reach them.
In order to test a device you can:
#. Go to *Settings > Technical > Devices > Remote devices*
#. In the Kanban view you'll wich devices can be reached as they'll have a green dot in
their card.
#. Go to one of those and click *Edit*.
#. You can start measuring from the remote device in the *Test measure* field.
On the technical side, you can use the widget in your own `Float``. You'll need to
provide an uom field so records that aren't in that UoM don't measure from the device.
.. code:: xml
<field name="float_field" widget="remote_measure" options="{'remote_device_field': 'measure_device_id', 'uom_field': 'uom_id'}" />

View File

@ -0,0 +1,3 @@
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
web_widget_remote_measure.access_remote_measure_device_user,access_remote_measure_device users,web_widget_remote_measure.model_remote_measure_device,base.group_user,1,0,0,0
web_widget_remote_measure.access_remote_measure_device_admin,access_remote_measure_device admin,web_widget_remote_measure.model_remote_measure_device,base.group_system,1,1,1,1
1 id name model_id:id group_id:id perm_read perm_write perm_create perm_unlink
2 web_widget_remote_measure.access_remote_measure_device_user access_remote_measure_device users web_widget_remote_measure.model_remote_measure_device base.group_user 1 0 0 0
3 web_widget_remote_measure.access_remote_measure_device_admin access_remote_measure_device admin web_widget_remote_measure.model_remote_measure_device base.group_system 1 1 1 1

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.4 KiB

View File

@ -0,0 +1,470 @@
<?xml version="1.0" encoding="utf-8" ?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<meta name="generator" content="Docutils: http://docutils.sourceforge.net/" />
<title>Remote Measure Devices Input</title>
<style type="text/css">
/*
:Author: David Goodger (goodger@python.org)
:Id: $Id: html4css1.css 7952 2016-07-26 18:15:59Z milde $
:Copyright: This stylesheet has been placed in the public domain.
Default cascading style sheet for the HTML output of Docutils.
See http://docutils.sf.net/docs/howto/html-stylesheets.html for how to
customize this style sheet.
*/
/* used to remove borders from tables and images */
.borderless, table.borderless td, table.borderless th {
border: 0 }
table.borderless td, table.borderless th {
/* Override padding for "table.docutils td" with "! important".
The right padding separates the table cells. */
padding: 0 0.5em 0 0 ! important }
.first {
/* Override more specific margin styles with "! important". */
margin-top: 0 ! important }
.last, .with-subtitle {
margin-bottom: 0 ! important }
.hidden {
display: none }
.subscript {
vertical-align: sub;
font-size: smaller }
.superscript {
vertical-align: super;
font-size: smaller }
a.toc-backref {
text-decoration: none ;
color: black }
blockquote.epigraph {
margin: 2em 5em ; }
dl.docutils dd {
margin-bottom: 0.5em }
object[type="image/svg+xml"], object[type="application/x-shockwave-flash"] {
overflow: hidden;
}
/* Uncomment (and remove this text!) to get bold-faced definition list terms
dl.docutils dt {
font-weight: bold }
*/
div.abstract {
margin: 2em 5em }
div.abstract p.topic-title {
font-weight: bold ;
text-align: center }
div.admonition, div.attention, div.caution, div.danger, div.error,
div.hint, div.important, div.note, div.tip, div.warning {
margin: 2em ;
border: medium outset ;
padding: 1em }
div.admonition p.admonition-title, div.hint p.admonition-title,
div.important p.admonition-title, div.note p.admonition-title,
div.tip p.admonition-title {
font-weight: bold ;
font-family: sans-serif }
div.attention p.admonition-title, div.caution p.admonition-title,
div.danger p.admonition-title, div.error p.admonition-title,
div.warning p.admonition-title, .code .error {
color: red ;
font-weight: bold ;
font-family: sans-serif }
/* Uncomment (and remove this text!) to get reduced vertical space in
compound paragraphs.
div.compound .compound-first, div.compound .compound-middle {
margin-bottom: 0.5em }
div.compound .compound-last, div.compound .compound-middle {
margin-top: 0.5em }
*/
div.dedication {
margin: 2em 5em ;
text-align: center ;
font-style: italic }
div.dedication p.topic-title {
font-weight: bold ;
font-style: normal }
div.figure {
margin-left: 2em ;
margin-right: 2em }
div.footer, div.header {
clear: both;
font-size: smaller }
div.line-block {
display: block ;
margin-top: 1em ;
margin-bottom: 1em }
div.line-block div.line-block {
margin-top: 0 ;
margin-bottom: 0 ;
margin-left: 1.5em }
div.sidebar {
margin: 0 0 0.5em 1em ;
border: medium outset ;
padding: 1em ;
background-color: #ffffee ;
width: 40% ;
float: right ;
clear: right }
div.sidebar p.rubric {
font-family: sans-serif ;
font-size: medium }
div.system-messages {
margin: 5em }
div.system-messages h1 {
color: red }
div.system-message {
border: medium outset ;
padding: 1em }
div.system-message p.system-message-title {
color: red ;
font-weight: bold }
div.topic {
margin: 2em }
h1.section-subtitle, h2.section-subtitle, h3.section-subtitle,
h4.section-subtitle, h5.section-subtitle, h6.section-subtitle {
margin-top: 0.4em }
h1.title {
text-align: center }
h2.subtitle {
text-align: center }
hr.docutils {
width: 75% }
img.align-left, .figure.align-left, object.align-left, table.align-left {
clear: left ;
float: left ;
margin-right: 1em }
img.align-right, .figure.align-right, object.align-right, table.align-right {
clear: right ;
float: right ;
margin-left: 1em }
img.align-center, .figure.align-center, object.align-center {
display: block;
margin-left: auto;
margin-right: auto;
}
table.align-center {
margin-left: auto;
margin-right: auto;
}
.align-left {
text-align: left }
.align-center {
clear: both ;
text-align: center }
.align-right {
text-align: right }
/* reset inner alignment in figures */
div.align-right {
text-align: inherit }
/* div.align-center * { */
/* text-align: left } */
.align-top {
vertical-align: top }
.align-middle {
vertical-align: middle }
.align-bottom {
vertical-align: bottom }
ol.simple, ul.simple {
margin-bottom: 1em }
ol.arabic {
list-style: decimal }
ol.loweralpha {
list-style: lower-alpha }
ol.upperalpha {
list-style: upper-alpha }
ol.lowerroman {
list-style: lower-roman }
ol.upperroman {
list-style: upper-roman }
p.attribution {
text-align: right ;
margin-left: 50% }
p.caption {
font-style: italic }
p.credits {
font-style: italic ;
font-size: smaller }
p.label {
white-space: nowrap }
p.rubric {
font-weight: bold ;
font-size: larger ;
color: maroon ;
text-align: center }
p.sidebar-title {
font-family: sans-serif ;
font-weight: bold ;
font-size: larger }
p.sidebar-subtitle {
font-family: sans-serif ;
font-weight: bold }
p.topic-title {
font-weight: bold }
pre.address {
margin-bottom: 0 ;
margin-top: 0 ;
font: inherit }
pre.literal-block, pre.doctest-block, pre.math, pre.code {
margin-left: 2em ;
margin-right: 2em }
pre.code .ln { color: grey; } /* line numbers */
pre.code, code { background-color: #eeeeee }
pre.code .comment, code .comment { color: #5C6576 }
pre.code .keyword, code .keyword { color: #3B0D06; font-weight: bold }
pre.code .literal.string, code .literal.string { color: #0C5404 }
pre.code .name.builtin, code .name.builtin { color: #352B84 }
pre.code .deleted, code .deleted { background-color: #DEB0A1}
pre.code .inserted, code .inserted { background-color: #A3D289}
span.classifier {
font-family: sans-serif ;
font-style: oblique }
span.classifier-delimiter {
font-family: sans-serif ;
font-weight: bold }
span.interpreted {
font-family: sans-serif }
span.option {
white-space: nowrap }
span.pre {
white-space: pre }
span.problematic {
color: red }
span.section-subtitle {
/* font-size relative to parent (h1..h6 element) */
font-size: 80% }
table.citation {
border-left: solid 1px gray;
margin-left: 1px }
table.docinfo {
margin: 2em 4em }
table.docutils {
margin-top: 0.5em ;
margin-bottom: 0.5em }
table.footnote {
border-left: solid 1px black;
margin-left: 1px }
table.docutils td, table.docutils th,
table.docinfo td, table.docinfo th {
padding-left: 0.5em ;
padding-right: 0.5em ;
vertical-align: top }
table.docutils th.field-name, table.docinfo th.docinfo-name {
font-weight: bold ;
text-align: left ;
white-space: nowrap ;
padding-left: 0 }
/* "booktabs" style (no vertical lines) */
table.docutils.booktabs {
border: 0px;
border-top: 2px solid;
border-bottom: 2px solid;
border-collapse: collapse;
}
table.docutils.booktabs * {
border: 0px;
}
table.docutils.booktabs th {
border-bottom: thin solid;
text-align: left;
}
h1 tt.docutils, h2 tt.docutils, h3 tt.docutils,
h4 tt.docutils, h5 tt.docutils, h6 tt.docutils {
font-size: 100% }
ul.auto-toc {
list-style-type: none }
</style>
</head>
<body>
<div class="document" id="remote-measure-devices-input">
<h1 class="title">Remote Measure Devices Input</h1>
<!-- !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
!! This file is generated by oca-gen-addon-readme !!
!! changes will be overwritten. !!
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! -->
<p><a class="reference external" href="https://odoo-community.org/page/development-status"><img alt="Beta" src="https://img.shields.io/badge/maturity-Beta-yellow.png" /></a> <a class="reference external" href="http://www.gnu.org/licenses/agpl-3.0-standalone.html"><img alt="License: AGPL-3" src="https://img.shields.io/badge/licence-AGPL--3-blue.png" /></a> <a class="reference external" href="https://github.com/OCA/web/tree/15.0/web_widget_remote_measure"><img alt="OCA/web" src="https://img.shields.io/badge/github-OCA%2Fweb-lightgray.png?logo=github" /></a> <a class="reference external" href="https://translation.odoo-community.org/projects/web-15-0/web-15-0-web_widget_remote_measure"><img alt="Translate me on Weblate" src="https://img.shields.io/badge/weblate-Translate%20me-F47D42.png" /></a> <a class="reference external" href="https://runbot.odoo-community.org/runbot/162/15.0"><img alt="Try me on Runbot" src="https://img.shields.io/badge/runbot-Try%20me-875A7B.png" /></a></p>
<p>This module allows to input data from remote devices in your network. Currently, only
websockets devices are supported, but it can be extended for any protocol like
Webservices.</p>
<p>Other modules can extend this one in order to use the widget.</p>
<p><strong>Table of contents</strong></p>
<div class="contents local topic" id="contents">
<ul class="simple">
<li><a class="reference internal" href="#configuration" id="id1">Configuration</a></li>
<li><a class="reference internal" href="#usage" id="id2">Usage</a></li>
<li><a class="reference internal" href="#known-issues-roadmap" id="id3">Known issues / Roadmap</a></li>
<li><a class="reference internal" href="#bug-tracker" id="id4">Bug Tracker</a></li>
<li><a class="reference internal" href="#credits" id="id5">Credits</a><ul>
<li><a class="reference internal" href="#authors" id="id6">Authors</a></li>
<li><a class="reference internal" href="#contributors" id="id7">Contributors</a></li>
<li><a class="reference internal" href="#maintainers" id="id8">Maintainers</a></li>
</ul>
</li>
</ul>
</div>
<div class="section" id="configuration">
<h1><a class="toc-backref" href="#id1">Configuration</a></h1>
<p>To configure your remote devices:</p>
<ol class="arabic simple">
<li>Go to <em>Settings &gt; Technical &gt; Devices &gt; Remote devices</em></li>
<li>Create a new one configuring the required info.</li>
<li>If the devices has an special port, set it up in the host data: e.g.: 10.1.1.2:3210</li>
</ol>
</div>
<div class="section" id="usage">
<h1><a class="toc-backref" href="#id2">Usage</a></h1>
<p>The remote device has to be in the users network so their web clients can reach them.</p>
<p>In order to test a device you can:</p>
<ol class="arabic simple">
<li>Go to <em>Settings &gt; Technical &gt; Devices &gt; Remote devices</em></li>
<li>In the Kanban view youll wich devices can be reached as theyll have a green dot in
their card.</li>
<li>Go to one of those and click <em>Edit</em>.</li>
<li>You can start measuring from the remote device in the <em>Test measure</em> field.</li>
</ol>
<p>On the technical side, you can use the widget in your own <cite>Float`</cite>. Youll need to
provide an uom field so records that arent in that UoM dont measure from the device.</p>
<pre class="code xml literal-block">
<span class="nt">&lt;field</span> <span class="na">name=</span><span class="s">&quot;float_field&quot;</span> <span class="na">widget=</span><span class="s">&quot;remote_measure&quot;</span> <span class="na">options=</span><span class="s">&quot;{'remote_device_field': 'measure_device_id', 'uom_field': 'uom_id'}&quot;</span> <span class="nt">/&gt;</span>
</pre>
</div>
<div class="section" id="known-issues-roadmap">
<h1><a class="toc-backref" href="#id3">Known issues / Roadmap</a></h1>
<p>Current support:</p>
<ul class="simple">
<li>Websockets connection</li>
<li>F501 protocol on continuous message stream.</li>
</ul>
<p>But this is a commonground to add:</p>
<ul class="simple">
<li>Other connection interfaces like Webservices APIs</li>
<li>Other device protocols.</li>
<li>Active device controls, la Tare, resets, etc.</li>
</ul>
</div>
<div class="section" id="bug-tracker">
<h1><a class="toc-backref" href="#id4">Bug Tracker</a></h1>
<p>Bugs are tracked on <a class="reference external" href="https://github.com/OCA/web/issues">GitHub Issues</a>.
In case of trouble, please check there if your issue has already been reported.
If you spotted it first, help us smashing it by providing a detailed and welcomed
<a class="reference external" href="https://github.com/OCA/web/issues/new?body=module:%20web_widget_remote_measure%0Aversion:%2015.0%0A%0A**Steps%20to%20reproduce**%0A-%20...%0A%0A**Current%20behavior**%0A%0A**Expected%20behavior**">feedback</a>.</p>
<p>Do not contact contributors directly about support or help with technical issues.</p>
</div>
<div class="section" id="credits">
<h1><a class="toc-backref" href="#id5">Credits</a></h1>
<div class="section" id="authors">
<h2><a class="toc-backref" href="#id6">Authors</a></h2>
<ul class="simple">
<li>Tecnativa</li>
</ul>
</div>
<div class="section" id="contributors">
<h2><a class="toc-backref" href="#id7">Contributors</a></h2>
<ul class="simple">
<li><a class="reference external" href="https://www.tecnativa.com">Tecnativa</a>:<ul>
<li>David Vidal</li>
</ul>
</li>
</ul>
</div>
<div class="section" id="maintainers">
<h2><a class="toc-backref" href="#id8">Maintainers</a></h2>
<p>This module is maintained by the OCA.</p>
<a class="reference external image-reference" href="https://odoo-community.org"><img alt="Odoo Community Association" src="https://odoo-community.org/logo.png" /></a>
<p>OCA, or the Odoo Community Association, is a nonprofit organization whose
mission is to support the collaborative development of Odoo features and
promote its widespread use.</p>
<p>Current <a class="reference external" href="https://odoo-community.org/page/maintainer-role">maintainer</a>:</p>
<p><a class="reference external" href="https://github.com/chienandalu"><img alt="chienandalu" src="https://github.com/chienandalu.png?size=40px" /></a></p>
<p>This module is part of the <a class="reference external" href="https://github.com/OCA/web/tree/15.0/web_widget_remote_measure">OCA/web</a> project on GitHub.</p>
<p>You are welcome to contribute. To learn how please visit <a class="reference external" href="https://odoo-community.org/page/Contribute">https://odoo-community.org/page/Contribute</a>.</p>
</div>
</div>
</div>
</body>
</html>

View File

@ -0,0 +1,33 @@
/** @odoo-module **/
import Widget from "web.Widget";
import widgetRegistry from "web.widget_registry";
const RemoteMeasureDeviceStatusWidget = Widget.extend({
template: "web_widget_remote_measure.measure_device_status",
xmlDependencies: [
"/web_widget_remote_measure/static/src/xml/measure_device_status.xml",
],
init(_parent, _data, options) {
this._super(...arguments);
this.className = "text-muted";
this.title = "Requesting status...";
this.host = options.attrs.host;
},
async willStart() {
await this._super(...arguments);
const socket = new WebSocket(this.host);
socket.onerror = async () => {
this.className = "text-danger";
this.title = "Device is down";
this.renderElement();
};
socket.onmessage = async () => {
socket.close();
this.className = "text-success";
this.title = "Device ready";
this.renderElement();
};
},
});
widgetRegistry.add("remote_measure_device_status", RemoteMeasureDeviceStatusWidget);

View File

@ -0,0 +1,359 @@
/** @odoo-module **/
// TODO: Implement in OWL in v16. It should have be a much much simpler implementation.
import {FieldFloat} from "web.basic_fields";
import {_lt} from "@web/core/l10n/translation";
import {_t} from "web.translation";
import fieldRegistry from "web.field_registry";
import session from "web.session";
// Animate the measure steps for each measure received.
export const nextState = {
"fa-thermometer-empty": "fa-thermometer-quarter",
"fa-thermometer-quarter": "fa-thermometer-half",
"fa-thermometer-half": "fa-thermometer-three-quarters",
"fa-thermometer-three-quarters": "fa-thermometer-full",
"fa-thermometer-full": "fa-thermometer-empty",
};
export const RemoteMeasureMixin = {
/**
* F501 Protocol response:
* [STX][status1][status2][data][ETX]
* - status1 beign weight status: \x20 (space) for stable weight and ? for unstable
* - status2 beign weight sign: + for positive and - for negative.
* - data being the weight itself with 6 characters for weight and one . for the
* decimal dot
*
* @param {String} msg ASCII string
* @returns {Object} with the value and the stable flag
*/
_proccess_msg_f501(msg) {
return {
stable: msg[1] === "\x20",
value: parseFloat(msg.slice(2, 10)),
};
},
/**
* Implemented for a continous remote stream
* TODO: Abstract more the possible device scenarios
*/
_connect_to_websockets() {
try {
this.socket = new WebSocket(this.host);
} catch (error) {
// Avoid websockets security error. Local devices won't have wss normally
if (error.code === 18) {
return;
}
throw error;
}
var icon = "fa-thermometer-empty";
var stream_success_counter = 10;
this.socket.onmessage = async (msg) => {
const data = await msg.data.text();
const processed_data = this[`_proccess_msg_${this.protocol}`](data);
if (!processed_data.stable) {
stream_success_counter = 5;
}
if (processed_data.stable && !stream_success_counter) {
this._stableMeasure();
this._closeSocket();
this._awaitingMeasure();
this._recordMeasure();
return;
}
this._unstableMeasure();
if (stream_success_counter) {
--stream_success_counter;
}
icon = this._nextStateIcon(icon);
this.amount = processed_data.value;
this._setMeasure();
};
this.socket.onerror = () => {
this._awaitingMeasure();
};
},
/**
* Implement for your device protocol service
*/
_connect_to_webservices() {
return;
},
/**
* Convert the measured units to the units expecte by the record if different
* @param {Number} amount
* @returns {Number} converted amount
*/
_compute_quantity(amount) {
if (this.uom.id === this.device_uom.id) {
return amount;
}
let converted_amount = amount / this.remote_device_data.uom_factor;
converted_amount *= this.uom.factor;
return converted_amount;
},
/**
* Set value
*/
async _setMeasure() {
if (isNaN(this.amount)) {
return;
}
this.amount = this._compute_quantity(this.amount);
if (this.start_add) {
this.amount += this.input_val;
}
this.$input.val(this.amount.toLocaleString(this.locale_code));
this._setValue(this.$input.val());
},
/**
* Procure to close the socket whenever the widget stops being used
*/
_closeSocket() {
if (this.socket) {
this.socket.close();
}
},
/**
* Animate the measure steps for each measure received.
* @param {String} icon
* @returns {String} next icon
*/
_nextStateIcon(icon) {
const next_icon = nextState[icon];
this.$icon.removeClass(icon);
this.$icon.addClass(next_icon);
return next_icon;
},
/**
* While a measure is not stable the button will be red
*/
_unstableMeasure() {
this.$stop_measure.removeClass("btn-primary btn-success");
this.$stop_measure.addClass("btn-danger");
},
/**
* Once we consider the measure is stable render the button as green
*/
_stableMeasure() {
this.$stop_measure.removeClass("btn-primary btn-danger");
this.$stop_measure.addClass("btn-success");
},
/**
* While the widget isn't querying it will be purple as a signal that we can start
*/
_awaitingMeasure() {
this.$start_measure.removeClass("btn-success btn-danger");
this.$start_measure.addClass("btn-primary");
this.$stop_measure.addClass("d-none");
this.$start_measure.removeClass("d-none");
if (this.$start_measure_add) {
this.$start_measure_add.removeClass("d-none");
}
},
/**
*
*/
_recordMeasure() {
this.start_add = false;
this.input_val = this.amount;
this.start_add = false;
},
/**
* Start requesting measures from the remote device
* @param {MouseEvent} ev
*/
_onMeasure(ev) {
ev.preventDefault();
this.$start_measure.addClass("d-none");
this.$stop_measure.removeClass("d-none");
this.$icon = this.$stop_measure.find("i");
this[`_connect_to_${this.connection_mode}`]();
},
_onMeasureAdd(ev) {
ev.preventDefault();
this.start_add = true;
this.$start_measure.addClass("d-none");
this.$start_measure_add.addClass("d-none");
this.$stop_measure.removeClass("d-none");
this.$icon = this.$stop_measure.find("i");
this[`_connect_to_${this.connection_mode}`]();
},
/**
* Validate the requested measure
* @param {MouseEvent} ev
*/
_onValidateMeasure(ev) {
ev.preventDefault();
this._closeSocket();
this._awaitingMeasure();
this._recordMeasure();
},
/**
* Remote measure handle to start measuring
* @returns {jQueryElement}
*/
_addRemoteMeasureWidgetStart() {
return $(
`
<span class="o_field_remote_device_start btn btn-primary mr-1">
<i class="fa fa-thermometer-half">
</span>
`
).on("click", this._onMeasure.bind(this));
},
/**
* Remote measure handle to start measuring
* @returns {jQueryElement}
*/
_addRemoteMeasureWidgetStartAdd() {
return $(
`
<span class="o_field_remote_device_start btn btn-link mr-1">
<i class="fa fa-plus">
</span>
`
).on("click", this._onMeasureAdd.bind(this));
},
/**
* Remote measure handle to stop and register measuring
* @returns {jQueryElement}
*/
_addRemoteMeasureWidgetStop() {
return $(
`
<span class="o_field_remote_device_stop btn btn-secondary d-none mr-1">
<i class="fa fa-thermometer-empty">
</span>
`
).on("click", this._onValidateMeasure.bind(this));
},
};
export const RemoteMeasure = FieldFloat.extend(RemoteMeasureMixin, {
description: _lt("Remote Measure"),
className: "o_field_remote_device o_field_number",
tagName: "span",
isQuickEditable: true,
resetOnAnyFieldChange: true,
events: Object.assign({}, FieldFloat.prototype.events, {
focusin: "_onFocusIn",
}),
/**
* Setup the field layout and the remote device parameters
*/
init() {
this._super(...arguments);
if (this.mode === "edit") {
this.tagName = "div";
this.className += " o_input";
}
this.locale_code = _t.database.parameters.code.replace("_", "-");
this.decimal_separator = _t.database.parameters.decimal_point;
this.thousands_sep = _t.database.parameters.thousands_sep;
this.remote_device_field = this.nodeOptions.remote_device_field;
this.default_user_device = this.nodeOptions.default_user_device;
if (this.nodeOptions.remote_device_field === "id") {
this.remote_device_data = this.recordData;
} else if (this.remote_device_field) {
this.remote_device_data = this.recordData[this.remote_device_field].data;
}
this.uom = this.recordData[this.nodeOptions.uom_field].data;
this.allow_additive_measure = this.nodeOptions.allow_additive_measure;
// Add to your view options so you can log requests and responses
},
/**
* Request the configured remote device info
*/
async willStart() {
await this._super(...arguments);
// Try to get the user's preferred device if any
if (!this.remote_device_data && this.default_user_device) {
[this.remote_device_data] = await this._rpc({
model: "res.users",
method: "read",
args: [session.uid, ["remote_measure_device_id"]],
});
if (!this.remote_device_data.remote_measure_device_id) {
return;
}
if (this.remote_device_data) {
this.remote_device_data.id =
this.remote_device_data.remote_measure_device_id[0];
}
}
if (!this.remote_device_data || !this.uom) {
return;
}
[this.remote_device_data] = await this._rpc({
model: "remote.measure.device",
method: "read",
args: [this.remote_device_data.id, []],
});
[this.uom] = await this._rpc({
model: "uom.uom",
method: "read",
args: [this.uom.id, []],
});
this.uom_category = this.uom.category_id[0];
this.device_uom_category = this.remote_device_data.uom_category_id[0];
this.device_uom = this.remote_device_data.uom_id[0];
this.host = this.remote_device_data && this.remote_device_data.host;
this.protocol = this.remote_device_data && this.remote_device_data.protocol;
this.connection_mode =
this.remote_device_data && this.remote_device_data.connection_mode;
},
/**
* Set de widget layout up
* @returns {Promise}
*/
_renderEdit() {
this.$el.empty();
var def = this._prepareInput(this.$input).appendTo(this.$el);
// From locale format
if (this.input_val === undefined) {
let pre_value = this.$input.val() || "0";
pre_value = pre_value.replace(this.thousands_sep, "");
pre_value = pre_value.replace(this.decimal_separator, ".");
this.input_val = parseFloat(pre_value);
}
this.start_add = false;
const [device_uom = undefined] =
(this.remote_device_data && this.remote_device_data.uom_id) || [];
if (
!this.remote_device_data ||
!this.uom ||
!device_uom ||
this.uom_category !== this.device_uom_category
) {
return def;
}
this.$start_measure = this._addRemoteMeasureWidgetStart();
this.$stop_measure = this._addRemoteMeasureWidgetStop();
if (this.allow_additive_measure && this.input_val > 0) {
this.$start_measure_add = this._addRemoteMeasureWidgetStartAdd();
this.$el.prepend(this.$start_measure_add);
}
this.$el.prepend(this.$start_measure, this.$stop_measure);
return def;
},
/**
* Ensure that the socket is allways closed
*/
destroy() {
this._closeSocket();
this._super.apply(this, arguments);
},
/**
* Auto select all the content
*/
_onFocusIn: function () {
// Auto select all content when user enters into fields with this
// widget.
this.$input.select();
},
});
fieldRegistry.add("remote_measure", RemoteMeasure);

View File

@ -0,0 +1,30 @@
.o_field_widget {
&.o_field_remote_device {
display: inline-flex;
> span,
> button {
flex: 0 0 auto;
}
}
&.o_field_remote_device {
&.o_input {
align-items: baseline;
> input {
width: 100px;
flex: 1 0 auto;
}
}
}
}
.o_list_view {
.o_list_table {
.o_data_row.o_selected_row
> .o_data_cell:not(.o_readonly_modifier):not(.o_invisible_modifier) {
.o_field_remote_device input {
width: 0;
margin: 0 4px;
}
}
}
}

View File

@ -0,0 +1,15 @@
<?xml version="1.0" encoding="utf-8" ?>
<template>
<t t-name="web_widget_remote_measure.measure_device_status">
<div class="float-right" name="is_up">
<span
t-attf-class="fa fa-circle {{widget.className}}"
role="img"
aria-label="Host is up?"
t-attf-title="{{widget.title}}"
name="is_up"
>
</span>
</div>
</t>
</template>

View File

@ -0,0 +1,92 @@
<?xml version="1.1" encoding="utf-8" ?>
<odoo>
<record id="measure_device_form" model="ir.ui.view">
<field name="model">remote.measure.device</field>
<field name="arch" type="xml">
<form>
<sheet>
<field name="active" invisible="1" />
<widget
name="web_ribbon"
title="Archived"
bg_color="bg-danger"
attrs="{'invisible': [('active', '=', True)]}"
/>
<div class="oe_title">
<h1><field name="name" placeholder="e.g. Dock 1 Scale" /></h1>
</div>
<group>
<group>
<field name="uom_id" widget="selection" />
<field name="host" />
</group>
<group>
<field name="protocol" />
<field name="connection_mode" />
</group>
</group>
</sheet>
</form>
</field>
</record>
<record id="measure_device_tree" model="ir.ui.view">
<field name="model">remote.measure.device</field>
<field name="arch" type="xml">
<tree>
<field name="name" />
<field name="host" />
<field name="protocol" optional="show" />
<field name="uom_id" optional="show" />
<field name="connection_mode" optional="show" />
</tree>
</field>
</record>
<record id="measure_device_kanban" model="ir.ui.view">
<field name="model">remote.measure.device</field>
<field name="arch" type="xml">
<kanban>
<field name="name" />
<field name="host" />
<field name="protocol" />
<field name="connection_mode" />
<templates>
<t t-name="kanban-box">
<div class="oe_kanban_global_click">
<div class="oe_kanban_details">
<div class="o_kanban_record_top">
<div class="o_kanban_record_headings">
<strong class="o_kanban_record_title">
<field name="name" placeholder="Device name" />
</strong>
<span class="o_kanban_record_subtitle"><field
name="host"
/></span>
</div>
</div>
<ul>
<li class="o_text_overflow"><field
name="protocol"
/></li>
<li class="o_text_overflow"><field
name="connection_mode"
/></li>
</ul>
</div>
</div>
</t>
</templates>
</kanban>
</field>
</record>
<record id="action_measure_device" model="ir.actions.act_window">
<field name="name">Remote Devices</field>
<field name="res_model">remote.measure.device</field>
<field name="view_mode">kanban,tree,form</field>
</record>
<menuitem id="menu_device_config" name="Devices" parent="base.menu_custom" />
<menuitem
id="menu_measure_device"
parent="menu_device_config"
action="action_measure_device"
/>
</odoo>

View File

@ -0,0 +1,17 @@
<?xml version="1.1" encoding="utf-8" ?>
<odoo>
<record id="view_users_form" model="ir.ui.view">
<field name="model">res.users</field>
<field name="inherit_id" ref="base.view_users_form" />
<field name="arch" type="xml">
<page name="preferences" position="inside">
<group name="devices" string="Devices">
<field
name="remote_measure_device_id"
string="Preferred remote device"
/>
</group>
</page>
</field>
</record>
</odoo>