odoo-driver-plugin-printer-escpos 1.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- odoo_driver_plugin_printer_escpos-1.1.0/PKG-INFO +105 -0
- odoo_driver_plugin_printer_escpos-1.1.0/README.md +77 -0
- odoo_driver_plugin_printer_escpos-1.1.0/odoo_driver_plugin_printer_escpos/__init__.py +17 -0
- odoo_driver_plugin_printer_escpos-1.1.0/odoo_driver_plugin_printer_escpos/i18n/babel.cfg +2 -0
- odoo_driver_plugin_printer_escpos-1.1.0/odoo_driver_plugin_printer_escpos/i18n/fr/LC_MESSAGES/messages.mo +0 -0
- odoo_driver_plugin_printer_escpos-1.1.0/odoo_driver_plugin_printer_escpos/i18n/fr/LC_MESSAGES/messages.po +60 -0
- odoo_driver_plugin_printer_escpos-1.1.0/odoo_driver_plugin_printer_escpos/i18n/messages.pot +59 -0
- odoo_driver_plugin_printer_escpos-1.1.0/odoo_driver_plugin_printer_escpos/printer_escpos_driver.py +32 -0
- odoo_driver_plugin_printer_escpos-1.1.0/odoo_driver_plugin_printer_escpos/printer_escpos_route_odoo.py +32 -0
- odoo_driver_plugin_printer_escpos-1.1.0/odoo_driver_plugin_printer_escpos/printer_escpos_route_website.py +42 -0
- odoo_driver_plugin_printer_escpos-1.1.0/odoo_driver_plugin_printer_escpos/static/audio/photo-taken.wav +0 -0
- odoo_driver_plugin_printer_escpos-1.1.0/odoo_driver_plugin_printer_escpos/static/img/epson__tm_t20.png +0 -0
- odoo_driver_plugin_printer_escpos-1.1.0/odoo_driver_plugin_printer_escpos/static/img/epson__tm_t88.png +0 -0
- odoo_driver_plugin_printer_escpos-1.1.0/odoo_driver_plugin_printer_escpos/static/img/generic_device.png +0 -0
- odoo_driver_plugin_printer_escpos-1.1.0/odoo_driver_plugin_printer_escpos/static/js/webcam-easy.js +186 -0
- odoo_driver_plugin_printer_escpos-1.1.0/odoo_driver_plugin_printer_escpos/templates/driver_printer_escpos.html.jinja +76 -0
- odoo_driver_plugin_printer_escpos-1.1.0/odoo_driver_plugin_printer_escpos/templates/photo_booth.html.jinja +59 -0
- odoo_driver_plugin_printer_escpos-1.1.0/odoo_driver_plugin_printer_escpos/usb_devices.py +5 -0
- odoo_driver_plugin_printer_escpos-1.1.0/pyproject.toml +51 -0
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: odoo-driver-plugin-printer-escpos
|
|
3
|
+
Version: 1.1.0
|
|
4
|
+
Summary: Plugin for odoo-driver. Support escpos printer devices. (Epson ®, ...)
|
|
5
|
+
License: AGPLv3+
|
|
6
|
+
Keywords: Odoo,Point Of Sale,Driver,Device,Printer, ESC/POS,escpos,Epson ®
|
|
7
|
+
Author: Sylvain LE GAL
|
|
8
|
+
Author-email: sylvain.legal@grap.coop
|
|
9
|
+
Maintainer: Sylvain LE GAL
|
|
10
|
+
Maintainer-email: sylvain.legal@grap.coop
|
|
11
|
+
Requires-Python: >=3.9,<4.0
|
|
12
|
+
Classifier: License :: OSI Approved :: GNU Affero General Public License v3 or later (AGPLv3+)
|
|
13
|
+
Classifier: Intended Audience :: Developers
|
|
14
|
+
Classifier: Development Status :: 3 - Alpha
|
|
15
|
+
Classifier: Operating System :: Unix
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
22
|
+
Classifier: Framework :: Odoo
|
|
23
|
+
Requires-Dist: odoo-driver
|
|
24
|
+
Requires-Dist: python-escpos
|
|
25
|
+
Project-URL: Repository, https://gitlab.com/grap-rhone-alpes/odoo-driver-plugin-printer-escpos
|
|
26
|
+
Description-Content-Type: text/markdown
|
|
27
|
+
|
|
28
|
+
# Odoo Driver Plugin - Printer ESC/POS
|
|
29
|
+
|
|
30
|
+

|
|
31
|
+

|
|
32
|
+

|
|
33
|
+

|
|
34
|
+

