mirta 0.1.2 → 0.2.1

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/dist/index.d.mts CHANGED
@@ -65,9 +65,9 @@ type StrictWhenSpecified<TObject, TKey extends keyof TObject, TReturn> = IsSpeci
65
65
  * Устанавливает указанное свойство в readonly при выполнении указанного условия.
66
66
  * @since 0.1.0
67
67
  **/
68
- type ReadonlyPropWhen<TObject, K extends keyof TObject, TCondition extends boolean | undefined> = TCondition extends true ? Omit<TObject, K> & {
68
+ type ReadonlyPropWhen<TObject, K extends keyof TObject, TCondition extends boolean | undefined> = TCondition extends true ? Expand<Omit<TObject, K> & {
69
69
  +readonly [P in K]-?: TObject[P];
70
- } : TObject;
70
+ }> : TObject;
71
71
  /**
72
72
  * Проверяет, что указанный тип объекта имеет хотя бы одно свойство заданного типа.
73
73
  * Применяется для работы с дженериками.
@@ -86,6 +86,58 @@ type HasPropertyOfType<TObject extends object, TProperty> = {
86
86
  [K in keyof TObject as TObject[K] extends TProperty ? K : never]: unknown;
87
87
  } extends infer R ? {} extends R ? false : true : never;
88
88
 
89
+ /**
90
+ * Набор политик, управляющих доступностью смены значения контрола.
91
+ *
92
+ * @since 0.2.0
93
+ *
94
+ **/
95
+ declare enum ChangePolicies {
96
+ /**
97
+ * Политика по умолчанию.
98
+ *
99
+ * - Применяет {@link Public} к контролам типа `switch`, `pushbutton`, `range` и `rgb`;
100
+ *
101
+ * - Применяет {@link Script} к остальным типам контролов.
102
+ *
103
+ **/
104
+ Default = "default",
105
+ /**
106
+ * Установка значения возможна как скриптами wb-rules, так и сторонними службами.
107
+ *
108
+ * Применимо к контролам виртуальных устройств.
109
+ *
110
+ **/
111
+ Public = "public",
112
+ /**
113
+ * Установка значения возможна только скриптами wb-rules.
114
+ *
115
+ * Применимо к контролам виртуальных устройств.
116
+ *
117
+ **/
118
+ Script = "script",
119
+ /**
120
+ * Полный запрет записи, значение доступно только для чтения.
121
+ *
122
+ * Применимо к контролам реальных устройств.
123
+ *
124
+ **/
125
+ ReadOnly = "read-only"
126
+ }
127
+ /**
128
+ * Определяет политику установки значений реальных устройств.
129
+ *
130
+ * @since 0.2.0
131
+ *
132
+ **/
133
+ type ChangePolicy = `${Exclude<ChangePolicies, ChangePolicies.Public | ChangePolicies.Script>}`;
134
+ /**
135
+ * Определяет политику установки значений контролов виртуальных устройств.
136
+ *
137
+ * @since 0.2.0
138
+ *
139
+ **/
140
+ type VirtualChangePolicy = `${Exclude<ChangePolicies, ChangePolicies.ReadOnly>}`;
89
141
  type ValueEventHandler<TValue> = (newValue: TValue, oldValue: TValue) => void;
