Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
602 changes: 600 additions & 2 deletions lib/clusters/doorLock.js

Large diffs are not rendered by default.

100 changes: 92 additions & 8 deletions lib/clusters/metering.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,98 @@ const Cluster = require('../Cluster');
const { ZCLDataTypes } = require('../zclTypes');

const ATTRIBUTES = {
currentSummationDelivered: { id: 0, type: ZCLDataTypes.uint48 },
currentSummationReceived: { id: 1, type: ZCLDataTypes.uint48 },
currentMaxDemandDelivered: { id: 2, type: ZCLDataTypes.uint48 },
currentMaxDemandReceived: { id: 3, type: ZCLDataTypes.uint48 },
powerFactor: { id: 6, type: ZCLDataTypes.int8 },
multiplier: { id: 769, type: ZCLDataTypes.uint24 },
divisor: { id: 770, type: ZCLDataTypes.uint24 },
instantaneousDemand: { id: 1024, type: ZCLDataTypes.int24 },
// Reading Information Set (0x00 - 0x0F)
currentSummationDelivered: { id: 0x0000, type: ZCLDataTypes.uint48 }, // Mandatory
currentSummationReceived: { id: 0x0001, type: ZCLDataTypes.uint48 },
currentMaxDemandDelivered: { id: 0x0002, type: ZCLDataTypes.uint48 },
currentMaxDemandReceived: { id: 0x0003, type: ZCLDataTypes.uint48 },
dftSummation: { id: 0x0004, type: ZCLDataTypes.uint48 },
dailyFreezeTime: { id: 0x0005, type: ZCLDataTypes.uint16 },
powerFactor: { id: 0x0006, type: ZCLDataTypes.int8 },
readingSnapShotTime: { id: 0x0007, type: ZCLDataTypes.uint32 }, // UTC time
currentMaxDemandDeliveredTime: { id: 0x0008, type: ZCLDataTypes.uint32 }, // UTC time
currentMaxDemandReceivedTime: { id: 0x0009, type: ZCLDataTypes.uint32 }, // UTC time
defaultUpdatePeriod: { id: 0x000A, type: ZCLDataTypes.uint8 },
fastPollUpdatePeriod: { id: 0x000B, type: ZCLDataTypes.uint8 },
currentBlockPeriodConsumptionDelivered: { id: 0x000C, type: ZCLDataTypes.uint48 },
dailyConsumptionTarget: { id: 0x000D, type: ZCLDataTypes.uint24 },
currentBlock: { id: 0x000E, type: ZCLDataTypes.enum8 },
profileIntervalPeriod: { id: 0x000F, type: ZCLDataTypes.enum8 },

// Summation TOU Information Set (0x0100 - 0x01FF)
currentTier1SummationDelivered: { id: 0x0100, type: ZCLDataTypes.uint48 },
currentTier1SummationReceived: { id: 0x0101, type: ZCLDataTypes.uint48 },
currentTier2SummationDelivered: { id: 0x0102, type: ZCLDataTypes.uint48 },
currentTier2SummationReceived: { id: 0x0103, type: ZCLDataTypes.uint48 },
currentTier3SummationDelivered: { id: 0x0104, type: ZCLDataTypes.uint48 },
currentTier3SummationReceived: { id: 0x0105, type: ZCLDataTypes.uint48 },
currentTier4SummationDelivered: { id: 0x0106, type: ZCLDataTypes.uint48 },
currentTier4SummationReceived: { id: 0x0107, type: ZCLDataTypes.uint48 },

// Meter Status (0x0200 - 0x02FF)
status: { id: 0x0200, type: ZCLDataTypes.map8 }, // MeterStatus bitmap
remainingBatteryLife: { id: 0x0201, type: ZCLDataTypes.uint8 },
hoursInOperation: { id: 0x0202, type: ZCLDataTypes.uint24 },
hoursInFault: { id: 0x0203, type: ZCLDataTypes.uint24 },
extendedStatus: { id: 0x0204, type: ZCLDataTypes.map64 },

// Formatting Set (0x0300 - 0x03FF) - Critical for value interpretation
unitOfMeasure: { id: 0x0300, type: ZCLDataTypes.enum8 }, // Mandatory: kWh, m³, etc.
multiplier: { id: 0x0301, type: ZCLDataTypes.uint24 },
divisor: { id: 0x0302, type: ZCLDataTypes.uint24 },
summationFormatting: { id: 0x0303, type: ZCLDataTypes.map8 }, // Mandatory: decimal places
demandFormatting: { id: 0x0304, type: ZCLDataTypes.map8 },
historicalConsumptionFormatting: { id: 0x0305, type: ZCLDataTypes.map8 },
meteringDeviceType: { id: 0x0306, type: ZCLDataTypes.map8 }, // Mandatory: Electric/Gas/Water
siteId: { id: 0x0307, type: ZCLDataTypes.octstr },
meterSerialNumber: { id: 0x0308, type: ZCLDataTypes.octstr },
energyCarrierUnitOfMeasure: { id: 0x0309, type: ZCLDataTypes.enum8 },
energyCarrierSummationFormatting: { id: 0x030A, type: ZCLDataTypes.map8 },
energyCarrierDemandFormatting: { id: 0x030B, type: ZCLDataTypes.map8 },
temperatureUnitOfMeasure: { id: 0x030C, type: ZCLDataTypes.enum8 },
temperatureFormatting: { id: 0x030D, type: ZCLDataTypes.map8 },
moduleSerialNumber: { id: 0x030E, type: ZCLDataTypes.octstr },
operatingTariffLabelDelivered: { id: 0x030F, type: ZCLDataTypes.octstr },
operatingTariffLabelReceived: { id: 0x0310, type: ZCLDataTypes.octstr },
customerIdNumber: { id: 0x0311, type: ZCLDataTypes.octstr },
alternativeUnitOfMeasure: { id: 0x0312, type: ZCLDataTypes.enum8 },
alternativeDemandFormatting: { id: 0x0313, type: ZCLDataTypes.map8 },
alternativeConsumptionFormatting: { id: 0x0314, type: ZCLDataTypes.map8 },

// Historical Consumption (0x0400 - 0x04FF)
instantaneousDemand: { id: 0x0400, type: ZCLDataTypes.int24 },
currentDayConsumptionDelivered: { id: 0x0401, type: ZCLDataTypes.uint24 },
currentDayConsumptionReceived: { id: 0x0402, type: ZCLDataTypes.uint24 },
previousDayConsumptionDelivered: { id: 0x0403, type: ZCLDataTypes.uint24 },
previousDayConsumptionReceived: { id: 0x0404, type: ZCLDataTypes.uint24 },
currentPartialProfileIntervalStartTimeDelivered: { id: 0x0405, type: ZCLDataTypes.uint32 },
currentPartialProfileIntervalStartTimeReceived: { id: 0x0406, type: ZCLDataTypes.uint32 },
currentPartialProfileIntervalValueDelivered: { id: 0x0407, type: ZCLDataTypes.uint24 },
currentPartialProfileIntervalValueReceived: { id: 0x0408, type: ZCLDataTypes.uint24 },
currentDayMaxPressure: { id: 0x0409, type: ZCLDataTypes.uint48 },
currentDayMinPressure: { id: 0x040A, type: ZCLDataTypes.uint48 },
previousDayMaxPressure: { id: 0x040B, type: ZCLDataTypes.uint48 },
previousDayMinPressure: { id: 0x040C, type: ZCLDataTypes.uint48 },
currentDayMaxDemand: { id: 0x040D, type: ZCLDataTypes.int24 },
previousDayMaxDemand: { id: 0x040E, type: ZCLDataTypes.int24 },
currentMonthMaxDemand: { id: 0x040F, type: ZCLDataTypes.int24 },
currentYearMaxDemand: { id: 0x0410, type: ZCLDataTypes.int24 },
currentDayMaxEnergyCarrierDemand: { id: 0x0411, type: ZCLDataTypes.int24 },
previousDayMaxEnergyCarrierDemand: { id: 0x0412, type: ZCLDataTypes.int24 },
currentMonthMaxEnergyCarrierDemand: { id: 0x0413, type: ZCLDataTypes.int24 },
currentMonthMinEnergyCarrierDemand: { id: 0x0414, type: ZCLDataTypes.int24 },
currentYearMaxEnergyCarrierDemand: { id: 0x0415, type: ZCLDataTypes.int24 },
currentYearMinEnergyCarrierDemand: { id: 0x0416, type: ZCLDataTypes.int24 },

// Load Profile Configuration (0x0500 - 0x05FF)
maxNumberOfPeriodsDelivered: { id: 0x0500, type: ZCLDataTypes.uint8 },

// Supply Limit (0x0600 - 0x06FF)
currentDemandDelivered: { id: 0x0600, type: ZCLDataTypes.uint24 },
demandLimit: { id: 0x0601, type: ZCLDataTypes.uint24 },
demandIntegrationPeriod: { id: 0x0602, type: ZCLDataTypes.uint8 },
numberOfDemandSubintervals: { id: 0x0603, type: ZCLDataTypes.uint8 },
demandLimitArmDuration: { id: 0x0604, type: ZCLDataTypes.uint16 },
};

