smart-bus 0.8.0 → 0.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,3 +1,12 @@
1
+ ## 0.9.0
2
+
3
+ * Add Get Curtain Duration commands (`0xE800`, `0xE801`)
4
+ * Add Dry Contact Multi-Channel Status command (`0x6F00`)
5
+ * Add Dry Contact broadcast status commands (`0x15D0`, `0x15D1`)
6
+ * Fix `0xE3E3` Curtain Status to support optional duration field
7
+ * Add comprehensive test coverage for new commands
8
+ * Maintain 100% backward compatibility with existing implementations
9
+
1
10
  ## 0.8.0
2
11
 
3
12
  * Update for Node.js v22 compatibility
package/lib/commands.js CHANGED
@@ -921,14 +921,29 @@ module.exports = {
921
921
  // FIXME: same as 0xE3E1
922
922
  0xE3E3: {
923
923
  parse: function(buffer) {
924
- return {
924
+ var data = {
925
925
  curtain: buffer.readUInt8(0),
926
926
  status: buffer.readUInt8(1)
927
927
  };
928
+ // Duration field (4 bytes total) - in deciseconds (0.1 second units)
929
+ if (buffer.length >= 4) {
930
+ data.duration = buffer.readUInt16BE(2) / 10;
931
+ }
932
+ return data;
928
933
  },
929
934
 
930
935
  encode: function(data) {
931
- return Buffer.from([data.curtain, data.status]);
936
+ // Support both 2-byte (legacy) and 4-byte (with duration) formats
937
+ if (data.duration !== undefined) {
938
+ var buffer = Buffer.allocUnsafe(4);
939
+ buffer.writeUInt8(data.curtain, 0);
940
+ buffer.writeUInt8(data.status, 1);
941
+ var duration = data.duration * 10;
942
+ buffer.writeUInt16BE(duration, 2);
943
+ return buffer;
944
+ } else {
945
+ return Buffer.from([data.curtain, data.status]);
946
+ }
932
947
  }
933
948
  },
934
949
 
@@ -966,6 +981,39 @@ module.exports = {
966
981
  }
967
982
  },
968
983
 
984
+ // 7.1.6 Get Curtain Duration Request
985
+ 0xE800: {
986
+ parse: function(buffer) {
987
+ return {
988
+ channel: buffer.readUInt8(0)
989
+ };
990
+ },
991
+
992
+ encode: function(data) {
993
+ return Buffer.from([data.channel]);
994
+ },
995
+
996
+ response: 0xE801
997
+ },
998
+
999
+ // 7.1.7 Get Curtain Duration Response
1000
+ 0xE801: {
1001
+ parse: function(buffer) {
1002
+ return {
1003
+ channel: buffer.readUInt8(0),
1004
+ duration: buffer.readUInt16BE(2)
1005
+ };
1006
+ },
1007
+
1008
+ encode: function(data) {
1009
+ var buffer = Buffer.allocUnsafe(4);
1010
+ buffer.writeUInt8(data.channel, 0);
1011
+ buffer.writeUInt8(0, 1); // Reserved
1012
+ buffer.writeUInt16BE(data.duration || 0, 2);
1013
+ return buffer;
1014
+ }
1015
+ },
1016
+
969
1017
  /* 8. GPRS Control */
970
1018
 
971
1019
  // 8.1.1 GPRS Control
@@ -1751,10 +1799,42 @@ module.exports = {
1751
1799
  /* 16. Dry Contact */
1752
1800
 
1753
1801
  // 16.1.1 Auto broadcast Dry Contact Status
1754
- // 0x15D0
1802
+ 0x15D0: {
1803
+ parse: function(buffer) {
1804
+ return {
1805
+ area: buffer.readUInt8(0),
1806
+ channel: buffer.readUInt8(1),
1807
+ status: buffer.readUInt8(2) === 0
1808
+ };
1809
+ },
1810
+
1811
+ encode: function(data) {
1812
+ var buffer = Buffer.allocUnsafe(3);
1813
+ buffer.writeUInt8(data.area, 0);
1814
+ buffer.writeUInt8(data.channel, 1);
1815
+ buffer.writeUInt8(data.status ? 0 : 1, 2);
1816
+ return buffer;
1817
+ }
1818
+ },
1755
1819
 
1756
1820
  // 16.1.2 Response Auto broadcast Dry Contact Status
1757
- // 0x15D1
1821
+ 0x15D1: {
1822
+ parse: function(buffer) {
1823
+ return {
1824
+ area: buffer.readUInt8(0),
1825
+ channel: buffer.readUInt8(1),
1826
+ status: buffer.readUInt8(2) === 0
1827
+ };
1828
+ },
1829
+
1830
+ encode: function(data) {
1831
+ var buffer = Buffer.allocUnsafe(3);
1832
+ buffer.writeUInt8(data.area, 0);
1833
+ buffer.writeUInt8(data.channel, 1);
1834
+ buffer.writeUInt8(data.status ? 0 : 1, 2);
1835
+ return buffer;
1836
+ }
1837
+ },
1758
1838
 
1759
1839
  // 16.1.3 Read Dry Contact Status
1760
1840
  0x15CE: {
@@ -1798,6 +1878,54 @@ module.exports = {
1798
1878
  }
1799
1879
  },
1800
1880
 
1881
+ // 16.1.5 Dry Contact Multi-Channel Status
1882
+ 0x6F00: {
1883
+ parse: function(buffer) {
1884
+ var data = {
1885
+ declaredChannels: buffer.readUInt8(1),
1886
+ statusMask: buffer.readUInt16BE(2),
1887
+ channels: []
1888
+ };
1889
+
1890
+ // Include subnet if present
1891
+ if (buffer.length >= 4) {
1892
+ data.subnet = buffer.readUInt8(0);
1893
+ }
1894
+
1895
+ // Parse 16-bit mask for up to 16 channels
1896
+ var mask = data.statusMask;
1897
+ var channels = data.channels;
1898
+
1899
+ for (var i = 0; i < 16; i++) {
1900
+ channels.push({
1901
+ number: i + 1,
1902
+ status: !!(mask & (1 << i))
1903
+ });
1904
+ }
1905
+
1906
+ return data;
1907
+ },
1908
+
1909
+ encode: function(data) {
1910
+ var buffer = Buffer.allocUnsafe(4);
1911
+ buffer.writeUInt8(data.subnet || 0, 0);
1912
+ buffer.writeUInt8(data.declaredChannels || 16, 1);
1913
+
1914
+ var mask = 0;
1915
+ var channels = data.channels || [];
1916
+
1917
+ for (var i = 0; i < channels.length; i++) {
1918
+ if (channels[i].status) {
1919
+ mask |= 1 << (i);
1920
+ }
1921
+ }
1922
+
1923
+ buffer.writeUInt16BE(mask, 2);
1924
+
1925
+ return buffer;
1926
+ }
1927
+ },
1928
+
1801
1929
  /* 17. DLP Music Play Control Command */
1802
1930
 
1803
1931
  // 17.1.1 Read Z-audio Current Status
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "smart-bus",
3
- "version": "0.8.0",
3
+ "version": "0.9.0",
4
4
  "license": "MIT",
5
5
  "description": "Node.js implementation of HDL SmartBus protocol",
6
6
  "repository": "caligo-mentis/smart-bus",
@@ -293,7 +293,7 @@ module.exports = {
293
293
  // 5.1.7 Response Read System Date and Time
294
294
  '0xDA01': {
295
295
  payload: Buffer.from('F812020C16020E01', 'hex'),
296
- data: { success: true, date: new Date(2018, 1, 12, 22, 02, 14) }
296
+ data: { success: true, date: new Date(2018, 1, 12, 22, 2, 14) }
297
297
  },
298
298
 
299
299
  // 5.1.8 Modify Read System Date and Time
@@ -374,7 +374,8 @@ module.exports = {
374
374
  // 7.1.4 Response Read Status of Curtain Switch
375
375
  '0xE3E3': [
376
376
  { payload: Buffer.from('0102', 'hex'), data: { curtain: 1, status: 2 } },
377
- { payload: Buffer.from('115A', 'hex'), data: { curtain: 17, status: 90 } }
377
+ { payload: Buffer.from('115A', 'hex'), data: { curtain: 17, status: 90 } },
378
+ { payload: Buffer.from('010201F4', 'hex'), data: { curtain: 1, status: 2, duration: 50.0 } }
378
379
  ],
379
380
 
380
381
  // 7.1.5 Broadcast Status of Status of Curtain Switches
@@ -386,6 +387,18 @@ module.exports = {
386
387
  ] }
387
388
  },
388
389
 
390
+ // 7.1.6 Get Curtain Duration Request
391
+ '0xE800': [
392
+ { payload: Buffer.from('01', 'hex'), data: { channel: 1 } },
393
+ { payload: Buffer.from('0A', 'hex'), data: { channel: 10 } }
394
+ ],
395
+
396
+ // 7.1.7 Get Curtain Duration Response
397
+ '0xE801': [
398
+ { payload: Buffer.from('01001388', 'hex'), data: { channel: 1, duration: 5000 } },
399
+ { payload: Buffer.from('030007D0', 'hex'), data: { channel: 3, duration: 2000 } }
400
+ ],
401
+
389
402
  /* 9. Panel Control */
390
403
 
391
404
  // 9.1.1 Panel Control
@@ -782,6 +795,30 @@ module.exports = {
782
795
  }
783
796
  ],
784
797
 
798
+ // 16.1.1 Auto broadcast Dry Contact Status
799
+ '0x15D0': [
800
+ {
801
+ payload: Buffer.from('010200', 'hex'),
802
+ data: { area: 1, channel: 2, status: true }
803
+ },
804
+ {
805
+ payload: Buffer.from('020301', 'hex'),
806
+ data: { area: 2, channel: 3, status: false }
807
+ }
808
+ ],
809
+
810
+ // 16.1.2 Response Auto broadcast Dry Contact Status
811
+ '0x15D1': [
812
+ {
813
+ payload: Buffer.from('010100', 'hex'),
814
+ data: { area: 1, channel: 1, status: true }
815
+ },
816
+ {
817
+ payload: Buffer.from('030401', 'hex'),
818
+ data: { area: 3, channel: 4, status: false }
819
+ }
820
+ ],
821
+
785
822
  // 16.1.3 Read Dry Contact Status
786
823
  '0x15CE': {
787
824
  payload: Buffer.from('0102', 'hex'),
@@ -800,6 +837,62 @@ module.exports = {
800
837
  }
801
838
  ],
802
839
 
840
+ // 16.1.5 Dry Contact Multi-Channel Status
841
+ '0x6F00': [
842
+ {
843
+ payload: Buffer.from('01080F00', 'hex'),
844
+ data: {
845
+ subnet: 1,
846
+ declaredChannels: 8,
847
+ statusMask: 0x0F00,
848
+ channels: [
849
+ { number: 1, status: false },
850
+ { number: 2, status: false },
851
+ { number: 3, status: false },
852
+ { number: 4, status: false },
853
+ { number: 5, status: false },
854
+ { number: 6, status: false },
855
+ { number: 7, status: false },
856
+ { number: 8, status: false },
857
+ { number: 9, status: true },
858
+ { number: 10, status: true },
859
+ { number: 11, status: true },
860
+ { number: 12, status: true },
861
+ { number: 13, status: false },
862
+ { number: 14, status: false },
863
+ { number: 15, status: false },
864
+ { number: 16, status: false }
865
+ ]
866
+ }
867
+ },
868
+ {
869
+ payload: Buffer.from('0216AA55', 'hex'),
870
+ data: {
871
+ subnet: 2,
872
+ declaredChannels: 22,
873
+ statusMask: 0xAA55,
874
+ channels: [
875
+ { number: 1, status: true },
876
+ { number: 2, status: false },
877
+ { number: 3, status: true },
878
+ { number: 4, status: false },
879
+ { number: 5, status: true },
880
+ { number: 6, status: false },
881
+ { number: 7, status: true },
882
+ { number: 8, status: false },
883
+ { number: 9, status: false },
884
+ { number: 10, status: true },
885
+ { number: 11, status: false },
886
+ { number: 12, status: true },
887
+ { number: 13, status: false },
888
+ { number: 14, status: true },
889
+ { number: 15, status: false },
890
+ { number: 16, status: true }
891
+ ]
892
+ }
893
+ }
894
+ ],
895
+
803
896
  /* XX. Undocumented Operation Codes */
804
897
 
805
898
  // XX.1 Panel