scale-protocol-scanner
v1.0.9
Published
A Node.js library to scan and identify scale protocols and read weight data from serial port scales
Downloads
13
Maintainers
Readme
Scale Protocol Scanner
A Node.js library and application to scan and identify scale protocols and read weight data from serial port scales.
Features
- Lists available serial ports
- Scans multiple scale protocols to identify which one works with your scale (36+ protocols supported)
- Connects to a scale via serial port
- Parses weight data from the scale
- Interactive mode for port selection
- Support for multiple scale protocols including Bizerba, Prix, Ruby, HoneyWell, Toledo, Filizola, and many others
- Configurable scale protocols
- Can be used as a standalone application or as an npm module
- Raw scale communication support for scales that continuously send data
Prerequisites
- Node.js (version 12 or higher)
- A serial port scale connected to your computer
Installation
npm install scale-protocol-scannerUsage
As a standalone application
# List available serial ports
node diagnose-ports.js
# Scan for working protocols
node lib/protocol-scanner.js
# Interactive mode
node interactive-scale-reader.js
# Enhanced interactive mode
node enhanced-interactive-scale-reader.js
# Configurable scale reader
node configurable-scale-reader.js
# Listen to scale data
node scale-listener.js [port] [baudRate]
# Examples:
# Use default settings (COM4, 9600 baud)
node scale-listener.js
# Use specific port (COM5, default baud rate 9600)
node scale-listener.js COM5
# Use specific port and baud rate
node scale-listener.js COM5 115200Example Scripts
Additional example scripts are available in the examples/ directory:
# Navigate to the examples directory
cd examples
# Run the basic example
node example.js
# Run the enhanced example
node enhanced-example.jsFor a complete list of available examples, see TEST_SCRIPTS.md.
As an npm module
const { scanProtocols, getWeight, protocols, listPorts, testRawCommunication } = require('scale-protocol-scanner');
// List available serial ports
async function listScalePorts() {
const ports = await listPorts();
console.log('Available ports:', ports);
}
// Scan for working protocols
async function findScaleProtocol() {
const ports = await listPorts();
if (ports.length > 0) {
const port = ports[0].path;
const baudRate = 9600;
const results = await scanProtocols(port, baudRate, Object.keys(protocols));
console.log('Working protocols:', results.filter(r => r.success));
}
}
// Read weight using a specific protocol
async function readScaleWeight() {
const ports = await listPorts();
if (ports.length > 0) {
const port = ports[0].path;
const baudRate = 9600;
const protocol = new protocols.DibaG310(); // or any other protocol
const weight = await getWeight(port, baudRate, protocol);
console.log('Current weight:', weight);
}
}
// Raw scale communication - for scales that continuously send data
async function readRawScaleData() {
const ports = await listPorts();
if (ports.length > 0) {
const port = ports[0].path;
const baudRate = 9600;
// The testRawCommunication function is specifically designed for scales that
// continuously send data without requiring specific commands to request it
const result = await testRawCommunication(port, baudRate);
if (result.success) {
console.log('Raw scale data received:', result.weight, result.unit || '');
console.log('Raw data:', result.raw);
return { success: true, weight: result.weight, unit: result.unit, raw: result.raw };
} else {
console.log('Failed to get raw scale data:', result.error);
return { success: false, error: result.error };
}
}
}
// Advanced usage: Read from raw scale with custom timeout settings
async function readRawScaleWithTimeout() {
const { SerialPort } = require('serialport');
return new Promise((resolve) => {
let port;
let receivedData = "";
const timeoutDuration = 3000; // 3 seconds timeout
let timeoutId;
try {
port = new SerialPort({
path: '/dev/ttyUSB0', // Replace with your actual port
baudRate: 9600, // Replace with your scale's baud rate
});
timeoutId = setTimeout(() => {
if (port && port.isOpen) {
port.close(() => {
// Process the received data here
const parsed = parseScaleData(receivedData);
resolve(parsed);
});
}
}, timeoutDuration);
port.on('data', (data) => {
receivedData += data.toString();
// Clear and reset timeout when new data arrives
clearTimeout(timeoutId);
timeoutId = setTimeout(() => {
if (port && port.isOpen) {
port.close(() => {
// Process the received data here
const parsed = parseScaleData(receivedData);
resolve(parsed);
});
}
}, 500); // 500ms after last data packet
});
port.on('error', (error) => {
clearTimeout(timeoutId);
resolve({ success: false, error: error.message });
});
} catch (error) {
if (timeoutId) clearTimeout(timeoutId);
resolve({ success: false, error: error.message });
}
});
}
// Helper function to parse scale data (similar to what's used internally)
function parseScaleData(rawData) {
// Convert buffer to string if needed
const data = Buffer.isBuffer(rawData) ? rawData.toString() : rawData;
// Look for patterns like "S-00.515kg" or "00.515kg"
const patterns = [
/S-(\d+\.\d+)kg/, // Pattern: S-00.515kg
/(\d+\.\d+)kg/, // Pattern: 00.515kg
/S-(\d+\.\d+)/, // Pattern: S-00.515
/(\d+\.\d+)/, // Pattern: 00.515
/S-(\d+)g/, // Pattern: S-515g
/(\d+)g/, // Pattern: 515g
/N-(\d+\.\d+)/, // Pattern: N-00.515
/(\d{1,3}\.\d{3})/ // Pattern: 3 digits before decimal point and 3 after (e.g., 00.515)
];
for (const pattern of patterns) {
const match = data.match(pattern);
if (match) {
let weightStr = match[1];
// Validate decimal places (max 5) and return appropriately formatted value
const decimalParts = weightStr.split('.');
if (decimalParts.length === 2 && decimalParts[1].length > 5) {
// If more than 5 decimal places, limit to 5
weightStr = parseFloat(weightStr).toFixed(5);
}
const weight = parseFloat(weightStr);
let unit = 'unknown';
if (data.includes('kg')) unit = 'kg';
else if (data.includes('g')) unit = 'g';
else if (data.includes('N-')) unit = 'N-';
// Validate that weight is a proper number and greater than zero
if (isNumeric(weightStr) && weight > 0) {
return {
weight: weight,
unit: unit,
raw: data
};
}
}
}
// If no pattern matches, return raw data
return {
weight: null,
unit: 'unknown',
raw: data
};
}
// Check if a string represents a valid number with max 5 decimal places
function isNumeric(str) {
if (typeof str !== 'string') return false;
str = str.trim();
if (!/^-?\d*\.?\d*$/.test(str)) {
return false;
}
const decimalPoints = (str.match(/\./g) || []).length;
if (decimalPoints > 1) {
return false;
}
if (decimalPoints === 1) {
const decimalPart = str.split('.')[1];
if (decimalPart && decimalPart.length > 5) {
return false;
}
}
const num = parseFloat(str);
if (isNaN(num) || !isFinite(num) || num < 0) {
return false;
}
// Additional validation: reasonable weight range (0 to 99999.99999 kg or similar units)
if (num > 99999.99999) {
return false;
}
return true;
}For detailed information about using this library as a package, see USING_AS_PACKAGE.md.
Raw Scale Communication
When working with scales that continuously broadcast their weight data, you need to use the raw communication approach rather than standard protocol commands. This is common with some industrial scales that continuously stream data without requiring specific commands to request it.
When to Use Raw Communication
Use raw communication when:
- Your scale continuously sends weight data without requiring commands
- You're using the
RawCommunicationprotocol - The standard protocols don't work but you can see data being sent by your scale
Proper Implementation for Raw Scales
To ensure raw scale reading works properly in your application:
Always verify your scale supports raw communication by first using the protocol scanner to confirm which protocols work.
Use appropriate timeout settings to capture data from scales that send data in intervals:
const { testRawCommunication, listPorts } = require('scale-protocol-scanner');
// Method 1: Using the built-in testRawCommunication function
async function readFromRawScale(portPath, baudRate) {
try {
const result = await testRawCommunication(portPath, baudRate);
if (result.success) {
return { weight: result.weight, unit: result.unit, raw: result.raw };
} else {
throw new Error(result.error);
}
} catch (error) {
console.error('Error reading from raw scale:', error);
return null;
}
}
// Method 2: Custom raw data reading with configurable timeouts
function readRawScaleWithCustomTimeout(portPath, baudRate, timeoutMs = 3000) {
const { SerialPort } = require('serialport');
return new Promise((resolve) => {
let receivedData = "";
let port;
let dataTimeout;
let totalTimeout;
try {
port = new SerialPort({
path: portPath,
baudRate: baudRate,
});
// Total timeout to ensure the function resolves even if no data comes
totalTimeout = setTimeout(() => {
if (port && port.isOpen) {
port.close(() => {
resolve({ success: false, error: 'No data received within timeout' });
});
}
}, timeoutMs);
port.on('data', (data) => {
receivedData += data.toString();
// Clear the chunk timeout if it exists
if (dataTimeout) {
clearTimeout(dataTimeout);
}
// Set a timeout to process data after a pause in incoming data
dataTimeout = setTimeout(() => {
if (port && port.isOpen) {
port.close(() => {
// Process and validate the received data
const parsed = parseScaleData(receivedData);
resolve({ success: true, ...parsed });
});
}
}, 300); // Wait 300ms after last data chunk
});
port.on('error', (error) => {
clearTimeout(totalTimeout);
if (dataTimeout) clearTimeout(dataTimeout);
resolve({ success: false, error: error.message });
});
} catch (error) {
if (totalTimeout) clearTimeout(totalTimeout);
if (dataTimeout) clearTimeout(dataTimeout);
resolve({ success: false, error: error.message });
}
});
}
// Example usage in your application
async function useRawScale() {
const ports = await listPorts();
if (ports.length > 0) {
const portPath = ports[0].path;
const baudRate = 9600;
// Try using the built-in function first
const rawData = await readFromRawScale(portPath, baudRate);
if (rawData) {
console.log('Raw scale data:', rawData);
} else {
console.log('Attempting with custom timeout...');
const customData = await readRawScaleWithCustomTimeout(portPath, baudRate, 5000);
if (customData.success) {
console.log('Custom raw scale data:', customData);
}
}
}
}Common Issues with Raw Scale Reading
Empty Data: The scale may not be actively sending data at the moment of reading. Raw scales often send data in intervals or only when weight changes.
Incorrect Baud Rate: Verify that the baud rate matches your scale's settings. This is often found in the scale's manual.
Data Format Mismatch: Raw scales may send data in different formats. The parsing function provided above handles common patterns like "S-00.515kg", "00.515kg", etc.
Permissions: Ensure your application has permission to access the serial port (Linux/Mac:
sudo usermod -a -G dialout $USER).
API Reference
scanProtocols(portPath, baudRate, protocols)
Scans multiple protocols to identify which one works with your scale.
Parameters:
portPath(string): The serial port path (e.g., 'COM3', '/dev/ttyUSB0')baudRate(number): The baud rate for the connection (e.g., 9600)protocols(Array): Array of protocol names to test
Returns: Promise<Array> - Array of test results
getWeight(portPath, baudRate, protocol, options)
Reads weight data from a scale using a specific protocol.
Parameters:
portPath(string): The serial port path (e.g., 'COM3', '/dev/ttyUSB0')baudRate(number): The baud rate for the connection (e.g., 9600)protocol(Object): A protocol instance to use for reading the weightoptions(Object): Optional. An object with timing options. See Timing Configuration.
Returns: Promise - The weight data as a string
Note: The 'Raw Communication' protocol (RawCommunication class) is handled specially during protocol scanning but is not used directly with getWeight in normal operation. For raw scales that send data without specific commands, see the raw-scale-usage.js example.
readFromRawScale(portPath, baudRate)
Reads weight data from raw scales that continuously send information without requiring specific commands.
Parameters:
portPath(string): The serial port path (e.g., 'COM3', '/dev/ttyUSB0')baudRate(number): The baud rate for the connection (e.g., 9600)
Returns: Promise - The weight data as a string, or null if no valid weight found
For raw scales, use the dedicated approach in examples/raw-scale-usage.js or the detectRawScale function to identify raw scales before reading weight.
getWeightEnhanced(portPath, baudRate, protocol, options)
Enhanced version of getWeight with better data handling.
Parameters:
portPath(string): The serial port path (e.g., 'COM3', '/dev/ttyUSB0')baudRate(number): The baud rate for the connection (e.g., 9600)protocol(Object): A protocol instance to use for reading the weightoptions(Object): Optional. An object with timing options. See Timing Configuration.
Returns: Promise - The weight data as a string
readWeightSimple(portPath, baudRate, communicationInterval)
Simple weight reading function that works with most scales.
Parameters:
portPath(string): The serial port path (e.g., 'COM3', '/dev/ttyUSB0')baudRate(number): The baud rate for the connection (e.g., 9600)communicationInterval(number): Optional. The time interval in milliseconds for the communication. Default:3000.
Returns: Promise - The weight data as a string
protocols
An object containing all available protocol classes that can be instantiated.
testRawCommunication(portPath, baudRate)
Tests raw communication with scales that continuously send data without specific commands.
Parameters:
portPath(string): The serial port path (e.g., 'COM3', '/dev/ttyUSB0')baudRate(number): The baud rate for the connection (e.g., 9600)
Returns: Promise - An object with success status and the parsed weight data if successful, or an error message if not.
Properties of the returned object:
success(boolean): True if valid data was received, false otherwiseweight(number|null): The parsed weight value if successfulunit(string|null): The unit of measurement ('kg', 'g', etc.) if detectedraw(string): The raw data received from the scaleerror(string|null): Error message if the operation failed
Timing Configuration
For scales that require specific timing, you can use the options parameter in the getWeight and getWeightEnhanced functions. The options object can have the following properties:
chunkTimeout(number): The timeout in milliseconds to wait for the next chunk of data from the scale. Default:200.totalTimeout(number): The total timeout in milliseconds for the entire read operation. Default:1000.sleepFactor(number): A multiplier for the internal sleep delays in the protocols. Default:1.
Example:
const options = {
chunkTimeout: 500,
totalTimeout: 2000,
sleepFactor: 1.5,
};
const weight = await getWeight(port, baudRate, protocol, options);For the readWeightSimple function, you can use the communicationInterval parameter:
communicationInterval(number): The time interval in milliseconds for the communication. Default:3000.
Example:
const weight = await readWeightSimple(port, baudRate, 5000);Troubleshooting
Empty Weight Readings After Successful Scan
If your scale successfully scans but returns empty weight readings, this is likely due to timing or response format issues. See TIMING_ISSUES.md and RESPONSE_FORMAT_ISSUE.md for detailed information and solutions.
Common Solutions
- Add delays between scanning and reading operations (2-3 seconds)
- Use protocol-specific delays for known problematic protocols
- Implement retry mechanisms with increasing delays
- Ensure the scale is powered on and ready for communication
- Check if the protocol implementation matches your scale's actual response format
Testing
This library includes comprehensive tests to ensure functionality:
Unit Tests
Run the unit tests with:
npm testThese tests verify:
- All functions are properly exported
- All protocol classes can be instantiated
- The SerialPort functionality is correctly mocked
- Basic functionality works as expected
Integration Test
The test-package directory contains a complete integration test that demonstrates how to use the library as an npm dependency:
cd test-package
npm install
node index.jsPackage Usage Documentation
For detailed examples of how to use this library in your own project, see the Package Usage Documentation which demonstrates:
- Scanning for working protocols
- Reading weight with successful protocols
- Multiple usage patterns and best practices
- Integration testing examples
Example Usage
See __tests__/example-usage.test.js for an example of how to use the library in a real application.
For more detailed information about testing, see TESTING.md.
License
MIT
Copyright (c) 2025 Helder Correia - Goldylocks Portugal [email protected] (https://www.goldylocks.pt)
Installation
- Clone or download this repository
- Install dependencies:
npm install
Usage
As a Standalone Application
Enhanced Interactive Mode (Recommended)
Run the enhanced interactive scale reader which will list available ports, allow you to select one, and choose a scale protocol:
npm run enhanced-interactiveEnhanced Standard Mode
Run the enhanced scale reader which connects to the configured port and uses the configured protocol:
npm run enhancedInteractive Mode
Run the interactive scale reader which will list available ports and allow you to select one:
npm run interactiveStandard Mode
Run the standard scale reader which connects to the first available port, or to a specific port if provided as a parameter:
npm start [port]
# Examples:
# Use first available port (default behavior)
npm start
# Use specific port
npm start COM3Note: You may need to modify the baud rate in the code to match your scale's settings.
Listen Mode
Listen to scale data continuously with optional port and baud rate parameters:
npm run listen [port] [baudRate]
# Examples:
# Use default settings (COM4, 9600 baud)
npm run listen
# Use specific port (COM5, default baud rate 9600)
npm run listen COM5
# Use specific port and baud rate
npm run listen COM5 115200As an npm Module
Install the package in your Node.js project:
npm install scale-protocol-scannerThen use it in your code:
const { scanProtocols, getWeight, protocols } = require('scale-protocol-scanner');
// Scan for working protocols
async function findScaleProtocol(port, baudRate) {
const protocolsToTest = Object.keys(protocols);
const results = await scanProtocols(port, baudRate, protocolsToTest);
const successful = results.filter(r => r.success);
if (successful.length > 0) {
return successful[0]; // Return the first successful protocol
}
return null;
}
// Get weight using a specific protocol
async function readScaleWeight(port, baudRate, protocolName) {
const ProtocolClass = protocols[protocolName];
if (!ProtocolClass) {
throw new Error(`Protocol ${protocolName} not found`);
}
const protocol = new ProtocolClass();
return await getWeight(port, baudRate, protocol);
}
// Example usage
(async () => {
try {
// First, scan to find the working protocol
const workingProtocol = await findScaleProtocol('COM3', 9600);
if (workingProtocol) {
console.log(`Found working protocol: ${workingProtocol.protocol}`);
// Then, read the weight using that protocol
const weight = await readScaleWeight('COM3', 9600, workingProtocol.protocol);
console.log(`Weight: ${weight}`);
} else {
console.log('No working protocol found');
}
} catch (error) {
console.error('Error:', error);
}
})();Configuration
The application uses a config.json file for configuration:
{
"defaultPort": "COM3",
"defaultBaudRate": 9600,
"dataFormat": "readline",
"delimiter": "\r\n",
"scaleProtocol": "GenericScale",
"scaleConfig": {
"command": "5",
"attempts": 3,
"sleep": 100
},
"availableProtocols": [
"GenericScale",
"BizerbaCS300",
"Prix3",
"Prix3Light",
"Asep30",
"Ruby",
"RubyBA50",
"DibaG",
"HoneyWell",
"HoneyWell2",
"GramXFOC",
"Tissot",
"PDII",
"MedinesPtiType0",
"MedinesPti",
"B0Checkout",
"Aviator7000",
"PC100",
"RubyDelta",
"DibaG310",
"UWEAM15K",
"DataValueCO1",
"DS700E",
"RubyLCD",
"Ruby200S",
"RubyRK100",
"Filizola",
"Minerva",
"KingshipKSP30",
"CASErPLUS",
"GRAMZFOC",
"TISA",
"ToledoProtocol",
"TISA2",
"ToledoProtocol2",
"DibalGSeries"
]
}Protocol Configuration
- GenericScale: A configurable protocol that can be adapted for many scales
- BizerbaCS300: For Bizerba CS300 series scales
- Prix3: For Prix 3 series scales
- Prix3Light: Light version of Prix 3 protocol
- Asep30: For Asep 30 scales
- Ruby: For Ruby series scales
- RubyBA50: For Ruby BA50 scales
- DibaG: For Diba G scales
- HoneyWell: For HoneyWell series scales
- HoneyWell2: Alternative HoneyWell protocol
- GramXFOC: For Gram XFOC scales
- Tissot: For Tissot scales
- PDII: For PDII scales
- MedinesPtiType0: For Medines PTI Type 0 scales
- MedinesPti: For Medines PTI scales
- B0Checkout: For B0 Checkout scales
- Aviator7000: For Aviator 7000 scales
- PC100: For PC 100 scales
- RubyDelta: For Ruby Delta scales
- DibaG310: For Diba G310 scales
- UWEAM15K: For UWE AM15K scales
- DataValueCO1: For Data Value CO1 scales
- DS700E: For DS 700E scales
- RubyLCD: For Ruby LCD scales
- Ruby200S: For Ruby 200S scales
- RubyRK100: For Ruby RK100 scales
- Filizola: For Filizola scales
- Minerva: For Minerva scales
- KingshipKSP30: For Kingship KSP30 scales
- CASErPLUS: For CAS Er PLUS scales
- GRAMZFOC: For GRAM ZFOC scales
- TISA: For TISA scales
- ToledoProtocol: For Toledo protocol scales
- TISA2: Alternative TISA protocol
- ToledoProtocol2: Alternative Toledo protocol
- DibalGSeries: For Dibal G Series scales
Troubleshooting
No serial ports found: Make sure your scale is properly connected to your computer via USB or serial cable.
Permission denied: On Linux or macOS, you may need to add your user to the dialout group:
sudo usermod -a -G dialout $USERThen log out and log back in.
Data not parsing correctly: The data format from your scale may differ from what this application expects. You may need to modify the
parseWeightDatafunction to match your scale's data format.Protocol not working: Try different protocols to see which one works with your scale. The GenericScale protocol can be configured with different commands.
Extending Protocols
To add a new scale protocol:
- Create a new class in
scale-protocols.jsthat extendsScaleProtocol - Implement the
getWeightmethod - Add the new protocol to the
protocolMapinenhanced-scale-reader.jsandenhanced-interactive-scale-reader.js - Add the protocol name to the
availableProtocolsarray inconfig.json
API Documentation
scanProtocols(portPath, baudRate, protocols)
Scans multiple protocols to find which ones work with your scale.
Parameters:
portPath(string): The serial port path (e.g., 'COM3', '/dev/ttyUSB0')baudRate(number): The baud rate for the connection (e.g., 9600)protocols(Array): Array of protocol names to test
Returns: Promise<Array> - Array of test results
getWeight(portPath, baudRate, protocol)
Reads weight data from a scale using a specific protocol.
Parameters:
portPath(string): The serial port path (e.g., 'COM3', '/dev/ttyUSB0')baudRate(number): The baud rate for the connection (e.g., 9600)protocol(Object): A protocol instance to use for reading the weight
Returns: Promise - The weight data as a string
protocols
An object containing all available protocol classes that can be instantiated.
License
MIT
Copyright (c) 2025 Helder Correia - Goldylocks Portugal [email protected] (https://www.goldylocks.pt)
