@m1kad0/hannah-proto 5.0.0 → 5.2.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.
Binary file
@@ -100,13 +100,15 @@ export interface CaptureControl {
100
100
  }
101
101
  /**
102
102
  * A state change event for a state Hannah asked to watch (AgentWatchMore), e.g. the
103
- * trigger engine's conditions. Devices are not reported this way, they use SlotUpdate.
103
+ * trigger engine's conditions. `state_id` is the identifier the adapter uses for the value
104
+ * (for ioBroker the state ID, the same as `Slot.identifier` for a slot of a device). Devices
105
+ * report their changes as SlotUpdate; a value that is both a slot and watched gets both.
104
106
  * ack=true means the device confirmed the value (as opposed to ack=false which is the
105
107
  * "desired" value). Hannah uses this to verify that a SetState command was executed
106
- * successfully.
108
+ * successfully. The value is the raw one of that state, not normalized to a slot scale.
107
109
  */
108
110
  export interface AgentStateUpdate {
109
- /** full ioBroker state ID, e.g. "0_userdata.0.feeded" */
111
+ /** identifier of the value, for ioBroker the full state ID, e.g. "0_userdata.0.feeded" */
110
112
  stateId: string;
111
113
  /** JSON-encoded value: "true", "42", "\"text\"" */
112
114
  value: string;
@@ -114,6 +116,13 @@ export interface AgentStateUpdate {
114
116
  ack: boolean;
115
117
  /** Unix timestamp in milliseconds */
116
118
  ts: bigint;
119
+ /**
120
+ * Field 5 is deliberately unused: in hannah.v1 it was `canonical_key`, a different type.
121
+ * true = the current value the adapter sends when it starts watching a state (its answer to
122
+ * AgentWatchMore), not a change. Hannah Core uses it to prefill the trigger engine's cache
123
+ * silently, without firing triggers: an update from "unknown" to a value is not a transition.
124
+ */
125
+ initial: boolean;
117
126
  }
118
127
  /** The text-command state in ioBroker changed — treat as a SubmitText call. */
119
128
  export interface AgentTextCommand {
@@ -183,15 +192,16 @@ export interface AgentFirmwareEvent {
183
192
  * confirms execution back to Hannah.
184
193
  */
185
194
  export interface AgentSetState {
186
- /** ioBroker state ID to write */
195
+ /** identifier of the value to write, for ioBroker the state ID */
187
196
  stateId: string;
188
197
  /** JSON-encoded value */
189
198
  value: string;
190
199
  }
191
200
  /**
192
- * Hannah requests additional state IDs to be included in the AgentStateUpdate stream.
193
- * Used by the trigger engine at runtime when triggers reference arbitrary state IDs
194
- * (e.g. unless.state: "0_userdata.0.feeded") that aren't covered by enum discovery.
201
+ * Hannah requests additional identifiers (for ioBroker state IDs) to be included in the
202
+ * AgentStateUpdate stream. Used by the trigger engine at runtime when triggers reference
203
+ * arbitrary values (e.g. unless.state: "0_userdata.0.feeded") that aren't covered by enum
204
+ * discovery. The adapter answers every new one with its current raw value, `initial` = true.
195
205
  */
196
206
  export interface AgentWatchMore {
197
207
  stateIds: string[];
package/dist/v2/agent.js CHANGED
@@ -476,7 +476,7 @@ exports.CaptureControl = {
476
476
  },
477
477
  };
478
478
  function createBaseAgentStateUpdate() {
479
- return { stateId: "", value: "", ack: false, ts: 0n };
479
+ return { stateId: "", value: "", ack: false, ts: 0n, initial: false };
480
480
  }
481
481
  exports.AgentStateUpdate = {
482
482
  encode(message, writer = new wire_1.BinaryWriter()) {
@@ -495,6 +495,9 @@ exports.AgentStateUpdate = {
495
495
  }
496
496
  writer.uint32(32).int64(message.ts);
497
497
  }
498
+ if (message.initial !== false) {
499
+ writer.uint32(48).bool(message.initial);
500
+ }
498
501
  return writer;
499
502
  },
500
503
  decode(input, length) {
@@ -538,6 +541,13 @@ exports.AgentStateUpdate = {
538
541
  message.ts = reader.int64();
539
542
  continue;
540
543
  }
544
+ case 6: {
545
+ if (tag !== 48) {
546
+ break;
547
+ }
548
+ message.initial = reader.bool();
549
+ continue;
550
+ }
541
551
  }
542
552
  if ((tag & 7) === 4 || tag === 0) {
543
553
  break;
@@ -559,6 +569,7 @@ exports.AgentStateUpdate = {
559
569
  message.value = object.value ?? "";
560
570
  message.ack = object.ack ?? false;
561
571
  message.ts = (object.ts !== undefined && object.ts !== null) ? BigInt(object.ts) : 0n;
572
+ message.initial = object.initial ?? false;
562
573
  return message;
563
574
  },
564
575
  };
@@ -1,5 +1,5 @@
1
1
  import { BinaryReader, BinaryWriter } from "@bufbuild/protobuf/wire";
2
- import { DeviceClass, Slot, SlotValue } from "./device_model";
2
+ import { DeviceClass, DeviceSubtype, Slot, SlotValue } from "./device_model";
3
3
  export declare const protobufPackage = "hannah.v2";
4
4
  /**
5
5
  * A device for control menus (WebUI, Telegram), in the typed device model: the class
@@ -16,6 +16,8 @@ export interface DeviceInfo {
16
16
  /** with current values; Slot.writable says what a menu may offer */
17
17
  slots: Slot[];
18
18
  available: boolean;
19
+ /** TypedDevice.subtype, e.g. window or door for a contact */
20
+ subtype: DeviceSubtype;
19
21
  }
20
22
  export interface RoomInfo {
21
23
  /** room_id (lowercase), e.g. "wohnzimmer" */
@@ -11,7 +11,7 @@ const wire_1 = require("@bufbuild/protobuf/wire");
11
11
  const device_model_1 = require("./device_model");
12
12
  exports.protobufPackage = "hannah.v2";
13
13
  function createBaseDeviceInfo() {
14
- return { id: "", name: "", deviceClass: 0, slots: [], available: false };
14
+ return { id: "", name: "", deviceClass: 0, slots: [], available: false, subtype: 0 };
15
15
  }
16
16
  exports.DeviceInfo = {
17
17
  encode(message, writer = new wire_1.BinaryWriter()) {
@@ -30,6 +30,9 @@ exports.DeviceInfo = {
30
30
  if (message.available !== false) {
31
31
  writer.uint32(96).bool(message.available);
32
32
  }
33
+ if (message.subtype !== 0) {
34
+ writer.uint32(104).int32(message.subtype);
35
+ }
33
36
  return writer;
34
37
  },
35
38
  decode(input, length) {
@@ -80,6 +83,13 @@ exports.DeviceInfo = {
80
83
  message.available = reader.bool();
81
84
  continue;
82
85
  }
86
+ case 13: {
87
+ if (tag !== 104) {
88
+ break;
89
+ }
90
+ message.subtype = reader.int32();
91
+ continue;
92
+ }
83
93
  }
84
94
  if ((tag & 7) === 4 || tag === 0) {
85
95
  break;
@@ -102,6 +112,7 @@ exports.DeviceInfo = {
102
112
  message.deviceClass = object.deviceClass ?? 0;
103
113
  message.slots = object.slots?.map((e) => device_model_1.Slot.fromPartial(e)) || [];
104
114
  message.available = object.available ?? false;
115
+ message.subtype = object.subtype ?? 0;
105
116
  return message;
106
117
  },
107
118
  };
@@ -16,8 +16,9 @@ export declare enum DeviceClass {
16
16
  */
17
17
  DEVICE_CLASS_GENERIC_BINARY_SWITCH = 3,
18
18
  /**
19
- * DEVICE_CLASS_THERMOSTAT - Thermostat/room climate. Slots: TARGET_TEMPERATURE (required), TEMPERATURE,
20
- * HUMIDITY, VALVE. Comfort/eco setpoints and similar are not slots.
19
+ * DEVICE_CLASS_THERMOSTAT - Radiator or room thermostat. Slots: TARGET_TEMPERATURE (required), TEMPERATURE,
20
+ * HUMIDITY, VALVE. Comfort/eco setpoints and similar are not slots. Units with an
21
+ * operating mode and a fan are DEVICE_CLASS_CLIMATE.
21
22
  */
22
23
  DEVICE_CLASS_THERMOSTAT = 4,
23
24
  /** DEVICE_CLASS_COVER - Blind, shutter, awning. Slots: POSITION (required), TILT, STOP. */
@@ -34,6 +35,26 @@ export declare enum DeviceClass {
34
35
  * (GENERIC_NUMBER / GENERIC_BOOL / GENERIC_TEXT), each with label and unit.
35
36
  */
36
37
  DEVICE_CLASS_GENERIC = 8,
38
+ /**
39
+ * DEVICE_CLASS_CLIMATE - Air conditioner / room climate unit with operating mode and fan. Slots: ON (required),
40
+ * MODE, TARGET_TEMPERATURE, TEMPERATURE, HUMIDITY, FAN_SPEED. A radiator thermostat
41
+ * without mode and fan is a THERMOSTAT, not a CLIMATE.
42
+ */
43
+ DEVICE_CLASS_CLIMATE = 9,
44
+ UNRECOGNIZED = -1
45
+ }
46
+ /**
47
+ * A finer kind within a device class, for the cases where the class alone does not say
48
+ * what a user means ("the windows" versus "the doors"). Optional: the adapter sets it
49
+ * where it knows (function, role, type detector), otherwise UNSPECIFIED.
50
+ */
51
+ export declare enum DeviceSubtype {
52
+ /** DEVICE_SUBTYPE_UNSPECIFIED - No statement. Hannah Core may fall back to the device name. */
53
+ DEVICE_SUBTYPE_UNSPECIFIED = 0,
54
+ /** DEVICE_SUBTYPE_WINDOW - DEVICE_CLASS_CONTACT: window contact. */
55
+ DEVICE_SUBTYPE_WINDOW = 1,
56
+ /** DEVICE_SUBTYPE_DOOR - DEVICE_CLASS_CONTACT: door contact. */
57
+ DEVICE_SUBTYPE_DOOR = 2,
37
58
  UNRECOGNIZED = -1
38
59
  }
39
60
  /**
@@ -66,6 +87,17 @@ export declare enum SlotKind {
66
87
  SLOT_KIND_HUMIDITY = 22,
67
88
  /** SLOT_KIND_VALVE - number, 0-100 (% open) */
68
89
  SLOT_KIND_VALVE = 23,
90
+ /**
91
+ * SLOT_KIND_MODE - Operating mode of a CLIMATE device, text, one of: "cool", "heat", "dry", "fan_only",
92
+ * "auto". The adapter normalizes the device's own values to this vocabulary. The modes the
93
+ * device really supports are listed in Slot.options.
94
+ */
95
+ SLOT_KIND_MODE = 24,
96
+ /**
97
+ * SLOT_KIND_FAN_SPEED - Fan level of a CLIMATE device, text, one of: "low", "medium", "high", "auto". Supported
98
+ * levels are listed in Slot.options.
99
+ */
100
+ SLOT_KIND_FAN_SPEED = 25,
69
101
  /** SLOT_KIND_POSITION - --- cover --- */
70
102
  SLOT_KIND_POSITION = 30,
71
103
  /** SLOT_KIND_TILT - number, 0-100 (%) */
@@ -124,6 +156,21 @@ export interface Slot {
124
156
  * Unset = not configured, no restriction — distinct from an explicit 0.
125
157
  */
126
158
  requiredTrustLevel?: number | undefined;
159
+ /**
160
+ * The values this slot accepts, for SLOT_KIND_MODE and SLOT_KIND_FAN_SPEED, taken from the
161
+ * vocabulary of the kind (e.g. a cooling-only unit lists just "cool", "dry", "fan_only").
162
+ * Empty = no statement, every value of the vocabulary may work. Hannah uses it to answer
163
+ * honestly when a device cannot do what was asked.
164
+ */
165
+ options: string[];
166
+ /**
167
+ * How a trigger (and any client building triggers) refers to the value behind this slot: the
168
+ * identifier the adapter uses for it, for ioBroker the state ID. Opaque to Hannah Core, which
169
+ * only passes it through (AgentWatchMore, AgentSetState, AgentStateUpdate, trigger conditions
170
+ * and actions). Empty = not addressable by triggers. Triggers compare against the raw value
171
+ * of the identified state (and its own scale), not against the normalized `value` above.
172
+ */
173
+ identifier: string;
127
174
  }
128
175
  /** A device, as the adapter derived it from the ioBroker objects (grouping, class, slots). */
129
176
  export interface TypedDevice {
@@ -139,6 +186,8 @@ export interface TypedDevice {
139
186
  slots: Slot[];
140
187
  /** false = device currently unreachable */
141
188
  available: boolean;
189
+ /** finer kind within the class, UNSPECIFIED = no statement */
190
+ subtype: DeviceSubtype;
142
191
  }
143
192
  /**
144
193
  * All devices the adapter knows. Sent on AgentConnect and whenever the set of devices
@@ -5,7 +5,7 @@
5
5
  // protoc unknown
6
6
  // source: hannah/v2/device_model.proto
7
7
  Object.defineProperty(exports, "__esModule", { value: true });
8
- exports.SetSlot = exports.DeviceAvailability = exports.SlotUpdate = exports.TypedDeviceSnapshot = exports.TypedDevice = exports.Slot = exports.SlotValue = exports.SlotKind = exports.DeviceClass = exports.protobufPackage = void 0;
8
+ exports.SetSlot = exports.DeviceAvailability = exports.SlotUpdate = exports.TypedDeviceSnapshot = exports.TypedDevice = exports.Slot = exports.SlotValue = exports.SlotKind = exports.DeviceSubtype = exports.DeviceClass = exports.protobufPackage = void 0;
9
9
  /* eslint-disable */
10
10
  const wire_1 = require("@bufbuild/protobuf/wire");
11
11
  exports.protobufPackage = "hannah.v2";
@@ -26,8 +26,9 @@ var DeviceClass;
26
26
  */
27
27
  DeviceClass[DeviceClass["DEVICE_CLASS_GENERIC_BINARY_SWITCH"] = 3] = "DEVICE_CLASS_GENERIC_BINARY_SWITCH";
28
28
  /**
29
- * DEVICE_CLASS_THERMOSTAT - Thermostat/room climate. Slots: TARGET_TEMPERATURE (required), TEMPERATURE,
30
- * HUMIDITY, VALVE. Comfort/eco setpoints and similar are not slots.
29
+ * DEVICE_CLASS_THERMOSTAT - Radiator or room thermostat. Slots: TARGET_TEMPERATURE (required), TEMPERATURE,
30
+ * HUMIDITY, VALVE. Comfort/eco setpoints and similar are not slots. Units with an
31
+ * operating mode and a fan are DEVICE_CLASS_CLIMATE.
31
32
  */
32
33
  DeviceClass[DeviceClass["DEVICE_CLASS_THERMOSTAT"] = 4] = "DEVICE_CLASS_THERMOSTAT";
33
34
  /** DEVICE_CLASS_COVER - Blind, shutter, awning. Slots: POSITION (required), TILT, STOP. */
@@ -44,8 +45,29 @@ var DeviceClass;
44
45
  * (GENERIC_NUMBER / GENERIC_BOOL / GENERIC_TEXT), each with label and unit.
45
46
  */
46
47
  DeviceClass[DeviceClass["DEVICE_CLASS_GENERIC"] = 8] = "DEVICE_CLASS_GENERIC";
48
+ /**
49
+ * DEVICE_CLASS_CLIMATE - Air conditioner / room climate unit with operating mode and fan. Slots: ON (required),
50
+ * MODE, TARGET_TEMPERATURE, TEMPERATURE, HUMIDITY, FAN_SPEED. A radiator thermostat
51
+ * without mode and fan is a THERMOSTAT, not a CLIMATE.
52
+ */
53
+ DeviceClass[DeviceClass["DEVICE_CLASS_CLIMATE"] = 9] = "DEVICE_CLASS_CLIMATE";
47
54
  DeviceClass[DeviceClass["UNRECOGNIZED"] = -1] = "UNRECOGNIZED";
48
55
  })(DeviceClass || (exports.DeviceClass = DeviceClass = {}));
56
+ /**
57
+ * A finer kind within a device class, for the cases where the class alone does not say
58
+ * what a user means ("the windows" versus "the doors"). Optional: the adapter sets it
59
+ * where it knows (function, role, type detector), otherwise UNSPECIFIED.
60
+ */
61
+ var DeviceSubtype;
62
+ (function (DeviceSubtype) {
63
+ /** DEVICE_SUBTYPE_UNSPECIFIED - No statement. Hannah Core may fall back to the device name. */
64
+ DeviceSubtype[DeviceSubtype["DEVICE_SUBTYPE_UNSPECIFIED"] = 0] = "DEVICE_SUBTYPE_UNSPECIFIED";
65
+ /** DEVICE_SUBTYPE_WINDOW - DEVICE_CLASS_CONTACT: window contact. */
66
+ DeviceSubtype[DeviceSubtype["DEVICE_SUBTYPE_WINDOW"] = 1] = "DEVICE_SUBTYPE_WINDOW";
67
+ /** DEVICE_SUBTYPE_DOOR - DEVICE_CLASS_CONTACT: door contact. */
68
+ DeviceSubtype[DeviceSubtype["DEVICE_SUBTYPE_DOOR"] = 2] = "DEVICE_SUBTYPE_DOOR";
69
+ DeviceSubtype[DeviceSubtype["UNRECOGNIZED"] = -1] = "UNRECOGNIZED";
70
+ })(DeviceSubtype || (exports.DeviceSubtype = DeviceSubtype = {}));
49
71
  /**
50
72
  * What a slot means, and therefore which scale and unit its value has. The adapter
51
73
  * normalizes every value to exactly this scale; Core relies on it.
@@ -77,6 +99,17 @@ var SlotKind;
77
99
  SlotKind[SlotKind["SLOT_KIND_HUMIDITY"] = 22] = "SLOT_KIND_HUMIDITY";
78
100
  /** SLOT_KIND_VALVE - number, 0-100 (% open) */
79
101
  SlotKind[SlotKind["SLOT_KIND_VALVE"] = 23] = "SLOT_KIND_VALVE";
102
+ /**
103
+ * SLOT_KIND_MODE - Operating mode of a CLIMATE device, text, one of: "cool", "heat", "dry", "fan_only",
104
+ * "auto". The adapter normalizes the device's own values to this vocabulary. The modes the
105
+ * device really supports are listed in Slot.options.
106
+ */
107
+ SlotKind[SlotKind["SLOT_KIND_MODE"] = 24] = "SLOT_KIND_MODE";
108
+ /**
109
+ * SLOT_KIND_FAN_SPEED - Fan level of a CLIMATE device, text, one of: "low", "medium", "high", "auto". Supported
110
+ * levels are listed in Slot.options.
111
+ */
112
+ SlotKind[SlotKind["SLOT_KIND_FAN_SPEED"] = 25] = "SLOT_KIND_FAN_SPEED";
80
113
  /** SLOT_KIND_POSITION - --- cover --- */
81
114
  SlotKind[SlotKind["SLOT_KIND_POSITION"] = 30] = "SLOT_KIND_POSITION";
82
115
  /** SLOT_KIND_TILT - number, 0-100 (%) */
@@ -188,7 +221,17 @@ exports.SlotValue = {
188
221
  },
189
222
  };
190
223
  function createBaseSlot() {
191
- return { slotId: "", kind: 0, value: undefined, writable: false, unit: "", label: "", requiredTrustLevel: undefined };
224
+ return {
225
+ slotId: "",
226
+ kind: 0,
227
+ value: undefined,
228
+ writable: false,
229
+ unit: "",
230
+ label: "",
231
+ requiredTrustLevel: undefined,
232
+ options: [],
233
+ identifier: "",
234
+ };
192
235
  }
193
236
  exports.Slot = {
194
237
  encode(message, writer = new wire_1.BinaryWriter()) {
@@ -213,6 +256,12 @@ exports.Slot = {
213
256
  if (message.requiredTrustLevel !== undefined) {
214
257
  writer.uint32(56).int32(message.requiredTrustLevel);
215
258
  }
259
+ for (const v of message.options) {
260
+ writer.uint32(66).string(v);
261
+ }
262
+ if (message.identifier !== "") {
263
+ writer.uint32(74).string(message.identifier);
264
+ }
216
265
  return writer;
217
266
  },
218
267
  decode(input, length) {
@@ -277,6 +326,20 @@ exports.Slot = {
277
326
  message.requiredTrustLevel = reader.int32();
278
327
  continue;
279
328
  }
329
+ case 8: {
330
+ if (tag !== 66) {
331
+ break;
332
+ }
333
+ message.options.push(reader.string());
334
+ continue;
335
+ }
336
+ case 9: {
337
+ if (tag !== 74) {
338
+ break;
339
+ }
340
+ message.identifier = reader.string();
341
+ continue;
342
+ }
280
343
  }
281
344
  if ((tag & 7) === 4 || tag === 0) {
282
345
  break;
@@ -303,11 +366,13 @@ exports.Slot = {
303
366
  message.unit = object.unit ?? "";
304
367
  message.label = object.label ?? "";
305
368
  message.requiredTrustLevel = object.requiredTrustLevel ?? undefined;
369
+ message.options = object.options?.map((e) => e) || [];
370
+ message.identifier = object.identifier ?? "";
306
371
  return message;
307
372
  },
308
373
  };
309
374
  function createBaseTypedDevice() {
310
- return { deviceId: "", name: "", room: "", floor: "", deviceClass: 0, slots: [], available: false };
375
+ return { deviceId: "", name: "", room: "", floor: "", deviceClass: 0, slots: [], available: false, subtype: 0 };
311
376
  }
312
377
  exports.TypedDevice = {
313
378
  encode(message, writer = new wire_1.BinaryWriter()) {
@@ -332,6 +397,9 @@ exports.TypedDevice = {
332
397
  if (message.available !== false) {
333
398
  writer.uint32(56).bool(message.available);
334
399
  }
400
+ if (message.subtype !== 0) {
401
+ writer.uint32(64).int32(message.subtype);
402
+ }
335
403
  return writer;
336
404
  },
337
405
  decode(input, length) {
@@ -396,6 +464,13 @@ exports.TypedDevice = {
396
464
  message.available = reader.bool();
397
465
  continue;
398
466
  }
467
+ case 8: {
468
+ if (tag !== 64) {
469
+ break;
470
+ }
471
+ message.subtype = reader.int32();
472
+ continue;
473
+ }
399
474
  }
400
475
  if ((tag & 7) === 4 || tag === 0) {
401
476
  break;
@@ -420,6 +495,7 @@ exports.TypedDevice = {
420
495
  message.deviceClass = object.deviceClass ?? 0;
421
496
  message.slots = object.slots?.map((e) => exports.Slot.fromPartial(e)) || [];
422
497
  message.available = object.available ?? false;
498
+ message.subtype = object.subtype ?? 0;
423
499
  return message;
424
500
  },
425
501
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@m1kad0/hannah-proto",
3
- "version": "5.0.0",
3
+ "version": "5.2.0",
4
4
  "description": "Generated TypeScript gRPC-js client stubs for the Hannah voice assistant proto schema",
5
5
  "license": "UNLICENSED",
6
6
  "main": "dist/index.js",
@@ -22,7 +22,7 @@
22
22
  "@grpc/grpc-js": "^1.14.4"
23
23
  },
24
24
  "devDependencies": {
25
- "@grpc/grpc-js": "1.14.4",
25
+ "@grpc/grpc-js": "1.14.5",
26
26
  "ts-proto": "2.12.1",
27
27
  "typescript": "7.0.2"
28
28
  },