Skip to main content

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.

  1. Python TCP/IP Server: Acts as a bridge handling multiple concurrent scale connections.
  2. 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 scale
  • unit (required, string) - Unit of measurement (lb, kg, oz)
  • scale (required, numeric) - Scale ID from data packet
  • station (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

ColumnTypeDescription
idINT (PK)Primary key
prepuff_company_idINT (FK)References prepuff_company.id
target_weightDECIMAL(10,2)Target bag weight in lbs
quantityINTTotal bags needed
bags_completedINTCurrent bag count (default: 0)
stationVARCHAR(50)Station identifier
statusENUM'active' or 'completed'
started_by_idINT (FK)User who started job
started_atDATETIMEJob start timestamp

Table: filler_bag

ColumnTypeDescription
idINT (PK)Primary key
prepuff_job_idINT (FK)References prepuff_job.id
weightDECIMAL(10,2)Bag weight in lbs
unitVARCHAR(10)Unit of measurement
scale_idINTScale that weighed this bag
in_toleranceBOOLEANWhether weight was within ±10%
weighed_atDATETIMEWhen 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