@fastybird/smart-panel-backend 1.1.0-alpha.12 → 1.1.0-alpha.13

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 (106) hide show
  1. package/dist/migrations/1000000000026-AddHardwareInputCategories.d.ts +7 -0
  2. package/dist/migrations/1000000000026-AddHardwareInputCategories.js +364 -0
  3. package/dist/migrations/1000000000026-AddHardwareInputCategories.js.map +1 -0
  4. package/dist/modules/devices/controllers/channels.controller.d.ts +5 -1
  5. package/dist/modules/devices/controllers/channels.controller.js +34 -2
  6. package/dist/modules/devices/controllers/channels.controller.js.map +1 -1
  7. package/dist/modules/devices/controllers/channels.properties.controller.js +5 -0
  8. package/dist/modules/devices/controllers/channels.properties.controller.js.map +1 -1
  9. package/dist/modules/devices/controllers/devices.channels.controller.d.ts +5 -1
  10. package/dist/modules/devices/controllers/devices.channels.controller.js +38 -2
  11. package/dist/modules/devices/controllers/devices.channels.controller.js.map +1 -1
  12. package/dist/modules/devices/controllers/devices.channels.properties.controller.js +5 -0
  13. package/dist/modules/devices/controllers/devices.channels.properties.controller.js.map +1 -1
  14. package/dist/modules/devices/devices.constants.d.ts +6 -0
  15. package/dist/modules/devices/devices.constants.js +6 -0
  16. package/dist/modules/devices/devices.constants.js.map +1 -1
  17. package/dist/modules/devices/devices.module.js +6 -0
  18. package/dist/modules/devices/devices.module.js.map +1 -1
  19. package/dist/modules/devices/devices.openapi.d.ts +3 -1
  20. package/dist/modules/devices/devices.openapi.js +7 -0
  21. package/dist/modules/devices/devices.openapi.js.map +1 -1
  22. package/dist/modules/devices/dto/channel-input-occurrence.dto.d.ts +10 -0
  23. package/dist/modules/devices/dto/channel-input-occurrence.dto.js +99 -0
  24. package/dist/modules/devices/dto/channel-input-occurrence.dto.js.map +1 -0
  25. package/dist/modules/devices/models/channel-input-capabilities.model.d.ts +20 -0
  26. package/dist/modules/devices/models/channel-input-capabilities.model.js +111 -0
  27. package/dist/modules/devices/models/channel-input-capabilities.model.js.map +1 -0
  28. package/dist/modules/devices/models/channel-input-occurrence.model.d.ts +22 -0
  29. package/dist/modules/devices/models/channel-input-occurrence.model.js +154 -0
  30. package/dist/modules/devices/models/channel-input-occurrence.model.js.map +1 -0
  31. package/dist/modules/devices/services/channel-input-deduplication.service.d.ts +17 -0
  32. package/dist/modules/devices/services/channel-input-deduplication.service.js +88 -0
  33. package/dist/modules/devices/services/channel-input-deduplication.service.js.map +1 -0
  34. package/dist/modules/devices/services/channel-input-occurrences.service.d.ts +21 -0
  35. package/dist/modules/devices/services/channel-input-occurrences.service.js +163 -0
  36. package/dist/modules/devices/services/channel-input-occurrences.service.js.map +1 -0
  37. package/dist/plugins/devices-shelly-ng/delegates/delegates-manager.service.d.ts +19 -3
  38. package/dist/plugins/devices-shelly-ng/delegates/delegates-manager.service.js +514 -50
  39. package/dist/plugins/devices-shelly-ng/delegates/delegates-manager.service.js.map +1 -1
  40. package/dist/plugins/devices-shelly-ng/delegates/shelly-device.delegate.d.ts +2 -0
  41. package/dist/plugins/devices-shelly-ng/delegates/shelly-device.delegate.js +10 -2
  42. package/dist/plugins/devices-shelly-ng/delegates/shelly-device.delegate.js.map +1 -1
  43. package/dist/plugins/devices-shelly-ng/devices-shelly-ng.constants.js +3 -2
  44. package/dist/plugins/devices-shelly-ng/devices-shelly-ng.constants.js.map +1 -1
  45. package/dist/plugins/devices-shelly-ng/mappings/definitions/inputs.yaml +80 -0
  46. package/dist/plugins/devices-shelly-ng/mappings/mapping-loader.service.js +7 -1
  47. package/dist/plugins/devices-shelly-ng/mappings/mapping-loader.service.js.map +1 -1
  48. package/dist/plugins/devices-shelly-ng/mappings/mapping.types.d.ts +6 -3
  49. package/dist/plugins/devices-shelly-ng/mappings/schema/mapping-schema.json +5 -0
  50. package/dist/plugins/devices-shelly-ng/services/device-manager.service.js +130 -7
  51. package/dist/plugins/devices-shelly-ng/services/device-manager.service.js.map +1 -1
  52. package/dist/plugins/devices-shelly-ng/services/poll-placement-diagnostics.service.js +3 -2
  53. package/dist/plugins/devices-shelly-ng/services/poll-placement-diagnostics.service.js.map +1 -1
  54. package/dist/plugins/devices-shelly-ng/services/shelly-ng.service.js +1 -1
  55. package/dist/plugins/devices-shelly-ng/services/shelly-ng.service.js.map +1 -1
  56. package/dist/plugins/devices-shelly-ng/services/shelly-rpc-client.service.js +3 -0
  57. package/dist/plugins/devices-shelly-ng/services/shelly-rpc-client.service.js.map +1 -1
  58. package/dist/plugins/devices-shelly-ng/services/shelly-ws-server.service.d.ts +1 -0
  59. package/dist/plugins/devices-shelly-ng/services/shelly-ws-server.service.js +13 -1
  60. package/dist/plugins/devices-shelly-ng/services/shelly-ws-server.service.js.map +1 -1
  61. package/dist/plugins/devices-shelly-v1/devices-shelly-v1.constants.d.ts +2 -0
  62. package/dist/plugins/devices-shelly-v1/devices-shelly-v1.constants.js +66 -4
  63. package/dist/plugins/devices-shelly-v1/devices-shelly-v1.constants.js.map +1 -1
  64. package/dist/plugins/devices-shelly-v1/devices-shelly-v1.plugin.js +2 -0
  65. package/dist/plugins/devices-shelly-v1/devices-shelly-v1.plugin.js.map +1 -1
  66. package/dist/plugins/devices-shelly-v1/platforms/shelly-v1.device.platform.js +7 -0
  67. package/dist/plugins/devices-shelly-v1/platforms/shelly-v1.device.platform.js.map +1 -1
  68. package/dist/plugins/devices-shelly-v1/services/device-mapper.service.d.ts +3 -1
  69. package/dist/plugins/devices-shelly-v1/services/device-mapper.service.js +20 -2
  70. package/dist/plugins/devices-shelly-v1/services/device-mapper.service.js.map +1 -1
  71. package/dist/plugins/devices-shelly-v1/services/shelly-v1-input-tracker.service.d.ts +16 -0
  72. package/dist/plugins/devices-shelly-v1/services/shelly-v1-input-tracker.service.js +107 -0
  73. package/dist/plugins/devices-shelly-v1/services/shelly-v1-input-tracker.service.js.map +1 -0
  74. package/dist/plugins/devices-shelly-v1/services/shelly-v1.service.d.ts +7 -1
  75. package/dist/plugins/devices-shelly-v1/services/shelly-v1.service.js +98 -3
  76. package/dist/plugins/devices-shelly-v1/services/shelly-v1.service.js.map +1 -1
  77. package/dist/plugins/devices-zigbee2mqtt/devices-zigbee2mqtt.constants.d.ts +3 -0
  78. package/dist/plugins/devices-zigbee2mqtt/devices-zigbee2mqtt.constants.js +41 -5
  79. package/dist/plugins/devices-zigbee2mqtt/devices-zigbee2mqtt.constants.js.map +1 -1
  80. package/dist/plugins/devices-zigbee2mqtt/interfaces/zigbee2mqtt.interface.d.ts +7 -1
  81. package/dist/plugins/devices-zigbee2mqtt/mappings/config-driven.converter.js +16 -15
  82. package/dist/plugins/devices-zigbee2mqtt/mappings/config-driven.converter.js.map +1 -1
  83. package/dist/plugins/devices-zigbee2mqtt/mappings/definitions/remotes.yaml +505 -74
  84. package/dist/plugins/devices-zigbee2mqtt/mappings/schema/mapping-schema.json +3 -3
  85. package/dist/plugins/devices-zigbee2mqtt/mappings/transformers/transformer.types.d.ts +1 -1
  86. package/dist/plugins/devices-zigbee2mqtt/services/base-client-adapter.d.ts +8 -2
  87. package/dist/plugins/devices-zigbee2mqtt/services/base-client-adapter.js +6 -5
  88. package/dist/plugins/devices-zigbee2mqtt/services/base-client-adapter.js.map +1 -1
  89. package/dist/plugins/devices-zigbee2mqtt/services/device-adoption.service.js +18 -11
  90. package/dist/plugins/devices-zigbee2mqtt/services/device-adoption.service.js.map +1 -1
  91. package/dist/plugins/devices-zigbee2mqtt/services/device-mapper.service.d.ts +9 -3
  92. package/dist/plugins/devices-zigbee2mqtt/services/device-mapper.service.js +166 -3
  93. package/dist/plugins/devices-zigbee2mqtt/services/device-mapper.service.js.map +1 -1
  94. package/dist/plugins/devices-zigbee2mqtt/services/mqtt-client-adapter.service.js +5 -2
  95. package/dist/plugins/devices-zigbee2mqtt/services/mqtt-client-adapter.service.js.map +1 -1
  96. package/dist/plugins/devices-zigbee2mqtt/services/zigbee2mqtt.service.d.ts +1 -0
  97. package/dist/plugins/devices-zigbee2mqtt/services/zigbee2mqtt.service.js +44 -4
  98. package/dist/plugins/devices-zigbee2mqtt/services/zigbee2mqtt.service.js.map +1 -1
  99. package/dist/spec/channels.d.ts +51 -1
  100. package/dist/spec/channels.js +64 -1
  101. package/dist/spec/channels.js.map +1 -1
  102. package/dist/spec/devices.d.ts +155 -2
  103. package/dist/spec/devices.js +161 -2
  104. package/dist/spec/devices.js.map +1 -1
  105. package/dist/tsconfig.build.tsbuildinfo +1 -1
  106. package/package.json +2 -2
