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.
Files changed (19) hide show
  1. odoo_driver_plugin_printer_escpos-1.1.0/PKG-INFO +105 -0
  2. odoo_driver_plugin_printer_escpos-1.1.0/README.md +77 -0
  3. odoo_driver_plugin_printer_escpos-1.1.0/odoo_driver_plugin_printer_escpos/__init__.py +17 -0
  4. odoo_driver_plugin_printer_escpos-1.1.0/odoo_driver_plugin_printer_escpos/i18n/babel.cfg +2 -0
  5. odoo_driver_plugin_printer_escpos-1.1.0/odoo_driver_plugin_printer_escpos/i18n/fr/LC_MESSAGES/messages.mo +0 -0
  6. odoo_driver_plugin_printer_escpos-1.1.0/odoo_driver_plugin_printer_escpos/i18n/fr/LC_MESSAGES/messages.po +60 -0
  7. odoo_driver_plugin_printer_escpos-1.1.0/odoo_driver_plugin_printer_escpos/i18n/messages.pot +59 -0
  8. odoo_driver_plugin_printer_escpos-1.1.0/odoo_driver_plugin_printer_escpos/printer_escpos_driver.py +32 -0
  9. odoo_driver_plugin_printer_escpos-1.1.0/odoo_driver_plugin_printer_escpos/printer_escpos_route_odoo.py +32 -0
  10. odoo_driver_plugin_printer_escpos-1.1.0/odoo_driver_plugin_printer_escpos/printer_escpos_route_website.py +42 -0
  11. odoo_driver_plugin_printer_escpos-1.1.0/odoo_driver_plugin_printer_escpos/static/audio/photo-taken.wav +0 -0
  12. odoo_driver_plugin_printer_escpos-1.1.0/odoo_driver_plugin_printer_escpos/static/img/epson__tm_t20.png +0 -0
  13. odoo_driver_plugin_printer_escpos-1.1.0/odoo_driver_plugin_printer_escpos/static/img/epson__tm_t88.png +0 -0
  14. odoo_driver_plugin_printer_escpos-1.1.0/odoo_driver_plugin_printer_escpos/static/img/generic_device.png +0 -0
  15. odoo_driver_plugin_printer_escpos-1.1.0/odoo_driver_plugin_printer_escpos/static/js/webcam-easy.js +186 -0
  16. odoo_driver_plugin_printer_escpos-1.1.0/odoo_driver_plugin_printer_escpos/templates/driver_printer_escpos.html.jinja +76 -0
  17. odoo_driver_plugin_printer_escpos-1.1.0/odoo_driver_plugin_printer_escpos/templates/photo_booth.html.jinja +59 -0
  18. odoo_driver_plugin_printer_escpos-1.1.0/odoo_driver_plugin_printer_escpos/usb_devices.py +5 -0
  19. 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
+ ![License: AGPL v3](https://img.shields.io/badge/License-AGPL_v3-blue.svg)
31
+ ![PyPI - Python Version](https://img.shields.io/pypi/pyversions/odoo-driver-plugin-printer-escpos)
32
+ ![PyPI - Downloads](https://img.shields.io/pypi/dm/odoo-driver-plugin-printer-escpos)
33
+ ![GitLab last commit](https://img.shields.io/gitlab/last-commit/81118598)
34
+ ![GitLab stars](https://img.shields.io/gitlab/stars/81118598?style=social)
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
+ ![License: AGPL v3](https://img.shields.io/badge/License-AGPL_v3-blue.svg)
4
+ ![PyPI - Python Version](https://img.shields.io/pypi/pyversions/odoo-driver-plugin-printer-escpos)
5
+ ![PyPI - Downloads](https://img.shields.io/pypi/dm/odoo-driver-plugin-printer-escpos)
6
+ ![GitLab last commit](https://img.shields.io/gitlab/last-commit/81118598)
7
+ ![GitLab stars](https://img.shields.io/gitlab/stars/81118598?style=social)
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]
@@ -0,0 +1,2 @@
1
+ [python: **.py]
2
+ [jinja2: **/templates/**.html.jinja]
@@ -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
+
@@ -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
+ )
@@ -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,5 @@
1
+ USB_DEVICES = {
2
+ (0x04B8, 0x0E15): {"name": "Epson - TM-T20II", "image": "epson__tm_t20.png"},
3
+ (0x04B8, 0x0E28): {"name": "Epson - TM-T20III", "image": "epson__tm_t20.png"},
4
+ (0x04B8, 0x0202): {"name": "Epson - TM-T88V", "image": "epson__tm_t88.png"},
5
+ }
@@ -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