iobroker.tidy 0.2.2 → 0.2.4

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.
@@ -3,24 +3,18 @@
3
3
  "Check alias targets": "检查别名目标",
4
4
  "Comment (optional)": "评论(可选)",
5
5
  "Configure which paths to scan for unused datapoints. Use the object picker or type a path manually. Each path will get its own result state with a JSON table.": "配置扫描哪些路径以查找未使用的数据点。使用对象选择器或手动输入路径。每个路径都会通过 JSON 表获得自己的结果状态。",
6
- "Datapoints listed on the Exceptions tab are omitted from all scan results and statistics. Use this for configuration values that rarely change but should not appear as dead or stale.\n\nPick a single state to exclude one datapoint, or pick a folder/channel to exclude an entire subtree.\n\nEach scan channel provides exceptionCount showing how many datapoints were excluded during the last scan (0 if none).": "所有扫描结果和统计数据中都会忽略“例外”选项卡上列出的数据点。将此用于很少更改但不应显示为无效或过时的配置值。\n\n选择一个状态来排除一个数据点,或选择一个文件夹/通道来排除整个子树。\n\n每个扫描通道提供异常计数,显示上次扫描期间排除了多少个数据点(如果没有则为 0)。",
7
6
  "Datapoints listed on the Exceptions tab are omitted from all scan results and statistics. Use this for configuration values that rarely change but should not appear as dead or stale.\n\nUse the picker to select individual datapoints (states). To exclude an entire folder or subtree, type the path directly into the field (e.g. 0_userdata.0.config).\n\nEach scan channel provides exceptionCount showing how many datapoints were excluded during the last scan (0 if none).": "所有扫描结果和统计数据中都会忽略“例外”选项卡上列出的数据点。将此用于很少更改但不应显示为无效或过时的配置值。\n\n使用选择器选择单个数据点(状态)。要排除整个文件夹或子树,请直接在字段中输入路径(例如 0_userdata.0.config)。\n\n每个扫描通道提供异常计数,显示上次扫描期间排除了多少个数据点(如果没有则为 0)。",
8
7
  "Datapoints not updated for this many days are marked as 'dead'": "这么多天未更新的数据点被标记为“死亡”",
9
8
  "Datapoints not updated for this many days are marked as 'stale'": "这么多天未更新的数据点被标记为“过时”",
10
9
  "Days until 'dead' (critical)": "距离“死亡”还有几天(关键)",
11
10
  "Days until 'stale' (warning)": "距离“过时”的天数(警告)",
12
- "Description": "描述",
13
11
  "Detection Thresholds": "检测阈值",
14
12
  "Each scan result contains a JSON array with the following fields per datapoint:": "每个扫描结果包含一个 JSON 数组,每个数据点包含以下 10 个字段:",
15
13
  "Enable automatic scans": "启用自动扫描",
16
14
  "Enabled": "启用",
17
- "Enter scan paths on the Scan Paths tab or copy IDs from the Objects tab.\n\nTo scan all instances of an adapter, remove the instance number from the path (e.g. alias.0 → alias). This matches alias.0, alias.1, and so on.": "在“扫描路径”选项卡上输入扫描路径或从“对象”选项卡中复制 ID。\n\n要扫描适配器的所有实例,请从路径中删除实例编号(例如,alias.0→alias)。这与 alias.0、alias.1 等匹配。",
18
- "Enter the scan path (e.g. 0_userdata.0, alias, javascript.0). Copy from the Objects tab if needed. Remove the instance number (alias.0 → alias) to scan all instances.": "输入扫描路径(例如 0_userdata.0、别名、javascript.0)。如果需要,请从“对象”选项卡进行复制。删除实例编号(alias.0→alias)以扫描所有实例。",
19
15
  "Exceptions": "例外情况",
