Scale API Reference¶
The Scale class provides an interface to precision balance hardware for weight measurements.
Overview¶
The Scale class:
- Connects to balance via USB serial
- Reads weight measurements
- Supports stable and unstable readings
- Handles taring and calibration
Class Reference¶
Scale
¶
Scale(port, baudrate=9600, timeout=10, parity=PARITY_NONE, stopbits=STOPBITS_ONE, bytesize=EIGHTBITS)
Class for a digital scale connected via serial port (A&D FX-120i protocol). Provides methods to send commands and parse responses according to the scale's protocol.
Initialize the Scale object and connection parameters. :param port: Serial port (e.g., 'COM1' or '/dev/ttyUSB0') :param baudrate: Baud rate for serial communication :param timeout: Read timeout in seconds :param parity: Parity setting (default: PARITY_NONE) :param stopbits: Stop bits setting (default: STOPBITS_ONE) :param bytesize: Byte size setting (default: EIGHTBITS)
Attributes¶
is_connected
property
¶
Check if the scale is currently connected. :return: True if connected, False otherwise
Functions¶
connect
¶
Establish a serial connection to the scale. Raises ScaleException if connection fails.
_handle_specific_error
¶
Handle specific error codes with unified retry logic. All retry logic is contained within this function.
| PARAMETER | DESCRIPTION |
|---|---|
error
|
The ScaleError that was received
TYPE:
|
cmd
|
The command that was being sent
TYPE:
|
expect_ack
|
Whether the command expects an ACK
TYPE:
|
is_dual_ack
|
Whether the command expects dual ACKs
TYPE:
|
| RETURNS | DESCRIPTION |
|---|---|
bool
|
True if command succeeded after error handling, False otherwise |
| RAISES | DESCRIPTION |
|---|---|
ScaleException
|
If error persists after all retries |
_wait_for_ack
¶
Wait for an ACK response from the scale.
| PARAMETER | DESCRIPTION |
|---|---|
timeout
|
Timeout in seconds
TYPE:
|
| RETURNS | DESCRIPTION |
|---|---|
tuple
|
Tuple of (success: bool, received_data: bytes, error: ScaleError or None) |
| RAISES | DESCRIPTION |
|---|---|
ScaleException
|
If error response received instead of ACK (for non-handled errors) |
_send_command
¶
Send a command to the scale with retry logic and ACK handling.
| PARAMETER | DESCRIPTION |
|---|---|
cmd
|
Command string to send
TYPE:
|
expect_data
|
If True, expects a data response after ACK
TYPE:
|
| RETURNS | DESCRIPTION |
|---|---|
str
|
Response string from the scale |
| RAISES | DESCRIPTION |
|---|---|
ScaleException
|
If command fails after retries or ACK timeout |
request_stable_weight
¶
Request the weight data when stabilized (S command).
request_continuous_weight
¶
Request the weight data continuously (SIR command).
request_stable_weight_escp
¶
Request the weight data when stabilized (ESC+P command).
set_tare_weight
¶
Set the tare weight (PT command). :param value: Tare weight value :param unit: Weighing unit (default 'g')
get_weight
¶
Get the current weight from the scale, parsing the response according to the data format. :param stable: If True, waits for stable weight; otherwise, allows unstable :return: The weight in grams
_parse_weight
¶
Parse the weight data string from the scale according to the protocol data format. Checks header, sign, value, unit, and overload state. Throws errors for protocol violations. :param data: Raw data string from the scale :param expect_stable: If True, expects 'ST' header; else allows 'ST' or 'US' :return: Parsed weight as float
Usage Examples¶
Basic Connection and Reading¶
from src.Scale import Scale
# Create and connect to scale
scale = Scale(port="/dev/ttyUSB0")
scale.connect()
if scale.is_connected:
# Get stable weight reading
weight = scale.get_weight(stable=True)
print(f"Weight: {weight}g")
# Disconnect when done
scale.disconnect()
Stable vs Unstable Readings¶
# Stable reading (waits for weight to stabilize)
stable_weight = scale.get_weight(stable=True)
print(f"Stable: {stable_weight}g")
# Unstable reading (immediate, may be changing)
unstable_weight = scale.get_weight(stable=False)
print(f"Unstable: {unstable_weight}g")
When to use each:
- Stable: For measurements you'll record or use for decisions
- Unstable: For real-time monitoring or progress display
Taring the Scale¶
# Tare (zero) the scale
scale.tare()
# Now readings are relative to current weight
# Place object on scale
weight = scale.get_weight(stable=True)
print(f"Object weight: {weight}g")
Integration with JubileeManager¶
The scale is typically used through JubileeManager:
from src.JubileeManager import JubileeManager
manager = JubileeManager()
manager.connect(scale_port="/dev/ttyUSB0")
# Get stable weight
weight = manager.get_weight_stable()
print(f"Weight: {weight}g")
# Get unstable weight (faster)
weight = manager.get_weight_unstable()
print(f"Weight: {weight}g")
Weight Monitoring¶
Continuous Monitoring¶
import time
def monitor_weight(scale, duration=10):
"""Monitor weight for specified duration."""
print("Monitoring weight...")
start_time = time.time()
while time.time() - start_time < duration:
weight = scale.get_weight(stable=False)
print(f"Weight: {weight:6.2f}g", end='\r')
time.sleep(0.1)
print() # New line
# Usage
monitor_weight(scale, duration=30)
Waiting for Stability¶
def wait_for_stable(scale, timeout=30):
"""Wait for weight to stabilize."""
import time
start_time = time.time()
previous_weight = None
stable_count = 0
required_stable_readings = 5
tolerance = 0.01 # grams
while time.time() - start_time < timeout:
weight = scale.get_weight(stable=False)
if previous_weight is not None:
if abs(weight - previous_weight) < tolerance:
stable_count += 1
if stable_count >= required_stable_readings:
return weight, True
else:
stable_count = 0
previous_weight = weight
time.sleep(0.2)
return previous_weight, False
# Usage
weight, is_stable = wait_for_stable(scale)
if is_stable:
print(f"Stable weight: {weight}g")
else:
print("Timeout waiting for stability")
Configuration¶
A&D Scale Setup (FX-120i and FX/FZ Series)¶
The Jubilee Powder system is designed to work with A&D precision balances, particularly the FX-120i model. The FX and FZ series scales from A&D should work without modification using the expected settings.
Required Scale Configuration¶
Configure your A&D FX-120i (or compatible FX/FZ series scale) with the following settings. Consult your scale manual for menu access instructions.
The Scale class is compatible with any desired weighing unit, as long as the maximum weight is set correctly in the jubilee_api_config folder.
Communication Settings¶
| Setting | Value | Description |
|---|---|---|
| Baud Rate | Faster better | Must match scale_baud_rate in software config |
| Data Bits | 8 | Standard configuration |
| Parity | None | Must match Scale object parameters |
| Stop Bits | 1 | Standard configuration |
| Terminator | CRLF | Carriage Return + Line Feed (required) |
Scale Behavior Settings¶
| Setting | Value | Description |
|---|---|---|
| Stability Bandwidth | 1 | Determines when weight is considered stable |
| Condition | 1 (Medium Response) | Controls response speed and stability tradeoff |
| Time/Date Output | No Output | Disables time/date in data stream |
| Zero After Output | 0 (Not Used) | Disables auto-zero after reading |
| AK Error Code | 1 (Output) | Enables error code output and error-correcting communication |
Data Format Settings¶
| Setting | Value | Description |
|---|---|---|
| Data Format | A&D Standard Format | Required for proper parsing |
Configuration Steps¶
- Access Configuration Menu
- Power on the scale
- Press and hold the
MODEbutton (or appropriate menu button per your manual) -
Navigate to communication settings
-
Set Communication Parameters
-
Set Response Characteristics
!!! note "Display Refresh Rate" The display refresh rate automatically adjusts based on the Condition setting. Condition 1 (Medium Response) provides a good balance between speed and stability.
-
Configure Output Settings
-
Save and Exit
- Save the configuration
- Exit the menu
- Power cycle the scale to ensure settings take effect
Verifying Configuration¶
Test the scale communication before using with the Jubilee system:
from src.Scale import Scale
# Create scale instance with matching parameters
scale = Scale(
port="/dev/ttyUSB0", # Your scale's serial port
baudrate=9600, # Must match scale setting
timeout=2.0
)
# Connect and test
if scale.connect():
print("Scale connected successfully")
# Test weight reading
weight = scale.get_weight(stable=True)
print(f"Weight reading: {weight}g")
# Test stability detection
if weight is not None:
print("Scale is communicating properly")
scale.disconnect()
else:
print("Connection failed - check settings and port")
Troubleshooting A&D Scale Setup¶
Scale not responding: - Verify baud rate matches between scale and software (9600) - Check that terminator is set to CRLF - Ensure cable is properly connected - Try power cycling the scale
Readings always unstable: - Increase Stability Bandwidth (try value 2 or 3) - Check for vibrations, air currents, or static. Scale must be grounded for proper use. - Ensure scale is on level, stable surface - Adjust Condition setting if needed
Incorrect weight values: - Verify Data Format is set to "A&D Standard" - Check that scale is calibrated - Ensure no environmental factors affecting readings
Communication errors: - Check Data Bits (must be 8) - Verify Parity is set to None - Confirm Stop Bits is 1 - Check that AK Error Code output is enabled
Compatible Models¶
The following A&D scales should work with these settings:
- FX Series: FX-120i, FX-200i, FX-300i (Precision balances)
- FZ Series: FZ-120i, FZ-200i, FZ-300i (Compact balances)
- Other A&D models with standard format output
Model-Specific Notes
While most FX/FZ series scales use identical settings, always consult your specific model's manual.
Serial Port Configuration¶
The scale port is configured in system_config.json:
Finding the Serial Port¶
Linux:
# List USB serial devices
ls -l /dev/ttyUSB*
ls -l /dev/ttyACM*
# Check dmesg for recent connections
dmesg | grep tty
Windows:
macOS:
Error Handling¶
Connection Failures¶
from src.Scale import Scale
scale = Scale(port="/dev/ttyUSB0")
try:
scale.connect()
if not scale.is_connected:
raise ConnectionError("Scale connection failed")
except Exception as e:
print(f"Error connecting to scale: {e}")
# Common causes:
# - Wrong port
# - Port already in use
# - Insufficient permissions
# - Hardware not connected
Reading Failures¶
try:
weight = scale.get_weight(stable=True)
if weight is None:
print("Failed to read weight")
except Exception as e:
print(f"Error reading scale: {e}")
# Common causes:
# - Communication timeout
# - Scale not responding
# - Invalid data from scale
Handling Disconnection¶
def safe_read_weight(scale):
"""Safely read weight with error handling."""
if not scale.is_connected:
print("Scale not connected")
return None
try:
weight = scale.get_weight(stable=True)
return weight
except Exception as e:
print(f"Error reading weight: {e}")
return None
Advanced Usage¶
Calibration¶
def calibrate_scale(scale, known_weight):
"""
Calibrate scale using a known reference weight.
Args:
scale: Connected Scale instance
known_weight: Weight of calibration standard in grams
"""
print("Remove all items from scale, then press Enter")
input()
# Tare the empty scale
scale.tare()
print("Scale tared")
print(f"Place {known_weight}g calibration weight on scale, then press Enter")
input()
# Read calibration weight
measured = scale.get_weight(stable=True)
print(f"Measured: {measured}g")
print(f"Expected: {known_weight}g")
print(f"Error: {measured - known_weight}g ({((measured - known_weight) / known_weight) * 100:.2f}%)")
if abs(measured - known_weight) > 0.1:
print("Warning: Calibration error exceeds 0.1g")
Data Logging¶
import csv
import time
from datetime import datetime
def log_weight_data(scale, output_file, duration=60, interval=1.0):
"""
Log weight data to CSV file.
Args:
scale: Connected Scale instance
output_file: Path to output CSV file
duration: How long to log in seconds
interval: Time between readings in seconds
"""
with open(output_file, 'w', newline='') as f:
writer = csv.writer(f)
writer.writerow(['Timestamp', 'Weight (g)', 'Stable'])
start_time = time.time()
while time.time() - start_time < duration:
timestamp = datetime.now().isoformat()
# Get both stable and unstable readings
unstable = scale.get_weight(stable=False)
stable = scale.get_weight(stable=True)
writer.writerow([timestamp, unstable, 'No'])
writer.writerow([timestamp, stable, 'Yes'])
time.sleep(interval)
print(f"Data logged to {output_file}")
# Usage
log_weight_data(scale, "weight_log.csv", duration=300, interval=5.0)
Multi-Scale Setup¶
For systems with multiple scales:
class ScaleManager:
"""Manage multiple scales."""
def __init__(self, ports):
"""
Initialize multiple scales.
Args:
ports: List of serial port paths
"""
self.scales = []
for i, port in enumerate(ports):
scale = Scale(port=port)
scale.connect()
if scale.is_connected:
self.scales.append((i, scale))
print(f"Scale {i} connected on {port}")
else:
print(f"Failed to connect scale {i} on {port}")
def read_all(self, stable=True):
"""Read all scales."""
readings = {}
for index, scale in self.scales:
readings[index] = scale.get_weight(stable=stable)
return readings
def disconnect_all(self):
"""Disconnect all scales."""
for index, scale in self.scales:
scale.disconnect()
# Usage
manager = ScaleManager(["/dev/ttyUSB0", "/dev/ttyUSB1"])
readings = manager.read_all(stable=True)
print(f"Scale readings: {readings}")
manager.disconnect_all()
Troubleshooting¶
Scale Not Responding¶
Symptoms:
- is_connected is False
- Timeout errors when reading
Solutions:
1. Verify physical connection (USB cable plugged in)
2. Check that port is correct (ls /dev/ttyUSB*)
3. Verify user has permission to access serial port:
Incorrect Readings¶
Symptoms: - Readings don't match display on scale - Readings are always zero - Readings are unstable
Solutions:
1. Tare the scale: scale.tare()
2. Calibrate using known weight
3. Check for environmental factors:
- Drafts/air currents
- Vibrations
- Temperature changes
4. Verify scale settings (units, precision)
5. Check baud rate matches scale configuration
Permission Denied¶
Linux symptom: PermissionError: [Errno 13] Permission denied: '/dev/ttyUSB0'
Solutions:
# Add user to dialout group
sudo usermod -a -G dialout $USER
# Or use udev rule
sudo nano /etc/udev/rules.d/50-scale.rules
# Add: SUBSYSTEM=="tty", ATTRS{idVendor}=="XXXX", MODE="0666"
# Reload udev rules
sudo udevadm control --reload-rules
sudo udevadm trigger
Best Practices¶
Always Tare Before Measurements¶
# GOOD
scale.tare()
# ... place object ...
weight = scale.get_weight(stable=True)
# BAD
weight = scale.get_weight(stable=True) # May include previous object's weight
Use Stable Readings for Decisions¶
# GOOD - for recording data
final_weight = scale.get_weight(stable=True)
save_to_database(final_weight)
# BAD - for recording data
weight = scale.get_weight(stable=False) # Might be changing!
save_to_database(weight)
# OK - for real-time monitoring
while filling:
current = scale.get_weight(stable=False)
print(f"Current: {current}g")
Handle Connection State¶
# GOOD
if scale.is_connected:
weight = scale.get_weight(stable=True)
else:
print("Scale not connected")
# BAD
weight = scale.get_weight(stable=True) # Might fail if not connected
Clean Up Resources¶
# GOOD
scale = Scale(port="/dev/ttyUSB0")
try:
scale.connect()
# ... use scale ...
finally:
scale.disconnect()
# BETTER - use context manager (if available)
with Scale(port="/dev/ttyUSB0") as scale:
weight = scale.get_weight(stable=True)
See Also¶
- JubileeManager - High-level scale operations
- Configuration Guide - Setting up scale port
- Results Interpretation - Analyzing weight data