|
|
35
|
+
|
|
36
|
+
This project provide a plugin for ``odoo-driver``
|
|
37
|
+
to communicate with Printers with ECS/POS protocol.
|
|
38
|
+
|
|
39
|
+
This plugin requires the following odoo module : `point_of_sale` (Odoo CE / EE).
|
|
40
|
+
|
|
41
|
+
To install this plugin, refer to the main project page : https://gitlab.com/grap-rhone-alpes/odoo-driver/.
|
|
42
|
+
|
|
43
|
+
For developpers, refer to this page : https://gitlab.com/grap-rhone-alpes/odoo-driver-plugin-printer-escpos/-/blob/main/DEVELOP.md.
|
|
44
|
+
|
|
45
|
+
## Table of contents
|
|
46
|
+
|
|
47
|
+
1. [Compatible Devices](#compatible-devices)
|
|
48
|
+
2. [Credits](#credits)
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
<a name="compatible-devices"></a>
|
|
52
|
+
|
|
53
|
+
## Compatible Devices
|
|
54
|
+
|
|
55
|
+
<table style="width: 100%;">
|
|
56
|
+
<tbody>
|
|
57
|
+
<tr>
|
|
58
|
+
<th colspan="2">Printers</th>
|
|
59
|
+
</tr>
|
|
60
|
+
<tr>
|
|
61
|
+
<td>
|
|
62
|
+
<li>
|
|
63
|
+
<ul>Epson - TM-T20II</ul>
|
|
64
|
+
<ul>Epson - TM-T20III</ul>
|
|
65
|
+
</li>
|
|
66
|
+
</td>
|
|
67
|
+
<td>
|
|
68
|
+
<img src="https://gitlab.com/grap-rhone-alpes/odoo-driver-plugin-printer-escpos/-/raw/main/odoo_driver_plugin_printer_escpos/static/img/epson__tm_t20.png" width="200" height="200" />
|
|
69
|
+
</td>
|
|
70
|
+
</tr>
|
|
71
|
+
<tr>
|
|
72
|
+
<td>
|
|
73
|
+
<li>
|
|
74
|
+
<ul>Epson - TM-T88V</ul>
|
|
75
|
+
</li>
|
|
76
|
+
</td>
|
|
77
|
+
<td>
|
|
78
|
+
<img src="https://gitlab.com/grap-rhone-alpes/odoo-driver-plugin-printer-escpos/-/raw/main/odoo_driver_plugin_printer_escpos/static/img/epson__tm_t88.png" width="200" height="200" />
|
|
79
|
+
</td>
|
|
80
|
+
</tr>
|
|
81
|
+
</tbody>
|
|
82
|
+
</table>
|
|
83
|
+
|
|
84
|
+
<a name="credits"></a>
|
|
85
|
+
|
|
86
|
+
## Credits
|
|
87
|
+
|
|
88
|
+
### Authors
|
|
89
|
+
|
|
90
|
+
* GRAP <https://www.grap.coop>
|
|
91
|
+
|
|
92
|
+
### Contributors
|
|
93
|
+
|
|
94
|
+
* Sylvain LE GAL <https://www.grap.coop/>
|
|
95
|
+
|
|
96
|
+
### Extra authorship
|
|
97
|
+
|
|
98
|
+
Part of the code in this project comes from the following projects, including:
|
|
99
|
+
|
|
100
|
+
* [Pywebdriver](https://github.com/pywebdriver/pywebdriver) (AGPL-3.0), by GRAP.
|
|
101
|
+
|
|
102
|
+
### Images
|
|
103
|
+
|
|
104
|
+
* [Thermal Receipt Printer Icon](https://www.flaticon.com/fr/icone-gratuite/facture_1649343), created by Icongeek26 (Flaticon).
|
|
105
|
+
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
# Odoo Driver Plugin - Printer ESC/POS
|
|
2
|
+
|
|
3
|
+

|
|
4
|
+

|
|
5
|
+

|
|
6
|
+

|
|
7
|
+

|
|
8
|
+
|
|
9
|
+
This project provide a plugin for ``odoo-driver``
|
|
10
|
+
to communicate with Printers with ECS/POS protocol.
|
|
11
|
+
|
|
12
|
+
This plugin requires the following odoo module : `point_of_sale` (Odoo CE / EE).
|
|
13
|
+
|
|
14
|
+
To install this plugin, refer to the main project page : https://gitlab.com/grap-rhone-alpes/odoo-driver/.
|
|
15
|
+
|
|
16
|
+
For developpers, refer to this page : https://gitlab.com/grap-rhone-alpes/odoo-driver-plugin-printer-escpos/-/blob/main/DEVELOP.md.
|
|
17
|
+
|
|
18
|
+
## Table of contents
|
|
19
|
+
|
|
20
|
+
1. [Compatible Devices](#compatible-devices)
|
|
21
|
+
2. [Credits](#credits)
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
<a name="compatible-devices"></a>
|
|
25
|
+
|
|
26
|
+
## Compatible Devices
|
|
27
|
+
|
|
28
|
+
<table style="width: 100%;">
|
|
29
|
+
<tbody>
|
|
30
|
+
<tr>
|
|
31
|
+
<th colspan="2">Printers</th>
|
|
32
|
+
</tr>
|
|
33
|
+
<tr>
|
|
34
|
+
<td>
|
|
35
|
+
<li>
|
|
36
|
+
<ul>Epson - TM-T20II</ul>
|
|
37
|
+
<ul>Epson - TM-T20III</ul>
|
|
38
|
+
</li>
|
|
39
|
+
</td>
|
|
40
|
+
<td>
|
|
41
|
+
<img src="https://gitlab.com/grap-rhone-alpes/odoo-driver-plugin-printer-escpos/-/raw/main/odoo_driver_plugin_printer_escpos/static/img/epson__tm_t20.png" width="200" height="200" />
|
|
42
|
+
</td>
|
|
43
|
+
</tr>
|
|
44
|
+
<tr>
|
|
45
|
+
<td>
|
|
46
|
+
<li>
|
|
47
|
+
<ul>Epson - TM-T88V</ul>
|
|
48
|
+
</li>
|
|
49
|
+
</td>
|
|
50
|
+
<td>
|
|
51
|
+
<img src="https://gitlab.com/grap-rhone-alpes/odoo-driver-plugin-printer-escpos/-/raw/main/odoo_driver_plugin_printer_escpos/static/img/epson__tm_t88.png" width="200" height="200" />
|
|
52
|
+
</td>
|
|
53
|
+
</tr>
|
|
54
|
+
</tbody>
|
|
55
|
+
</table>
|
|
56
|
+
|
|
57
|
+
<a name="credits"></a>
|
|
58
|
+
|
|
59
|
+
## Credits
|
|
60
|
+
|
|
61
|
+
### Authors
|
|
62
|
+
|
|
63
|
+
* GRAP <https://www.grap.coop>
|
|
64
|
+
|
|
65
|
+
### Contributors
|
|
66
|
+
|
|
67
|
+
* Sylvain LE GAL <https://www.grap.coop/>
|
|
68
|
+
|
|
69
|
+
### Extra authorship
|
|
70
|
+
|
|
71
|
+
Part of the code in this project comes from the following projects, including:
|
|
72
|
+
|
|
73
|
+
* [Pywebdriver](https://github.com/pywebdriver/pywebdriver) (AGPL-3.0), by GRAP.
|
|
74
|
+
|
|
75
|
+
### Images
|
|
76
|
+
|
|
77
|
+
* [Thermal Receipt Printer Icon](https://www.flaticon.com/fr/icone-gratuite/facture_1649343), created by Icongeek26 (Flaticon).
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
from pathlib import Path
|
|
2
|
+
|
|
3
|
+
from single_source import get_version
|
|
4
|
+
|
|
5
|
+
from . import usb_devices
|
|
6
|
+
from .printer_escpos_driver import DriverPrinterEscPos
|
|
7
|
+
from .printer_escpos_route_odoo import driver_routes_odoo
|
|
8
|
+
from .printer_escpos_route_website import driver_routes_website
|
|
9
|
+
|
|
10
|
+
__version__ = get_version(__name__, Path(__file__).parent.parent)
|
|
11
|
+
__package_name__ = __name__
|
|
12
|
+
|
|
13
|
+
DRIVER_FUNCTION = "printer"
|
|
14
|
+
PLUGIN_NAME = "printer_escpos"
|
|
15
|
+
DRIVER_CLASS = DriverPrinterEscPos
|
|
16
|
+
|
|
17
|
+
DRIVER_ROUTES_GROUPS = [driver_routes_odoo, driver_routes_website]
|
|
Binary file
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
# French translations for PROJECT.
|
|
2
|
+
# Copyright (C) 2026 ORGANIZATION
|
|
3
|
+
# This file is distributed under the same license as the PROJECT project.
|
|
4
|
+
# FIRST AUTHOR <EMAIL@ADDRESS>, 2026.
|
|
5
|
+
#
|
|
6
|
+
msgid ""
|
|
7
|
+
msgstr ""
|
|
8
|
+
"Project-Id-Version: PROJECT VERSION\n"
|
|
9
|
+
"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
|
|
10
|
+
"POT-Creation-Date: 2026-04-24 23:51+0200\n"
|
|
11
|
+
"PO-Revision-Date: 2026-04-24 17:54+0200\n"
|
|
12
|
+
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
|
13
|
+
"Language: fr\n"
|
|
14
|
+
"Language-Team: fr <LL@li.org>\n"
|
|
15
|
+
"Plural-Forms: nplurals=2; plural=(n > 1);\n"
|
|
16
|
+
"MIME-Version: 1.0\n"
|
|
17
|
+
"Content-Type: text/plain; charset=utf-8\n"
|
|
18
|
+
"Content-Transfer-Encoding: 8bit\n"
|
|
19
|
+
"Generated-By: Babel 2.18.0\n"
|
|
20
|
+
|
|
21
|
+
#: printer_escpos_driver.py:18
|
|
22
|
+
msgid "Printer"
|
|
23
|
+
msgstr "Imprimante"
|
|
24
|
+
|
|
25
|
+
#: templates/driver_printer_escpos.html.jinja:7
|
|
26
|
+
msgid "Test communication with printer"
|
|
27
|
+
msgstr "Test la communication avec l'imprimante"
|
|
28
|
+
|
|
29
|
+
#: templates/driver_printer_escpos.html.jinja:8
|
|
30
|
+
msgid "Print an image"
|
|
31
|
+
msgstr "Imprimer une image"
|
|
32
|
+
|
|
33
|
+
#: templates/driver_printer_escpos.html.jinja:15
|
|
34
|
+
msgid "png, jpg, etc."
|
|
35
|
+
msgstr "png, jpg, etc."
|
|
36
|
+
|
|
37
|
+
#: templates/driver_printer_escpos.html.jinja:18
|
|
38
|
+
msgid "Print"
|
|
39
|
+
msgstr "Imprimer"
|
|
40
|
+
|
|
41
|
+
#: templates/driver_printer_escpos.html.jinja:21
|
|
42
|
+
msgid "Open cashbox"
|
|
43
|
+
msgstr "Ouvrir le tiroir caisse"
|
|
44
|
+
|
|
45
|
+
#: templates/driver_printer_escpos.html.jinja:23
|
|
46
|
+
msgid "Open"
|
|
47
|
+
msgstr "Ouvrir"
|
|
48
|
+
|
|
49
|
+
#: templates/driver_printer_escpos.html.jinja:26
|
|
50
|
+
msgid "Use Photo Booth"
|
|
51
|
+
msgstr "Utiliser le photomaton"
|
|
52
|
+
|
|
53
|
+
#: templates/driver_printer_escpos.html.jinja:29
|
|
54
|
+
msgid "Photo Booth"
|
|
55
|
+
msgstr "Photomaton"
|
|
56
|
+
|
|
57
|
+
#: templates/photo_booth.html.jinja:13
|
|
58
|
+
msgid "Take a photo"
|
|
59
|
+
msgstr "Prendre une photo"
|
|
60
|
+
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
# Translations template for PROJECT.
|
|
2
|
+
# Copyright (C) 2026 ORGANIZATION
|
|
3
|
+
# This file is distributed under the same license as the PROJECT project.
|
|
4
|
+
# FIRST AUTHOR <EMAIL@ADDRESS>, 2026.
|
|
5
|
+
#
|
|
6
|
+
#, fuzzy
|
|
7
|
+
msgid ""
|
|
8
|
+
msgstr ""
|
|
9
|
+
"Project-Id-Version: PROJECT VERSION\n"
|
|
10
|
+
"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
|
|
11
|
+
"POT-Creation-Date: 2026-04-24 23:51+0200\n"
|
|
12
|
+
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
|
13
|
+
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
|
14
|
+
"Language-Team: LANGUAGE <LL@li.org>\n"
|
|
15
|
+
"MIME-Version: 1.0\n"
|
|
16
|
+
"Content-Type: text/plain; charset=utf-8\n"
|
|
17
|
+
"Content-Transfer-Encoding: 8bit\n"
|
|
18
|
+
"Generated-By: Babel 2.18.0\n"
|
|
19
|
+
|
|
20
|
+
#: printer_escpos_driver.py:18
|
|
21
|
+
msgid "Printer"
|
|
22
|
+
msgstr ""
|
|
23
|
+
|
|
24
|
+
#: templates/driver_printer_escpos.html.jinja:7
|
|
25
|
+
msgid "Test communication with printer"
|
|
26
|
+
msgstr ""
|
|
27
|
+
|
|
28
|
+
#: templates/driver_printer_escpos.html.jinja:8
|
|
29
|
+
msgid "Print an image"
|
|
30
|
+
msgstr ""
|
|
31
|
+
|
|
32
|
+
#: templates/driver_printer_escpos.html.jinja:15
|
|
33
|
+
msgid "png, jpg, etc."
|
|
34
|
+
msgstr ""
|
|
35
|
+
|
|
36
|
+
#: templates/driver_printer_escpos.html.jinja:18
|
|
37
|
+
msgid "Print"
|
|
38
|
+
msgstr ""
|
|
39
|
+
|
|
40
|
+
#: templates/driver_printer_escpos.html.jinja:21
|
|
41
|
+
msgid "Open cashbox"
|
|
42
|
+
msgstr ""
|
|
43
|
+
|
|
44
|
+
#: templates/driver_printer_escpos.html.jinja:23
|
|
45
|
+
msgid "Open"
|
|
46
|
+
msgstr ""
|
|
47
|
+
|
|
48
|
+
#: templates/driver_printer_escpos.html.jinja:26
|
|
49
|
+
msgid "Use Photo Booth"
|
|
50
|
+
msgstr ""
|
|
51
|
+
|
|
52
|
+
#: templates/driver_printer_escpos.html.jinja:29
|
|
53
|
+
msgid "Photo Booth"
|
|
54
|
+
msgstr ""
|
|
55
|
+
|
|
56
|
+
#: templates/photo_booth.html.jinja:13
|
|
57
|
+
msgid "Take a photo"
|
|
58
|
+
msgstr ""
|
|
59
|
+
|
odoo_driver_plugin_printer_escpos-1.1.0/odoo_driver_plugin_printer_escpos/printer_escpos_driver.py
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import base64
|
|
2
|
+
from io import BytesIO
|
|
3
|
+
|
|
4
|
+
from escpos.printer import Usb
|
|
5
|
+
from flask_babel import gettext as _
|
|
6
|
+
from odoo_driver.drivers.driver_abstract_with_queue import DriverAbstractWithQueue
|
|
7
|
+
from PIL import Image
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class DriverPrinterEscPos(DriverAbstractWithQueue):
|
|
11
|
+
_driver_function = "printer"
|
|
12
|
+
_plugin_name = "printer_escpos"
|
|
13
|
+
_default_max_queue_size = 3
|
|
14
|
+
_default_delay = 1
|
|
15
|
+
|
|
16
|
+
@property
|
|
17
|
+
def name(self):
|
|
18
|
+
return _("Printer")
|
|
19
|
+
|
|
20
|
+
def _process_task(self, task):
|
|
21
|
+
printer = Usb(self._usb_device.idVendor, self._usb_device.idProduct, 0)
|
|
22
|
+
(action, value) = task.data
|
|
23
|
+
|
|
24
|
+
if action == "print":
|
|
25
|
+
im = Image.open(BytesIO(base64.b64decode(value)))
|
|
26
|
+
printer.image(im)
|
|
27
|
+
printer.cut()
|
|
28
|
+
|
|
29
|
+
elif action == "open_cashbox":
|
|
30
|
+
printer.cashdraw(2)
|
|
31
|
+
|
|
32
|
+
printer.close()
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
from flask import Blueprint, jsonify, request
|
|
2
|
+
from loguru import logger
|
|
3
|
+
from odoo_driver.tools.tools_interface import interface
|
|
4
|
+
|
|
5
|
+
driver_routes_odoo = Blueprint(
|
|
6
|
+
"route_printer_escpos_odoo", __name__, template_folder="templates"
|
|
7
|
+
)
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
@driver_routes_odoo.route("/hw_proxy/default_printer_action", methods=["POST", "PUT"])
|
|
11
|
+
@logger.catch
|
|
12
|
+
def default_printer_action():
|
|
13
|
+
driver = interface.drivers.get("printer")
|
|
14
|
+
data = request.json.get("params", {}).get("data", {})
|
|
15
|
+
|
|
16
|
+
data_copy = data.copy()
|
|
17
|
+
if data_copy.get("action") == "print_receipt":
|
|
18
|
+
data_copy.pop("receipt")
|
|
19
|
+
interface.log_data(data_copy, "printer", "DEBUG")
|
|
20
|
+
|
|
21
|
+
if data.get("action") == "print_receipt":
|
|
22
|
+
receipt = data["receipt"]
|
|
23
|
+
interface.log_image(receipt, "DEBUG")
|
|
24
|
+
driver.add_task(("print", receipt))
|
|
25
|
+
|
|
26
|
+
elif data.get("action") == "cashbox":
|
|
27
|
+
driver.add_task(("open_cashbox", False))
|
|
28
|
+
|
|
29
|
+
else:
|
|
30
|
+
raise Exception(f"Incorrect action value: '{data.get('action')}'")
|
|
31
|
+
|
|
32
|
+
return jsonify(jsonrpc="2.0", result=True)
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import os
|
|
2
|
+
|
|
3
|
+
from flask import Blueprint, render_template, send_from_directory
|
|
4
|
+
from loguru import logger
|
|
5
|
+
from odoo_driver.tools.tools_interface import interface
|
|
6
|
+
|
|
7
|
+
driver_routes_website = Blueprint(
|
|
8
|
+
"route_printer_escpos_website", __name__, template_folder="templates"
|
|
9
|
+
)
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
@driver_routes_website.route("/driver/printer_escpos.html")
|
|
13
|
+
@logger.catch
|
|
14
|
+
def driver_printer_escpos():
|
|
15
|
+
driver = interface.drivers.get("printer")
|
|
16
|
+
return render_template("driver_printer_escpos.html.jinja", driver=driver)
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
@driver_routes_website.route("/printer_escpos/photo_booth.html")
|
|
20
|
+
@logger.catch
|
|
21
|
+
def photo_booth():
|
|
22
|
+
return render_template("photo_booth.html.jinja")
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
@driver_routes_website.route("/printer_escpos/photo_taken.wav")
|
|
26
|
+
@logger.catch
|
|
27
|
+
def printer_escpos_photo_taken():
|
|
28
|
+
return send_from_directory(
|
|
29
|
+
os.path.join(driver_routes_website.root_path, "static", "audio"),
|
|
30
|
+
"photo-taken.wav",
|
|
31
|
+
mimetype="audio/wav",
|
|
32
|
+
)
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
@driver_routes_website.route("/printer_escpos/webcam-easy.js")
|
|
36
|
+
@logger.catch
|
|
37
|
+
def printer_escpos_webcam_easy():
|
|
38
|
+
return send_from_directory(
|
|
39
|
+
os.path.join(driver_routes_website.root_path, "static", "js"),
|
|
40
|
+
"webcam-easy.js",
|
|
41
|
+
mimetype="application/javascript",
|
|
42
|
+
)
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
odoo_driver_plugin_printer_escpos-1.1.0/odoo_driver_plugin_printer_escpos/static/js/webcam-easy.js
ADDED
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
class Webcam {
|
|
2
|
+
constructor(webcamElement, facingMode = 'user', canvasElement = null, snapSoundElement = null) {
|
|
3
|
+
this._webcamElement = webcamElement;
|
|
4
|
+
this._webcamElement.width = this._webcamElement.width || 640;
|
|
5
|
+
this._webcamElement.height = this._webcamElement.height || 360;
|
|
6
|
+
this._facingMode = facingMode;
|
|
7
|
+
this._webcamList = [];
|
|
8
|
+
this._streamList = [];
|
|
9
|
+
this._selectedDeviceId = '';
|
|
10
|
+
this._canvasElement = canvasElement;
|
|
11
|
+
this._snapSoundElement = snapSoundElement;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
get facingMode(){
|
|
15
|
+
return this._facingMode;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
set facingMode(value){
|
|
19
|
+
this._facingMode = value;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
get webcamList(){
|
|
23
|
+
return this._webcamList;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
get webcamCount(){
|
|
27
|
+
return this._webcamList.length;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
get selectedDeviceId(){
|
|
31
|
+
return this._selectedDeviceId;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/* Get all video input devices info */
|
|
35
|
+
getVideoInputs(mediaDevices){
|
|
36
|
+
this._webcamList = [];
|
|
37
|
+
mediaDevices.forEach(mediaDevice => {
|
|
38
|
+
if (mediaDevice.kind === 'videoinput') {
|
|
39
|
+
this._webcamList.push(mediaDevice);
|
|
40
|
+
}
|
|
41
|
+
});
|
|
42
|
+
if(this._webcamList.length == 1){
|
|
43
|
+
this._facingMode = 'user';
|
|
44
|
+
}
|
|
45
|
+
return this._webcamList;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/* Get media constraints */
|
|
49
|
+
getMediaConstraints() {
|
|
50
|
+
var videoConstraints = {};
|
|
51
|
+
if (this._selectedDeviceId == '') {
|
|
52
|
+
videoConstraints.facingMode = this._facingMode;
|
|
53
|
+
} else {
|
|
54
|
+
videoConstraints.deviceId = { exact: this._selectedDeviceId};
|
|
55
|
+
}
|
|
56
|
+
videoConstraints.width = {exact: this._webcamElement.width};
|
|
57
|
+
videoConstraints.height = {exact: this._webcamElement.height};
|
|
58
|
+
var constraints = {
|
|
59
|
+
video: videoConstraints,
|
|
60
|
+
audio: false
|
|
61
|
+
};
|
|
62
|
+
return constraints;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/* Select camera based on facingMode */
|
|
66
|
+
selectCamera(){
|
|
67
|
+
for(let webcam of this._webcamList){
|
|
68
|
+
if( (this._facingMode=='user' && webcam.label.toLowerCase().includes('front'))
|
|
69
|
+
|| (this._facingMode=='enviroment' && webcam.label.toLowerCase().includes('back'))
|
|
70
|
+
)
|
|
71
|
+
{
|
|
72
|
+
this._selectedDeviceId = webcam.deviceId;
|
|
73
|
+
break;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/* Change Facing mode and selected camera */
|
|
79
|
+
flip(){
|
|
80
|
+
this._facingMode = (this._facingMode == 'user')? 'enviroment': 'user';
|
|
81
|
+
this._webcamElement.style.transform = "";
|
|
82
|
+
this.selectCamera();
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/*
|
|
86
|
+
1. Get permission from user
|
|
87
|
+
2. Get all video input devices info
|
|
88
|
+
3. Select camera based on facingMode
|
|
89
|
+
4. Start stream
|
|
90
|
+
*/
|
|
91
|
+
async start(startStream = true) {
|
|
92
|
+
return new Promise((resolve, reject) => {
|
|
93
|
+
this.stop();
|
|
94
|
+
navigator.mediaDevices.getUserMedia(this.getMediaConstraints()) //get permisson from user
|
|
95
|
+
.then(stream => {
|
|
96
|
+
this._streamList.push(stream);
|
|
97
|
+
this.info() //get all video input devices info
|
|
98
|
+
.then(webcams =>{
|
|
99
|
+
this.selectCamera(); //select camera based on facingMode
|
|
100
|
+
if(startStream){
|
|
101
|
+
this.stream()
|
|
102
|
+
.then(facingMode =>{
|
|
103
|
+
resolve(this._facingMode);
|
|
104
|
+
})
|
|
105
|
+
.catch(error => {
|
|
106
|
+
reject(error);
|
|
107
|
+
});
|
|
108
|
+
}else{
|
|
109
|
+
resolve(this._selectedDeviceId);
|
|
110
|
+
}
|
|
111
|
+
})
|
|
112
|
+
.catch(error => {
|
|
113
|
+
reject(error);
|
|
114
|
+
});
|
|
115
|
+
})
|
|
116
|
+
.catch(error => {
|
|
117
|
+
reject(error);
|
|
118
|
+
});
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/* Get all video input devices info */
|
|
123
|
+
async info(){
|
|
124
|
+
return new Promise((resolve, reject) => {
|
|
125
|
+
navigator.mediaDevices.enumerateDevices()
|
|
126
|
+
.then(devices =>{
|
|
127
|
+
this.getVideoInputs(devices);
|
|
128
|
+
resolve(this._webcamList);
|
|
129
|
+
})
|
|
130
|
+
.catch(error => {
|
|
131
|
+
reject(error);
|
|
132
|
+
});
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/* Start streaming webcam to video element */
|
|
137
|
+
async stream() {
|
|
138
|
+
return new Promise((resolve, reject) => {
|
|
139
|
+
navigator.mediaDevices.getUserMedia(this.getMediaConstraints())
|
|
140
|
+
.then(stream => {
|
|
141
|
+
this._streamList.push(stream);
|
|
142
|
+
this._webcamElement.srcObject = stream;
|
|
143
|
+
if(this._facingMode == 'user'){
|
|
144
|
+
this._webcamElement.style.transform = "scale(-1,1)";
|
|
145
|
+
}
|
|
146
|
+
this._webcamElement.play();
|
|
147
|
+
resolve(this._facingMode);
|
|
148
|
+
})
|
|
149
|
+
.catch(error => {
|
|
150
|
+
console.log(error);
|
|
151
|
+
reject(error);
|
|
152
|
+
});
|
|
153
|
+
});
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/* Stop streaming webcam */
|
|
157
|
+
stop() {
|
|
158
|
+
this._streamList.forEach(stream => {
|
|
159
|
+
stream.getTracks().forEach(track => {
|
|
160
|
+
track.stop();
|
|
161
|
+
});
|
|
162
|
+
});
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
snap() {
|
|
166
|
+
if(this._canvasElement!=null){
|
|
167
|
+
if(this._snapSoundElement!= null){
|
|
168
|
+
this._snapSoundElement.play();
|
|
169
|
+
}
|
|
170
|
+
this._canvasElement.height = this._webcamElement.scrollHeight;
|
|
171
|
+
this._canvasElement.width = this._webcamElement.scrollWidth;
|
|
172
|
+
let context = this._canvasElement.getContext('2d');
|
|
173
|
+
if(this._facingMode == 'user'){
|
|
174
|
+
context.translate(this._canvasElement.width, 0);
|
|
175
|
+
context.scale(-1, 1);
|
|
176
|
+
}
|
|
177
|
+
context.clearRect(0, 0, this._canvasElement.width, this._canvasElement.height);
|
|
178
|
+
context.drawImage(this._webcamElement, 0, 0, this._canvasElement.width, this._canvasElement.height);
|
|
179
|
+
let data = this._canvasElement.toDataURL('image/png');
|
|
180
|
+
return data;
|
|
181
|
+
}
|
|
182
|
+
else{
|
|
183
|
+
throw "canvas element is missing";
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
|
|
3
|
+
{% extends "layout.html.jinja" %}
|
|
4
|
+
|
|
5
|
+
{% block content %}
|
|
6
|
+
|
|
7
|
+
<h1>{{_('Test communication with printer')}}</h1>
|
|
8
|
+
<h2>{{_('Print an image')}}</h2>
|
|
9
|
+
|
|
10
|
+
<div class="input-group alert alert-danger" id="alert_no_file" role="alert">
|
|
11
|
+
This is a danger alert—check it out!
|
|
12
|
+
</div>
|
|
13
|
+
<div class="input-group">
|
|
14
|
+
<input type="file" class="form-control" id="print_file" autofocus/>
|
|
15
|
+
<span class="input-group-text">{{_('png, jpg, etc.')}}</span>
|
|
16
|
+
</div>
|
|
17
|
+
<div class="input-group">
|
|
18
|
+
<button type="button" class="btn {%if driver.is_connected %}btn-primary{% else %}btn-secondary{% endif %}" onclick="test_print(); return false;">{{_('Print')}}</button>
|
|
19
|
+
</div>
|
|
20
|
+
|
|
21
|
+
<h2>{{_('Open cashbox')}}</h2>
|
|
22
|
+
<div class="input-group">
|
|
23
|
+
<button type="button" class="btn {%if driver.is_connected %}btn-primary{% else %}btn-secondary{% endif %}" onclick="test_open_cashbox(); return false;">{{_('Open')}}</button>
|
|
24
|
+
</div>
|
|
25
|
+
|
|
26
|
+
<h2>{{_('Use Photo Booth')}}</h2>
|
|
27
|
+
<div class="input-group">
|
|
28
|
+
|
|
29
|
+
<a class="btn {%if driver.is_connected %}btn-primary{% else %}btn-secondary{% endif %}" href="/printer_escpos/photo_booth.html" role="button">{{_('Photo Booth')}}</a>
|
|
30
|
+
</div>
|
|
31
|
+
|
|
32
|
+
<script>
|
|
33
|
+
|
|
34
|
+
/* ***************************
|
|
35
|
+
Device Printer
|
|
36
|
+
******************************/
|
|
37
|
+
$( document ).ready(function() {
|
|
38
|
+
$("#alert_no_file").hide();
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
function test_print() {
|
|
42
|
+
const myFile = $('#print_file').prop('files')[0];
|
|
43
|
+
const reader = new FileReader();
|
|
44
|
+
if (myFile) {
|
|
45
|
+
$("#alert_no_file").hide();
|
|
46
|
+
reader.readAsDataURL(myFile);
|
|
47
|
+
reader.onload = (event) => {
|
|
48
|
+
const data = {
|
|
49
|
+
params: {
|
|
50
|
+
data: {
|
|
51
|
+
action: "print_receipt",
|
|
52
|
+
receipt: reader.result.split(",")[1],
|
|
53
|
+
},
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
_call_ajax("/hw_proxy/default_printer_action", "printer", data);
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
else {
|
|
60
|
+
$("#alert_no_file").show();
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function test_open_cashbox() {
|
|
65
|
+
const data = {
|
|
66
|
+
params: {
|
|
67
|
+
data: {action: "cashbox"},
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
_call_ajax("/hw_proxy/default_printer_action", "printer", data)
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
</script>
|
|
75
|
+
|
|
76
|
+
{% endblock %}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
|
|
3
|
+
{% extends "layout.html.jinja" %}
|
|
4
|
+
|
|
5
|
+
{% block header_content %}
|
|
6
|
+
<script src="/printer_escpos/webcam-easy.js"></script>
|
|
7
|
+
{% endblock %}
|
|
8
|
+
|
|
9
|
+
{% block content %}
|
|
10
|
+
|
|
11
|
+
<div class="container-fluid">
|
|
12
|
+
<div class="row">
|
|
13
|
+
<button type="button" class="btn btn-primary" onclick="web_cam_snap(); return false;" autofocus>{{_('Take a photo')}}</button>
|
|
14
|
+
<div class="col-lg-6">
|
|
15
|
+
<video id="webcam" autoplay playsinline width="640" height="480"></video>
|
|
16
|
+
</div>
|
|
17
|
+
<div class="col-lg-6">
|
|
18
|
+
<canvas id="canvas"></canvas>
|
|
19
|
+
<audio id="snapSound" src="/printer_escpos/photo_taken.wav" preload = "auto"></audio>
|
|
20
|
+
</div>
|
|
21
|
+
</div>
|
|
22
|
+
</div>
|
|
23
|
+
<script>
|
|
24
|
+
|
|
25
|
+
var webcam;
|
|
26
|
+
|
|
27
|
+
$( document ).ready(function() {
|
|
28
|
+
const webcamElement = document.getElementById('webcam');
|
|
29
|
+
const canvasElement = document.getElementById('canvas');
|
|
30
|
+
const snapSoundElement = document.getElementById('snapSound');
|
|
31
|
+
webcam = new Webcam(webcamElement, 'user', canvasElement, snapSoundElement);
|
|
32
|
+
|
|
33
|
+
webcam.start()
|
|
34
|
+
.then(result =>{
|
|
35
|
+
console.log("webcam started");
|
|
36
|
+
})
|
|
37
|
+
.catch(err => {
|
|
38
|
+
console.log(err);
|
|
39
|
+
});
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
function web_cam_snap() {
|
|
44
|
+
let picture = webcam.snap();
|
|
45
|
+
$('#download-photo').href = picture;
|
|
46
|
+
var data = {
|
|
47
|
+
params: {
|
|
48
|
+
data: {
|
|
49
|
+
action: "print_receipt",
|
|
50
|
+
receipt: picture.split(",")[1],
|
|
51
|
+
},
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
_call_ajax("/hw_proxy/default_printer_action", "printer", data);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
</script>
|
|
58
|
+
|
|
59
|
+
{% endblock %}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "odoo-driver-plugin-printer-escpos"
|
|
3
|
+
version = "1.1.0"
|
|
4
|
+
description = """Plugin for odoo-driver. Support escpos printer devices. (Epson ®, ...)"""
|
|
5
|
+
authors = [
|
|
6
|
+
{name = "Sylvain LE GAL", email = "sylvain.legal@grap.coop"},
|
|
7
|
+
]
|
|
8
|
+
maintainers = [
|
|
9
|
+
{name = "Sylvain LE GAL", email = "sylvain.legal@grap.coop"},
|
|
10
|
+
]
|
|
11
|
+
readme = "README.md"
|
|
12
|
+
license = "AGPLv3+"
|
|
13
|
+
# packages = [{include = "odoo_driver"}]
|
|
14
|
+
keywords = ["Odoo", "Point Of Sale", "Driver", "Device", "Printer", " ESC/POS", "escpos", "Epson ®"]
|
|
15
|
+
classifiers = [
|
|
16
|
+
"License :: OSI Approved :: GNU Affero General Public License v3 or later (AGPLv3+)",
|
|
17
|
+
"Intended Audience :: Developers",
|
|
18
|
+
"Development Status :: 3 - Alpha",
|
|
19
|
+
"Operating System :: Unix",
|
|
20
|
+
"Programming Language :: Python :: 3.9",
|
|
21
|
+
"Programming Language :: Python :: 3.10",
|
|
22
|
+
"Programming Language :: Python :: 3.11",
|
|
23
|
+
"Programming Language :: Python :: 3.12",
|
|
24
|
+
"Programming Language :: Python :: 3.13",
|
|
25
|
+
"Programming Language :: Python :: 3.14",
|
|
26
|
+
"Framework :: Odoo",
|
|
27
|
+
]
|
|
28
|
+
|
|
29
|
+
requires-python = ">=3.9,<4.0"
|
|
30
|
+
dependencies = [
|
|
31
|
+
"odoo-driver",
|
|
32
|
+
"python-escpos",
|
|
33
|
+
]
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
[project.urls]
|
|
37
|
+
repository = "https://gitlab.com/grap-rhone-alpes/odoo-driver-plugin-printer-escpos"
|
|
38
|
+
|
|
39
|
+
[project.entry-points.'odoo_driver.plugins']
|
|
40
|
+
printer_escpos = 'odoo_driver_plugin_printer_escpos'
|
|
41
|
+
|
|
42
|
+
[build-system]
|
|
43
|
+
requires = ["poetry-core"]
|
|
44
|
+
build-backend = "poetry.core.masonry.api"
|
|
45
|
+
|
|
46
|
+
[tool.black]
|
|
47
|
+
line-length = 88
|
|
48
|
+
|
|
49
|
+
[tool.isort]
|
|
50
|
+
profile = "black"
|
|
51
|
+
line_length = 88
|