Compare commits

...

6 Commits

Author SHA1 Message Date
nono
8ecb93656c Change signature 2025-10-16 18:39:29 +02:00
nono
eac737b889 Remove flash from the printer class, update web class 2025-10-16 18:36:16 +02:00
nono
dfeb1be0f0 update raspberry pi code 2025-10-16 18:35:57 +02:00
nono
0eefafdeb2 Add configuration ports for the flash 2025-10-16 18:35:35 +02:00
nono
ce70d498ca Add requirements 2025-10-16 18:35:27 +02:00
nono
991f6794c7 Update readme with more deps 2025-10-16 18:35:16 +02:00
8 changed files with 342 additions and 100 deletions

View File

@ -20,7 +20,7 @@ To make this project work, you will need :
- Some knowledge of the command line, - Some knowledge of the command line,
- Some knowledge of Python. - Some knowledge of Python.
- 3h of your time, 5h if things need debugging. - 3h of your time, 5h if things need debugging.
- `git`, `virtualenv`,`pip` and `python` >= 3.8.6. - `fswebcam`, `libjpeg-dev` ,`zlib1g-dev`,`libffi-dev`,`git`, `virtualenv`,`pip` and `python` >= 3.8.6.
- A webcam for the webcam page to work. Will work on a smartphone. Not required. - A webcam for the webcam page to work. Will work on a smartphone. Not required.
## Context ## Context

View File

@ -8,8 +8,9 @@ upload_folder = "src/static/uploads"
# Raspberry Pi Configuration # Raspberry Pi Configuration
[rpi] [rpi]
button_gpio_port_number = 17 button_gpio_port_number = 2
indicator_gpio_port_number = 18 indicator_gpio_port_number = 22
flash_gpio_port_number = 16
flash = true flash = true
# Users = Password # Users = Password

View File

