@enyo-energy/energy-app-sdk 0.0.188 → 0.0.190

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.
Files changed (37) hide show
  1. package/dist/cjs/energy-app-permission.type.cjs +1 -0
  2. package/dist/cjs/energy-app-permission.type.d.cts +1 -0
  3. package/dist/cjs/energy-app.cjs +16 -0
  4. package/dist/cjs/energy-app.d.cts +15 -0
  5. package/dist/cjs/enyo-energy-app-sdk.d.cts +3 -0
  6. package/dist/cjs/implementations/modbus/define-modbus-server-register.cjs +85 -0
  7. package/dist/cjs/implementations/modbus/define-modbus-server-register.d.cts +71 -0
  8. package/dist/cjs/implementations/modbus/modbus-server-validators.cjs +161 -0
  9. package/dist/cjs/implementations/modbus/modbus-server-validators.d.cts +73 -0
  10. package/dist/cjs/index.cjs +4 -0
  11. package/dist/cjs/index.d.cts +4 -0
  12. package/dist/cjs/packages/energy-app-modbus-server.cjs +14 -0
  13. package/dist/cjs/packages/energy-app-modbus-server.d.cts +122 -0
  14. package/dist/cjs/types/enyo-inverter-appliance.d.cts +13 -0
  15. package/dist/cjs/types/enyo-modbus-server.cjs +149 -0
  16. package/dist/cjs/types/enyo-modbus-server.d.cts +346 -0
  17. package/dist/cjs/version.cjs +1 -1
  18. package/dist/cjs/version.d.cts +1 -1
  19. package/dist/energy-app-permission.type.d.ts +1 -0
  20. package/dist/energy-app-permission.type.js +1 -0
  21. package/dist/energy-app.d.ts +15 -0
  22. package/dist/energy-app.js +16 -0
  23. package/dist/enyo-energy-app-sdk.d.ts +3 -0
  24. package/dist/implementations/modbus/define-modbus-server-register.d.ts +71 -0
  25. package/dist/implementations/modbus/define-modbus-server-register.js +82 -0
  26. package/dist/implementations/modbus/modbus-server-validators.d.ts +73 -0
  27. package/dist/implementations/modbus/modbus-server-validators.js +154 -0
  28. package/dist/index.d.ts +4 -0
  29. package/dist/index.js +4 -0
  30. package/dist/packages/energy-app-modbus-server.d.ts +122 -0
  31. package/dist/packages/energy-app-modbus-server.js +13 -0
  32. package/dist/types/enyo-inverter-appliance.d.ts +13 -0
  33. package/dist/types/enyo-modbus-server.d.ts +346 -0
  34. package/dist/types/enyo-modbus-server.js +144 -0
  35. package/dist/version.d.ts +1 -1
  36. package/dist/version.js +1 -1
  37. package/package.json +1 -1
@@ -27,6 +27,7 @@ var EnergyAppPermissionTypeEnum;
27
27
  EnergyAppPermissionTypeEnum["EnergyManager"] = "EnergyManager";
28
28
  EnergyAppPermissionTypeEnum["ElectricityTariff"] = "ElectricityTariff";
29
29
  EnergyAppPermissionTypeEnum["ModbusRtu"] = "ModbusRtu";
30
+ EnergyAppPermissionTypeEnum["ModbusServer"] = "ModbusServer";
30
31
  EnergyAppPermissionTypeEnum["EnergyPrices"] = "EnergyPrices";
31
32
  EnergyAppPermissionTypeEnum["WeatherForecastRegister"] = "WeatherForecastRegister";
32
33
  EnergyAppPermissionTypeEnum["WeatherForecastUse"] = "WeatherForecastUse";