@@ -18,6 +18,7 @@ const common_1 = require("@nestjs/common");
18
18
  const logger_1 = require("../../../common/logger");
19
19
  const transform_utils_1 = require("../../../common/utils/transform.utils");
20
20
  const devices_constants_1 = require("../../../modules/devices/devices.constants");
21
+ const channel_input_occurrences_service_1 = require("../../../modules/devices/services/channel-input-occurrences.service");
21
22
  const channels_properties_service_1 = require("../../../modules/devices/services/channels.properties.service");
22
23
  const channels_service_1 = require("../../../modules/devices/services/channels.service");
23
24
  const command_latency_trace_collector_service_1 = require("../../../modules/devices/services/command-latency-trace-collector.service");
@@ -33,8 +34,17 @@ const device_manager_service_1 = require("../services/device-manager.service");
33
34
  const poll_placement_diagnostics_service_1 = require("../services/poll-placement-diagnostics.service");
34
35
  const transform_utils_2 = require("../utils/transform.utils");
35
36
  const shelly_device_delegate_1 = require("./shelly-device.delegate");
37
+ const SHELLY_TO_PANEL_EVENT_MAP = {
38
+ single_push: 'press',
39
+ double_push: 'double_press',
40
+ triple_push: 'triple_press',
41
+ triple_press: 'triple_press',
42
+ long_push: 'long_press',
43
+ btn_down: 'down',
44
+ btn_up: 'up',
45
+ };
36
46
  let DelegatesManagerService = DelegatesManagerService_1 = class DelegatesManagerService {
37
- constructor(devicesService, channelsService, channelsPropertiesService, deviceConnectivityService, deviceManagerService, deviceAddressService, propertyMappingStorage, transformerRegistry, commandLatencyTraceCollector = new command_latency_trace_collector_service_1.CommandLatencyTraceCollectorService(), pollPlacementDiagnostics = new poll_placement_diagnostics_service_1.PollPlacementDiagnosticsService()) {
47
+ constructor(devicesService, channelsService, channelsPropertiesService, deviceConnectivityService, deviceManagerService, deviceAddressService, propertyMappingStorage, transformerRegistry, commandLatencyTraceCollector = new command_latency_trace_collector_service_1.CommandLatencyTraceCollectorService(), pollPlacementDiagnostics = new poll_placement_diagnostics_service_1.PollPlacementDiagnosticsService(), channelInputOccurrencesService) {
38
48
  this.devicesService = devicesService;
39
49
  this.channelsService = channelsService;
40
50
  this.channelsPropertiesService = channelsPropertiesService;
@@ -45,16 +55,24 @@ let DelegatesManagerService = DelegatesManagerService_1 = class DelegatesManager
45
55
  this.transformerRegistry = transformerRegistry;
46
56
  this.commandLatencyTraceCollector = commandLatencyTraceCollector;
47
57
  this.pollPlacementDiagnostics = pollPlacementDiagnostics;
58
+ this.channelInputOccurrencesService = channelInputOccurrencesService;
48
59
  this.logger = (0, logger_1.createExtensionLogger)(devices_shelly_ng_constants_1.DEVICES_SHELLY_NG_PLUGIN_NAME, 'DelegatesManagerService');
49
60
  this.delegates = new Map();
50
61
  this.delegateValueHandlers = new Map();
51
62
  this.delegateConnectionHandlers = new Map();
63
+ this.delegateEventHandlers = new Map();
64
+ this.delegateEventQueues = new Map();
65
+ this.delegateEventGenerations = new Map();
52
66
  this.changeHandlers = new Map();
53
67
  this.setChannelsHandlers = new Map();
54
68
  this.setPropertiesHandlers = new Map();
55
69
  this.pendingWrites = new Map();
70
+ this.delegatePendingWrites = new Map();
71
+ this.delegateActiveWrites = new Map();
56
72
  this.pendingPollWrites = new Map();
57
73
  this.activePollWrites = new Map();
74
+ this.activePollDrainPromises = new Map();
75
+ this.delegatePollDeviceIds = new Map();
58
76
  this.delegatePollGenerations = new Map();
59
77
  this.currentValueUpdateContext = null;
60
78
  this.propertiesMap = new Map();
@@ -77,7 +95,7 @@ let DelegatesManagerService = DelegatesManagerService_1 = class DelegatesManager
77
95
  async performInsert(shelly, force = false) {
78
96
  if (this.delegates.has(shelly.id)) {
79
97
  if (force) {
80
- this.performRemove(shelly.id);
98
+ await this.performRemove(shelly.id);
81
99
  }
82
100
  else {
83
101
  return this.delegates.get(shelly.id);
@@ -823,6 +841,73 @@ let DelegatesManagerService = DelegatesManagerService_1 = class DelegatesManager
823
841
  await this.wireMeterProperty(delegate, device, comp.key, channelIdentifier, 'total_act_energy', 'total_act_energy', comp.total_act_energy, false);
824
842
  await this.wireMeterProperty(delegate, device, comp.key, channelIdentifier, 'total_act_ret_energy', 'total_act_ret_energy', comp.total_act_ret_energy, false);
825
843
  }
844
+ if (this.insertGeneration.get(shelly.id) !== generation)
845
+ return delegate;
846
+ for (const comp of delegate.inputs.values()) {
847
+ const channel = await this.channelsService.findOneBy('identifier', `input:${comp.id}`, device.id, devices_shelly_ng_constants_1.DEVICES_SHELLY_NG_TYPE);
848
+ if (channel === null) {
849
+ continue;
850
+ }
851
+ if (channel.category === devices_constants_1.ChannelCategory.BUTTON) {
852
+ const detectedProp = await this.channelsPropertiesService.findOneBy('category', devices_constants_1.PropertyCategory.DETECTED, channel.id);
853
+ if (detectedProp) {
854
+ await this.setDefaultPropertyValue(device.id, detectedProp, Boolean(comp.state));
855
+ this.changeHandlers.set(`${delegate.id}|${comp.key}|state`, (val) => {
856
+ if (typeof val === 'boolean') {
857
+ this.handleChange(detectedProp, val, false).catch((err) => {
858
+ this.logger.error(`Failed to update detected for ${detectedProp.id}: ${err.message}`);
859
+ });
860
+ }
861
+ });
862
+ }
863
+ }
864
+ else if (channel.category === devices_constants_1.ChannelCategory.BINARY_INPUT) {
865
+ const stateProp = await this.channelsPropertiesService.findOneBy('category', devices_constants_1.PropertyCategory.STATE, channel.id);
866
+ if (stateProp) {
867
+ await this.setDefaultPropertyValue(device.id, stateProp, Boolean(comp.state));
868
+ this.changeHandlers.set(`${delegate.id}|${comp.key}|state`, (val) => {
869
+ if (typeof val === 'boolean') {
870
+ this.handleChange(stateProp, val, false).catch((err) => {
871
+ this.logger.error(`Failed to update state for ${stateProp.id}: ${err.message}`);
872
+ });
873
+ }
874
+ });
875
+ }
876
+ }
877
+ else if (channel.category === devices_constants_1.ChannelCategory.ANALOG_INPUT) {
878
+ const valueProp = await this.channelsPropertiesService.findOneBy('category', devices_constants_1.PropertyCategory.VALUE, channel.id);
879
+ if (valueProp) {
880
+ const hasExtendedPercent = typeof comp.xpercent === 'number';
881
+ const hasExtendedCount = typeof comp.counts?.xtotal === 'number';
882
+ let initialVal;
883
+ if (comp.counts !== undefined) {
884
+ initialVal = hasExtendedCount ? comp.counts.xtotal : (comp.counts.total ?? 0);
885
+ }
886
+ else {
887
+ initialVal = hasExtendedPercent ? comp.xpercent : (comp.percent ?? 0);
888
+ }
889
+ await this.setDefaultPropertyValue(device.id, valueProp, initialVal);
890
+ const percentAttr = hasExtendedPercent ? 'xpercent' : 'percent';
891
+ this.changeHandlers.set(`${delegate.id}|${comp.key}|${percentAttr}`, (val) => {
892
+ const n = (0, transform_utils_1.coerceNumberSafe)(val);
893
+ if (n !== null) {
894
+ this.handleChange(valueProp, n, false).catch((err) => {
895
+ this.logger.error(`Failed to update value for ${valueProp.id}: ${err.message}`);
896
+ });
897
+ }
898
+ });
899
+ const countAttr = hasExtendedCount ? 'counts.xtotal' : 'counts.total';
900
+ this.changeHandlers.set(`${delegate.id}|${comp.key}|${countAttr}`, (val) => {
901
+ const n = (0, transform_utils_1.coerceNumberSafe)(val);
902
+ if (n !== null) {
903
+ this.handleChange(valueProp, n, false).catch((err) => {
904
+ this.logger.error(`Failed to update counts for ${valueProp.id}: ${err.message}`);
905
+ });
906
+ }
907
+ });
908
+ }
909
+ }
910
+ }
826
911
  if (this.insertGeneration.get(shelly.id) !== generation)
827
912
  return delegate;
828
913
  const valueHandler = (compKey, attr, val, origin = 'notify') => {
@@ -955,6 +1040,34 @@ let DelegatesManagerService = DelegatesManagerService_1 = class DelegatesManager
955
1040
  };
956
1041
  delegate.on('connected', connectionHandler);
957
1042
  this.delegateConnectionHandlers.set(delegate.id, connectionHandler);
1043
+ const eventGeneration = (this.delegateEventGenerations.get(delegate.id) ?? 0) + 1;
1044
+ this.delegateEventGenerations.set(delegate.id, eventGeneration);
1045
+ const eventHandler = (params) => {
1046
+ const previousPromise = this.delegateEventQueues.get(delegate.id) ?? Promise.resolve();
1047
+ const nextPromise = previousPromise
1048
+ .then(async () => {
1049
+ if (this.delegateEventGenerations.get(delegate.id) !== eventGeneration) {
1050
+ return;
1051
+ }
1052
+ await this.handleDeviceEvent(delegate, device, params, eventGeneration);
1053
+ })
1054
+ .catch((error) => {
1055
+ const err = error;
1056
+ this.logger.error(`Shelly event error for device=${device.id}`, {
1057
+ resource: device.id,
1058
+ message: err.message,
1059
+ stack: err.stack,
1060
+ });
1061
+ })
1062
+ .finally(() => {
1063
+ if (this.delegateEventQueues.get(delegate.id) === nextPromise) {
1064
+ this.delegateEventQueues.delete(delegate.id);
1065
+ }
1066
+ });
1067
+ this.delegateEventQueues.set(delegate.id, nextPromise);
1068
+ };
1069
+ delegate.on('event', eventHandler);
1070
+ this.delegateEventHandlers.set(delegate.id, eventHandler);
958
1071
  if (delegate.connected) {
959
1072
  connectionHandler(true);
960
1073
  }
@@ -962,19 +1075,45 @@ let DelegatesManagerService = DelegatesManagerService_1 = class DelegatesManager
962
1075
  return delegate;
963
1076
  }
964
1077
  remove(deviceId) {
965
- return this.withDeviceLock(deviceId, () => {
966
- this.performRemove(deviceId);
967
- return Promise.resolve();
968
- });
1078
+ return this.withDeviceLock(deviceId, () => this.performRemove(deviceId));
1079
+ }
1080
+ async teardownDelegateEvents(delegate) {
1081
+ const eventHandler = this.delegateEventHandlers.get(delegate.id);
1082
+ if (eventHandler) {
1083
+ delegate.off('event', eventHandler);
1084
+ }
1085
+ const pendingQueue = this.delegateEventQueues.get(delegate.id);
1086
+ this.delegateEventHandlers.delete(delegate.id);
1087
+ this.delegateEventQueues.delete(delegate.id);
1088
+ this.delegateEventGenerations.set(delegate.id, (this.delegateEventGenerations.get(delegate.id) ?? 0) + 1);
1089
+ if (pendingQueue !== undefined) {
1090
+ try {
1091
+ await pendingQueue;
1092
+ }
1093
+ catch (err) {
1094
+ this.logger.error(`Error while awaiting event queue teardown for delegate=${delegate.id}`, {
1095
+ message: err.message,
1096
+ stack: err.stack,
1097
+ });
1098
+ }
1099
+ }
969
1100
  }
970
- performRemove(deviceId) {
1101
+ async performRemove(deviceId) {
971
1102
  const delegate = this.delegates.get(deviceId);
972
1103
  if (!delegate) {
973
1104
  return;
974
1105
  }
1106
+ const valueHandler = this.delegateValueHandlers.get(delegate.id);
1107
+ if (valueHandler) {
1108
+ delegate.off('value', valueHandler);
1109
+ this.delegateValueHandlers.delete(delegate.id);
1110
+ }
975
1111
  this.delegatePollGenerations.set(deviceId, (this.delegatePollGenerations.get(deviceId) ?? 0) + 1);
976
1112
  this.cancelPollWritesForDelegate(deviceId);
977
- const valueHandler = this.delegateValueHandlers.get(delegate.id);
1113
+ this.cancelPendingWritesForDelegate(deviceId);
1114
+ await this.teardownDelegateEvents(delegate);
1115
+ await this.awaitActiveWritesForDelegate(deviceId);
1116
+ await this.awaitActivePollDrainsForDelegate(deviceId);
978
1117
  const connectionHandler = this.delegateConnectionHandlers.get(delegate.id);
979
1118
  if (connectionHandler) {
980
1119
  connectionHandler(null);
@@ -997,14 +1136,10 @@ let DelegatesManagerService = DelegatesManagerService_1 = class DelegatesManager
997
1136
  });
998
1137
  }
999
1138
  }
1000
- if (valueHandler) {
1001
- delegate.off('value', valueHandler);
1002
- }
1003
1139
  if (connectionHandler) {
1004
1140
  delegate.off('connected', connectionHandler);
1141
+ this.delegateConnectionHandlers.delete(delegate.id);
1005
1142
  }
1006
- this.delegateValueHandlers.delete(delegate.id);
1007
- this.delegateConnectionHandlers.delete(delegate.id);
1008
1143
  for (const key of Array.from(this.changeHandlers.keys())) {
1009
1144
  if (key.startsWith(`${deviceId}|`)) {
1010
1145
  this.changeHandlers.delete(key);
@@ -1046,6 +1181,10 @@ let DelegatesManagerService = DelegatesManagerService_1 = class DelegatesManager
1046
1181
  this.delegateToIdentifier.delete(deviceId);
1047
1182
  this.pollPlacementDiagnostics.removeDelegate(deviceId);
1048
1183
  this.insertGeneration.delete(deviceId);
1184
+ this.delegatePendingWrites.delete(deviceId);
1185
+ this.delegateActiveWrites.delete(deviceId);
1186
+ this.delegatePollGenerations.delete(deviceId);
1187
+ this.delegatePollDeviceIds.delete(deviceId);
1049
1188
  this.delegates.delete(deviceId);
1050
1189
  this.logger.log(`Detached Shelly device=${deviceId}`, { resource: deviceId });
1051
1190
  }
@@ -1110,8 +1249,102 @@ let DelegatesManagerService = DelegatesManagerService_1 = class DelegatesManager
1110
1249
  return value;
1111
1250
  }
1112
1251
  }
1113
- async handleChange(property, value, immediately = true) {
1252
+ async handleDeviceEvent(delegate, device, params, generation) {
1253
+ if (this.delegateEventGenerations.get(delegate.id) !== generation) {
1254
+ return;
1255
+ }
1256
+ if (typeof params !== 'object' || params === null) {
1257
+ return;
1258
+ }
1259
+ const eventPayload = params;
1260
+ const rawEvents = Array.isArray(eventPayload.events) ? eventPayload.events : [eventPayload];
1261
+ for (const rawItem of rawEvents) {
1262
+ if (this.delegateEventGenerations.get(delegate.id) !== generation) {
1263
+ return;
1264
+ }
1265
+ if (typeof rawItem !== 'object' || rawItem === null) {
1266
+ continue;
1267
+ }
1268
+ const item = rawItem;
1269
+ const rawEvent = typeof item.event === 'string' ? item.event : undefined;
1270
+ if (!rawEvent) {
1271
+ continue;
1272
+ }
1273
+ const mappedEvent = SHELLY_TO_PANEL_EVENT_MAP[rawEvent];
1274
+ if (!mappedEvent) {
1275
+ this.logger.debug(`Unknown Shelly event=${rawEvent} from device=${device.id}`);
1276
+ continue;
1277
+ }
1278
+ let channelIdentifier;
1279
+ if (typeof item.component === 'string' && item.component.length > 0) {
1280
+ channelIdentifier = item.component;
1281
+ }
1282
+ else if (typeof item.id === 'number' || typeof item.id === 'string') {
1283
+ channelIdentifier = `input:${item.id}`;
1284
+ }
1285
+ else {
1286
+ continue;
1287
+ }
1288
+ const channel = await this.channelsService.findOneBy('identifier', channelIdentifier, device.id, devices_shelly_ng_constants_1.DEVICES_SHELLY_NG_TYPE);
1289
+ if (this.delegateEventGenerations.get(delegate.id) !== generation) {
1290
+ return;
1291
+ }
1292
+ if (!channel) {
1293
+ this.logger.debug(`No channel found for ${channelIdentifier} on device=${device.id}`);
1294
+ continue;
1295
+ }
1296
+ if (rawEvent === 'btn_down' || rawEvent === 'btn_up') {
1297
+ try {
1298
+ const detectedProp = await this.channelsPropertiesService.findOneBy('category', devices_constants_1.PropertyCategory.DETECTED, channel.id);
1299
+ if (this.delegateEventGenerations.get(delegate.id) !== generation) {
1300
+ return;
1301
+ }
1302
+ if (detectedProp) {
1303
+ await this.handleChange(detectedProp, rawEvent === 'btn_down', true);
1304
+ }
1305
+ }
1306
+ catch (err) {
1307
+ this.logger.error(`Failed to update DETECTED property for channel=${channel.id} on device=${device.id}: ${err.message}`);
1308
+ }
1309
+ }
1310
+ if (this.delegateEventGenerations.get(delegate.id) !== generation) {
1311
+ return;
1312
+ }
1313
+ const eventProp = await this.channelsPropertiesService.findOneBy('category', devices_constants_1.PropertyCategory.EVENT, channel.id);
1314
+ if (this.delegateEventGenerations.get(delegate.id) !== generation) {
1315
+ return;
1316
+ }
1317
+ if (!eventProp) {
1318
+ this.logger.debug(`No EVENT property on channel=${channel.id} for device=${device.id}`);
1319
+ continue;
1320
+ }
1321
+ if (this.channelInputOccurrencesService) {
1322
+ const ts = typeof item.ts === 'number' ? item.ts : typeof eventPayload.ts === 'number' ? eventPayload.ts : undefined;
1323
+ const timestamp = ts !== undefined ? new Date(ts < 1e11 ? Math.round(ts * 1000) : Math.round(ts)) : new Date();
1324
+ const sourceOccurrenceId = `${channel.id}:${rawEvent}:${ts ?? timestamp.getTime()}`;
1325
+ try {
1326
+ if (this.delegateEventGenerations.get(delegate.id) !== generation) {
1327
+ return;
1328
+ }
1329
+ await this.channelInputOccurrencesService.publishOccurrence({
1330
+ deviceId: device.id,
1331
+ channelId: channel.id,
1332
+ propertyId: eventProp.id,
1333
+ event: mappedEvent,
1334
+ nativeEventType: rawEvent,
1335
+ sourceTimestamp: timestamp.toISOString(),
1336
+ sourceOccurrenceId,
1337
+ });
1338
+ }
1339
+ catch (err) {
1340
+ this.logger.error(`Failed to publish occurrence for channel=${channel.id} on device=${device.id}: ${err.message}`);
1341
+ }
1342
+ }
1343
+ }
1344
+ }
1345
+ async handleChange(property, value, immediately = true, delegateId) {
1114
1346
  const context = this.currentValueUpdateContext;
1347
+ const effectiveDelegateId = delegateId ?? context?.delegateId;
1115
1348
  if (context !== null) {
1116
1349
  this.commandLatencyTraceCollector.recordProviderReceipt(property.id, value, context.origin, {
1117
1350
  delegateId: context.delegateId,
@@ -1129,10 +1362,36 @@ let DelegatesManagerService = DelegatesManagerService_1 = class DelegatesManager
1129
1362
  if (immediately) {
1130
1363
  this.cancelScheduledWrite(property.id);
1131
1364
  this.cancelPollWrite(property.id);
1132
- await this.writeValueToProperty(property, value);
1365
+ if (effectiveDelegateId) {
1366
+ let active = this.delegateActiveWrites.get(effectiveDelegateId);
1367
+ if (!active) {
1368
+ active = new Set();
1369
+ this.delegateActiveWrites.set(effectiveDelegateId, active);
1370
+ }
1371
+ const writePromise = (async () => {
1372
+ await this.writeValueToProperty(property, value);
1373
+ })();
1374
+ void writePromise.catch(() => { });
1375
+ active.add(writePromise);
1376
+ try {
1377
+ await writePromise;
1378
+ }
1379
+ finally {
1380
+ const currentActive = this.delegateActiveWrites.get(effectiveDelegateId);
1381
+ if (currentActive) {
1382
+ currentActive.delete(writePromise);
1383
+ if (currentActive.size === 0) {
1384
+ this.delegateActiveWrites.delete(effectiveDelegateId);
1385
+ }
1386
+ }
1387
+ }
1388
+ }
1389
+ else {
1390
+ await this.writeValueToProperty(property, value);
1391
+ }
1133
1392
  }
1134
1393
  else {
1135
- this.scheduleWrite(property, value);
1394
+ this.scheduleWrite(property, value, effectiveDelegateId);
1136
1395
  }
1137
1396
  }
1138
1397
  async setDefaultPropertyValue(deviceId, property, value) {
@@ -1270,10 +1529,21 @@ let DelegatesManagerService = DelegatesManagerService_1 = class DelegatesManager
1270
1529
  });
1271
1530
  });
1272
1531
  }
1273
- scheduleWrite(property, value) {
1532
+ scheduleWrite(property, value, delegateId) {
1274
1533
  const existing = this.pendingWrites.get(property.id);
1275
1534
  if (existing) {
1276
1535
  clearTimeout(existing);
1536
+ for (const set of this.delegatePendingWrites.values()) {
1537
+ set.delete(property.id);
1538
+ }
1539
+ }
1540
+ if (delegateId) {
1541
+ let set = this.delegatePendingWrites.get(delegateId);
1542
+ if (!set) {
1543
+ set = new Set();
1544
+ this.delegatePendingWrites.set(delegateId, set);
1545
+ }
1546
+ set.add(property.id);
1277
1547
  }
1278
1548
  const t = setTimeout(() => {
1279
1549
  const current = this.pendingWrites.get(property.id);
@@ -1281,13 +1551,53 @@ let DelegatesManagerService = DelegatesManagerService_1 = class DelegatesManager
1281
1551
  clearTimeout(current);
1282
1552
  this.pendingWrites.delete(property.id);
1283
1553
  }
1284
- this.writeValueToProperty(property, value).catch((err) => {
1285
- this.logger.error(`Failed to process scheduled write of value=${(0, transform_utils_1.safeToString)(value)} to property=${property.id}`, { message: err.message, stack: err.stack });
1286
- });
1554
+ if (delegateId) {
1555
+ const set = this.delegatePendingWrites.get(delegateId);
1556
+ if (set) {
1557
+ set.delete(property.id);
1558
+ if (set.size === 0) {
1559
+ this.delegatePendingWrites.delete(delegateId);
1560
+ }
1561
+ }
1562
+ if (!this.delegates.has(delegateId)) {
1563
+ return;
1564
+ }
1565
+ }
1566
+ const writePromise = (async () => {
1567
+ try {
1568
+ await this.writeValueToProperty(property, value);
1569
+ }
1570
+ catch (err) {
1571
+ this.logger.error(`Failed to process scheduled write of value=${(0, transform_utils_1.safeToString)(value)} to property=${property.id}`, { message: err.message, stack: err.stack });
1572
+ }
1573
+ })();
1574
+ if (delegateId) {
1575
+ let active = this.delegateActiveWrites.get(delegateId);
1576
+ if (!active) {
1577
+ active = new Set();
1578
+ this.delegateActiveWrites.set(delegateId, active);
1579
+ }
1580
+ active.add(writePromise);
1581
+ void writePromise.finally(() => {
1582
+ const currentActive = this.delegateActiveWrites.get(delegateId);
1583
+ if (currentActive) {
1584
+ currentActive.delete(writePromise);
1585
+ if (currentActive.size === 0) {
1586
+ this.delegateActiveWrites.delete(delegateId);
1587
+ }
1588
+ }
1589
+ });
1590
+ }
1287
1591
  }, 250);
1288
1592
  this.pendingWrites.set(property.id, t);
1289
1593
  }
1290
1594
  schedulePollWrite(context, property, value) {
1595
+ let tracked = this.delegatePollDeviceIds.get(context.delegateId);
1596
+ if (!tracked) {
1597
+ tracked = new Set();
1598
+ this.delegatePollDeviceIds.set(context.delegateId, tracked);
1599
+ }
1600
+ tracked.add(context.deviceId);
1291
1601
  let bucket = this.pendingPollWrites.get(context.deviceId);
1292
1602
  if (!bucket) {
1293
1603
  const values = new Map();
@@ -1317,32 +1627,53 @@ let DelegatesManagerService = DelegatesManagerService_1 = class DelegatesManager
1317
1627
  this.activePollWrites.set(deviceId, activeBuckets);
1318
1628
  }
1319
1629
  activeBuckets.add(bucket);
1320
- try {
1321
- for (const [propertyId, pending] of [...bucket.values.entries()]) {
1322
- if (bucket.values.get(propertyId) !== pending) {
1323
- continue;
1324
- }
1325
- if (!this.delegates.has(pending.delegateId) ||
1326
- (this.delegatePollGenerations.get(pending.delegateId) ?? 0) !== pending.delegateGeneration) {
1327
- continue;
1328
- }
1329
- try {
1330
- this.commandLatencyTraceCollector.recordPollActivity(pending.property.id, 'poll-drain', {
1331
- delegateId: pending.delegateId,
1332
- deviceId,
1333
- });
1334
- await this.writeValueToProperty(pending.property, pending.value);
1630
+ const drainPromise = (async () => {
1631
+ try {
1632
+ for (const [propertyId, pending] of [...bucket.values.entries()]) {
1633
+ if (bucket.values.get(propertyId) !== pending) {
1634
+ continue;
1635
+ }
1636
+ if (!this.delegates.has(pending.delegateId) ||
1637
+ (this.delegatePollGenerations.get(pending.delegateId) ?? 0) !== pending.delegateGeneration) {
1638
+ continue;
1639
+ }
1640
+ try {
1641
+ this.commandLatencyTraceCollector.recordPollActivity(pending.property.id, 'poll-drain', {
1642
+ delegateId: pending.delegateId,
1643
+ deviceId,
1644
+ });
1645
+ await this.writeValueToProperty(pending.property, pending.value);
1646
+ }
1647
+ catch (error) {
1648
+ const err = error;
1649
+ this.logger.error(`Failed to process poll write of value=${(0, transform_utils_1.safeToString)(pending.value)} to property=${pending.property.id}`, { message: err.message, stack: err.stack });
1650
+ }
1335
1651
  }
1336
- catch (error) {
1337
- const err = error;
1338
- this.logger.error(`Failed to process poll write of value=${(0, transform_utils_1.safeToString)(pending.value)} to property=${pending.property.id}`, { message: err.message, stack: err.stack });
1652
+ }
1653
+ finally {
1654
+ activeBuckets.delete(bucket);
1655
+ if (activeBuckets.size === 0 && this.activePollWrites.get(deviceId) === activeBuckets) {
1656
+ this.activePollWrites.delete(deviceId);
1339
1657
  }
1340
1658
  }
1659
+ })();
1660
+ void drainPromise.catch(() => { });
1661
+ let drains = this.activePollDrainPromises.get(deviceId);
1662
+ if (!drains) {
1663
+ drains = new Set();
1664
+ this.activePollDrainPromises.set(deviceId, drains);
1665
+ }
1666
+ drains.add(drainPromise);
1667
+ try {
1668
+ await drainPromise;
1341
1669
  }
1342
1670
  finally {
1343
- activeBuckets.delete(bucket);
1344
- if (activeBuckets.size === 0 && this.activePollWrites.get(deviceId) === activeBuckets) {
1345
- this.activePollWrites.delete(deviceId);
1671
+ const currentDrains = this.activePollDrainPromises.get(deviceId);
1672
+ if (currentDrains) {
1673
+ currentDrains.delete(drainPromise);
1674
+ if (currentDrains.size === 0) {
1675
+ this.activePollDrainPromises.delete(deviceId);
1676
+ }
1346
1677
  }
1347
1678
  }
1348
1679
  }
@@ -1352,6 +1683,117 @@ let DelegatesManagerService = DelegatesManagerService_1 = class DelegatesManager
1352
1683
  clearTimeout(timer);
1353
1684
  this.pendingWrites.delete(propertyId);
1354
1685
  }
1686
+ for (const [delegateId, set] of this.delegatePendingWrites.entries()) {
1687
+ set.delete(propertyId);
1688
+ if (set.size === 0) {
1689
+ this.delegatePendingWrites.delete(delegateId);
1690
+ }
1691
+ }
1692
+ }
1693
+ cancelPendingWritesForDelegate(delegateId) {
1694
+ const pendingPropertyIds = this.delegatePendingWrites.get(delegateId);
1695
+ if (pendingPropertyIds) {
1696
+ for (const propertyId of pendingPropertyIds) {
1697
+ const timer = this.pendingWrites.get(propertyId);
1698
+ if (timer) {
1699
+ clearTimeout(timer);
1700
+ this.pendingWrites.delete(propertyId);
1701
+ }
1702
+ }
1703
+ this.delegatePendingWrites.delete(delegateId);
1704
+ }
1705
+ }
1706
+ async awaitActiveWritesForDelegate(delegateId) {
1707
+ while (true) {
1708
+ const activeWrites = this.delegateActiveWrites.get(delegateId);
1709
+ if (!activeWrites || activeWrites.size === 0) {
1710
+ break;
1711
+ }
1712
+ const toAwait = Array.from(activeWrites);
1713
+ await Promise.allSettled(toAwait);
1714
+ for (const p of toAwait) {
1715
+ activeWrites.delete(p);
1716
+ }
1717
+ }
1718
+ this.delegateActiveWrites.delete(delegateId);
1719
+ }
1720
+ async awaitAllActiveWrites() {
1721
+ while (true) {
1722
+ const allPromises = [];
1723
+ for (const activeWrites of this.delegateActiveWrites.values()) {
1724
+ allPromises.push(...activeWrites);
1725
+ }
1726
+ if (allPromises.length === 0) {
1727
+ break;
1728
+ }
1729
+ await Promise.allSettled(allPromises);
1730
+ for (const activeWrites of this.delegateActiveWrites.values()) {
1731
+ for (const p of allPromises) {
1732
+ activeWrites.delete(p);
1733
+ }
1734
+ }
1735
+ }
1736
+ this.delegateActiveWrites.clear();
1737
+ }
1738
+ async awaitActivePollDrainsForDelegate(delegateId) {
1739
+ const deviceDbId = this.delegateDeviceIds.get(delegateId);
1740
+ const relevantDeviceIds = new Set([delegateId]);
1741
+ if (deviceDbId) {
1742
+ relevantDeviceIds.add(deviceDbId);
1743
+ }
1744
+ const trackedPollDeviceIds = this.delegatePollDeviceIds.get(delegateId);
1745
+ if (trackedPollDeviceIds) {
1746
+ for (const devId of trackedPollDeviceIds) {
1747
+ relevantDeviceIds.add(devId);
1748
+ }
1749
+ }
1750
+ for (const [devId, buckets] of this.activePollWrites.entries()) {
1751
+ for (const bucket of buckets) {
1752
+ for (const pending of bucket.values.values()) {
1753
+ if (pending.delegateId === delegateId) {
1754
+ relevantDeviceIds.add(devId);
1755
+ }
1756
+ }
1757
+ }
1758
+ }
1759
+ while (true) {
1760
+ const promises = [];
1761
+ for (const devId of relevantDeviceIds) {
1762
+ const drains = this.activePollDrainPromises.get(devId);
1763
+ if (drains && drains.size > 0) {
1764
+ promises.push(...drains);
1765
+ }
1766
+ }
1767
+ if (promises.length === 0) {
1768
+ break;
1769
+ }
1770
+ await Promise.allSettled(promises);
1771
+ for (const devId of relevantDeviceIds) {
1772
+ const drains = this.activePollDrainPromises.get(devId);
1773
+ if (drains) {
1774
+ for (const p of promises) {
1775
+ drains.delete(p);
1776
+ }
1777
+ }
1778
+ }
1779
+ }
1780
+ }
1781
+ async awaitAllActivePollDrains() {
1782
+ while (true) {
1783
+ const allPromises = [];
1784
+ for (const drains of this.activePollDrainPromises.values()) {
1785
+ allPromises.push(...drains);
1786
+ }
1787
+ if (allPromises.length === 0) {
1788
+ break;
1789
+ }
1790
+ await Promise.allSettled(allPromises);
1791
+ for (const drains of this.activePollDrainPromises.values()) {
1792
+ for (const p of allPromises) {
1793
+ drains.delete(p);
1794
+ }
1795
+ }
1796
+ }
1355
1797
  }
1356
1798
  cancelPollWrite(propertyId) {
1357
1799
  for (const [deviceId, bucket] of this.pendingPollWrites.entries()) {
@@ -1437,7 +1879,8 @@ let DelegatesManagerService = DelegatesManagerService_1 = class DelegatesManager
1437
1879
  }
1438
1880
  return fallback;
1439
1881
  }
1440
- detach() {
1882
+ async detach() {
1883
+ const teardownPromises = [];
1441
1884
  for (const [, delegate] of this.delegates.entries()) {
1442
1885
  const valueHandler = this.delegateValueHandlers.get(delegate.id);
1443
1886
  const connectionHandler = this.delegateConnectionHandlers.get(delegate.id);
@@ -1447,20 +1890,39 @@ let DelegatesManagerService = DelegatesManagerService_1 = class DelegatesManager
1447
1890
  if (connectionHandler) {
1448
1891
  delegate.off('connected', connectionHandler);
1449
1892
  }
1893
+ teardownPromises.push(this.teardownDelegateEvents(delegate));
1894
+ }
1895
+ for (const pendingWrite of this.pendingWrites.values()) {
1896
+ clearTimeout(pendingWrite);
1897
+ }
1898
+ this.pendingWrites.clear();
1899
+ this.delegatePendingWrites.clear();
1900
+ for (const bucket of this.pendingPollWrites.values()) {
1901
+ clearTimeout(bucket.timer);
1450
1902
  }
1903
+ this.pendingPollWrites.clear();
1904
+ for (const buckets of this.activePollWrites.values()) {
1905
+ for (const bucket of buckets) {
1906
+ bucket.values.clear();
1907
+ }
1908
+ }
1909
+ await Promise.all(teardownPromises);
1910
+ await this.awaitAllActiveWrites();
1911
+ await this.awaitAllActivePollDrains();
1912
+ this.activePollWrites.clear();
1913
+ this.activePollDrainPromises.clear();
1914
+ this.delegatePollDeviceIds.clear();
1451
1915
  this.delegates.clear();
1452
1916
  this.delegateValueHandlers.clear();
1453
1917
  this.delegateConnectionHandlers.clear();
1918
+ this.delegateEventHandlers.clear();
1919
+ this.delegateEventQueues.clear();
1920
+ this.delegateEventGenerations.clear();
1454
1921
  this.delegateDeviceIds.clear();
1455
1922
  this.changeHandlers.clear();
1456
1923
  this.setPropertiesHandlers.clear();
1457
1924
  this.setChannelsHandlers.clear();
1458
1925
  this.propertiesMap.clear();
1459
- for (const pendingWrite of this.pendingWrites.values()) {
1460
- clearTimeout(pendingWrite);
1461
- }
1462
- this.pendingWrites.clear();
1463
- this.clearPollWrites();
1464
1926
  this.delegatePollGenerations.clear();
1465
1927
  this.currentValueUpdateContext = null;
1466
1928
  this.deviceLocks.clear();
@@ -1475,8 +1937,8 @@ let DelegatesManagerService = DelegatesManagerService_1 = class DelegatesManager
1475
1937
  this.delegateConnectedState.clear();
1476
1938
  this.insertGeneration.clear();
1477
1939
  }
1478
- destroy() {
1479
- this.detach();
1940
+ async destroy() {
1941
+ await this.detach();
1480
1942
  }
1481
1943
  async checkHealth(pingTimeoutMs = 5_000) {
1482
1944
  const CONCURRENCY = 10;
@@ -1597,6 +2059,7 @@ exports.DelegatesManagerService = DelegatesManagerService = DelegatesManagerServ
1597
2059
  (0, common_1.Injectable)(),
1598
2060
  __param(8, (0, common_1.Optional)()),
1599
2061
  __param(9, (0, common_1.Optional)()),
2062
+ __param(10, (0, common_1.Optional)()),
1600
2063
  __metadata("design:paramtypes", [devices_service_1.DevicesService,
1601
2064
  channels_service_1.ChannelsService,
1602
2065
  channels_properties_service_1.ChannelsPropertiesService,
@@ -1606,6 +2069,7 @@ exports.DelegatesManagerService = DelegatesManagerService = DelegatesManagerServ
1606
2069
  mappings_1.PropertyMappingStorageService,
1607
2070
  mappings_1.TransformerRegistry,
1608
2071
  command_latency_trace_collector_service_1.CommandLatencyTraceCollectorService,
1609
- poll_placement_diagnostics_service_1.PollPlacementDiagnosticsService])
2072
+ poll_placement_diagnostics_service_1.PollPlacementDiagnosticsService,
2073
+ channel_input_occurrences_service_1.ChannelInputOccurrencesService])
1610
2074
  ], DelegatesManagerService);
1611
2075
  //# sourceMappingURL=delegates-manager.service.js.map