Skip to content

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

port instance-attribute

port = port

baudrate instance-attribute

baudrate = baudrate

parity instance-attribute

parity = parity

stopbits instance-attribute

stopbits = stopbits

bytesize instance-attribute

bytesize = bytesize

timeout instance-attribute

timeout = timeout

serial instance-attribute

serial = None

_is_connected instance-attribute

_is_connected = False

is_connected property

is_connected

Check if the scale is currently connected. :return: True if connected, False otherwise

Functions

connect

connect()

Establish a serial connection to the scale. Raises ScaleException if connection fails.

disconnect

disconnect()

Close the serial connection to the scale.

_handle_specific_error

_handle_specific_error(error, cmd, expect_ack=True, is_dual_ack=False)

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: ScaleError

cmd

The command that was being sent

TYPE: str

expect_ack

Whether the command expects an ACK

TYPE: bool DEFAULT: True

is_dual_ack

Whether the command expects dual ACKs

TYPE: bool DEFAULT: False

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_ack(timeout=ACK_TIMEOUT)

Wait for an ACK response from the scale.

PARAMETER DESCRIPTION
timeout

Timeout in seconds

TYPE: float DEFAULT: ACK_TIMEOUT

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_command(cmd, expect_data=False)

Send a command to the scale with retry logic and ACK handling.

PARAMETER DESCRIPTION
cmd

Command string to send

TYPE: str

expect_data

If True, expects a data response after ACK

TYPE: bool DEFAULT: False

RETURNS DESCRIPTION
str

Response string from the scale

RAISES DESCRIPTION
ScaleException

If command fails after retries or ACK timeout

cancel

cancel()

Cancel the S or SIR command.

query_weight

query_weight()

Request the weight data immediately (Q command).

request_stable_weight

request_stable_weight()

Request the weight data when stabilized (S command).

request_instant_weight

request_instant_weight()

Request the weight data immediately (SI command).

request_continuous_weight

request_continuous_weight()

Request the weight data continuously (SIR command).

request_stable_weight_escp

request_stable_weight_escp()

Request the weight data when stabilized (ESC+P command).

calibrate

calibrate()

Perform calibration (CAL command).

calibrate_external

calibrate_external()

Calibrate using an external weight (EXC command).

display_off

display_off()

Turn the display off (OFF command).

display_on

display_on()

Turn the display on (ON command).

power_on

power_on()

Alias for turning the display on.

power_off

power_off()

Alias for turning the display off.

print_weight

print_weight()

Print the current weight (PRT command).

re_zero

re_zero()

Re-zero the scale (R command).

sample

sample()

Sample command (SMP command).

tare

tare()

Tare the scale (T command).

mode

mode()

Change the weighing mode (U command).

get_id

get_id()

Request the ID number (?ID command).

get_serial_number

get_serial_number()

Request the serial number (?SN command).

get_model

get_model()

Request the model name (?TN command).

get_tare_weight

get_tare_weight()

Request the tare weight (?PT command).

set_tare_weight

set_tare_weight(value, unit='g')

Set the tare weight (PT command). :param value: Tare weight value :param unit: Weighing unit (default 'g')

get_weight

get_weight(stable=True)

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_weight(data, expect_stable=True)

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

  1. Access Configuration Menu
  2. Power on the scale
  3. Press and hold the MODE button (or appropriate menu button per your manual)
  4. Navigate to communication settings

  5. Set Communication Parameters

    Baud Rate:    9600
    Data Bits:    8
    Parity:       None
    Stop Bits:    1
    Terminator:   CRLF
    

  6. Set Response Characteristics

    Stability Bandwidth:  1
    Condition:            1 (Medium Response)
    

!!! 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.

  1. Configure Output Settings

    Data Format:       A&D Standard
    Time/Date Output:  No Output
    Zero After Output: 0 (Not Used)
    AK Error Code:     1 (Output)
    

  2. Save and Exit

  3. Save the configuration
  4. Exit the menu
  5. 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:

{
  "system": {
    "scale_port": "/dev/ttyUSB0",
    "scale_baud_rate": 9600,
    "scale_timeout": 2.0
  }
}

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:

# List COM ports
Get-WmiObject Win32_SerialPort | Select-Object Name, DeviceID

macOS:

# List serial devices
ls -l /dev/tty.*
ls -l /dev/cu.*

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:

sudo usermod -a -G dialout $USER
# Log out and log back in
4. Try different USB port 5. Check that no other program is using the scale 6. Verify scale is powered on

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