@@ -24,6 +24,7 @@ export declare enum EnergyAppPermissionTypeEnum {
24
24
  EnergyManager = "EnergyManager",
25
25
  ElectricityTariff = "ElectricityTariff",
26
26
  ModbusRtu = "ModbusRtu",
27
+ ModbusServer = "ModbusServer",
27
28
  EnergyPrices = "EnergyPrices",
28
29
  WeatherForecastRegister = "WeatherForecastRegister",
29
30
  WeatherForecastUse = "WeatherForecastUse",
@@ -212,6 +212,22 @@ class EnergyApp {
212
212
  useModbusRtu() {
213
213
  return this.energyAppSdk.useModbusRtu();
214
214
  }
215
+ /**
216
+ * Gets the Modbus server API for serving your app's data to Modbus clients.
217
+ *
218
+ * The inverse of {@link EnergyApp.useModbus}: the hub itself answers Modbus
219
+ * requests, and this app supplies the values behind individual registers by
220
+ * registering them with read/write handlers. The listener is host-owned and
221
+ * shared with every other installed app, so address ranges are
222
+ * first-come-first-served across apps.
223
+ *
224
+ * Requires the `ModbusServer` permission.
225
+ *
226
+ * @returns The Modbus server API instance
227
+ */
228
+ useModbusServer() {
229
+ return this.energyAppSdk.useModbusServer();
230
+ }
215
231
  /**
216
232
  * Gets the EEbus API for SHIP/SPINE device communication.
217
233
  *
@@ -26,6 +26,7 @@ import { EnergyAppDynamicPriceForecast } from "./packages/energy-app-dynamic-pri
26
26
  import { EnergyAppPvSystem } from "./packages/energy-app-pv-system.cjs";
27
27
  import { EnergyAppSequenceGenerator } from "./packages/energy-app-sequence-generator.cjs";
28
28
  import { EnergyAppModbusRtu } from "./packages/energy-app-modbus-rtu.cjs";
29
+ import { EnergyAppModbusServer } from "./packages/energy-app-modbus-server.cjs";
29
30
  import { EnergyAppEebus } from "./packages/energy-app-eebus.cjs";
30
31
  import { EnergyAppMqtt } from "./packages/energy-app-mqtt.cjs";
31
32
  import { EnergyAppBluetooth } from "./packages/energy-app-bluetooth.cjs";
@@ -171,6 +172,20 @@ export declare class EnergyApp implements EnyoEnergyAppSdk {
171
172
  * @returns The Modbus RTU API instance
172
173
  */
173
174
  useModbusRtu(): EnergyAppModbusRtu;
175
+ /**
176
+ * Gets the Modbus server API for serving your app's data to Modbus clients.
177
+ *
178
+ * The inverse of {@link EnergyApp.useModbus}: the hub itself answers Modbus
179
+ * requests, and this app supplies the values behind individual registers by
180
+ * registering them with read/write handlers. The listener is host-owned and
181
+ * shared with every other installed app, so address ranges are
182
+ * first-come-first-served across apps.
183
+ *
184
+ * Requires the `ModbusServer` permission.
185
+ *
186
+ * @returns The Modbus server API instance
187
+ */
188
+ useModbusServer(): EnergyAppModbusServer;
174
189
  /**
175
190
  * Gets the EEbus API for SHIP/SPINE device communication.
176
191
  *
@@ -25,6 +25,7 @@ import { EnergyAppDynamicPriceForecast } from "./packages/energy-app-dynamic-pri
25
25
  import { EnergyAppPvSystem } from "./packages/energy-app-pv-system.cjs";
26
26
  import { EnergyAppSequenceGenerator } from "./packages/energy-app-sequence-generator.cjs";
27
27
  import { EnergyAppModbusRtu } from "./packages/energy-app-modbus-rtu.cjs";
28
+ import { EnergyAppModbusServer } from "./packages/energy-app-modbus-server.cjs";
28
29
  import { EnergyAppEebus } from "./packages/energy-app-eebus.cjs";
29
30
  import { EnergyAppMqtt } from "./packages/energy-app-mqtt.cjs";
30
31
  import { EnergyAppBluetooth } from "./packages/energy-app-bluetooth.cjs";
@@ -123,6 +124,8 @@ export interface EnyoEnergyAppSdk {
123
124
  useSequenceGenerator: () => EnergyAppSequenceGenerator;
124
125
  /** Get the Modbus RTU serial communication API */
125
126
  useModbusRtu: () => EnergyAppModbusRtu;
127
+ /** Get the Modbus server API for serving registers to Modbus clients */
128
+ useModbusServer: () => EnergyAppModbusServer;
126
129
  /** Get the EEbus SHIP/SPINE communication API for device pairing, data access, and power management */
127
130
  useEebus: () => EnergyAppEebus;
128
131
  /** Get the MQTT communication API for connecting to internal or external MQTT brokers */
@@ -0,0 +1,85 @@
1
+ "use strict";
2
+ /**
3
+ * Ergonomic authoring helpers for {@link EnyoModbusServerRegistration}s.
4
+ *
5
+ * These exist for one concrete reason: type inference. A registration's handler
6
+ * signatures depend on its `dataType` — a `float32` register reads a `number`, a
7
+ * `string` register reads a `string` — but that link only holds when TypeScript
8
+ * can *infer* the data type from the literal. Annotating an object literal as
9
+ * {@link EnyoModbusServerRegistration} defeats that: the generic falls back to
10
+ * the full data-type union and every handler widens to `string | number`.
11
+ *
12
+ * Building registers through these factories keeps the link intact, so a handler
13
+ * returning the wrong shape is a compile error rather than a runtime decode
14
+ * failure. They also fill in `space`, which is the one field that is pure
15
+ * boilerplate.
16
+ *
17
+ * Mirrors the SDK's `onboardingV2Block` pattern.
18
+ */
19
+ Object.defineProperty(exports, "__esModule", { value: true });
20
+ exports.modbusServerRegister = void 0;
21
+ const enyo_modbus_server_js_1 = require("../../types/enyo-modbus-server.cjs");
22
+ /**
23
+ * Typed factories for each Modbus address space. Each fills in `space` and
24
+ * returns the corresponding registration, inferring the word data type from the
25
+ * supplied `dataType` so handler values stay exact.
26
+ *
27
+ * @example
28
+ * ```typescript
29
+ * const registers = [
30
+ * modbusServerRegister.holding({
31
+ * address: 40071,
32
+ * key: 'active-power-w',
33
+ * name: [{language: 'en', value: 'Active power'}],
34
+ * unit: 'W',
35
+ * dataType: 'float32',
36
+ * onRead: () => currentPowerW, // must be a number
37
+ * onWrite: (value) => applyLimit(value),
38
+ * }),
39
+ * modbusServerRegister.input({
40
+ * address: 40080,
41
+ * key: 'serial',
42
+ * name: [{language: 'en', value: 'Serial number'}],
43
+ * dataType: 'string',
44
+ * quantity: 8,
45
+ * onRead: () => deviceSerial, // must be a string
46
+ * }),
47
+ * ];
48
+ * ```
49
+ */
50
+ exports.modbusServerRegister = {
51
+ /**
52
+ * A read/write single-bit coil.
53
+ * @param registration - Everything but `space`; omit `onWrite` for a read-only coil.
54
+ */
55
+ coil: (registration) => ({
56
+ ...registration,
57
+ space: enyo_modbus_server_js_1.EnyoModbusServerRegisterSpace.Coil,
58
+ }),
59
+ /**
60
+ * A read-only single-bit discrete input.
61
+ * @param registration - Everything but `space`.
62
+ */
63
+ discreteInput: (registration) => ({
64
+ ...registration,
65
+ space: enyo_modbus_server_js_1.EnyoModbusServerRegisterSpace.DiscreteInput,
66
+ }),
67
+ /**
68
+ * A read/write 16-bit holding register.
69
+ * @param registration - Everything but `space`. The `dataType` fixes the
70
+ * handler value types; omit `onWrite` for a read-only holding register.
71
+ */
72
+ holding: (registration) => ({
73
+ ...registration,
74
+ space: enyo_modbus_server_js_1.EnyoModbusServerRegisterSpace.Holding,
75
+ }),
76
+ /**
77
+ * A read-only 16-bit input register.
78
+ * @param registration - Everything but `space`. The `dataType` fixes the
79
+ * handler value type.
80
+ */
81
+ input: (registration) => ({
82
+ ...registration,
83
+ space: enyo_modbus_server_js_1.EnyoModbusServerRegisterSpace.Input,
84
+ }),
85
+ };
@@ -0,0 +1,71 @@
1
+ /**
2
+ * Ergonomic authoring helpers for {@link EnyoModbusServerRegistration}s.
3
+ *
4
+ * These exist for one concrete reason: type inference. A registration's handler
5
+ * signatures depend on its `dataType` — a `float32` register reads a `number`, a
6
+ * `string` register reads a `string` — but that link only holds when TypeScript
7
+ * can *infer* the data type from the literal. Annotating an object literal as
8
+ * {@link EnyoModbusServerRegistration} defeats that: the generic falls back to
9
+ * the full data-type union and every handler widens to `string | number`.
10
+ *
11
+ * Building registers through these factories keeps the link intact, so a handler
12
+ * returning the wrong shape is a compile error rather than a runtime decode
13
+ * failure. They also fill in `space`, which is the one field that is pure
14
+ * boilerplate.
15
+ *
16
+ * Mirrors the SDK's `onboardingV2Block` pattern.
17
+ */
18
+ import { type EnyoModbusServerCoilRegistration, type EnyoModbusServerDiscreteInputRegistration, type EnyoModbusServerHoldingRegistration, type EnyoModbusServerInputRegistration } from '../../types/enyo-modbus-server.cjs';
19
+ import type { EnergyAppModbusDataType } from './interfaces.cjs';
20
+ /**
21
+ * Typed factories for each Modbus address space. Each fills in `space` and
22
+ * returns the corresponding registration, inferring the word data type from the
23
+ * supplied `dataType` so handler values stay exact.
24
+ *
25
+ * @example
26
+ * ```typescript
27
+ * const registers = [
28
+ * modbusServerRegister.holding({
29
+ * address: 40071,
30
+ * key: 'active-power-w',
31
+ * name: [{language: 'en', value: 'Active power'}],
32
+ * unit: 'W',
33
+ * dataType: 'float32',
34
+ * onRead: () => currentPowerW, // must be a number
35
+ * onWrite: (value) => applyLimit(value),
36
+ * }),
37
+ * modbusServerRegister.input({
38
+ * address: 40080,
39
+ * key: 'serial',
40
+ * name: [{language: 'en', value: 'Serial number'}],
41
+ * dataType: 'string',
42
+ * quantity: 8,
43
+ * onRead: () => deviceSerial, // must be a string
44
+ * }),
45
+ * ];
46
+ * ```
47
+ */
48
+ export declare const modbusServerRegister: {
49
+ /**
50
+ * A read/write single-bit coil.
51
+ * @param registration - Everything but `space`; omit `onWrite` for a read-only coil.
52
+ */
53
+ coil: (registration: Omit<EnyoModbusServerCoilRegistration, "space">) => EnyoModbusServerCoilRegistration;
54
+ /**
55
+ * A read-only single-bit discrete input.
56
+ * @param registration - Everything but `space`.
57
+ */
58
+ discreteInput: (registration: Omit<EnyoModbusServerDiscreteInputRegistration, "space">) => EnyoModbusServerDiscreteInputRegistration;
59
+ /**
60
+ * A read/write 16-bit holding register.
61
+ * @param registration - Everything but `space`. The `dataType` fixes the
62
+ * handler value types; omit `onWrite` for a read-only holding register.
63
+ */
64
+ holding: <D extends EnergyAppModbusDataType>(registration: Omit<EnyoModbusServerHoldingRegistration<D>, "space">) => EnyoModbusServerHoldingRegistration<D>;
65
+ /**
66
+ * A read-only 16-bit input register.
67
+ * @param registration - Everything but `space`. The `dataType` fixes the
68
+ * handler value type.
69
+ */
70
+ input: <D extends EnergyAppModbusDataType>(registration: Omit<EnyoModbusServerInputRegistration<D>, "space">) => EnyoModbusServerInputRegistration<D>;
71
+ };
@@ -0,0 +1,161 @@
1
+ "use strict";
2
+ /**
3
+ * Client-side validator for {@link EnyoModbusServerRegistration} lists.
4
+ *
5
+ * The host rejects a conflicting registration at call time by throwing
6
+ * {@link EnyoModbusServerRegistrationConflictError}, which is the authoritative
7
+ * check but a poor way to find out your own register map overlaps itself. This
8
+ * validator catches everything that is knowable without the host — self-overlap,
9
+ * duplicate keys, missing metadata — so an app can fail fast at startup with all
10
+ * the problems at once instead of one throw at a time.
11
+ *
12
+ * It cannot see other apps' registrations, so a clean result here does **not**
13
+ * guarantee registration will succeed.
14
+ */
15
+ Object.defineProperty(exports, "__esModule", { value: true });
16
+ exports.ModbusServerValidationError = void 0;
17
+ exports.modbusServerRegisterSpan = modbusServerRegisterSpan;
18
+ exports.validateModbusServerRegistrations = validateModbusServerRegistrations;
19
+ exports.assertValidModbusServerRegistrations = assertValidModbusServerRegistrations;
20
+ const enyo_modbus_server_js_1 = require("../../types/enyo-modbus-server.cjs");
21
+ /**
22
+ * Thrown by {@link assertValidModbusServerRegistrations} when a register map
23
+ * fails validation. The message lists every blocking error.
24
+ */
25
+ class ModbusServerValidationError extends Error {
26
+ /** The individual blocking errors that caused the failure. */
27
+ errors;
28
+ /**
29
+ * @param errors - The blocking validation errors.
30
+ */
31
+ constructor(errors) {
32
+ super(`Invalid Modbus server register map:\n- ${errors.join('\n- ')}`);
33
+ this.name = 'ModbusServerValidationError';
34
+ this.errors = errors;
35
+ }
36
+ }
37
+ exports.ModbusServerValidationError = ModbusServerValidationError;
38
+ /** Word counts for the data types whose span is fixed. */
39
+ const WORDS_BY_DATA_TYPE = {
40
+ uint16: 1,
41
+ int16: 1,
42
+ acc16: 1,
43
+ uint32: 2,
44
+ int32: 2,
45
+ float32: 2,
46
+ acc32: 2,
47
+ };
48
+ /** The two spaces addressed in bits rather than 16-bit words. */
49
+ const BIT_SPACES = new Set([
50
+ enyo_modbus_server_js_1.EnyoModbusServerRegisterSpace.Coil,
51
+ enyo_modbus_server_js_1.EnyoModbusServerRegisterSpace.DiscreteInput,
52
+ ]);
53
+ /**
54
+ * How many words (or bits) a registration occupies, starting at its `address`.
55
+ *
56
+ * Bit spaces always occupy one. Word spaces derive their span from `dataType`,
57
+ * except `'string'`, which needs an explicit `quantity` — when that is missing
58
+ * the span is unknowable and this returns `undefined` so the caller can report
59
+ * it rather than guess.
60
+ *
61
+ * @param registration - The registration to measure.
62
+ * @returns The span, or `undefined` when it cannot be derived.
63
+ */
64
+ function modbusServerRegisterSpan(registration) {
65
+ if (BIT_SPACES.has(registration.space))
66
+ return 1;
67
+ const { dataType, quantity } = registration;
68
+ if (dataType === 'string') {
69
+ return quantity && quantity > 0 ? quantity : undefined;
70
+ }
71
+ return WORDS_BY_DATA_TYPE[dataType];
72
+ }
73
+ /**
74
+ * Checks a register map for problems that are knowable without the host.
75
+ *
76
+ * Reports as errors: overlapping ranges within the same space, duplicate `key`s,
77
+ * a missing or empty `name`, a `'string'` register without `quantity`, an
78
+ * unknown `dataType`, and a negative address. Reports as warnings: `scale` on a
79
+ * string register (which the host ignores) and a `unit` on a coil or discrete
80
+ * input (a bit has no unit).
81
+ *
82
+ * Two registrations only conflict within the same space — holding 40071 and
83
+ * input 40071 are different addresses and both are fine.
84
+ *
85
+ * @param registrations - The register map to check.
86
+ * @returns The {@link ModbusServerValidationResult}.
87
+ */
88
+ function validateModbusServerRegistrations(registrations) {
89
+ const errors = [];
90
+ const warnings = [];
91
+ if (!registrations?.length) {
92
+ return { ok: true, errors, warnings };
93
+ }
94
+ const keys = new Set();
95
+ /** Claimed ranges per space, so the overlap check never crosses spaces. */
96
+ const claimed = new Map();
97
+ for (const [i, registration] of registrations.entries()) {
98
+ const at = `registrations[${i}] (${registration.key || '?'})`;
99
+ if (!registration.key?.trim()) {
100
+ errors.push(`${at}: key is required.`);
101
+ }
102
+ else if (keys.has(registration.key)) {
103
+ errors.push(`${at}: duplicate key "${registration.key}".`);
104
+ }
105
+ else {
106
+ keys.add(registration.key);
107
+ }
108
+ if (!registration.name?.length) {
109
+ errors.push(`${at}: name is required — it is what an installer sees in the register map.`);
110
+ }
111
+ if (!Number.isInteger(registration.address) || registration.address < 0) {
112
+ errors.push(`${at}: address must be a non-negative integer.`);
113
+ continue;
114
+ }
115
+ if (BIT_SPACES.has(registration.space) && registration.unit) {
116
+ warnings.push(`${at}: unit "${registration.unit}" on a ${registration.space} is meaningless — a bit has no unit.`);
117
+ }
118
+ const span = modbusServerRegisterSpan(registration);
119
+ if (span === undefined) {
120
+ const { dataType } = registration;
121
+ if (dataType === 'string') {
122
+ errors.push(`${at}: dataType "string" needs a positive quantity — its length cannot be derived.`);
123
+ }
124
+ else {
125
+ errors.push(`${at}: unknown dataType "${dataType}".`);
126
+ }
127
+ continue;
128
+ }
129
+ if (!BIT_SPACES.has(registration.space)) {
130
+ const { dataType, scale } = registration;
131
+ if (dataType === 'string' && scale !== undefined) {
132
+ warnings.push(`${at}: scale is ignored on a string register.`);
133
+ }
134
+ }
135
+ const from = registration.address;
136
+ const to = from + span - 1;
137
+ const inSpace = claimed.get(registration.space) ?? [];
138
+ const clash = inSpace.find((r) => from <= r.to && to >= r.from);
139
+ if (clash) {
140
+ errors.push(`${at}: ${registration.space} range ${from}..${to} overlaps ${clash.at} (${clash.from}..${clash.to}).`);
141
+ }
142
+ inSpace.push({ from, to, at });
143
+ claimed.set(registration.space, inSpace);
144
+ }
145
+ return { ok: errors.length === 0, errors, warnings };
146
+ }
147
+ /**
148
+ * Like {@link validateModbusServerRegistrations}, but throws
149
+ * {@link ModbusServerValidationError} when there are blocking errors. Warnings
150
+ * never throw; the validated list is returned on success for chaining.
151
+ *
152
+ * @param registrations - The register map to check.
153
+ * @returns The same list when it has no blocking errors.
154
+ * @throws {ModbusServerValidationError} When validation produces any error.
155
+ */
156
+ function assertValidModbusServerRegistrations(registrations) {
157
+ const { ok, errors } = validateModbusServerRegistrations(registrations);
158
+ if (!ok)
159
+ throw new ModbusServerValidationError(errors);
160
+ return registrations;
161
+ }
@@ -0,0 +1,73 @@
1
+ /**
2
+ * Client-side validator for {@link EnyoModbusServerRegistration} lists.
3
+ *
4
+ * The host rejects a conflicting registration at call time by throwing
5
+ * {@link EnyoModbusServerRegistrationConflictError}, which is the authoritative
6
+ * check but a poor way to find out your own register map overlaps itself. This
7
+ * validator catches everything that is knowable without the host — self-overlap,
8
+ * duplicate keys, missing metadata — so an app can fail fast at startup with all
9
+ * the problems at once instead of one throw at a time.
10
+ *
11
+ * It cannot see other apps' registrations, so a clean result here does **not**
12
+ * guarantee registration will succeed.
13
+ */
14
+ import { type EnyoModbusServerRegistration } from '../../types/enyo-modbus-server.cjs';
15
+ /** The outcome of validating a list of registrations. */
16
+ export interface ModbusServerValidationResult {
17
+ /** True when there are no blocking `errors` (warnings are still allowed). */
18
+ ok: boolean;
19
+ /** Blocking problems — registration will fail or serve wrong data. */
20
+ errors: string[];
21
+ /** Advisory problems — allowed, but usually worth fixing. */
22
+ warnings: string[];
23
+ }
24
+ /**
25
+ * Thrown by {@link assertValidModbusServerRegistrations} when a register map
26
+ * fails validation. The message lists every blocking error.
27
+ */
28
+ export declare class ModbusServerValidationError extends Error {
29
+ /** The individual blocking errors that caused the failure. */
30
+ readonly errors: string[];
31
+ /**
32
+ * @param errors - The blocking validation errors.
33
+ */
34
+ constructor(errors: string[]);
35
+ }
36
+ /**
37
+ * How many words (or bits) a registration occupies, starting at its `address`.
38
+ *
39
+ * Bit spaces always occupy one. Word spaces derive their span from `dataType`,
40
+ * except `'string'`, which needs an explicit `quantity` — when that is missing
41
+ * the span is unknowable and this returns `undefined` so the caller can report
42
+ * it rather than guess.
43
+ *
44
+ * @param registration - The registration to measure.
45
+ * @returns The span, or `undefined` when it cannot be derived.
46
+ */
47
+ export declare function modbusServerRegisterSpan(registration: EnyoModbusServerRegistration): number | undefined;
48
+ /**
49
+ * Checks a register map for problems that are knowable without the host.
50
+ *
51
+ * Reports as errors: overlapping ranges within the same space, duplicate `key`s,
52
+ * a missing or empty `name`, a `'string'` register without `quantity`, an
53
+ * unknown `dataType`, and a negative address. Reports as warnings: `scale` on a
54
+ * string register (which the host ignores) and a `unit` on a coil or discrete
55
+ * input (a bit has no unit).
56
+ *
57
+ * Two registrations only conflict within the same space — holding 40071 and
58
+ * input 40071 are different addresses and both are fine.
59
+ *
60
+ * @param registrations - The register map to check.
61
+ * @returns The {@link ModbusServerValidationResult}.
62
+ */
63
+ export declare function validateModbusServerRegistrations(registrations: EnyoModbusServerRegistration[]): ModbusServerValidationResult;
64
+ /**
65
+ * Like {@link validateModbusServerRegistrations}, but throws
66
+ * {@link ModbusServerValidationError} when there are blocking errors. Warnings
67
+ * never throw; the validated list is returned on success for chaining.
68
+ *
69
+ * @param registrations - The register map to check.
70
+ * @returns The same list when it has no blocking errors.
71
+ * @throws {ModbusServerValidationError} When validation produces any error.
72
+ */
73
+ export declare function assertValidModbusServerRegistrations(registrations: EnyoModbusServerRegistration[]): EnyoModbusServerRegistration[];
@@ -54,6 +54,10 @@ __exportStar(require("./types/enyo-currency.cjs"), exports);
54
54
  __exportStar(require("./packages/energy-app-sequence-generator.cjs"), exports);
55
55
  __exportStar(require("./packages/energy-app-energy-prices.cjs"), exports);
56
56
  __exportStar(require("./packages/energy-app-modbus-rtu.cjs"), exports);
57
+ __exportStar(require("./types/enyo-modbus-server.cjs"), exports);
58
+ __exportStar(require("./packages/energy-app-modbus-server.cjs"), exports);
59
+ __exportStar(require("./implementations/modbus/define-modbus-server-register.cjs"), exports);
60
+ __exportStar(require("./implementations/modbus/modbus-server-validators.cjs"), exports);
57
61
  __exportStar(require("./types/enyo-spine.cjs"), exports);
58
62
  __exportStar(require("./types/enyo-eebus.cjs"), exports);
59
63
  __exportStar(require("./types/enyo-eebus-use-cases.cjs"), exports);
@@ -38,6 +38,10 @@ export * from './types/enyo-currency.cjs';
38
38
  export * from './packages/energy-app-sequence-generator.cjs';
39
39
  export * from './packages/energy-app-energy-prices.cjs';
40
40
  export * from './packages/energy-app-modbus-rtu.cjs';
41
+ export * from './types/enyo-modbus-server.cjs';
42
+ export * from './packages/energy-app-modbus-server.cjs';
43
+ export * from './implementations/modbus/define-modbus-server-register.cjs';
44
+ export * from './implementations/modbus/modbus-server-validators.cjs';
41
45
  export * from './types/enyo-spine.cjs';
42
46
  export * from './types/enyo-eebus.cjs';
43
47
  export * from './types/enyo-eebus-use-cases.cjs';
@@ -0,0 +1,14 @@
1
+ "use strict";
2
+ /**
3
+ * The **Modbus server** package: serve your app's data to Modbus clients on the
4
+ * hub's own listener.
5
+ *
6
+ * This is the inverse of {@link EnergyAppModbus}. There, the app is a client
7
+ * polling somebody else's device. Here, the hub *is* the device: a third-party
8
+ * energy manager, a building-automation controller or an installer's diagnostic
9
+ * tool connects to the hub, and the values it reads come from handlers this app
10
+ * registers.
11
+ *
12
+ * Requires the `ModbusServer` permission.
13
+ */
14
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -0,0 +1,122 @@
1
+ /**
2
+ * The **Modbus server** package: serve your app's data to Modbus clients on the
3
+ * hub's own listener.
4
+ *
5
+ * This is the inverse of {@link EnergyAppModbus}. There, the app is a client
6
+ * polling somebody else's device. Here, the hub *is* the device: a third-party
7
+ * energy manager, a building-automation controller or an installer's diagnostic
8
+ * tool connects to the hub, and the values it reads come from handlers this app
9
+ * registers.
10
+ *
11
+ * Requires the `ModbusServer` permission.
12
+ */
13
+ import type { EnergyAppModbusDataType } from '../implementations/modbus/interfaces.cjs';
14
+ import type { EnyoModbusServerConfig, EnyoModbusServerRegistration, EnyoModbusServerRegistrationHandle, EnyoModbusServerRegistrationOf } from '../types/enyo-modbus-server.cjs';
15
+ /**
16
+ * Interface for serving Modbus registers from an enyo package.
17
+ *
18
+ * **The server is shared.** One host-owned TCP listener, one unit id, and every
19
+ * installed app carving up the same address space. Two consequences an app
20
+ * author has to design around:
21
+ *
22
+ * 1. Address ranges are first-come, first-served across *all* apps, so
23
+ * registration can fail with
24
+ * {@link EnyoModbusServerRegistrationConflictError} through no fault of your
25
+ * own. Handle it at runtime; do not assume startup registration succeeds.
26
+ * 2. The unit id from {@link getModbusServerConfig} does not identify your app.
27
+ * A client addressing it reaches the whole hub, not your package.
28
+ *
29
+ * Register metadata (translated name, description, unit) never travels over
30
+ * Modbus — the protocol carries bare words. It exists so the host can show the
31
+ * hub's register map and generate installer documentation.
32
+ *
33
+ * @example
34
+ * ```typescript
35
+ * const server = energyApp.useModbusServer();
36
+ * const {addresses, port, unitId} = await server.getModbusServerConfig();
37
+ * console.log(`Reachable at ${addresses.join(', ')}:${port}, unit ${unitId}`);
38
+ *
39
+ * const handle = await server.registerRegister(modbusServerRegister.holding({
40
+ * address: 40071,
41
+ * key: 'active-power-w',
42
+ * name: [
43
+ * {language: 'de', value: 'Momentanleistung'},
44
+ * {language: 'en', value: 'Active power'},
45
+ * ],
46
+ * description: [
47
+ * {language: 'de', value: 'Aktuelle Wirkleistung der Anlage.'},
48
+ * {language: 'en', value: 'Current active power of the plant.'},
49
+ * ],
50
+ * unit: 'W',
51
+ * dataType: 'float32', // claims 40071..40072
52
+ * onRead: async () => currentPowerW,
53
+ * onWrite: async (value) => applyPowerLimit(value),
54
+ * }));
55
+ *
56
+ * // later, on shutdown
57
+ * await handle.unregister();
58
+ * ```
59
+ */
60
+ export interface EnergyAppModbusServer {
61
+ /**
62
+ * Returns where the hub's Modbus server can be reached — addresses, port and
63
+ * the shared unit id.
64
+ *
65
+ * Everything returned is host-owned and read-only. Use it to tell an
66
+ * installer where to point their client; prefer showing
67
+ * {@link EnyoModbusServerConfig.addresses} in full over guessing which
68
+ * interface they are on.
69
+ *
70
+ * @returns The current server configuration.
71
+ */
72
+ getModbusServerConfig(): Promise<EnyoModbusServerConfig>;
73
+ /**
74
+ * Claims one address range and serves it from the supplied handlers.
75
+ *
76
+ * The range claimed runs from `address` for as many words as the
77
+ * registration's `dataType` implies (1 for `uint16`, 2 for `float32`,
78
+ * `quantity` for `string`, 1 bit for a coil or discrete input). Any overlap
79
+ * with an existing registration — yours or another app's — fails the whole
80
+ * call; nothing partial is claimed.
81
+ *
82
+ * @param registration - The register to serve, with its metadata and handlers.
83
+ * @returns A handle for releasing the range again.
84
+ * @throws {EnyoModbusServerRegistrationConflictError} If any word of the
85
+ * range is already registered. Check `heldByCaller` to tell your own
86
+ * double-registration from another app holding the address.
87
+ */
88
+ registerRegister<D extends EnergyAppModbusDataType>(registration: EnyoModbusServerRegistrationOf<D>): Promise<EnyoModbusServerRegistrationHandle>;
89
+ /**
90
+ * Claims several address ranges at once, atomically.
91
+ *
92
+ * Either every registration succeeds or none does — a conflict partway
93
+ * through the list leaves no ranges claimed. Prefer this over a loop of
94
+ * {@link registerRegister} when registering a device's whole register map,
95
+ * so a mid-list conflict cannot strand you with half a map published.
96
+ *
97
+ * @param registrations - The registers to serve.
98
+ * @returns One handle per registration, in the order supplied.
99
+ * @throws {EnyoModbusServerRegistrationConflictError} If any range in the
100
+ * list conflicts, with the details of the first conflict found. Overlaps
101
+ * *within* the supplied list are reported the same way, with
102
+ * `heldByCaller` true.
103
+ */
104
+ registerRegisters(registrations: EnyoModbusServerRegistration[]): Promise<EnyoModbusServerRegistrationHandle[]>;
105
+ /**
106
+ * Lists the registrations this app currently holds.
107
+ *
108
+ * Scoped to the calling app — registrations belonging to other apps are
109
+ * never visible, even though they share the address space.
110
+ *
111
+ * @returns The live handles, in no guaranteed order.
112
+ */
113
+ getRegisteredRegisters(): Promise<EnyoModbusServerRegistrationHandle[]>;
114
+ /**
115
+ * Releases every range this app holds.
116
+ *
117
+ * Idempotent, and safe to call when nothing is registered. Worth calling on
118
+ * shutdown: ranges stay claimed while the app is installed, so a stale
119
+ * registration blocks a later re-registration of the same address.
120
+ */
121
+ unregisterAll(): Promise<void>;
122
+ }
@@ -42,4 +42,17 @@ export interface EnyoInverterApplianceMetadata {
42
42
  * `undefined` means "not configured" and should be treated as `false`.
43
43
  */
44
44
  blockFeedInOnNegativePrices?: boolean;
45
+ /**
46
+ * Whether the energy manager may actively steer this inverter (e.g. curtail
47
+ * production). When omitted, consumers fall back to their configured
48
+ * default behaviour — same contract as the SDK's own `controlAllowed`.
49
+ */
50
+ controlAllowed?: boolean;
51
+ /**
52
+ * Peak power of the PV modules attached to this inverter, in Watt-peak, as
53
+ * stated on the Anlagenpass. Distinct from `maxPvProductionW`, which is
54
+ * what the inverter itself can put out on the AC side; together with
55
+ * `moduleGroups` it also gives the power of a single module.
56
+ */
57
+ installedPeakPowerWp?: number;
45
58
  }