@ -1,36 +1,261 @@
Adafruit_Thermal>=1.1.0 appdirs==1.4.4
appdirs>=1.4.4 apt-listchanges==4.8
argcomplete>=2.0.6 argcomplete==3.6.2
cffi>=1.17.1 arrow==1.3.0
click>=8.1.8 attrs==25.3.0
commonmark>=0.9.1 babel==2.17.0
cryptography>=45.0.4 bcrypt==4.2.0
Deprecated>=1.2.18 bidict==0.23.1
escpos>=2.0.0 blinker==1.9.0
Flask>=2.1.3 certifi==2025.1.31
Flask-Limiter>=2.4.5.1 cffi==1.17.1
future>=0.18.3 chardet==5.2.0
itsdangerous>=2.1.2 charset-normalizer==3.4.2
Jinja2>=3.1.6 click==8.3.0
limits>=2.6.3 cloud-init==25.2
MarkupSafe>=2.1.5 colorzero==2.0
numpy>=2.3.0 configobj==5.0.9
packaging>=21.3 cryptography==43.0.0
pillow>=11.2.1 Deprecated==1.2.18
pycparser>=2.22 distlib==0.3.9
Pygments>=2.12.0 distro==1.9.0
pyparsing>=3.0.9 escpos==2.0.0
pyserial>=3.5 filelock==3.18.0
python-barcode>=0.13.1 Flask==3.1.2
pyusb>=1.2.1 Flask-Limiter==4.0.0
PyYAML>=6.0.2 Flask-SocketIO==5.5.1
qrcode>=7.3.1 fqdn==1.5.1
rich>=12.4.4 future==1.0.0
setuptools>=80.9.0 gpiod==2.2.0
six>=1.16.0 gpiozero==2.0.1
toml>=0.10.2 h11==0.16.0
typing_extensions>=4.2.0 idna==3.10
Unidecode>=1.3.8 isoduration==20.11.0
viivakoodi>=0.8.0 itsdangerous==2.2.0
Werkzeug>=2.1.2 Jinja2==3.1.6
wrapt>=1.14.1 jsonpatch==1.32
jsonpointer==2.4
jsonschema==4.19.2
jsonschema-specifications==2023.12.1
lgpio==0.2.2.0
limits==5.6.0
linkify-it-py==2.0.3
markdown-it-py==3.0.0
MarkupSafe==2.1.5
mdurl==0.1.2
numpy==2.3.4
oauthlib==3.2.2
olefile==0.47
ordered-set==4.1.0
packaging==25.0
pillow==11.1.0
platformdirs==4.3.7
ply==3.11
pycparser==2.22
pycryptodomex==3.20.0
Pygments==2.18.0
PyJWT==2.10.1
pyserial==3.5
python-apt==3.0.0
python-barcode==0.16.1
python-dateutil==2.9.0
python-engineio==4.12.3
python-socketio==5.14.1
pyusb==1.3.1
PyYAML==6.0.2
qrcode==8.2
referencing==0.36.2
requests==2.32.3
rfc3339-validator==0.1.4
rfc3986-validator==0.1.1
rfc3987==1.3.8
rich==13.9.4
rpds-py==0.21.0
rpi-keyboard-config==1.0
rpi-lgpio==0.6
setuptools==80.9.0
simple-websocket==1.1.0
six==1.17.0
smbus2==0.4.3
spidev==3.6
ssh-import-id==5.10
toml==0.10.2
types-aiofiles==24.1
types-antlr4-python3-runtime==4.13
types-assertpy==1.1
types-atheris==2.3
types-aws-xray-sdk==2.14
types-beautifulsoup4==4.12
types-bleach==6.2
types-boltons==24.1
types-braintree==4.31
types-cachetools==5.5
types-caldav==1.3
types-capturer==3.0
types-cffi==1.16
types-chevron==0.14
types-click-default-group==1.2
types-click-spinner==0.1
types-colorama==0.4
types-commonmark==0.9
types-console-menu==0.8
types-corus==0.10
types-croniter==5.0.1
types-dateparser==1.2
types-decorator==5.1
types-defusedxml==0.7
types-Deprecated==1.2.15
types-docker==7.1
types-dockerfile-parse==2.0
types-docutils==0.21
types-editdistance==0.8
types-entrypoints==0.4
types-ExifRead==3.0
types-fanstatic==1.4
types-first==2.0
types-flake8==7.1
types-flake8-bugbear==24.12.12
types-flake8-builtins==2.5
types-flake8-docstrings==1.7
types-flake8-rst-docstrings==0.3
types-flake8-simplify==0.21
types-flake8-typing-imports==1.16
types-Flask-Cors==5.0
types-Flask-Migrate==4.0
types-Flask-SocketIO==5.4
types-fpdf2==2.8.2
types-gdb==15.0
types-gevent==24.11
types-google-cloud-ndb==2.3
types-greenlet==3.1
types-hdbcli==2.23
types-html5lib==1.1
types-httplib2==0.22
types-humanfriendly==10.0
types-hvac==2.3
types-ibm-db==3.2.4
types-icalendar==6.1
types-influxdb-client==1.45
types-inifile==0.4
types-JACK-Client==0.5
types-Jetson.GPIO==2.1
types-jmespath==1.0
types-jsonschema==4.23
types-jwcrypto==1.5
types-keyboard==0.13
types-ldap3==2.9
types-libsass==0.23
types-lupa==2.2
types-lzstring==1.0
types-m3u8==6.0
types-Markdown==3.7
types-mock==5.1
types-mypy-extensions==1.0
types-mysqlclient==2.2
types-nanoid==2.0.0
types-netaddr==1.3
types-netifaces==0.11
types-networkx==3.4.2
types-oauthlib==3.2
types-objgraph==3.6
types-olefile==0.47
types-openpyxl==3.1.5
types-opentracing==2.4
types-paramiko==3.5
types-parsimonious==0.10
types-passlib==1.7
types-passpy==1.0
types-peewee==3.17.8
types-pep8-naming==0.14
types-pexpect==4.9
types-pika-ts==1.3
types-polib==1.2
types-portpicker==1.6
types-protobuf==5.29.1
types-psutil==6.1
types-psycopg2==2.9.10
types-pyasn1==0.6
types-pyaudio==0.2
types-PyAutoGUI==0.9
types-pycocotools==2.0
types-pycurl==7.45.4
types-pyfarmhash==0.4
types-pyflakes==3.2
types-pygit2==1.15
types-Pygments==2.18
types-pyinstaller==6.11
types-pyjks==20.0
types-PyMySQL==1.1
types-pynput==1.7.7
types-pyOpenSSL==24.1
types-pyRFC3339==2.0.1
types-PyScreeze==1.0.1
types-pyserial==3.5
types-pysftp==0.2
types-pytest-lazy-fixture==0.6
types-python-crontab==3.2
types-python-datemath==3.0.1
types-python-dateutil==2.9
types-python-http-client==3.3.7
types-python-jenkins==1.8
types-python-jose==3.3
types-python-nmap==0.7
types-python-xlib==0.33
types-pytz==2024.2
types-pywin32==308
types-pyxdg==0.28
types-PyYAML==6.0
types-qrbill==1.1
types-qrcode==8.0
types-regex==2024.11.6
types-reportlab==4.2.5
types-requests==2.32
types-requests-oauthlib==2.0
types-retry==0.9
types-RPi.GPIO==0.7
types-s2clientprotocol==5
types-seaborn==0.13.2
types-Send2Trash==1.8
types-setuptools==75.6
types-shapely==2.0
types-simplejson==3.19
types-singledispatch==4.1
types-six==1.17
types-slumber==0.7
types-str2bool==1.1
types-tabulate==0.9
types-tensorflow==2.18.0
types-TgCrypto==1.2
types-toml==0.10
types-toposort==1.10
types-tqdm==4.67
types-translationstring==1.4
types-tree-sitter-languages==1.10
types-ttkthemes==3.2
types-ujson==5.10
types-unidiff==0.7
types-untangle==1.2
types-usersettings==1.1
types-uWSGI==2.0
types-vobject==0.9.9
types-waitress==3.0.1
types-WebOb==1.8
types-whatthepatch==1.0
types-workalendar==17.0
types-WTForms==3.2.1
types-wurlitzer==3.1
types-xdgenvpy==3.0
types-xmltodict==0.14
types-zstd==1.5
types-zxcvbn==4.4
typing_extensions==4.15.0
uc-micro-py==1.0.3
uritemplate==4.1.1
urllib3==2.3.0
viivakoodi==0.8.0
virtualenv==20.31.2
webcolors==1.13
Werkzeug==3.1.3
wheel==0.46.1
wrapt==1.17.3
wsproto==1.2.0

