@enyo-energy/sunspec-sdk 0.0.73 → 0.0.75

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/README.md CHANGED
@@ -332,7 +332,11 @@ See [`@enyo-energy/appliance-calibration`'s README](https://www.npmjs.com/packag
332
332
  3. Activates the first entry immediately, then advances to subsequent entries as their `seconds` offsets elapse on a 1-second tick (driven by `EnergyApp.useInterval()`).
333
333
  4. Each `Charge` entry writes `chaGriSet=GRID`, `inWRte = powerW / installedWChaMax × 100`, `storCtlMod=CHARGE`.
334
334
  5. Each `Discharge` entry writes `chaGriSet=PV`, `outWRte = powerW / installedWChaMax × 100`, `storCtlMod=DISCHARGE`.
335
- 6. On `mode: auto`, a replacement schedule, `disconnect()`, or process restart with an active schedule still persisted, the SDK restores the snapshotted pre-schedule registers via `onRollback`.
335
+ 6. Restores the snapshotted pre-schedule registers (`storCtlMod`, `chaGriSet`, `wChaMax`, `inWRte`, `outWRte`) **only on `mode: auto`, `disconnect()`, or process-restart recovery** not when one `mode: schedule` is replaced by another. Schedule-to-schedule replacement keeps the device on the active register set and lets the new schedule's first entry take over directly, so consecutive schedules don't leak a `storCtlMod=AUTO` (`0x3`) write between them. The pre-schedule snapshot stays sticky across every replacement until one of the three terminal events fires.
336
+
337
+ ### Register write order
338
+
339
+ `writeBatteryControls` issues each register write sequentially in this order so the device never sees a stale-parameter window when the control mode changes: `chaGriSet → wChaMax → inWRte → outWRte → minRsvPct → storCtlMod`. Source pin and the limit/rate parameters land first; the control mode is written last so the device only "starts acting" once every governing value is fresh.
336
340
 
337
341
  ### Power cap
338
342
 
@@ -18,6 +18,23 @@ class SunspecBatteryScheduleHandler extends storage_schedule_handler_js_1.Storag
18
18
  getSnapshotService;
19
19
  onErrorCallback;
20
20
  installedWChaMax;
21
+ /**
22
+ * Sticky pre-schedule snapshot, captured once on the first `onInit` and
23
+ * held across every subsequent schedule-to-schedule replacement. Cleared
24
+ * only when a real rollback fires (`mode: auto` from the data bus or
25
+ * `dispose`). Lets the eventual restore write the *true* baseline rather
26
+ * than the last-active-entry register set that the device happens to be
27
+ * in at the moment of replacement.
28
+ */
29
+ originalBaseline;
30
+ /**
31
+ * Set inside the overridden `applyMessage` when an incoming
32
+ * `mode: schedule` message would replace an already-running schedule.
33
+ * Consumed (and reset) inside `onRollback` so the base library's
34
+ * automatic `doReleaseSchedule → onRollback` step does not actually
35
+ * write the snapshot back during a replacement.
36
+ */
37
+ suppressNextRollbackWrite = false;
21
38
  constructor(opts) {
22
39
  super(opts);
23
40
  this.sunspecClient = opts.sunspecClient;
@@ -26,19 +43,53 @@ class SunspecBatteryScheduleHandler extends storage_schedule_handler_js_1.Storag
26
43
  this.getSnapshotService = opts.getSnapshotService;
27
44
  this.onErrorCallback = opts.onErrorCallback;
28
45
  }
46
+ /**
47
+ * Override the base-class data-bus router so a schedule-to-schedule
48
+ * replacement marks the next `onRollback` as "skip the write". The base
49
+ * library still owns the actual schedule lifecycle — we just steer one
50
+ * decision inside `onRollback`. `mode: auto` and any path that does not
51
+ * have an active schedule fall through unchanged, so the rollback fires
52
+ * normally there.
53
+ */
54
+ async applyMessage(msg) {
55
+ if (msg.applianceId === this.applianceIdForLog
56
+ && msg.data.mode === enyo_data_bus_value_js_1.EnyoStorageScheduleModeEnum.Schedule
57
+ && this.getActiveEntry() !== undefined) {
58
+ this.suppressNextRollbackWrite = true;
59
+ }
60
+ try {
61
+ await super.applyMessage(msg);
62
+ }
63
+ finally {
64
+ // Defence in depth: if the base class skipped the rollback for
65
+ // any reason (validation error, disposed, etc.), the flag would
66
+ // otherwise stay set and silently swallow the next real rollback.
67
+ this.suppressNextRollbackWrite = false;
68
+ }
69
+ }
29
70
  async onInit(_active) {
71
+ if (this.originalBaseline !== undefined) {
72
+ // Replacement install: keep handing the library the sticky
73
+ // baseline so the persisted snapshot continues to point at the
74
+ // original pre-schedule state. The library will overwrite its
75
+ // storage row with the same value — harmless.
76
+ this.installedWChaMax = this.originalBaseline.wChaMax;
77
+ return { ...this.originalBaseline };
78
+ }
30
79
  const controls = await this.sunspecClient.readBatteryControls(this.unitId);
31
80
  if (!controls) {
32
81
  throw new Error(`SunspecBatteryScheduleHandler ${this.applianceIdForLog}: failed to read pre-schedule controls`);
33
82
  }
34
- this.installedWChaMax = controls.wChaMax;
35
- return {
83
+ const baseline = {
36
84
  storCtlMod: controls.storCtlMod,
37
85
  chaGriSet: controls.chaGriSet,
38
86
  wChaMax: controls.wChaMax,
39
87
  inWRte: controls.inWRte,
40
88
  outWRte: controls.outWRte,
41
89
  };
90
+ this.originalBaseline = baseline;
91
+ this.installedWChaMax = baseline.wChaMax;
92
+ return { ...baseline };
42
93
  }
43
94
  onChange(active, _previous) {
44
95
  void this.applyEntry(active).catch(err => {
@@ -46,6 +97,25 @@ class SunspecBatteryScheduleHandler extends storage_schedule_handler_js_1.Storag
46
97
  });
47
98
  }
48
99
  onRollback(registers) {
100
+ if (this.suppressNextRollbackWrite) {
101
+ // Schedule-to-schedule replacement. Do NOT write the snapshot
102
+ // back; the new schedule's first `onChange` will own the
103
+ // register state. `installedWChaMax` stays valid (set in
104
+ // onInit) so the very next applyEntry has a baseline to divide
105
+ // against — this is the fix for the
106
+ // "no usable wChaMax baseline (installedWChaMax=undefined)"
107
+ // race seen in production logs.
108
+ this.suppressNextRollbackWrite = false;
109
+ return;
110
+ }
111
+ // Real rollback (mode: auto, dispose, restart recovery). Clear the
112
+ // sticky baseline so the next `onInit` re-reads the device state.
113
+ // Important: clear synchronously here — NOT in the async finally
114
+ // that follows — so a concurrent `onInit` queued behind this one
115
+ // (impossible today, but defensive) cannot observe a half-cleared
116
+ // state.
117
+ this.originalBaseline = undefined;
118
+ this.installedWChaMax = undefined;
49
119
  void (async () => {
50
120
  try {
51
121
  await this.sunspecClient.writeBatteryControls(this.unitId, registers);
@@ -56,9 +126,6 @@ class SunspecBatteryScheduleHandler extends storage_schedule_handler_js_1.Storag
56
126
  catch (err) {
57
127
  console.error(`SunspecBatteryScheduleHandler ${this.applianceIdForLog}: onRollback failed: ${err}`);
58
128
  }
59
- finally {
60
- this.installedWChaMax = undefined;
61
- }
62
129
  })();
63
130
  }
64
131
  onError(err) {
@@ -1,4 +1,5 @@
1
1
  import { StorageScheduleHandler, type ActiveStorageScheduleEntry, type StorageScheduleHandlerOptions } from "@enyo-energy/energy-app-sdk/dist/implementations/storage/storage-schedule-handler.js";
2
+ import { type EnyoDataBusSetStorageScheduleV1 } from "@enyo-energy/energy-app-sdk/dist/types/enyo-data-bus-value.js";
2
3
  import { type SnapshotService } from "@enyo-energy/appliance-calibration";
3
4
  import { type SunspecBatteryControls } from "./sunspec-interfaces.cjs";
4
5
  /**
@@ -58,7 +59,33 @@ export declare class SunspecBatteryScheduleHandler extends StorageScheduleHandle
58
59
  private readonly getSnapshotService;
59
60
  private readonly onErrorCallback?;
60
61
  private installedWChaMax;
62
+ /**
63
+ * Sticky pre-schedule snapshot, captured once on the first `onInit` and
64
+ * held across every subsequent schedule-to-schedule replacement. Cleared
65
+ * only when a real rollback fires (`mode: auto` from the data bus or
66
+ * `dispose`). Lets the eventual restore write the *true* baseline rather
67
+ * than the last-active-entry register set that the device happens to be
68
+ * in at the moment of replacement.
69
+ */
70
+ private originalBaseline?;
71
+ /**
72
+ * Set inside the overridden `applyMessage` when an incoming
73
+ * `mode: schedule` message would replace an already-running schedule.
74
+ * Consumed (and reset) inside `onRollback` so the base library's
75
+ * automatic `doReleaseSchedule → onRollback` step does not actually
76
+ * write the snapshot back during a replacement.
77
+ */
78
+ private suppressNextRollbackWrite;
61
79
  constructor(opts: SunspecBatteryScheduleHandlerOptions);
80
+ /**
81
+ * Override the base-class data-bus router so a schedule-to-schedule
82
+ * replacement marks the next `onRollback` as "skip the write". The base
83
+ * library still owns the actual schedule lifecycle — we just steer one
84
+ * decision inside `onRollback`. `mode: auto` and any path that does not
85
+ * have an active schedule fall through unchanged, so the rollback fires
86
+ * normally there.
87
+ */
88
+ applyMessage(msg: EnyoDataBusSetStorageScheduleV1): Promise<void>;
62
89
  protected onInit(_active: ActiveStorageScheduleEntry): Promise<SunspecScheduleRegisters>;
63
90
  protected onChange(active: ActiveStorageScheduleEntry, _previous: ActiveStorageScheduleEntry | undefined): void;
64
91
  protected onRollback(registers: SunspecScheduleRegisters): void;
@@ -711,8 +711,6 @@ class SunspecInverter extends BaseSunspecDevice {
711
711
  this.dataBus = this.energyApp.useDataBus();
712
712
  this.dataBusListenerId = this.dataBus.listenForMessages([
713
713
  enyo_data_bus_value_js_1.EnyoDataBusMessageEnum.SetInverterFeedInLimitV1,
714
- enyo_data_bus_value_js_1.EnyoDataBusMessageEnum.StartCalibrationV1,
715
- enyo_data_bus_value_js_1.EnyoDataBusMessageEnum.StopCalibrationV1,
716
714
  ], (entry) => this.handleInverterCommand(entry));
717
715
  console.log(`Inverter ${this.applianceId}: started data bus listening (listener ${this.dataBusListenerId})`);
718
716
  }
@@ -737,12 +735,6 @@ class SunspecInverter extends BaseSunspecDevice {
737
735
  case enyo_data_bus_value_js_1.EnyoDataBusMessageEnum.SetInverterFeedInLimitV1:
738
736
  await this.handleSetFeedInLimit(entry);
739
737
  break;
740
- case enyo_data_bus_value_js_1.EnyoDataBusMessageEnum.StartCalibrationV1:
741
- await this.handleStartCalibration(entry);
742
- break;
743
- case enyo_data_bus_value_js_1.EnyoDataBusMessageEnum.StopCalibrationV1:
744
- await this.handleStopCalibration(entry);
745
- break;
746
738
  }
747
739
  }
748
740
  catch (error) {
@@ -788,51 +780,6 @@ class SunspecInverter extends BaseSunspecDevice {
788
780
  await this.snapshotService.initialize();
789
781
  console.log(`Inverter ${this.applianceId}: snapshot service initialized`);
790
782
  }
791
- async handleStartCalibration(msg) {
792
- if (!this.isConnected() || !this.applianceId) {
793
- this.sendCommandAcknowledge(msg.id, msg.message, enyo_data_bus_value_js_1.EnyoCommandAcknowledgeAnswerEnum.Rejected, 'Not connected');
794
- return;
795
- }
796
- if (!this.snapshotService) {
797
- await this.initSnapshotService();
798
- }
799
- if (!this.snapshotService) {
800
- this.sendCommandAcknowledge(msg.id, msg.message, enyo_data_bus_value_js_1.EnyoCommandAcknowledgeAnswerEnum.Rejected, 'Snapshot service unavailable');
801
- return;
802
- }
803
- if (this.snapshotService.isCalibrating()) {
804
- console.log(`Inverter ${this.applianceId}: calibration already active — ack idempotently`);
805
- this.sendCommandAcknowledge(msg.id, msg.message, enyo_data_bus_value_js_1.EnyoCommandAcknowledgeAnswerEnum.Accepted);
806
- return;
807
- }
808
- console.log(`Inverter ${this.applianceId}: handling StartCalibrationV1`);
809
- const controls = await this.sunspecClient.readInverterControls(this.unitId);
810
- if (!controls) {
811
- this.sendCommandAcknowledge(msg.id, msg.message, enyo_data_bus_value_js_1.EnyoCommandAcknowledgeAnswerEnum.Rejected, 'Failed to read inverter controls for snapshot');
812
- return;
813
- }
814
- try {
815
- await this.snapshotService.startCalibration(controls);
816
- }
817
- catch (error) {
818
- this.sendCommandAcknowledge(msg.id, msg.message, enyo_data_bus_value_js_1.EnyoCommandAcknowledgeAnswerEnum.Rejected, `Failed to persist snapshot: ${error}`);
819
- return;
820
- }
821
- this.sendCommandAcknowledge(msg.id, msg.message, enyo_data_bus_value_js_1.EnyoCommandAcknowledgeAnswerEnum.Accepted);
822
- }
823
- async handleStopCalibration(msg) {
824
- if (!this.applianceId) {
825
- this.sendCommandAcknowledge(msg.id, msg.message, enyo_data_bus_value_js_1.EnyoCommandAcknowledgeAnswerEnum.Rejected, 'Not initialized');
826
- return;
827
- }
828
- console.log(`Inverter ${this.applianceId}: handling StopCalibrationV1`);
829
- // SnapshotService.stopCalibration fires `restoreInverterSnapshot` internally with
830
- // reason="stop" before returning. Any restore failure is logged by the callback
831
- // (the persisted snapshot is already gone by then — see library notes) so we
832
- // always ack Accepted from here.
833
- await this.snapshotService?.stopCalibration();
834
- this.sendCommandAcknowledge(msg.id, msg.message, enyo_data_bus_value_js_1.EnyoCommandAcknowledgeAnswerEnum.Accepted);
835
- }
836
783
  /**
837
784
  * `onRestore` callback for the inverter's {@link SnapshotService}. Writes only the
838
785
  * subset of writable inverter fields that other commands actually touched during the
@@ -1371,7 +1318,10 @@ class SunspecBattery extends BaseSunspecDevice {
1371
1318
  console.error('Battery not connected');
1372
1319
  return false;
1373
1320
  }
1374
- console.log('Writing battery controls:', controls);
1321
+ // Redundant with the modbus client's own per-write debug trace. Keep at debug
1322
+ // so the high-level info logs (setStorageMode / enableGridCharging / etc.) stay
1323
+ // the only info-level signal per battery-control action.
1324
+ console.debug('Writing battery controls:', controls);
1375
1325
  return this.sunspecClient.writeBatteryControls(this.unitId, controls);
1376
1326
  }
1377
1327
  mapToEnyoStorageMode(storageMode) {
@@ -1469,8 +1419,6 @@ class SunspecBattery extends BaseSunspecDevice {
1469
1419
  this.dataBus = this.energyApp.useDataBus();
1470
1420
  this.dataBusListenerId = this.dataBus.listenForMessages([
1471
1421
  enyo_data_bus_value_js_1.EnyoDataBusMessageEnum.SetStorageScheduleV1,
1472
- enyo_data_bus_value_js_1.EnyoDataBusMessageEnum.StartCalibrationV1,
1473
- enyo_data_bus_value_js_1.EnyoDataBusMessageEnum.StopCalibrationV1,
1474
1422
  ], (entry) => this.handleStorageCommand(entry));
1475
1423
  if (!this.applianceId) {
1476
1424
  throw new Error("SunspecBattery.startDataBusListening: applianceId required — call connect() first.");
@@ -1509,12 +1457,6 @@ class SunspecBattery extends BaseSunspecDevice {
1509
1457
  case enyo_data_bus_value_js_1.EnyoDataBusMessageEnum.SetStorageScheduleV1:
1510
1458
  this.handleSetStorageScheduleAck(entry);
1511
1459
  break;
1512
- case enyo_data_bus_value_js_1.EnyoDataBusMessageEnum.StartCalibrationV1:
1513
- await this.handleStartCalibration(entry);
1514
- break;
1515
- case enyo_data_bus_value_js_1.EnyoDataBusMessageEnum.StopCalibrationV1:
1516
- await this.handleStopCalibration(entry);
1517
- break;
1518
1460
  }
1519
1461
  }
1520
1462
  catch (error) {
@@ -1554,51 +1496,6 @@ class SunspecBattery extends BaseSunspecDevice {
1554
1496
  await this.snapshotService.initialize();
1555
1497
  console.log(`Battery ${this.applianceId}: snapshot service initialized`);
1556
1498
  }
1557
- async handleStartCalibration(msg) {
1558
- if (!this.isConnected() || !this.applianceId) {
1559
- this.sendCommandAcknowledge(msg.id, msg.message, enyo_data_bus_value_js_1.EnyoCommandAcknowledgeAnswerEnum.Rejected, 'Not connected');
1560
- return;
1561
- }
1562
- if (!this.snapshotService) {
1563
- await this.initSnapshotService();
1564
- }
1565
- if (!this.snapshotService) {
1566
- this.sendCommandAcknowledge(msg.id, msg.message, enyo_data_bus_value_js_1.EnyoCommandAcknowledgeAnswerEnum.Rejected, 'Snapshot service unavailable');
1567
- return;
1568
- }
1569
- if (this.snapshotService.isCalibrating()) {
1570
- console.log(`Battery ${this.applianceId}: calibration already active — ack idempotently`);
1571
- this.sendCommandAcknowledge(msg.id, msg.message, enyo_data_bus_value_js_1.EnyoCommandAcknowledgeAnswerEnum.Accepted);
1572
- return;
1573
- }
1574
- console.log(`Battery ${this.applianceId}: handling StartCalibrationV1`);
1575
- const controls = await this.sunspecClient.readBatteryControls(this.unitId);
1576
- if (!controls) {
1577
- this.sendCommandAcknowledge(msg.id, msg.message, enyo_data_bus_value_js_1.EnyoCommandAcknowledgeAnswerEnum.Rejected, 'Failed to read battery controls for snapshot');
1578
- return;
1579
- }
1580
- try {
1581
- await this.snapshotService.startCalibration(controls);
1582
- }
1583
- catch (error) {
1584
- this.sendCommandAcknowledge(msg.id, msg.message, enyo_data_bus_value_js_1.EnyoCommandAcknowledgeAnswerEnum.Rejected, `Failed to persist snapshot: ${error}`);
1585
- return;
1586
- }
1587
- this.sendCommandAcknowledge(msg.id, msg.message, enyo_data_bus_value_js_1.EnyoCommandAcknowledgeAnswerEnum.Accepted);
1588
- }
1589
- async handleStopCalibration(msg) {
1590
- if (!this.applianceId) {
1591
- this.sendCommandAcknowledge(msg.id, msg.message, enyo_data_bus_value_js_1.EnyoCommandAcknowledgeAnswerEnum.Rejected, 'Not initialized');
1592
- return;
1593
- }
1594
- console.log(`Battery ${this.applianceId}: handling StopCalibrationV1`);
1595
- // SnapshotService.stopCalibration fires `restoreBatterySnapshot` internally
1596
- // with reason="stop" before returning. Failures from the restore callback are
1597
- // logged inside the callback (the persisted snapshot is already gone — see
1598
- // library notes) so we always ack Accepted here.
1599
- await this.snapshotService?.stopCalibration();
1600
- this.sendCommandAcknowledge(msg.id, msg.message, enyo_data_bus_value_js_1.EnyoCommandAcknowledgeAnswerEnum.Accepted);
1601
- }
1602
1499
  /**
1603
1500
  * `onRestore` callback for the battery's {@link SnapshotService}. Writes only the
1604
1501
  * subset of writable battery fields touched during the calibration. Errors are
@@ -1704,13 +1601,11 @@ class SunspecMeter extends BaseSunspecDevice {
1704
1601
  console.error(`Failed to update meter appliance: ${error}`);
1705
1602
  }
1706
1603
  }
1707
- this.startDataBusListening();
1708
1604
  }
1709
1605
  /**
1710
1606
  * Disconnect from the meter and update appliance state
1711
1607
  */
1712
1608
  async disconnect() {
1713
- this.stopDataBusListening();
1714
1609
  if (this.applianceId) {
1715
1610
  try {
1716
1611
  await this.applianceManager.updateApplianceState(this.applianceId, enyo_appliance_js_1.EnyoApplianceConnectionType.Connector, enyo_appliance_js_1.EnyoApplianceStateEnum.Offline);
@@ -1722,35 +1617,6 @@ class SunspecMeter extends BaseSunspecDevice {
1722
1617
  // Close just this meter's unit; other devices on the same network device stay open.
1723
1618
  await this.sunspecClient.disconnectUnit(this.unitId);
1724
1619
  }
1725
- /**
1726
- * Meter does not implement calibration; it only subscribes to Start/StopCalibrationV1 to
1727
- * answer NotSupported (per the data-bus contract that every command must be acknowledged).
1728
- */
1729
- startDataBusListening() {
1730
- if (this.dataBusListenerId) {
1731
- return;
1732
- }
1733
- this.dataBus = this.energyApp.useDataBus();
1734
- this.dataBusListenerId = this.dataBus.listenForMessages([
1735
- enyo_data_bus_value_js_1.EnyoDataBusMessageEnum.StartCalibrationV1,
1736
- enyo_data_bus_value_js_1.EnyoDataBusMessageEnum.StopCalibrationV1,
1737
- ], (entry) => this.handleMeterCommand(entry));
1738
- console.log(`Meter ${this.applianceId}: started data bus listening (listener ${this.dataBusListenerId})`);
1739
- }
1740
- stopDataBusListening() {
1741
- if (this.dataBusListenerId && this.dataBus) {
1742
- this.dataBus.unsubscribe(this.dataBusListenerId);
1743
- console.log(`Meter ${this.applianceId}: stopped data bus listening (listener ${this.dataBusListenerId})`);
1744
- }
1745
- this.dataBusListenerId = undefined;
1746
- this.dataBus = undefined;
1747
- }
1748
- handleMeterCommand(entry) {
1749
- if (entry.applianceId !== this.applianceId) {
1750
- return;
1751
- }
1752
- this.sendCommandAcknowledge(entry.id, entry.message, enyo_data_bus_value_js_1.EnyoCommandAcknowledgeAnswerEnum.NotSupported, 'Meter does not support calibration');
1753
- }
1754
1620
  /**
1755
1621
  * Update meter data and return data bus messages
1756
1622
  */
@@ -178,8 +178,6 @@ export declare class SunspecInverter extends BaseSunspecDevice {
178
178
  * process restarts. Idempotent.
179
179
  */
180
180
  private initSnapshotService;
181
- private handleStartCalibration;
182
- private handleStopCalibration;
183
181
  /**
184
182
  * `onRestore` callback for the inverter's {@link SnapshotService}. Writes only the
185
183
  * subset of writable inverter fields that other commands actually touched during the
@@ -408,8 +406,6 @@ export declare class SunspecBattery extends BaseSunspecDevice {
408
406
  * process restarts. Idempotent.
409
407
  */
410
408
  private initSnapshotService;
411
- private handleStartCalibration;
412
- private handleStopCalibration;
413
409
  /**
414
410
  * `onRestore` callback for the battery's {@link SnapshotService}. Writes only the
415
411
  * subset of writable battery fields touched during the calibration. Errors are
@@ -432,13 +428,6 @@ export declare class SunspecMeter extends BaseSunspecDevice {
432
428
  * Disconnect from the meter and update appliance state
433
429
  */
434
430
  disconnect(): Promise<void>;
435
- /**
436
- * Meter does not implement calibration; it only subscribes to Start/StopCalibrationV1 to
437
- * answer NotSupported (per the data-bus contract that every command must be acknowledged).
438
- */
439
- startDataBusListening(): void;
440
- stopDataBusListening(): void;
441
- private handleMeterCommand;
442
431
  /**
443
432
  * Update meter data and return data bus messages
444
433
  */