20
- "Exclude datapoints from scan results that are intentionally static or should be ignored (e.g. configuration values that rarely change). Select a single datapoint or a folder to exclude everything beneath it.": "从扫描结果中排除有意静态或应忽略的数据点(例如很少更改的配置值)。选择单个数据点或文件夹以排除其下方的所有内容。",
21
16
  "Exclude datapoints from scan results that are intentionally static or should be ignored. Use the picker to select individual datapoints. To exclude an entire folder, type the path manually (e.g. 0_userdata.0.config).": "从扫描结果中排除有意静态或应忽略的数据点。使用选择器选择单个数据点。要排除整个文件夹,请手动键入路径(例如 0_userdata.0.config)。",
22
17
  "Excluded datapoints": "排除的数据点",
23
- "Field": "钥匙",
24
18
  "For German tables: Use status_de and issue_de columns\nFor English tables: Use status and issue columns\n\nFilter by 'issue != null' to show only problematic datapoints.": "对于德语表:使用 status_de 和 issues_de 列。\n对于英文表格:使用状态和问题列\n\n按“问题!= null”过滤以仅显示有问题的数据点。",
25
19
  "For alias.* paths: Check if target datapoints exist (ghost detection)": "对于 alias.* 路径:检查目标数据点是否存在(幽灵检测)",
26
20
  "General Settings": "常规设置",
@@ -28,37 +22,23 @@
28
22
  "How often the automatic scan should run (minimum 1 hour)": "自动扫描应运行的频率(至少 1 小时)",
29
23
  "If enabled, the adapter will scan the entire object tree and store the result in a separate 'complete' result channel.": "如果启用,适配器将扫描整个对象树并将结果存储在单独的“完整”结果通道中。",
30
24
  "JSON Table Field Description": "JSON 表字段说明",
31
- "Name": "姓名",
32
- "Name (for result state)": "名称(结果状态)",
33
25
  "Name (for result state, optional)": "名称(结果状态,可选)",
34
26
  "Object": "目的",
35
27
  "Optional note, e.g. alarm clock radio station": "可选注释,例如闹钟广播电台",
36
28
  "Optional. If empty, the path will be used as name.": "选修的。如果为空,则路径将用作名称。",
37
29
  "Path Configuration": "路径配置",
38
30
  "Paths to scan": "要扫描的路径",
39
- "Pick a folder in the object tree to exclude an entire subtree, or type a single state ID manually (e.g. 0_userdata.0.radio.station).": "在对象树中选择一个文件夹以排除整个子树,或手动键入单个状态 ID(例如 0_userdata.0.radio.station)。",
40
- "Pick a folder in the object tree, or type a path manually (e.g. 0_userdata.0). Remove the instance number (alias.0 → alias) to scan all instances.": "在对象树中选择一个文件夹,或手动键入路径(例如 0_userdata.0)。删除实例编号(alias.0→alias)以扫描所有实例。",
41
- "Pick a folder or namespace (e.g. alias, 0_userdata.0, alias.0.Heizungen). Individual datapoints are not shown. The path stays editable — remove the instance number (alias.0 → alias) to scan all instances.": "选择一个文件夹或命名空间(例如alias、0_userdata.0、alias.0.Heizungen)。未显示单个数据点。路径保持可编辑状态 — 删除实例编号(alias.0 → 别名)以扫描所有实例。",
42
- "Pick a folder/channel to exclude an entire subtree. To exclude a single state, type its ID directly into the field (e.g. 0_userdata.0.radio.station).": "选择一个文件夹/频道以排除整个子树。要排除单个状态,请直接在字段中输入其 ID(例如 0_userdata.0.radio.station)。",
43
- "Pick a path from the object tree or type manually. Remove the instance number (e.g. alias.0 → alias) to scan all instances of an adapter.": "从对象树中选择路径或手动键入。删除实例编号(例如alias.0→alias)以扫描适配器的所有实例。",
44
- "Pick an adapter instance (e.g. 0_userdata.0, alias.0). The path stays editable — append a subfolder or remove the instance number (alias.0 → alias) after picking.": "选择一个适配器实例(例如 0_userdata.0、alias.0)。路径保持可编辑状态 - 附加子文件夹或在选择后删除实例编号(alias.0 → 别名)。",
45
- "Pick an adapter instance (e.g. 0_userdata.0, alias.0). The path stays editable — remove the instance number (alias.0 → alias) to scan all instances.": "选择一个适配器实例(例如 0_userdata.0、alias.0)。路径保持可编辑状态 — 删除实例编号(alias.0 → 别名)以扫描所有实例。",
46
- "Pick an adapter instance or subfolder (e.g. 0_userdata.0, alias.0). The path stays editable — remove the instance number (alias.0 → alias) to scan all instances.": "选择一个适配器实例或子文件夹(例如 0_userdata.0、alias.0)。路径保持可编辑状态 — 删除实例编号(alias.0 → 别名)以扫描所有实例。",
31
+ "Pick a namespace (e.g. 0_userdata.0, alias.0). The path stays editable remove the instance number (alias.0 alias) to scan all instances.": "选择一个适配器实例或子文件夹(例如 0_userdata.0、alias.0)。路径保持可编辑状态 — 删除实例编号(alias.0 → 别名)以扫描所有实例。",
47
32
  "Pick an individual datapoint to exclude it. To exclude an entire folder, type the folder path directly (e.g. 0_userdata.0.config).": "选择一个单独的数据点将其排除。要排除整个文件夹,请直接键入文件夹路径(例如 0_userdata.0.config)。",