const COMMANDS = {};
Expand Down
31 changes: 23 additions & 8 deletions lib/clusters/occupancySensing.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,21 +4,36 @@ const Cluster = require('../Cluster');
const { ZCLDataTypes } = require('../zclTypes');

const ATTRIBUTES = {
occupancy: { id: 0, type: ZCLDataTypes.map8('occupied') }, // TODO: verify this bitmap
// Occupancy Sensor Information (0x0000 - 0x000F)
occupancy: { id: 0x0000, type: ZCLDataTypes.map8('occupied') }, // Mandatory: bit 0 = occupied
occupancySensorType: {
id: 1,
id: 0x0001, // Mandatory
type: ZCLDataTypes.enum8({
pir: 0, // 0x00 PIR
ultrasonic: 1, // 0x01 Ultrasonic
pirAndUltrasonic: 2, // 0x02 PIR and ultrasonic
physicalContact: 3, // 0x03 Physical Contact
}),
},
pirOccupiedToUnoccupiedDelay: { id: 16, type: ZCLDataTypes.uint16 },
pirUnoccupiedToOccupiedDelay: { id: 17, type: ZCLDataTypes.uint16 },
pirUnoccupiedToOccupiedThreshold: { id: 18, type: ZCLDataTypes.uint8 },
ultrasonicOccupiedToUnoccupiedDelay: { id: 32, type: ZCLDataTypes.uint16 },
ultrasonicUnoccupiedToOccupiedDelay: { id: 33, type: ZCLDataTypes.uint16 },
ultrasonicUnoccupiedToOccupiedThreshold: { id: 34, type: ZCLDataTypes.uint8 },
occupancySensorTypeBitmap: {
id: 0x0002, // Mandatory
type: ZCLDataTypes.map8('pir', 'ultrasonic', 'physicalContact'),
},

// PIR Configuration (0x0010 - 0x001F)
pirOccupiedToUnoccupiedDelay: { id: 0x0010, type: ZCLDataTypes.uint16 },
pirUnoccupiedToOccupiedDelay: { id: 0x0011, type: ZCLDataTypes.uint16 },
pirUnoccupiedToOccupiedThreshold: { id: 0x0012, type: ZCLDataTypes.uint8 },

// Ultrasonic Configuration (0x0020 - 0x002F)
ultrasonicOccupiedToUnoccupiedDelay: { id: 0x0020, type: ZCLDataTypes.uint16 },
ultrasonicUnoccupiedToOccupiedDelay: { id: 0x0021, type: ZCLDataTypes.uint16 },
ultrasonicUnoccupiedToOccupiedThreshold: { id: 0x0022, type: ZCLDataTypes.uint8 },

// Physical Contact Configuration (0x0030 - 0x003F)
physicalContactOccupiedToUnoccupiedDelay: { id: 0x0030, type: ZCLDataTypes.uint16 },
physicalContactUnoccupiedToOccupiedDelay: { id: 0x0031, type: ZCLDataTypes.uint16 },
physicalContactUnoccupiedToOccupiedThreshold: { id: 0x0032, type: ZCLDataTypes.uint8 },
};

