Compare commits
12 Commits
v1
...
b6cc945adb
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b6cc945adb | ||
|
|
a22d808b7b | ||
| 3198696c46 | |||
|
|
a90efc621d | ||
|
|
d8b710805f | ||
| 1e6778f2bf | |||
| 574ee7773c | |||
|
|
fbb868c244 | ||
|
|
3a68431a21 | ||
| ad0fb66b7a | |||
| 788867c1af | |||
| a695640009 |
1
.gitignore
vendored
1
.gitignore
vendored
@@ -1 +1,2 @@
|
||||
pos_database.db
|
||||
ScannerGO/ScannerGO-*
|
||||
17
Dockerfile
17
Dockerfile
@@ -2,10 +2,21 @@ FROM python:3.11-slim
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
COPY requirements.txt ./
|
||||
|
||||
# Install dependencies
|
||||
COPY requirements.txt .
|
||||
RUN pip install --no-cache-dir -r requirements.txt
|
||||
|
||||
COPY . .
|
||||
# Copy source code
|
||||
COPY app.py .
|
||||
COPY templates/ ./templates/
|
||||
COPY static/ ./static/
|
||||
|
||||
# Create the folder structure for the volume mounts
|
||||
RUN mkdir -p /app/static/cache
|
||||
|
||||
EXPOSE 5000
|
||||
|
||||
# Run with unbuffered output so you can actually see the logs in Portainer
|
||||
ENV PYTHONUNBUFFERED=1
|
||||
|
||||
CMD ["python", "app.py"]
|
||||
76
KeyGenerator/generate_keys.py
Normal file
76
KeyGenerator/generate_keys.py
Normal file
@@ -0,0 +1,76 @@
|
||||
import barcode
|
||||
from barcode.writer import ImageWriter
|
||||
from PIL import Image, ImageDraw, ImageFont
|
||||
import os
|
||||
import random
|
||||
|
||||
# Items to generate
|
||||
ITEMS = [
|
||||
{"name": "Plátano", "icon": "🍌"},
|
||||
{"name": "Manzana", "icon": "🍎"},
|
||||
{"name": "Tomate", "icon": "🍅"},
|
||||
{"name": "Lechuga", "icon": "🥬"},
|
||||
{"name": "Cebolla", "icon": "🧅"},
|
||||
{"name": "Pan Batido", "icon": "🥖"}
|
||||
]
|
||||
|
||||
os.makedirs('keychain_cards', exist_ok=True)
|
||||
|
||||
def generate_card(item):
|
||||
name = item['name']
|
||||
# Generate a private EAN-13 starting with 99
|
||||
# We need 12 digits (the 13th is a checksum added by the library)
|
||||
random_digits = ''.join([str(random.randint(0, 9)) for _ in range(10)])
|
||||
code_str = f"99{random_digits}"
|
||||
|
||||
# Generate Barcode Image
|
||||
EAN = barcode.get_barcode_class('ean13')
|
||||
ean = EAN(code_str, writer=ImageWriter())
|
||||
|
||||
# Customizing the output image size
|
||||
options = {
|
||||
'module_height': 15.0,
|
||||
'font_size': 10,
|
||||
'text_distance': 3.0,
|
||||
'write_text': True
|
||||
}
|
||||
|
||||
# Create the card canvas (300x450 pixels ~ 2.5x3.5 inches)
|
||||
card = Image.new('RGB', (300, 400), color='white')
|
||||
draw = ImageDraw.Draw(card)
|
||||
|
||||
# Draw a border for cutting
|
||||
draw.rectangle([0, 0, 299, 399], outline="black", width=2)
|
||||
|
||||
# Try to add the Emoji/Text (Requires a font that supports emojis, otherwise just text)
|
||||
try:
|
||||
# If on Linux, try to find a ttf
|
||||
font = ImageFont.truetype("/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf", 40)
|
||||
title_font = ImageFont.truetype("/usr/share/fonts/truetype/dejavu/DejaVuSans-Bold.ttf", 25)
|
||||
except:
|
||||
font = ImageFont.load_default()
|
||||
title_font = ImageFont.load_default()
|
||||
|
||||
# Draw Name and Emoji
|
||||
draw.text((150, 60), item['icon'], fill="black", font=font, anchor="mm")
|
||||
draw.text((150, 120), name, fill="black", font=title_font, anchor="mm")
|
||||
|
||||
# Save barcode to temp file
|
||||
barcode_img_path = f"keychain_cards/{code_str}_tmp"
|
||||
ean.save(barcode_img_path, options=options)
|
||||
|
||||
# Paste barcode onto card
|
||||
b_img = Image.open(f"{barcode_img_path}.png")
|
||||
b_img = b_img.resize((260, 180)) # Resize to fit card
|
||||
card.paste(b_img, (20, 180))
|
||||
|
||||
# Cleanup and save final
|
||||
os.remove(f"{barcode_img_path}.png")
|
||||
final_path = f"keychain_cards/{name.replace(' ', '_')}.png"
|
||||
card.save(final_path)
|
||||
print(f"Generated {name}: {ean.get_fullcode()}")
|
||||
|
||||
for item in ITEMS:
|
||||
generate_card(item)
|
||||
|
||||
print("\nAll cards generated in 'keychain_cards/' folder.")
|
||||
85
README.md
85
README.md
@@ -1,2 +1,85 @@
|
||||
# SekiPOS
|
||||
# SekiPOS v1.0 🍫🥤
|
||||
|
||||
A reactive POS inventory system for software engineers with a snack addiction. Features real-time UI updates, automatic product discovery via Open Food Facts, and local image caching.
|
||||
|
||||
## 🚀 Features
|
||||
- **Real-time UI:** Instant updates via Socket.IO.
|
||||
- **Smart Fetch:** Pulls product names/images from Open Food Facts if not found locally.
|
||||
- **Local Cache:** Saves images locally to `static/cache` to avoid IP bans.
|
||||
- **CLP Ready:** Chilean Peso formatting ($1.234) for local commerce.
|
||||
- **Secure:** Hashed password authentication via Flask-Login.
|
||||
|
||||
## 🐳 Docker Deployment (Server)
|
||||
|
||||
Build and run the central inventory server:
|
||||
|
||||
```bash
|
||||
# Build the image
|
||||
docker build -t sekipos:latest .
|
||||
|
||||
# Run the container (Map port 5000 and persist the database/cache)
|
||||
docker run -d \
|
||||
-p 5000:5000 \
|
||||
-v $(pwd)/sekipos/db:/app/db \
|
||||
-v $(pwd)/sekipos/static/cache:/app/static/cache \
|
||||
--name sekipos-server \
|
||||
sekipos:latest
|
||||
```
|
||||
|
||||
Or use this stack:
|
||||
```yml
|
||||
name: sekipos
|
||||
services:
|
||||
sekipos:
|
||||
ports:
|
||||
- 5000:5000
|
||||
volumes:
|
||||
- YOUR_PATH/sekipos/db:/app/db
|
||||
- YOUR_PATH/sekipos/static/cache:/app/static/cache
|
||||
container_name: sekipos-server
|
||||
image: sekipos:latest
|
||||
```
|
||||
|
||||
## 🔌 Hardware Scanner Bridge (`ScannerGO`)
|
||||
|
||||
The server needs a bridge to talk to your physical COM port. Use the `ScannerGO` binary on the machine where the scanner is plugged in.
|
||||
|
||||
### 🐧 Linux
|
||||
```bash
|
||||
chmod +x ScannerGO-linux
|
||||
./ScannerGO-linux -port "/dev/ttyACM0" -baud 115200 -url "http://<SERVER_IP>:5000/scan"
|
||||
```
|
||||
|
||||
### 🪟 Windows
|
||||
```powershell
|
||||
.\ScannerGO-windows.exe -port "COM3" -baud 115200 -url "http://<SERVER_IP>:5000/scan"
|
||||
```
|
||||
|
||||
*Note: Ensure the `-url` points to your Docker container's IP address.*
|
||||
|
||||
All this program does its send the COM data from the scanner gun to:
|
||||
```
|
||||
https://scanner.sekidesu.xyz/scan?content=BAR-CODE
|
||||
```
|
||||
|
||||
## 📦 Local Installation (Development)
|
||||
|
||||
If you're too afraid of Docker:
|
||||
```bash
|
||||
pip install -r requirements.txt
|
||||
python app.py
|
||||
```
|
||||
|
||||
## 🔐 Credentials
|
||||
- **Username:** `admin`
|
||||
- **Password:** `seki123` (Change this in `app.py` or you'll be hacked by a smart-fridge)
|
||||
|
||||
## 📁 Structure
|
||||
- `app.py`: The inventory/web server.
|
||||
- `static/cache/`: Local repository for product images.
|
||||
- `db/pos_database.db`: SQLite storage.
|
||||
|
||||
## 📋 TODOs?
|
||||
- Fruits and vegetables list
|
||||
- Better admin registration(?)
|
||||
- Cache user edited pictures
|
||||
41
ScannerPython/scanner.py
Normal file
41
ScannerPython/scanner.py
Normal file
@@ -0,0 +1,41 @@
|
||||
import serial
|
||||
import requests
|
||||
import time
|
||||
|
||||
# --- CONFIGURATION ---
|
||||
COM_PORT = 'COM5' # Change to /dev/ttyUSB0 on Linux
|
||||
BAUD_RATE = 115200
|
||||
# The IP of the PC running your Flask WebUI
|
||||
SERVER_URL = "https://scanner.sekidesu.xyz/scan" # Change to your server's URL
|
||||
|
||||
def run_bridge():
|
||||
try:
|
||||
# Initialize serial connection
|
||||
ser = serial.Serial(COM_PORT, BAUD_RATE, timeout=0.1)
|
||||
print(f"Connected to {COM_PORT} at {BAUD_RATE} bauds.")
|
||||
print("Ready to scan. Try not to break it.")
|
||||
|
||||
while True:
|
||||
# Read line from scanner (most scanners send \r or \n at the end)
|
||||
if ser.in_waiting > 0:
|
||||
barcode = ser.readline().decode('utf-8').strip()
|
||||
|
||||
if barcode:
|
||||
print(f"Scanned: {barcode}")
|
||||
try:
|
||||
# Send to your existing Flask server
|
||||
# We use the same parameter 'content' so your server doesn't know the difference
|
||||
resp = requests.get(SERVER_URL, params={'content': barcode})
|
||||
print(f"Server responded: {resp.status_code}")
|
||||
except Exception as e:
|
||||
print(f"Failed to send to server: {e}")
|
||||
|
||||
time.sleep(0.01) # Don't melt your CPU
|
||||
|
||||
except serial.SerialException as e:
|
||||
print(f"Error opening {COM_PORT}: {e}")
|
||||
except KeyboardInterrupt:
|
||||
print("\nBridge stopped by user. Quitter.")
|
||||
|
||||
if __name__ == "__main__":
|
||||
run_bridge()
|
||||
2
app.py
2
app.py
@@ -14,7 +14,7 @@ socketio = SocketIO(app, cors_allowed_origins="*")
|
||||
login_manager = LoginManager(app)
|
||||
login_manager.login_view = 'login'
|
||||
|
||||
DB_FILE = 'pos_database.db'
|
||||
DB_FILE = 'db/pos_database.db'
|
||||
CACHE_DIR = 'static/cache'
|
||||
os.makedirs(CACHE_DIR, exist_ok=True)
|
||||
|
||||
|
||||
2
db/.gitignore
vendored
Normal file
2
db/.gitignore
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
*
|
||||
!.gitignore
|
||||
BIN
pos_database.db
BIN
pos_database.db
Binary file not shown.
Reference in New Issue
Block a user