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 Python.
- 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.
## Context

View File

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

View File

@ -1,36 +1,261 @@
Adafruit_Thermal>=1.1.0
appdirs>=1.4.4
argcomplete>=2.0.6
cffi>=1.17.1
click>=8.1.8
commonmark>=0.9.1
cryptography>=45.0.4
Deprecated>=1.2.18
escpos>=2.0.0
Flask>=2.1.3
Flask-Limiter>=2.4.5.1
future>=0.18.3
itsdangerous>=2.1.2
Jinja2>=3.1.6
limits>=2.6.3
MarkupSafe>=2.1.5
numpy>=2.3.0
packaging>=21.3
pillow>=11.2.1
pycparser>=2.22
Pygments>=2.12.0
pyparsing>=3.0.9
pyserial>=3.5
python-barcode>=0.13.1
pyusb>=1.2.1
PyYAML>=6.0.2
qrcode>=7.3.1
rich>=12.4.4
setuptools>=80.9.0
six>=1.16.0
toml>=0.10.2
typing_extensions>=4.2.0
Unidecode>=1.3.8
viivakoodi>=0.8.0
Werkzeug>=2.1.2
wrapt>=1.14.1
appdirs==1.4.4
apt-listchanges==4.8
argcomplete==3.6.2
arrow==1.3.0
attrs==25.3.0
babel==2.17.0
bcrypt==4.2.0
bidict==0.23.1
blinker==1.9.0
certifi==2025.1.31
cffi==1.17.1
chardet==5.2.0
charset-normalizer==3.4.2
click==8.3.0
cloud-init==25.2
colorzero==2.0
configobj==5.0.9
cryptography==43.0.0
Deprecated==1.2.18
distlib==0.3.9
distro==1.9.0
escpos==2.0.0
filelock==3.18.0
Flask==3.1.2
Flask-Limiter==4.0.0
Flask-SocketIO==5.5.1
fqdn==1.5.1
future==1.0.0
gpiod==2.2.0
gpiozero==2.0.1
h11==0.16.0
idna==3.10
isoduration==20.11.0
itsdangerous==2.2.0
Jinja2==3.1.6
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()
# 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()
if RASPBERRY_PI_CONNECTED:
rpi.initialise_gpio()
#############################################################
@ -91,7 +95,7 @@ if RASPBERRY_PI_CONNECTED:
web = Web(app, printer)
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(
get_remote_address,

View File

@ -1,5 +1,4 @@
# Importing the module to mage the connection to the printer.
from flask import flash
from escpos.printer import Usb, USBNotFoundError
from time import sleep, gmtime, strftime
import os.path
@ -46,12 +45,10 @@ class Printer(object):
match status:
case 0:
self.app.logger.error('Printer has no more paper, aborting...')
flash("No more paper on the printer. Sorry.",category='error')
self.printer.close()
raise Exception("No more paper in the printer")
case 1:
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()
case 2:
self.app.logger.debug('Printer has paper, good to go')
@ -101,7 +98,7 @@ class Printer(object):
return True
def print_sms(self, msg, signature) -> None:
def print_sms(self, msg, signature="Guest") -> None:
clean_msg = str(msg)
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)))
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)))
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))
raise e
flash("Message printed : " + clean_msg ,category='info')
return True
def print_img(self, path, sign) -> None:
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)))
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:
self.check_paper()
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.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.close()
self.app.logger.debug("Printed an image : " + str(path))
@ -214,7 +210,7 @@ def process_image(self, path):
# Final resize check
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.")
return False

View File

@ -1,5 +1,5 @@
from flask_socketio import SocketIO
from gpiozero import Button, LED
from gpiozero import Button, LED, DigitalOutputDevice
from time import sleep
import io # To check if we are on a Raspberry Pi
import subprocess
@ -13,24 +13,17 @@ class Raspberry(object):
- 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.socketio = socketio
self.app = app
self.flash_gpio = flash_gpio_port_number
self.is_flash_present = is_flash_present
self.button_gpio = button_gpio_port_number
self.led_gpio = indicator_gpio_port_number
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):
# Check if we are running on a raspberry pi
try:
@ -59,48 +52,71 @@ class Raspberry(object):
return False
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:
subprocess.run(['fswebcam', '--no-banner', self.image_path])
self.initialise_gpio()
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
return True
def flash(state):
if state:
# turn on the flash
pass
else:
# turn off the flash
pass
def initialise_gpio(self):
self.app.logger.debug('Initializing GPIO')
def flash_indicator(self):
# The indicator will flash three times before return true
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()
self.led = LED(self.led_gpio)
self.app.logger.debug('Activated indicator LED')
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.flash_indicator() # The indicator will flash a countdown LED
self.flash(True)
self.indicator_countdown(iters=5,multi=20) # The indicator will flash a countdown LED
self.flash_toggle()
try:
self.take_picture()
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
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();
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, {
method: 'POST', // or 'PUT'

View File

@ -21,7 +21,7 @@ class Web(object):
self.app.logger.error(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):
@ -57,7 +57,7 @@ class Web(object):
self.app.logger.debug("File saved")
except Exception as e:
self.app.logger.error("Could not save file")
flash(e,'error')
flash(str(e),'error')
return False
self.app.logger.debug("File saved to " + str(os.path.join(self.app.config['UPLOAD_FOLDER'], filename)))