View File

@ -77,10 +77,14 @@ printer = Printer(app,0x04b8, 0x0e28)
printer.init_printer() printer.init_printer()
# Find out if we are running on a Raspberry Pi # Find out if we are running on a Raspberry Pi
rpi = Raspberry(printer, app, socketio, configuration_file['rpi']['button_gpio_port_number'], configuration_file['rpi']['indicator_gpio_port_number'], configuration_file['rpi']['flash'] ) rpi = Raspberry(printer,
app,
socketio,
configuration_file['rpi']['button_gpio_port_number'], configuration_file['rpi']['indicator_gpio_port_number'],
configuration_file['rpi']['flash_gpio_port_number'],
configuration_file['rpi']['flash'] )
RASPBERRY_PI_CONNECTED = rpi.is_raspberry_pi() RASPBERRY_PI_CONNECTED = rpi.is_raspberry_pi()
if RASPBERRY_PI_CONNECTED:
rpi.initialise_gpio()
############################################################# #############################################################
@ -91,7 +95,7 @@ if RASPBERRY_PI_CONNECTED:
web = Web(app, printer) web = Web(app, printer)
if __name__ == "__main__": if __name__ == "__main__":
app.run(ssl_context='adhoc') app.run(debug=True, use_reloader=False, host='0.0.0.0', ssl_context='adhoc')
limiter = Limiter( limiter = Limiter(
get_remote_address, get_remote_address,

View File

@ -1,5 +1,4 @@
# Importing the module to mage the connection to the printer. # Importing the module to mage the connection to the printer.
from flask import flash
from escpos.printer import Usb, USBNotFoundError from escpos.printer import Usb, USBNotFoundError
from time import sleep, gmtime, strftime from time import sleep, gmtime, strftime
import os.path import os.path
@ -46,12 +45,10 @@ class Printer(object):
match status: match status:
case 0: case 0:
self.app.logger.error('Printer has no more paper, aborting...') self.app.logger.error('Printer has no more paper, aborting...')
flash("No more paper on the printer. Sorry.",category='error')
self.printer.close() self.printer.close()
raise Exception("No more paper in the printer") raise Exception("No more paper in the printer")
case 1: case 1:
self.app.logger.warning('Printer needs paper to be changed very soon ! ') self.app.logger.warning('Printer needs paper to be changed very soon ! ')
flash('Printer needs paper to be changed very soon ! ', category='info')
self.printer.close() self.printer.close()
case 2: case 2:
self.app.logger.debug('Printer has paper, good to go') self.app.logger.debug('Printer has paper, good to go')
@ -101,7 +98,7 @@ class Printer(object):
return True return True
def print_sms(self, msg, signature) -> None: def print_sms(self, msg, signature="Guest") -> None:
clean_msg = str(msg) clean_msg = str(msg)
clean_signature = str(signature) clean_signature = str(signature)
@ -109,7 +106,7 @@ class Printer(object):
self.app.logger.warning("Could not print message of this length: " + str(len(clean_msg))) self.app.logger.warning("Could not print message of this length: " + str(len(clean_msg)))
raise Exception("Could not print message of this length :" + str(len(clean_msg)) + ", needs to between 3 and 4096 caracters long.") raise Exception("Could not print message of this length :" + str(len(clean_msg)) + ", needs to between 3 and 4096 caracters long.")
if len(signature) > 256 or len(signature) < 3: if len(signature) > 256 or len(signature) < 1:
self.app.logger.warning("Could not print signature of this length: " + str(len(clean_signature))) self.app.logger.warning("Could not print signature of this length: " + str(len(clean_signature)))
raise Exception("Could not print signature of this length :" + str(len(clean_signature)) + ", needs to between 3 and 256 caracters long.") raise Exception("Could not print signature of this length :" + str(len(clean_signature)) + ", needs to between 3 and 256 caracters long.")
@ -133,13 +130,12 @@ class Printer(object):
self.app.logger.error("Unable to print because : " + str(e)) self.app.logger.error("Unable to print because : " + str(e))
raise e raise e
flash("Message printed : " + clean_msg ,category='info')
return True return True
def print_img(self, path, sign) -> None: def print_img(self, path, sign) -> None:
clean_signature = str(sign) clean_signature = str(sign)
if len(sign) > 256 or len(sign) < 3: if len(sign) > 256 or len(sign) < 1:
self.app.logger.warning("Could not print signature of this length: " + str(len(clean_signature))) self.app.logger.warning("Could not print signature of this length: " + str(len(clean_signature)))
raise Exception("Could not print signature of this length :" + str(len(clean_signature)) + ", needs to between 3 and 256 caracters long.") raise Exception("Could not print signature of this length :" + str(len(clean_signature)) + ", needs to between 3 and 256 caracters long.")
@ -160,10 +156,10 @@ class Printer(object):
try: try:
self.check_paper() self.check_paper()
self.printer.open(self.usb_args) self.printer.open(self.usb_args)
self.printer.textln("Printed by LittlePrinter ")
self.printer.textln("n07070.xyz/articles/littleprynter")
self.printer.textln("> " + clean_signature + " @ " + strftime("%Y-%m-%d %H:%M:%S", gmtime()))
self.printer.image(path) self.printer.image(path)
self.printer.textln("Printed by LittlePrynter ")
self.printer.textln("n07070.xyz/articles/littleprynter")
self.printer.textln(clean_signature + " @ " + strftime("%Y-%m-%d %H:%M:%S", gmtime()))
self.printer.cut() self.printer.cut()
self.printer.close() self.printer.close()
self.app.logger.debug("Printed an image : " + str(path)) self.app.logger.debug("Printed an image : " + str(path))
@ -214,7 +210,7 @@ def process_image(self, path):
# Final resize check # Final resize check
if original_img.height > max_height: if original_img.height > max_height:
flash("Image is too long, sorry! Keep it below 575×1000 pixels.", 'error') raise ValueError("Image is too long, sorry! Keep it below 575×1000 pixels.")
self.app.logger.error("Image is too long, sorry! Keep it below 575×1000 pixels.") self.app.logger.error("Image is too long, sorry! Keep it below 575×1000 pixels.")
return False return False

View File

@ -1,5 +1,5 @@
from flask_socketio import SocketIO from flask_socketio import SocketIO
from gpiozero import Button, LED from gpiozero import Button, LED, DigitalOutputDevice
from time import sleep from time import sleep
import io # To check if we are on a Raspberry Pi import io # To check if we are on a Raspberry Pi
import subprocess import subprocess
@ -13,24 +13,17 @@ class Raspberry(object):
- Flash an indicator light - Flash an indicator light
""" """
def __init__(self, printer, app, socketio, button_gpio_port_number, indicator_gpio_port_number, is_flash_present,): def __init__(self, printer, app, socketio, button_gpio_port_number, indicator_gpio_port_number, flash_gpio_port_number, is_flash_present,):
self.printer = printer self.printer = printer
self.socketio = socketio self.socketio = socketio
self.app = app self.app = app
self.flash_gpio = flash_gpio_port_number
self.is_flash_present = is_flash_present self.is_flash_present = is_flash_present
self.button_gpio = button_gpio_port_number self.button_gpio = button_gpio_port_number
self.led_gpio = indicator_gpio_port_number self.led_gpio = indicator_gpio_port_number
self.image_path = self.app.config['UPLOAD_FOLDER'] + '/image.jpg' self.image_path = self.app.config['UPLOAD_FOLDER'] + '/image.jpg'
def initialise_gpio(self):
self.button = Button(self.button_gpio)
self.led = LED(self.led_gpio)
button.when_pressed = self.on_button_pressed()
def is_raspberry_pi(self, raise_on_errors=False): def is_raspberry_pi(self, raise_on_errors=False):
# Check if we are running on a raspberry pi # Check if we are running on a raspberry pi
try: try:
@ -59,48 +52,71 @@ class Raspberry(object):
return False return False
self.app.logger.debug('It seems we are on a Raspberry Pi') self.app.logger.debug('It seems we are on a Raspberry Pi')
return True
def take_picture(self):
# return a path to a picture
try: try:
subprocess.run(['fswebcam', '--no-banner', self.image_path]) self.initialise_gpio()
except Exception as e: except Exception as e:
self.app.logger.error('Unable to take a picture :' + e) self.app.logger.debug('Could not init GPIO : ' + str(e))
raise e raise e
return True return True
def flash(state): def initialise_gpio(self):
if state: self.app.logger.debug('Initializing GPIO')
# turn on the flash
pass
else:
# turn off the flash
pass
def flash_indicator(self): self.led = LED(self.led_gpio)
# The indicator will flash three times before return true self.app.logger.debug('Activated indicator LED')
self.led.on()
sleep(0.1)
self.led.off()
sleep(1)
self.led.on()
sleep(0.1)
self.led.off()
sleep(1)
self.led.on()
sleep(0.1)
self.led.off()
def on_button_pressed(): self.button = Button(self.button_gpio, pull_up=True, bounce_time=0.1)
self.button.when_pressed = self.on_button_pressed
self.app.logger.debug('Activated button')
# The "flash" is a relay-controlled device ( light bulb for example )
self.flash = DigitalOutputDevice(self.flash_gpio)
self.app.logger.debug('Activated flash')
def take_picture(self):
try:
subprocess.run(['fswebcam', '--no-banner', self.image_path])
except Exception as e:
self.app.logger.error('Unable to take a picture :' + str(e))
raise e
return True
def indicator_countdown(self,iters=10,multi=10):
for i in range(iters,0,-1):
self.led.on()
sleep(i/multi)
self.led.off()
sleep(i/multi)
def indicator_led(self,timing=0.2,l=5):
for i in range(l):
self.app.logger.debug("LED turned on")
self.led.on()
sleep(timing)
self.led.off()
self.app.logger.debug("LED turned off")
sleep(timing)
def flash_toggle(self):
self.app.logger.debug("Flash turned on")
self.flash.on()
sleep(0.3)
self.flash.off()
self.app.logger.debug("Flash turned off")
def on_button_pressed(self):
self.app.logger.debug("Button has been pressed")
self.socketio.emit('button_pressed') # Notify clients that a button has been pressed on the raspberry pi self.socketio.emit('button_pressed') # Notify clients that a button has been pressed on the raspberry pi
self.flash_indicator() # The indicator will flash a countdown LED self.indicator_countdown(iters=5,multi=20) # The indicator will flash a countdown LED
self.flash(True) self.flash_toggle()
try: try:
self.take_picture() self.take_picture()
self.printer.print_image(self.image_path) self.printer.print_image(self.image_path)
except Exception as e:
app.logger.error("Could not take a picture after the button press : " + e)
self.led.on()
self.flash(False)
self.socketio.emit('picture_taken') # Notify clients that a picture has been taken self.socketio.emit('picture_taken') # Notify clients that a picture has been taken
except Exception as e:
self.app.logger.error("Could not take a picture after the button press : " + str(e))
return True

View File

@ -244,7 +244,7 @@ function print_picture(data){
let time = currentDate.getHours() + ":" + currentDate.getMinutes() + ":" + currentDate.getSeconds(); let time = currentDate.getHours() + ":" + currentDate.getMinutes() + ":" + currentDate.getSeconds();
formData.set("img", picture, "picture.png"); formData.set("img", picture, "picture.png");
formData.set("signature", " Accès Libre ~ 1 An ~ Fête de la Musique Libre @ " + time) formData.set("signature", "Webcam")
fetch(url, { fetch(url, {
method: 'POST', // or 'PUT' method: 'POST', // or 'PUT'

View File

@ -21,7 +21,7 @@ class Web(object):
self.app.logger.error(e) self.app.logger.error(e)
flash("Error while printing the SMS : "+ str(e)) flash("Error while printing the SMS : "+ str(e))
flash("You message has been printed :)") flash("You message " + str( texte ) + " has been printed :)")
def print_image(self, image, sign: str): def print_image(self, image, sign: str):
@ -57,7 +57,7 @@ class Web(object):
self.app.logger.debug("File saved") self.app.logger.debug("File saved")
except Exception as e: except Exception as e:
self.app.logger.error("Could not save file") self.app.logger.error("Could not save file")
flash(e,'error') flash(str(e),'error')
return False return False
self.app.logger.debug("File saved to " + str(os.path.join(self.app.config['UPLOAD_FOLDER'], filename))) self.app.logger.debug("File saved to " + str(os.path.join(self.app.config['UPLOAD_FOLDER'], filename)))