48
- "Purpose": "目的",
49
33
  "Scan Paths": "扫描路径",
50
34
  "Scan all objects (complete)": "扫描所有对象(完整)",
51
35
  "Scan interval (hours)": "扫描间隔(小时)",
52
36
  "Scan path": "扫描路径",
53
- "Select a datapoint or folder to exclude. Folders exclude all states beneath them.": "选择要排除的数据点或文件夹。文件夹排除其下面的所有状态。",
54
- "Tip: The path remains editable after picking. Remove the instance number (e.g. alias.0 → alias) to scan all instances of an adapter.": "提示:拾取后路径仍然可编辑。删除实例编号(例如alias.0→alias)以扫描适配器的所有实例。",
55
- "Tip: The path stays editable after picking. Remove the instance number (e.g. alias.0 → alias) to scan all instances of an adapter.": "提示:拾取后路径保持可编辑状态。删除实例编号(例如alias.0→alias)以扫描适配器的所有实例。",
37
+ "Team inventwo • <a href=\"https://github.com/inventwo\">https://github.com/inventwo</a> by skvarel": "Team inventwo • <a href=\"https://github.com/inventwo\">https://github.com/inventwo</a> by skvarel",
56
38
  "Tip: The path stays editable after picking. Remove the instance number (e.g. alias.0 → alias) to scan all instances. You can also copy any object ID from the Objects tab and paste it directly into the path field.": "提示:拾取后路径保持可编辑状态。删除实例编号(例如alias.0→alias)以扫描所有实例。您还可以从“对象”选项卡复制任何对象 ID,并将其直接粘贴到路径字段中。",
57
39
  "Usage in VIS Widgets": "在 VIS 小部件中的使用",
58
40
  "Use the object picker on the Scan Paths tab or type a path manually. The path stays editable after picking.\n\nTo scan all instances of an adapter, remove the instance number from the path (e.g. alias.0 → alias). This matches alias.0, alias.1, and so on.": "使用“扫描路径”选项卡上的对象选取器或手动键入路径。拾取后路径保持可编辑状态。\n\n要扫描适配器的所有实例,请从路径中删除实例编号(例如,alias.0→alias)。这与 alias.0、alias.1 等匹配。",
59
- "Use the object picker on the Scan Paths tab or type a path manually. The path stays editable after picking.\nTo scan all instances of an adapter, remove the instance number from the path (e.g. alias.0 → alias). This matches alias.0, alias.1, and so on.": "使用“扫描路径”选项卡上的对象选取器或手动键入路径。拾取后路径保持可编辑状态。\n\n要扫描适配器的所有实例,请从路径中删除实例编号(例如,alias.0→alias)。这与 alias.0、alias.1 等匹配。",
60
41
  "When enabled, all configured paths will be scanned automatically at the specified interval": "启用后,将按照指定的时间间隔自动扫描所有配置的路径",
