Appearance
Device Control Protocol
The device control protocol uses bytes represented as hexadecimal strings. All multi-byte values are sent in MSB (Most Significant Byte) first format unless stated otherwise.
Message Format
The protocol supports two message formats:
Standard Format (No User ID)
[HEADER][COMMAND][DATA]User Identification Format
[HEADER][USERID]#[COMMAND][DATA]Header Structure
The header is 8 bytes long and has the following format:
| Byte | Value | Description |
|---|---|---|
| 0 | 0x07 | Always starts with 0x07 |
| 1 | 0x01 or 0x03 | Identification byte: 0x01 = no user ID, 0x03 = user ID included |
| 2-7 | 0x00 | Remaining bytes are always 0x00 |
Header Examples:
- No User ID:
070100000000000000(8 bytes) - With User ID:
070300000000000000(8 bytes)
User ID Format
When user identification is included (0x03 in header byte 1), the user ID follows the header and is terminated with a # character:
Numeric User Code
- Format: 4-6 digits (keypad code/PIN)
- Example:
1234#for user code 1234
Email Address
- Format: Full email address
- Example:
user@example.com#
Command Structure
Commands are 2 bytes long and transmitted in LSB first format:
| Command Value | Hex String Representation |
|---|---|
0x000D | "0D00" |
0x0001 | "0100" |
0x00FF | "FF00" |
Complete Message Examples
Standard Command (No User ID)
Header: 070100000000000000
Command: 0D00
Data: [command-specific data]
Complete: 0701000000000000000D00[data]Command with Numeric User ID
Header: 070300000000000000
User ID: 1234#
Command: 0D00
Data: [command-specific data]
Complete: 0703000000000000001234#0D00[data]Data Section
The [DATA] section content varies by command and will be described in command-specific documentation.
Device Error Responses
When a device encounters an error processing a command, it may return error information in the response data. The error format depends on the device model:
General Error Responses
| Error Response | Description |
|---|---|
[0x00xx][FORMAT] | Wrong format of the command |
[0x00xx][NACK] | Command not recognized |
[0x00xx][NRDY] | Command and format are valid, but device is not ready to execute |
[0x00xx][SNRDY] | Command and format are valid, but slave is not ready to execute |
[0x00xx][DISABLED] | Command and format is valid, but command is disabled or cannot be executed |
[0x0000] | Command number [0x00xx] is not recognized |
[0x00xx][response],[E:reason] | Command is valid but an error has occurred during execution |
[0x00xx][ERROR:reason] | Command and its format is valid, but command not executed |
ERROR Responses with Reasons
| Error Response | Description |
|---|---|
[ERROR:WRONG_CODE] | Invalid code for system state change |
[ERROR:FAULT] | Command failed due to a detected fault in the system |
[ERROR:CONTROL_AREA] | No permission to manage a specific area |
[ERROR:NOSLEEP] | Controlled area does not have a sleep/stay feature |
[ERROR:NACK] | Parameters are incorrect, no such controllable objects in the product, synchronization required |
Gate Controller Output Control Errors
| Error Response | Description |
|---|---|
[E:USER] | No such user exists |
[E:TIME] | User is not allowed due to time constraints |
[E:LORA] | No such expander exists |
[E:PGM] | No PGM user assigned |
Notes:
[0x00xx]represents the original command code that caused the error- Error responses are model-dependent and may vary between device types
- Gate controllers may return specific errors when controlling outputs
- These are device-level errors returned in the response data, separate from API-level errors
Command Reference
This page documents the available device control commands that can be sent using the Control Device API endpoint.
0x0001 - Capabilities
Retrieves device capabilities and configuration information.
Request/Response format
Request
[HEADER][0x0001] → 070100000000000000100Response Format
Success Response:
[HEADER][0x0001][DATA ASCII]Error Response:
[HEADER][0x0001][NACK]Response Data Fields
The response data is returned as ASCII text with comma-delimited fields:
| Field | Format | Description |
|---|---|---|
HW | HW:x | Hardware ID (hexadecimal) |
Z | Z:x | Maximum zone count (omit if none) |
O | O:x | Maximum output count (omit if none) |
TRB | TRB:x | Trouble reporting capability (1 = can send faults 0 = can not, see command 0x000F) (may also omit this) |
SYS | SYS:x | Maximum areas count |
TM | TM:x | Internal clock availability (0 = not configurable, 1 = configurable) |
SEN | SEN:x | Maximum temperature sensors count |
IMEI | IMEI:xxxxxxxxx | GSM device IMEI number |
MAC | MAC:xx:xx:xx:xx:xx:xx | Ethernet device MAC address |
CMD | CMD:x.x.x | Device's supported commands list |
V | V:xxxxxxx | Firmware version |
BT | BT:xxxxxxx | Bootloader version |
SN | SN:xxxxxxx | Serial number |
F | F:x | Fire sensor support (1 = has fire sensor and supports fire reset command) |
NS | NS:x | Stay/Sleep mode support (1 = does NOT support STAY and SLEEP mode) |
CO | CO:x | Custom Output feature (1 = device uses Custom Output feature) |
UID | UID:xxxxxxxxx | UID of device (can be omitted if IMEI or MAC used) |
Field Delimiter: All fields are separated by comma (, = 0x2C)
Example Response
Example Response
Raw ASCII Data:
HW:2,Z:32,O:5,TRB:FFFFFFF,SYS:8,IMEI:45464689789,V:SP231_120612,BT:SP131x_boot_1v0,CMD:A.B.D.E.F.10Field Breakdown:
HW:2- Hardware ID is 2Z:32- Maximum 32 zones availableO:5- Maximum 5 outputs availableTRB:FFFFFFF- Trouble types supported (hex format)SYS:8- Maximum 8 areasIMEI:45464689789- Device IMEI codeV:SP231_120612- Firmware version SP231_120612BT:SP131x_boot_1v0- Bootloader version SP131x_boot_1v0CMD:A.B.D.E.F.10- List of supported Relay API commands
Usage Example
javascript
async function getDeviceCapabilities(partialUid) {
// Build capabilities command
const header = "070100000000000000"; // Standard header
const command = "0100"; // Command 0x0001 in MSB format
const commandData = header + command;
try {
const result = await sendDeviceCommand(partialUid, commandData, true);
if (result.data && result.data.length > 0) {
// Parse ASCII response
const capabilitiesText = hexToAscii(result.data[0]);
const capabilities = parseCapabilities(capabilitiesText);
console.log('Device Capabilities:', capabilities);
return capabilities;
}
throw new Error('No capabilities data received');
} catch (error) {
console.error('Failed to get capabilities:', error);
throw error;
}
}
function parseCapabilities(asciiData) {
const fields = {};
const pairs = asciiData.split(',');
pairs.forEach(pair => {
const [key, value] = pair.split(':');
if (key && value) {
fields[key] = value;
}
});
return {
hardwareId: fields.HW,
maxZones: parseInt(fields.Z),
maxOutputs: parseInt(fields.O),
troubleReporting: fields.TRB,
maxAreas: parseInt(fields.SYS),
clockConfigurable: fields.TM === '1',
maxSensors: parseInt(fields.SEN),
imei: fields.IMEI,
macAddress: fields.MAC,
supportedCommands: fields.CMD,
firmwareVersion: fields.V,
bootloaderVersion: fields.BT,
serialNumber: fields.SN,
fireSupport: fields.F === '1',
noStaySleep: fields.NS === '1',
customOutput: fields.CO === '1'
};
}
function hexToAscii(hex) {
let ascii = '';
for (let i = 0; i < hex.length; i += 2) {
ascii += String.fromCharCode(parseInt(hex.substr(i, 2), 16));
}
return ascii;
}0x0002 - Status
Retrieves comprehensive system status including zone states, output states, partition status, signal strength, trouble bits, temperature sensors, and system time.
Note: Devices can send status responses (0x0002) autonomously without a command request.
Request/Response format
Request
[HEADER][0x0002] → 070100000000000000200Response Format
Legacy Response (deprecated, old devices):
[HEADER][0x0002][DATA ASCII]Modern Response (current devices):
[HEADER][0x000D][DATA ASCII]Note: Modern devices respond with command 0x000D instead of 0x0002. The DAT:01 format is deprecated.
Response Data Format
The response data follows this structure:
#F:1,DAT:01,NR,{zones},{outputs},{partitions},{signal},{trouble},{temperature},{timestamp}Response Data Fields
| Position | Field | Format | Description |
|---|---|---|---|
| 1 | F | F:1 | Data start indicator |
| 2 | DAT | DAT:01 | Data type - system status |
| 3 | NR | 00-FF | Message numbering (HEX ASCII) |
| 4 | Zones | Variable hex chars | Zone states (1 char per zone, length depends on device config) |
| 5 | Outputs | Variable hex chars | PGM output states (1 char per output, length depends on device config) |
| 6 | Partitions | Variable hex chars | System partition states (2 chars per partition, length depends on device config) |
| 7 | Signal | 00-99 | Signal strength |
| 8 | Trouble | 8 hex chars | Trouble bits (32 bits) |
| 9 | Temperature | Variable hex chars | Temperature sensor states (1 char per sensor, length depends on device config) |
| 10 | Timestamp | YYYY/MM/DD HH:MM:SS | Module time |
Note: Zone, output, partition, and temperature sensor field lengths vary based on device capabilities. Use the 0x0001 - Capabilities command to determine the actual counts for a specific device.
Zone States (Position 4)
Each zone is represented by 1 hex character (4 bits):
- Bit 0:
1= Zone enabled,0= Zone disabled (Not Available) - Bit 1:
1= Zone alarm,0= Zone restore - Bit 2:
1= Zone tamper/wire fault,0= Restore OK - Bit 3:
1= Bypass ON,0= Bypass OFF
Output States (Position 5)
Each PGM output is represented by 1 hex character (4 bits):
- Bit 0:
1= Remote control enabled,0= Remote control disabled - Bit 1:
1= Output state ON,0= Output state OFF - Bit 2:
1= Reset fire sensor - Bit 3: Reserved
Partition States (Position 6)
Each partition uses 2 hex characters representing system states:
| Hex Value | State Name | Description |
|---|---|---|
00 | st_ARM | Armed |
01 | st_ARM_starting | Arming in progress |
02 | st_ARM_waiting | Waiting to arm |
03 | st_PrepARM | Preparing to arm |
04 | st_DisARM | Disarmed |
05 | st_DisARM_waiting | Waiting to disarm |
06 | st_PrepDisARM | Preparing to disarm |
07 | st_PrepDisARMRemote | Remote disarm/rearm function |
08 | st_Remote_DisARM_waiting | Remote disarm waiting |
09 | st_STAY_DELAY | Stay delay |
0A | st_STAY_DELAY_starting | Stay delay starting |
0B | st_STAY_Delay_waiting | Stay delay waiting |
0C | st_STAY_Delay_waiting_prepare | Stay delay waiting prepare |
0D | st_SLEEP | Sleep mode |
0E | st_SLEEP_starting | Sleep starting |
0F | st_SLEEP_waiting_prepare | Sleep waiting prepare |
10 | st_SLEEP_waiting | Sleep waiting |
11 | st_Cancel | Cancel |
Signal Strength (Position 7)
Signal strength values:
0= -115 dBm or less1= -111 dBm2-30= -110 to -54 dBm31= -52 dBm or greater99= Not known or not detectable
Trouble Bits (Position 8)
32-bit trouble status (0 = OK, 1 = Fault):
- Bit 0: Battery trouble
- Bit 1: AC power loss
- Bit 2: Aux current trouble
- Bit 3: Bell trouble
- Bit 4: Clock trouble
- Bit 5: Communication trouble
- Bit 6: COM BUS trouble
- Bit 7: MCI trouble
- Bit 8: Zone tamper
- Bit 9: Fire alarm
- Bits 10-31: Reserved
Temperature Sensors (Position 9)
Each temperature sensor is represented by 1 hex character (4 bits):
- Bit 0:
1= Sensor enabled,0= Sensor disabled (Not Available) - Bit 1:
1= Sensor online/active,0= Sensor trouble - Bit 2:
1= High temperature alarm,0= High temperature restore - Bit 3:
1= Low temperature alarm,0= Low temperature restore
Example Response
Example Response
Raw ASCII Data:
#F:1,DAT:01,NR,1100000010,10103,0404040404040404,15,3331110000,2012/07/15 15:12:45Field Breakdown:
F:1- Data start indicatorDAT:01- System status data typeNR- Message number1100000010- Zone states: 10 zones configured (Zone 1&2 enabled+alarm, Zone 9 enabled only, others disabled)10103- Output states: 5 outputs configured (OUT1 remote enabled, OUT2 state ON, OUT3 remote enabled)0404040404040404- Partition states: 8 partitions configured, all in state 0x04 (st_DisARM)15- Signal strength level 15 (-95 dBm approximately)3331110000- Temperature sensors: 10 sensors configured (First 3 enabled+online, 4th enabled only)2012/07/15 15:12:45- Current module time
Note: This example shows a device with 10 zones, 5 outputs, 8 partitions, and 10 temperature sensors. Actual field lengths will match the device's capabilities.
Usage Example
javascript
async function getSystemStatus(partialUid) {
// Build status command
const header = "070100000000000000"; // Standard header
const command = "0200"; // Command 0x0002 in MSB format
const commandData = header + command;
try {
const result = await sendDeviceCommand(partialUid, commandData, true);
if (result.data && result.data.length > 0) {
// Parse ASCII response
const statusText = hexToAscii(result.data[0]);
const status = parseSystemStatus(statusText);
console.log('System Status:', status);
return status;
}
throw new Error('No status data received');
} catch (error) {
console.error('Failed to get system status:', error);
throw error;
}
}
function parseSystemStatus(asciiData) {
// Remove # prefix and split by commas
const data = asciiData.replace('#', '').split(',');
if (data.length < 10) {
throw new Error('Invalid status data format');
}
return {
dataStart: data[0], // F:1
dataType: data[1], // DAT:01
messageNumber: data[2], // NR
zones: parseZoneStates(data[3]),
outputs: parseOutputStates(data[4]),
partitions: parsePartitionStates(data[5]),
signalStrength: parseInt(data[6]),
troubleBits: parseTroubleBits(data[7]),
temperatureSensors: parseTemperatureSensors(data[8]),
timestamp: data[9]
};
}
function parseZoneStates(zoneData) {
const zones = [];
for (let i = 0; i < zoneData.length; i++) {
const zoneValue = parseInt(zoneData[i], 16);
zones.push({
zone: i + 1,
enabled: !!(zoneValue & 0x01),
alarm: !!(zoneValue & 0x02),
tamper: !!(zoneValue & 0x04),
bypass: !!(zoneValue & 0x08)
});
}
return zones;
}
function parseOutputStates(outputData) {
const outputs = [];
for (let i = 0; i < outputData.length; i++) {
const outputValue = parseInt(outputData[i], 16);
outputs.push({
output: i + 1,
remoteControlEnabled: !!(outputValue & 0x01),
state: !!(outputValue & 0x02),
resetFireSensor: !!(outputValue & 0x04)
});
}
return outputs;
}
function parsePartitionStates(partitionData) {
const partitions = [];
for (let i = 0; i < partitionData.length; i += 2) {
const partitionValue = partitionData.substr(i, 2);
partitions.push({
partition: (i / 2) + 1,
state: parseInt(partitionValue, 16),
stateDescription: getPartitionStateDescription(parseInt(partitionValue, 16))
});
}
return partitions;
}
function parseTroubleBits(troubleData) {
const troubleValue = parseInt(troubleData, 16);
return {
battery: !!(troubleValue & 0x01),
acLoss: !!(troubleValue & 0x02),
auxCurrent: !!(troubleValue & 0x04),
bell: !!(troubleValue & 0x08),
clock: !!(troubleValue & 0x10),
communication: !!(troubleValue & 0x20),
comBus: !!(troubleValue & 0x40),
mci: !!(troubleValue & 0x80),
zoneTamper: !!(troubleValue & 0x100),
fire: !!(troubleValue & 0x200)
};
}
function parseTemperatureSensors(tempData) {
const sensors = [];
for (let i = 0; i < tempData.length; i++) {
const sensorValue = parseInt(tempData[i], 16);
sensors.push({
sensor: i + 1,
enabled: !!(sensorValue & 0x01),
online: !!(sensorValue & 0x02),
highTempAlarm: !!(sensorValue & 0x04),
lowTempAlarm: !!(sensorValue & 0x08)
});
}
return sensors;
}
function getPartitionStateDescription(state) {
const states = {
0x00: 'st_ARM',
0x01: 'st_ARM_starting',
0x02: 'st_ARM_waiting',
0x03: 'st_PrepARM',
0x04: 'st_DisARM',
0x05: 'st_DisARM_waiting',
0x06: 'st_PrepDisARM',
0x07: 'st_PrepDisARMRemote',
0x08: 'st_Remote_DisARM_waiting',
0x09: 'st_STAY_DELAY',
0x0A: 'st_STAY_DELAY_starting',
0x0B: 'st_STAY_Delay_waiting',
0x0C: 'st_STAY_Delay_waiting_prepare',
0x0D: 'st_SLEEP',
0x0E: 'st_SLEEP_starting',
0x0F: 'st_SLEEP_waiting_prepare',
0x10: 'st_SLEEP_waiting',
0x11: 'st_Cancel'
};
return states[state] || `Unknown state: 0x${state.toString(16).toUpperCase()}`;
}
function hexToAscii(hex) {
let ascii = '';
for (let i = 0; i < hex.length; i += 2) {
ascii += String.fromCharCode(parseInt(hex.substr(i, 2), 16));
}
return ascii;
}0x0003 - Set Output Level
Controls PGM output states, allowing you to turn outputs on or off. Includes special handling for fire sensor reset functionality.
Request/Response format
Request
[HEADER][0x0003][PGM:S] → 070100000000000000300[PGM:S]Where:
PGM= Index of PGM output (decimal format)S= Output status (0= turn off,1= turn on)- Special case: PGM index
99= Fire Reset
Examples:
- Turn on PGM 1:
070100000000000000300313a31 - Turn off PGM 3:
070100000000000000300333a30 - Fire Reset:
07010000000000000030039393a31
Response Format
Success Response (Output State):
[HEADER][0x0003][O:xxxxx]System State Response:
[HEADER][0x0002][SystemState]Error Response:
[HEADER][0x0003][FORMAT]
[HEADER][0x0003][NACK]
[HEADER][0x0003][ERROR]Note: The device may send a system state update (0x0002) in addition to the command response.
Command Parameters
| Parameter | Format | Description |
|---|---|---|
PGM | Decimal number | PGM output index (1-based, device-specific range) |
S | 0 or 1 | Output state: 0 = OFF, 1 = ON |
Special PGM Indices
| PGM Index | Description |
|---|---|
1-N | Regular PGM outputs (N = device-specific, see capabilities) |
99 | Fire Reset - Special command to reset fire sensors |
Response Data Format
Output State Response:
O:xxxxxWhere xxxxx represents the current state of all PGM outputs (similar to O: field in 0x000D Status response).
Each character represents one output state using the same bit encoding:
- Bit 0:
1= Remote control enabled,0= Remote control disabled - Bit 1:
1= Output state ON,0= Output state OFF - Bit 2:
1= Reset fire sensor - Bit 3: Reserved
Event Notifications
Some devices can send event messages in Sur-Gard MLR2-DG multiline receiver Ademco Contact ID format when output states change:
Event Format:
<E|R> 780 <Area> <Zone>Event Fields:
E= PGM activated eventR= PGM deactivated event780= Event code for PGM controlArea= PGM number (decimal)Zone= User ID (decimal)
Example Response
Example Responses
Turn ON PGM 2 - Success:
Request: 070100000000000000300323a31
Response: [HEADER][0x0003][O:10103]Turn OFF PGM 1 - Success:
Request: 070100000000000000300313a30
Response: [HEADER][0x0003][O:00103]Fire Reset - Success:
Request: 07010000000000000030039393a31
Response: [HEADER][0x0003][O:10107]
Event: E 780 99 1234Invalid PGM Index - Error:
Request: 0701000000000000030031303a31
Response: [HEADER][0x0003][NACK]Response Breakdown:
O:10103- 5 outputs: OUT1=OFF+remote, OUT2=ON+remote, OUT3=remote, OUT4=OFF, OUT5=OFFO:00103- Same as above but OUT1 turned OFFO:10107- Fire reset activated (bit 2 set on applicable output)- Event
E 780 99 1234- PGM activated, fire reset (99), by user 1234
Usage Example
javascript
async function setPgmOutput(partialUid, pgmIndex, state) {
// Build set output command
const header = "070100000000000000"; // Standard header
const command = "0300"; // Command 0x0003 in MSB format
// Build command data: PGM:S
const commandData = `${pgmIndex}:${state}`;
const fullCommand = header + command + asciiToHex(commandData);
try {
const result = await sendDeviceCommand(partialUid, fullCommand, true);
if (result.data && result.data.length > 0) {
// Parse ASCII response
const responseText = hexToAscii(result.data[0]);
const outputStates = parseOutputResponse(responseText);
console.log(`PGM ${pgmIndex} set to ${state ? 'ON' : 'OFF'}`);
console.log('Output states:', outputStates);
return outputStates;
}
throw new Error('No response data received');
} catch (error) {
console.error(`Failed to set PGM ${pgmIndex}:`, error);
throw error;
}
}
// Convenience functions for common operations
async function turnOnOutput(partialUid, pgmIndex) {
return await setPgmOutput(partialUid, pgmIndex, 1);
}
async function turnOffOutput(partialUid, pgmIndex) {
return await setPgmOutput(partialUid, pgmIndex, 0);
}
async function fireReset(partialUid) {
console.log('Initiating fire sensor reset...');
return await setPgmOutput(partialUid, 99, 1);
}
// Control multiple outputs
async function setMultipleOutputs(partialUid, outputCommands) {
const results = [];
for (const cmd of outputCommands) {
try {
const result = await setPgmOutput(partialUid, cmd.pgm, cmd.state);
results.push({
pgm: cmd.pgm,
state: cmd.state,
success: true,
result: result
});
// Small delay between commands to avoid overwhelming the device
await new Promise(resolve => setTimeout(resolve, 100));
} catch (error) {
results.push({
pgm: cmd.pgm,
state: cmd.state,
success: false,
error: error.message
});
}
}
return results;
}
function parseOutputResponse(responseText) {
// Expected format: O:xxxxx
if (!responseText.startsWith('O:')) {
throw new Error('Invalid output response format');
}
const outputData = responseText.substring(2);
const outputs = [];
for (let i = 0; i < outputData.length; i++) {
const outputValue = parseInt(outputData[i], 16);
outputs.push({
output: i + 1,
remoteControlEnabled: !!(outputValue & 0x01),
state: !!(outputValue & 0x02),
resetFireSensor: !!(outputValue & 0x04),
raw: outputValue
});
}
return outputs;
}
function asciiToHex(ascii) {
let hex = '';
for (let i = 0; i < ascii.length; i++) {
hex += ascii.charCodeAt(i).toString(16).padStart(2, '0');
}
return hex;
}
function hexToAscii(hex) {
let ascii = '';
for (let i = 0; i < hex.length; i += 2) {
ascii += String.fromCharCode(parseInt(hex.substr(i, 2), 16));
}
return ascii;
}
// Usage examples
async function demonstrateOutputControl(partialUid) {
try {
// Turn on multiple outputs
console.log('Setting multiple outputs...');
const commands = [
{ pgm: 1, state: 1 }, // Turn on PGM 1
{ pgm: 2, state: 0 }, // Turn off PGM 2
{ pgm: 3, state: 1 } // Turn on PGM 3
];
const results = await setMultipleOutputs(partialUid, commands);
console.log('Batch operation results:', results);
// Emergency fire reset
console.log('Performing fire reset...');
await fireReset(partialUid);
} catch (error) {
console.error('Output control demonstration failed:', error);
}
}0x0004 - Set Device Time
Sets the internal clock/time on the device. This command allows synchronizing the device's internal clock with an external time source.
Request/Response format
Request
[HEADER][0x0004][YYYY/MM/DD HH:mm:ss] → 070100000000000000400[YYYY/MM/DD HH:mm:ss]Where:
YYYY/MM/DD HH:mm:ss= Date and time in ASCII format- Date format:
YYYY/MM/DD(4-digit year, 2-digit month, 2-digit day) - Time format:
HH:mm:ss(24-hour format with colons)
Examples:
- Set to Dec 25, 2023 3:30:45 PM:
070100000000000000400323032332f31322f32352031353a33303a3435 - Set to Jan 1, 2024 midnight:
070100000000000000400323032342f30312f30312030303a30303a3030
Response Format
Success Response:
[HEADER][0x0004][TM:YYYY/MM/DD HH-mm-ss]Error Response:
[HEADER][0x0004][FORMAT]
[HEADER][0x0004][NACK]
[HEADER][0x0004][ERROR]Note: The response uses hyphens (-) instead of colons (:) in the time portion to match the format used in 0x000D Status responses.
Command Parameters
| Parameter | Format | Required | Description |
|---|---|---|---|
YYYY | 4 digits | Yes | Year (e.g., 2024) |
MM | 2 digits | Yes | Month (01-12) |
DD | 2 digits | Yes | Day (01-31) |
HH | 2 digits | Yes | Hour (00-23, 24-hour format) |
mm | 2 digits | Yes | Minutes (00-59) |
ss | 2 digits | Yes | Seconds (00-59) |
Time Format Requirements
Request Format:
- Date separator:
/(forward slash) - Date/Time separator:
(space) - Time separator:
:(colon) - Format:
YYYY/MM/DD HH:mm:ss
Response Format:
- Date separator:
/(forward slash) - Date/Time separator:
(space) - Time separator:
-(hyphen) - Format:
TM:YYYY/MM/DD HH-mm-ss
Device Compatibility
Clock Support: Use the 0x0001 - Capabilities command to check if the device supports time configuration:
TM:0= Clock not configurableTM:1= Clock configurable (supports 0x0004 command)
Important Notes:
- Only devices with
TM:1capability support this command - Time is stored in device's local timezone
- Some devices may automatically adjust for daylight saving time
- Setting time may trigger system events on some devices
Example Response
Example Responses
Set time to 2024/03/15 14:30:00 - Success:
Request: 070100000000000000400323032342f30332f31352031343a33303a3030
Response: [HEADER][0x0004][544d3a323032342f30332f31352031343a33303a3030]Invalid date format - Error:
Request: 0701000000000000004004d61726368203135, 20323032342032 3a33302 504d
Response: [HEADER][0x0004][FORMAT]Device doesn't support clock - Error:
Request: 070100000000000000400323032342f30332f31352031343a33303a3030
Response: [HEADER][0x0004][NACK]Invalid time values - Error:
Request: 070100000000000000400323032342f31332f33322032353a37303a3830
Response: [HEADER][0x0004][FORMAT]Usage Example
javascript
async function setDeviceTime(partialUid, dateTime) {
// Build set time command
const header = "070100000000000000"; // Standard header
const command = "0400"; // Command 0x0004 in MSB format
// Format datetime string (ensure proper format)
let formattedDateTime;
if (dateTime instanceof Date) {
formattedDateTime = formatDateTimeForDevice(dateTime);
} else if (typeof dateTime === 'string') {
// Validate and use provided string
if (!isValidDateTimeFormat(dateTime)) {
throw new Error('Invalid datetime format. Use YYYY/MM/DD HH:mm:ss');
}
formattedDateTime = dateTime;
} else {
throw new Error('DateTime must be a Date object or string in YYYY/MM/DD HH:mm:ss format');
}
const fullCommand = header + command + asciiToHex(formattedDateTime);
try {
const result = await sendDeviceCommand(partialUid, fullCommand, true);
if (result.data && result.data.length > 0) {
// Parse ASCII response
const responseText = hexToAscii(result.data[0]);
const timeResponse = parseTimeResponse(responseText);
console.log('Device time set successfully:', timeResponse);
return timeResponse;
}
throw new Error('No response data received');
} catch (error) {
console.error('Failed to set device time:', error);
throw error;
}
}
// Convenience functions
async function setDeviceTimeNow(partialUid) {
const now = new Date();
return await setDeviceTime(partialUid, now);
}
async function setDeviceTimeFromString(partialUid, dateTimeString) {
// Example: "2024/03/15 14:30:00"
return await setDeviceTime(partialUid, dateTimeString);
}
async function checkClockSupport(partialUid) {
// Get device capabilities to check clock support
const capabilities = await getDeviceCapabilities(partialUid);
return capabilities.clockConfigurable;
}
function formatDateTimeForDevice(date) {
const year = date.getFullYear();
const month = String(date.getMonth() + 1).padStart(2, '0');
const day = String(date.getDate()).padStart(2, '0');
const hours = String(date.getHours()).padStart(2, '0');
const minutes = String(date.getMinutes()).padStart(2, '0');
const seconds = String(date.getSeconds()).padStart(2, '0');
return `${year}/${month}/${day} ${hours}:${minutes}:${seconds}`;
}
function isValidDateTimeFormat(dateTimeString) {
// Check format: YYYY/MM/DD HH:mm:ss
const regex = /^\d{4}\/\d{2}\/\d{2} \d{2}:\d{2}:\d{2}$/;
if (!regex.test(dateTimeString)) {
return false;
}
// Parse and validate actual date values
const parts = dateTimeString.split(' ');
const dateParts = parts[0].split('/');
const timeParts = parts[1].split(':');
const year = parseInt(dateParts[0]);
const month = parseInt(dateParts[1]);
const day = parseInt(dateParts[2]);
const hour = parseInt(timeParts[0]);
const minute = parseInt(timeParts[1]);
const second = parseInt(timeParts[2]);
// Basic range validation
if (month < 1 || month > 12) return false;
if (day < 1 || day > 31) return false;
if (hour < 0 || hour > 23) return false;
if (minute < 0 || minute > 59) return false;
if (second < 0 || second > 59) return false;
// Create date object to check validity
const testDate = new Date(year, month - 1, day, hour, minute, second);
return testDate.getFullYear() === year &&
testDate.getMonth() === month - 1 &&
testDate.getDate() === day &&
testDate.getHours() === hour &&
testDate.getMinutes() === minute &&
testDate.getSeconds() === second;
}
function parseTimeResponse(responseText) {
// Expected format: TM:YYYY/MM/DD HH-mm-ss
if (!responseText.startsWith('TM:')) {
throw new Error('Invalid time response format');
}
const timeString = responseText.substring(3); // Remove "TM:" prefix
// Convert response format (HH-mm-ss) to standard format (HH:mm:ss)
const standardFormat = timeString.replace(/(\d{2})-(\d{2})-(\d{2})$/, '$1:$2:$3');
return {
raw: responseText,
formatted: standardFormat,
timestamp: new Date(standardFormat.replace('/', '-').replace('/', '-'))
};
}
function asciiToHex(ascii) {
let hex = '';
for (let i = 0; i < ascii.length; i++) {
hex += ascii.charCodeAt(i).toString(16).padStart(2, '0');
}
return hex;
}
function hexToAscii(hex) {
let ascii = '';
for (let i = 0; i < hex.length; i += 2) {
ascii += String.fromCharCode(parseInt(hex.substr(i, 2), 16));
}
return ascii;
}
// Usage examples
async function demonstrateTimeManagement(partialUid) {
try {
// Check if device supports clock configuration
console.log('Checking clock support...');
const clockSupported = await checkClockSupport(partialUid);
if (!clockSupported) {
console.log('Device does not support clock configuration');
return;
}
// Set device time to current time
console.log('Setting device time to current time...');
const currentTimeResult = await setDeviceTimeNow(partialUid);
console.log('Current time set:', currentTimeResult);
// Set specific time
console.log('Setting specific time...');
const specificTime = '2024/12/25 09:00:00';
const specificTimeResult = await setDeviceTimeFromString(partialUid, specificTime);
console.log('Specific time set:', specificTimeResult);
// Synchronize with server time (example)
console.log('Synchronizing with server time...');
const serverTime = new Date(); // Would typically be from NTP or server API
const syncResult = await setDeviceTime(partialUid, serverTime);
console.log('Server time synchronized:', syncResult);
} catch (error) {
console.error('Time management demonstration failed:', error);
}
}0x0005 - Remote Device Reset
Sends a reboot command to the device. This command initiates a device restart/reset remotely without expecting a response from the device.
Request/Response format
Request
[HEADER][0x0005] → 070100000000000000500Examples:
- Standard reset:
070100000000000000500
Response Format
No Response Expected:
No response - Device will reboot immediatelyImportant Notes:
- This is a fire-and-forget command
- The device will not send a response before rebooting
- Connection will be lost during the reset process
- Device may take 30-120 seconds to fully restart and reconnect
Command Behavior
| Aspect | Description |
|---|---|
| Execution | Immediate device reboot initiation |
| Response | None - command does not wait for acknowledgment |
| Connection | Will be terminated during reset |
| Recovery Time | Typically 30-120 seconds for full restart |
Safety Considerations
⚠️ Important Warnings:
- No Confirmation: Device resets immediately without confirmation
- Connection Loss: All active connections will be terminated
- Service Interruption: Device services will be unavailable during restart
- Unsaved Data: Any unsaved device state may be lost
- Remote Recovery: Ensure device can be accessed after restart
Use Cases
| Scenario | Description |
|---|---|
| System Recovery | Reset device when it becomes unresponsive |
| Configuration Apply | Force restart after configuration changes |
| Memory Clear | Clear temporary states and caches |
| Maintenance | Scheduled system maintenance restarts |
Device Recovery Process
- Command Sent: Device receives 0x0005 command
- Immediate Reset: Device begins shutdown process
- Connection Lost: All network connections terminated
- Boot Process: Device hardware and firmware restart
- Service Start: Device services and networking restart
- Ready State: Device becomes available and reconnects
Example Usage
Example Scenarios
Device Reset:
Request: 070100000000000000500
Response: [Connection lost - no response]Usage Example
javascript
async function resetDevice(partialUid, waitForReconnect = true) {
// Build reset command
const header = "070100000000000000"; // Standard header
const command = "0500"; // Command 0x0005 in MSB format
const fullCommand = header + command;
try {
console.log(`Initiating device reset for ${partialUid}...`);
// Send reset command (expect no response)
await sendDeviceCommand(partialUid, fullCommand, false);
console.log('Reset command sent successfully');
if (waitForReconnect) {
console.log('Waiting for device to restart and reconnect...');
await waitForDeviceReconnection(partialUid);
console.log('Device has successfully restarted and reconnected');
}
return {
success: true,
message: 'Device reset completed',
timestamp: new Date().toISOString()
};
} catch (error) {
// Connection errors are expected during reset
if (error.message.includes('connection') || error.message.includes('timeout')) {
console.log('Connection lost as expected during device reset');
if (waitForReconnect) {
console.log('Waiting for device to restart...');
await waitForDeviceReconnection(partialUid);
console.log('Device has successfully restarted');
}
return {
success: true,
message: 'Device reset initiated (connection lost as expected)',
timestamp: new Date().toISOString()
};
}
console.error('Failed to send reset command:', error);
throw error;
}
}
async function waitForDeviceReconnection(partialUid, maxWaitMinutes = 5) {
const maxWaitMs = maxWaitMinutes * 60 * 1000;
const checkInterval = 10000; // Check every 10 seconds
const startTime = Date.now();
console.log(`Waiting up to ${maxWaitMinutes} minutes for device reconnection...`);
while (Date.now() - startTime < maxWaitMs) {
try {
// Try to get device status to check if it's back online
const status = await getDeviceStatus(partialUid);
if (status) {
const waitTime = Math.round((Date.now() - startTime) / 1000);
console.log(`Device reconnected after ${waitTime} seconds`);
return true;
}
} catch (error) {
// Device still not ready, continue waiting
const elapsed = Math.round((Date.now() - startTime) / 1000);
console.log(`Still waiting... (${elapsed}s elapsed)`);
}
// Wait before next check
await new Promise(resolve => setTimeout(resolve, checkInterval));
}
throw new Error(`Device did not reconnect within ${maxWaitMinutes} minutes`);
}
// Convenience functions
async function emergencyReset(partialUid) {
console.log('🚨 EMERGENCY RESET - Device may be unresponsive');
return await resetDevice(partialUid, true);
}
async function scheduledRestart(partialUid) {
console.log('🔧 SCHEDULED RESTART - Performing maintenance reboot');
return await resetDevice(partialUid, true);
}
async function quickReset(partialUid) {
// Reset without waiting for reconnection (fire and forget)
console.log('⚡ QUICK RESET - Not waiting for reconnection');
return await resetDevice(partialUid, false);
}
// Enhanced reset with pre-checks
async function safeDeviceReset(partialUid) {
try {
// Optional: Check device status before reset
console.log('Checking device status before reset...');
try {
const preResetStatus = await getDeviceStatus(partialUid);
console.log('Pre-reset status obtained:', preResetStatus ? 'Device responsive' : 'Device unresponsive');
} catch (error) {
console.log('Device appears unresponsive, proceeding with reset...');
}
// Perform the reset
const result = await resetDevice(partialUid, true);
// Optional: Verify device functionality after reset
console.log('Verifying device functionality after reset...');
const postResetStatus = await getDeviceStatus(partialUid);
console.log('Post-reset verification successful');
return {
...result,
preResetCheck: true,
postResetVerification: true
};
} catch (error) {
console.error('Safe device reset failed:', error);
throw error;
}
}
// Usage examples
async function demonstrateDeviceReset(partialUid) {
try {
// Example 1: Emergency reset
console.log('\n=== Emergency Reset Example ===');
await emergencyReset(partialUid);
// Wait a bit between examples
await new Promise(resolve => setTimeout(resolve, 2000));
// Example 2: Scheduled maintenance restart
console.log('\n=== Scheduled Restart Example ===');
await scheduledRestart(partialUid);
// Example 3: Quick reset without waiting
console.log('\n=== Quick Reset Example ===');
await quickReset(partialUid);
} catch (error) {
console.error('Device reset demonstration failed:', error);
}
}0x0006 - BYPASS Zone
Controls zone bypass functionality, allowing zones to be bypassed (ignored during arming) or restored to normal operation.
Request/Response format
Request
[HEADER][0x0006][BYP] → 070100000000000000600[BYP]Where:
BYP= Bypass data string with zone states- Format: String of hex characters representing zone bypass states
- Each character:
0= Normal operation,1= Bypassed
Examples:
- Bypass zones 1 and 3:
070100000000000000600313031 - Restore all zones to normal:
070100000000000000600303030303030 - Bypass zone 5 only:
070100000000000000600303030313030
Response Format
Legacy Response (old devices):
[HEADER][0x0002][SystemState]Modern Response (current devices):
[HEADER][0x0006][Z:xxxxxxx]Error Response:
[HEADER][0x0006][FORMAT]
[HEADER][0x0006][NACK]
[HEADER][0x0006][ERROR]Note: Legacy devices respond with a full system state update (0x0002), while modern devices return the specific zone bypass states in Z: format.
Command Parameters
| Parameter | Format | Description |
|---|---|---|
BYP | Hex string | Zone bypass states (1 hex char per zone) |
Bypass States
Each zone is represented by 1 hex character:
0= Normal operation - Zone is active and will trigger alarms1= Bypassed - Zone is ignored during arming and operation
Zone Configuration
Zone Count: Use the 0x0001 - Capabilities command to determine the maximum number of zones (Z:x field) supported by the device.
Zone Indexing: Zones are indexed starting from 1:
- First character = Zone 1
- Second character = Zone 2
- And so on...
Response Data Format
Modern Response (Z: format):
Z:xxxxxxxWhere xxxxxxx represents the current bypass state of all zones, using the same format as the Zone States field in 0x0002 Status responses.
Each hex character represents one zone with bit encoding:
- Bit 0:
1= Zone enabled,0= Zone disabled (Not Available) - Bit 1:
1= Zone alarm,0= Zone restore - Bit 2:
1= Zone tamper/wire fault,0= Restore OK - Bit 3:
1= Bypass ON,0= Bypass OFF
Safety Considerations
⚠️ Important Warnings:
- Security Impact: Bypassed zones will not trigger alarms
- Zone Status: Bypassed zones may still show tamper/fault conditions
- Arming Impact: System can be armed with bypassed zones
- Automatic Restore: Some systems may automatically restore bypassed zones after disarming
Example Response
Example Responses
Bypass zones 1 and 3 - Success:
Request: 070100000000000000600313031
Response: [HEADER][0x0006][Z:9000900000]Restore all zones to normal - Success:
Request: 070100000000000000600303030303030
Response: [HEADER][0x0006][Z:1000100010]Bypass zone 5 only - Success:
Request: 070100000000000000600303030313030
Response: [HEADER][0x0006][Z:1000100090]Invalid zone configuration - Error:
Request: 0701000000000000006003130313230
Response: [HEADER][0x0006][FORMAT]Response Breakdown:
Z:9000900000- Zone 1 and 3 bypassed (hex 9 = binary 1001: enabled + bypassed)Z:1000100010- All zones normal operation (hex 1 = binary 0001: enabled only)Z:1000100090- Zone 5 bypassed, others normal
Usage Example
javascript
async function setZoneBypass(partialUid, zoneStates) {
// Build bypass command
const header = "070100000000000000"; // Standard header
const command = "0600"; // Command 0x0006 in MSB format
// Build bypass data string
let bypassData;
if (Array.isArray(zoneStates)) {
// Convert array of boolean values to hex string
bypassData = zoneStates.map(bypassed => bypassed ? '1' : '0').join('');
} else if (typeof zoneStates === 'string') {
// Use provided hex string directly
bypassData = zoneStates;
} else {
throw new Error('Zone states must be an array of booleans or a hex string');
}
const fullCommand = header + command + asciiToHex(bypassData);
try {
const result = await sendDeviceCommand(partialUid, fullCommand, true);
if (result.data && result.data.length > 0) {
// Parse ASCII response
const responseText = hexToAscii(result.data[0]);
const zoneResponse = parseZoneBypassResponse(responseText);
console.log('Zone bypass states updated:', zoneResponse);
return zoneResponse;
}
throw new Error('No response data received');
} catch (error) {
console.error('Failed to set zone bypass:', error);
throw error;
}
}
// Convenience functions for common operations
async function bypassZone(partialUid, zoneNumber, totalZones) {
const zoneStates = new Array(totalZones).fill(false);
zoneStates[zoneNumber - 1] = true; // Zone numbers are 1-based
return await setZoneBypass(partialUid, zoneStates);
}
async function bypassMultipleZones(partialUid, zoneNumbers, totalZones) {
const zoneStates = new Array(totalZones).fill(false);
zoneNumbers.forEach(zoneNum => {
if (zoneNum >= 1 && zoneNum <= totalZones) {
zoneStates[zoneNum - 1] = true; // Zone numbers are 1-based
}
});
return await setZoneBypass(partialUid, zoneStates);
}
async function restoreZone(partialUid, zoneNumber, totalZones) {
const zoneStates = new Array(totalZones).fill(false);
return await setZoneBypass(partialUid, zoneStates);
}
async function restoreAllZones(partialUid, totalZones) {
const zoneStates = new Array(totalZones).fill(false);
return await setZoneBypass(partialUid, zoneStates);
}
// Get current zone bypass states
async function getZoneBypassStates(partialUid) {
try {
// Get system status to see current zone states
const status = await getSystemStatus(partialUid);
if (status && status.zones) {
return status.zones.map(zone => ({
zone: zone.zone,
bypassed: zone.bypass,
enabled: zone.enabled,
alarm: zone.alarm,
tamper: zone.tamper
}));
}
throw new Error('No zone status data available');
} catch (error) {
console.error('Failed to get zone bypass states:', error);
throw error;
}
}
function parseZoneBypassResponse(responseText) {
// Expected format: Z:xxxxxxx
if (!responseText.startsWith('Z:')) {
throw new Error('Invalid zone bypass response format');
}
const zoneData = responseText.substring(2);
const zones = [];
for (let i = 0; i < zoneData.length; i++) {
const zoneValue = parseInt(zoneData[i], 16);
zones.push({
zone: i + 1,
enabled: !!(zoneValue & 0x01),
alarm: !!(zoneValue & 0x02),
tamper: !!(zoneValue & 0x04),
bypassed: !!(zoneValue & 0x08),
raw: zoneValue
});
}
return {
zones: zones,
bypassedZones: zones.filter(z => z.bypassed).map(z => z.zone),
normalZones: zones.filter(z => !z.bypassed && z.enabled).map(z => z.zone)
};
}
function asciiToHex(ascii) {
let hex = '';
for (let i = 0; i < ascii.length; i++) {
hex += ascii.charCodeAt(i).toString(16).padStart(2, '0');
}
return hex;
}
function hexToAscii(hex) {
let ascii = '';
for (let i = 0; i < hex.length; i += 2) {
ascii += String.fromCharCode(parseInt(hex.substr(i, 2), 16));
}
return ascii;
}
// Usage examples
async function demonstrateZoneBypass(partialUid) {
try {
// Get device capabilities to determine zone count
console.log('Getting device capabilities...');
const capabilities = await getDeviceCapabilities(partialUid);
const maxZones = capabilities.maxZones;
console.log(`Device supports ${maxZones} zones`);
// Get current zone bypass states
console.log('Getting current zone states...');
const currentStates = await getZoneBypassStates(partialUid);
console.log('Current zone states:', currentStates);
// Bypass specific zones
console.log('Bypassing zones 1 and 3...');
const bypassResult1 = await bypassMultipleZones(partialUid, [1, 3], maxZones);
console.log('Bypass result:', bypassResult1);
// Wait a moment
await new Promise(resolve => setTimeout(resolve, 2000));
// Bypass a single zone
console.log('Bypassing zone 5 only...');
const bypassResult2 = await bypassZone(partialUid, 5, maxZones);
console.log('Single zone bypass result:', bypassResult2);
// Wait a moment
await new Promise(resolve => setTimeout(resolve, 2000));
// Restore all zones to normal
console.log('Restoring all zones to normal...');
const restoreResult = await restoreAllZones(partialUid, maxZones);
console.log('Restore result:', restoreResult);
// Final status check
console.log('Final zone states:');
const finalStates = await getZoneBypassStates(partialUid);
console.log('Final states:', finalStates);
} catch (error) {
console.error('Zone bypass demonstration failed:', error);
}
}
// Advanced bypass management
async function smartZoneBypass(partialUid, options = {}) {
const {
zonesToBypass = [],
zonesToRestore = [],
restoreAll = false,
checkAlarms = true
} = options;
try {
// Get device capabilities
const capabilities = await getDeviceCapabilities(partialUid);
const maxZones = capabilities.maxZones;
// Get current zone states if checking alarms
let currentStates = [];
if (checkAlarms) {
currentStates = await getZoneBypassStates(partialUid);
// Warn if trying to bypass zones that are in alarm
const alarmedZones = currentStates.filter(z => z.alarm && zonesToBypass.includes(z.zone));
if (alarmedZones.length > 0) {
console.warn('Warning: Attempting to bypass zones currently in alarm:',
alarmedZones.map(z => z.zone));
}
}
// Build new zone states
let newStates;
if (restoreAll) {
newStates = new Array(maxZones).fill(false);
} else {
// Start with current bypass states or all normal
newStates = currentStates.length > 0
? currentStates.map(z => z.bypassed)
: new Array(maxZones).fill(false);
// Apply bypass changes
zonesToBypass.forEach(zoneNum => {
if (zoneNum >= 1 && zoneNum <= maxZones) {
newStates[zoneNum - 1] = true;
}
});
// Apply restore changes
zonesToRestore.forEach(zoneNum => {
if (zoneNum >= 1 && zoneNum <= maxZones) {
newStates[zoneNum - 1] = false;
}
});
}
// Apply the new bypass configuration
const result = await setZoneBypass(partialUid, newStates);
return {
success: true,
bypassedZones: result.bypassedZones,
normalZones: result.normalZones,
totalZones: maxZones,
changes: {
bypassed: zonesToBypass,
restored: zonesToRestore,
restoredAll: restoreAll
}
};
} catch (error) {
console.error('Smart zone bypass failed:', error);
throw error;
}
}0x0007 - Get Device TIME
Retrieves the current date and time from the device's internal clock. This command allows reading the device's time without modifying it.
Request/Response format
Request
[HEADER][0x0007] → 070100000000000000700Examples:
- Get current device time:
070100000000000000700
Response Format
Success Response:
[HEADER][0x0007][TM:YYYY/MM/DD HH:MM:SS]Error Response:
[HEADER][0x0007][NACK]
[HEADER][0x0007][ERROR]Note: The response uses the same time format as the timestamp field in 0x000D Status responses.
Command Parameters
This command has no parameters - it simply requests the current device time.
Response Data Format
Time Response:
TM:YYYY/MM/DD HH:MM:SSWhere:
YYYY= 4-digit yearMM= 2-digit month (01-12)DD= 2-digit day (01-31)HH= 2-digit hour (00-23, 24-hour format)MM= 2-digit minutes (00-59)SS= 2-digit seconds (00-59)
Time Format Details
Format Structure:
- Date separator:
/(forward slash) - Date/Time separator:
(space) - Time separator:
:(colon) - Complete format:
TM:YYYY/MM/DD HH:MM:SS
Device Compatibility
Clock Support: Use the 0x0001 - Capabilities command to check if the device has an internal clock:
TM:0= Clock not available/configurableTM:1= Clock available and configurable
Important Notes:
- Devices without internal clocks will return NACK
- Time is returned in device's local timezone
- Time accuracy depends on device's clock source and synchronization
- Some devices may return approximate time if clock is not precisely synchronized
Use Cases
| Scenario | Description |
|---|---|
| Time Verification | Check if device time matches expected time |
| Synchronization Check | Verify time before/after synchronization |
| Logging Correlation | Get device timestamp for event correlation |
| Diagnostic Information | Include device time in system diagnostics |
Example Response
Example Responses
Get current time - Success:
Request: 070100000000000000700
Response: [HEADER][0x0007][544d3a323031322f30372f31352031353a31323a3435]Device without clock - Error:
Request: 070100000000000000700
Response: [HEADER][0x0007][NACK]Clock not synchronized - Error:
Request: 070100000000000000700
Response: [HEADER][0x0007][ERROR]Response Breakdown:
TM:2012/07/15 15:12:45- Device time: July 15, 2012 at 3:12:45 PM- Hex encoded:
544d3a323031322f30372f31352031353a31323a3435
Usage Example
javascript
async function getDeviceTime(partialUid) {
// Build get time command
const header = "070100000000000000"; // Standard header
const command = "0700"; // Command 0x0007 in MSB format
const fullCommand = header + command;
try {
const result = await sendDeviceCommand(partialUid, fullCommand, true);
if (result.data && result.data.length > 0) {
// Parse ASCII response
const responseText = hexToAscii(result.data[0]);
const timeData = parseTimeResponse(responseText);
console.log('Device time retrieved:', timeData);
return timeData;
}
throw new Error('No time data received');
} catch (error) {
console.error('Failed to get device time:', error);
throw error;
}
}
// Time comparison and validation functions
async function compareDeviceTimeWithSystem(partialUid, toleranceSeconds = 30) {
try {
const deviceTimeData = await getDeviceTime(partialUid);
const systemTime = new Date();
const deviceTime = deviceTimeData.timestamp;
const timeDifferenceMs = Math.abs(systemTime.getTime() - deviceTime.getTime());
const timeDifferenceSeconds = Math.floor(timeDifferenceMs / 1000);
const isWithinTolerance = timeDifferenceSeconds <= toleranceSeconds;
return {
deviceTime: deviceTimeData,
systemTime: systemTime,
difference: {
milliseconds: timeDifferenceMs,
seconds: timeDifferenceSeconds,
formatted: formatTimeDifference(timeDifferenceMs)
},
withinTolerance: isWithinTolerance,
toleranceSeconds: toleranceSeconds,
needsSynchronization: !isWithinTolerance
};
} catch (error) {
console.error('Failed to compare device time with system time:', error);
throw error;
}
}
async function checkDeviceClockHealth(partialUid) {
try {
// Check if device supports clock
console.log('Checking device clock capabilities...');
const capabilities = await getDeviceCapabilities(partialUid);
if (!capabilities.clockConfigurable) {
return {
supported: false,
message: 'Device does not have an internal clock',
recommendation: 'Time-dependent features may not be available'
};
}
// Get device time
console.log('Retrieving device time...');
const deviceTimeData = await getDeviceTime(partialUid);
// Compare with system time
console.log('Comparing with system time...');
const comparison = await compareDeviceTimeWithSystem(partialUid, 60); // 1 minute tolerance
// Analyze clock health
const health = {
supported: true,
deviceTime: deviceTimeData,
comparison: comparison,
status: 'unknown',
recommendation: ''
};
if (comparison.withinTolerance) {
health.status = 'healthy';
health.recommendation = 'Device clock is synchronized and accurate';
} else if (comparison.difference.seconds < 300) { // 5 minutes
health.status = 'drift';
health.recommendation = 'Minor time drift detected, consider synchronization';
} else if (comparison.difference.seconds < 3600) { // 1 hour
health.status = 'significant_drift';
health.recommendation = 'Significant time drift detected, synchronization recommended';
} else {
health.status = 'critical_drift';
health.recommendation = 'Critical time drift detected, immediate synchronization required';
}
return health;
} catch (error) {
return {
supported: false,
error: error.message,
status: 'error',
recommendation: 'Unable to assess clock health due to error'
};
}
}
// Time synchronization workflow
async function synchronizeDeviceTime(partialUid, sourceTime = null) {
try {
console.log('Starting time synchronization workflow...');
// Get current device time for comparison
console.log('Getting current device time...');
const currentDeviceTime = await getDeviceTime(partialUid);
console.log('Current device time:', currentDeviceTime.formatted);
// Determine source time
const syncTime = sourceTime || new Date();
console.log('Synchronization target time:', syncTime.toISOString());
// Set the new time
console.log('Setting device time...');
const setResult = await setDeviceTime(partialUid, syncTime);
// Verify synchronization
console.log('Verifying synchronization...');
await new Promise(resolve => setTimeout(resolve, 1000)); // Wait 1 second
const newDeviceTime = await getDeviceTime(partialUid);
// Calculate synchronization accuracy
const syncAccuracy = Math.abs(
new Date().getTime() - newDeviceTime.timestamp.getTime()
) / 1000;
return {
success: true,
oldTime: currentDeviceTime,
newTime: newDeviceTime,
targetTime: syncTime,
accuracySeconds: syncAccuracy,
synchronized: syncAccuracy <= 5 // Consider synchronized if within 5 seconds
};
} catch (error) {
console.error('Time synchronization failed:', error);
throw error;
}
}
function parseTimeResponse(responseText) {
// Expected format: TM:YYYY/MM/DD HH:MM:SS
if (!responseText.startsWith('TM:')) {
throw new Error('Invalid time response format');
}
const timeString = responseText.substring(3); // Remove "TM:" prefix
// Parse the time string
const timeRegex = /^(\d{4})\/(\d{2})\/(\d{2}) (\d{2}):(\d{2}):(\d{2})$/;
const match = timeString.match(timeRegex);
if (!match) {
throw new Error('Invalid time format in response');
}
const [, year, month, day, hour, minute, second] = match;
const timestamp = new Date(
parseInt(year),
parseInt(month) - 1, // Month is 0-based in JavaScript
parseInt(day),
parseInt(hour),
parseInt(minute),
parseInt(second)
);
return {
raw: responseText,
formatted: timeString,
timestamp: timestamp,
components: {
year: parseInt(year),
month: parseInt(month),
day: parseInt(day),
hour: parseInt(hour),
minute: parseInt(minute),
second: parseInt(second)
}
};
}
function formatTimeDifference(milliseconds) {
const seconds = Math.floor(milliseconds / 1000);
const minutes = Math.floor(seconds / 60);
const hours = Math.floor(minutes / 60);
const days = Math.floor(hours / 24);
if (days > 0) {
return `${days}d ${hours % 24}h ${minutes % 60}m ${seconds % 60}s`;
} else if (hours > 0) {
return `${hours}h ${minutes % 60}m ${seconds % 60}s`;
} else if (minutes > 0) {
return `${minutes}m ${seconds % 60}s`;
} else {
return `${seconds}s`;
}
}
function hexToAscii(hex) {
let ascii = '';
for (let i = 0; i < hex.length; i += 2) {
ascii += String.fromCharCode(parseInt(hex.substr(i, 2), 16));
}
return ascii;
}
// Usage examples
async function demonstrateTimeRetrieval(partialUid) {
try {
// Basic time retrieval
console.log('=== Basic Time Retrieval ===');
const deviceTime = await getDeviceTime(partialUid);
console.log('Device time:', deviceTime);
// Time comparison with system
console.log('\n=== Time Comparison ===');
const comparison = await compareDeviceTimeWithSystem(partialUid);
console.log('Time comparison result:', comparison);
// Clock health check
console.log('\n=== Clock Health Check ===');
const healthCheck = await checkDeviceClockHealth(partialUid);
console.log('Clock health:', healthCheck);
// Conditional synchronization
if (healthCheck.status === 'significant_drift' || healthCheck.status === 'critical_drift') {
console.log('\n=== Time Synchronization ===');
const syncResult = await synchronizeDeviceTime(partialUid);
console.log('Synchronization result:', syncResult);
}
} catch (error) {
console.error('Time demonstration failed:', error);
}
}
// Scheduled time monitoring
async function monitorDeviceTime(partialUid, intervalMinutes = 60) {
console.log(`Starting time monitoring for device ${partialUid} (every ${intervalMinutes} minutes)`);
const monitor = setInterval(async () => {
try {
const comparison = await compareDeviceTimeWithSystem(partialUid, 120); // 2 minute tolerance
console.log(`[${new Date().toISOString()}] Device time check:`, {
deviceTime: comparison.deviceTime.formatted,
difference: comparison.difference.formatted,
status: comparison.withinTolerance ? 'OK' : 'DRIFT'
});
// Auto-sync if drift is significant
if (!comparison.withinTolerance && comparison.difference.seconds > 300) {
console.log('Significant drift detected, initiating auto-synchronization...');
await synchronizeDeviceTime(partialUid);
}
} catch (error) {
console.error('Time monitoring check failed:', error);
}
}, intervalMinutes * 60 * 1000);
return monitor; // Return interval ID for cleanup
}0x0008 - Get Sensors Values
Retrieves temperature sensor values and states from all configured sensors on the device. The response format is similar to the SEN: field in 0x000D Status responses.
Request/Response format
Request
[HEADER][0x0008] → 070100000000000000800Examples:
- Get all sensor values:
070100000000000000800
Response Format
Success Response:
[HEADER][0x0008][TempSens]Error Response:
[HEADER][0x0008][NACK]
[HEADER][0x0008][ERROR]Note: The response format is identical to the SEN: field format used in 0x000D Status responses but contains only sensor data.
Command Parameters
This command has no parameters - it requests data from all configured temperature sensors.
Response Data Format
Temperature Sensors Response:
TempSens = Variable hex chars (1 char per sensor)Each temperature sensor is represented by 1 hex character (4 bits) with the following bit encoding:
- Bit 0:
1= Sensor enabled,0= Sensor disabled (Not Available) - Bit 1:
1= Sensor online/active,0= Sensor trouble/offline - Bit 2:
1= High temperature alarm,0= High temperature restore - Bit 3:
1= Low temperature alarm,0= Low temperature restore
Sensor States
| Hex Value | Binary | Description |
|---|---|---|
0 | 0000 | Sensor disabled/not available |
1 | 0001 | Sensor enabled, no issues |
2 | 0010 | Sensor enabled, offline/trouble |
3 | 0011 | Sensor enabled, online |
5 | 0101 | Sensor enabled, high temp alarm |
7 | 0111 | Sensor enabled, online + high temp alarm |
9 | 1001 | Sensor enabled, low temp alarm |
B | 1011 | Sensor enabled, online + low temp alarm |
D | 1101 | Sensor enabled, both temp alarms |
F | 1111 | Sensor enabled, online + both temp alarms |
Device Compatibility
Sensor Support: Use the 0x0001 - Capabilities command to check sensor availability:
SEN:0= No temperature sensors availableSEN:x= Maximum number of temperature sensors (where x > 0)
Important Notes:
- Devices without temperature sensors will return NACK
- Sensor count and availability varies by device model
- Response length matches the sensor count from capabilities
- Sensors may be physically absent but still appear in response as disabled (hex
0)
Use Cases
| Scenario | Description |
|---|---|
| Environmental Monitoring | Track temperature conditions in protected areas |
| Alarm Verification | Check sensor states during temperature events |
| Maintenance Planning | Identify sensors with communication issues |
| System Diagnostics | Monitor sensor health and connectivity |
Example Response
Example Responses
Get sensor values - Success (5 sensors):
Request: 070100000000000000800
Response: [HEADER][0x0008][3331110000]Get sensor values - Success (8 sensors):
Request: 070100000000000000800
Response: [HEADER][0x0008][3310250000F1]Device without sensors - Error:
Request: 070100000000000000800
Response: [HEADER][0x0008][NACK]Response Breakdown:
Example 1: 3331110000
- Sensor 1:
3= Enabled + Online (binary 0011) - Sensor 2:
3= Enabled + Online (binary 0011) - Sensor 3:
3= Enabled + Online (binary 0011) - Sensor 4:
1= Enabled only (binary 0001) - Sensor 5:
1= Enabled only (binary 0001) - Sensors 6-10:
0= Disabled/Not available
Example 2: 3310250000F1
- Sensor 1:
3= Enabled + Online - Sensor 2:
3= Enabled + Online - Sensor 3:
1= Enabled only - Sensor 4:
0= Disabled - Sensor 5:
2= Enabled but offline/trouble - Sensor 6:
5= Enabled + High temp alarm - Sensor 7:
0= Disabled - Sensor 8:
0= Disabled - Sensor 9:
0= Disabled - Sensor 10:
0= Disabled - Sensor 11:
F= Enabled + Online + Both temp alarms - Sensor 12:
1= Enabled only
Usage Example
javascript
async function getSensorValues(partialUid) {
// Build get sensors command
const header = "070100000000000000"; // Standard header
const command = "0800"; // Command 0x0008 in MSB format
const fullCommand = header + command;
try {
const result = await sendDeviceCommand(partialUid, fullCommand, true);
if (result.data && result.data.length > 0) {
// Parse hex response directly (no ASCII conversion needed)
const sensorData = result.data[0];
const sensors = parseSensorValues(sensorData);
console.log('Sensor values retrieved:', sensors);
return sensors;
}
throw new Error('No sensor data received');
} catch (error) {
console.error('Failed to get sensor values:', error);
throw error;
}
}
// Enhanced sensor monitoring with capabilities check
async function getFullSensorStatus(partialUid) {
try {
// First check device capabilities
console.log('Checking sensor capabilities...');
const capabilities = await getDeviceCapabilities(partialUid);
if (!capabilities.maxSensors || capabilities.maxSensors === 0) {
return {
supported: false,
message: 'Device does not have temperature sensors',
sensors: []
};
}
console.log(`Device supports ${capabilities.maxSensors} temperature sensors`);
// Get sensor values
const sensors = await getSensorValues(partialUid);
// Analyze sensor health
const healthSummary = analyzeSensorHealth(sensors);
return {
supported: true,
maxSensors: capabilities.maxSensors,
sensors: sensors,
health: healthSummary
};
} catch (error) {
console.error('Failed to get full sensor status:', error);
throw error;
}
}
// Sensor monitoring and alerting
async function monitorTemperatureSensors(partialUid, alertCallback = null) {
try {
const sensorStatus = await getFullSensorStatus(partialUid);
if (!sensorStatus.supported) {
console.log('Temperature monitoring not available - no sensors found');
return sensorStatus;
}
// Check for alerts
const alerts = [];
sensorStatus.sensors.forEach(sensor => {
if (!sensor.enabled) return;
if (!sensor.online) {
alerts.push({
sensor: sensor.sensor,
type: 'offline',
message: `Sensor ${sensor.sensor} is offline or has communication trouble`
});
}
if (sensor.highTempAlarm) {
alerts.push({
sensor: sensor.sensor,
type: 'high_temperature',
message: `Sensor ${sensor.sensor} high temperature alarm active`
});
}
if (sensor.lowTempAlarm) {
alerts.push({
sensor: sensor.sensor,
type: 'low_temperature',
message: `Sensor ${sensor.sensor} low temperature alarm active`
});
}
});
// Trigger callback if alerts found
if (alerts.length > 0 && alertCallback) {
alertCallback(alerts, sensorStatus);
}
return {
...sensorStatus,
alerts: alerts,
alertCount: alerts.length,
hasAlerts: alerts.length > 0
};
} catch (error) {
console.error('Temperature monitoring failed:', error);
throw error;
}
}
function parseSensorValues(sensorData) {
const sensors = [];
for (let i = 0; i < sensorData.length; i++) {
const sensorValue = parseInt(sensorData[i], 16);
sensors.push({
sensor: i + 1,
enabled: !!(sensorValue & 0x01),
online: !!(sensorValue & 0x02),
highTempAlarm: !!(sensorValue & 0x04),
lowTempAlarm: !!(sensorValue & 0x08),
raw: sensorValue,
hexValue: sensorData[i],
status: getSensorStatusDescription(sensorValue)
});
}
return sensors;
}
function getSensorStatusDescription(sensorValue) {
const descriptions = {
0x0: 'Disabled/Not Available',
0x1: 'Enabled - Normal',
0x2: 'Enabled - Offline/Trouble',
0x3: 'Enabled - Online/Normal',
0x5: 'Enabled - High Temp Alarm',
0x7: 'Enabled - Online + High Temp Alarm',
0x9: 'Enabled - Low Temp Alarm',
0xB: 'Enabled - Online + Low Temp Alarm',
0xD: 'Enabled - Both Temp Alarms',
0xF: 'Enabled - Online + Both Temp Alarms'
};
return descriptions[sensorValue] || `Unknown state: 0x${sensorValue.toString(16).toUpperCase()}`;
}
function analyzeSensorHealth(sensors) {
const enabled = sensors.filter(s => s.enabled);
const online = sensors.filter(s => s.enabled && s.online);
const withAlarms = sensors.filter(s => s.enabled && (s.highTempAlarm || s.lowTempAlarm));
const offline = sensors.filter(s => s.enabled && !s.online);
return {
total: sensors.length,
enabled: enabled.length,
online: online.length,
offline: offline.length,
withAlarms: withAlarms.length,
healthScore: enabled.length > 0 ? Math.round((online.length / enabled.length) * 100) : 0,
status: determineOverallSensorHealth(enabled.length, online.length, withAlarms.length)
};
}
function determineOverallSensorHealth(enabled, online, withAlarms) {
if (enabled === 0) return 'no_sensors';
if (withAlarms > 0) return 'alarms_active';
if (online === 0) return 'all_offline';
if (online < enabled) return 'some_offline';
return 'healthy';
}
// Usage examples
async function demonstrateSensorMonitoring(partialUid) {
try {
// Basic sensor value retrieval
console.log('=== Basic Sensor Values ===');
const sensors = await getSensorValues(partialUid);
console.log('Sensor values:', sensors);
// Full sensor status with capabilities
console.log('\n=== Full Sensor Status ===');
const fullStatus = await getFullSensorStatus(partialUid);
console.log('Full sensor status:', fullStatus);
// Temperature monitoring with alerts
console.log('\n=== Temperature Monitoring ===');
const monitoring = await monitorTemperatureSensors(partialUid, (alerts, status) => {
console.log('🚨 Temperature Alerts:', alerts);
alerts.forEach(alert => {
console.log(` - ${alert.type.toUpperCase()}: ${alert.message}`);
});
});
console.log('Monitoring result:', monitoring);
} catch (error) {
console.error('Sensor monitoring demonstration failed:', error);
}
}
// Scheduled sensor monitoring
async function startSensorMonitoring(partialUid, intervalMinutes = 10) {
console.log(`Starting sensor monitoring for device ${partialUid} (every ${intervalMinutes} minutes)`);
const monitor = setInterval(async () => {
try {
const result = await monitorTemperatureSensors(partialUid, (alerts) => {
const timestamp = new Date().toISOString();
console.log(`[${timestamp}] 🚨 Temperature alerts detected:`, alerts.length);
alerts.forEach(alert => console.log(` ${alert.message}`));
});
if (!result.hasAlerts) {
console.log(`[${new Date().toISOString()}] Temperature monitoring: All sensors normal`);
}
} catch (error) {
console.error('Sensor monitoring check failed:', error);
}
}, intervalMinutes * 60 * 1000);
return monitor; // Return interval ID for cleanup
}
// Compare sensors with system status
async function compareSensorsWithSystemStatus(partialUid) {
try {
// Get dedicated sensor values
const sensorValues = await getSensorValues(partialUid);
// Get system status for comparison
const systemStatus = await getSystemStatus(partialUid);
// Compare sensor data
const comparison = {
dedicatedCommand: sensorValues,
systemStatus: systemStatus.temperatureSensors,
match: JSON.stringify(sensorValues) === JSON.stringify(systemStatus.temperatureSensors),
differences: []
};
// Find differences if any
if (!comparison.match && sensorValues.length === systemStatus.temperatureSensors.length) {
for (let i = 0; i < sensorValues.length; i++) {
const dedicated = sensorValues[i];
const system = systemStatus.temperatureSensors[i];
if (dedicated.raw !== system.raw) {
comparison.differences.push({
sensor: i + 1,
dedicated: dedicated.hexValue,
system: system.hexValue || 'N/A'
});
}
}
}
return comparison;
} catch (error) {
console.error('Sensor comparison failed:', error);
throw error;
}
}0x0009 - System ARM/DISARM/STAY/SLEEP
Controls the arming and disarming states of security system areas/partitions. Supports ARM, DISARM, STAY, and SLEEP modes with user authentication.
Request/Response format
Request
Without User Authentication:
[HEADER datatype=1][0x0009][P:S] → 070100000000000000900[P:S]With User Authentication:
[HEADER datatype=3][UserCode#][0x0009][P:S] → 070300000000000000[UserCode#]0900[P:S]Where:
P= Area/Partition number (1-based)S= System state:0= DISARM,1= ARM,2= SLEEP,3= STAYUserCode= Authentication code (numeric PIN only)
Examples:
- ARM area 1 with user 1234:
0703000000000000001234#0900313a31 - DISARM area 2 without user:
070100000000000000900323a30 - STAY mode area 1 with user:
0703000000000000001234#0900313a33
Response Format
Legacy Response (system state update):
[HEADER][0x0002][SystemState]Modern Response (area states):
[HEADER][0x0009][SYS:xxxxxxxxxxxxxxxx]Additional Status Response:
[HEADER][0x000D][DATA ASCII]Error Response:
[HEADER][0x0009][ERROR:reason]
[HEADER][0x0009][NACK]
[HEADER][0x0009][FORMAT]Note: Modern devices may send both a SYS: response and a full status update (0x000D). The SYS: format is identical to the partition states in 0x000D Status responses.
Command Parameters
| Parameter | Format | Description |
|---|---|---|
P | Decimal number | Area/Partition number (1-based, device-specific range) |
S | Single digit | System state code |
System States
| State Code | State Name | Description |
|---|---|---|
0 | DISARM | System disarmed, all zones inactive |
1 | ARM | Full arm mode, all zones active |
2 | SLEEP | Sleep mode, perimeter zones active |
3 | STAY | Stay mode, entry/exit delays disabled |
Area Configuration
Area Count: Use the 0x0001 - Capabilities command to determine the maximum number of areas (SYS:x field) supported by the device.
Area Indexing: Areas/partitions are indexed starting from 1:
- Area 1 = First partition
- Area 2 = Second partition
- And so on...
Authentication Requirements
User Codes: Some operations may require user authentication:
- Numeric PIN: 4-6 digit codes (e.g.,
1234#) - No Authentication: Some devices allow operation without user codes
Authentication Errors: Invalid or missing user codes may result in:
[ERROR:WRONG_CODE]- Invalid user code[ERROR:CONTROL_AREA]- No permission for specific area[ERROR:NACK]- Authentication required but not provided
Response Data Format
Modern Response (SYS: format):
SYS:xxxxxxxxxxxxxxxxWhere each area uses 2 hex characters representing partition states, identical to the format used in 0x0002 Status responses:
| Hex Value | State Name | Description |
|---|---|---|
00 | st_ARM | Armed |
01 | st_ARM_starting | Arming in progress |
02 | st_ARM_waiting | Waiting to arm |
03 | st_PrepARM | Preparing to arm |
04 | st_DisARM | Disarmed |
05 | st_DisARM_waiting | Waiting to disarm |
06 | st_PrepDisARM | Preparing to disarm |
07 | st_PrepDisARMRemote | Remote disarm/rearm function |
08 | st_Remote_DisARM_waiting | Remote disarm waiting |
09 | st_STAY_DELAY | Stay delay |
0A | st_STAY_DELAY_starting | Stay delay starting |
0B | st_STAY_Delay_waiting | Stay delay waiting |
0C | st_STAY_Delay_waiting_prepare | Stay delay waiting prepare |
0D | st_SLEEP | Sleep mode |
0E | st_SLEEP_starting | Sleep starting |
0F | st_SLEEP_waiting_prepare | Sleep waiting prepare |
10 | st_SLEEP_waiting | Sleep waiting |
11 | st_Cancel | Cancel |
Safety Considerations
⚠️ Important Warnings:
- User Authentication: Verify user codes have appropriate permissions
- Area Dependencies: Some areas may be linked and affect each other
- Entry/Exit Delays: ARM and DISARM may have programmed delays
- Zone Bypass: Check bypassed zones before arming
- System Faults: Address any system faults before arming
Example Response
Example Responses
ARM area 1 with user code - Success:
Request: 0703000000000000001234#0900313a31
Response 1: [HEADER][0x0009][SYS:0004040404040404]
Response 2: [HEADER][0x000D][#F:1,DAT:01,NR,1100000010,10103,0004040404040404,15,3331110000,2024/10/21 14:30:00]DISARM area 2 without user - Success:
Request: 070100000000000000900323a30
Response: [HEADER][0x0009][SYS:0404040404040404]STAY mode area 1 - Success:
Request: 0703000000000000001234#0900313a33
Response: [HEADER][0x0009][SYS:0904040404040404]Invalid user code - Error:
Request: 0703000000000000009999#0900313a31
Response: [HEADER][0x0009][ERROR:WRONG_CODE]Area not supported - Error:
Request: 070100000000000000900393a31
Response: [HEADER][0x0009][ERROR:CONTROL_AREA]Response Breakdown:
SYS:0004040404040404- Area 1 armed (00), others disarmed (04)SYS:0404040404040404- All areas disarmed (04)SYS:0904040404040404- Area 1 in STAY delay (09), others disarmed- Multiple responses indicate both immediate state and full system status
Usage Example
javascript
async function setSystemState(partialUid, area, state, userCode = null) {
// Build system control command
let header, command;
if (userCode) {
// With user authentication
header = "070300000000000000"; // Header with user ID flag
command = `${userCode}#0900`; // User code + command
} else {
// Without user authentication
header = "070100000000000000"; // Standard header
command = "0900"; // Command only
}
// Build command data: P:S
const commandData = `${area}:${state}`;
const fullCommand = header + command + asciiToHex(commandData);
try {
const result = await sendDeviceCommand(partialUid, fullCommand, true);
if (result.data && result.data.length > 0) {
// Parse response (could be SYS: format or error)
const responseText = hexToAscii(result.data[0]);
const systemResponse = parseSystemStateResponse(responseText);
console.log(`Area ${area} state change to ${getStateName(state)} completed`);
console.log('System response:', systemResponse);
return systemResponse;
}
throw new Error('No response data received');
} catch (error) {
console.error(`Failed to set area ${area} to state ${state}:`, error);
throw error;
}
}
// Convenience functions for common operations
async function armSystem(partialUid, area, userCode = null) {
console.log(`Arming area ${area}${userCode ? ` with user ${userCode}` : ''}...`);
return await setSystemState(partialUid, area, 1, userCode);
}
async function disarmSystem(partialUid, area, userCode = null) {
console.log(`Disarming area ${area}${userCode ? ` with user ${userCode}` : ''}...`);
return await setSystemState(partialUid, area, 0, userCode);
}
async function setStayMode(partialUid, area, userCode = null) {
console.log(`Setting area ${area} to STAY mode${userCode ? ` with user ${userCode}` : ''}...`);
return await setSystemState(partialUid, area, 3, userCode);
}
async function setSleepMode(partialUid, area, userCode = null) {
console.log(`Setting area ${area} to SLEEP mode${userCode ? ` with user ${userCode}` : ''}...`);
return await setSystemState(partialUid, area, 2, userCode);
}
// Multi-area operations
async function setMultipleAreas(partialUid, areaCommands, userCode = null) {
const results = [];
for (const cmd of areaCommands) {
try {
const result = await setSystemState(partialUid, cmd.area, cmd.state, userCode);
results.push({
area: cmd.area,
state: cmd.state,
stateName: getStateName(cmd.state),
success: true,
result: result
});
// Small delay between commands
await new Promise(resolve => setTimeout(resolve, 500));
} catch (error) {
results.push({
area: cmd.area,
state: cmd.state,
stateName: getStateName(cmd.state),
success: false,
error: error.message
});
}
}
return results;
}
// System state verification
async function verifySystemStates(partialUid, expectedStates) {
try {
// Get current system status
const systemStatus = await getSystemStatus(partialUid);
const currentStates = systemStatus.partitions;
const verification = {
expected: expectedStates,
current: currentStates,
matches: [],
mismatches: []
};
expectedStates.forEach(expected => {
const current = currentStates.find(p => p.partition === expected.area);
if (current) {
const match = {
area: expected.area,
expectedState: expected.state,
expectedStateName: getStateName(expected.state),
currentState: current.state,
currentStateName: current.stateDescription,
matches: isStateMatch(expected.state, current.state)
};
if (match.matches) {
verification.matches.push(match);
} else {
verification.mismatches.push(match);
}
}
});
return verification;
} catch (error) {
console.error('System state verification failed:', error);
throw error;
}
}
// Enhanced security operations with validation
async function secureSystemOperation(partialUid, operation, options = {}) {
const {
area,
state,
userCode,
validateUser = true,
checkFaults = true,
verifyResult = true,
timeout = 30000
} = options;
try {
console.log(`Starting secure ${operation} operation for area ${area}`);
// Pre-operation checks
if (checkFaults) {
console.log('Checking system faults...');
const systemStatus = await getSystemStatus(partialUid);
if (systemStatus.troubleBits.fire || systemStatus.troubleBits.zoneTamper) {
throw new Error('System faults detected - operation aborted for safety');
}
}
// User validation (if required)
if (validateUser && userCode) {
console.log('Validating user permissions...');
// This would typically involve checking user permissions
// For now, we'll proceed with the provided user code
}
// Execute the operation
console.log(`Executing ${operation}...`);
const startTime = Date.now();
const result = await setSystemState(partialUid, area, state, userCode);
// Verify the result if requested
if (verifyResult) {
console.log('Verifying operation result...');
// Wait for state transition to complete
let verified = false;
const verifyStartTime = Date.now();
while (!verified && (Date.now() - verifyStartTime) < timeout) {
await new Promise(resolve => setTimeout(resolve, 2000));
const verification = await verifySystemStates(partialUid, [{ area, state }]);
if (verification.matches.length > 0) {
verified = true;
console.log('Operation verified successfully');
} else if (verification.mismatches.length > 0) {
const mismatch = verification.mismatches[0];
console.log(`Still transitioning: ${mismatch.currentStateName} (expected: ${mismatch.expectedStateName})`);
}
}
if (!verified) {
throw new Error(`Operation verification failed - timeout after ${timeout}ms`);
}
}
const operationTime = Date.now() - startTime;
return {
success: true,
operation: operation,
area: area,
state: state,
stateName: getStateName(state),
userCode: userCode ? '***' : null, // Mask user code in response
executionTime: operationTime,
verified: verifyResult,
result: result
};
} catch (error) {
console.error(`Secure ${operation} operation failed:`, error);
throw error;
}
}
function parseSystemStateResponse(responseText) {
// Handle different response formats
if (responseText.startsWith('SYS:')) {
// Parse SYS: format
const sysData = responseText.substring(4);
const partitions = [];
for (let i = 0; i < sysData.length; i += 2) {
const partitionValue = sysData.substr(i, 2);
partitions.push({
partition: (i / 2) + 1,
state: parseInt(partitionValue, 16),
stateDescription: getPartitionStateDescription(parseInt(partitionValue, 16)),
raw: partitionValue
});
}
return {
type: 'system_states',
partitions: partitions
};
} else if (responseText.startsWith('ERROR:')) {
// Parse error response
const errorReason = responseText.substring(6);
return {
type: 'error',
error: errorReason,
description: getErrorDescription(errorReason)
};
} else {
// Unknown format
return {
type: 'unknown',
raw: responseText
};
}
}
function getStateName(stateCode) {
const stateNames = {
0: 'DISARM',
1: 'ARM',
2: 'SLEEP',
3: 'STAY'
};
return stateNames[stateCode] || `Unknown(${stateCode})`;
}
function getPartitionStateDescription(state) {
const states = {
0x00: 'st_ARM',
0x01: 'st_ARM_starting',
0x02: 'st_ARM_waiting',
0x03: 'st_PrepARM',
0x04: 'st_DisARM',
0x05: 'st_DisARM_waiting',
0x06: 'st_PrepDisARM',
0x07: 'st_PrepDisARMRemote',
0x08: 'st_Remote_DisARM_waiting',
0x09: 'st_STAY_DELAY',
0x0A: 'st_STAY_DELAY_starting',
0x0B: 'st_STAY_Delay_waiting',
0x0C: 'st_STAY_Delay_waiting_prepare',
0x0D: 'st_SLEEP',
0x0E: 'st_SLEEP_starting',
0x0F: 'st_SLEEP_waiting_prepare',
0x10: 'st_SLEEP_waiting',
0x11: 'st_Cancel'
};
return states[state] || `Unknown state: 0x${state.toString(16).toUpperCase()}`;
}
function getErrorDescription(errorReason) {
const errors = {
'WRONG_CODE': 'Invalid user code provided',
'CONTROL_AREA': 'No permission to control this area',
'FAULT': 'System fault prevents operation',
'NOSLEEP': 'Area does not support SLEEP mode',
'NACK': 'Command parameters incorrect or area not available'
};
return errors[errorReason] || `Unknown error: ${errorReason}`;
}
function isStateMatch(requestedState, currentPartitionState) {
// Map requested states to expected partition states
const stateMapping = {
0: [0x04, 0x05, 0x06], // DISARM states
1: [0x00, 0x01, 0x02, 0x03], // ARM states
2: [0x0D, 0x0E, 0x0F, 0x10], // SLEEP states
3: [0x09, 0x0A, 0x0B, 0x0C] // STAY states
};
const expectedStates = stateMapping[requestedState] || [];
return expectedStates.includes(currentPartitionState);
}
function asciiToHex(ascii) {
let hex = '';
for (let i = 0; i < ascii.length; i++) {
hex += ascii.charCodeAt(i).toString(16).padStart(2, '0');
}
return hex;
}
function hexToAscii(hex) {
let ascii = '';
for (let i = 0; i < hex.length; i += 2) {
ascii += String.fromCharCode(parseInt(hex.substr(i, 2), 16));
}
return ascii;
}
// Usage examples
async function demonstrateSystemControl(partialUid) {
try {
const userCode = '1234'; // Example user code
// Basic operations
console.log('=== Basic System Control ===');
// ARM area 1
console.log('Arming area 1...');
const armResult = await armSystem(partialUid, 1, userCode);
console.log('ARM result:', armResult);
await new Promise(resolve => setTimeout(resolve, 5000)); // Wait 5 seconds
// Set STAY mode
console.log('Setting STAY mode...');
const stayResult = await setStayMode(partialUid, 1, userCode);
console.log('STAY result:', stayResult);
await new Promise(resolve => setTimeout(resolve, 5000));
// DISARM
console.log('Disarming system...');
const disarmResult = await disarmSystem(partialUid, 1, userCode);
console.log('DISARM result:', disarmResult);
// Multi-area operations
console.log('\n=== Multi-Area Operations ===');
const multiCommands = [
{ area: 1, state: 1 }, // ARM area 1
{ area: 2, state: 3 }, // STAY area 2
];
const multiResults = await setMultipleAreas(partialUid, multiCommands, userCode);
console.log('Multi-area results:', multiResults);
// Secure operations with validation
console.log('\n=== Secure Operations ===');
const secureResult = await secureSystemOperation(partialUid, 'ARM', {
area: 1,
state: 1,
userCode: userCode,
validateUser: true,
checkFaults: true,
verifyResult: true
});
console.log('Secure operation result:', secureResult);
} catch (error) {
console.error('System control demonstration failed:', error);
}
}0x000A - Set Output Pulse
Controls PGM outputs with timed pulse functionality, allowing outputs to be turned on or off for a specified duration. Supports single or multiple output control in one command with individual timing for each output.
Request/Response format
Request
[HEADER][0x000A][PGM:S:TIME] → 070100000000000000A00[PGM:S:TIME]Where:
PGM= Index of PGM output (decimal format)S= Output state:0= turn OFF,1= turn ONTIME= Pulse length in seconds (decimal, 0-999999, variable length)
Single Output Examples:
- Turn on PGM 1 for 10 seconds:
070100000000000000A00313a313a3130 - Turn off PGM 2 for 5 seconds:
070100000000000000A00323a303a35 - Turn on PGM 4 for 1589 seconds:
070100000000000000A00343a313a31353839
Multiple Output Examples:
- Multiple outputs:
070100000000000000A00313a313a31302c323a313a353238392c343a303a31353839- PGM 1: ON for 10 seconds
- PGM 2: ON for 5289 seconds
- PGM 4: OFF for 1589 seconds
Response Format
System State Response:
[HEADER][0x0002][O:SystemState]Output State Response:
[HEADER][0x000A][O:xxxxx]Error Response:
[HEADER][0x000A][NACK]
[HEADER][0x000A][FORMAT]
[HEADER][0x000A][ERROR]
[HEADER][0x000A][O:xxxxx],[E:xx]Note: The output state format is similar to the O: field in 0x000D Status responses. Error responses may include both unchanged state and error code.
Command Parameters
| Parameter | Format | Description |
|---|---|---|
PGM | Decimal number | PGM output index (1-based, device-specific range) |
S | 0 or 1 | Output state: 0 = OFF, 1 = ON |
TIME | Decimal number | Pulse duration in seconds (0-999999) |
Pulse Duration
Time Range: 0 to 999999 seconds (approximately 11.5 days maximum)
0= Instantaneous pulse (minimal duration)1-999999= Pulse duration in seconds- Variable length format (no leading zeros required)
Pulse Behavior:
- ON Pulse: Output turns ON for specified duration, then automatically turns OFF
- OFF Pulse: Output turns OFF for specified duration, then automatically turns ON
- Timing Accuracy: Depends on device timing resolution (typically ±1 second)
Multiple Output Support
Multi-Output Format:
PGM1:S1:TIME1,PGM2:S2:TIME2,PGM3:S3:TIME3Capabilities:
- Some devices support multiple outputs in one command
- Each output can have different state and timing
- Comma-separated format for multiple outputs
- Individual error handling per output
Output Configuration
Output Count: Use the 0x0001 - Capabilities command to determine the maximum number of outputs (O:x field) supported by the device.
Output Indexing: Outputs are indexed starting from 1:
- Output 1 = First PGM output
- Output 2 = Second PGM output
- And so on...
Response Data Format
Output State Response:
O:xxxxxWhere xxxxx represents the current state of all PGM outputs, using the same bit encoding as 0x0003 Set Output Level:
- Bit 0:
1= Remote control enabled,0= Remote control disabled - Bit 1:
1= Output state ON,0= Output state OFF - Bit 2:
1= Reset fire sensor - Bit 3: Reserved
Error Response with State:
O:xxxxx,E:xxWhere E:xx represents an error code while O:xxxxx shows the unchanged output states.
Event Notifications
Some devices can send event messages in Sur-Gard MLR2-DG multiline receiver Ademco Contact ID format when output states change:
Event Format:
<E|R> 780 <Area> <Zone>Event Fields:
E= PGM activated event (output turned ON)R= PGM deactivated event (output turned OFF)780= Event code for PGM controlArea= PGM number (decimal)Zone= User ID (decimal)
Use Cases
| Scenario | Description |
|---|---|
| Timed Access Control | Door locks, gate controls with automatic timeout |
| Alert Systems | Sirens, beacons with controlled duration |
| Equipment Control | Motors, pumps with timed operation cycles |
| Notification Systems | Indicator lights with pulse timing |
Example Response
Example Responses
Single output pulse - Success:
Request: 070100000000000000A00313a313a3130
Response 1: [HEADER][0x0002][O:SystemState]
Response 2: [HEADER][0x000A][O:30103]Multiple output pulse - Success:
Request: 070100000000000000A00313a313a31302c323a313a353238392c343a303a31353839
Response: [HEADER][0x000A][O:33103]Invalid PGM index - Error:
Request: 070100000000000000A00393a313a3130
Response: [HEADER][0x000A][NACK]Invalid time value - Error:
Request: 070100000000000000A00313a313a31303030303030
Response: [HEADER][0x000A][FORMAT]Partial success with error:
Request: 070100000000000000A00313a313a31302c393a313a35
Response: [HEADER][0x000A][O:30103,E:22]Event notification:
Request: 070100000000000000A00313a313a3130
Response: [HEADER][0x000A][O:30103]
Event: E 780 1 1234
// 10 seconds later
Event: R 780 1 1234Response Breakdown:
O:30103- PGM 1 ON+remote (3), others same as beforeO:33103- PGM 1&2 ON+remote (3), PGM 4 OFF (1 becomes 1)E:22- Error code 22 (device-specific error)- Events show activation and automatic deactivation after pulse time
Usage Example
javascript
async function setOutputPulse(partialUid, pgmIndex, state, pulseTimeSeconds) {
// Build pulse output command
const header = "070100000000000000"; // Standard header
const command = "A00"; // Command 0x000A in MSB format
// Build command data: PGM:S:TIME
const commandData = `${pgmIndex}:${state}:${pulseTimeSeconds}`;
const fullCommand = header + command + asciiToHex(commandData);
try {
const result = await sendDeviceCommand(partialUid, fullCommand, true);
if (result.data && result.data.length > 0) {
// Parse responses (may be multiple)
const responses = result.data.map(data => {
const responseText = hexToAscii(data);
return parseOutputPulseResponse(responseText);
});
console.log(`PGM ${pgmIndex} pulse ${state ? 'ON' : 'OFF'} for ${pulseTimeSeconds}s completed`);
console.log('Pulse responses:', responses);
return responses;
}
throw new Error('No response data received');
} catch (error) {
console.error(`Failed to set PGM ${pgmIndex} pulse:`, error);
throw error;
}
}
// Multiple output pulse control
async function setMultipleOutputPulses(partialUid, outputCommands) {
// Build multiple output command
const header = "070100000000000000"; // Standard header
const command = "A00"; // Command 0x000A in MSB format
// Build command data: PGM1:S1:TIME1,PGM2:S2:TIME2,...
const commandParts = outputCommands.map(cmd =>
`${cmd.pgm}:${cmd.state}:${cmd.time}`
);
const commandData = commandParts.join(',');
const fullCommand = header + command + asciiToHex(commandData);
try {
const result = await sendDeviceCommand(partialUid, fullCommand, true);
if (result.data && result.data.length > 0) {
const responses = result.data.map(data => {
const responseText = hexToAscii(data);
return parseOutputPulseResponse(responseText);
});
console.log('Multiple output pulse operation completed');
console.log('Commands:', outputCommands);
console.log('Responses:', responses);
return responses;
}
throw new Error('No response data received');
} catch (error) {
console.error('Failed to set multiple output pulses:', error);
throw error;
}
}
// Convenience functions for common operations
async function pulseOutputOn(partialUid, pgmIndex, seconds) {
console.log(`Pulsing PGM ${pgmIndex} ON for ${seconds} seconds...`);
return await setOutputPulse(partialUid, pgmIndex, 1, seconds);
}
async function pulseOutputOff(partialUid, pgmIndex, seconds) {
console.log(`Pulsing PGM ${pgmIndex} OFF for ${seconds} seconds...`);
return await setOutputPulse(partialUid, pgmIndex, 0, seconds);
}
// Common pulse durations
async function shortPulse(partialUid, pgmIndex, state = 1) {
return await setOutputPulse(partialUid, pgmIndex, state, 5); // 5 seconds
}
async function mediumPulse(partialUid, pgmIndex, state = 1) {
return await setOutputPulse(partialUid, pgmIndex, state, 30); // 30 seconds
}
async function longPulse(partialUid, pgmIndex, state = 1) {
return await setOutputPulse(partialUid, pgmIndex, state, 300); // 5 minutes
}
// Advanced pulse control with monitoring
async function monitoredPulse(partialUid, pgmIndex, state, pulseTimeSeconds) {
try {
console.log(`Starting monitored pulse: PGM ${pgmIndex}, state ${state}, duration ${pulseTimeSeconds}s`);
// Get initial output state
const initialStatus = await getSystemStatus(partialUid);
const initialOutput = initialStatus.outputs.find(o => o.output === pgmIndex);
console.log(`Initial PGM ${pgmIndex} state:`, initialOutput ? initialOutput.state : 'unknown');
// Execute pulse command
const pulseStart = Date.now();
const pulseResult = await setOutputPulse(partialUid, pgmIndex, state, pulseTimeSeconds);
// Monitor pulse completion (check periodically)
const checkInterval = Math.min(pulseTimeSeconds * 1000 / 4, 5000); // Check 4 times or every 5s max
let pulseCompleted = false;
const monitor = setInterval(async () => {
try {
const currentStatus = await getSystemStatus(partialUid);
const currentOutput = currentStatus.outputs.find(o => o.output === pgmIndex);
if (currentOutput) {
const expectedState = state === 1;
const currentState = currentOutput.state;
console.log(`Pulse monitor: PGM ${pgmIndex} is ${currentState ? 'ON' : 'OFF'}`);
// Check if pulse has completed (state returned to opposite)
const elapsed = Date.now() - pulseStart;
if (elapsed >= pulseTimeSeconds * 1000 && currentState !== expectedState) {
console.log(`Pulse completed after ${Math.round(elapsed / 1000)}s`);
pulseCompleted = true;
clearInterval(monitor);
}
}
} catch (error) {
console.error('Pulse monitoring error:', error);
}
}, checkInterval);
// Stop monitoring after pulse time + buffer
setTimeout(() => {
if (!pulseCompleted) {
console.log('Pulse monitoring timeout - stopping monitor');
clearInterval(monitor);
}
}, (pulseTimeSeconds + 10) * 1000);
return {
success: true,
pgmIndex: pgmIndex,
state: state,
pulseTimeSeconds: pulseTimeSeconds,
startTime: new Date(pulseStart),
result: pulseResult,
monitored: true
};
} catch (error) {
console.error('Monitored pulse failed:', error);
throw error;
}
}
// Pulse sequence operations
async function pulseSequence(partialUid, sequence, delayBetween = 1000) {
const results = [];
for (let i = 0; i < sequence.length; i++) {
const pulse = sequence[i];
try {
console.log(`Sequence ${i + 1}/${sequence.length}: PGM ${pulse.pgm} ${pulse.state ? 'ON' : 'OFF'} for ${pulse.time}s`);
const result = await setOutputPulse(partialUid, pulse.pgm, pulse.state, pulse.time);
results.push({
step: i + 1,
pulse: pulse,
success: true,
result: result
});
// Wait before next pulse (if not last)
if (i < sequence.length - 1 && delayBetween > 0) {
console.log(`Waiting ${delayBetween}ms before next pulse...`);
await new Promise(resolve => setTimeout(resolve, delayBetween));
}
} catch (error) {
results.push({
step: i + 1,
pulse: pulse,
success: false,
error: error.message
});
console.error(`Sequence step ${i + 1} failed:`, error);
// Continue with remaining pulses
}
}
return {
sequence: sequence,
results: results,
successCount: results.filter(r => r.success).length,
totalSteps: sequence.length
};
}
function parseOutputPulseResponse(responseText) {
// Handle different response formats
if (responseText.startsWith('O:')) {
// Check for error code in response
const parts = responseText.split(',');
const outputData = parts[0].substring(2); // Remove "O:"
const response = {
type: 'output_state',
outputData: outputData,
outputs: parseOutputStates(outputData)
};
// Check for error code
if (parts.length > 1 && parts[1].startsWith('E:')) {
response.error = {
code: parts[1].substring(2),
description: getOutputErrorDescription(parts[1].substring(2))
};
}
return response;
} else if (responseText === 'NACK' || responseText === 'FORMAT' || responseText === 'ERROR') {
return {
type: 'error',
error: responseText,
description: getOutputErrorDescription(responseText)
};
} else {
return {
type: 'unknown',
raw: responseText
};
}
}
function parseOutputStates(outputData) {
const outputs = [];
for (let i = 0; i < outputData.length; i++) {
const outputValue = parseInt(outputData[i], 16);
outputs.push({
output: i + 1,
remoteControlEnabled: !!(outputValue & 0x01),
state: !!(outputValue & 0x02),
resetFireSensor: !!(outputValue & 0x04),
raw: outputValue,
hexValue: outputData[i]
});
}
return outputs;
}
function getOutputErrorDescription(errorCode) {
const errors = {
'NACK': 'Command not acknowledged - invalid PGM index or device not ready',
'FORMAT': 'Invalid command format - check PGM:S:TIME syntax',
'ERROR': 'General command error',
'22': 'PGM output error or timing conflict',
// Add more device-specific error codes as needed
};
return errors[errorCode] || `Unknown error code: ${errorCode}`;
}
function asciiToHex(ascii) {
let hex = '';
for (let i = 0; i < ascii.length; i++) {
hex += ascii.charCodeAt(i).toString(16).padStart(2, '0');
}
return hex;
}
function hexToAscii(hex) {
let ascii = '';
for (let i = 0; i < hex.length; i += 2) {
ascii += String.fromCharCode(parseInt(hex.substr(i, 2), 16));
}
return ascii;
}
// Usage examples
async function demonstrateOutputPulseControl(partialUid) {
try {
// Basic pulse operations
console.log('=== Basic Output Pulse Control ===');
// Short pulse on PGM 1
const shortResult = await shortPulse(partialUid, 1);
console.log('Short pulse result:', shortResult);
await new Promise(resolve => setTimeout(resolve, 2000));
// Medium pulse off PGM 2
const mediumResult = await pulseOutputOff(partialUid, 2, 30);
console.log('Medium pulse result:', mediumResult);
// Multiple output pulses
console.log('\n=== Multiple Output Pulses ===');
const multiCommands = [
{ pgm: 1, state: 1, time: 10 }, // PGM 1 ON for 10s
{ pgm: 2, state: 1, time: 5289 }, // PGM 2 ON for 5289s
{ pgm: 4, state: 0, time: 1589 } // PGM 4 OFF for 1589s
];
const multiResult = await setMultipleOutputPulses(partialUid, multiCommands);
console.log('Multiple pulse result:', multiResult);
// Monitored pulse
console.log('\n=== Monitored Pulse ===');
const monitoredResult = await monitoredPulse(partialUid, 1, 1, 15);
console.log('Monitored pulse result:', monitoredResult);
// Pulse sequence
console.log('\n=== Pulse Sequence ===');
const sequence = [
{ pgm: 1, state: 1, time: 5 },
{ pgm: 2, state: 1, time: 5 },
{ pgm: 3, state: 1, time: 5 }
];
const sequenceResult = await pulseSequence(partialUid, sequence, 2000);
console.log('Pulse sequence result:', sequenceResult);
} catch (error) {
console.error('Output pulse control demonstration failed:', error);
}
}0x000B - Set Device TIME Silently
Sets the internal clock/time on the device without triggering system events or notifications. This command provides a "silent" alternative to 0x0004 - Set Device Time that updates the clock without disturbing normal operations.
Request/Response format
Request
[HEADER][0x000B][YYYY/MM/DD HH:mm:ss] → 070100000000000000B00[YYYY/MM/DD HH:mm:ss]Where:
YYYY/MM/DD HH:mm:ss= Date and time in ASCII format- Date format:
YYYY/MM/DD(4-digit year, 2-digit month, 2-digit day) - Time format:
HH:mm:ss(24-hour format with colons)
Examples:
- Set to Dec 25, 2023 3:30:45 PM:
070100000000000000B00323032332f31322f32352031353a33303a3435 - Set to Jan 1, 2024 midnight:
070100000000000000B00323032342f30312f30312030303a30303a3030
Response Format
System State Response (legacy):
[HEADER][0x0002][SystemState]Time Confirmation Response:
[HEADER][0x000B][TM:YYYY/MM/DD HH-mm-ss]Error Response:
[HEADER][0x000B][FORMAT]
[HEADER][0x000B][NACK]
[HEADER][0x000B][ERROR]Note: The device may send both a system state update (0x0002) and a time confirmation response. The TM: format is similar to the timestamp field in 0x000D Status responses.
Command Parameters
| Parameter | Format | Required | Description |
|---|---|---|---|
YYYY | 4 digits | Yes | Year (e.g., 2024) |
MM | 2 digits | Yes | Month (01-12) |
DD | 2 digits | Yes | Day (01-31) |
HH | 2 digits | Yes | Hour (00-23, 24-hour format) |
mm | 2 digits | Yes | Minutes (00-59) |
ss | 2 digits | Yes | Seconds (00-59) |
Time Format Requirements
Request Format:
- Date separator:
/(forward slash) - Date/Time separator:
(space) - Time separator:
:(colon) - Format:
YYYY/MM/DD HH:mm:ss
Response Format:
- Date separator:
/(forward slash) - Date/Time separator:
(space) - Time separator:
-(hyphen) - Format:
TM:YYYY/MM/DD HH-mm-ss
Silent Operation
Key Differences from 0x0004:
- No System Events: Does not trigger time change events or notifications
- No User Notifications: Silent operation without user interface updates
- Background Update: Clock updated in background without service interruption
- Dual Response: May provide both system state and time confirmation
Use Cases:
- Automated Synchronization: Regular time sync without user awareness
- Background Maintenance: System maintenance time updates
- Service Operations: Remote time correction during normal operation
- Scheduled Updates: Periodic time synchronization from time servers
Device Compatibility
Clock Support: Use the 0x0001 - Capabilities command to check if the device supports time configuration:
TM:0= Clock not configurableTM:1= Clock configurable (supports both 0x0004 and 0x000B commands)
Important Notes:
- Only devices with
TM:1capability support this command - Silent operation means no visible indication of time change
- Time is stored in device's local timezone
- May be preferred for automated systems to avoid event flooding
- Some devices may still log the time change internally
Example Response
Example Responses
Set time silently to 2024/03/15 14:30:00 - Success:
Request: 070100000000000000B00323032342f30332f31352031343a33303a3030
Response 1: [HEADER][0x0002][#F:1,DAT:01,NR,1100000010,10103,0404040404040404,15,3331110000,2024/03/15 14:30:00]
Response 2: [HEADER][0x000B][544d3a323032342f30332f31352031343a33303a3030]Invalid date format - Error:
Request: 070100000000000000B004d61726368203135, 20323032342032 3a33302 504d
Response: [HEADER][0x000B][FORMAT]Device doesn't support clock - Error:
Request: 070100000000000000B00323032342f30332f31352031343a33303a3030
Response: [HEADER][0x000B][NACK]Invalid time values - Error:
Request: 070100000000000000B00323032342f31332f33322032353a37303a3830
Response: [HEADER][0x000B][FORMAT]Response Breakdown:
- Response 1: Full system state with updated timestamp
- Response 2: Time confirmation with TM: prefix
- Silent operation means no additional event notifications sent
Usage Example
javascript
async function setDeviceTimeSilently(partialUid, dateTime) {
// Build silent set time command
const header = "070100000000000000"; // Standard header
const command = "B00"; // Command 0x000B in MSB format (note: B00 not 0B00)
// Format datetime string (ensure proper format)
let formattedDateTime;
if (dateTime instanceof Date) {
formattedDateTime = formatDateTimeForDevice(dateTime);
} else if (typeof dateTime === 'string') {
// Validate and use provided string
if (!isValidDateTimeFormat(dateTime)) {
throw new Error('Invalid datetime format. Use YYYY/MM/DD HH:mm:ss');
}
formattedDateTime = dateTime;
} else {
throw new Error('DateTime must be a Date object or string in YYYY/MM/DD HH:mm:ss format');
}
const fullCommand = header + command + asciiToHex(formattedDateTime);
try {
const result = await sendDeviceCommand(partialUid, fullCommand, true);
if (result.data && result.data.length > 0) {
// Parse responses (may be multiple)
const responses = result.data.map(data => {
const responseText = hexToAscii(data);
return parseTimeResponse(responseText);
});
console.log('Device time set silently:', responses);
return responses;
}
throw new Error('No response data received');
} catch (error) {
console.error('Failed to set device time silently:', error);
throw error;
}
}
// Silent time synchronization for automated systems
async function silentTimeSync(partialUid, timeSource = 'system') {
try {
let syncTime;
switch (timeSource) {
case 'system':
syncTime = new Date();
break;
case 'ntp':
// In real implementation, this would fetch from NTP server
syncTime = new Date(); // Placeholder
console.log('Getting time from NTP server...');
break;
case 'server':
// In real implementation, this would fetch from time API
syncTime = new Date(); // Placeholder
console.log('Getting time from server API...');
break;
default:
throw new Error('Invalid time source. Use: system, ntp, or server');
}
console.log(`Silent time sync from ${timeSource}: ${syncTime.toISOString()}`);
// Set time silently
const result = await setDeviceTimeSilently(partialUid, syncTime);
// Extract time confirmation if available
const timeConfirmation = result.find(r => r.type === 'time_response');
return {
success: true,
source: timeSource,
syncTime: syncTime,
confirmation: timeConfirmation,
silent: true,
responses: result
};
} catch (error) {
console.error('Silent time synchronization failed:', error);
throw error;
}
}
// Scheduled silent time synchronization
async function scheduleSilentTimeSync(partialUid, intervalMinutes = 60, timeSource = 'system') {
console.log(`Starting silent time sync every ${intervalMinutes} minutes from ${timeSource}`);
const syncJob = setInterval(async () => {
try {
const result = await silentTimeSync(partialUid, timeSource);
console.log(`[${new Date().toISOString()}] Silent time sync completed:`, {
source: result.source,
success: result.success,
syncTime: result.syncTime.toISOString()
});
} catch (error) {
console.error(`[${new Date().toISOString()}] Silent time sync failed:`, error.message);
}
}, intervalMinutes * 60 * 1000);
return syncJob; // Return interval ID for cleanup
}
// Compare silent vs normal time setting
async function compareTimeSettingMethods(partialUid, testTime) {
try {
console.log('Comparing silent vs normal time setting methods...');
// Method 1: Normal time setting (0x0004)
console.log('Testing normal time setting (0x0004)...');
const normalStart = Date.now();
const normalResult = await setDeviceTime(partialUid, testTime);
const normalDuration = Date.now() - normalStart;
// Wait a moment
await new Promise(resolve => setTimeout(resolve, 2000));
// Method 2: Silent time setting (0x000B)
console.log('Testing silent time setting (0x000B)...');
const silentStart = Date.now();
const silentResult = await setDeviceTimeSilently(partialUid, testTime);
const silentDuration = Date.now() - silentStart;
return {
testTime: testTime,
normal: {
duration: normalDuration,
result: normalResult,
responseCount: 1
},
silent: {
duration: silentDuration,
result: silentResult,
responseCount: silentResult.length
},
comparison: {
speedDifference: normalDuration - silentDuration,
fasterMethod: normalDuration < silentDuration ? 'normal' : 'silent'
}
};
} catch (error) {
console.error('Time setting method comparison failed:', error);
throw error;
}
}
function parseTimeResponse(responseText) {
// Handle multiple response types
if (responseText.startsWith('TM:')) {
// Time confirmation response
const timeString = responseText.substring(3);
// Parse the time string
const timeRegex = /^(\d{4})\/(\d{2})\/(\d{2}) (\d{2})-(\d{2})-(\d{2})$/;
const match = timeString.match(timeRegex);
if (!match) {
throw new Error('Invalid time format in response');
}
const [, year, month, day, hour, minute, second] = match;
const timestamp = new Date(
parseInt(year),
parseInt(month) - 1,
parseInt(day),
parseInt(hour),
parseInt(minute),
parseInt(second)
);
return {
type: 'time_response',
raw: responseText,
formatted: timeString,
timestamp: timestamp,
source: 'silent_time_set'
};
} else if (responseText.startsWith('#F:')) {
// System state response
return {
type: 'system_state',
raw: responseText,
source: 'silent_time_set'
};
} else {
// Unknown response type
return {
type: 'unknown',
raw: responseText,
source: 'silent_time_set'
};
}
}
function formatDateTimeForDevice(date) {
const year = date.getFullYear();
const month = String(date.getMonth() + 1).padStart(2, '0');
const day = String(date.getDate()).padStart(2, '0');
const hours = String(date.getHours()).padStart(2, '0');
const minutes = String(date.getMinutes()).padStart(2, '0');
const seconds = String(date.getSeconds()).padStart(2, '0');
return `${year}/${month}/${day} ${hours}:${minutes}:${seconds}`;
}
function isValidDateTimeFormat(dateTimeString) {
// Check format: YYYY/MM/DD HH:mm:ss
const regex = /^\d{4}\/\d{2}\/\d{2} \d{2}:\d{2}:\d{2}$/;
if (!regex.test(dateTimeString)) {
return false;
}
// Parse and validate actual date values
const parts = dateTimeString.split(' ');
const dateParts = parts[0].split('/');
const timeParts = parts[1].split(':');
const year = parseInt(dateParts[0]);
const month = parseInt(dateParts[1]);
const day = parseInt(dateParts[2]);
const hour = parseInt(timeParts[0]);
const minute = parseInt(timeParts[1]);
const second = parseInt(timeParts[2]);
// Basic range validation
if (month < 1 || month > 12) return false;
if (day < 1 || day > 31) return false;
if (hour < 0 || hour > 23) return false;
if (minute < 0 || minute > 59) return false;
if (second < 0 || second > 59) return false;
// Create date object to check validity
const testDate = new Date(year, month - 1, day, hour, minute, second);
return testDate.getFullYear() === year &&
testDate.getMonth() === month - 1 &&
testDate.getDate() === day &&
testDate.getHours() === hour &&
testDate.getMinutes() === minute &&
testDate.getSeconds() === second;
}
function asciiToHex(ascii) {
let hex = '';
for (let i = 0; i < ascii.length; i++) {
hex += ascii.charCodeAt(i).toString(16).padStart(2, '0');
}
return hex;
}
function hexToAscii(hex) {
let ascii = '';
for (let i = 0; i < hex.length; i += 2) {
ascii += String.fromCharCode(parseInt(hex.substr(i, 2), 16));
}
return ascii;
}
// Usage examples
async function demonstrateSilentTimeOperations(partialUid) {
try {
// Basic silent time setting
console.log('=== Silent Time Setting ===');
const testTime = new Date();
const silentResult = await setDeviceTimeSilently(partialUid, testTime);
console.log('Silent time setting result:', silentResult);
// Automated time synchronization
console.log('\n=== Automated Time Sync ===');
const syncResult = await silentTimeSync(partialUid, 'system');
console.log('Silent sync result:', syncResult);
// Method comparison
console.log('\n=== Method Comparison ===');
const comparison = await compareTimeSettingMethods(partialUid, new Date());
console.log('Comparison result:', comparison);
// Start scheduled sync (run for 5 minutes then stop)
console.log('\n=== Scheduled Silent Sync ===');
const scheduledSync = await scheduleSilentTimeSync(partialUid, 1, 'system'); // Every minute
// Stop after 5 minutes
setTimeout(() => {
clearInterval(scheduledSync);
console.log('Scheduled silent sync stopped');
}, 5 * 60 * 1000);
} catch (error) {
console.error('Silent time operations demonstration failed:', error);
}
}0x000C - Set Device Ping Period
Sets the ping period for the device communication heartbeat. The ping period determines how frequently the device sends keepalive signals to maintain connection status.
Request/Response format
Request
[HEADER][0x000C][sssss] → 070100000000000000C00[sssss]Where:
sssss= Ping period value (seconds divided by 10)- Range: 1-65535 (representing 10 to 655350 seconds)
- Format: Decimal number as ASCII string
Examples:
- Set 30 second ping period:
070100000000000000C00330(sssss = "3") - Set 60 second ping period:
070100000000000000C00360(sssss = "6") - Set 300 second ping period:
070100000000000000C003330(sssss = "30")
Response Format
System State Response:
[HEADER][0x0002][SystemState]Ping Period Confirmation:
[HEADER][0x000D][PP:sssss]Error Response:
[HEADER][0x000C][NACK]
[HEADER][0x000C][FORMAT]
[HEADER][0x000C][ERROR]Note: The ping period confirmation comes through a 0x000D Status response where PP:sssss shows the current setting (multiply by 10 to get actual seconds).
Command Parameters
| Parameter | Format | Description |
|---|---|---|
sssss | ASCII decimal | Ping period value (seconds ÷ 10) |
Ping Period Guidelines
- 10-30 seconds (send 1-3): Real-time monitoring, frequent updates
- 30-60 seconds (send 3-6): Standard operation (recommended)
- 60-300 seconds (send 6-30): Battery conservation, reduced traffic
- 300+ seconds (send 30+): Minimal monitoring, extended battery life
Error Conditions
NACK- Invalid ping period value or device doesn't support settingFORMAT- Invalid data format (non-numeric or out of range)ERROR- Device communication error during configuration
Usage Example
javascript
async function setPingPeriod(partialUid, seconds) {
// Convert seconds to ping value (seconds ÷ 10)
const pingValue = Math.round(seconds / 10);
// Validate range
if (pingValue < 1 || pingValue > 65535) {
throw new Error(`Ping period out of range: ${seconds}s (value: ${pingValue})`);
}
// Build command
const header = "070100000000000000";
const command = "C00";
const data = pingValue.toString();
const dataHex = asciiToHex(data);
const fullCommand = header + command + dataHex;
try {
const result = await sendDeviceCommand(partialUid, fullCommand, true);
if (result.data && result.data.length > 0) {
const responses = result.data.map(data => {
const responseText = hexToAscii(data);
return parsePingResponse(responseText);
});
console.log(`Ping period set to ${seconds} seconds`);
return responses;
}
throw new Error('No response received');
} catch (error) {
console.error(`Failed to set ping period:`, error);
throw error;
}
}
function parsePingResponse(responseText) {
if (responseText.startsWith('PP:')) {
const pingValue = parseInt(responseText.substring(3));
return {
type: 'ping_period',
pingValue: pingValue,
seconds: pingValue * 10
};
}
if (['NACK', 'FORMAT', 'ERROR'].includes(responseText)) {
return {
type: 'error',
error: responseText
};
}
return { type: 'unknown', data: responseText };
}
// Usage examples
async function demonstratePingControl(partialUid) {
try {
// Set standard 30-second ping
await setPingPeriod(partialUid, 30);
// Set fast 10-second ping for monitoring
await setPingPeriod(partialUid, 10);
// Set slow 5-minute ping for battery saving
await setPingPeriod(partialUid, 300);
} catch (error) {
console.error('Ping period demonstration failed:', error);
}
}0x000D - Status
Retrieves comprehensive system status including zones, outputs, areas, signal strength, troubles, and sensors. This command provides a complete overview of the device state and is essential for monitoring and troubleshooting.
Request/Response format
Request
[HEADER][0x000D] → 070100000000000000D00Response Format
[HEADER][0x000D][DATA ASCII]Response Data Fields:
- Z:xxxxxx - Zone statuses (4 bits per zone)
- O:xxxxx - Output statuses (4 bits per output)
- O2:X#SSSS.X#SSSS... - Extended output parameters (dot-separated)
- SYS:AAAAAA - Area statuses (2 hex digits per area)
- RSSI:L - GSM signal level
- TRB:TTTTT - Trouble statuses (4 bits per trouble)
- SEN:Svvvvvvvv... - Sensor data (status + 8-byte value per sensor)
- PP:P - Ping period setting (optional)
- TM:YYYY/MM/DD HH-mm-ss - Device time (optional)
Response Data Structure
| Field | Format | Description |
|---|---|---|
Z: | Hex string | Zone statuses (4 bits per zone) |
O: | Hex string | Output statuses (4 bits per output) |
O2: | Dot-separated | Extended output parameters |
SYS: | Hex pairs | Area statuses (2 hex digits per area) |
RSSI: | Decimal | GSM signal strength level |
TRB: | Hex string | Trouble statuses (4 bits per trouble) |
SEN: | Hex string | Sensor statuses and values |
PP: | Decimal | Ping period setting (when available) |
TM: | DateTime | Current device time (when available) |
Zone Status Decoding
Each zone uses 4 bits encoded as one hex digit:
| Hex | Binary | Enabled | Alarm | Tamper | Bypass | Description |
|---|---|---|---|---|---|---|
0 | 0000 | No | No | No | No | Disabled, Normal |
1 | 0001 | Yes | No | No | No | Enabled, Normal |
2 | 0010 | No | Yes | No | No | Disabled, Alarm |
3 | 0011 | Yes | Yes | No | No | Enabled, Alarm |
4 | 0100 | No | No | Yes | No | Disabled, Tamper |
5 | 0101 | Yes | No | Yes | No | Enabled, Tamper |
8 | 1000 | No | No | No | Yes | Disabled, Bypassed |
9 | 1001 | Yes | No | No | Yes | Enabled, Bypassed |
Output Status Decoding
Each output uses 4 bits encoded as one hex digit:
| Hex | Binary | Remote | State | Fire Reset | Description |
|---|---|---|---|---|---|
0 | 0000 | No | OFF | No | No remote, OFF |
1 | 0001 | Yes | OFF | No | Remote enabled, OFF |
2 | 0010 | No | ON | No | No remote, ON |
3 | 0011 | Yes | ON | No | Remote enabled, ON |
4 | 0100 | No | OFF | Yes | No remote, OFF, Fire reset |
5 | 0101 | Yes | OFF | Yes | Remote enabled, OFF, Fire reset |
Area State Descriptions
| Code | State | Description |
|---|---|---|
00-03 | ARM States | Armed or arming process |
04-08 | DISARM States | Disarmed or disarming process |
09-0C | STAY States | Stay mode (partial arm) |
0D-10 | SLEEP States | Sleep mode |
11 | Cancel | Operation cancelled |
12 | Panic | Panic button area |
13 | FC | Fire communicator area/loop |
FF | N/A | Area disabled |
Trouble Status Decoding
Each trouble uses 4 bits encoded as one hex digit (index starts from 1):
| Hex | Binary | Enabled | Fault | Description |
|---|---|---|---|---|
0 | 0000 | No | No | Disabled, OK |
1 | 0001 | Yes | No | Enabled, OK |
2 | 0010 | No | Yes | Disabled, Fault |
3 | 0011 | Yes | Yes | Enabled, Fault |
4-F | xxxx | - | - | Reserved combinations |
Trouble Type Descriptions
| Index | Description |
|---|---|
1 | AC lost |
2 | No or Low battery |
3 | Wireless transmitter battery low |
4 | Power failure |
5 | Bell disconnected |
6 | Bell current failure |
7 | Auxiliary current failure |
8 | Communication failure |
9 | Timer loss |
10 | Tamper/Zone wiring failure |
11 | Telephone line monitoring failure |
12 | Fire zone trouble |
13 | Module loss |
14 | Wireless transmitter supervision lost |
15 | Keypad fault |
RSSI Signal Strength Decoding
| Value | dBm Range | Quality |
|---|---|---|
0 | ≤ -115 dBm | No signal |
1 | -111 dBm | Very poor |
2-10 | -110 to -92 dBm | Poor |
11-20 | -90 to -72 dBm | Fair |
21-30 | -70 to -54 dBm | Good |
31 | ≥ -52 dBm | Excellent |
99 | Unknown | Not detectable |
Sensor Status Decoding
Each sensor uses 1 hex digit for status + 8 hex bytes for value:
| Hex | Binary | Enabled | Online | High Alarm | Low Alarm | Description |
|---|---|---|---|---|---|---|
0 | 0000 | No | No | No | No | Disabled, Offline |
1 | 0001 | Yes | No | No | No | Enabled, Offline |
2 | 0010 | No | Yes | No | No | Disabled, Online |
3 | 0011 | Yes | Yes | No | No | Enabled, Online, Normal |
7 | 0111 | Yes | Yes | Yes | No | Enabled, Online, High Alarm |
B | 1011 | Yes | Yes | No | Yes | Enabled, Online, Low Alarm |
F | 1111 | Yes | Yes | Yes | Yes | Enabled, Online, Both Alarms |
Usage Example
javascript
async function getSystemStatus(partialUid) {
// Build status request command
const header = "070100000000000000";
const command = "D00";
const fullCommand = header + command;
try {
const result = await sendDeviceCommand(partialUid, fullCommand, true);
if (result.data && result.data.length > 0) {
const responseText = hexToAscii(result.data[0]);
const status = parseSystemStatus(responseText);
console.log('System Status Retrieved:', status);
return status;
}
throw new Error('No status data received');
} catch (error) {
console.error('Failed to get system status:', error);
throw error;
}
}
function parseSystemStatus(statusText) {
const status = {
zones: [],
outputs: [],
extendedOutputs: [],
areas: [],
rssi: null,
troubles: [],
sensors: [],
pingPeriod: null,
deviceTime: null,
raw: statusText
};
// Parse each field
const fields = statusText.split(/(?=[A-Z]+:)/);
fields.forEach(field => {
if (field.startsWith('Z:')) {
status.zones = parseZoneStatus(field.substring(2));
} else if (field.startsWith('O2:')) {
status.extendedOutputs = parseExtendedOutputs(field.substring(3));
} else if (field.startsWith('O:')) {
status.outputs = parseOutputStatus(field.substring(2));
} else if (field.startsWith('SYS:')) {
status.areas = parseAreaStatus(field.substring(4));
} else if (field.startsWith('RSSI:')) {
status.rssi = parseRSSI(field.substring(5));
} else if (field.startsWith('TRB:')) {
status.troubles = parseTroubleStatus(field.substring(4));
} else if (field.startsWith('SEN:')) {
status.sensors = parseSensorStatus(field.substring(4));
} else if (field.startsWith('PP:')) {
status.pingPeriod = parseInt(field.substring(3)) * 10; // Convert to seconds
} else if (field.startsWith('TM:')) {
status.deviceTime = field.substring(3);
}
});
return status;
}
function parseZoneStatus(zoneData) {
const zones = [];
for (let i = 0; i < zoneData.length; i++) {
const hexDigit = parseInt(zoneData[i], 16);
zones.push({
zone: i + 1,
enabled: !!(hexDigit & 0x01),
alarm: !!(hexDigit & 0x02),
tamper: !!(hexDigit & 0x04),
bypassed: !!(hexDigit & 0x08),
raw: zoneData[i]
});
}
return zones;
}
function parseOutputStatus(outputData) {
const outputs = [];
for (let i = 0; i < outputData.length; i++) {
const hexDigit = parseInt(outputData[i], 16);
outputs.push({
output: i + 1,
remoteEnabled: !!(hexDigit & 0x01),
state: !!(hexDigit & 0x02),
fireReset: !!(hexDigit & 0x04),
raw: outputData[i]
});
}
return outputs;
}
function parseExtendedOutputs(extData) {
return extData.split('.').map((item, index) => {
if (item.length >= 6) {
return {
output: index + 1,
type: item[0], // I, M, O, W
current: parseInt(item[1]), // 0-3
schedule: item.substring(2, 6),
raw: item
};
}
return { output: index + 1, raw: item };
});
}
function parseAreaStatus(areaData) {
const areas = [];
for (let i = 0; i < areaData.length; i += 2) {
if (i + 1 < areaData.length) {
const areaCode = areaData.substring(i, i + 2);
areas.push({
area: (i / 2) + 1,
state: areaCode,
description: getAreaStateDescription(areaCode),
raw: areaCode
});
}
}
return areas;
}
function parseRSSI(rssiValue) {
const rssi = parseInt(rssiValue);
let dbm;
if (rssi === 0) dbm = '<= -115';
else if (rssi === 1) dbm = '-111';
else if (rssi >= 2 && rssi <= 30) dbm = (-110 + (rssi - 2) * 2).toString();
else if (rssi === 31) dbm = '>= -52';
else if (rssi === 99) dbm = 'Unknown';
else dbm = 'Invalid';
return {
level: rssi,
dbm: dbm,
quality: rssi <= 10 ? 'Poor' : rssi <= 20 ? 'Fair' : rssi <= 30 ? 'Good' : 'Excellent'
};
}
function parseTroubleStatus(troubleData) {
const troubles = [];
for (let i = 0; i < troubleData.length; i++) {
const hexDigit = parseInt(troubleData[i], 16);
troubles.push({
trouble: i + 1,
enabled: !!(hexDigit & 0x01),
fault: !!(hexDigit & 0x02),
raw: troubleData[i]
});
}
return troubles;
}
function parseSensorStatus(sensorData) {
const sensors = [];
let i = 0;
while (i < sensorData.length) {
if (i + 8 < sensorData.length) {
const status = parseInt(sensorData[i], 16);
const valueHex = sensorData.substring(i + 1, i + 9);
sensors.push({
sensor: Math.floor(i / 9) + 1,
enabled: !!(status & 0x01),
online: !!(status & 0x02),
highAlarm: !!(status & 0x04),
lowAlarm: !!(status & 0x08),
valueHex: valueHex,
value: parseFloat32FromHex(valueHex),
raw: sensorData.substring(i, i + 9)
});
i += 9;
} else {
break;
}
}
return sensors;
}
function parseFloat32FromHex(hexValue) {
// Convert hex string to float32 with byte swap
try {
const bytes = [];
for (let i = 0; i < hexValue.length; i += 2) {
bytes.push(parseInt(hexValue.substr(i, 2), 16));
}
// Byte swap for little endian
const swapped = [bytes[3], bytes[2], bytes[1], bytes[0]];
const buffer = new ArrayBuffer(4);
const view = new DataView(buffer);
swapped.forEach((byte, index) => {
view.setUint8(index, byte);
});
return view.getFloat32(0, false); // big endian
} catch (error) {
return null;
}
}
function getAreaStateDescription(stateCode) {
const states = {
'00': 'Armed',
'01': 'Arming Starting',
'02': 'Arming Waiting',
'03': 'Prepare ARM',
'04': 'Disarmed',
'05': 'Disarming Waiting',
'06': 'Prepare Disarm',
'07': 'Prepare Remote Disarm',
'08': 'Remote Disarm Waiting',
'09': 'Stay Delay',
'0A': 'Stay Delay Starting',
'0B': 'Stay Delay Waiting',
'0C': 'Stay Delay Waiting Prepare',
'0D': 'Sleep',
'0E': 'Sleep Starting',
'0F': 'Sleep Waiting Prepare',
'10': 'Sleep Waiting',
'11': 'Cancel',
'12': 'Panic Button Area',
'13': 'Fire Control Area',
'FF': 'Disabled/N/A'
};
return states[stateCode.toUpperCase()] || `Unknown (${stateCode})`;
}
// Usage examples
async function demonstrateStatusMonitoring(partialUid) {
try {
console.log('=== System Status Monitoring ===');
// Get complete system status
const status = await getSystemStatus(partialUid);
// Display zone summary
console.log('\n--- Zone Status ---');
status.zones.forEach(zone => {
if (zone.enabled) {
const alerts = [];
if (zone.alarm) alerts.push('ALARM');
if (zone.tamper) alerts.push('TAMPER');
if (zone.bypassed) alerts.push('BYPASSED');
console.log(`Zone ${zone.zone}: ${alerts.length ? alerts.join(', ') : 'Normal'}`);
}
});
// Display area status
console.log('\n--- Area Status ---');
status.areas.forEach(area => {
if (area.state !== 'FF') {
console.log(`Area ${area.area}: ${area.description}`);
}
});
// Display signal strength
if (status.rssi) {
console.log(`\n--- Signal Strength ---`);
console.log(`RSSI: ${status.rssi.level} (${status.rssi.dbm} dBm) - ${status.rssi.quality}`);
}
// Display active troubles
console.log('\n--- Troubles ---');
const activeTroubles = status.troubles.filter(t => t.enabled && t.fault);
if (activeTroubles.length > 0) {
activeTroubles.forEach(trouble => {
console.log(`Trouble ${trouble.trouble}: FAULT`);
});
} else {
console.log('No active troubles');
}
return status;
} catch (error) {
console.error('Status monitoring demonstration failed:', error);
}
}0x000E - Inputs State of Area
Retrieves the input (zone) status for a specific area or all areas. This command provides zone status information similar to the Z: field in 0x000D Status but filtered by area.
Request/Response format
Request
[HEADER][0x000E][SYS:N] → 070100000000000000E00[SYS:N]
[HEADER][0x000E] → 070100000000000000E00Where:
SYS:N= Area number (N = decimal area number)- If
SYS:Nis omitted, returns input state of all areas
Examples:
- Get inputs for area 1:
070100000000000000E005359533A31(SYS:1) - Get inputs for area 2:
070100000000000000E005359533A32(SYS:2) - Get inputs for all areas:
070100000000000000E00
Response Format
[HEADER][0x000E][Z:xxxxxx]Where:
Z:xxxxxx= Zone statuses using the same 4-bit encoding as 0x000D Status
Response Examples:
- Single area zones:
[HEADER][0x000E][Z:1100] - Multiple area zones:
[HEADER][0x000E][Z:11001001]
Command Parameters
| Parameter | Format | Description |
|---|---|---|
SYS:N | ASCII | Area number (optional, omit for all areas) |
N | Decimal | Area number (1-based indexing) |
Zone Status Encoding
Uses the same 4-bit encoding per zone as 0x000D Status:
- Bit 0:
1= Zone Enabled,0= Zone Disabled - Bit 1:
1= Zone Alarm,0= Zone Restore - Bit 2:
1= Zone Tamper/Wire Fault,0= Restore OK - Bit 3:
1= Zone Bypassed,0= Bypass OFF
For complete zone status decoding, see the Zone Status Decoding table in the 0x000D command documentation.
Error Handling
Common Error Responses:
NACK- Invalid area number or area doesn't existFORMAT- Invalid SYS:N formatERROR- Communication error during retrieval
Usage Example
javascript
async function getAreaInputsState(partialUid, areaNumber = null) {
// Build inputs state request command
const header = "070100000000000000";
const command = "E00";
let fullCommand;
if (areaNumber !== null) {
// Request specific area
const areaParam = `SYS:${areaNumber}`;
const areaHex = asciiToHex(areaParam);
fullCommand = header + command + areaHex;
} else {
// Request all areas
fullCommand = header + command;
}
try {
const result = await sendDeviceCommand(partialUid, fullCommand, true);
if (result.data && result.data.length > 0) {
const responseText = hexToAscii(result.data[0]);
const inputsState = parseInputsState(responseText);
console.log(`Inputs state retrieved${areaNumber ? ` for area ${areaNumber}` : ' for all areas'}:`, inputsState);
return inputsState;
}
throw new Error('No inputs state data received');
} catch (error) {
console.error(`Failed to get inputs state${areaNumber ? ` for area ${areaNumber}` : ''}:`, error);
throw error;
}
}
function parseInputsState(responseText) {
if (!responseText.startsWith('Z:')) {
throw new Error(`Invalid inputs state response: ${responseText}`);
}
const zoneData = responseText.substring(2);
const zones = [];
for (let i = 0; i < zoneData.length; i++) {
const hexDigit = parseInt(zoneData[i], 16);
zones.push({
zone: i + 1,
enabled: !!(hexDigit & 0x01),
alarm: !!(hexDigit & 0x02),
tamper: !!(hexDigit & 0x04),
bypassed: !!(hexDigit & 0x08),
raw: zoneData[i],
status: getZoneStatusDescription(hexDigit)
});
}
return {
zones: zones,
totalZones: zones.length,
activeAlarms: zones.filter(z => z.alarm).length,
tamperFaults: zones.filter(z => z.tamper).length,
bypassedZones: zones.filter(z => z.bypassed).length,
raw: responseText
};
}
function getZoneStatusDescription(hexValue) {
const descriptions = {
0x0: 'Disabled, Normal',
0x1: 'Enabled, Normal',
0x2: 'Disabled, Alarm',
0x3: 'Enabled, Alarm',
0x4: 'Disabled, Tamper',
0x5: 'Enabled, Tamper',
0x8: 'Disabled, Bypassed',
0x9: 'Enabled, Bypassed',
0xA: 'Disabled, Alarm+Bypassed',
0xB: 'Enabled, Alarm+Bypassed',
0xC: 'Disabled, Tamper+Bypassed',
0xD: 'Enabled, Tamper+Bypassed'
};
return descriptions[hexValue] || `Unknown status: 0x${hexValue.toString(16)}`;
}
// Convenience functions for common operations
async function getSpecificAreaInputs(partialUid, areaNumber) {
return await getAreaInputsState(partialUid, areaNumber);
}
async function getAllAreasInputs(partialUid) {
return await getAreaInputsState(partialUid, null);
}
async function checkAreaAlarms(partialUid, areaNumber) {
try {
const inputsState = await getAreaInputsState(partialUid, areaNumber);
const alarmZones = inputsState.zones.filter(zone => zone.alarm && zone.enabled);
return {
hasAlarms: alarmZones.length > 0,
alarmCount: alarmZones.length,
alarmZones: alarmZones.map(zone => ({
zone: zone.zone,
status: zone.status
}))
};
} catch (error) {
console.error(`Failed to check area ${areaNumber} alarms:`, error);
throw error;
}
}
async function checkAreaTampers(partialUid, areaNumber) {
try {
const inputsState = await getAreaInputsState(partialUid, areaNumber);
const tamperZones = inputsState.zones.filter(zone => zone.tamper && zone.enabled);
return {
hasTampers: tamperZones.length > 0,
tamperCount: tamperZones.length,
tamperZones: tamperZones.map(zone => ({
zone: zone.zone,
status: zone.status
}))
};
} catch (error) {
console.error(`Failed to check area ${areaNumber} tampers:`, error);
throw error;
}
}
// Monitor specific area for changes
async function monitorAreaInputs(partialUid, areaNumber, intervalSeconds = 5) {
let previousState = null;
const monitor = setInterval(async () => {
try {
const currentState = await getAreaInputsState(partialUid, areaNumber);
if (previousState) {
// Check for changes
const changes = detectZoneChanges(previousState.zones, currentState.zones);
if (changes.length > 0) {
console.log(`Area ${areaNumber} zone changes detected:`, changes);
}
}
previousState = currentState;
} catch (error) {
console.error(`Area ${areaNumber} monitoring error:`, error);
}
}, intervalSeconds * 1000);
return monitor; // Return interval ID for stopping
}
function detectZoneChanges(previousZones, currentZones) {
const changes = [];
for (let i = 0; i < Math.max(previousZones.length, currentZones.length); i++) {
const prev = previousZones[i];
const curr = currentZones[i];
if (!prev && curr) {
changes.push({ zone: curr.zone, change: 'added', current: curr.status });
} else if (prev && !curr) {
changes.push({ zone: prev.zone, change: 'removed', previous: prev.status });
} else if (prev && curr && prev.raw !== curr.raw) {
changes.push({
zone: curr.zone,
change: 'modified',
previous: prev.status,
current: curr.status
});
}
}
return changes;
}
// Usage examples
async function demonstrateInputsMonitoring(partialUid) {
try {
console.log('=== Area Inputs Monitoring Demonstration ===');
// Get inputs for specific area
console.log('\n--- Area 1 Inputs ---');
const area1Inputs = await getSpecificAreaInputs(partialUid, 1);
console.log(`Area 1: ${area1Inputs.totalZones} zones, ${area1Inputs.activeAlarms} alarms`);
// Check for alarms in area 1
const area1Alarms = await checkAreaAlarms(partialUid, 1);
if (area1Alarms.hasAlarms) {
console.log('Active alarms in area 1:', area1Alarms.alarmZones);
}
// Get inputs for all areas
console.log('\n--- All Areas Inputs ---');
const allInputs = await getAllAreasInputs(partialUid);
console.log(`Total zones: ${allInputs.totalZones}`);
// Start monitoring area 2 for 30 seconds
console.log('\n--- Starting Area 2 Monitoring ---');
const monitorId = await monitorAreaInputs(partialUid, 2, 3);
setTimeout(() => {
clearInterval(monitorId);
console.log('Area 2 monitoring stopped');
}, 30000);
console.log('Inputs monitoring demonstration completed');
} catch (error) {
console.error('Inputs monitoring demonstration failed:', error);
}
}0x000F - Fault State
Retrieves fault state information for SP231 devices. This command returns device-specific fault information using a different fault classification system than the standard 0x000D Status command.
Request/Response format
Request
[HEADER][0x000F] → 070100000000000000F00Response Format
[HEADER][0x000F][TRB:GGIIIFFFFFFFFFF]Where:
GG= Fault Group (2 digits)III= Fault Index (3 digits)FFFFFFFFFF= Fault state data (variable length hex)
Response Examples:
- Single fault:
[HEADER][0x000F][TRB:10009](Group 10, Index 009) - Multiple faults:
[HEADER][0x000F][TRB:10009FFFFFFFFFF](Group 10, Index 009 + fault states)
Fault Group Classification
| Group | Category | Description |
|---|---|---|
10 | System | Core system faults (power, battery, hardware) |
20 | Communicator | Communication path faults |
30 | Zone Tamper | Zone tampering detection |
40 | Network COMBUS | COMBUS module troubles |
50 | Network MCI | MCI module troubles |
60 | Zone Fault | Zone wiring/sensor faults |
70 | Wireless Battery | Wireless zone battery issues |
80 | Wireless Supervision | Wireless zone supervision loss |
90 | Functionality | Firmware and operational faults |
System Faults (Group 10)
| Index | Description |
|---|---|
001 | AC Failure |
002 | Low Battery |
003 | Battery Missing/Dead |
004 | Aux Current Limit |
005 | Bell Current Limit |
006 | Bell Absent |
007 | Clock loss |
008 | CROW RF Receiver Jam Detected |
009 | CROW RF Receiver supervision loss |
Communicator Faults (Group 20)
| Index | Description |
|---|---|
001 | Primary (IP/SMS) |
002 | Backup1 (IP/SMS) |
003 | Backup2 (SMS) |
004 | MCI Backup (Ethernet/GSM/RF) Parallel Channel |
005 | GSM Registration, Network, SIM Card |
006 | No SIM card |
007 | Incorrect SIM card PIN code |
008 | Programming problem (No APN) |
009 | Registration to GSM network problem |
010 | Registration to GPRS/UMTS network problem |
011 | No connection with the receiver (deprecated) |
Zone-Related Faults (Groups 30, 60, 70, 80)
| Group | Index Format | Description |
|---|---|---|
30 | zzz | Zone Tamper - Zones 1-32 (zzz = zone number) |
60 | zzz | Zone Fault/Anti-Masking/Fire Loop Trouble - Zones 1-32 |
70 | zzz | Wireless Zone Low Battery - Zones 1-32 |
80 | zzz | Wireless Zone Supervision Loss - Zones 1-32 |
Module Faults (Groups 40, 50)
| Group | Index Format | Description |
|---|---|---|
40 | mmm | COMBUS Module Trouble (mmm = module number) |
50 | mmm | MCI Module Trouble (mmm = module number) |
Functionality Faults (Group 90)
| Index | Description |
|---|---|
001 | Memory fault |
002 | Firmware is corrupted |
003 | Lost connection with control panel |
004 | Incorrectly set type of operating mode |
005 | Critical error in structure of parameters |
006 | Error in list of users |
Usage Example
javascript
async function getFaultState(partialUid) {
// Build fault state request command (SP231 specific)
const header = "070100000000000000";
const command = "F00";
const fullCommand = header + command;
try {
const result = await sendDeviceCommand(partialUid, fullCommand, true);
if (result.data && result.data.length > 0) {
const responseText = hexToAscii(result.data[0]);
const faultState = parseFaultState(responseText);
console.log('SP231 Fault State Retrieved:', faultState);
return faultState;
}
throw new Error('No fault state data received');
} catch (error) {
console.error('Failed to get SP231 fault state:', error);
throw error;
}
}
function parseFaultState(responseText) {
if (!responseText.startsWith('TRB:')) {
throw new Error(`Invalid fault state response: ${responseText}`);
}
const faultData = responseText.substring(4);
// Parse group and index from first 5 characters
if (faultData.length < 5) {
throw new Error('Fault data too short');
}
const group = parseInt(faultData.substring(0, 2));
const index = parseInt(faultData.substring(2, 5));
const stateData = faultData.length > 5 ? faultData.substring(5) : null;
const fault = {
group: group,
index: index,
groupName: getFaultGroupName(group),
description: getFaultDescription(group, index),
stateData: stateData,
raw: responseText
};
return fault;
}
function getFaultGroupName(group) {
const groups = {
10: 'System',
20: 'Communicator',
30: 'Zone Tamper',
40: 'Network COMBUS',
50: 'Network MCI',
60: 'Zone Fault',
70: 'Wireless Battery',
80: 'Wireless Supervision',
90: 'Functionality'
};
return groups[group] || `Unknown Group ${group}`;
}
function getFaultDescription(group, index) {
// System faults (Group 10)
if (group === 10) {
const systemFaults = {
1: 'AC Failure',
2: 'Low Battery',
3: 'Battery Missing/Dead',
4: 'Aux Current Limit',
5: 'Bell Current Limit',
6: 'Bell Absent',
7: 'Clock loss',
8: 'CROW RF Receiver Jam Detected',
9: 'CROW RF Receiver supervision loss'
};
return systemFaults[index] || `Unknown System Fault ${index}`;
}
// Communicator faults (Group 20)
if (group === 20) {
const commFaults = {
1: 'Primary (IP/SMS)',
2: 'Backup1 (IP/SMS)',
3: 'Backup2 (SMS)',
4: 'MCI Backup (Ethernet/GSM/RF) Parallel Channel',
5: 'GSM Registration, Network, SIM Card',
6: 'No SIM card',
7: 'Incorrect SIM card PIN code',
8: 'Programming problem (No APN)',
9: 'Registration to GSM network problem',
10: 'Registration to GPRS/UMTS network problem',
11: 'No connection with the receiver (deprecated)'
};
return commFaults[index] || `Unknown Communicator Fault ${index}`;
}
// Zone-related faults
if (group === 30) {
return `Zone ${index} Tamper`;
}
if (group === 60) {
return `Zone ${index} Fault/Anti-Masking/Fire Loop Trouble`;
}
if (group === 70) {
return `Wireless Zone ${index} Low Battery`;
}
if (group === 80) {
return `Wireless Zone ${index} Supervision Loss`;
}
// Module faults
if (group === 40) {
return `COMBUS Module ${index} Trouble`;
}
if (group === 50) {
return `MCI Module ${index} Trouble`;
}
// Functionality faults (Group 90)
if (group === 90) {
const funcFaults = {
1: 'Memory fault',
2: 'Firmware is corrupted',
3: 'Lost connection with control panel',
4: 'Incorrectly set type of operating mode',
5: 'Critical error in structure of parameters',
6: 'Error in list of users'
};
return funcFaults[index] || `Unknown Functionality Fault ${index}`;
}
return `Unknown fault: Group ${group}, Index ${index}`;
}
// Convenience functions for specific fault types
async function checkSystemFaults(partialUid) {
try {
const fault = await getFaultState(partialUid);
return fault.group === 10 ? fault : null;
} catch (error) {
console.error('Failed to check system faults:', error);
return null;
}
}
async function checkCommunicatorFaults(partialUid) {
try {
const fault = await getFaultState(partialUid);
return fault.group === 20 ? fault : null;
} catch (error) {
console.error('Failed to check communicator faults:', error);
return null;
}
}
async function checkZoneFaults(partialUid) {
try {
const fault = await getFaultState(partialUid);
return [30, 60, 70, 80].includes(fault.group) ? fault : null;
} catch (error) {
console.error('Failed to check zone faults:', error);
return null;
}
}
// Monitor faults periodically
async function monitorSP231Faults(partialUid, intervalSeconds = 30) {
let previousFault = null;
const monitor = setInterval(async () => {
try {
const currentFault = await getFaultState(partialUid);
if (!previousFault ||
previousFault.group !== currentFault.group ||
previousFault.index !== currentFault.index) {
console.log('SP231 Fault State Changed:', {
group: currentFault.group,
groupName: currentFault.groupName,
index: currentFault.index,
description: currentFault.description
});
}
previousFault = currentFault;
} catch (error) {
console.error('SP231 fault monitoring error:', error);
}
}, intervalSeconds * 1000);
return monitor;
}
// Usage examples
async function demonstrateSP231FaultMonitoring(partialUid) {
try {
console.log('=== SP231 Fault State Monitoring ===');
// Get current fault state
const faultState = await getFaultState(partialUid);
console.log('Current fault state:', faultState);
// Check specific fault types
console.log('\n--- System Faults Check ---');
const systemFault = await checkSystemFaults(partialUid);
if (systemFault) {
console.log('System fault detected:', systemFault.description);
} else {
console.log('No system faults');
}
console.log('\n--- Communicator Faults Check ---');
const commFault = await checkCommunicatorFaults(partialUid);
if (commFault) {
console.log('Communicator fault detected:', commFault.description);
} else {
console.log('No communicator faults');
}
console.log('\n--- Zone Faults Check ---');
const zoneFault = await checkZoneFaults(partialUid);
if (zoneFault) {
console.log('Zone fault detected:', zoneFault.description);
} else {
console.log('No zone faults');
}
// Start fault monitoring
console.log('\n--- Starting Fault Monitoring (60 seconds) ---');
const monitorId = await monitorSP231Faults(partialUid, 10);
setTimeout(() => {
clearInterval(monitorId);
console.log('SP231 fault monitoring stopped');
}, 60000);
console.log('SP231 fault monitoring demonstration completed');
} catch (error) {
console.error('SP231 fault monitoring demonstration failed:', error);
}
}0x0010 - Fire Sensors Reset
Resets all fire sensors on the device. This command clears fire sensor alarm states and restores sensors to their normal monitoring state.
Request/Response format
Request
[HEADER][0x0010] → 0701000000000000001000Response Format
The response format varies depending on the device. Three possible acknowledgment responses:
Status Response:
[HEADER][0x000D][Status Data]System State Response:
[HEADER][0x0002][SystemState]Command Acknowledgment:
[HEADER][0x0010][ACK]Note: The specific response type depends on the device model and firmware version. Some devices may send multiple response types.
Command Operation
| Operation | Description |
|---|---|
| Fire Sensor Reset | Clears alarm states from all fire detection sensors |
| State Restoration | Returns fire sensors to normal monitoring mode |
| System Update | Updates device status to reflect reset operation |
Safety Considerations
Important Safety Notes:
- Only reset fire sensors after confirming no actual fire condition exists
- Verify fire condition has been resolved before issuing reset command
- Ensure proper fire department notification procedures have been followed
- Reset should only be performed by authorized personnel
Response Handling
Multiple Response Types:
- Different devices may respond with different message types
- Applications should handle all three possible response formats
- Response indicates successful fire sensor reset operation
Status Verification:
- Use 0x000D Status command to verify sensor states after reset
- Check fire sensor zones for proper restoration to normal state
- Confirm fire loop integrity after reset operation
Usage Example
javascript
async function resetFireSensors(partialUid) {
// Build fire sensors reset command
const header = "070100000000000000";
const command = "1000"; // Command 0x0010 in MSB format
const fullCommand = header + command;
try {
console.log('Resetting all fire sensors...');
const result = await sendDeviceCommand(partialUid, fullCommand, true);
if (result.data && result.data.length > 0) {
const responses = result.data.map(data => {
const responseText = hexToAscii(data);
return parseFireResetResponse(responseText);
});
console.log('Fire sensors reset completed');
console.log('Reset responses:', responses);
// Verify reset by checking status
await verifyFireReset(partialUid);
return responses;
}
throw new Error('No response received for fire sensor reset');
} catch (error) {
console.error('Failed to reset fire sensors:', error);
throw error;
}
}
function parseFireResetResponse(responseText) {
// Handle different response types
if (responseText === 'ACK') {
return {
type: 'acknowledgment',
success: true,
message: 'Fire sensor reset acknowledged'
};
}
if (responseText.startsWith('Z:') || responseText.startsWith('O:') || responseText.startsWith('SYS:')) {
return {
type: 'status_data',
success: true,
statusData: responseText,
message: 'Status data received after fire reset'
};
}
if (responseText.match(/^[0-9A-F,]+$/)) {
return {
type: 'system_state',
success: true,
systemState: responseText,
message: 'System state updated after fire reset'
};
}
return {
type: 'unknown',
success: false,
data: responseText,
message: 'Unknown response format'
};
}
async function verifyFireReset(partialUid) {
try {
console.log('Verifying fire sensor reset...');
// Wait a moment for system to update
await new Promise(resolve => setTimeout(resolve, 2000));
// Get current system status to verify reset
const status = await getSystemStatus(partialUid);
// Check fire-related zones
const fireZones = status.zones.filter(zone => zone.enabled);
const activeFireAlarms = fireZones.filter(zone => zone.alarm);
if (activeFireAlarms.length === 0) {
console.log('Fire sensor reset verification: SUCCESS - No active fire alarms');
return {
success: true,
message: 'All fire sensors successfully reset',
fireZones: fireZones.length,
activeAlarms: 0
};
} else {
console.log('Fire sensor reset verification: WARNING - Some fire alarms still active');
return {
success: false,
message: 'Some fire alarms remain active after reset',
fireZones: fireZones.length,
activeAlarms: activeFireAlarms.length,
activeZones: activeFireAlarms.map(z => z.zone)
};
}
} catch (error) {
console.error('Failed to verify fire sensor reset:', error);
return {
success: false,
message: 'Unable to verify reset status',
error: error.message
};
}
}
// Safety-focused reset function with confirmation
async function safeFireSensorReset(partialUid, confirmationCallback) {
try {
// Safety confirmation
if (confirmationCallback) {
const confirmed = await confirmationCallback();
if (!confirmed) {
console.log('Fire sensor reset cancelled by user');
return { success: false, message: 'Reset cancelled - safety confirmation failed' };
}
}
// Get pre-reset status
console.log('Checking system status before fire sensor reset...');
const preResetStatus = await getSystemStatus(partialUid);
const preResetAlarms = preResetStatus.zones.filter(z => z.alarm && z.enabled);
console.log(`Found ${preResetAlarms.length} active fire alarms before reset`);
if (preResetAlarms.length === 0) {
console.log('No active fire alarms detected - reset may not be necessary');
}
// Perform reset
const resetResult = await resetFireSensors(partialUid);
// Additional verification delay for safety
console.log('Waiting for system stabilization...');
await new Promise(resolve => setTimeout(resolve, 5000));
// Final verification
const verification = await verifyFireReset(partialUid);
return {
success: resetResult.length > 0 && verification.success,
preResetAlarms: preResetAlarms.length,
postResetAlarms: verification.activeAlarms || 0,
resetResponses: resetResult,
verification: verification,
message: verification.message
};
} catch (error) {
console.error('Safe fire sensor reset failed:', error);
throw error;
}
}
// Monitor fire sensors after reset
async function monitorPostFireReset(partialUid, durationMinutes = 10) {
console.log(`Starting ${durationMinutes}-minute post-reset monitoring...`);
let monitorCount = 0;
const maxMonitors = durationMinutes * 2; // Check every 30 seconds
const monitor = setInterval(async () => {
try {
monitorCount++;
const status = await getSystemStatus(partialUid);
const fireAlarms = status.zones.filter(z => z.alarm && z.enabled);
if (fireAlarms.length > 0) {
console.log(`⚠️ Fire alarm detected during post-reset monitoring: ${fireAlarms.length} zones`);
fireAlarms.forEach(zone => {
console.log(` Zone ${zone.zone}: ${zone.status}`);
});
} else {
console.log(`✓ Post-reset monitor ${monitorCount}/${maxMonitors}: All fire sensors normal`);
}
if (monitorCount >= maxMonitors) {
clearInterval(monitor);
console.log('Post-fire-reset monitoring completed');
}
} catch (error) {
console.error('Post-reset monitoring error:', error);
}
}, 30000); // Check every 30 seconds
return monitor;
}
// Usage examples with safety considerations
async function demonstrateFireSensorReset(partialUid) {
try {
console.log('=== Fire Sensor Reset Demonstration ===');
console.log('⚠️ SAFETY WARNING: Only reset fire sensors after confirming no fire condition exists');
// Safety confirmation callback example
const safetyConfirmation = async () => {
console.log('Safety Checklist:');
console.log('1. Fire condition investigated and resolved? [Required]');
console.log('2. Fire department notified if required? [Required]');
console.log('3. Authorized personnel performing reset? [Required]');
// In real implementation, this would be interactive confirmation
// For demo purposes, we'll assume safety conditions are met
return true;
};
// Perform safe fire sensor reset
const resetResult = await safeFireSensorReset(partialUid, safetyConfirmation);
if (resetResult.success) {
console.log('✓ Fire sensor reset completed successfully');
console.log(`Pre-reset alarms: ${resetResult.preResetAlarms}`);
console.log(`Post-reset alarms: ${resetResult.postResetAlarms}`);
// Start post-reset monitoring
const monitorId = await monitorPostFireReset(partialUid, 5); // 5 minutes
// Stop monitoring after demonstration
setTimeout(() => {
clearInterval(monitorId);
console.log('Fire sensor reset demonstration completed');
}, 5 * 60 * 1000);
} else {
console.log('❌ Fire sensor reset failed or incomplete');
console.log('Reason:', resetResult.message);
}
} catch (error) {
console.error('Fire sensor reset demonstration failed:', error);
}
}0x0011 - Get Users
Retrieves the complete user list from GV17 and WP17 devices. Users are sent sequentially in multiple packets due to data size limitations. This command supports selective parameter requests to optimize data transfer.
Request/Response format
Request
[HEADER][0x0011][XXXXYY] → 0701000000000000001100024300Where:
XXXX= Requested user parameters (2-byte hex value)YY= Initial packet number (1-byte hex value, typically00to start from beginning)
Response Format
First Packet (Packet 0):
[HEADER][0x0011][00XXXXRRFAAA][ASCII users data]Subsequent Packets:
[HEADER][0x0011][YY][ASCII users data]Where:
00= Packet 0 identifierYY= Current packet number (01, 02, 03...)RR= Total number of packets (hex) - only in packet 0F= Internal language encoding (hex) - only in packet 0AAA= Maximum packet length (fixed to5B0= 1456 bytes) - only in packet 0
Parameter Request Bits (XXXX)
| Bit | Parameter | Description |
|---|---|---|
15 | Reserved | - |
14 | Reserved | - |
13 | Reserved | - |
12 | Reserved | - |
11 | PIN Code | User PIN code |
10 | Work Mode | Working status (1/0) |
9 | User email address | |
8 | Counter Current | Current counter value (hex up to FF) |
7 | Counter Set | Counter set value (hex up to FF) |
6 | Output Permissions | Output control permissions (hex bits) |
5 | Valid Till | Valid until timestamp |
4 | Valid From | Valid from timestamp |
3 | Scheduler ID | Assigned scheduler (0=disabled, 1-A) |
2 | Checkboxes | Enable flags (hex bits) |
1 | Phone/RFID | Phone number or RFID tag |
0 | Name | User name (LSB) |
Common Parameter Combinations
| Use Case | Bits | Hex Value | Description |
|---|---|---|---|
| Basic Info | 0,1 | 0x0003 → "000300" | Name and phone/RFID |
| Access Control | 0,1,6 | 0x0043 → "004300" | Name, phone, permissions |
| Full Profile | 0,1,6,9 | 0x0243 → "024300" | Name, phone, permissions, email |
| Time Restricted | 0,1,4,5,6 | 0x0073 → "007300" | Basic info with time validity |
| Complete Data | All bits | 0x0FFF → "0FFF00" | All available user parameters |
User Data Format
Data Structure:
UserID,Param1,Param2,Param3...|UserID,Param1,Param2,Param3...- Users separated by pipe (
|) symbol - Parameters separated by comma (
,) - User ID is always first parameter
- Parameters order matches bit order (LSB to MSB)
User ID Ranges
| Range | User Type | Description |
|---|---|---|
1-7 | Administrators | Full system access |
10 | Not Authorized | Special unauthorized user |
11-1010 | Regular Users | Standard user accounts |
Output Permissions Format
Hex value parsed in bits for output control:
- Bit 0: Output 1 control
- Bit 1: Output 2 control
- Bit 2: Output 3 control (relay)
Checkbox Flags Format
Single hex symbol with bit flags:
- Bit 0: Enable user
- Bit 1: Valid from enabled
- Bit 2: Valid till enabled
- Bit 3: Enable counter
Time Format
Time values in seconds as hex, calculated from March 1, 2016:
- 0 seconds = 2016-03-01 00:00:00
- Example:
0x5A4EC400= specific date/time after base
Packet Sequence Behavior
Important Notes:
- After sending packet 0 request, device automatically sends all packets
- All packets sent sequentially without further requests
- Maximum packet size: 1456 bytes (1448 data + 8 header)
Usage Example
javascript
async function getUsersList(partialUid, requestedParams = 0x0243, startPacket = 0) {
// Build users list request
const header = "070100000000000000";
const command = "1100";
// Convert parameters to 4-digit hex string
const paramsHex = requestedParams.toString(16).padStart(4, '0').toUpperCase();
const packetHex = startPacket.toString(16).padStart(2, '0').toUpperCase();
const requestData = paramsHex + packetHex;
const requestDataHex = asciiToHex(requestData);
const fullCommand = header + command + requestDataHex;
try {
console.log(`Requesting users list with parameters 0x${paramsHex}...`);
const result = await sendDeviceCommand(partialUid, fullCommand, true);
if (result.data && result.data.length > 0) {
// Parse all received packets
let firstPacketParams;
const packets = result.data.map((data, index) => {
const responseText = hexToAscii(data);
if (index === 0) {
const firstPacket = parseUsersPacket(responseText, index);
firstPacketParams = firstPacket.requestedParams;
return firstPacket;
} else {
return parseUsersPacket(responseText, index, firstPacketParams);
}
});
// Combine all user data from packets
const allUsers = combineUserPackets(packets);
console.log(`Users list retrieved: ${allUsers.length} users from ${packets.length} packets`);
return {
users: allUsers,
packets: packets,
requestedParams: requestedParams,
totalPackets: packets.length > 0 ? packets[0].totalPackets : 0
};
}
throw new Error('No users data received');
} catch (error) {
console.error('Failed to get users list:', error);
throw error;
}
}
function parseUsersPacket(responseText, packetIndex, firstPacketParams) {
let packetNum, requestedParams, totalPackets, language, maxLength;
let usersDataStart = 0;
if (packetIndex === 0) {
// First packet: 00XXXXRRFAAA
packetNum = parseInt(responseText.substring(0, 2), 16);
requestedParams = parseInt(responseText.substring(2, 6), 16);
totalPackets = parseInt(responseText.substring(6, 8), 16);
language = parseInt(responseText.substring(8, 9), 16);
maxLength = parseInt(responseText.substring(9, 12), 16);
usersDataStart = 12;
} else {
// Subsequent packets: PP
packetNum = parseInt(responseText.substring(0, 2), 16);
requestedParams = firstPacketParams; // Use params from first packet
usersDataStart = 2;
}
const usersData = responseText.substring(usersDataStart);
const users = parseUsersData(usersData, requestedParams);
return {
packetNumber: packetNum,
requestedParams: requestedParams,
totalPackets: totalPackets || null,
language: language || null,
maxLength: maxLength || null,
users: users,
rawData: usersData
};
}
function parseUsersData(usersData, requestedParams) {
if (!usersData || usersData.trim() === '') {
return [];
}
const userStrings = usersData.split('|');
const users = [];
userStrings.forEach(userString => {
if (userString.trim()) {
const user = parseUserString(userString, requestedParams);
if (user) {
users.push(user);
}
}
});
return users;
}
function parseUserString(userString, requestedParams) {
const parts = userString.split(',');
if (parts.length === 0) return null;
const user = {
id: parseInt(parts[0]),
type: getUserType(parseInt(parts[0]))
};
let paramIndex = 1;
// Parse parameters based on requested bits (LSB first)
for (let bit = 0; bit < 16; bit++) {
if (requestedParams & (1 << bit)) {
if (paramIndex < parts.length) {
const value = parts[paramIndex];
switch (bit) {
case 0: user.name = value; break;
case 1: user.phone = value; break;
case 2: user.checkboxes = parseCheckboxes(value); break;
case 3: user.schedulerId = parseInt(value, 16); break;
case 4: user.validFrom = parseTimeValue(value); break;
case 5: user.validTill = parseTimeValue(value); break;
case 6: user.outputPermissions = parseOutputPermissions(value); break;
case 7: user.counterSet = parseInt(value, 16); break;
case 8: user.counterCurrent = parseInt(value, 16); break;
case 9: user.email = value; break;
case 10: user.workMode = parseInt(value) === 1; break;
case 11: user.pinCode = value; break;
default: user[`param${bit}`] = value; break;
}
paramIndex++;
}
}
}
return user;
}
function getUserType(userId) {
if (userId >= 1 && userId <= 7) return 'Administrator';
if (userId === 10) return 'Not Authorized';
if (userId >= 11 && userId <= 1010) return 'Regular User';
return 'Unknown';
}
function parseCheckboxes(hexValue) {
const value = parseInt(hexValue, 16);
return {
enabled: !!(value & 0x01),
validFromEnabled: !!(value & 0x02),
validTillEnabled: !!(value & 0x04),
counterEnabled: !!(value & 0x08)
};
}
function parseOutputPermissions(hexValue) {
const value = parseInt(hexValue, 16);
return {
output1: !!(value & 0x01),
output2: !!(value & 0x02),
output3: !!(value & 0x04)
};
}
function parseTimeValue(hexValue) {
try {
const seconds = parseInt(hexValue, 16);
const baseDate = new Date('2016-03-01T00:00:00Z');
const resultDate = new Date(baseDate.getTime() + (seconds * 1000));
return {
timestamp: seconds,
date: resultDate.toISOString(),
readable: resultDate.toLocaleString()
};
} catch (error) {
return { timestamp: hexValue, error: 'Invalid time format' };
}
}
function combineUserPackets(packets) {
const allUsers = [];
packets.forEach(packet => {
if (packet.users) {
allUsers.push(...packet.users);
}
});
// Remove duplicates and sort by user ID
const uniqueUsers = allUsers.filter((user, index, array) =>
array.findIndex(u => u.id === user.id) === index
);
return uniqueUsers.sort((a, b) => a.id - b.id);
}
// Convenience functions for common requests
async function getBasicUsersList(partialUid) {
// Request name and phone/RFID only (bits 0,1 = 0x0003)
return await getUsersList(partialUid, 0x0003);
}
async function getFullUsersList(partialUid) {
// Request name, phone, permissions, email (bits 0,1,6,9 = 0x0243)
return await getUsersList(partialUid, 0x0243);
}
async function getAdministratorsList(partialUid) {
const allUsers = await getFullUsersList(partialUid);
return allUsers.users.filter(user => user.type === 'Administrator');
}
async function getActiveUsersList(partialUid) {
// Request full data including work mode and checkboxes
const allUsers = await getUsersList(partialUid, 0x0643); // Include bits 2,10
return allUsers.users.filter(user => user.workMode && user.checkboxes?.enabled);
}
// Usage examples
async function demonstrateUsersListRetrieval(partialUid) {
try {
console.log('=== Users List Retrieval Demonstration ===');
// Get basic users list
console.log('\n--- Basic Users List ---');
const basicUsers = await getBasicUsersList(partialUid);
console.log(`Retrieved ${basicUsers.users.length} users (basic info)`);
basicUsers.users.slice(0, 3).forEach(user => {
console.log(`User ${user.id} (${user.type}): ${user.name} - ${user.phone}`);
});
// Get full users list
console.log('\n--- Full Users List ---');
const fullUsers = await getFullUsersList(partialUid);
console.log(`Retrieved ${fullUsers.users.length} users (full info)`);
console.log(`Total packets received: ${fullUsers.totalPackets}`);
// Display administrators
const admins = await getAdministratorsList(partialUid);
console.log(`\nAdministrators: ${admins.length}`);
admins.forEach(admin => {
console.log(`Admin ${admin.id}: ${admin.name} - ${admin.email || 'No email'}`);
});
// Display active users
const activeUsers = await getActiveUsersList(partialUid);
console.log(`\nActive Users: ${activeUsers.users.length}`);
console.log('Users list retrieval demonstration completed');
} catch (error) {
console.error('Users list demonstration failed:', error);
}
}0x0012 - Write Users
Writes user data to GV17 and WP17 devices. This command allows adding, editing, or deleting users by sending user information in the same format as 0x0011 Get Users List.
Request/Response format
Request
[HEADER][0x0012][YYXXXX][Users data] → 0701000000000000001200[YYXXXX][Users data]Where:
YY= Current packet number (2-char hex string)XXXX= Sent data parameters (2-byte hex value as 4-char hex string, LSB format)[Users data]= User information in same format as 0x0011 command
Parameter Construction:
- Take 2-byte hex value for parameters (e.g., 0x0243)
- Convert to 4-character hex string ("0243")
- Prepend 2-character packet number ("00")
- Result: "000243" → ASCII hex encoded
Example Request:
javascript
// Write user with name, phone, permissions, email (0x0243)
// Packet 00, User ID 15: "John,+1234567890,5,john@example.com"
{
"command": "0x0012000000000000120036303032343315,John,+1234567890,5,john@example.com"
}Response Format
[HEADER][0x0012][OK] // Successful write
[HEADER][0x0012][ERROR] // Write failedUser Data Format
Uses identical format to 0x0011 Get Users List:
Single User:
UserID,Param1,Param2,Param3...Multiple Users:
UserID1,Param1,Param2|UserID2,Param1,Param2|UserID3,Param1,Param2User Deletion:
UserID,,, // Empty data with only commasParameter Bits (XXXX)
Uses the same parameter bit mapping as 0x0011 Get Users List:
| Bit | Parameter | Description |
|---|---|---|
11 | PIN Code | User PIN code |
10 | Work Mode | Working status (1/0) |
9 | User email address | |
8 | Counter Current | Current counter value (hex up to FF) |
7 | Counter Set | Counter set value (hex up to FF) |
6 | Output Permissions | Output control permissions (hex bits) |
5 | Valid Till | Valid until timestamp |
4 | Valid From | Valid from timestamp |
3 | Scheduler ID | Assigned scheduler (0=disabled, 1-A) |
2 | Checkboxes | Enable flags (hex bits) |
1 | Phone/RFID | Phone number or RFID tag |
0 | Name | User name (LSB) |
User Operations
| Operation | Data Format | Description |
|---|---|---|
| Add User | UserID,Param1,Param2... | Create new user with specified parameters |
| Edit User | UserID,Param1,Param2... | Update existing user parameters |
| Delete User | UserID,,, | Delete user (empty data with commas) |
| Multi-User | User1|User2|User3 | Multiple operations separated by pipes |
Response Handling
Success Response (OK):
- User data saved successfully
- User sequence numbers match device list
- All parameters validated correctly
Error Response (ERROR):
- User sequence number not found
- Parameter validation failed
- Data format incorrect
- Device storage error
User ID Validation
Must use valid user ID ranges from 0x0011 Get Users List:
- 1-7: Administrators
- 10: Not Authorized user
- 11-1010: Regular users
Data Validation Rules
Parameter Order: Must match requested bits order (LSB to MSB) Required Fields: User ID always required as first parameter Data Types: Each parameter must match expected format for its bit Permissions: Output permissions must be valid hex bit flags Time Values: Must use seconds from March 1, 2016 base
Usage Example
javascript
async function writeUsersList(partialUid, users, parameters = 0x0243, packetNumber = 0) {
// Build users write command
const header = "070100000000000000";
const command = "1200";
// Format packet header: YYXXXX
const packetHex = packetNumber.toString(16).padStart(2, '0').toUpperCase();
const paramsHex = parameters.toString(16).padStart(4, '0').toUpperCase();
const headerData = packetHex + paramsHex;
// Format users data
const usersData = formatUsersData(users, parameters);
const fullData = headerData + usersData;
const dataHex = asciiToHex(fullData);
const fullCommand = header + command + dataHex;
try {
console.log(`Writing ${users.length} users with parameters 0x${paramsHex}...`);
const result = await sendDeviceCommand(partialUid, fullCommand, true);
if (result.data && result.data.length > 0) {
const responseText = hexToAscii(result.data[0]);
if (responseText === 'OK') {
console.log('Users write successful');
return { success: true, message: 'Users saved successfully' };
} else if (responseText === 'ERROR') {
console.log('Users write failed');
return { success: false, message: 'Device returned ERROR' };
} else {
return { success: false, message: `Unexpected response: ${responseText}` };
}
}
throw new Error('No response received');
} catch (error) {
console.error('Failed to write users list:', error);
throw error;
}
}
function formatUsersData(users, parameters) {
return users.map(user => formatSingleUser(user, parameters)).join('|');
}
function formatSingleUser(user, parameters) {
const parts = [user.id.toString()];
// Add parameters based on requested bits (LSB first)
for (let bit = 0; bit < 16; bit++) {
if (parameters & (1 << bit)) {
let value = '';
switch (bit) {
case 0: value = user.name || ''; break;
case 1: value = user.phone || ''; break;
case 2: value = formatCheckboxes(user.checkboxes); break;
case 3: value = (user.schedulerId || 0).toString(16).toUpperCase(); break;
case 4: value = formatTimeValue(user.validFrom); break;
case 5: value = formatTimeValue(user.validTill); break;
case 6: value = formatOutputPermissions(user.outputPermissions); break;
case 7: value = (user.counterSet || 0).toString(16).padStart(2, '0').toUpperCase(); break;
case 8: value = (user.counterCurrent || 0).toString(16).padStart(2, '0').toUpperCase(); break;
case 9: value = user.email || ''; break;
case 10: value = user.workMode ? '1' : '0'; break;
case 11: value = user.pinCode || ''; break;
default: value = user[`param${bit}`] || ''; break;
}
parts.push(value);
}
}
return parts.join(',');
}
function formatCheckboxes(checkboxes) {
if (!checkboxes) return '0';
let value = 0;
if (checkboxes.enabled) value |= 0x01;
if (checkboxes.validFromEnabled) value |= 0x02;
if (checkboxes.validTillEnabled) value |= 0x04;
if (checkboxes.counterEnabled) value |= 0x08;
return value.toString(16).toUpperCase();
}
function formatOutputPermissions(permissions) {
if (!permissions) return '0';
let value = 0;
if (permissions.output1) value |= 0x01;
if (permissions.output2) value |= 0x02;
if (permissions.output3) value |= 0x04;
return value.toString(16).toUpperCase();
}
function formatTimeValue(timeData) {
if (!timeData) return '0';
if (typeof timeData === 'number') {
return timeData.toString(16).toUpperCase();
}
if (timeData.timestamp) {
return timeData.timestamp.toString(16).toUpperCase();
}
// If it's a Date object or date string, convert to timestamp
try {
const baseDate = new Date('2016-03-01T00:00:00Z');
const targetDate = new Date(timeData);
const seconds = Math.floor((targetDate.getTime() - baseDate.getTime()) / 1000);
return Math.max(0, seconds).toString(16).toUpperCase();
} catch (error) {
return '0';
}
}
// Convenience functions for common operations
async function addUser(partialUid, user, parameters = 0x0243) {
return await writeUsersList(partialUid, [user], parameters, 0);
}
async function editUser(partialUid, user, parameters = 0x0243) {
return await writeUsersList(partialUid, [user], parameters, 0);
}
async function deleteUser(partialUid, userId) {
// Create delete user with empty data (only commas)
const deleteUser = {
id: userId,
name: '',
phone: '',
email: '',
// All other fields empty
};
return await writeUsersList(partialUid, [deleteUser], 0x0243, 0);
}
async function addMultipleUsers(partialUid, users, parameters = 0x0243) {
// Split into packets if too many users
const maxUsersPerPacket = 10; // Adjust based on data size
const results = [];
for (let i = 0; i < users.length; i += maxUsersPerPacket) {
const userBatch = users.slice(i, i + maxUsersPerPacket);
const packetNumber = Math.floor(i / maxUsersPerPacket);
const result = await writeUsersList(partialUid, userBatch, parameters, packetNumber);
results.push(result);
if (!result.success) {
console.error(`Batch ${packetNumber + 1} failed:`, result.message);
break;
}
// Small delay between packets
await new Promise(resolve => setTimeout(resolve, 500));
}
return {
success: results.every(r => r.success),
results: results,
totalUsers: users.length
};
}
// User management functions
async function createBasicUser(partialUid, id, name, phone, email = '') {
const user = {
id: id,
name: name,
phone: phone,
email: email,
outputPermissions: { output1: true, output2: false, output3: false },
checkboxes: { enabled: true, validFromEnabled: false, validTillEnabled: false, counterEnabled: false },
workMode: true
};
return await addUser(partialUid, user, 0x0643); // Include work mode and checkboxes
}
async function createAdministrator(partialUid, id, name, phone, email, pinCode) {
if (id < 1 || id > 7) {
throw new Error('Administrator ID must be between 1 and 7');
}
const admin = {
id: id,
name: name,
phone: phone,
email: email,
pinCode: pinCode,
outputPermissions: { output1: true, output2: true, output3: true },
checkboxes: { enabled: true, validFromEnabled: false, validTillEnabled: false, counterEnabled: false },
workMode: true
};
return await addUser(partialUid, admin, 0x0E43); // Include PIN code
}
async function updateUserPermissions(partialUid, userId, permissions) {
const user = {
id: userId,
outputPermissions: permissions
};
return await editUser(partialUid, user, 0x0040); // Only output permissions bit
}
// Usage examples
async function demonstrateUsersWrite(partialUid) {
try {
console.log('=== Users Write Operations Demonstration ===');
// Create a basic user
console.log('\n--- Creating Basic User ---');
const createResult = await createBasicUser(partialUid, 50, 'John Doe', '+1234567890', 'john@example.com');
console.log('Create result:', createResult);
// Edit user permissions
console.log('\n--- Updating User Permissions ---');
const newPermissions = { output1: true, output2: true, output3: false };
const editResult = await updateUserPermissions(partialUid, 50, newPermissions);
console.log('Edit result:', editResult);
// Create multiple users
console.log('\n--- Creating Multiple Users ---');
const newUsers = [
{ id: 51, name: 'Jane Smith', phone: '+1987654321', email: 'jane@example.com' },
{ id: 52, name: 'Bob Wilson', phone: '+1555123456', email: 'bob@example.com' }
];
const multiResult = await addMultipleUsers(partialUid, newUsers.map(u => ({
...u,
outputPermissions: { output1: true, output2: false, output3: false },
checkboxes: { enabled: true, validFromEnabled: false, validTillEnabled: false, counterEnabled: false },
workMode: true
})));
console.log('Multiple users result:', multiResult);
// Create administrator
console.log('\n--- Creating Administrator ---');
const adminResult = await createAdministrator(partialUid, 2, 'Admin User', '+1800555000', 'admin@example.com', '9999');
console.log('Administrator result:', adminResult);
// Delete user
console.log('\n--- Deleting User ---');
const deleteResult = await deleteUser(partialUid, 52);
console.log('Delete result:', deleteResult);
console.log('Users write demonstration completed');
} catch (error) {
console.error('Users write demonstration failed:', error);
}
}0x0013 - Add/Remove Users
Command to add or remove users by name, email, and number. When the full list of users in the device is not known, this command adds only the user name and number to the first free slot in the list. Added users will be able to control all outputs. In order of priority, the user is first added to the list of administrators (if not full), otherwise to the list of users. The - sign means that if the user is found according to at least one parameter, he will be deleted from the list.
Request/Response format
Request
[HEADER][0x0013][ASCII user data] → 0701000000000000001300[ASCII user data]User Data Format
Cloud application sends ASCII text with the following format:
- First character:
+(add) or-(remove) - Parameters separated by commas:
email,number,name - Multiple users separated by pipe character
| - Each user group must start with
+or-
+email,number,name|-,email,number,name|+email2,number2,name2Request Examples
Single User Addition:
[HEADER][0x0013][+john@john.com,+12345678,Jonathan]
→ 0701000000000000001300+john@john.com,+12345678,JonathanSingle User Removal:
[HEADER][0x0013][-john@john.com,+12345678,Jonathan]
→ 0701000000000000001300-john@john.com,+12345678,JonathanMultiple Users (Mixed Operations):
[HEADER][0x0013][+john@john.com,+12345678,Jonathan|-jane@example.com,+87654321,Jane]
→ 0701000000000000001300+john@john.com,+12345678,Jonathan|-jane@example.com,+87654321,JaneResponse Format
Success Response:
[HEADER][0x0013][OK]Error Response:
[HEADER][0x0013][ERROR]Operation Types
| Operation | Symbol | Description |
|---|---|---|
| Add User | + | Adds user to first available slot |
| Remove User | - | Removes user matching any parameter |
Parameter Format
| Position | Parameter | Description | Required |
|---|---|---|---|
| 1 | User email address | Yes | |
| 2 | Number | Phone number with + prefix | Yes |
| 3 | Name | User display name | Yes |
Important Notes
- Comma Count: Maintain appropriate number of commas even for empty parameters
- Parameter Matching: For removal, user is deleted if ANY parameter matches
- Priority Order: Users added to administrators list first, then regular users
- Permissions: Added users automatically get all output control permissions
User Removal Logic
When using - operation, the user will be removed if any of the provided parameters match:
- Email matches existing user
- Phone number matches existing user
- Name matches existing user
Response Handling
Success Response (OK):
- User successfully added to available slot
- User successfully removed from list
- All parameters processed correctly
Error Response (ERROR):
- No available slots for new user
- Parameter format incorrect
- Device storage error
- User not found (for removal)
Cross-References
Related commands for user management:
- 0x0011 - Get Users List: Retrieve current user list
- 0x0012 - Write Users List: Full user list management
Usage Example
javascript
async function addUserToList(partialUid, email, phoneNumber, name) {
// Build add user command
const header = "070100000000000000";
const command = "1300";
// Format user data: +email,number,name
const userData = `+${email},${phoneNumber},${name}`;
const dataHex = asciiToHex(userData);
const fullCommand = header + command + dataHex;
try {
console.log(`Adding user: ${name} (${email}, ${phoneNumber})`);
const result = await sendDeviceCommand(partialUid, fullCommand, true);
if (result.data && result.data.length > 0) {
const responseText = hexToAscii(result.data[0]);
if (responseText === 'OK') {
console.log('User added successfully');
return {
success: true,
message: 'User added to device list',
user: { email, phoneNumber, name }
};
} else if (responseText === 'ERROR') {
console.log('Failed to add user');
return {
success: false,
message: 'Device returned ERROR - no available slots or invalid data'
};
} else {
return {
success: false,
message: `Unexpected response: ${responseText}`
};
}
}
throw new Error('No response received from device');
} catch (error) {
console.error('Failed to add user:', error);
throw error;
}
}
async function removeUserFromList(partialUid, email = '', phoneNumber = '', name = '') {
// Build remove user command
const header = "070100000000000000";
const command = "1300";
// Format user data: -email,number,name (any parameter can match)
const userData = `-${email},${phoneNumber},${name}`;
const dataHex = asciiToHex(userData);
const fullCommand = header + command + dataHex;
try {
console.log(`Removing user matching: email="${email}", phone="${phoneNumber}", name="${name}"`);
const result = await sendDeviceCommand(partialUid, fullCommand, true);
if (result.data && result.data.length > 0) {
const responseText = hexToAscii(result.data[0]);
if (responseText === 'OK') {
console.log('User removed successfully');
return {
success: true,
message: 'User removed from device list',
matchedBy: { email, phoneNumber, name }
};
} else if (responseText === 'ERROR') {
console.log('Failed to remove user');
return {
success: false,
message: 'Device returned ERROR - user not found or invalid data'
};
} else {
return {
success: false,
message: `Unexpected response: ${responseText}`
};
}
}
throw new Error('No response received from device');
} catch (error) {
console.error('Failed to remove user:', error);
throw error;
}
}
async function addMultipleUsers(partialUid, users) {
// Build multiple users command
const header = "070100000000000000";
const command = "1300";
// Format multiple users: +email1,num1,name1|+email2,num2,name2|-email3,num3,name3
const userData = users.map(user => {
const operation = user.operation || '+'; // Default to add
return `${operation}${user.email},${user.phoneNumber},${user.name}`;
}).join('|');
const dataHex = asciiToHex(userData);
const fullCommand = header + command + dataHex;
try {
console.log(`Processing ${users.length} user operations`);
const result = await sendDeviceCommand(partialUid, fullCommand, true);
if (result.data && result.data.length > 0) {
const responseText = hexToAscii(result.data[0]);
if (responseText === 'OK') {
console.log('All user operations completed successfully');
return {
success: true,
message: 'All user operations processed successfully',
processedUsers: users.length
};
} else if (responseText === 'ERROR') {
console.log('Some user operations failed');
return {
success: false,
message: 'Device returned ERROR - some operations may have failed'
};
} else {
return {
success: false,
message: `Unexpected response: ${responseText}`
};
}
}
throw new Error('No response received from device');
} catch (error) {
console.error('Failed to process multiple users:', error);
throw error;
}
}
// Helper function to validate user data format
function validateUserData(email, phoneNumber, name) {
const errors = [];
if (!email || !email.includes('@')) {
errors.push('Valid email address is required');
}
if (!phoneNumber || !phoneNumber.startsWith('+')) {
errors.push('Phone number must start with + prefix');
}
if (!name || name.trim().length === 0) {
errors.push('User name is required');
}
// Check for comma characters in parameters (would break format)
if (email.includes(',') || phoneNumber.includes(',') || name.includes(',')) {
errors.push('Parameters cannot contain comma characters');
}
// Check for pipe characters in parameters (would break multi-user format)
if (email.includes('|') || phoneNumber.includes('|') || name.includes('|')) {
errors.push('Parameters cannot contain pipe characters');
}
return {
valid: errors.length === 0,
errors: errors
};
}
// Convenience functions for common operations
async function quickAddUser(partialUid, email, phoneNumber, name) {
const validation = validateUserData(email, phoneNumber, name);
if (!validation.valid) {
throw new Error(`Invalid user data: ${validation.errors.join(', ')}`);
}
return await addUserToList(partialUid, email, phoneNumber, name);
}
async function quickRemoveUserByEmail(partialUid, email) {
return await removeUserFromList(partialUid, email, '', '');
}
async function quickRemoveUserByPhone(partialUid, phoneNumber) {
return await removeUserFromList(partialUid, '', phoneNumber, '');
}
async function quickRemoveUserByName(partialUid, name) {
return await removeUserFromList(partialUid, '', '', name);
}
async function bulkAddUsers(partialUid, userList) {
// Add operation to all users
const usersWithOperation = userList.map(user => ({
...user,
operation: '+'
}));
return await addMultipleUsers(partialUid, usersWithOperation);
}
async function bulkRemoveUsers(partialUid, userList) {
// Remove operation to all users
const usersWithOperation = userList.map(user => ({
...user,
operation: '-'
}));
return await addMultipleUsers(partialUid, usersWithOperation);
}
// Usage demonstration
async function demonstrateUserManagement(partialUid) {
try {
console.log('=== User Management Demonstration ===');
// Add single user
console.log('\n--- Adding Single User ---');
const addResult = await quickAddUser(
partialUid,
'john@example.com',
'+1234567890',
'John Doe'
);
console.log('Add result:', addResult);
// Add multiple users
console.log('\n--- Adding Multiple Users ---');
const multipleUsers = [
{ email: 'jane@example.com', phoneNumber: '+1987654321', name: 'Jane Smith' },
{ email: 'bob@example.com', phoneNumber: '+1555123456', name: 'Bob Wilson' }
];
const bulkAddResult = await bulkAddUsers(partialUid, multipleUsers);
console.log('Bulk add result:', bulkAddResult);
// Mixed operations
console.log('\n--- Mixed Add/Remove Operations ---');
const mixedOperations = [
{ email: 'alice@example.com', phoneNumber: '+1333444555', name: 'Alice Johnson', operation: '+' },
{ email: 'john@example.com', phoneNumber: '+1234567890', name: 'John Doe', operation: '-' }
];
const mixedResult = await addMultipleUsers(partialUid, mixedOperations);
console.log('Mixed operations result:', mixedResult);
// Remove user by different criteria
console.log('\n--- Removing Users ---');
// Remove by email
const removeByEmailResult = await quickRemoveUserByEmail(partialUid, 'jane@example.com');
console.log('Remove by email result:', removeByEmailResult);
// Remove by phone number
const removeByPhoneResult = await quickRemoveUserByPhone(partialUid, '+1555123456');
console.log('Remove by phone result:', removeByPhoneResult);
console.log('User management demonstration completed');
} catch (error) {
console.error('User management demonstration failed:', error);
}
}0x0014 - Get Users
Reads a list of users from CG17/SP3 based devices.
Request/Response format
Request
[HEADER][0x0014] → 070100000000000000140Response Format
Success Response:
[HEADER][0x0014][ASCII users data]Error Response:
[HEADER][0x0014][ERROR]Response Data Fields
The response data is returned as ASCII text containing user information.
Device Compatibility
This command is specifically designed for CG17/SP3 based devices.
Cross-References
Related commands for user management:
- 0x0011 - Get Users List: Retrieve user list with parameters (GV17/WP17 devices)
- 0x0012 - Write Users List: Full user list management (GV17/WP17 devices)
- 0x0013 - Add/Remove Users: Add/remove individual users
Usage Example
javascript
async function getUsersFromDevice(partialUid) {
// Build get users command for CG17/SP3 devices
const header = "070100000000000000";
const command = "1400";
const fullCommand = header + command;
try {
console.log('Requesting users list from CG17/SP3 device...');
const result = await sendDeviceCommand(partialUid, fullCommand, true);
if (result.data && result.data.length > 0) {
const responseText = hexToAscii(result.data[0]);
if (responseText === 'ERROR') {
console.log('Failed to retrieve users');
return {
success: false,
message: 'Device returned ERROR - unable to retrieve users'
};
} else {
console.log('Users list retrieved successfully');
return {
success: true,
message: 'Users retrieved successfully',
usersData: responseText,
rawResponse: result.data[0]
};
}
}
throw new Error('No response received from device');
} catch (error) {
console.error('Failed to get users from device:', error);
throw error;
}
}
// Usage demonstration
async function demonstrateGetUsers(partialUid) {
try {
console.log('=== Get Users Demonstration (CG17/SP3) ===');
const result = await getUsersFromDevice(partialUid);
if (result.success) {
console.log('Users data:', result.usersData);
console.log('Raw response:', result.rawResponse);
} else {
console.log('Failed to get users:', result.message);
}
console.log('Get users demonstration completed');
} catch (error) {
console.error('Get users demonstration failed:', error);
}
}0x0015 - Write Users
Writing user list to device for CG17/SP3 based devices.
Request/Response format
Request
[HEADER][0x0015][ASCII users data] → 0701000000000000001500[ASCII users data]Response Format
Success Response:
[HEADER][0x0015][OK]Error Response:
[HEADER][0x0015][ERROR]Request Data Format
The request data should be sent as ASCII text containing user information formatted for CG17/SP3 devices.
Device Compatibility
This command is specifically designed for CG17/SP3 based devices.
Response Handling
Success Response (OK):
- Users list successfully written to device
- All user data processed correctly
Error Response (ERROR):
- Invalid user data format
- Device storage error
- Write operation failed
Cross-References
Related commands for user management:
- 0x0011 - Get Users List: Retrieve user list with parameters (GV17/WP17 devices)
- 0x0012 - Write Users List: Full user list management (GV17/WP17 devices)
- 0x0013 - Add/Remove Users: Add/remove individual users
- 0x0014 - Get Users: Retrieve user list (CG17/SP3 devices)
Usage Example
javascript
async function writeUsersToDevice(partialUid, usersData) {
// Build write users command for CG17/SP3 devices
const header = "070100000000000000";
const command = "1500";
// Convert users data to hex
const dataHex = asciiToHex(usersData);
const fullCommand = header + command + dataHex;
try {
console.log('Writing users list to CG17/SP3 device...');
const result = await sendDeviceCommand(partialUid, fullCommand, true);
if (result.data && result.data.length > 0) {
const responseText = hexToAscii(result.data[0]);
if (responseText === 'OK') {
console.log('Users list written successfully');
return {
success: true,
message: 'Users list written successfully'
};
} else if (responseText === 'ERROR') {
console.log('Failed to write users list');
return {
success: false,
message: 'Device returned ERROR - write operation failed'
};
} else {
return {
success: false,
message: `Unexpected response: ${responseText}`
};
}
}
throw new Error('No response received from device');
} catch (error) {
console.error('Failed to write users to device:', error);
throw error;
}
}
// Usage demonstration
async function demonstrateWriteUsers(partialUid) {
try {
console.log('=== Write Users Demonstration (CG17/SP3) ===');
// Example users data - format would depend on device specifications
const usersData = "user1,data1|user2,data2|user3,data3";
const result = await writeUsersToDevice(partialUid, usersData);
if (result.success) {
console.log('Users successfully written to device');
} else {
console.log('Failed to write users:', result.message);
}
console.log('Write users demonstration completed');
} catch (error) {
console.error('Write users demonstration failed:', error);
}
}0x0016 - Bypass Zones of Area
Controls zone bypass status for a specific area, allowing zones to be bypassed or restored to normal operation.
Request/Response format
Request
[HEADER][0x0016][N:BYP] → 0701000000000000001600[N:BYP]Request Parameters
| Parameter | Format | Description |
|---|---|---|
N | Number | Area number |
BYP | Binary string | Zone bypass states (0=normal, 1=bypassed) |
Request Examples
Set bypass for Area 1:
[HEADER][0x0016][1:0001100]
→ 0701000000000000001600313A30303031313030Set bypass for Area 2:
[HEADER][0x0016][2:1100000]
→ 0701000000000000001600323A31313030303030Response Format
Success Response:
[HEADER][0x0016][Z:xxxxxxx]Error Response:
[HEADER][0x0016][ERROR]Zone Bypass States
The BYP parameter is a string of binary digits where:
- 0 = Zone is in normal operation
- 1 = Zone is bypassed (excluded from area monitoring)
Each position represents a zone number (position 1 = zone 1, position 2 = zone 2, etc.).
Response Data Format
The response contains the current zone states in the format Z:xxxxxxx, where each position indicates:
- 0 = Zone normal/secure
- 1 = Zone active/triggered
- ? = Zone status unknown
This format matches the zone status format from 0x000D - Status.
Bypass Operation Logic
Setting Bypassed (1):
- Zone is excluded from area monitoring
- Zone alarms will not trigger area alarm
- Zone remains monitored but ignored for security purposes
Setting Normal (0):
- Zone returns to active monitoring
- Zone alarms will trigger area alarm
- Zone participates fully in area security
Cross-References
Related commands for zone and area management:
- 0x000D - Status: Get current zone and area status
- 0x0002 - Bypass Zones: General zone bypass control
- 0x0008 - Areas Arm/Disarm: Area arming control
Usage Example
javascript
async function bypassAreaZones(partialUid, areaNumber, bypassStates) {
// Build bypass zones command
const header = "070100000000000000";
const command = "1600";
// Format request data: N:BYP
const requestData = `${areaNumber}:${bypassStates}`;
const dataHex = asciiToHex(requestData);
const fullCommand = header + command + dataHex;
try {
console.log(`Setting zone bypass for area ${areaNumber}: ${bypassStates}`);
const result = await sendDeviceCommand(partialUid, fullCommand, true);
if (result.data && result.data.length > 0) {
const responseText = hexToAscii(result.data[0]);
if (responseText.startsWith('Z:')) {
const zoneStates = responseText.substring(2);
console.log('Zone bypass set successfully, current zone status:', zoneStates);
return {
success: true,
message: 'Zone bypass updated successfully',
areaNumber: areaNumber,
bypassStates: bypassStates,
currentZoneStates: zoneStates
};
} else if (responseText === 'ERROR') {
console.log('Failed to set zone bypass');
return {
success: false,
message: 'Device returned ERROR - invalid area or bypass data'
};
} else {
return {
success: false,
message: `Unexpected response: ${responseText}`
};
}
}
throw new Error('No response received from device');
} catch (error) {
console.error('Failed to set zone bypass:', error);
throw error;
}
}
// Helper function to create bypass states string
function createBypassStates(zoneCount, bypassedZones = []) {
let states = '';
for (let i = 1; i <= zoneCount; i++) {
states += bypassedZones.includes(i) ? '1' : '0';
}
return states;
}
// Helper function to parse bypass states
function parseBypassStates(bypassStates) {
const bypassedZones = [];
for (let i = 0; i < bypassStates.length; i++) {
if (bypassStates[i] === '1') {
bypassedZones.push(i + 1); // Zone numbers start from 1
}
}
return bypassedZones;
}
// Convenience functions
async function bypassSpecificZones(partialUid, areaNumber, zoneCount, zonesToBypass) {
const bypassStates = createBypassStates(zoneCount, zonesToBypass);
return await bypassAreaZones(partialUid, areaNumber, bypassStates);
}
async function clearAllBypasses(partialUid, areaNumber, zoneCount) {
const bypassStates = '0'.repeat(zoneCount);
return await bypassAreaZones(partialUid, areaNumber, bypassStates);
}
async function bypassAllZones(partialUid, areaNumber, zoneCount) {
const bypassStates = '1'.repeat(zoneCount);
return await bypassAreaZones(partialUid, areaNumber, bypassStates);
}
// Usage demonstration
async function demonstrateZoneBypass(partialUid) {
try {
console.log('=== Zone Bypass Demonstration ===');
const areaNumber = 1;
const totalZones = 8;
// Bypass specific zones (3 and 5)
console.log('\n--- Bypassing Zones 3 and 5 ---');
const bypassResult = await bypassSpecificZones(partialUid, areaNumber, totalZones, [3, 5]);
console.log('Bypass result:', bypassResult);
if (bypassResult.success) {
console.log('Bypassed zones:', parseBypassStates(bypassResult.bypassStates));
}
// Clear specific bypass (zone 3 only)
console.log('\n--- Bypassing Only Zone 5 ---');
const partialBypassResult = await bypassSpecificZones(partialUid, areaNumber, totalZones, [5]);
console.log('Partial bypass result:', partialBypassResult);
// Clear all bypasses
console.log('\n--- Clearing All Bypasses ---');
const clearResult = await clearAllBypasses(partialUid, areaNumber, totalZones);
console.log('Clear bypasses result:', clearResult);
console.log('Zone bypass demonstration completed');
} catch (error) {
console.error('Zone bypass demonstration failed:', error);
}
}0x0017 - Change Object ID
Changes the object ID of the device, allowing modification of the device identifier.
Request/Response format
Request
[HEADER][0x0017][ID1:New_ID] → 0701000000000000001700[ID1:New_ID]Request Parameters
| Parameter | Format | Description |
|---|---|---|
ID1 | Keyword | Fixed keyword indicating object number change |
New_ID | String | New object ID to assign to device |
Request Examples
Change object ID:
[HEADER][0x0017][ID1:ABC123]
→ 0701000000000000001700493431343A414243313233Change to numeric ID:
[HEADER][0x0017][ID1:456789]
→ 0701000000000000001700493431343A343536373839Response Format
Success Response:
[HEADER][0x0017][ID1:New_ID]Error Responses:
[HEADER][0x0017][ERROR] // Wrong keyword received
[HEADER][0x0017][FORMAT] // Faulty object ID length receivedParameter Requirements
ID1 Keyword:
- Must be exactly
ID1 - Case-sensitive requirement
- Indicates object number change operation
New_ID Format:
- Object ID string format
- Length restrictions apply (device-specific)
- Alphanumeric characters typically supported
Error Conditions
| Error | Condition | Description |
|---|---|---|
ERROR | Wrong keyword | Keyword other than ID1 was received |
FORMAT | Invalid ID length | Object ID length doesn't meet device requirements |
Response Handling
Success Response (ID1:New_ID):
- Object ID successfully changed
- Response echoes the new ID for confirmation
- Device will use new ID for future communications
Error Response (ERROR):
- Invalid keyword provided
- Must use exactly
ID1as keyword
Error Response (FORMAT):
- Object ID length is invalid
- Check device specifications for ID length requirements
Cross-References
Related commands for device configuration:
- 0x0001 - Capabilities: Get device information including current ID
- 0x000D - Status: Device status information
Usage Example
javascript
async function changeObjectId(partialUid, newObjectId) {
// Build change object ID command
const header = "070100000000000000";
const command = "1700";
// Format request data: ID1:New_ID
const requestData = `ID1:${newObjectId}`;
const dataHex = asciiToHex(requestData);
const fullCommand = header + command + dataHex;
try {
console.log(`Changing object ID to: ${newObjectId}`);
const result = await sendDeviceCommand(partialUid, fullCommand, true);
if (result.data && result.data.length > 0) {
const responseText = hexToAscii(result.data[0]);
if (responseText.startsWith('ID1:')) {
const confirmedId = responseText.substring(4);
console.log('Object ID changed successfully to:', confirmedId);
return {
success: true,
message: 'Object ID changed successfully',
oldId: partialUid,
newId: confirmedId,
confirmed: confirmedId === newObjectId
};
} else if (responseText === 'ERROR') {
console.log('Failed to change object ID - wrong keyword');
return {
success: false,
message: 'Device returned ERROR - wrong keyword (must use ID1)',
errorType: 'KEYWORD_ERROR'
};
} else if (responseText === 'FORMAT') {
console.log('Failed to change object ID - invalid format');
return {
success: false,
message: 'Device returned FORMAT - invalid object ID length',
errorType: 'FORMAT_ERROR'
};
} else {
return {
success: false,
message: `Unexpected response: ${responseText}`,
errorType: 'UNKNOWN_RESPONSE'
};
}
}
throw new Error('No response received from device');
} catch (error) {
console.error('Failed to change object ID:', error);
throw error;
}
}
// Helper function to validate object ID format
function validateObjectId(objectId) {
const errors = [];
if (!objectId || objectId.trim().length === 0) {
errors.push('Object ID cannot be empty');
}
if (objectId.length > 32) { // Assuming max length - adjust based on device specs
errors.push('Object ID too long (max 32 characters)');
}
if (objectId.length < 3) { // Assuming min length - adjust based on device specs
errors.push('Object ID too short (min 3 characters)');
}
// Check for invalid characters (adjust regex based on device requirements)
if (!/^[A-Za-z0-9_-]+$/.test(objectId)) {
errors.push('Object ID contains invalid characters (only alphanumeric, underscore, and dash allowed)');
}
// Check for colon character (would break format)
if (objectId.includes(':')) {
errors.push('Object ID cannot contain colon character');
}
return {
valid: errors.length === 0,
errors: errors
};
}
// Convenience function with validation
async function changeObjectIdSafe(partialUid, newObjectId) {
const validation = validateObjectId(newObjectId);
if (!validation.valid) {
throw new Error(`Invalid object ID: ${validation.errors.join(', ')}`);
}
return await changeObjectId(partialUid, newObjectId);
}
// Usage demonstration
async function demonstrateObjectIdChange(partialUid) {
try {
console.log('=== Object ID Change Demonstration ===');
console.log('Current device ID:', partialUid);
// Change to alphanumeric ID
console.log('\n--- Changing to Alphanumeric ID ---');
const newId1 = 'DEV_001';
const result1 = await changeObjectIdSafe(partialUid, newId1);
console.log('Change result:', result1);
if (result1.success) {
// Change to numeric ID
console.log('\n--- Changing to Numeric ID ---');
const newId2 = '123456';
const result2 = await changeObjectIdSafe(result1.newId, newId2);
console.log('Second change result:', result2);
if (result2.success) {
console.log('Final device ID:', result2.newId);
}
}
// Demonstrate error handling with invalid ID
console.log('\n--- Testing Invalid ID (too long) ---');
try {
const invalidId = 'A'.repeat(50); // Too long ID
await changeObjectIdSafe(partialUid, invalidId);
} catch (error) {
console.log('Expected validation error:', error.message);
}
console.log('Object ID change demonstration completed');
} catch (error) {
console.error('Object ID change demonstration failed:', error);
}
}0x0018 - Change Domain/IP Address or Port
Changes network configuration for communication channels, allowing modification of IP addresses, domain names, and port numbers, or disabling specific channels.
Request/Response format
Request
[HEADER][0x0018][kcp:a:n] → 0701000000000000001800[kcp:a:n]Request Parameters
| Parameter | Format | Description |
|---|---|---|
k | Keyword | Command purpose: A (change address/port), D (disable channel) |
c | Channel | Channel selection: 1, 2, 3, C (Cloud/Protegus), A (Active) |
p | Priority | Channel priority: P (Primary), B (Backup) |
a | Address | New IP address or domain name (when k=A) |
n | Number | New port number (when k=A) |
Request Examples
Change First Primary channel:
[HEADER][0x0018][A1P:195.14.1.100:1000]
→ 0701000000000000001800413150343131393535413431343531314131303031313030303030Change Cloud Primary channel:
[HEADER][0x0018][ACP:i01.protegus.eu:55019]
→ 0701000000000000001800414350693031343470726F746567757334653735353030313939Disable Second Backup channel:
[HEADER][0x0018][D2B]
→ 0701000000000000001800443242Response Format
Success Response:
[HEADER][0x0018][OK]Error Response:
[HEADER][0x0018][ERROR]Command Keywords
| Keyword | Purpose | Parameters Required |
|---|---|---|
A | Change address/port | Channel, Priority, Address, Port |
D | Disable channel | Channel, Priority only |
Channel Selection
| Code | Channel | Description |
|---|---|---|
1 | First | First communication channel |
2 | Second | Second communication channel |
3 | Third | Third communication channel |
C | Cloud | Cloud (Protegus) channel |
A | Active | Currently active channel |
Priority Types
| Code | Priority | Description |
|---|---|---|
P | Primary | Primary channel configuration |
B | Backup | Backup channel configuration |
Parameter Rules
Empty Parameters:
- Empty parameters are allowed and preserve current values
- Only specified parameters are changed
- Useful for partial configuration updates
Address Format:
- IP addresses: Standard IPv4 format (e.g.,
195.14.1.100) - Domain names: FQDN format (e.g.,
i01.protegus.eu) - No spaces or special characters except dots and dashes
Port Format:
- Numeric values (e.g.,
1000,55019) - Valid port range: 1-65535
Configuration Management
Change Activation:
- Changes take effect after device response
- Device may require restart for full activation
- Configuration is persistent until restored to defaults
Restoration:
- Changed data can be canceled using Restore Defaults procedure
- Device-initiated restart may be required
- Backup configurations remain available
Cross-References
Related commands for device configuration:
- 0x0001 - Capabilities: Get device network information
- 0x0017 - Change Object ID: Change device identifier
Usage Example
javascript
async function changeChannelConfig(partialUid, keyword, channel, priority, address = '', port = '') {
// Build change channel configuration command
const header = "070100000000000000";
const command = "1800";
let requestData;
if (keyword === 'A') {
// Format: kcp:a:n (change address/port)
requestData = `${keyword}${channel}${priority}:${address}:${port}`;
} else if (keyword === 'D') {
// Format: kcp (disable channel)
requestData = `${keyword}${channel}${priority}`;
} else {
throw new Error('Invalid keyword. Use "A" for address change or "D" for disable.');
}
const dataHex = asciiToHex(requestData);
const fullCommand = header + command + dataHex;
try {
console.log(`Changing channel configuration: ${requestData}`);
const result = await sendDeviceCommand(partialUid, fullCommand, true);
if (result.data && result.data.length > 0) {
const responseText = hexToAscii(result.data[0]);
if (responseText === 'OK') {
console.log('Channel configuration changed successfully');
return {
success: true,
message: 'Channel configuration updated successfully',
keyword: keyword,
channel: channel,
priority: priority,
address: address,
port: port,
command: requestData
};
} else if (responseText === 'ERROR') {
console.log('Failed to change channel configuration');
return {
success: false,
message: 'Device returned ERROR - invalid configuration parameters'
};
} else {
return {
success: false,
message: `Unexpected response: ${responseText}`
};
}
}
throw new Error('No response received from device');
} catch (error) {
console.error('Failed to change channel configuration:', error);
throw error;
}
}
// Helper functions for specific operations
async function changeChannelAddress(partialUid, channel, priority, address, port) {
return await changeChannelConfig(partialUid, 'A', channel, priority, address, port);
}
async function disableChannel(partialUid, channel, priority) {
return await changeChannelConfig(partialUid, 'D', channel, priority);
}
// Validation functions
function validateIpAddress(ip) {
const ipRegex = /^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/;
return ipRegex.test(ip);
}
function validateDomainName(domain) {
const domainRegex = /^[a-zA-Z0-9]([a-zA-Z0-9\-]{0,61}[a-zA-Z0-9])?(\.[a-zA-Z0-9]([a-zA-Z0-9\-]{0,61}[a-zA-Z0-9])?)*$/;
return domainRegex.test(domain) && domain.length <= 253;
}
function validatePort(port) {
const portNum = parseInt(port);
return !isNaN(portNum) && portNum >= 1 && portNum <= 65535;
}
function validateChannelConfig(channel, priority, address = '', port = '') {
const errors = [];
// Validate channel
if (!['1', '2', '3', 'C', 'A'].includes(channel)) {
errors.push('Invalid channel. Use 1, 2, 3, C (Cloud), or A (Active)');
}
// Validate priority
if (!['P', 'B'].includes(priority)) {
errors.push('Invalid priority. Use P (Primary) or B (Backup)');
}
// Validate address if provided
if (address && address.trim().length > 0) {
if (!validateIpAddress(address) && !validateDomainName(address)) {
errors.push('Invalid address format. Use valid IP address or domain name');
}
}
// Validate port if provided
if (port && port.trim().length > 0) {
if (!validatePort(port)) {
errors.push('Invalid port number. Use value between 1 and 65535');
}
}
return {
valid: errors.length === 0,
errors: errors
};
}
// Safe wrapper functions with validation
async function changeChannelAddressSafe(partialUid, channel, priority, address, port) {
const validation = validateChannelConfig(channel, priority, address, port);
if (!validation.valid) {
throw new Error(`Invalid configuration: ${validation.errors.join(', ')}`);
}
return await changeChannelAddress(partialUid, channel, priority, address, port);
}
async function disableChannelSafe(partialUid, channel, priority) {
const validation = validateChannelConfig(channel, priority);
if (!validation.valid) {
throw new Error(`Invalid configuration: ${validation.errors.join(', ')}`);
}
return await disableChannel(partialUid, channel, priority);
}
// Convenience functions for common scenarios
async function setupCloudChannel(partialUid, domain, port) {
return await changeChannelAddressSafe(partialUid, 'C', 'P', domain, port);
}
async function setupPrimaryChannel(partialUid, channelNum, ipAddress, port) {
return await changeChannelAddressSafe(partialUid, channelNum, 'P', ipAddress, port);
}
async function setupBackupChannel(partialUid, channelNum, ipAddress, port) {
return await changeChannelAddressSafe(partialUid, channelNum, 'B', ipAddress, port);
}
// Usage demonstration
async function demonstrateChannelConfig(partialUid) {
try {
console.log('=== Channel Configuration Demonstration ===');
// Setup primary channel
console.log('\n--- Setting Up Primary Channel 1 ---');
const primaryResult = await setupPrimaryChannel(partialUid, '1', '192.168.1.100', '8080');
console.log('Primary setup result:', primaryResult);
// Setup backup channel
console.log('\n--- Setting Up Backup Channel 1 ---');
const backupResult = await setupBackupChannel(partialUid, '1', '192.168.1.101', '8080');
console.log('Backup setup result:', backupResult);
// Setup cloud channel
console.log('\n--- Setting Up Cloud Channel ---');
const cloudResult = await setupCloudChannel(partialUid, 'i01.protegus.eu', '55019');
console.log('Cloud setup result:', cloudResult);
// Disable a channel
console.log('\n--- Disabling Second Backup Channel ---');
const disableResult = await disableChannelSafe(partialUid, '2', 'B');
console.log('Disable result:', disableResult);
// Demonstrate partial update (empty parameters)
console.log('\n--- Updating Only Port (Empty Address) ---');
const portOnlyResult = await changeChannelAddress(partialUid, '3', 'P', '', '9090');
console.log('Port-only update result:', portOnlyResult);
// Test validation
console.log('\n--- Testing Invalid Configuration ---');
try {
await changeChannelAddressSafe(partialUid, 'X', 'P', '192.168.1.1', '8080');
} catch (error) {
console.log('Expected validation error:', error.message);
}
console.log('Channel configuration demonstration completed');
} catch (error) {
console.error('Channel configuration demonstration failed:', error);
}
}0x0019 - Change Parameter of Input/Output
Changes configuration parameters of input/output channels, including type configuration and schedule assignments.
Request/Response format
Request
[HEADER][0x0019][T:I/O:State,T:I/O:State,S:I/O:ID] → 0701000000000000001900[T:I/O:State,T:I/O:State,S:I/O:ID]Request Parameters
| Parameter | Format | Description |
|---|---|---|
T | Type | Parameter type: T (Type change), S (Schedule assignment) |
I/O | Number | Input/Output number (starts from 1) |
State | Number | For T: 0 (disabled), 1 (input), 2 (output) |
ID | Hex | For S: Schedule data in hex format (4 symbols) |
Request Examples
Change I/O 3 to output:
[HEADER][0x0019][T:3:2]
→ 0701000000000000001900543A333A32Disable I/O 5:
[HEADER][0x0019][T:5:0]
→ 0701000000000000001900543A353A30Set schedule to output 2:
[HEADER][0x0019][S:2:0A52]
→ 0701000000000000001900533A323A30413532Multiple changes:
[HEADER][0x0019][T:3:2,T:5:0,S:2:0A52]
→ 0701000000000000001900543A333A322C543A353A302C533A323A30413532Response Format
Success Response:
[HEADER][0x0019][O:SystemState] // Same format as 0x000D StatusError Response:
[HEADER][0x0019][NACK] // I/O doesn't support function or wrong dataParameter Types
| Type | Code | Description | State Values |
|---|---|---|---|
| Type Change | T | Change I/O functionality | 0=disabled, 1=input, 2=output |
| Schedule | S | Set schedule assignment | 4-digit hex schedule ID |
I/O Type States
| State | Value | Description |
|---|---|---|
| Disabled | 0 | I/O channel is disabled |
| Input | 1 | Channel configured as input |
| Output | 2 | Channel configured as output |
Schedule Format
Schedule ID:
- 4-character hexadecimal format
- Example:
0A52,FF00,1234 - Represents schedule configuration data
I/O Numbering:
- Starts from 1 (not 0)
- Corresponds to physical order on device
- Sequential numbering for inputs and outputs
Multiple Operations
Commands can contain multiple parameter changes separated by commas:
T:3:2,T:5:0,S:2:0A52This example:
- Changes I/O 3 to output type
- Disables I/O 5
- Sets schedule 0x0A52 to I/O 2
Response Handling
Success Response (O:SystemState):
- Returns current system state after changes
- Format matches 0x000D - Status command
- Confirms successful parameter updates
Error Response (NACK):
- I/O doesn't support the requested function
- Invalid parameter data provided
- Configuration change not permitted
Cross-References
Related commands for I/O management:
- 0x000D - Status: Get current system and I/O status
- 0x0003 - Control Outputs: Control output states
Usage Example
javascript
async function changeIOParameter(partialUid, changes) {
// Build change I/O parameter command
const header = "070100000000000000";
const command = "1900";
// Format changes array into command string
let changeStrings;
if (Array.isArray(changes)) {
changeStrings = changes.map(change => {
if (change.type === 'T') {
return `T:${change.io}:${change.state}`;
} else if (change.type === 'S') {
return `S:${change.io}:${change.scheduleId}`;
} else {
throw new Error('Invalid change type. Use "T" for type or "S" for schedule.');
}
}).join(',');
} else if (typeof changes === 'string') {
changeStrings = changes;
} else {
throw new Error('Changes must be array of change objects or string');
}
const dataHex = asciiToHex(changeStrings);
const fullCommand = header + command + dataHex;
try {
console.log(`Changing I/O parameters: ${changeStrings}`);
const result = await sendDeviceCommand(partialUid, fullCommand, true);
if (result.data && result.data.length > 0) {
const responseText = hexToAscii(result.data[0]);
if (responseText.startsWith('O:')) {
const systemState = responseText.substring(2);
console.log('I/O parameters changed successfully, system state:', systemState);
return {
success: true,
message: 'I/O parameters updated successfully',
changes: changeStrings,
systemState: systemState
};
} else if (responseText === 'NACK') {
console.log('Failed to change I/O parameters');
return {
success: false,
message: 'Device returned NACK - I/O doesn\'t support function or wrong data'
};
} else {
return {
success: false,
message: `Unexpected response: ${responseText}`
};
}
}
throw new Error('No response received from device');
} catch (error) {
console.error('Failed to change I/O parameters:', error);
throw error;
}
}
// Helper functions for specific operations
async function setIOType(partialUid, ioNumber, type) {
const change = { type: 'T', io: ioNumber, state: type };
return await changeIOParameter(partialUid, [change]);
}
async function setIOSchedule(partialUid, ioNumber, scheduleId) {
const change = { type: 'S', io: ioNumber, scheduleId: scheduleId };
return await changeIOParameter(partialUid, [change]);
}
async function disableIO(partialUid, ioNumber) {
return await setIOType(partialUid, ioNumber, 0);
}
async function configureAsInput(partialUid, ioNumber) {
return await setIOType(partialUid, ioNumber, 1);
}
async function configureAsOutput(partialUid, ioNumber) {
return await setIOType(partialUid, ioNumber, 2);
}
// Validation functions
function validateIONumber(ioNumber) {
const num = parseInt(ioNumber);
return !isNaN(num) && num >= 1 && num <= 255; // Assuming max 255 I/Os
}
function validateIOType(type) {
return [0, 1, 2].includes(parseInt(type));
}
function validateScheduleId(scheduleId) {
return /^[0-9A-Fa-f]{4}$/.test(scheduleId);
}
function validateIOChange(change) {
const errors = [];
if (!['T', 'S'].includes(change.type)) {
errors.push('Invalid change type. Use "T" for type or "S" for schedule');
}
if (!validateIONumber(change.io)) {
errors.push('Invalid I/O number. Must be between 1 and 255');
}
if (change.type === 'T') {
if (!validateIOType(change.state)) {
errors.push('Invalid I/O type. Use 0 (disabled), 1 (input), or 2 (output)');
}
} else if (change.type === 'S') {
if (!validateScheduleId(change.scheduleId)) {
errors.push('Invalid schedule ID. Must be 4-character hex string');
}
}
return {
valid: errors.length === 0,
errors: errors
};
}
// Safe wrapper functions with validation
async function changeIOParameterSafe(partialUid, changes) {
if (!Array.isArray(changes)) {
changes = [changes];
}
// Validate all changes
for (const change of changes) {
const validation = validateIOChange(change);
if (!validation.valid) {
throw new Error(`Invalid change: ${validation.errors.join(', ')}`);
}
}
return await changeIOParameter(partialUid, changes);
}
// Convenience functions for common scenarios
async function setupOutputWithSchedule(partialUid, ioNumber, scheduleId) {
const changes = [
{ type: 'T', io: ioNumber, state: 2 }, // Configure as output
{ type: 'S', io: ioNumber, scheduleId: scheduleId } // Set schedule
];
return await changeIOParameterSafe(partialUid, changes);
}
async function bulkDisableIOs(partialUid, ioNumbers) {
const changes = ioNumbers.map(ioNumber => ({
type: 'T',
io: ioNumber,
state: 0
}));
return await changeIOParameterSafe(partialUid, changes);
}
async function bulkConfigureAsInputs(partialUid, ioNumbers) {
const changes = ioNumbers.map(ioNumber => ({
type: 'T',
io: ioNumber,
state: 1
}));
return await changeIOParameterSafe(partialUid, changes);
}
async function bulkConfigureAsOutputs(partialUid, ioNumbers) {
const changes = ioNumbers.map(ioNumber => ({
type: 'T',
io: ioNumber,
state: 2
}));
return await changeIOParameterSafe(partialUid, changes);
}
// Usage demonstration
async function demonstrateIOParameterChange(partialUid) {
try {
console.log('=== I/O Parameter Change Demonstration ===');
// Single type change
console.log('\n--- Configuring I/O 3 as Output ---');
const outputResult = await configureAsOutput(partialUid, 3);
console.log('Output configuration result:', outputResult);
// Disable an I/O
console.log('\n--- Disabling I/O 5 ---');
const disableResult = await disableIO(partialUid, 5);
console.log('Disable result:', disableResult);
// Set schedule
console.log('\n--- Setting Schedule to I/O 2 ---');
const scheduleResult = await setIOSchedule(partialUid, 2, '0A52');
console.log('Schedule result:', scheduleResult);
// Multiple changes at once
console.log('\n--- Multiple Changes ---');
const multipleChanges = [
{ type: 'T', io: 1, state: 1 }, // I/O 1 as input
{ type: 'T', io: 2, state: 2 }, // I/O 2 as output
{ type: 'S', io: 2, scheduleId: 'FF00' } // Set schedule for I/O 2
];
const multipleResult = await changeIOParameterSafe(partialUid, multipleChanges);
console.log('Multiple changes result:', multipleResult);
// Setup output with schedule
console.log('\n--- Setting Up Complete Output Configuration ---');
const completeSetupResult = await setupOutputWithSchedule(partialUid, 4, '1234');
console.log('Complete setup result:', completeSetupResult);
// Bulk operations
console.log('\n--- Bulk Configure I/Os 6-8 as Inputs ---');
const bulkInputResult = await bulkConfigureAsInputs(partialUid, [6, 7, 8]);
console.log('Bulk input configuration result:', bulkInputResult);
// Test validation
console.log('\n--- Testing Invalid Configuration ---');
try {
await changeIOParameterSafe(partialUid, [{ type: 'T', io: 1, state: 5 }]); // Invalid state
} catch (error) {
console.log('Expected validation error:', error.message);
}
console.log('I/O parameter change demonstration completed');
} catch (error) {
console.error('I/O parameter change demonstration failed:', error);
}
}0x001A - Read Schedule
Reads schedule configuration from device. Can request specific types of schedules or multiple schedule types in a single command.
Request/Response format
Request
[HEADER][0x001A][ScheduleType1|ScheduleTypeN-1|ScheduleTypeN] → 0701000000000000001A00[ScheduleTypes]Request Parameters
| Parameter | Format | Description |
|---|---|---|
ScheduleType | Number | Schedule type to read: 1 (GV17 users), 2 (GV17 outputs) |
| ` | ` | Separator |
Request Examples
Get both user and output schedules:
[HEADER][0x001A][1|2]
→ 0701000000000000001A00317C32Get only output schedules:
[HEADER][0x001A][2]
→ 0701000000000000001A0032Response Format
Success Response:
[HEADER][0x001A][ScheduleData1|ScheduleDataN]Error Response:
[HEADER][0x001A][NACK] // Command not supported or wrong dataSchedule Types
| Type | Code | Description |
|---|---|---|
| User Schedules | 1 | Schedule configuration for GV17 users |
| Output Schedules | 2 | Schedule configuration for GV17 outputs |
Schedule Data Format
All schedule data is returned in decimal format with multiple schedules separated by colons (:) and different schedule types separated by pipes (|).
Type 1 - User Schedules
Format: type,id,enableFlags,startHour,startMin,startWeekDay,stopHour,stopMin,stopWeekDay
| Field | Range | Description |
|---|---|---|
type | - | Schedule type (1 for users) |
id | - | Queue number/schedule ID |
enableFlags | 0-1 | Enable/disable flag (bit 0) |
startHour | 0-23 | Start hour (24-hour format) |
startMin | 0-59 | Start minutes |
startWeekDay | Bitmask | Start weekdays (bit0=Monday, bit6=Sunday) |
stopHour | 0-23 | Stop hour (24-hour format) |
stopMin | 0-59 | Stop minutes |
stopWeekDay | Bitmask | Stop weekdays (bit0=Monday, bit6=Sunday) |
Type 2 - Output Schedules
Format: type,id,enableFlags,outType,startHour,startMin,startWeekDay,stopHour,stopMin,stopWeekDay
| Field | Range | Description |
|---|---|---|
type | - | Schedule type (2 for outputs) |
id | - | Queue number/schedule ID |
enableFlags | 0-1 | Enable/disable flag (bit 0) |
outType | 0-1 | Output type: 0 (pulse), 1 (level) |
startHour | 0-23 | Start hour (24-hour format) |
startMin | 0-59 | Start minutes |
startWeekDay | Bitmask | Start weekdays (bit0=Monday, bit6=Sunday) |
stopHour | 0-23 | Stop hour (24-hour format) |
stopMin | 0-59 | Stop minutes |
stopWeekDay | Bitmask | Stop weekdays (bit0=Monday, bit6=Sunday) |
Weekday Bitmask
| Bit | Day | Value |
|---|---|---|
| 0 | Monday | 1 |
| 1 | Tuesday | 2 |
| 2 | Wednesday | 4 |
| 3 | Thursday | 8 |
| 4 | Friday | 16 |
| 5 | Saturday | 32 |
| 6 | Sunday | 64 |
Response Example
Combined user and output schedules:
1,1,1,12,22,15,44,5:1,2,1,14,45,19,22,8|2,1,1,0,12,22,15,44,5:2,2,1,1,14,45,19,22,8This response contains:
- Type 1 (User schedules): Two schedules separated by
: - Type 2 (Output schedules): Two schedules separated by
: - Types separated by:
|
Cross-References
Related commands for schedule management:
- 0x0019 - Change Parameter of Input/Output: Set schedule assignments to I/O
Usage Example
javascript
async function readSchedule(partialUid, scheduleTypes) {
// Build read schedule command
const header = "070100000000000000";
const command = "1A00";
// Format schedule types
let scheduleTypesStr;
if (Array.isArray(scheduleTypes)) {
scheduleTypesStr = scheduleTypes.join('|');
} else {
scheduleTypesStr = scheduleTypes.toString();
}
const dataHex = asciiToHex(scheduleTypesStr);
const fullCommand = header + command + dataHex;
try {
console.log(`Reading schedules for types: ${scheduleTypesStr}`);
const result = await sendDeviceCommand(partialUid, fullCommand, true);
if (result.data && result.data.length > 0) {
const responseText = hexToAscii(result.data[0]);
if (responseText === 'NACK') {
console.log('Failed to read schedules');
return {
success: false,
message: 'Device returned NACK - command not supported or wrong data'
};
} else {
console.log('Schedules read successfully');
const parsedSchedules = parseScheduleData(responseText);
return {
success: true,
message: 'Schedules retrieved successfully',
requestedTypes: scheduleTypes,
rawData: responseText,
schedules: parsedSchedules
};
}
}
throw new Error('No response received from device');
} catch (error) {
console.error('Failed to read schedules:', error);
throw error;
}
}
// Parse schedule data response
function parseScheduleData(responseText) {
const schedules = {
userSchedules: [],
outputSchedules: []
};
// Split by pipe to separate different schedule types
const scheduleTypes = responseText.split('|');
scheduleTypes.forEach(scheduleTypeData => {
if (!scheduleTypeData.trim()) return;
// Split by colon to get individual schedules
const individualSchedules = scheduleTypeData.split(':');
individualSchedules.forEach(scheduleStr => {
if (!scheduleStr.trim()) return;
const parts = scheduleStr.split(',');
if (parts.length < 9) return; // Invalid schedule data
const schedule = {
type: parseInt(parts[0]),
id: parseInt(parts[1]),
enabled: parseInt(parts[2]) === 1,
startHour: parseInt(parts[parts.length >= 10 ? 4 : 3]), // Account for outType in type 2
startMin: parseInt(parts[parts.length >= 10 ? 5 : 4]),
startWeekDay: parseInt(parts[parts.length >= 10 ? 6 : 5]),
stopHour: parseInt(parts[parts.length >= 10 ? 7 : 6]),
stopMin: parseInt(parts[parts.length >= 10 ? 8 : 7]),
stopWeekDay: parseInt(parts[parts.length >= 10 ? 9 : 8])
};
// Add outType for type 2 schedules
if (parts.length >= 10) {
schedule.outType = parseInt(parts[3]);
schedule.outTypeDescription = schedule.outType === 0 ? 'pulse' : 'level';
}
// Add weekday descriptions
schedule.startWeekDays = parseWeekdays(schedule.startWeekDay);
schedule.stopWeekDays = parseWeekdays(schedule.stopWeekDay);
// Add formatted time strings
schedule.startTime = `${schedule.startHour.toString().padStart(2, '0')}:${schedule.startMin.toString().padStart(2, '0')}`;
schedule.stopTime = `${schedule.stopHour.toString().padStart(2, '0')}:${schedule.stopMin.toString().padStart(2, '0')}`;
// Add to appropriate array
if (schedule.type === 1) {
schedules.userSchedules.push(schedule);
} else if (schedule.type === 2) {
schedules.outputSchedules.push(schedule);
}
});
});
return schedules;
}
// Parse weekday bitmask
function parseWeekdays(bitmask) {
const days = [];
const dayNames = ['Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday', 'Sunday'];
for (let i = 0; i < 7; i++) {
if (bitmask & (1 << i)) {
days.push(dayNames[i]);
}
}
return days;
}
// Helper function to create weekday bitmask
function createWeekdayBitmask(days) {
const dayMap = {
'monday': 1, 'tuesday': 2, 'wednesday': 4, 'thursday': 8,
'friday': 16, 'saturday': 32, 'sunday': 64
};
let bitmask = 0;
days.forEach(day => {
const dayLower = day.toLowerCase();
if (dayMap[dayLower]) {
bitmask |= dayMap[dayLower];
}
});
return bitmask;
}
// Convenience functions
async function readUserSchedules(partialUid) {
return await readSchedule(partialUid, [1]);
}
async function readOutputSchedules(partialUid) {
return await readSchedule(partialUid, [2]);
}
async function readAllSchedules(partialUid) {
return await readSchedule(partialUid, [1, 2]);
}
// Helper function to format schedule for display
function formatScheduleDisplay(schedule) {
const typeStr = schedule.type === 1 ? 'User' : 'Output';
const outTypeStr = schedule.outType !== undefined ? ` (${schedule.outTypeDescription})` : '';
return {
...schedule,
description: `${typeStr} Schedule ${schedule.id}${outTypeStr}`,
timeRange: `${schedule.startTime} - ${schedule.stopTime}`,
activeWeekdays: {
start: schedule.startWeekDays.join(', '),
stop: schedule.stopWeekDays.join(', ')
},
status: schedule.enabled ? 'Enabled' : 'Disabled'
};
}
// Usage demonstration
async function demonstrateScheduleReading(partialUid) {
try {
console.log('=== Schedule Reading Demonstration ===');
// Read all schedules
console.log('\n--- Reading All Schedules ---');
const allSchedulesResult = await readAllSchedules(partialUid);
console.log('All schedules result:', allSchedulesResult);
if (allSchedulesResult.success) {
console.log('Raw schedule data:', allSchedulesResult.rawData);
// Display user schedules
if (allSchedulesResult.schedules.userSchedules.length > 0) {
console.log('\n--- User Schedules ---');
allSchedulesResult.schedules.userSchedules.forEach(schedule => {
const formatted = formatScheduleDisplay(schedule);
console.log(`${formatted.description}: ${formatted.timeRange} (${formatted.status})`);
console.log(` Start days: ${formatted.activeWeekdays.start}`);
console.log(` Stop days: ${formatted.activeWeekdays.stop}`);
});
}
// Display output schedules
if (allSchedulesResult.schedules.outputSchedules.length > 0) {
console.log('\n--- Output Schedules ---');
allSchedulesResult.schedules.outputSchedules.forEach(schedule => {
const formatted = formatScheduleDisplay(schedule);
console.log(`${formatted.description}: ${formatted.timeRange} (${formatted.status})`);
console.log(` Start days: ${formatted.activeWeekdays.start}`);
console.log(` Stop days: ${formatted.activeWeekdays.stop}`);
});
}
}
// Read only user schedules
console.log('\n--- Reading Only User Schedules ---');
const userSchedulesResult = await readUserSchedules(partialUid);
console.log('User schedules result:', userSchedulesResult);
// Read only output schedules
console.log('\n--- Reading Only Output Schedules ---');
const outputSchedulesResult = await readOutputSchedules(partialUid);
console.log('Output schedules result:', outputSchedulesResult);
console.log('Schedule reading demonstration completed');
} catch (error) {
console.error('Schedule reading demonstration failed:', error);
}
}0x001B - Write Schedule
Writes schedule configuration to device. Can write a single schedule or a complete list of schedules using the same format as the Read Schedule command.
Request/Response format
Request
[HEADER][0x001B][ScheduleData1:ScheduleDataN] → 0701000000000000001B00[ScheduleData]Schedule Data Format
Uses the same format as 0x001A - Read Schedule:
Type 1 (User Schedules):
type,id,enableFlags,startHour,startMin,startWeekDay,stopHour,stopMin,stopWeekDayType 2 (Output Schedules):
type,id,enableFlags,outType,startHour,startMin,startWeekDay,stopHour,stopMin,stopWeekDayRequest Examples
Write single user schedule:
[HEADER][0x001B][1,1,1,12,22,15,44,5]
→ 0701000000000000001B00312C312C312C31322C32322C31352C34342C35Write multiple schedules:
[HEADER][0x001B][1,1,1,12,22,15,44,5:2,1,1,0,14,45,19,22,8]
→ 0701000000000000001B00312C312C312C31322C32322C31352C34342C353A322C312C312C302C31342C34352C31392C32322C38Response Format
Success Response:
[HEADER][0x001B][OK]Error Response:
[HEADER][0x001B][NACK] // Command not supported, invalid schedule, or wrong dataSchedule Data Rules
Data Format:
- All values in decimal format
- Multiple schedules separated by colons (
:) - Fields separated by commas (
,) - Same format as returned by Read Schedule command
Schedule Types:
- Type 1: User schedules (9 fields)
- Type 2: Output schedules (10 fields with
outType)
Field Validation:
- Hours: 0-23 (24-hour format)
- Minutes: 0-59
- Weekdays: Bitmask (bit 0=Monday, bit 6=Sunday)
- Enable flags: 0 (disabled) or 1 (enabled)
- Output type: 0 (pulse) or 1 (level)
Write Operations
Single Schedule:
- Write individual schedule entry
- Overwrites existing schedule with same ID
Multiple Schedules:
- Write complete schedule list
- Can mix user and output schedules
- Separated by colon characters
Schedule Replacement:
- New schedules replace existing ones
- Schedule IDs determine which entries are updated
Cross-References
Related commands for schedule management:
- 0x001A - Read Schedule: Read schedule configuration
- 0x0019 - Change Parameter of Input/Output: Assign schedules to I/O
Usage Example
javascript
async function writeSchedule(partialUid, scheduleData) {
// Build write schedule command
const header = "070100000000000000";
const command = "1B00";
// Format schedule data
let scheduleDataStr;
if (Array.isArray(scheduleData)) {
// Convert schedule objects to string format
scheduleDataStr = scheduleData.map(schedule => formatScheduleForWrite(schedule)).join(':');
} else if (typeof scheduleData === 'string') {
scheduleDataStr = scheduleData;
} else if (typeof scheduleData === 'object') {
// Single schedule object
scheduleDataStr = formatScheduleForWrite(scheduleData);
} else {
throw new Error('Schedule data must be array of schedules, string, or schedule object');
}
const dataHex = asciiToHex(scheduleDataStr);
const fullCommand = header + command + dataHex;
try {
console.log(`Writing schedule data: ${scheduleDataStr}`);
const result = await sendDeviceCommand(partialUid, fullCommand, true);
if (result.data && result.data.length > 0) {
const responseText = hexToAscii(result.data[0]);
if (responseText === 'OK') {
console.log('Schedule written successfully');
return {
success: true,
message: 'Schedule configuration updated successfully',
scheduleData: scheduleDataStr
};
} else if (responseText === 'NACK') {
console.log('Failed to write schedule');
return {
success: false,
message: 'Device returned NACK - command not supported, invalid schedule, or wrong data'
};
} else {
return {
success: false,
message: `Unexpected response: ${responseText}`
};
}
}
throw new Error('No response received from device');
} catch (error) {
console.error('Failed to write schedule:', error);
throw error;
}
}
// Format schedule object for write command
function formatScheduleForWrite(schedule) {
if (schedule.type === 1) {
// User schedule: type,id,enableFlags,startHour,startMin,startWeekDay,stopHour,stopMin,stopWeekDay
return [
schedule.type,
schedule.id,
schedule.enabled ? 1 : 0,
schedule.startHour,
schedule.startMin,
schedule.startWeekDay,
schedule.stopHour,
schedule.stopMin,
schedule.stopWeekDay
].join(',');
} else if (schedule.type === 2) {
// Output schedule: type,id,enableFlags,outType,startHour,startMin,startWeekDay,stopHour,stopMin,stopWeekDay
return [
schedule.type,
schedule.id,
schedule.enabled ? 1 : 0,
schedule.outType,
schedule.startHour,
schedule.startMin,
schedule.startWeekDay,
schedule.stopHour,
schedule.stopMin,
schedule.stopWeekDay
].join(',');
} else {
throw new Error('Invalid schedule type. Must be 1 (user) or 2 (output)');
}
}
// Helper function to create weekday bitmask from array of day names
function createWeekdayBitmask(days) {
const dayMap = {
'monday': 1, 'tuesday': 2, 'wednesday': 4, 'thursday': 8,
'friday': 16, 'saturday': 32, 'sunday': 64
};
let bitmask = 0;
days.forEach(day => {
const dayLower = day.toLowerCase();
if (dayMap[dayLower]) {
bitmask |= dayMap[dayLower];
}
});
return bitmask;
}
// Validation functions
function validateScheduleTime(hour, minute) {
return hour >= 0 && hour <= 23 && minute >= 0 && minute <= 59;
}
function validateWeekdayBitmask(bitmask) {
return bitmask >= 0 && bitmask <= 127; // 7 bits maximum
}
function validateSchedule(schedule) {
const errors = [];
if (![1, 2].includes(schedule.type)) {
errors.push('Invalid schedule type. Must be 1 (user) or 2 (output)');
}
if (!Number.isInteger(schedule.id) || schedule.id < 0) {
errors.push('Invalid schedule ID. Must be non-negative integer');
}
if (!validateScheduleTime(schedule.startHour, schedule.startMin)) {
errors.push('Invalid start time. Hour must be 0-23, minute must be 0-59');
}
if (!validateScheduleTime(schedule.stopHour, schedule.stopMin)) {
errors.push('Invalid stop time. Hour must be 0-23, minute must be 0-59');
}
if (!validateWeekdayBitmask(schedule.startWeekDay)) {
errors.push('Invalid start weekday bitmask. Must be 0-127');
}
if (!validateWeekdayBitmask(schedule.stopWeekDay)) {
errors.push('Invalid stop weekday bitmask. Must be 0-127');
}
if (schedule.type === 2 && ![0, 1].includes(schedule.outType)) {
errors.push('Invalid output type for output schedule. Must be 0 (pulse) or 1 (level)');
}
return {
valid: errors.length === 0,
errors: errors
};
}
// Safe wrapper with validation
async function writeScheduleSafe(partialUid, scheduleData) {
let schedulesToValidate = Array.isArray(scheduleData) ? scheduleData : [scheduleData];
// Skip validation for string input
if (typeof scheduleData !== 'string') {
for (const schedule of schedulesToValidate) {
const validation = validateSchedule(schedule);
if (!validation.valid) {
throw new Error(`Invalid schedule: ${validation.errors.join(', ')}`);
}
}
}
return await writeSchedule(partialUid, scheduleData);
}
// Convenience functions for common scenarios
async function writeUserSchedule(partialUid, id, enabled, startTime, stopTime, startDays, stopDays) {
const schedule = {
type: 1,
id: id,
enabled: enabled,
startHour: startTime.hour,
startMin: startTime.minute,
startWeekDay: Array.isArray(startDays) ? createWeekdayBitmask(startDays) : startDays,
stopHour: stopTime.hour,
stopMin: stopTime.minute,
stopWeekDay: Array.isArray(stopDays) ? createWeekdayBitmask(stopDays) : stopDays
};
return await writeScheduleSafe(partialUid, schedule);
}
async function writeOutputSchedule(partialUid, id, enabled, outType, startTime, stopTime, startDays, stopDays) {
const schedule = {
type: 2,
id: id,
enabled: enabled,
outType: outType,
startHour: startTime.hour,
startMin: startTime.minute,
startWeekDay: Array.isArray(startDays) ? createWeekdayBitmask(startDays) : startDays,
stopHour: stopTime.hour,
stopMin: stopTime.minute,
stopWeekDay: Array.isArray(stopDays) ? createWeekdayBitmask(stopDays) : stopDays
};
return await writeScheduleSafe(partialUid, schedule);
}
async function writeMultipleSchedules(partialUid, schedules) {
return await writeScheduleSafe(partialUid, schedules);
}
// Usage demonstration
async function demonstrateScheduleWriting(partialUid) {
try {
console.log('=== Schedule Writing Demonstration ===');
// Write single user schedule
console.log('\n--- Writing User Schedule ---');
const userScheduleResult = await writeUserSchedule(
partialUid,
1, // ID
true, // Enabled
{ hour: 12, minute: 22 }, // Start time
{ hour: 14, minute: 45 }, // Stop time
['monday', 'tuesday', 'wednesday'], // Start days
['friday'] // Stop days
);
console.log('User schedule result:', userScheduleResult);
// Write single output schedule
console.log('\n--- Writing Output Schedule ---');
const outputScheduleResult = await writeOutputSchedule(
partialUid,
1, // ID
true, // Enabled
0, // Pulse output
{ hour: 8, minute: 0 }, // Start time
{ hour: 18, minute: 0 }, // Stop time
['monday', 'tuesday', 'wednesday', 'thursday', 'friday'], // Weekdays
['monday', 'tuesday', 'wednesday', 'thursday', 'friday'] // Weekdays
);
console.log('Output schedule result:', outputScheduleResult);
// Write multiple schedules
console.log('\n--- Writing Multiple Schedules ---');
const multipleSchedules = [
{
type: 1,
id: 2,
enabled: true,
startHour: 9,
startMin: 0,
startWeekDay: createWeekdayBitmask(['monday', 'wednesday', 'friday']),
stopHour: 17,
stopMin: 0,
stopWeekDay: createWeekdayBitmask(['monday', 'wednesday', 'friday'])
},
{
type: 2,
id: 2,
enabled: true,
outType: 1, // Level output
startHour: 20,
startMin: 0,
startWeekDay: createWeekdayBitmask(['saturday', 'sunday']),
stopHour: 22,
stopMin: 30,
stopWeekDay: createWeekdayBitmask(['saturday', 'sunday'])
}
];
const multipleResult = await writeMultipleSchedules(partialUid, multipleSchedules);
console.log('Multiple schedules result:', multipleResult);
// Write raw string format
console.log('\n--- Writing Raw Schedule String ---');
const rawScheduleString = '1,3,1,6,30,31,18,0,31'; // User schedule: 06:30-18:00 Mon-Fri
const rawResult = await writeSchedule(partialUid, rawScheduleString);
console.log('Raw string result:', rawResult);
// Test validation
console.log('\n--- Testing Invalid Schedule ---');
try {
await writeScheduleSafe(partialUid, {
type: 1,
id: 1,
enabled: true,
startHour: 25, // Invalid hour
startMin: 0,
startWeekDay: 1,
stopHour: 18,
stopMin: 0,
stopWeekDay: 1
});
} catch (error) {
console.log('Expected validation error:', error.message);
}
console.log('Schedule writing demonstration completed');
} catch (error) {
console.error('Schedule writing demonstration failed:', error);
}
}0x001C - Get Checksums
Requests or validates checksums for different data types. Can be used to check data integrity and detect changes in user lists, schedules, and other configuration data. Supports both checksum queries and validation operations.
Request/Response format
Request Format - Query Checksums
[HEADER][0x001C][Type1|Type2|...] → 0701000000000000001C00[TypeList]Request Format - Validate Checksums
[HEADER][0x001C][Type1:CheckSum|Type2:CheckSum|...] → 0701000000000000001C00[TypeChecksumList]Request Examples
Query single checksum:
[HEADER][0x001C][1] // Query user list checksum
→ 0701000000000000001C0031Query multiple checksums:
[HEADER][0x001C][1|2] // Query user list and schedule checksums
→ 0701000000000000001C00317C32Validate checksums:
[HEADER][0x001C][1:A5F6|2:54BB] // Validate user list and schedule checksums
→ 0701000000000000001C00313A413546367C323A35344242Response Format
Checksum Response:
[HEADER][0x001C][Type1:CheckSum|TypeX:CheckSum]Data Update Response (when checksums don't match):
[HEADER][0x00XX][DataBasedOnCommandID] // Returns updated data for mismatched checksumsError Response:
[HEADER][0x001C][NACK] // Command not supported, invalid type, or wrong dataResponse Examples
Successful checksum query:
← [HEADER][0x001C][1:A5F6|2:54BB]
← 0701000000000000001C00313A413546367C323A35344242Data update needed (mismatched checksums):
← [HEADER][0x0018][UserData...] // Returns updated user data if type 1 checksum mismatch
← [HEADER][0x001A][ScheduleData...] // Returns updated schedule data if type 2/3 checksum mismatchChecksum Types
Available Data Types:
- Type 1: User list (GV17) - Checksum of all user configuration data
- Type 2: Schedule - GV17 Users schedules
- Type 3: Schedule - GV17 Outputs schedules
Checksum Format:
- 4 ASCII characters representing 2-byte hex value
- Based on TRK CRC16 calculation algorithm
- Example:
A5F6,54BB,12CD
Type Combinations:
- Multiple types can be requested in single command
- Device will not combine different schedule types inappropriately
- Valid combinations: schedule output + schedule users
- Invalid combinations: users + schedule users (same data type)
Operation Modes
Query Mode:
- Request current checksums for specified types
- Device returns current checksum values
- Used to check if local data is up-to-date
Validation Mode:
- Send known checksums for validation
- Device compares with current checksums
- If mismatch detected, device sends updated data via appropriate command
Data Synchronization:
- Mismatched Type 1: Device responds with [0x0018] user data
- Mismatched Type 2/3: Device responds with [0x001A] schedule data
- Matched checksums: Device confirms with checksum response
Cross-References
Related commands for data management:
- 0x0018 - Get User Information: User data retrieval when Type 1 checksum mismatch
- 0x001A - Read Schedule: Schedule data retrieval when Type 2/3 checksum mismatch
- 0x001B - Write Schedule: Update schedules after checksum validation
Usage Example
javascript
async function getChecksums(partialUid, types) {
// Build get checksums command
const header = "070100000000000000";
const command = "1C00";
// Format types list
let typesStr;
if (Array.isArray(types)) {
typesStr = types.join('|');
} else if (typeof types === 'string') {
typesStr = types;
} else {
typesStr = types.toString();
}
const dataHex = asciiToHex(typesStr);
const fullCommand = header + command + dataHex;
try {
console.log(`Requesting checksums for types: ${typesStr}`);
const result = await sendDeviceCommand(partialUid, fullCommand, true);
if (result.data && result.data.length > 0) {
const responseText = hexToAscii(result.data[0]);
if (responseText === 'NACK') {
return {
success: false,
message: 'Device returned NACK - command not supported, invalid type, or wrong data'
};
}
// Parse checksums response
const checksums = parseChecksumResponse(responseText);
console.log('Received checksums:', checksums);
return {
success: true,
checksums: checksums,
raw: responseText
};
}
throw new Error('No response received from device');
} catch (error) {
console.error('Failed to get checksums:', error);
throw error;
}
}
async function validateChecksums(partialUid, checksums) {
// Build validate checksums command
const header = "070100000000000000";
const command = "1C00";
// Format checksums for validation
let checksumStr;
if (typeof checksums === 'object' && !Array.isArray(checksums)) {
// Convert object to string format
checksumStr = Object.entries(checksums)
.map(([type, checksum]) => `${type}:${checksum}`)
.join('|');
} else if (typeof checksums === 'string') {
checksumStr = checksums;
} else {
throw new Error('Checksums must be object with type:checksum pairs or string');
}
const dataHex = asciiToHex(checksumStr);
const fullCommand = header + command + dataHex;
try {
console.log(`Validating checksums: ${checksumStr}`);
const result = await sendDeviceCommand(partialUid, fullCommand, true);
if (result.data && result.data.length > 0) {
const responseText = hexToAscii(result.data[0]);
if (responseText === 'NACK') {
return {
success: false,
message: 'Device returned NACK - command not supported, invalid type, or wrong data'
};
}
// Check if response is checksum confirmation or data update
if (responseText.includes(':')) {
// Checksums match - device confirmed
const updatedChecksums = parseChecksumResponse(responseText);
console.log('Checksums validated successfully:', updatedChecksums);
return {
success: true,
validated: true,
checksums: updatedChecksums,
message: 'All checksums match - data is up to date'
};
} else {
// Data mismatch detected - device will send updated data
console.log('Checksum mismatch detected - expecting data update');
return {
success: true,
validated: false,
message: 'Checksum mismatch detected - device will send updated data',
mismatchResponse: responseText
};
}
}
throw new Error('No response received from device');
} catch (error) {
console.error('Failed to validate checksums:', error);
throw error;
}
}
// Parse checksum response format: "Type1:CheckSum|TypeX:CheckSum"
function parseChecksumResponse(responseText) {
const checksums = {};
if (!responseText || responseText === 'NACK') {
return checksums;
}
// Split by pipe separator
const pairs = responseText.split('|');
pairs.forEach(pair => {
const colonIndex = pair.indexOf(':');
if (colonIndex > 0) {
const type = pair.substring(0, colonIndex);
const checksum = pair.substring(colonIndex + 1);
checksums[type] = checksum;
}
});
return checksums;
}
// Get checksum type description
function getChecksumTypeDescription(type) {
const descriptions = {
'1': 'User list (GV17)',
'2': 'Schedule - GV17 Users',
'3': 'Schedule - GV17 Outputs'
};
return descriptions[type] || `Unknown type ${type}`;
}
// Calculate TRK CRC16 checksum (placeholder - actual implementation needed)
function calculateTrkCrc16(data) {
// This is a placeholder for the actual TRK CRC16 algorithm
// Implementation would depend on the specific CRC16 variant used by TRK
console.warn('TRK CRC16 calculation not implemented - placeholder function');
return '0000';
}
// Validate checksum format
function isValidChecksumFormat(checksum) {
return /^[0-9A-Fa-f]{4}$/.test(checksum);
}
// Convenience functions
async function getUserListChecksum(partialUid) {
const result = await getChecksums(partialUid, [1]);
return result.success ? result.checksums['1'] : null;
}
async function getScheduleChecksums(partialUid) {
const result = await getChecksums(partialUid, [2, 3]);
if (result.success) {
return {
userSchedules: result.checksums['2'],
outputSchedules: result.checksums['3']
};
}
return null;
}
async function getAllChecksums(partialUid) {
return await getChecksums(partialUid, [1, 2, 3]);
}
async function validateAllData(partialUid, localChecksums) {
return await validateChecksums(partialUid, localChecksums);
}
// Data synchronization workflow
async function syncDataWithDevice(partialUid, localChecksums) {
try {
console.log('=== Data Synchronization Workflow ===');
// Step 1: Validate current checksums
console.log('\n--- Validating Local Checksums ---');
const validationResult = await validateChecksums(partialUid, localChecksums);
if (validationResult.validated) {
console.log('All data is up to date - no sync needed');
return {
synchronized: true,
message: 'All data is current',
checksums: validationResult.checksums
};
}
// Step 2: Get current checksums to identify what changed
console.log('\n--- Getting Current Device Checksums ---');
const currentResult = await getAllChecksums(partialUid);
if (!currentResult.success) {
throw new Error('Failed to get current checksums from device');
}
// Step 3: Identify changed data types
const changedTypes = [];
Object.entries(localChecksums).forEach(([type, localChecksum]) => {
const deviceChecksum = currentResult.checksums[type];
if (deviceChecksum && deviceChecksum !== localChecksum) {
changedTypes.push({
type: type,
description: getChecksumTypeDescription(type),
local: localChecksum,
device: deviceChecksum
});
}
});
console.log('Changed data types:', changedTypes);
// Step 4: Request updated data for changed types
const updatedData = {};
for (const change of changedTypes) {
console.log(`\n--- Updating ${change.description} ---`);
if (change.type === '1') {
// User data changed - would call getUserInformation
console.log('User data needs update - call getUserInformation()');
updatedData.users = 'Updated user data would be retrieved here';
} else if (change.type === '2' || change.type === '3') {
// Schedule data changed - would call readSchedule
console.log('Schedule data needs update - call readSchedule()');
updatedData.schedules = 'Updated schedule data would be retrieved here';
}
}
return {
synchronized: false,
changedTypes: changedTypes,
updatedData: updatedData,
newChecksums: currentResult.checksums,
message: `Synchronized ${changedTypes.length} data types`
};
} catch (error) {
console.error('Data synchronization failed:', error);
throw error;
}
}
// Usage demonstration
async function demonstrateChecksumOperations(partialUid) {
try {
console.log('=== Checksum Operations Demonstration ===');
// Get all checksums
console.log('\n--- Getting All Checksums ---');
const allChecksums = await getAllChecksums(partialUid);
console.log('All checksums result:', allChecksums);
// Get specific checksums
console.log('\n--- Getting User List Checksum ---');
const userChecksum = await getUserListChecksum(partialUid);
console.log('User checksum:', userChecksum);
console.log('\n--- Getting Schedule Checksums ---');
const scheduleChecksums = await getScheduleChecksums(partialUid);
console.log('Schedule checksums:', scheduleChecksums);
// Validate existing checksums
console.log('\n--- Validating Checksums ---');
if (allChecksums.success && Object.keys(allChecksums.checksums).length > 0) {
const validationResult = await validateChecksums(partialUid, allChecksums.checksums);
console.log('Validation result:', validationResult);
}
// Test with mock outdated checksums
console.log('\n--- Testing Outdated Checksums ---');
const mockOutdatedChecksums = {
'1': '0000', // Fake outdated user checksum
'2': '1111' // Fake outdated schedule checksum
};
const outdatedValidation = await validateChecksums(partialUid, mockOutdatedChecksums);
console.log('Outdated validation result:', outdatedValidation);
// Demonstrate synchronization workflow
console.log('\n--- Data Synchronization Demo ---');
if (allChecksums.success) {
const syncResult = await syncDataWithDevice(partialUid, allChecksums.checksums);
console.log('Synchronization result:', syncResult);
}
// Test checksum format validation
console.log('\n--- Testing Checksum Format Validation ---');
console.log('Valid checksum A5F6:', isValidChecksumFormat('A5F6'));
console.log('Valid checksum 54bb:', isValidChecksumFormat('54bb'));
console.log('Invalid checksum XYZ:', isValidChecksumFormat('XYZ'));
console.log('Invalid checksum 12345:', isValidChecksumFormat('12345'));
console.log('Checksum operations demonstration completed');
} catch (error) {
console.error('Checksum operations demonstration failed:', error);
}
}0x001D - Time Zone Change
Changes the device's time zone offset setting. Allows configuration of timezone offsets in minutes from UTC for proper local time handling and schedule management.
Request/Response format
Request Format
[HEADER][0x001D][ZO:±offset] → 0701000000000000001D00[ZoneOffsetData]Request Examples
Set positive timezone (UTC+12 hours):
[HEADER][0x001D][ZO:+720] // +720 minutes = +12 hours
→ 0701000000000000001D005A4F3A2B373230Set negative timezone (UTC-1 hour):
[HEADER][0x001D][ZO:-60] // -60 minutes = -1 hour
→ 0701000000000000001D005A4F3A2D3630Set positive timezone (UTC+2 hours):
[HEADER][0x001D][ZO:+120] // +120 minutes = +2 hours
→ 0701000000000000001D005A4F3A2B313230Response Format
Success Response:
[HEADER][0x001D][OK]Error Response:
[HEADER][0x001D][NACK] // Command not supported, invalid timezone, or wrong dataTimezone Offset Format
Data Format:
- Prefix:
ZO:(Zone Offset identifier) - Sign:
+for positive offset (east of UTC),-for negative offset (west of UTC) - Value: Offset in minutes from UTC
- Range: Typically -720 to +840 minutes (-12 to +14 hours)
Common Timezone Examples:
- UTC+0:
ZO:+0orZO:-0 - UTC+1 (CET):
ZO:+60 - UTC-5 (EST):
ZO:-300 - UTC-8 (PST):
ZO:-480 - UTC+9 (JST):
ZO:+540 - UTC+12 (NZST):
ZO:+720
Timezone Validation
Valid Ranges:
- Standard Range: -720 to +720 minutes (-12 to +12 hours)
- Extended Range: -840 to +840 minutes (-14 to +14 hours)
- Quarter Hours: Some timezones use 15, 30, 45 minute offsets
Format Requirements:
- Must include
ZO:prefix - Must include sign (
+or-) - Value must be integer minutes
- No spaces in the format string
Time Impact
System Effects:
- Local Time: Changes how device interprets and displays local time
- Schedules: Affects schedule execution relative to local time
- Logging: Timestamps in logs adjusted to new timezone
- Real-time Clock: System time reference updated
Schedule Considerations:
- Existing schedules may execute at different local times
- Schedule times are typically stored in local time format
- Consider schedule adjustments when changing timezones
Cross-References
Related commands for time management:
- 0x0007 - Change Date and Time: Set device date and time
- 0x001A - Read Schedule: View schedule configurations affected by timezone
- 0x001B - Write Schedule: Update schedules for new timezone
Usage Example
javascript
async function changeTimezone(partialUid, offsetMinutes) {
// Build timezone change command
const header = "070100000000000000";
const command = "1D00";
// Format timezone offset
const sign = offsetMinutes >= 0 ? '+' : '-';
const absOffset = Math.abs(offsetMinutes);
const timezoneStr = `ZO:${sign}${absOffset}`;
const dataHex = asciiToHex(timezoneStr);
const fullCommand = header + command + dataHex;
try {
console.log(`Setting timezone offset to ${offsetMinutes} minutes (${formatTimezoneOffset(offsetMinutes)})`);
const result = await sendDeviceCommand(partialUid, fullCommand, true);
if (result.data && result.data.length > 0) {
const responseText = hexToAscii(result.data[0]);
if (responseText === 'OK') {
console.log('Timezone changed successfully');
return {
success: true,
message: `Timezone set to ${formatTimezoneOffset(offsetMinutes)}`,
offsetMinutes: offsetMinutes,
timezoneString: timezoneStr
};
} else if (responseText === 'NACK') {
console.log('Failed to change timezone');
return {
success: false,
message: 'Device returned NACK - command not supported, invalid timezone, or wrong data'
};
} else {
return {
success: false,
message: `Unexpected response: ${responseText}`
};
}
}
throw new Error('No response received from device');
} catch (error) {
console.error('Failed to change timezone:', error);
throw error;
}
}
// Format timezone offset for display
function formatTimezoneOffset(offsetMinutes) {
const sign = offsetMinutes >= 0 ? '+' : '-';
const absOffset = Math.abs(offsetMinutes);
const hours = Math.floor(absOffset / 60);
const minutes = absOffset % 60;
if (minutes === 0) {
return `UTC${sign}${hours}`;
} else {
return `UTC${sign}${hours}:${minutes.toString().padStart(2, '0')}`;
}
}
// Parse timezone offset from string
function parseTimezoneOffset(timezoneString) {
// Handle formats like "UTC+5", "UTC-8:30", "+0300", "-0500"
const match = timezoneString.match(/([+-]?)(\d{1,2})(?::?(\d{2}))?/);
if (!match) {
throw new Error('Invalid timezone format');
}
const sign = match[1] === '-' ? -1 : 1;
const hours = parseInt(match[2], 10);
const minutes = match[3] ? parseInt(match[3], 10) : 0;
return sign * (hours * 60 + minutes);
}
// Validate timezone offset
function validateTimezoneOffset(offsetMinutes) {
// Standard timezone range: -12 to +14 hours
const minOffset = -12 * 60; // -720 minutes
const maxOffset = 14 * 60; // +840 minutes
if (offsetMinutes < minOffset || offsetMinutes > maxOffset) {
return {
valid: false,
error: `Timezone offset ${offsetMinutes} minutes is outside valid range (${minOffset} to ${maxOffset} minutes)`
};
}
return { valid: true };
}
// Common timezone presets
const COMMON_TIMEZONES = {
'UTC': 0,
'GMT': 0,
'CET': 60, // Central European Time
'EET': 120, // Eastern European Time
'MSK': 180, // Moscow Time
'GST': 240, // Gulf Standard Time
'IST': 330, // India Standard Time
'JST': 540, // Japan Standard Time
'AEST': 600, // Australian Eastern Standard Time
'NZST': 720, // New Zealand Standard Time
'EST': -300, // Eastern Standard Time
'CST': -360, // Central Standard Time
'MST': -420, // Mountain Standard Time
'PST': -480, // Pacific Standard Time
'AKST': -540, // Alaska Standard Time
'HST': -600 // Hawaii Standard Time
};
// Get timezone offset by name
function getTimezoneByName(timezoneName) {
const offset = COMMON_TIMEZONES[timezoneName.toUpperCase()];
if (offset === undefined) {
throw new Error(`Unknown timezone: ${timezoneName}`);
}
return offset;
}
// List available timezone presets
function listAvailableTimezones() {
return Object.entries(COMMON_TIMEZONES).map(([name, offset]) => ({
name: name,
offset: offset,
display: `${name} (${formatTimezoneOffset(offset)})`
}));
}
// Convenience functions
async function setTimezoneByName(partialUid, timezoneName) {
const offset = getTimezoneByName(timezoneName);
return await changeTimezone(partialUid, offset);
}
async function setTimezoneByString(partialUid, timezoneString) {
const offset = parseTimezoneOffset(timezoneString);
return await changeTimezone(partialUid, offset);
}
async function setUTC(partialUid) {
return await changeTimezone(partialUid, 0);
}
// Safe wrapper with validation
async function changeTimezoneSafe(partialUid, offsetMinutes) {
const validation = validateTimezoneOffset(offsetMinutes);
if (!validation.valid) {
throw new Error(validation.error);
}
return await changeTimezone(partialUid, offsetMinutes);
}
// Get current timezone equivalent for common locations
function suggestTimezoneForLocation(location) {
const suggestions = {
'new york': 'EST',
'los angeles': 'PST',
'london': 'GMT',
'paris': 'CET',
'berlin': 'CET',
'moscow': 'MSK',
'dubai': 'GST',
'mumbai': 'IST',
'delhi': 'IST',
'tokyo': 'JST',
'sydney': 'AEST',
'auckland': 'NZST'
};
const locationLower = location.toLowerCase();
const timezone = suggestions[locationLower];
if (timezone) {
return {
timezone: timezone,
offset: COMMON_TIMEZONES[timezone],
display: formatTimezoneOffset(COMMON_TIMEZONES[timezone])
};
}
return null;
}
// Calculate timezone difference
function calculateTimezoneDifference(offset1, offset2) {
const diffMinutes = offset2 - offset1;
return {
minutes: diffMinutes,
hours: diffMinutes / 60,
display: formatTimezoneOffset(Math.abs(diffMinutes)),
direction: diffMinutes > 0 ? 'ahead' : 'behind'
};
}
// Usage demonstration
async function demonstrateTimezoneOperations(partialUid) {
try {
console.log('=== Timezone Operations Demonstration ===');
// Set timezone by offset
console.log('\n--- Setting Timezone by Offset ---');
const offsetResult = await changeTimezoneSafe(partialUid, 60); // UTC+1
console.log('Offset result:', offsetResult);
// Set timezone by name
console.log('\n--- Setting Timezone by Name ---');
const nameResult = await setTimezoneByName(partialUid, 'JST'); // Japan Standard Time
console.log('Name result:', nameResult);
// Set timezone by string
console.log('\n--- Setting Timezone by String ---');
const stringResult = await setTimezoneByString(partialUid, 'UTC-5:30');
console.log('String result:', stringResult);
// Reset to UTC
console.log('\n--- Resetting to UTC ---');
const utcResult = await setUTC(partialUid);
console.log('UTC result:', utcResult);
// List available timezones
console.log('\n--- Available Timezone Presets ---');
const timezones = listAvailableTimezones();
timezones.forEach(tz => {
console.log(` ${tz.display}`);
});
// Location suggestions
console.log('\n--- Location-based Suggestions ---');
const locations = ['new york', 'london', 'tokyo', 'sydney'];
locations.forEach(location => {
const suggestion = suggestTimezoneForLocation(location);
if (suggestion) {
console.log(` ${location}: ${suggestion.timezone} (${suggestion.display})`);
}
});
// Timezone difference calculation
console.log('\n--- Timezone Differences ---');
const diff1 = calculateTimezoneDifference(0, 540); // UTC to JST
console.log(`UTC to JST: ${diff1.hours} hours ${diff1.direction}`);
const diff2 = calculateTimezoneDifference(-480, 60); // PST to CET
console.log(`PST to CET: ${diff2.hours} hours ${diff2.direction}`);
// Test validation
console.log('\n--- Testing Invalid Timezone ---');
try {
await changeTimezoneSafe(partialUid, 1000); // Invalid: too large
} catch (error) {
console.log('Expected validation error:', error.message);
}
// Test parsing
console.log('\n--- Testing Timezone Parsing ---');
const parseTests = ['UTC+5', 'UTC-8:30', '+0300', '-0500'];
parseTests.forEach(test => {
try {
const parsed = parseTimezoneOffset(test);
console.log(` ${test} → ${parsed} minutes (${formatTimezoneOffset(parsed)})`);
} catch (error) {
console.log(` ${test} → Error: ${error.message}`);
}
});
console.log('Timezone operations demonstration completed');
} catch (error) {
console.error('Timezone operations demonstration failed:', error);
}
}0x001E - Set User Working Mode
Sets the working state for a specific user, marking them as working or out of work. Requires authorization with datatype 3 (user email authentication). Device responds with status confirmation and may optionally send CID events for work start/stop.
Request/Response format
Request Format
[HEADER][0x001E][UserID:State] → 0701000000000000001E00[UserWorkingData]Request Examples
Set user 25 to working state:
[HEADER][0x001E][25:1] // User ID 25, state 1 (working)
→ 0701000000000000001E00 32353A31Set user 150 to out of work:
[HEADER][0x001E][150:0] // User ID 150, state 0 (out of work)
→ 0701000000000000001E00 3135303A30Set user 999 to working:
[HEADER][0x001E][999:1] // User ID 999, state 1 (working)
→ 0701000000000000001E00 3939393A31Response Format
Success Response:
[HEADER][0x001E][OK]Error Response:
[HEADER][0x001E][NACK] // Command not supported, invalid user ID, wrong state, or authorization failedOptional CID Events
Device may send Contact ID (CID) events after successful state change:
Work Start Event:
[CID_EVENT] - User started work sessionWork Stop Event:
[CID_EVENT] - User ended work sessionAuthorization Requirements
Authentication Type:
- Datatype 3: User email authentication required
- Authorization Method: Must authenticate with existing user email
- Security Level: User-level access control
Access Control:
- Only authorized users can change working states
- User email must exist in device user database
- Authentication must be valid before command execution
User ID and State Format
User ID Range:
- Valid Range: 11-999
- Format: String representation of numeric ID
- Examples: "25", "150", "999"
Working State Values:
- State 1: User is working (start work session)
- State 0: User is out of work (end work session)
- Format: Single digit (0 or 1)
Data Format:
- Pattern:
UserID:State - Separator: Colon (
:) - Example:
25:1,150:0,999:1
Working Mode Effects
System Behavior:
- Work Tracking: Device tracks user work sessions
- Event Logging: Work start/stop events logged
- Access Control: May affect user access permissions
- Reporting: Work hours and attendance tracking
CID Integration:
- Optional Events: Device may generate CID events
- Work Start: CID event when user starts work
- Work Stop: CID event when user stops work
- Event Details: Include user ID and timestamp
Cross-References
Related commands for user management:
- 0x0018 - Get User Information: View user configuration and details
- 0x0008 - Authorization: Required authentication for this command
- 0x000E - Get Logs: View work session and CID event logs
Usage Example
javascript
async function setUserWorkingMode(partialUid, userId, workingState, userEmail) {
// Validate parameters
if (!isValidUserId(userId)) {
throw new Error('Invalid user ID. Must be between 11 and 999');
}
if (!isValidWorkingState(workingState)) {
throw new Error('Invalid working state. Must be 0 (out of work) or 1 (working)');
}
if (!userEmail) {
throw new Error('User email required for authorization');
}
try {
// Step 1: Authenticate with user email (datatype 3)
console.log(`Authenticating user email: ${userEmail}`);
const authResult = await authenticateUser(partialUid, userEmail, 3);
if (!authResult.success) {
throw new Error('Authentication failed: ' + authResult.message);
}
// Step 2: Build set working mode command
const header = "070100000000000000";
const command = "1E00";
const workingData = `${userId}:${workingState}`;
const dataHex = asciiToHex(workingData);
const fullCommand = header + command + dataHex;
console.log(`Setting user ${userId} working state to ${workingState ? 'working' : 'out of work'}`);
const result = await sendDeviceCommand(partialUid, fullCommand, true);
if (result.data && result.data.length > 0) {
const responseText = hexToAscii(result.data[0]);
if (responseText === 'OK') {
const stateDescription = workingState ? 'started work' : 'stopped work';
console.log(`User ${userId} ${stateDescription} successfully`);
return {
success: true,
message: `User ${userId} ${stateDescription}`,
userId: userId,
workingState: workingState,
stateDescription: stateDescription
};
} else if (responseText === 'NACK') {
console.log('Failed to set user working mode');
return {
success: false,
message: 'Device returned NACK - command not supported, invalid user ID, wrong state, or authorization failed'
};
} else {
return {
success: false,
message: `Unexpected response: ${responseText}`
};
}
}
throw new Error('No response received from device');
} catch (error) {
console.error('Failed to set user working mode:', error);
throw error;
}
}
// Validation functions
function isValidUserId(userId) {
const numericId = parseInt(userId, 10);
return !isNaN(numericId) && numericId >= 11 && numericId <= 999;
}
function isValidWorkingState(state) {
return state === 0 || state === 1 || state === '0' || state === '1';
}
function normalizeWorkingState(state) {
if (state === '0' || state === 0) return 0;
if (state === '1' || state === 1) return 1;
throw new Error('Invalid working state');
}
function normalizeUserId(userId) {
const numericId = parseInt(userId, 10);
if (isNaN(numericId)) {
throw new Error('User ID must be numeric');
}
return numericId.toString();
}
// Authentication wrapper (references 0x0008 command)
async function authenticateUser(partialUid, userEmail, datatype) {
// This would use the 0x0008 Authorization command
// Implementation depends on the authorization command structure
console.log(`Performing datatype ${datatype} authentication for ${userEmail}`);
// Placeholder implementation - actual authentication would use 0x0008
return {
success: true,
message: 'Authentication successful',
userEmail: userEmail,
datatype: datatype
};
}
// Convenience functions
async function startUserWork(partialUid, userId, userEmail) {
return await setUserWorkingMode(partialUid, userId, 1, userEmail);
}
async function stopUserWork(partialUid, userId, userEmail) {
return await setUserWorkingMode(partialUid, userId, 0, userEmail);
}
async function toggleUserWorkingMode(partialUid, userId, userEmail, currentState) {
const newState = currentState ? 0 : 1;
return await setUserWorkingMode(partialUid, userId, newState, userEmail);
}
// Safe wrapper with validation
async function setUserWorkingModeSafe(partialUid, userId, workingState, userEmail) {
try {
const normalizedUserId = normalizeUserId(userId);
const normalizedState = normalizeWorkingState(workingState);
return await setUserWorkingMode(partialUid, normalizedUserId, normalizedState, userEmail);
} catch (error) {
console.error('Validation error:', error.message);
throw error;
}
}
// Batch operations
async function setMultipleUsersWorkingMode(partialUid, userUpdates, userEmail) {
const results = [];
for (const update of userUpdates) {
try {
const result = await setUserWorkingModeSafe(
partialUid,
update.userId,
update.workingState,
userEmail
);
results.push({ ...update, result });
// Small delay to avoid overwhelming the device
await new Promise(resolve => setTimeout(resolve, 100));
} catch (error) {
results.push({
...update,
result: { success: false, error: error.message }
});
}
}
return results;
}
// Work session management
class WorkSessionManager {
constructor(partialUid, userEmail) {
this.partialUid = partialUid;
this.userEmail = userEmail;
this.activeSessions = new Map();
}
async startWorkSession(userId) {
try {
const result = await startUserWork(this.partialUid, userId, this.userEmail);
if (result.success) {
this.activeSessions.set(userId, {
startTime: new Date(),
userId: userId
});
}
return result;
} catch (error) {
console.error(`Failed to start work session for user ${userId}:`, error);
throw error;
}
}
async stopWorkSession(userId) {
try {
const result = await stopUserWork(this.partialUid, userId, this.userEmail);
if (result.success) {
const session = this.activeSessions.get(userId);
if (session) {
session.endTime = new Date();
session.duration = session.endTime - session.startTime;
}
this.activeSessions.delete(userId);
}
return result;
} catch (error) {
console.error(`Failed to stop work session for user ${userId}:`, error);
throw error;
}
}
getActiveSession(userId) {
return this.activeSessions.get(userId);
}
getAllActiveSessions() {
return Array.from(this.activeSessions.entries()).map(([userId, session]) => ({
userId,
...session,
currentDuration: new Date() - session.startTime
}));
}
isUserWorking(userId) {
return this.activeSessions.has(userId);
}
}
// CID Event monitoring (placeholder for actual CID handling)
function setupWorkingModeCidMonitoring(partialUid, onCidEvent) {
// This would integrate with the actual CID event system
console.log('Setting up CID monitoring for working mode events');
// Placeholder implementation
const mockCidHandler = (cidEvent) => {
if (cidEvent.type === 'WORK_START' || cidEvent.type === 'WORK_STOP') {
console.log('Work mode CID event received:', cidEvent);
if (onCidEvent) {
onCidEvent(cidEvent);
}
}
};
return mockCidHandler;
}
// Usage demonstration
async function demonstrateUserWorkingMode(partialUid, userEmail) {
try {
console.log('=== User Working Mode Demonstration ===');
// Start work for user 25
console.log('\n--- Starting Work Session ---');
const startResult = await startUserWork(partialUid, '25', userEmail);
console.log('Start work result:', startResult);
// Stop work for user 25
console.log('\n--- Stopping Work Session ---');
const stopResult = await stopUserWork(partialUid, '25', userEmail);
console.log('Stop work result:', stopResult);
// Toggle working mode
console.log('\n--- Toggle Working Mode ---');
const toggleResult = await toggleUserWorkingMode(partialUid, '150', userEmail, false);
console.log('Toggle result:', toggleResult);
// Batch operations
console.log('\n--- Batch User Updates ---');
const userUpdates = [
{ userId: '25', workingState: 1 },
{ userId: '150', workingState: 1 },
{ userId: '999', workingState: 0 }
];
const batchResults = await setMultipleUsersWorkingMode(partialUid, userUpdates, userEmail);
console.log('Batch results:', batchResults);
// Work session management
console.log('\n--- Work Session Management ---');
const sessionManager = new WorkSessionManager(partialUid, userEmail);
await sessionManager.startWorkSession('100');
await sessionManager.startWorkSession('200');
console.log('Active sessions:', sessionManager.getAllActiveSessions());
console.log('User 100 working:', sessionManager.isUserWorking('100'));
await sessionManager.stopWorkSession('100');
console.log('Active sessions after stop:', sessionManager.getAllActiveSessions());
// CID event monitoring setup
console.log('\n--- CID Event Monitoring ---');
const cidHandler = setupWorkingModeCidMonitoring(partialUid, (event) => {
console.log('Working mode event:', event);
});
// Test validation
console.log('\n--- Testing Invalid Inputs ---');
try {
await setUserWorkingModeSafe(partialUid, '5', 1, userEmail); // Invalid user ID
} catch (error) {
console.log('Expected validation error:', error.message);
}
try {
await setUserWorkingModeSafe(partialUid, '25', 2, userEmail); // Invalid state
} catch (error) {
console.log('Expected validation error:', error.message);
}
// Test different input formats
console.log('\n--- Testing Input Format Variations ---');
const formatTests = [
{ userId: 25, state: '1' }, // Numeric user ID, string state
{ userId: '150', state: 0 }, // String user ID, numeric state
{ userId: '999', state: '0' } // Both strings
];
for (const test of formatTests) {
try {
const result = await setUserWorkingModeSafe(partialUid, test.userId, test.state, userEmail);
console.log(`Format test ${test.userId}:${test.state} - Success:`, result.success);
} catch (error) {
console.log(`Format test ${test.userId}:${test.state} - Error:`, error.message);
}
}
console.log('User working mode demonstration completed');
} catch (error) {
console.error('User working mode demonstration failed:', error);
}
}0x001F - Panel Status
Requests comprehensive status information from the security panel. Returns detailed ASCII status data including power, communications, network connectivity, and hardware status information.
Request/Response format
Request Format
[HEADER][0x001F] → 070100000000000000001F00Response Format
[HEADER][0x001F][Panel status in ASCII]Response Example
← [HEADER][0x001F][AX:1,AXV:13.22,AC:0,ACV:17.22,BT:1,BTV:13.22,BTC:120,BE:0,BEV:13.75,BEM:1,CMS:1,PROT:2,GL:100%,WL:55%,WSI:Trikdis,WS:3,WST:2020-05-22 12:55,SIS:1,SIT:2020-05-22 12:55,RSS:3,RST:2020-05-22 12:55]Status Field Definitions
Power System Status:
- AX: Aux voltage output status -
0overcurrent,1OK - AXV: Aux voltage in volts (e.g.,
13.22) - AC: AC voltage fail status -
0OK,1AC failed - ACV: AC voltage in volts (e.g.,
17.22)
Battery Status:
- BT: Battery status -
0battery OK,1battery missing - BTV: Battery voltage in volts (e.g.,
13.22) - BTC: Battery charging current in mA (e.g.,
120)
Bell System Status:
- BE: Bell+ overcurrent status -
0OK,1bell+ overcurrent fault - BEV: Bell+ voltage in volts (e.g.,
13.75) - BEM: Bell missing status -
0bell OK,1bell missing
Communication Systems:
- CMS: CMS communication type -
0disabled,1GPRS/LAN,2WiFi,3LAN (RS485) - PROT: Protegus communication type -
0disabled,1GPRS/LAN,2WiFi,3LAN (RS485)
Network Status Details
GSM/Cellular:
- GL: GSM level percentage -
-1no level/GSM trouble, otherwise GSM signal level - SIS: SIM card status:
0Active1No SIM2PIN fail3GPRS network trouble4GSM network failure5Mobile data trouble
- SIT: SIM last checked timestamp (e.g.,
2020-05-22 12:55)
WiFi Status:
- WL: WiFi level percentage -
-1no WiFi connected, otherwise WiFi signal level - WSI: WiFi SSID name - empty if disabled, otherwise SSID string
- WS: WiFi status:
0WiFi OK1No SSID2Disconnected3No internet- Note: For HW4/HW6/HW7, this indicates integrated LAN status
- WST: Last WiFi check timestamp (e.g.,
2020-05-22 12:55)
RS485 LAN Module:
- RSS: RS485 LAN module status:
0Active1Module not programmed (disabled)2Module lost3No cable or DHCP trouble4Internet trouble
- RST: RS485 LAN module last checked timestamp
Dual SIM (HW7 only):
- SI2S: SIM2 status (same values as SIS)
- SI2T: SIM2 last checked timestamp
Hardware Revision Notes
Integrated LAN Status (Revision 3E00): When using integrated LAN, SIS field meanings change:
0Active1No cable2DHCP trouble3Internet trouble
HW4/HW6/HW7 WiFi Status: WS field represents integrated LAN status instead of WiFi for these hardware revisions.
Cross-References
Related commands for system monitoring:
- 0x000E - Get Logs: View detailed system and communication logs
- 0x0001 - Get Device Capabilities: View device hardware capabilities
- 0x0002 - Get Device Status: Basic device operational status
Usage Example
javascript
async function getPanelStatus(partialUid) {
// Build panel status request command
const header = "070100000000000000";
const command = "1F00";
const fullCommand = header + command;
try {
console.log('Requesting panel status...');
const result = await sendDeviceCommand(partialUid, fullCommand, true);
if (result.data && result.data.length > 0) {
const statusText = hexToAscii(result.data[0]);
if (statusText === 'NACK') {
return {
success: false,
message: 'Device returned NACK - panel status not available'
};
}
// Parse panel status
const panelStatus = parsePanelStatus(statusText);
console.log('Panel status received:', panelStatus);
return {
success: true,
status: panelStatus,
rawStatus: statusText
};
}
throw new Error('No response received from device');
} catch (error) {
console.error('Failed to get panel status:', error);
throw error;
}
}
// Parse panel status string into structured object
function parsePanelStatus(statusText) {
const status = {};
if (!statusText) {
return status;
}
// Split by commas and parse key:value pairs
const pairs = statusText.split(',');
pairs.forEach(pair => {
const colonIndex = pair.indexOf(':');
if (colonIndex > 0) {
const key = pair.substring(0, colonIndex).trim();
const value = pair.substring(colonIndex + 1).trim();
// Parse numeric values
if (isNumericField(key)) {
const numericValue = parseFloat(value);
status[key] = isNaN(numericValue) ? value : numericValue;
} else {
status[key] = value;
}
}
});
return status;
}
// Identify numeric fields for proper parsing
function isNumericField(field) {
const numericFields = ['AX', 'AXV', 'AC', 'ACV', 'BT', 'BTV', 'BTC', 'BE', 'BEV', 'BEM', 'CMS', 'PROT', 'WS', 'SIS', 'RSS', 'SI2S'];
return numericFields.includes(field);
}
// Get interpreted status with human-readable descriptions
function interpretPanelStatus(panelStatus) {
const interpretation = {
power: interpretPowerStatus(panelStatus),
battery: interpretBatteryStatus(panelStatus),
bell: interpretBellStatus(panelStatus),
communications: interpretCommunicationStatus(panelStatus),
network: interpretNetworkStatus(panelStatus)
};
return interpretation;
}
function interpretPowerStatus(status) {
return {
auxOutput: {
status: status.AX === 1 ? 'OK' : 'Overcurrent',
voltage: status.AXV,
healthy: status.AX === 1
},
acPower: {
status: status.AC === 0 ? 'OK' : 'Failed',
voltage: status.ACV,
healthy: status.AC === 0
}
};
}
function interpretBatteryStatus(status) {
return {
status: status.BT === 0 ? 'OK' : 'Missing',
voltage: status.BTV,
chargingCurrent: status.BTC,
healthy: status.BT === 0
};
}
function interpretBellStatus(status) {
return {
overcurrent: {
status: status.BE === 0 ? 'OK' : 'Overcurrent fault',
healthy: status.BE === 0
},
voltage: status.BEV,
connection: {
status: status.BEM === 0 ? 'OK' : 'Missing',
healthy: status.BEM === 0
}
};
}
function interpretCommunicationStatus(status) {
const commTypes = ['Disabled', 'GPRS/LAN', 'WiFi', 'LAN (RS485)'];
return {
cms: {
type: status.CMS,
description: commTypes[status.CMS] || 'Unknown'
},
protegus: {
type: status.PROT,
description: commTypes[status.PROT] || 'Unknown'
}
};
}
function interpretNetworkStatus(status) {
const network = {};
// GSM Status
if (status.GL !== undefined) {
network.gsm = {
signalLevel: status.GL === '-1' ? null : parseInt(status.GL.replace('%', '')),
status: status.GL === '-1' ? 'No signal/Trouble' : 'Connected',
healthy: status.GL !== '-1'
};
}
// SIM Status
if (status.SIS !== undefined) {
const simStatuses = ['Active', 'No SIM', 'PIN fail', 'GPRS trouble', 'GSM network failure', 'Mobile data trouble'];
network.sim = {
status: status.SIS,
description: simStatuses[status.SIS] || 'Unknown',
lastChecked: status.SIT,
healthy: status.SIS === 0
};
}
// WiFi Status
if (status.WL !== undefined) {
network.wifi = {
signalLevel: status.WL === '-1' ? null : parseInt(status.WL.replace('%', '')),
ssid: status.WSI || null,
status: getWifiStatusDescription(status.WS),
lastChecked: status.WST,
healthy: status.WS === 0,
connected: status.WL !== '-1'
};
}
// RS485 LAN Status
if (status.RSS !== undefined) {
const rs485Statuses = ['Active', 'Not programmed', 'Module lost', 'Cable/DHCP trouble', 'Internet trouble'];
network.rs485 = {
status: status.RSS,
description: rs485Statuses[status.RSS] || 'Unknown',
lastChecked: status.RST,
healthy: status.RSS === 0
};
}
// SIM2 Status (HW7 only)
if (status.SI2S !== undefined) {
const simStatuses = ['Active', 'No SIM', 'PIN fail', 'GPRS trouble', 'GSM network failure', 'Mobile data trouble'];
network.sim2 = {
status: status.SI2S,
description: simStatuses[status.SI2S] || 'Unknown',
lastChecked: status.SI2T,
healthy: status.SI2S === 0
};
}
return network;
}
function getWifiStatusDescription(status) {
const wifiStatuses = ['WiFi OK', 'No SSID', 'Disconnected', 'No internet'];
return wifiStatuses[status] || 'Unknown';
}
// Health check functions
function checkPanelHealth(panelStatus) {
const interpretation = interpretPanelStatus(panelStatus);
const issues = [];
const warnings = [];
// Power issues
if (!interpretation.power.auxOutput.healthy) {
issues.push('Auxiliary output overcurrent detected');
}
if (!interpretation.power.acPower.healthy) {
issues.push('AC power failure detected');
}
// Battery issues
if (!interpretation.battery.healthy) {
issues.push('Battery missing or failed');
}
if (interpretation.battery.voltage < 11.0) {
warnings.push('Low battery voltage');
}
// Bell issues
if (!interpretation.bell.overcurrent.healthy) {
issues.push('Bell overcurrent fault');
}
if (!interpretation.bell.connection.healthy) {
warnings.push('Bell disconnected');
}
// Network issues
const network = interpretation.network;
if (network.sim && !network.sim.healthy) {
issues.push(`SIM card issue: ${network.sim.description}`);
}
if (network.gsm && !network.gsm.healthy) {
issues.push('GSM signal trouble');
}
if (network.wifi && network.wifi.connected && !network.wifi.healthy) {
warnings.push(`WiFi issue: ${network.wifi.status}`);
}
if (network.rs485 && !network.rs485.healthy) {
warnings.push(`RS485 LAN issue: ${network.rs485.description}`);
}
return {
overall: issues.length === 0 ? 'healthy' : 'issues_detected',
critical: issues,
warnings: warnings,
issueCount: issues.length,
warningCount: warnings.length
};
}
// Monitoring functions
function monitorPanelStatus(partialUid, intervalSeconds = 60, onStatusUpdate) {
let monitoring = true;
const checkStatus = async () => {
if (!monitoring) return;
try {
const result = await getPanelStatus(partialUid);
if (result.success) {
const interpretation = interpretPanelStatus(result.status);
const health = checkPanelHealth(result.status);
if (onStatusUpdate) {
onStatusUpdate({
timestamp: new Date(),
raw: result.status,
interpreted: interpretation,
health: health
});
}
}
} catch (error) {
console.error('Panel status monitoring error:', error);
}
// Schedule next check
if (monitoring) {
setTimeout(checkStatus, intervalSeconds * 1000);
}
};
// Start monitoring
checkStatus();
// Return stop function
return () => {
monitoring = false;
};
}
// Convenience functions
async function getPowerStatus(partialUid) {
const result = await getPanelStatus(partialUid);
if (result.success) {
return interpretPowerStatus(result.status);
}
return null;
}
async function getBatteryStatus(partialUid) {
const result = await getPanelStatus(partialUid);
if (result.success) {
return interpretBatteryStatus(result.status);
}
return null;
}
async function getNetworkStatus(partialUid) {
const result = await getPanelStatus(partialUid);
if (result.success) {
return interpretNetworkStatus(result.status);
}
return null;
}
async function checkCommunicationHealth(partialUid) {
const result = await getPanelStatus(partialUid);
if (result.success) {
const network = interpretNetworkStatus(result.status);
const health = {
gsm: network.gsm?.healthy || false,
wifi: network.wifi?.healthy || false,
rs485: network.rs485?.healthy || false
};
health.overall = health.gsm || health.wifi || health.rs485;
return health;
}
return null;
}
// Usage demonstration
async function demonstratePanelStatus(partialUid) {
try {
console.log('=== Panel Status Demonstration ===');
// Get full panel status
console.log('\n--- Getting Panel Status ---');
const statusResult = await getPanelStatus(partialUid);
console.log('Panel status result:', statusResult);
if (statusResult.success) {
// Interpret status
console.log('\n--- Status Interpretation ---');
const interpretation = interpretPanelStatus(statusResult.status);
console.log('Power status:', interpretation.power);
console.log('Battery status:', interpretation.battery);
console.log('Network status:', interpretation.network);
// Health check
console.log('\n--- Health Check ---');
const health = checkPanelHealth(statusResult.status);
console.log('Health summary:', health);
if (health.critical.length > 0) {
console.log('Critical issues:', health.critical);
}
if (health.warnings.length > 0) {
console.log('Warnings:', health.warnings);
}
// Specific status checks
console.log('\n--- Specific Status Checks ---');
const powerStatus = interpretPowerStatus(statusResult.status);
console.log('Power healthy:', powerStatus.auxOutput.healthy && powerStatus.acPower.healthy);
const batteryStatus = interpretBatteryStatus(statusResult.status);
console.log('Battery voltage:', batteryStatus.voltage, 'V');
const commHealth = await checkCommunicationHealth(partialUid);
console.log('Communication health:', commHealth);
}
// Start monitoring (demo for 30 seconds)
console.log('\n--- Starting Status Monitoring ---');
const stopMonitoring = monitorPanelStatus(partialUid, 10, (update) => {
console.log(`Status update: ${update.health.overall} (${update.health.issueCount} issues, ${update.health.warningCount} warnings)`);
});
// Stop monitoring after 30 seconds
setTimeout(() => {
stopMonitoring();
console.log('Monitoring stopped');
}, 30000);
console.log('Panel status demonstration completed');
} catch (error) {
console.error('Panel status demonstration failed:', error);
}
}0x0020 - RF Sensors Data
Requests wireless RF sensor data from the security panel. Returns comprehensive information for up to 42 wireless sensors including sensor type, signal strength, temperature, and battery voltage.
Request/Response format
Request Format
[HEADER][0x0020] → 070100000000000000002000Response Format
[HEADER][0x0020][RF sensors data in ASCII]Response Example
← [HEADER][0x0020][ID:32,L:20,T:27,V:3.20|ID:34,L:0,T:24,V:3.25|ID:36,L:0,T:0,V:0.0|ID:31,L:85,T:22,V:3.15|ID:97,L:75,T:25,V:3.30]Sensor Data Format
Data Structure:
- Separator: Pipe character (
|) separates individual sensors - Field Format:
FieldName:Valuepairs separated by commas - Maximum Sensors: Up to 42 sensors supported
- Fields Per Sensor: ID, L (RSSI level), T (temperature), V (voltage)
Field Definitions:
- ID: Hexadecimal sensor type identifier (2 characters)
- L: RSSI signal level as percentage (0-100%)
- T: Internal sensor temperature in Celsius (0 = not available)
- V: Battery voltage in volts (0.0 = not available)
Sensor Type Definitions
Security Sensors:
- 31: Wireless PIR, Curtain PIR
- 37: Wireless outdoor PIR
- 32: Magnetic/vibration sensor
- 33: Pendant button (panic button)
- 34: Smoke sensor
- 36: Glass break sensor
- 38: Flood sensor
- 39: Magnetic sensor
- 3C: Temperature, magnetic, flood sensor (multi-function)
System Devices:
- 97: Wireless icon keypad
- 45: Wireless siren
- 46: Wireless siren (alternate type)
Data Availability Notes
Signal Level (L):
- Range: 0-100%
- 0: No signal or sensor offline
- 1-100: Signal strength percentage
Temperature (T):
- Range: Celsius temperature reading
- 0: Temperature not available or sensor doesn't support temperature
- Typical Range: -40°C to +85°C for most sensors
Voltage (V):
- Format: Decimal voltage (e.g., 3.20, 3.25)
- 0.0: Voltage not available or sensor doesn't report voltage
- Typical Range: 2.0V to 3.6V for lithium batteries
Cross-References
Related commands for sensor management:
- 0x0012 - Set Fire Trouble Status: Configure fire sensor states
- 0x0013 - Set User Code: Manage keypad user codes
- 0x001F - Panel Status: Overall panel health including RF communication
Usage Example
javascript
async function getRfSensorsData(partialUid) {
// Build RF sensors data request command
const header = "070100000000000000";
const command = "2000";
const fullCommand = header + command;
try {
console.log('Requesting RF sensors data...');
const result = await sendDeviceCommand(partialUid, fullCommand, true);
if (result.data && result.data.length > 0) {
const sensorsText = hexToAscii(result.data[0]);
if (sensorsText === 'NACK') {
return {
success: false,
message: 'Device returned NACK - RF sensors data not available'
};
}
// Parse RF sensors data
const sensorsData = parseRfSensorsData(sensorsText);
console.log(`RF sensors data received: ${sensorsData.length} sensors`);
return {
success: true,
sensors: sensorsData,
sensorCount: sensorsData.length,
rawData: sensorsText
};
}
throw new Error('No response received from device');
} catch (error) {
console.error('Failed to get RF sensors data:', error);
throw error;
}
}
// Parse RF sensors data string into structured array
function parseRfSensorsData(sensorsText) {
const sensors = [];
if (!sensorsText) {
return sensors;
}
// Split by pipe separator
const sensorStrings = sensorsText.split('|').filter(s => s.trim().length > 0);
sensorStrings.forEach((sensorString, index) => {
try {
const sensorData = parseSingleSensor(sensorString.trim(), index + 1);
if (sensorData) {
sensors.push(sensorData);
}
} catch (error) {
console.warn(`Failed to parse sensor ${index + 1}: ${error.message}`);
}
});
return sensors;
}
// Parse individual sensor data string
function parseSingleSensor(sensorString, sensorIndex) {
const sensor = { index: sensorIndex };
// Split by commas and parse key:value pairs
const pairs = sensorString.split(',');
pairs.forEach(pair => {
const colonIndex = pair.indexOf(':');
if (colonIndex > 0) {
const key = pair.substring(0, colonIndex).trim();
const value = pair.substring(colonIndex + 1).trim();
switch (key) {
case 'ID':
sensor.id = value;
sensor.type = getSensorTypeDescription(value);
sensor.category = getSensorCategory(value);
break;
case 'L':
sensor.rssi = parseInt(value, 10);
sensor.signalStrength = getRssiDescription(sensor.rssi);
sensor.online = sensor.rssi > 0;
break;
case 'T':
sensor.temperature = parseInt(value, 10);
sensor.temperatureAvailable = sensor.temperature !== 0;
break;
case 'V':
sensor.voltage = parseFloat(value);
sensor.voltageAvailable = sensor.voltage !== 0.0;
sensor.batteryLevel = getBatteryLevelDescription(sensor.voltage);
break;
}
}
});
// Add sensor health assessment
sensor.health = assessSensorHealth(sensor);
return sensor;
}
// Get sensor type description from ID
function getSensorTypeDescription(sensorId) {
const sensorTypes = {
'31': 'Wireless PIR / Curtain PIR',
'37': 'Wireless Outdoor PIR',
'32': 'Magnetic/Vibration Sensor',
'33': 'Pendant Button (Panic)',
'34': 'Smoke Sensor',
'36': 'Glass Break Sensor',
'38': 'Flood Sensor',
'39': 'Magnetic Sensor',
'3C': 'Multi-function Sensor (Temp/Magnetic/Flood)',
'97': 'Wireless Icon Keypad',
'45': 'Wireless Siren',
'46': 'Wireless Siren (Alt)'
};
return sensorTypes[sensorId] || `Unknown Sensor (${sensorId})`;
}
// Get sensor category for grouping
function getSensorCategory(sensorId) {
const categories = {
'31': 'Motion Detection',
'37': 'Motion Detection',
'32': 'Door/Window',
'33': 'Emergency',
'34': 'Fire Safety',
'36': 'Intrusion',
'38': 'Environmental',
'39': 'Door/Window',
'3C': 'Multi-function',
'97': 'Control',
'45': 'Notification',
'46': 'Notification'
};
return categories[sensorId] || 'Unknown';
}
// Get RSSI signal strength description
function getRssiDescription(rssi) {
if (rssi === 0) return 'Offline';
if (rssi >= 80) return 'Excellent';
if (rssi >= 60) return 'Good';
if (rssi >= 40) return 'Fair';
if (rssi >= 20) return 'Poor';
return 'Very Poor';
}
// Get battery level description from voltage
function getBatteryLevelDescription(voltage) {
if (voltage === 0.0) return 'Not Available';
if (voltage >= 3.0) return 'Good';
if (voltage >= 2.7) return 'Fair';
if (voltage >= 2.4) return 'Low';
return 'Critical';
}
// Assess overall sensor health
function assessSensorHealth(sensor) {
const issues = [];
const warnings = [];
// Check connectivity
if (!sensor.online) {
issues.push('Sensor offline - no signal');
} else if (sensor.rssi < 20) {
warnings.push('Very weak signal strength');
} else if (sensor.rssi < 40) {
warnings.push('Weak signal strength');
}
// Check battery
if (sensor.voltageAvailable) {
if (sensor.voltage < 2.4) {
issues.push('Critical battery level');
} else if (sensor.voltage < 2.7) {
warnings.push('Low battery level');
}
}
// Check temperature (if available)
if (sensor.temperatureAvailable) {
if (sensor.temperature < -20 || sensor.temperature > 60) {
warnings.push('Temperature out of normal range');
}
}
return {
status: issues.length > 0 ? 'critical' : (warnings.length > 0 ? 'warning' : 'healthy'),
issues: issues,
warnings: warnings,
score: calculateHealthScore(sensor)
};
}
// Calculate numeric health score (0-100)
function calculateHealthScore(sensor) {
let score = 100;
// Signal strength impact (0-30 points)
if (!sensor.online) {
score -= 50;
} else {
score -= Math.max(0, (80 - sensor.rssi) * 0.375); // Linear decrease from 80% to 0%
}
// Battery impact (0-20 points)
if (sensor.voltageAvailable) {
if (sensor.voltage < 2.4) {
score -= 30;
} else if (sensor.voltage < 2.7) {
score -= 15;
} else if (sensor.voltage < 3.0) {
score -= 5;
}
}
return Math.max(0, Math.round(score));
}
// Filter and query functions
function filterSensorsByType(sensors, sensorTypes) {
if (!Array.isArray(sensorTypes)) {
sensorTypes = [sensorTypes];
}
return sensors.filter(sensor => sensorTypes.includes(sensor.id));
}
function filterSensorsByCategory(sensors, category) {
return sensors.filter(sensor => sensor.category === category);
}
function filterOnlineSensors(sensors) {
return sensors.filter(sensor => sensor.online);
}
function filterOfflineSensors(sensors) {
return sensors.filter(sensor => !sensor.online);
}
function filterLowBatterySensors(sensors, voltageThreshold = 2.7) {
return sensors.filter(sensor =>
sensor.voltageAvailable && sensor.voltage < voltageThreshold
);
}
function filterWeakSignalSensors(sensors, rssiThreshold = 40) {
return sensors.filter(sensor =>
sensor.online && sensor.rssi < rssiThreshold
);
}
// Analysis functions
function analyzeSensorHealth(sensors) {
const analysis = {
total: sensors.length,
online: 0,
offline: 0,
healthy: 0,
warnings: 0,
critical: 0,
categories: {},
batteryIssues: 0,
signalIssues: 0
};
sensors.forEach(sensor => {
// Count by status
if (sensor.online) analysis.online++;
else analysis.offline++;
// Count by health
switch (sensor.health.status) {
case 'healthy': analysis.healthy++; break;
case 'warning': analysis.warnings++; break;
case 'critical': analysis.critical++; break;
}
// Count by category
const category = sensor.category;
if (!analysis.categories[category]) {
analysis.categories[category] = { total: 0, online: 0, healthy: 0 };
}
analysis.categories[category].total++;
if (sensor.online) analysis.categories[category].online++;
if (sensor.health.status === 'healthy') analysis.categories[category].healthy++;
// Count specific issues
if (sensor.voltageAvailable && sensor.voltage < 2.7) {
analysis.batteryIssues++;
}
if (sensor.online && sensor.rssi < 40) {
analysis.signalIssues++;
}
});
// Calculate percentages
analysis.onlinePercentage = analysis.total > 0 ? (analysis.online / analysis.total * 100).toFixed(1) : 0;
analysis.healthyPercentage = analysis.total > 0 ? (analysis.healthy / analysis.total * 100).toFixed(1) : 0;
return analysis;
}
// Monitoring functions
function monitorRfSensors(partialUid, intervalSeconds = 300, onSensorsUpdate) {
let monitoring = true;
const checkSensors = async () => {
if (!monitoring) return;
try {
const result = await getRfSensorsData(partialUid);
if (result.success) {
const analysis = analyzeSensorHealth(result.sensors);
if (onSensorsUpdate) {
onSensorsUpdate({
timestamp: new Date(),
sensors: result.sensors,
analysis: analysis,
issues: result.sensors.filter(s => s.health.status === 'critical'),
warnings: result.sensors.filter(s => s.health.status === 'warning')
});
}
}
} catch (error) {
console.error('RF sensors monitoring error:', error);
}
// Schedule next check
if (monitoring) {
setTimeout(checkSensors, intervalSeconds * 1000);
}
};
// Start monitoring
checkSensors();
// Return stop function
return () => {
monitoring = false;
};
}
// Convenience functions
async function getOnlineSensors(partialUid) {
const result = await getRfSensorsData(partialUid);
if (result.success) {
return filterOnlineSensors(result.sensors);
}
return [];
}
async function getSensorsByCategory(partialUid, category) {
const result = await getRfSensorsData(partialUid);
if (result.success) {
return filterSensorsByCategory(result.sensors, category);
}
return [];
}
async function getLowBatterySensors(partialUid, threshold = 2.7) {
const result = await getRfSensorsData(partialUid);
if (result.success) {
return filterLowBatterySensors(result.sensors, threshold);
}
return [];
}
async function getSensorHealthSummary(partialUid) {
const result = await getRfSensorsData(partialUid);
if (result.success) {
return analyzeSensorHealth(result.sensors);
}
return null;
}
// Usage demonstration
async function demonstrateRfSensors(partialUid) {
try {
console.log('=== RF Sensors Data Demonstration ===');
// Get all sensor data
console.log('\n--- Getting RF Sensors Data ---');
const sensorsResult = await getRfSensorsData(partialUid);
console.log('Sensors result:', sensorsResult);
if (sensorsResult.success) {
// Analyze sensor health
console.log('\n--- Sensor Health Analysis ---');
const analysis = analyzeSensorHealth(sensorsResult.sensors);
console.log('Health analysis:', analysis);
// Show sensors by category
console.log('\n--- Sensors by Category ---');
Object.entries(analysis.categories).forEach(([category, stats]) => {
console.log(`${category}: ${stats.online}/${stats.total} online, ${stats.healthy} healthy`);
});
// Filter specific sensor types
console.log('\n--- Motion Sensors ---');
const motionSensors = filterSensorsByCategory(sensorsResult.sensors, 'Motion Detection');
motionSensors.forEach(sensor => {
console.log(` ${sensor.type}: Signal ${sensor.rssi}%, Battery ${sensor.voltage}V`);
});
// Check for issues
console.log('\n--- Sensor Issues ---');
const lowBattery = filterLowBatterySensors(sensorsResult.sensors);
if (lowBattery.length > 0) {
console.log('Low battery sensors:', lowBattery.map(s => `${s.type} (${s.voltage}V)`));
}
const weakSignal = filterWeakSignalSensors(sensorsResult.sensors);
if (weakSignal.length > 0) {
console.log('Weak signal sensors:', weakSignal.map(s => `${s.type} (${s.rssi}%)`));
}
const offline = filterOfflineSensors(sensorsResult.sensors);
if (offline.length > 0) {
console.log('Offline sensors:', offline.map(s => s.type));
}
// Get specific categories
console.log('\n--- Fire Safety Sensors ---');
const fireSensors = await getSensorsByCategory(partialUid, 'Fire Safety');
console.log('Fire sensors:', fireSensors.length);
console.log('\n--- Emergency Sensors ---');
const emergencySensors = await getSensorsByCategory(partialUid, 'Emergency');
console.log('Emergency sensors:', emergencySensors.length);
}
// Start monitoring (demo for 60 seconds)
console.log('\n--- Starting Sensor Monitoring ---');
const stopMonitoring = monitorRfSensors(partialUid, 30, (update) => {
console.log(`Sensor update: ${update.analysis.online}/${update.analysis.total} online, ${update.issues.length} critical issues`);
});
// Stop monitoring after 60 seconds
setTimeout(() => {
stopMonitoring();
console.log('Sensor monitoring stopped');
}, 60000);
console.log('RF sensors demonstration completed');
} catch (error) {
console.error('RF sensors demonstration failed:', error);
}
}0x0021 - Read Settings
Available on G16x, E16x devices
Status: Undocumented command - implementation details not available.
0x0022 - Write Settings
Available on G16x, E16x devices
Status: Undocumented command - implementation details not available.
0x0023 - Cancel Alarm/Silence Siren
Cancels alarm state and silences sirens for a specific area without disarming the system. Requires user authorization and can optionally generate Cancel Alarm CID events to monitoring stations.
Request/Response format
Request Format
[HEADER][0x0023][Area:Silence:Cancel] → 0701000000000000002300[AreaSilenceData]Request Examples
Silence alarm in area 1 with CID event:
[HEADER][0x0023][1:1:1] // Area 1, silence alarm, generate CID
→ 0701000000000000002300313A313A31Cancel alarm in area 2 without silencing:
[HEADER][0x0023][2:0:1] // Area 2, don't silence, generate CID
→ 0701000000000000002300323A303A31Silence area 3 without CID event:
[HEADER][0x0023][3:1:0] // Area 3, silence alarm, no CID
→ 0701000000000000002300333A313A30Response Format
Success Response:
[HEADER][0x0023][OK]Error Response:
[HEADER][0x0023][NACK] // Command not supported, wrong data, or authorization failedParameter Definitions
Area Number:
- Range: Valid area numbers configured in the system
- Purpose: Specifies which area to silence or cancel alarm
- Format: Numeric string (e.g., "1", "2", "3")
Silence Parameter:
- 0: Alarm will not be turned off (sirens continue)
- 1: Alarm will be silenced (sirens and buzzers turned off)
- Purpose: Controls local alarm indication without disarming
Cancel Parameter:
- 0: Cancel CID will not be generated
- 1: Cancel CID will be generated to all IP channels
- Purpose: Notifies monitoring stations of alarm cancellation
Authorization Requirements
User Authentication:
- Required: User authorization must be completed before command execution
- Authentication Type: Valid user credentials required
- Security Level: Appropriate user access level for alarm management
Access Control:
- Only authorized users can cancel alarms
- User must have alarm management permissions
- Authentication failure results in NACK response
Operational Behavior
Alarm Silencing (Silence=1):
- Sirens: All sirens in specified area turned off
- Buzzers: All buzzers and audible indicators silenced
- System State: Area remains in alarm state but audio notifications stopped
- Restoration: Manual disarm or auto-restore required to clear alarm state
CID Event Generation (Cancel=1):
- Event Type: Cancel Alarm CID event
- Recipients: All configured IP channels (monitoring stations)
- Content: Area information and timestamp
- Standard: Follows Contact ID protocol specifications
Use Cases
Emergency Situations:
- False Alarm: Silence sirens while maintaining alarm state
- Controlled Response: Stop audio alerts without full disarm
- Monitoring Integration: Notify stations of alarm cancellation
Operational Scenarios:
- Area Isolation: Silence specific area while others remain active
- Partial Cancellation: Cancel alarm indication without system disarm
- Professional Response: Allow security personnel to manage alarm audio
Cross-References
Related commands for alarm management:
- 0x0003 - Arm/Disarm Areas: Full area disarm operations
- 0x0008 - Authorization: Required user authentication
- 0x000E - Get Logs: View alarm and CID event logs
Usage Example
javascript
async function cancelAlarmSilenceSiren(partialUid, areaNumber, silenceAlarm, generateCid, userCredentials) {
// Validate parameters
if (!isValidAreaNumber(areaNumber)) {
throw new Error('Invalid area number');
}
if (!isValidBooleanFlag(silenceAlarm) || !isValidBooleanFlag(generateCid)) {
throw new Error('Silence and Cancel parameters must be 0 or 1');
}
if (!userCredentials) {
throw new Error('User credentials required for alarm cancellation');
}
try {
// Step 1: Authenticate user
console.log('Authenticating user for alarm cancellation...');
const authResult = await authenticateUser(partialUid, userCredentials);
if (!authResult.success) {
throw new Error('Authentication failed: ' + authResult.message);
}
// Step 2: Build cancel alarm command
const header = "070100000000000000";
const command = "2300";
const silenceFlag = silenceAlarm ? '1' : '0';
const cancelFlag = generateCid ? '1' : '0';
const alarmData = `${areaNumber}:${silenceFlag}:${cancelFlag}`;
const dataHex = asciiToHex(alarmData);
const fullCommand = header + command + dataHex;
console.log(`Cancelling alarm for area ${areaNumber} (silence: ${silenceFlag}, CID: ${cancelFlag})`);
const result = await sendDeviceCommand(partialUid, fullCommand, true);
if (result.data && result.data.length > 0) {
const responseText = hexToAscii(result.data[0]);
if (responseText === 'OK') {
const actions = [];
if (silenceAlarm) actions.push('alarm silenced');
if (generateCid) actions.push('CID event generated');
const actionDescription = actions.length > 0 ? actions.join(', ') : 'alarm cancelled';
console.log(`Area ${areaNumber} ${actionDescription} successfully`);
return {
success: true,
message: `Area ${areaNumber} alarm cancelled (${actionDescription})`,
areaNumber: areaNumber,
silenced: silenceAlarm,
cidGenerated: generateCid
};
} else if (responseText === 'NACK') {
console.log('Failed to cancel alarm');
return {
success: false,
message: 'Device returned NACK - command not supported, wrong data, or authorization failed'
};
} else {
return {
success: false,
message: `Unexpected response: ${responseText}`
};
}
}
throw new Error('No response received from device');
} catch (error) {
console.error('Failed to cancel alarm:', error);
throw error;
}
}
// Validation functions
function isValidAreaNumber(areaNumber) {
const numericArea = parseInt(areaNumber, 10);
return !isNaN(numericArea) && numericArea >= 1 && numericArea <= 99; // Assuming max 99 areas
}
function isValidBooleanFlag(flag) {
return flag === 0 || flag === 1 || flag === '0' || flag === '1';
}
function normalizeBooleanFlag(flag) {
return (flag === '1' || flag === 1) ? 1 : 0;
}
// Convenience functions
async function silenceAlarmWithCid(partialUid, areaNumber, userCredentials) {
return await cancelAlarmSilenceSiren(partialUid, areaNumber, true, true, userCredentials);
}
async function silenceAlarmOnly(partialUid, areaNumber, userCredentials) {
return await cancelAlarmSilenceSiren(partialUid, areaNumber, true, false, userCredentials);
}
async function cancelAlarmWithCid(partialUid, areaNumber, userCredentials) {
return await cancelAlarmSilenceSiren(partialUid, areaNumber, false, true, userCredentials);
}
async function generateCancelCidOnly(partialUid, areaNumber, userCredentials) {
return await cancelAlarmSilenceSiren(partialUid, areaNumber, false, true, userCredentials);
}
// Safe wrapper with validation
async function cancelAlarmSafe(partialUid, areaNumber, silenceAlarm, generateCid, userCredentials) {
try {
const normalizedArea = parseInt(areaNumber, 10).toString();
const normalizedSilence = normalizeBooleanFlag(silenceAlarm);
const normalizedCancel = normalizeBooleanFlag(generateCid);
return await cancelAlarmSilenceSiren(
partialUid,
normalizedArea,
normalizedSilence,
normalizedCancel,
userCredentials
);
} catch (error) {
console.error('Validation error:', error.message);
throw error;
}
}
// Batch operations for multiple areas
async function cancelMultipleAreas(partialUid, areaConfigs, userCredentials) {
const results = [];
// Authenticate once for all operations
console.log('Authenticating user for batch alarm cancellation...');
const authResult = await authenticateUser(partialUid, userCredentials);
if (!authResult.success) {
throw new Error('Authentication failed: ' + authResult.message);
}
for (const config of areaConfigs) {
try {
const result = await cancelAlarmSafe(
partialUid,
config.area,
config.silence,
config.generateCid,
userCredentials
);
results.push({ ...config, result });
// Small delay to avoid overwhelming the device
await new Promise(resolve => setTimeout(resolve, 200));
} catch (error) {
results.push({
...config,
result: { success: false, error: error.message }
});
}
}
return results;
}
// Emergency alarm management
class AlarmManager {
constructor(partialUid, userCredentials) {
this.partialUid = partialUid;
this.userCredentials = userCredentials;
this.activeAlarms = new Map();
}
async silenceArea(areaNumber, generateCid = true) {
try {
const result = await silenceAlarmWithCid(
this.partialUid,
areaNumber,
this.userCredentials
);
if (result.success) {
this.activeAlarms.set(areaNumber, {
silenced: true,
timestamp: new Date(),
cidGenerated: generateCid
});
}
return result;
} catch (error) {
console.error(`Failed to silence area ${areaNumber}:`, error);
throw error;
}
}
async cancelAreaAlarm(areaNumber) {
try {
const result = await cancelAlarmWithCid(
this.partialUid,
areaNumber,
this.userCredentials
);
if (result.success) {
this.activeAlarms.delete(areaNumber);
}
return result;
} catch (error) {
console.error(`Failed to cancel alarm for area ${areaNumber}:`, error);
throw error;
}
}
async silenceAllAreas(areas) {
const configs = areas.map(area => ({
area: area,
silence: true,
generateCid: true
}));
return await cancelMultipleAreas(this.partialUid, configs, this.userCredentials);
}
getSilencedAreas() {
return Array.from(this.activeAlarms.entries()).map(([area, info]) => ({
area,
...info
}));
}
isAreaSilenced(areaNumber) {
return this.activeAlarms.has(areaNumber);
}
}
// CID event monitoring (placeholder for actual CID handling)
function setupAlarmCancellationCidMonitoring(partialUid, onCidEvent) {
console.log('Setting up CID monitoring for alarm cancellation events');
// Placeholder implementation
const mockCidHandler = (cidEvent) => {
if (cidEvent.type === 'CANCEL_ALARM') {
console.log('Alarm cancellation CID event received:', cidEvent);
if (onCidEvent) {
onCidEvent(cidEvent);
}
}
};
return mockCidHandler;
}
// Usage demonstration
async function demonstrateAlarmCancellation(partialUid, userCredentials) {
try {
console.log('=== Alarm Cancellation Demonstration ===');
// Silence alarm with CID
console.log('\n--- Silencing Alarm with CID ---');
const silenceResult = await silenceAlarmWithCid(partialUid, '1', userCredentials);
console.log('Silence result:', silenceResult);
// Cancel alarm without silencing
console.log('\n--- Cancel Alarm Only ---');
const cancelResult = await cancelAlarmWithCid(partialUid, '2', userCredentials);
console.log('Cancel result:', cancelResult);
// Silence without CID
console.log('\n--- Silence Without CID ---');
const silenceNoResult = await silenceAlarmOnly(partialUid, '3', userCredentials);
console.log('Silence only result:', silenceNoResult);
// Batch operations
console.log('\n--- Batch Area Operations ---');
const areaConfigs = [
{ area: '1', silence: true, generateCid: true },
{ area: '2', silence: true, generateCid: false },
{ area: '3', silence: false, generateCid: true }
];
const batchResults = await cancelMultipleAreas(partialUid, areaConfigs, userCredentials);
console.log('Batch results:', batchResults);
// Alarm manager demo
console.log('\n--- Alarm Manager ---');
const alarmManager = new AlarmManager(partialUid, userCredentials);
await alarmManager.silenceArea('5');
await alarmManager.silenceArea('6');
console.log('Silenced areas:', alarmManager.getSilencedAreas());
console.log('Area 5 silenced:', alarmManager.isAreaSilenced('5'));
await alarmManager.cancelAreaAlarm('5');
console.log('Areas after cancellation:', alarmManager.getSilencedAreas());
// CID monitoring setup
console.log('\n--- CID Event Monitoring ---');
const cidHandler = setupAlarmCancellationCidMonitoring(partialUid, (event) => {
console.log('Alarm cancellation event:', event);
});
// Test validation
console.log('\n--- Testing Invalid Inputs ---');
try {
await cancelAlarmSafe(partialUid, 'invalid', 1, 1, userCredentials);
} catch (error) {
console.log('Expected validation error:', error.message);
}
try {
await cancelAlarmSafe(partialUid, '1', 'invalid', 1, userCredentials);
} catch (error) {
console.log('Expected validation error:', error.message);
}
console.log('Alarm cancellation demonstration completed');
} catch (error) {
console.error('Alarm cancellation demonstration failed:', error);
}
}0x0024 - Panic/Medical/Fire Event
Generates emergency events (fire, panic, or medical) with optional local alarm activation. Events are always sent to CMS (Central Monitoring Station) via CID protocol, with configurable local alarm indication.
Request/Response format
Request Format
[HEADER][0x0024][Event:Alarm] → 0701000000000000002400[EventAlarmData]Request Examples
Medical event with local alarm:
[HEADER][0x0024][0:1] // Medical event, generate local alarm
→ 0701000000000000002400303A31Fire event silent (CID only):
[HEADER][0x0024][1:0] // Fire event, CID only, no local alarm
→ 0701000000000000002400313A30Panic event with local alarm:
[HEADER][0x0024][2:1] // Panic event, generate local alarm
→ 0701000000000000002400323A31Response Format
Success Response:
[HEADER][0x0024][OK]Error Response:
[HEADER][0x0024][NACK] // Command not supported or wrong dataEvent Type Definitions
Event Types:
- 0 - Medical Event: Medical emergency or assistance required
- 1 - Fire Event: Fire alarm or smoke detection emergency
- 2 - Panic Event: Security threat or panic situation
Alarm Generation:
- 0 - Silent: CID event to CMS only, no local alarm activation
- 1 - Audible: CID event to CMS plus local alarm (sirens, strobes)
Emergency Response Behavior
CID Event Generation:
- Always Generated: All emergency events create CID messages to CMS
- Event Code: Specific Contact ID codes for each emergency type
- Timestamp: Includes accurate timestamp of event generation
- Priority: High priority transmission to monitoring stations
Local Alarm Response (Alarm=1):
- Sirens: Activate local sirens and audible alerts
- Strobes: Visual indication (if fire event)
- Panel Display: Emergency status displayed on keypads
- Notification: Immediate local emergency notification
Emergency Event Types
Medical Emergency (Event=0):
- Purpose: Medical assistance, health emergency, duress
- CID Code: Medical alarm Contact ID code
- Response: Medical/ambulance response typically required
- Priority: High priority emergency
Fire Emergency (Event=1):
- Purpose: Fire detection, smoke alarm, fire safety
- CID Code: Fire alarm Contact ID code
- Response: Fire department response typically required
- Priority: Highest priority emergency
Panic Emergency (Event=2):
- Purpose: Security threat, intrusion, personal safety
- CID Code: Panic alarm Contact ID code
- Response: Police/security response typically required
- Priority: High priority emergency
Cross-References
Related commands for emergency management:
- 0x0023 - Cancel Alarm/Silence Siren: Cancel emergency alarms
- 0x0012 - Set Fire Trouble Status: Manage fire system status
- 0x000E - Get Logs: View emergency event logs
Usage Example
javascript
async function generateEmergencyEvent(partialUid, eventType, generateAlarm) {
// Validate parameters
if (!isValidEventType(eventType)) {
throw new Error('Invalid event type. Must be 0 (medical), 1 (fire), or 2 (panic)');
}
if (!isValidAlarmFlag(generateAlarm)) {
throw new Error('Invalid alarm flag. Must be 0 (silent) or 1 (audible)');
}
// Build emergency event command
const header = "070100000000000000";
const command = "2400";
const alarmFlag = generateAlarm ? '1' : '0';
const eventData = `${eventType}:${alarmFlag}`;
const dataHex = asciiToHex(eventData);
const fullCommand = header + command + dataHex;
try {
const eventDescription = getEventTypeDescription(eventType);
const alarmDescription = generateAlarm ? 'with local alarm' : 'silent (CID only)';
console.log(`Generating ${eventDescription} ${alarmDescription}...`);
const result = await sendDeviceCommand(partialUid, fullCommand, true);
if (result.data && result.data.length > 0) {
const responseText = hexToAscii(result.data[0]);
if (responseText === 'OK') {
console.log(`${eventDescription} generated successfully`);
return {
success: true,
message: `${eventDescription} event generated`,
eventType: eventType,
eventDescription: eventDescription,
localAlarm: generateAlarm,
cidGenerated: true
};
} else if (responseText === 'NACK') {
console.log('Failed to generate emergency event');
return {
success: false,
message: 'Device returned NACK - command not supported or wrong data'
};
} else {
return {
success: false,
message: `Unexpected response: ${responseText}`
};
}
}
throw new Error('No response received from device');
} catch (error) {
console.error('Failed to generate emergency event:', error);
throw error;
}
}
// Validation functions
function isValidEventType(eventType) {
const numericType = parseInt(eventType, 10);
return !isNaN(numericType) && numericType >= 0 && numericType <= 2;
}
function isValidAlarmFlag(flag) {
return flag === 0 || flag === 1 || flag === '0' || flag === '1';
}
function getEventTypeDescription(eventType) {
const descriptions = {
0: 'Medical Emergency',
1: 'Fire Emergency',
2: 'Panic Emergency'
};
return descriptions[eventType] || `Unknown Event Type ${eventType}`;
}
function getEventTypeName(eventType) {
const names = {
0: 'Medical',
1: 'Fire',
2: 'Panic'
};
return names[eventType] || 'Unknown';
}
// Convenience functions for specific emergency types
async function generateMedicalEmergency(partialUid, audible = true) {
return await generateEmergencyEvent(partialUid, 0, audible);
}
async function generateFireEmergency(partialUid, audible = true) {
return await generateEmergencyEvent(partialUid, 1, audible);
}
async function generatePanicEmergency(partialUid, audible = true) {
return await generateEmergencyEvent(partialUid, 2, audible);
}
async function generateSilentMedical(partialUid) {
return await generateEmergencyEvent(partialUid, 0, false);
}
async function generateSilentFire(partialUid) {
return await generateEmergencyEvent(partialUid, 1, false);
}
async function generateSilentPanic(partialUid) {
return await generateEmergencyEvent(partialUid, 2, false);
}
// Safe wrapper with normalization
async function generateEmergencyEventSafe(partialUid, eventType, generateAlarm) {
try {
const normalizedType = parseInt(eventType, 10);
const normalizedAlarm = (generateAlarm === '1' || generateAlarm === 1) ? 1 : 0;
return await generateEmergencyEvent(partialUid, normalizedType, normalizedAlarm);
} catch (error) {
console.error('Validation error:', error.message);
throw error;
}
}
// Emergency management class
class EmergencyManager {
constructor(partialUid) {
this.partialUid = partialUid;
this.activeEmergencies = new Map();
this.emergencyLog = [];
}
async activateMedicalEmergency(audible = true) {
try {
const result = await generateMedicalEmergency(this.partialUid, audible);
if (result.success) {
this.recordEmergency('medical', audible, result);
}
return result;
} catch (error) {
console.error('Failed to activate medical emergency:', error);
throw error;
}
}
async activateFireEmergency(audible = true) {
try {
const result = await generateFireEmergency(this.partialUid, audible);
if (result.success) {
this.recordEmergency('fire', audible, result);
}
return result;
} catch (error) {
console.error('Failed to activate fire emergency:', error);
throw error;
}
}
async activatePanicEmergency(audible = true) {
try {
const result = await generatePanicEmergency(this.partialUid, audible);
if (result.success) {
this.recordEmergency('panic', audible, result);
}
return result;
} catch (error) {
console.error('Failed to activate panic emergency:', error);
throw error;
}
}
recordEmergency(type, audible, result) {
const emergency = {
id: Date.now(),
type: type,
audible: audible,
timestamp: new Date(),
result: result
};
this.activeEmergencies.set(emergency.id, emergency);
this.emergencyLog.push(emergency);
console.log(`Emergency recorded: ${type} (${audible ? 'audible' : 'silent'})`);
}
getActiveEmergencies() {
return Array.from(this.activeEmergencies.values());
}
getEmergencyHistory() {
return [...this.emergencyLog];
}
clearEmergencyRecord(emergencyId) {
this.activeEmergencies.delete(emergencyId);
}
getEmergencyStatistics() {
const stats = {
total: this.emergencyLog.length,
medical: 0,
fire: 0,
panic: 0,
audible: 0,
silent: 0
};
this.emergencyLog.forEach(emergency => {
stats[emergency.type]++;
if (emergency.audible) {
stats.audible++;
} else {
stats.silent++;
}
});
return stats;
}
}
// CID event monitoring for emergency events
function setupEmergencyCidMonitoring(partialUid, onEmergencyEvent) {
console.log('Setting up CID monitoring for emergency events');
// Placeholder implementation
const mockCidHandler = (cidEvent) => {
if (['MEDICAL_ALARM', 'FIRE_ALARM', 'PANIC_ALARM'].includes(cidEvent.type)) {
console.log('Emergency CID event received:', cidEvent);
if (onEmergencyEvent) {
onEmergencyEvent(cidEvent);
}
}
};
return mockCidHandler;
}
// Emergency response protocols
const EMERGENCY_PROTOCOLS = {
medical: {
priority: 'high',
responseType: 'medical',
contacts: ['ambulance', 'emergency_medical'],
initialAction: 'Verify nature of medical emergency',
followUp: 'Dispatch appropriate medical response'
},
fire: {
priority: 'critical',
responseType: 'fire',
contacts: ['fire_department', 'emergency_services'],
initialAction: 'Confirm fire/smoke detection',
followUp: 'Dispatch fire department and evacuate if needed'
},
panic: {
priority: 'high',
responseType: 'security',
contacts: ['police', 'security_services'],
initialAction: 'Assess security threat level',
followUp: 'Dispatch appropriate security response'
}
};
function getEmergencyProtocol(eventType) {
const typeNames = ['medical', 'fire', 'panic'];
const typeName = typeNames[eventType];
return EMERGENCY_PROTOCOLS[typeName] || null;
}
// Usage demonstration
async function demonstrateEmergencyEvents(partialUid) {
try {
console.log('=== Emergency Events Demonstration ===');
// Generate different emergency types
console.log('\n--- Medical Emergency (Audible) ---');
const medicalResult = await generateMedicalEmergency(partialUid, true);
console.log('Medical emergency result:', medicalResult);
console.log('\n--- Fire Emergency (Silent) ---');
const fireResult = await generateSilentFire(partialUid);
console.log('Fire emergency result:', fireResult);
console.log('\n--- Panic Emergency (Audible) ---');
const panicResult = await generatePanicEmergency(partialUid, true);
console.log('Panic emergency result:', panicResult);
// Emergency manager demo
console.log('\n--- Emergency Manager ---');
const emergencyManager = new EmergencyManager(partialUid);
await emergencyManager.activateMedicalEmergency(false); // Silent medical
await emergencyManager.activateFireEmergency(true); // Audible fire
await emergencyManager.activatePanicEmergency(true); // Audible panic
console.log('Active emergencies:', emergencyManager.getActiveEmergencies());
console.log('Emergency statistics:', emergencyManager.getEmergencyStatistics());
// Protocol information
console.log('\n--- Emergency Protocols ---');
for (let i = 0; i <= 2; i++) {
const protocol = getEmergencyProtocol(i);
if (protocol) {
console.log(`${getEventTypeDescription(i)} Protocol:`, protocol);
}
}
// CID monitoring setup
console.log('\n--- CID Event Monitoring ---');
const cidHandler = setupEmergencyCidMonitoring(partialUid, (event) => {
console.log('Emergency CID event:', event);
});
// Test validation
console.log('\n--- Testing Invalid Inputs ---');
try {
await generateEmergencyEventSafe(partialUid, 5, 1); // Invalid event type
} catch (error) {
console.log('Expected validation error:', error.message);
}
try {
await generateEmergencyEventSafe(partialUid, 1, 'invalid'); // Invalid alarm flag
} catch (error) {
console.log('Expected validation error:', error.message);
}
// Emergency response scenarios
console.log('\n--- Emergency Response Scenarios ---');
const scenarios = [
{ type: 0, audible: false, scenario: 'Silent medical duress' },
{ type: 1, audible: true, scenario: 'Fire detection with evacuation' },
{ type: 2, audible: true, scenario: 'Security intrusion with alarm' }
];
for (const scenario of scenarios) {
console.log(`\nScenario: ${scenario.scenario}`);
const result = await generateEmergencyEventSafe(partialUid, scenario.type, scenario.audible);
console.log(`Result: ${result.success ? 'Success' : 'Failed'} - ${result.message}`);
}
console.log('Emergency events demonstration completed');
} catch (error) {
console.error('Emergency events demonstration failed:', error);
}
}0x0025 - Reserved
Status: Reserved command - not currently implemented.
0x0026 - Reserved
Status: Reserved command - not currently implemented.
0x0027 - Reserved
Status: Reserved command - not currently implemented.
0x0028 - Reserved
Status: Reserved command - not currently implemented.