Pre-Puff
Architecture Overview
The Pre-Puff system integrates network-enabled industrial scales, a central Python TCP/IP server (main.py), and the Shelter web application.
- Python TCP/IP Server: Acts as a bridge handling multiple concurrent scale connections.
- Web Application APIs: Receives weight data, validates targets, and stores records.
Hardware Reference
The current pre-puff scales use the Avery Weigh-Tronix ZM405 weight indicator.
Python TCP/IP Server Architecture
File Location: main.py (scale-app project)
The Python server acts as a bridge between network-enabled scales and the web application, handling multiple concurrent scale connections and forwarding weight data through authenticated API calls.
Server Initialization
def main():
# Set up a TCP/IP server
server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# Bind the socket to server address and port 10001
server_address = (settings['host'], settings['port'])
server.bind(server_address)
# Listen on port 10001
server.listen()
print('Server is listening for scales...')
while True:
connection, address = server.accept()
threading.Thread(target=handler, args=(connection, address)).start()
Behavior:
- Creates TCP socket server bound to configured host/port (e.g.,
0.0.0.0:10001) - Enters infinite loop accepting incoming connections
- For each new connection, spawns a dedicated thread running
handler()function - Allows multiple scales to connect simultaneously without blocking
Connection Handler (handler() function)
def handler(connection, address):
try:
print("Connected to scale with IP: {}".format(address))
while True:
data = connection.recv(16).decode()
# Assuming data is comma-separated, split it into a list
data_list = data.split(',')
# Temporary fix for third scale being used as replacement
if data_list[0] == 3:
scale_id = 2
else:
scale_id = data_list[0]
weight_data = data_list[1].strip().split(' ')
weight = weight_data[0]
unit = weight_data[1]
print("Scale ID: " + scale_id)
print("Weight: " + weight + " " + unit)
send_data(weight, unit, scale_id)
if not data:
break
finally:
connection.close()
API Communication (send_data() function)
def send_data(weight, unit, scale):
# The location of the website function to scan a bag in
scan_function = settings['site_url'] + settings['controller'] + '/' + settings['function']
# Header with Bearer token for API auth
headers = {"Authorization": "Bearer " + settings['api_key']}
# data to be sent to api
data = {
'weight': weight,
'unit': unit,
'scale': scale,
'station': settings['station']
}
# sending post request and saving response as response object
try:
response = requests.post(url=scan_function, data=data, timeout=10, headers=headers)
except TimeoutError:
logging.error('Request timed out...')
return False
# extracting response text
print_response(response)
Settings Management
settings.json Structure:
{
"host": "0.0.0.0",
"port": 10001,
"site_url": "https://app.shelter.example.com",
"controller": "/api/prepuff",
"function": "scan_bag",
"api_key": "Bearer_Token_String_Here",
"station": "Station 1"
}
Python Server Deployment
Installation Steps:
# Install Python dependencies
pip install requests
# Create logs directory
mkdir logs
# Create settings.json configuration file
nano settings.json
Running as background service (Linux with systemd):
Create systemd service file /etc/systemd/system/prepuff-server.service
[Unit]
Description=Pre-Puff Scale Server
After=network.target
[Service]
Type=simple
User=youruser
WorkingDirectory=/path/to/scale-app
ExecStart=/usr/bin/python3 /path/to/scale-app/main.py
Restart=always
RestartSec=10
[Install]
WantedBy=multi-user.target
Scale Configuration
Network Configuration: Scales must be configured as TCP/IP clients to connect to the Python server.
Required Settings:
- Protocol: TCP/IP Client
- Server IP: IP address of machine running Python server
- Server Port: 10001 (or configured port)
- Data Format: Comma-separated values
- Data Structure:
[scale_id],[weight] [unit]
API Endpoints
API Controller: src/app/Controllers/Api/Prepuff.php
Endpoint: scan_bag() - POST
Route: /api/prepuff/scan_bag
Purpose: Receives weight data from Python server and creates filler bag records
Request Parameters:
weight(required, numeric) - Weight value from scaleunit(required, string) - Unit of measurement (lb, kg, oz)scale(required, numeric) - Scale ID from data packetstation(required, string) - Station identifier from settings.json
Response Examples:
Success (200 OK):
{
"success": true,
"bag_id": 12345,
"bag_num": 47,
"in_tolerance": true,
"job_complete": false
}
Database Tables
Table: prepuff_job
| Column | Type | Description |
|---|---|---|
id | INT (PK) | Primary key |
prepuff_company_id | INT (FK) | References prepuff_company.id |
target_weight | DECIMAL(10,2) | Target bag weight in lbs |
quantity | INT | Total bags needed |
bags_completed | INT | Current bag count (default: 0) |
station | VARCHAR(50) | Station identifier |
status | ENUM | 'active' or 'completed' |
started_by_id | INT (FK) | User who started job |
started_at | DATETIME | Job start timestamp |
Table: filler_bag
| Column | Type | Description |
|---|---|---|
id | INT (PK) | Primary key |
prepuff_job_id | INT (FK) | References prepuff_job.id |
weight | DECIMAL(10,2) | Bag weight in lbs |
unit | VARCHAR(10) | Unit of measurement |
scale_id | INT | Scale that weighed this bag |
in_tolerance | BOOLEAN | Whether weight was within ±10% |
weighed_at | DATETIME | When bag was weighed |
Troubleshooting Checklist
When weights aren't being recorded:
- Python server is running
- Scale is connected (check server logs)
- Active job exists for the station
- Station identifier matches between job and settings.json
- API endpoint is accessible from Python server
- Bearer token is valid
- Firewall allows outbound HTTPS from Python server
Debugging Commands:
# Check if server is listening on port
netstat -tuln | grep 10001
# Test local connectivity
telnet localhost 10001
# View recent logs
tail -50 logs/log-$(date +%Y-%m-%d).txt
# Check system service status
sudo systemctl status prepuff-server