61
- "e.g. 0_userdata.0 or alias": "例如0_userdata.0 或别名",
62
42
  "help_json_fields": "<table style=\"width:100%;border-collapse:collapse;font-size:0.875rem\"><thead><tr style=\"border-bottom:1pxsolid #546e7a\"><th style=\"text-align:left;padding:8px 6px;width:6%\">#</th><th style=\"text-align:left;padding:8px 6px;width:14%\">键</th><th style=\"text-align:left;padding:8px 6px;width:40%\">说明</th><th style=\"text-align:left;padding:8px 6px\">用途</th></tr></thead><tbody><tr style=\"border-bottom:1pxsolid rgba(84,110,122,0.35)\"><td style=\"padding:6px\">01</td><td style=\"padding:6px\"><code>id</code></td><td style=\"padding:6px\">完整数据点路径</td><td style=\"padding:6px\">唯一标识</td></tr><tr style=\"border-bottom:1pxsolid rgba(84,110,122,0.35)\"><td style=\"padding:6px\">02</td><td style=\"padding:6px\"><code>name</code></td><td style=\"padding:6px\">common.name 或 ID 的最后部分</td><td style=\"padding:6px\">用户友好名称</td></tr><tr style=\"border-bottom:1pxsolid rgba(84,110,122,0.35)\"><td style=\"padding:6px\">03</td><td style=\"padding:6px\"><code>last_ts</code></td><td style=\"padding:6px\">Unix 时间戳(毫秒)或 null</td><td style=\"padding:6px\">在后台排序</td></tr><tr style=\"border-bottom:1pxsolid rgba(84,110,122,0.35)\"><td style=\"padding:6px\">04</td><td style=\"padding:6px\"><code>last_ts_iso</code></td><td style=\"padding:6px\">格式化日期字符串</td><td style=\"padding:6px\">在表格中显示</td></tr><tr style=\"border-bottom:1pxsolid rgba(84,110,122,0.35)\"><td style=\"padding:6px\">05</td><td style=\"padding:6px\"><code>值</code></td><td style=\"padding:6px\">当前数据点值</td><td style=\"padding:6px\">删除前的最终检查</td></tr><tr style=\"border-bottom:1pxsolid rgba(84,110,122,0.35)\"><td style=\"padding:6px\">06</td><td style=\"padding:6px\"><code>状态</code></td><td style=\"padding:6px\">活动、失效、过时、未定义、孤立</td><td style=\"padding:6px\">分类(英语)</td></tr><tr style=\"border-bottom:1pxsolid rgba(84,110,122,0.35)\"><td style=\"padding:6px\">07</td><td style=\"padding:6px\"><code>status_de</code></td><td style=\"padding:6px\">aktiv、inaktiv、veraltet、undefiniert、verwaist</td><td style=\"padding:6px\">分类(德语)</td></tr><tr style=\"border-bottom:1px Solid rgba(84,110,122,0.35)\"><td style=\"padding:6px\">08</td><td style=\"padding:6px\"><code>问题</code></td><td style=\"padding:6px\">失效、陈旧、orphaned_alias 或 null</td><td style=\"padding:6px\">过滤条件(null = OK)</td></tr><tr style=\"border-bottom:1pxsolid rgba(84,110,122,0.35)\"><td style=\"padding:6px\">09</td><td style=\"padding:6px\"><code>issue_de</code></td><td style=\"padding:6px\">inaktiv,veraltet, verwaistes 别名,或 null</td><td style=\"padding:6px\">过滤条件(德语)</td></tr><tr style=\"border-bottom:1pxsolid rgba(84,110,122,0.35)\"><td style=\"padding:6px\">10</td><td style=\"padding:6px\"><code>尺寸</code></td><td style=\"padding:6px\">JSON.stringify(val).length</td><td style=\"padding:6px\">查找存储占用</td></tr></tbody></table>",