90
142
  interface Control<TValue> {
91
143
  /** Актуальное значение контрола. */
@@ -117,8 +169,8 @@ type VirtualTypeMapper<K extends keyof WbRules.TypeMappings = keyof WbRules.Type
117
169
  interface BaseControlDef {
118
170
  /** Идентификатор контрола. Если не указан, используется название свойства. */
119
171
  controlId?: string;
120
- /** Если `true`, то значение доступно только для чтения. */
121
- isReadonly?: boolean;
172
+ /** Политика доступа на запись значения. */
173
+ changePolicy?: VirtualChangePolicy | ChangePolicy;
122
174
  }
123
175
  /**
124
176
  * Определения основных контролов.
@@ -166,7 +218,7 @@ type StrictVirtualControls<TControls extends VirtualControls> = TControls & Virt
166
218
  * при описании устройства в `defineDevice()`.
167
219
  **/
168
220
  type CreatedControls<TControls extends Controls | VirtualControls> = {
169
- [K in keyof TControls]: Expand<MaybeReadonlyControl<StrictWhenSpecified<TControls[K], 'defaultValue', WbRules.TypeMappings[TControls[K]['type']]>, TControls[K]['isReadonly']>>;
221
+ [K in keyof TControls]: Expand<MaybeReadonlyControl<StrictWhenSpecified<TControls[K], 'defaultValue', WbRules.TypeMappings[TControls[K]['type']]>, TControls[K]['changePolicy'] extends ChangePolicies.ReadOnly ? true : false>>;
170
222
  };
171
223
  /** Используется для извлечения типа значения из значения по умолчанию. */
172
224
  type InferDefaultType<TProp> = [TProp] extends [{
@@ -431,5 +483,5 @@ declare function getControlSafe(context: DeviceContext, controlId: string): Cont
431
483
  **/
432
484
  declare function getControlSafe(deviceId: string, controlId: string, isReadyFunc: () => boolean): ControlSafe;
433
485
 
434
- export { defineVirtualDevice, defineWiredDevice, defineZigbeeDevice, getControlSafe, getDeviceSafe };
486
+ export { ChangePolicies, defineVirtualDevice, defineWiredDevice, defineZigbeeDevice, getControlSafe, getDeviceSafe };
435
487
  export type { ControlSafe, DeviceContext, DeviceType, PropType, TrackCallback };
package/dist/index.mjs CHANGED
@@ -1,4 +1,4 @@
1
- import { useEvent, isFunction } from '@mirta/basics';
1
+ import { useEvent, mqttToBoolean, isFunction } from '@mirta/basics';
2
2
  export * from '@mirta/basics';
3
3
  import '@mirta/polyfills';
4
4
 
@@ -23,6 +23,45 @@ function getControlSafe(context, controlId, isReadyFunc = () => true) {
23
23
  };
24
24
  }
25
25
 
26
+ /**
27
+ * Набор политик, управляющих доступностью смены значения контрола.
28
+ *
29
+ * @since 0.2.0
30
+ *
31
+ **/
32
+ var ChangePolicies;
33
+ (function (ChangePolicies) {
34
+ /**
35
+ * Политика по умолчанию.
36
+ *
37
+ * - Применяет {@link Public} к контролам типа `switch`, `pushbutton`, `range` и `rgb`;
38
+ *
39
+ * - Применяет {@link Script} к остальным типам контролов.
40
+ *
41
+ **/
42
+ ChangePolicies["Default"] = "default";
43
+ /**
44
+ * Установка значения возможна как скриптами wb-rules, так и сторонними службами.
45
+ *
46
+ * Применимо к контролам виртуальных устройств.
47
+ *
48
+ **/
49
+ ChangePolicies["Public"] = "public";
50
+ /**
51
+ * Установка значения возможна только скриптами wb-rules.
52
+ *
53
+ * Применимо к контролам виртуальных устройств.
54
+ *
55
+ **/
56
+ ChangePolicies["Script"] = "script";
57
+ /**
58
+ * Полный запрет записи, значение доступно только для чтения.
59
+ *
60
+ * Применимо к контролам реальных устройств.
61
+ *
62
+ **/
63
+ ChangePolicies["ReadOnly"] = "read-only";
64
+ })(ChangePolicies || (ChangePolicies = {}));
26
65
  const typeMappings = {
27
66
  'text': 'string',
28
67
  'value': 'number',
@@ -35,10 +74,11 @@ const typeMappings = {
35
74
  /**
36
75
  * Создаёт контрол устройства.
37
76
  * @since 0.1.0
77
+ *
38
78
  **/
39
79
  function createControl(context, controlId, options) {
40
80
  const { deviceType, deviceId } = context;
41
- const { type, isReadonly } = options;
81
+ const { type, changePolicy = ChangePolicies.Default } = options;
42
82
  const defaultValue = 'defaultValue' in options
43
83
  ? options['defaultValue']
44
84
  : void 0;
@@ -61,6 +101,7 @@ function createControl(context, controlId, options) {
61
101
  * Устанавливает новое значение, если оно отличается от существующего.
62
102
  * @param newValue Устанавливаемое значение.
63
103
  * @param preventEmit Предотвращает отправку значения в устройство.
104
+ *
64
105
  **/
65
106
  function setValue(newValue, preventEmit = false) {
66
107
  const oldValue = localValue;
@@ -75,21 +116,26 @@ function createControl(context, controlId, options) {
75
116
  trackMqtt(`/devices/${deviceId}/controls/${controlId}`, (payload) => {
76
117
  const incomingType = typeof payload.value;
77
118
  const expectingType = typeMappings[type];
119
+ let value;
78
120
  if (incomingType === 'string' && expectingType === 'number') {
79
- const value = Number(payload.value);
80
- if (isNaN(value)) {
121
+ const parsedValue = Number(payload.value);
122
+ if (isNaN(parsedValue)) {
81
123
  log.error(`Value ignored: control '${deviceId}/${controlId}' expects number, but Number(value) is NaN.`);
82
124
  return;
83
125
  }
84
- setValue(value, true);
126
+ value = parsedValue;
127
+ }
128
+ else if (expectingType === 'boolean') {
129
+ value = mqttToBoolean(payload.value);
130
+ }
131
+ else if (incomingType !== expectingType) {
132
+ log.error(`Value ignored: control '${deviceId}/${controlId}' expects type '${expectingType}', but received '${incomingType}'`);
133
+ return;
85
134
  }
86
135
  else {
87
- if (incomingType !== expectingType) {
88
- log.error(`Value ignored: control '${deviceId}/${controlId}' expects type '${expectingType}', but received '${incomingType}'`);
89
- return;
90
- }
91
- setValue(payload.value, true);
136
+ value = payload.value;
92
137
  }
138
+ setValue(value, true);
93
139
  });
94
140
  return {
95
141
  get value() {
@@ -102,8 +148,8 @@ function createControl(context, controlId, options) {
102
148
  return localValue;
103
149
  },
104
150
  set value(newValue) {
105
- if (isReadonly) {
106
- log.warning(`Value of '${deviceId}/${controlId}' is readonly`);
151
+ if (changePolicy === ChangePolicies.ReadOnly) {
152
+ log.warning(`A new value of '${deviceId}/${controlId}' has been rejected: control is readonly.`);
107
153
  return;
108
154
  }
109
155
  setValue(newValue);
@@ -139,6 +185,9 @@ function createContext(deviceType, deviceId, state) {
139
185
  };
140
186
  return context;
141
187
  }
188
+ const extendWithReadonly = (changePolicy) => changePolicy && changePolicy !== ChangePolicies.Default
189
+ ? { readonly: changePolicy === ChangePolicies.Script || changePolicy === ChangePolicies.ReadOnly }
190
+ : {};
142
191
  function configureControls(deviceId, controls) {
143
192
  const device = getDevice(deviceId);
144
193
  if (!device)
@@ -152,8 +201,7 @@ function configureControls(deviceId, controls) {
152
201
  device.removeControl(controlId);
153
202
  device.addControl(controlId, assign({}, control, {
154
203
  value: control.defaultValue,
155
- readonly: control.isReadonly,
156
- }));
204
+ }, extendWithReadonly(control.changePolicy)));
157
205
  });
158
206
  }
159
207
  function configureContext(type, deviceId, controls, title) {
@@ -180,9 +228,10 @@ function configureContext(type, deviceId, controls, title) {
180
228
  if (type === 'virtual') {
181
229
  const cells = {};
182
230
  Object.keys(controls).forEach((key) => {
183
- const cell = assign({}, controls[key], {
184
- value: controls[key]['defaultValue'],
185
- }, controls[key]['isReadonly'] ? { readonly: true } : {});
231
+ const control = controls[key];
232
+ const cell = assign({}, control, {
233
+ value: control['defaultValue'],
234
+ }, extendWithReadonly(control['changePolicy']));
186
235
  cells[key] = cell;
187
236
  });
188
237
  global.defineVirtualDevice(deviceId, {
@@ -217,8 +266,8 @@ function createDevice(deviceType, deviceId, propDefs, props, options) {
217
266
  const controlId = controlDef.controlId ?? key;
218
267
  const control = createControl({ deviceType, deviceId, isReady: true }, controlId, {
219
268
  type: controlDef.type,
269
+ changePolicy: controlDef.changePolicy ?? ChangePolicies.Default,
220
270
  defaultValue: controlDef.defaultValue,
221
- isReadonly: controlDef.isReadonly,
222
271
  forceDefault: controlDef.forceDefault,
223
272
  lazyInit: controlDef.lazyInit,
224
273
  });
@@ -320,4 +369,4 @@ function getDeviceSafe(deviceId, isReadyFunc = () => true) {
320
369
  };
321
370
  }
322
371
 
323
- export { defineVirtualDevice, defineWiredDevice, defineZigbeeDevice, getControlSafe, getDeviceSafe };
372
+ export { ChangePolicies, defineVirtualDevice, defineWiredDevice, defineZigbeeDevice, getControlSafe, getDeviceSafe };
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "mirta",
3
3
  "description": "The powerful framework to write smart home automation scripts.",
4
- "version": "0.1.2",
4
+ "version": "0.2.1",
5
5
  "license": "Unlicense",
6
6
  "keywords": [
7
7
  "mirta",
@@ -36,9 +36,9 @@
36
36
  "url": "https://pay.cloudtips.ru/p/58512cca"
37
37
  },
38
38
  "dependencies": {
39
- "@mirta/globals": "0.1.2",
40
- "@mirta/polyfills": "0.1.2",
41
- "@mirta/basics": "0.1.2"
39
+ "@mirta/globals": "0.2.1",
40
+ "@mirta/basics": "0.2.1",
41
+ "@mirta/polyfills": "0.2.1"
42
42
  },
43
43
  "publishConfig": {
44
44
  "access": "public"