const COMMANDS = {};
Expand Down
53 changes: 43 additions & 10 deletions lib/clusters/windowCovering.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,9 @@ const Cluster = require('../Cluster');
const { ZCLDataTypes } = require('../zclTypes');

const ATTRIBUTES = {
// Window Covering Information (0x0000 - 0x000F)
windowCoveringType: {
id: 0,
id: 0x0000,
type: ZCLDataTypes.enum8({
rollershade: 0,
rollershade2Motor: 1,
Expand All @@ -19,15 +20,47 @@ const ATTRIBUTES = {
projectorScreen: 9,
}),
},
physicalClosedLimitLift: { id: 1, type: ZCLDataTypes.uint16 },
physicalClosedLimitTilt: { id: 2, type: ZCLDataTypes.uint16 },
currentPositionLift: { id: 3, type: ZCLDataTypes.uint16 },
currentPositionTilt: { id: 4, type: ZCLDataTypes.uint16 },
numberofActuationsLift: { id: 5, type: ZCLDataTypes.uint16 },
numberofActuationsTilt: { id: 6, type: ZCLDataTypes.uint16 },
configStatus: { id: 7, type: ZCLDataTypes.map8('operational', 'online', 'reversalLiftCommands', 'controlLift', 'controlTilt', 'encoderLift', 'encoderTilt', 'reserved') },
currentPositionLiftPercentage: { id: 8, type: ZCLDataTypes.uint8 },
currentPositionTiltPercentage: { id: 9, type: ZCLDataTypes.uint8 },
physicalClosedLimitLift: { id: 0x0001, type: ZCLDataTypes.uint16 },
physicalClosedLimitTilt: { id: 0x0002, type: ZCLDataTypes.uint16 },
currentPositionLift: { id: 0x0003, type: ZCLDataTypes.uint16 },
currentPositionTilt: { id: 0x0004, type: ZCLDataTypes.uint16 },
numberofActuationsLift: { id: 0x0005, type: ZCLDataTypes.uint16 },
numberofActuationsTilt: { id: 0x0006, type: ZCLDataTypes.uint16 },
configStatus: {
id: 0x0007,
type: ZCLDataTypes.map8(
'operational',
'online',
'reversalLiftCommands',
'controlLift',
'controlTilt',
'encoderLift',
'encoderTilt',
'reserved',
),
},
currentPositionLiftPercentage: { id: 0x0008, type: ZCLDataTypes.uint8 },
currentPositionTiltPercentage: { id: 0x0009, type: ZCLDataTypes.uint8 },

// Settings (0x0010 - 0x001F)
installedOpenLimitLift: { id: 0x0010, type: ZCLDataTypes.uint16 },
installedClosedLimitLift: { id: 0x0011, type: ZCLDataTypes.uint16 },
installedOpenLimitTilt: { id: 0x0012, type: ZCLDataTypes.uint16 },
installedClosedLimitTilt: { id: 0x0013, type: ZCLDataTypes.uint16 },
velocityLift: { id: 0x0014, type: ZCLDataTypes.uint16 },
accelerationTimeLift: { id: 0x0015, type: ZCLDataTypes.uint16 },
decelerationTimeLift: { id: 0x0016, type: ZCLDataTypes.uint16 },
mode: {
id: 0x0017, // Mandatory
type: ZCLDataTypes.map8(
'motorDirectionReversed',
'calibrationMode',
'maintenanceMode',
'ledFeedback',
),
},
intermediateSetpointsLift: { id: 0x0018, type: ZCLDataTypes.octstr },
intermediateSetpointsTilt: { id: 0x0019, type: ZCLDataTypes.octstr },
};

const COMMANDS = {
Expand Down
110 changes: 110 additions & 0 deletions test/colorControl.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
// eslint-disable-next-line max-classes-per-file,lines-around-directive
'use strict';

const assert = require('assert');
const BoundCluster = require('../lib/BoundCluster');
const ColorControlCluster = require('../lib/clusters/colorControl');
const Node = require('../lib/Node');
const { ZCLStandardHeader } = require('../lib/zclFrames');

const endpointId = 1;

describe('Color Control', function() {
it('should receive moveToColor', function(done) {
const node = new Node({
sendFrame: () => null,
endpointDescriptors: [{
endpointId,
inputClusters: [ColorControlCluster.ID],
}],
});

node.endpoints[endpointId].bind(
ColorControlCluster.NAME,
new (class extends BoundCluster {

async moveToColor(data) {
assert.strictEqual(data.colorX, 0x5000);
assert.strictEqual(data.colorY, 0x3000);
assert.strictEqual(data.transitionTime, 20);
done();
}

})(),
);

const frame = new ZCLStandardHeader();
frame.cmdId = ColorControlCluster.COMMANDS.moveToColor.id;
frame.frameControl.directionToClient = false;
frame.frameControl.clusterSpecific = true;
// colorX (uint16 LE): 0x5000, colorY (uint16 LE): 0x3000, transitionTime (uint16 LE): 20
frame.data = Buffer.from([0x00, 0x50, 0x00, 0x30, 0x14, 0x00]);

node.handleFrame(endpointId, ColorControlCluster.ID, frame.toBuffer(), {});
});

it('should receive moveToColorTemperature', function(done) {
const node = new Node({
sendFrame: () => null,
endpointDescriptors: [{
endpointId,
inputClusters: [ColorControlCluster.ID],
}],
});

node.endpoints[endpointId].bind(
ColorControlCluster.NAME,
new (class extends BoundCluster {

async moveToColorTemperature(data) {
assert.strictEqual(data.colorTemperature, 370);
assert.strictEqual(data.transitionTime, 15);
done();
}

})(),
);

const frame = new ZCLStandardHeader();
frame.cmdId = ColorControlCluster.COMMANDS.moveToColorTemperature.id;
frame.frameControl.directionToClient = false;
frame.frameControl.clusterSpecific = true;
// colorTemperature (uint16 LE): 370 = 0x0172, transitionTime (uint16 LE): 15 = 0x000F
frame.data = Buffer.from([0x72, 0x01, 0x0F, 0x00]);

node.handleFrame(endpointId, ColorControlCluster.ID, frame.toBuffer(), {});
});

it('should receive moveToHueAndSaturation', function(done) {
const node = new Node({
sendFrame: () => null,
endpointDescriptors: [{
endpointId,
inputClusters: [ColorControlCluster.ID],
}],
});

node.endpoints[endpointId].bind(
ColorControlCluster.NAME,
new (class extends BoundCluster {

async moveToHueAndSaturation(data) {
assert.strictEqual(data.hue, 180);
assert.strictEqual(data.saturation, 200);
assert.strictEqual(data.transitionTime, 10);
done();
}

})(),
);

const frame = new ZCLStandardHeader();
frame.cmdId = ColorControlCluster.COMMANDS.moveToHueAndSaturation.id;
frame.frameControl.directionToClient = false;
frame.frameControl.clusterSpecific = true;
// hue (uint8): 180, saturation (uint8): 200, transitionTime (uint16 LE): 10
frame.data = Buffer.from([0xB4, 0xC8, 0x0A, 0x00]);

node.handleFrame(endpointId, ColorControlCluster.ID, frame.toBuffer(), {});
});
});
Loading