63
43
  "help_readme": "<b>有关完整文档,请单击下面的此处:</b><br><br>• <a href=\"https://github.com/inventwo/ioBroker.tidy/blob/main/README.md\">适配器文档</a>",
64
44
  "paypal_donate": "如果您愿意支持我们。",
package/io-package.json CHANGED
@@ -1,8 +1,34 @@
1
1
  {
2
2
  "common": {
3
3
  "name": "tidy",
4
- "version": "0.2.2",
4
+ "version": "0.2.4",
5
5
  "news": {
6
+ "0.2.4": {
7
+ "en": "Fixed admin UI translations for scan path tooltips and help texts\nFixed runtime validation for scan interval and stale/dead thresholds\nModified scan result timestamps to use ISO 8601 format",
8
+ "de": "Fixed Admin UI Übersetzungen für Scan Path Tooltips und Hilfetexte\nFeste Laufzeitvalidierung für Scan-Intervall und abgestandene/tote Schwellenwerte\nGeänderte Scanergebnis-Zeitstempel zur Verwendung des ISO 8601-Formats",
9
+ "ru": "Переводы пользовательского интерфейса с фиксированным администратором для подсказок пути сканирования и текстов помощи\nФиксированная проверка времени выполнения для интервала сканирования и устаревших/мертвых порогов\nМодифицированные временные метки результатов сканирования для использования формата ISO 8601",
10
+ "pt": "Traduções de UI de administrador fixas para verificar dicas de caminho e textos de ajuda\nValidação fixa do tempo de execução para o intervalo de varredura e limiares estacionários/mortos\nHorários de verificação modificados para usar o formato ISO 8601",
11
+ "nl": "Vaste admin UI vertalingen voor scan pad tooltips en help teksten\nVaste runtime validatie voor scaninterval en oude/dode drempels\nGewijzigde scan resultaat tijdstempels om ISO 8601-formaat te gebruiken",
12
+ "fr": "Correction des traductions de l'interface utilisateur admin pour scanner le chemin tooltips et aider les textes\nValidation fixe du temps d'exécution pour l'intervalle d'analyse et les seuils stal/mort\nHorodatage des résultats modifiés pour l'utilisation du format ISO 8601",
13
+ "it": "Traduzioni dell'interfaccia utente dell'amministratore fisso per i tooltips del percorso di scansione e i testi di aiuto\nValidazione runtime fissa per l'intervallo di scansione e le soglie di stallo/morte\nTempi di scansione modificati per utilizzare il formato ISO 8601",
14
+ "es": "Administro fijo UI traducciones para escaneo de herramientas y textos de ayuda\nValidación de tiempo fijo para intervalos de escaneo y umbrales de estalla/muerto\nTiempos de resultados de escaneado modificados para utilizar el formato ISO 8601",
15
+ "pl": "Naprawiono tłumaczenia Interfejs administracyjny dla podpowiedzi ścieżki skanowania i tekstów pomocy\nStała walidacja czasu pracy dla interwałów skanowania i niestałych / martwych progów\nZmodyfikowane znaczniki czasu wyników skanowania w formacie ISO 8601",
16
+ "uk": "Фіксований адміністратор UI переклади для сканування шляхових інструментів та допоміжних текстів\nФіксована перевірка часу виконання для інтервалу сканування та пороги застосування / деда\nМодифіковані скануючі імпульси для використання ISO 8601 формат",
17
+ "zh-cn": "扫描路径提示和帮助文本的固定管理 UI 翻译\n扫描间隔和 Stale/ dead 阈值的固定运行时间验证\n修改了扫描结果时间戳以使用ISO 8601格式"
18
+ },
19
+ "0.2.3": {
20
+ "en": "Fixed i18n translation",
21
+ "de": "Fixed i18n Übersetzung",
22
+ "ru": "Исправленный перевод i18n",
23
+ "pt": "Tradução fixa i18n",
24
+ "nl": "Vaste i18n vertaling",
25
+ "fr": "Correction de la traduction i18n",
26
+ "it": "Traduzione i18n fissa",
27
+ "es": "Traducción fija i18n",
28
+ "pl": "Naprawiono tłumaczenie i18n",
29
+ "uk": "Фіксований переклад i18n",
30
+ "zh-cn": "固定 i18n 翻译"
31
+ },
6
32
  "0.2.2": {
7
33
  "en": "Added meta object types for adapter and instance namespace",
8
34
  "de": "Meta-Objekttypen für Adapter und Instanz-Namespace hinzugefügt",
@@ -67,32 +93,6 @@
67
93
  "pl": "Aktualizacja zależności\nStałe wydanie kontrolera repo",
68
94
  "uk": "Оновлені залежності\nВиправлено проблему перевірки репо",
69
95
  "zh-cn": "更新的依赖关系\n固定回购检查器问题"
70
- },
71
- "0.1.4": {
72
- "en": "Revised titel and desciption",
73
- "de": "Überarbeiteter Titel und Abschied",
74
- "ru": "Пересмотренный титель и расшифровка",
75
- "pt": "Título revisto e decisão",
76
- "nl": "Herziene titel en omschrijving",
77
- "fr": "Titre et desciption révisés",
78
- "it": "Titel modificato e desciptazione",
79
- "es": "Titel revisado y descipación",
80
- "pl": "Poprawiony tytel i deszypcja",
81
- "uk": "Відреставрований титель і відхилення",
82
- "zh-cn": "订正钛和剥离"
83
- },
84
- "0.1.3": {
85
- "en": "Fixed repo checker issue",
86
- "de": "Problem der Repo Checker behoben",
87
- "ru": "Фиксированная проблема проверки репо",
88
- "pt": "Problema fixo do verificador de repo",
89
- "nl": "Probleem met vaste repochecker",
90
- "fr": "Correction du problème de repo checker",
91
- "it": "Rischio di errore",
92
- "es": "Número de comprobación de reposo fijo",
93
- "pl": "Stałe wydanie kontrolera repo",
94
- "uk": "Виправлено проблему перевірки репо",
95
- "zh-cn": "固定回购检查器问题"
96
96
  }
97
97
  },
98
98
  "titleLang": {
package/main.js CHANGED
@@ -65,9 +65,15 @@ class Tidy extends utils.Adapter {
65
65
  this.subscribeForeignObjects(`system.adapter.${this.namespace}`);
66
66
 
67
67
  // Setup automatic scanning if enabled
68
- if (this.config.autoScan && this.config.scanInterval > 0) {
69
- const intervalMs = this.config.scanInterval * 60 * 60 * 1000; // Convert hours to milliseconds
70
- this.log.info(`Automatic scanning enabled: Every ${this.config.scanInterval} hour(s)`);
68
+ if (this.config.autoScan) {
69
+ const scanIntervalHours = this.clampScanIntervalHours(this.config.scanInterval);
70
+ if (scanIntervalHours !== Number(this.config.scanInterval)) {
71
+ this.log.warn(
72
+ `scanInterval ${this.config.scanInterval} is out of range (1-168 hours), using ${scanIntervalHours} hour(s)`,
73
+ );
74
+ }
75
+ const intervalMs = scanIntervalHours * 60 * 60 * 1000;
76
+ this.log.info(`Automatic scanning enabled: Every ${scanIntervalHours} hour(s)`);
71
77
  this.scanInterval = this.setInterval(async () => {
72
78
  this.log.info('Running automatic scan...');
73
79
  await this.scanAllPaths();
@@ -599,8 +605,8 @@ class Tidy extends utils.Adapter {
599
605
  */
600
606
  async analyzeDatapoint(id, obj, state, pathConfig) {
601
607
  const now = Date.now();
602
- const daysUntilStale = this.config.daysUntilStale || 90;
603
- const daysUntilDead = this.config.daysUntilDead || 365;
608
+ const daysUntilStale = this.clampDaysUntil(this.config.daysUntilStale, 90);
609
+ const daysUntilDead = this.clampDaysUntil(this.config.daysUntilDead, 365);
604
610
 
605
611
  const result = {
606
612
  id: id,
@@ -618,7 +624,7 @@ class Tidy extends utils.Adapter {
618
624
  // Get timestamp
619
625
  if (state && state.ts) {
620
626
  result.last_ts = state.ts;
621
- result.last_ts_iso = new Date(state.ts).toLocaleString('de-DE');
627
+ result.last_ts_iso = new Date(state.ts).toISOString();
622
628
  result.value = state.val;
623
629
 
624
630
  // Calculate age in days
@@ -753,29 +759,6 @@ class Tidy extends utils.Adapter {
753
759
  }
754
760
  }
755
761
 
756
- buildExceptionSets() {
757
- this._exceptionExact = new Set();
758
- this._exceptionPrefixes = [];
759
-
760
- for (const exc of this.config.exceptions || []) {
761
- if (!exc?.id || !String(exc.id).trim()) {
762
- continue;
763
- }
764
- if (exc.enabled === false) {
765
- continue;
766
- }
767
-
768
- const id = String(exc.id).trim();
769
- const objectType = exc.objectType || 'state';
770
-
771
- if (objectType === 'state') {
772
- this._exceptionExact.add(id);
773
- } else {
774
- this._exceptionPrefixes.push(id);
775
- }
776
- }
777
- }
778
-
779
762
  /**
780
763
  * Check whether a state ID is excluded from scan results
781
764
  *
@@ -784,7 +767,8 @@ class Tidy extends utils.Adapter {
784
767
  */
785
768
  isExcluded(stateId) {
786
769
  if (!this._exceptionExact) {
787
- this.buildExceptionSets();
770
+ this.log.warn('Exception sets not loaded before isExcluded() — call loadExceptionSets() first');
771
+ return false;
788
772
  }
789
773
 
790
774
  if (this._exceptionExact.has(stateId)) {
@@ -820,6 +804,35 @@ class Tidy extends utils.Adapter {
820
804
  sanitizeName(name) {
821
805
  return name.replace(/[^a-zA-Z0-9_-]/g, '_').toLowerCase();
822
806
  }
807
+
808
+ /**
809
+ * Clamp automatic scan interval to the supported range (1-168 hours)
810
+ *
811
+ * @param {number} value - Configured interval in hours
812
+ * @returns {number} Validated interval in hours
813
+ */
814
+ clampScanIntervalHours(value) {
815
+ const hours = Number(value);
816
+ if (!Number.isFinite(hours)) {
817
+ return 24;
818
+ }
819
+ return Math.max(1, Math.min(168, Math.trunc(hours)));
820
+ }
821
+
822
+ /**
823
+ * Clamp day-based thresholds to a valid positive range
824
+ *
825
+ * @param {number} value - Configured day count
826
+ * @param {number} fallback - Default when value is invalid
827
+ * @returns {number} Validated day count
828
+ */
829
+ clampDaysUntil(value, fallback) {
830
+ const days = Number(value);
831
+ if (!Number.isFinite(days)) {
832
+ return fallback;
833
+ }
834
+ return Math.max(1, Math.min(3650, Math.trunc(days)));
835
+ }
823
836
  // If you need to accept messages in your adapter, uncomment the following block and the corresponding line in the constructor.
824
837
  // /**
825
838
  // * Some message was sent to this instance over message box. Used by email, pushover, text2speech, ...
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "iobroker.tidy",
3
- "version": "0.2.2",
3
+ "version": "0.2.4",
4
4
  "description": "Analyzes ioBroker objects for unused datapoints and helps you clean up your instance",
5
5
  "author": {
6
6
  "name": "skvarel",