iobroker.idm-multitalent_002 2.2.0 → 2.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/README.md +14 -4
- package/io-package.json +14 -14
- package/lib/idm-session.js +51 -9
- package/lib/idm-session.test.js +65 -3
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -56,6 +56,9 @@ Example screenshots of objects:
|
|
|
56
56
|

|
|
57
57
|
|
|
58
58
|
## Changelog
|
|
59
|
+
### 2.2.1 (2026-09-13)
|
|
60
|
+
* (zloe) the per-sweep log line introduced in 2.2.0 (`sensor sweep done in ...`) is now logged at debug level instead of info - in normal operation a sensor sweep completes roughly every 10s, which was far too chatty for info level. In its place, a new info-level summary is logged once every 10 minutes with a small statistic (sweep count, min/avg/max duration) per group covering that window
|
|
61
|
+
|
|
59
62
|
### 2.2.0 (2026-09-11)
|
|
60
63
|
* (zloe) sensor and settings are no longer polled in lockstep: sensor is read as fast as the protocol allows (throttled to at most once every 10s), settings only once every 60s plus once right away after any value is written to the heat pump - both intervals measured from the start of one full sweep to the start of the next, and neither configurable. This noticeably reduces how often the heat pump's own control gets polled for the slower-changing settings data, without making sensor data any less fresh
|
|
61
64
|
* (zloe) a group's sweep (round-robin or multi-block alike) is never interrupted by the other group becoming due partway through - previously only a multi-block collection had this guarantee; the classic one-block-at-a-time round-robin now gets it too
|
|
@@ -265,13 +268,20 @@ sequenceDiagram
|
|
|
265
268
|
```
|
|
266
269
|
|
|
267
270
|
Each completed sweep logs its own actual elapsed time plus a rolling average over the last 10
|
|
268
|
-
sweeps, e.g. `sensor sweep done in 1400ms (avg of last 10: 1360ms)` - one line
|
|
269
|
-
the moment that group's sweep finishes.
|
|
270
|
-
line: now that the two groups run on independent schedules there is no single shared interval left
|
|
271
|
-
to time them together, so each gets its own line instead. As before, this is driven by data
|
|
271
|
+
sweeps at **debug** level, e.g. `sensor sweep done in 1400ms (avg of last 10: 1360ms)` - one line
|
|
272
|
+
per group, logged the moment that group's sweep finishes. As before, this is driven by data
|
|
272
273
|
actually being *received* (`recordBlockRead()`), not merely requested - a block that was asked for
|
|
273
274
|
but never got a reply (a retry, a response-watchdog reset, the periodic resync) does not count.
|
|
274
275
|
|
|
276
|
+
Since a sensor sweep normally completes roughly every 10s, debug is the right level for that
|
|
277
|
+
per-sweep detail - logging it at info would flood the log in normal operation. Instead, once every
|
|
278
|
+
10 minutes (fixed), one small **info**-level summary line reports both groups' activity over that
|
|
279
|
+
window, e.g. `last 10min - sensor: 58 sweep(s), 1360ms avg (min 1210ms, max 1890ms); settings: 9
|
|
280
|
+
sweep(s), 640ms avg (min 600ms, max 720ms)` - or `0 sweeps` for a group that hasn't completed one
|
|
281
|
+
in that window. This is the only sweep-related log at info level now; it replaces the earlier
|
|
282
|
+
combined "full-coverage cycle" line, which no longer made sense once the two groups started running
|
|
283
|
+
on independent schedules.
|
|
284
|
+
|
|
275
285
|
#### Multi-block requests, per group
|
|
276
286
|
|
|
277
287
|
The serial protocol allows requesting several data blocks in one `0171` message, combined in a
|
package/io-package.json
CHANGED
|
@@ -1,8 +1,21 @@
|
|
|
1
1
|
{
|
|
2
2
|
"common": {
|
|
3
3
|
"name": "idm-multitalent_002",
|
|
4
|
-
"version": "2.2.
|
|
4
|
+
"version": "2.2.1",
|
|
5
5
|
"news": {
|
|
6
|
+
"2.2.1": {
|
|
7
|
+
"en": "the per-sweep log line introduced in 2.2.0 (\"sensor sweep done in ...\") is now logged at debug level instead of info - a sensor sweep normally completes roughly every 10s, which was too chatty for info level. In its place, a small info-level summary is now logged once every 10 minutes, with a statistic (sweep count, min/avg/max duration) per group covering that window",
|
|
8
|
+
"de": "die in 2.2.0 eingeführte Log-Zeile pro Durchlauf (\"sensor sweep done in ...\") wird jetzt auf Debug-Level statt Info protokolliert - ein Sensor-Durchlauf ist normalerweise etwa alle 10s abgeschlossen, was auf Info-Level zu viel Rauschen erzeugte. Stattdessen wird jetzt alle 10 Minuten eine kleine Info-Level-Zusammenfassung protokolliert, mit einer Statistik (Anzahl Durchläufe, min/durchschnittliche/max Dauer) pro Gruppe für dieses Zeitfenster",
|
|
9
|
+
"ru": "строка журнала для каждого прохода, добавленная в 2.2.0 (\"sensor sweep done in ...\"), теперь записывается на уровне debug вместо info - проход сенсоров обычно завершается примерно каждые 10с, что было слишком часто для уровня info. Вместо этого теперь раз в 10 минут записывается небольшая сводка на уровне info со статистикой (количество проходов, мин/среднее/макс длительность) по каждой группе за этот период",
|
|
10
|
+
"pt": "a linha de log por varredura introduzida na 2.2.0 (\"sensor sweep done in ...\") agora é registrada em nível debug em vez de info - uma varredura de sensores normalmente é concluída a cada 10s, o que era muito frequente para o nível info. Em seu lugar, agora é registrado um pequeno resumo em nível info a cada 10 minutos, com uma estatística (contagem de varreduras, duração mín/média/máx) por grupo naquele período",
|
|
11
|
+
"nl": "de per-ronde logregel die in 2.2.0 werd geïntroduceerd (\"sensor sweep done in ...\") wordt nu op debug-niveau gelogd in plaats van info - een sensorronde is normaal ongeveer elke 10s voltooid, wat te veel ruis gaf op info-niveau. In plaats daarvan wordt nu elke 10 minuten een kleine samenvatting op info-niveau gelogd, met een statistiek (aantal rondes, min/gemiddelde/max duur) per groep over die periode",
|
|
12
|
+
"fr": "la ligne de journal par tour introduite dans la 2.2.0 (\"sensor sweep done in ...\") est désormais enregistrée au niveau debug au lieu de info - un tour des capteurs se termine normalement environ toutes les 10s, ce qui était trop fréquent pour le niveau info. À la place, un petit résumé de niveau info est maintenant enregistré toutes les 10 minutes, avec une statistique (nombre de tours, durée min/moyenne/max) par groupe sur cette période",
|
|
13
|
+
"it": "la riga di log per giro introdotta nella 2.2.0 (\"sensor sweep done in ...\") viene ora registrata a livello debug invece che info - un giro dei sensori normalmente si completa circa ogni 10s, il che era troppo frequente per il livello info. Al suo posto, ora viene registrato un piccolo riepilogo a livello info ogni 10 minuti, con una statistica (numero di giri, durata min/media/max) per gruppo relativa a quel periodo",
|
|
14
|
+
"es": "la línea de registro por pasada introducida en la 2.2.0 (\"sensor sweep done in ...\") ahora se registra en nivel debug en lugar de info - una pasada de sensores normalmente se completa cada 10s aproximadamente, lo cual era demasiado frecuente para el nivel info. En su lugar, ahora se registra un pequeño resumen en nivel info cada 10 minutos, con una estadística (número de pasadas, duración mín/media/máx) por grupo durante ese periodo",
|
|
15
|
+
"pl": "linia dziennika dla każdego przebiegu wprowadzona w 2.2.0 (\"sensor sweep done in ...\") jest teraz zapisywana na poziomie debug zamiast info - przebieg czujników zwykle kończy się co około 10s, co było zbyt częste dla poziomu info. Zamiast tego co 10 minut zapisywane jest teraz małe podsumowanie na poziomie info, ze statystyką (liczba przebiegów, min/średni/maks czas trwania) dla każdej grupy w tym okresie",
|
|
16
|
+
"zh-cn": "2.2.0 中引入的每轮采集日志行(\"sensor sweep done in ...\")现在改为以 debug 级别记录,而不是 info 级别——传感器一轮采集通常约每10秒完成一次,这在 info 级别下过于频繁。取而代之的是,现在每10分钟记录一条小型 info 级别摘要,包含该分组在此期间的统计信息(采集次数、最小/平均/最大耗时)",
|
|
17
|
+
"uk": "рядок журналу для кожного проходу, доданий у 2.2.0 (\"sensor sweep done in ...\"), тепер записується на рівні debug замість info - прохід датчиків зазвичай завершується приблизно кожні 10с, що було занадто часто для рівня info. Натомість тепер раз на 10 хвилин записується невеликий підсумок на рівні info зі статистикою (кількість проходів, мін/середня/макс тривалість) для кожної групи за цей період"
|
|
18
|
+
},
|
|
6
19
|
"2.2.0": {
|
|
7
20
|
"en": "sensor and settings are no longer polled in lockstep: sensor is read as fast as the protocol allows (throttled to at most once every 10s), while settings is only read once every 60s, plus once right away after any value is written to the heat pump - reducing how often the heat pump's own control gets polled for the slower-changing settings data, without making sensor data any less fresh. A group's sweep - round-robin or multi-block alike - is never interrupted by the other group becoming due partway through. The combined \"full-coverage cycle\" log line is replaced by one line per group, logged the moment that group's own sweep completes, showing its actual elapsed time and a rolling average over its last 10 sweeps",
|
|
8
21
|
"de": "Sensor- und Settings-Abfragen laufen nicht mehr im Gleichschritt: Sensor wird so schnell wie das Protokoll es erlaubt gelesen (gedrosselt auf höchstens einmal alle 10s), während Settings nur alle 60s gelesen wird, plus einmal sofort nachdem ein Wert zur Wärmepumpe geschrieben wurde - das reduziert, wie oft die Steuerung der Wärmepumpe für die sich selten ändernden Settings-Daten abgefragt wird, ohne dass Sensordaten dadurch weniger aktuell werden. Ein Durchlauf einer Gruppe - egal ob round-robin oder multi-block - wird nie unterbrochen, nur weil die andere Gruppe währenddessen fällig wird. Die kombinierte \"full-coverage cycle\"-Logzeile wird durch eine Zeile pro Gruppe ersetzt, die genau dann protokolliert wird, wenn der Durchlauf dieser Gruppe abgeschlossen ist, und die tatsächliche verstrichene Zeit sowie einen gleitenden Durchschnitt der letzten 10 Durchläufe zeigt",
|
|
@@ -80,19 +93,6 @@
|
|
|
80
93
|
"pl": "dla S_H726100 wartości ustawień są teraz zbierane w jednym połączonym żądaniu zamiast jednego bloku na cykl odpytywania - pełne odświeżenie ustawień trwa teraz sekundy zamiast około minuty. Pozostałe obsługiwane firmware nie są tym dotknięte",
|
|
81
94
|
"zh-cn": "对于 S_H726100,设置值现在通过一次合并请求一次性收集,而不再是每个轮询周期只读取一个区块——完整刷新一次设置现在只需几秒钟,而不是大约一分钟。其他所有受支持的固件均不受影响",
|
|
82
95
|
"uk": "для S_H726100 значення налаштувань тепер збираються одним об'єднаним запитом замість одного блоку за цикл опитування - повне оновлення налаштувань тепер триває секунди замість приблизно хвилини. Усі інші підтримувані прошивки це не стосується"
|
|
83
|
-
},
|
|
84
|
-
"1.3.10": {
|
|
85
|
-
"en": "fix: a retry on an already finely-tuned data block no longer jumps the delay up by the full coarse step - it now corrects by the same small step the tuning had converged to, and only escalates back up if retries keep actually recurring",
|
|
86
|
-
"de": "fix: ein Retry auf einen bereits fein eingestellten Datenblock springt das Delay nicht mehr um den vollen groben Schritt hoch - er korrigiert jetzt um denselben kleinen Schritt, auf den die Feinabstimmung bereits konvergiert war, und eskaliert nur weiter, wenn Retries tatsächlich wiederholt auftreten",
|
|
87
|
-
"ru": "исправление: повтор для уже точно настроенного блока данных больше не увеличивает задержку сразу на весь грубый шаг - теперь коррекция происходит на тот же малый шаг, до которого сошлась точная настройка, и шаг увеличивается снова только при повторяющихся сбоях",
|
|
88
|
-
"pt": "correção: uma nova tentativa em um bloco de dados já ajustado finamente não aumenta mais o atraso pelo passo grosseiro completo - agora corrige pelo mesmo passo pequeno ao qual o ajuste havia convergido, e só aumenta novamente se as tentativas realmente se repetirem",
|
|
89
|
-
"nl": "fix: een retry op een al fijn afgesteld datablok verhoogt de vertraging niet meer met de volledige grove stap - er wordt nu gecorrigeerd met dezelfde kleine stap waarnaar de afstelling al was geconvergeerd, en de stap wordt alleen weer groter als retries daadwerkelijk blijven terugkomen",
|
|
90
|
-
"fr": "correctif : une nouvelle tentative sur un bloc de données déjà finement ajusté n'augmente plus le délai du pas grossier complet - elle corrige désormais du même petit pas vers lequel l'ajustement avait convergé, et n'escalade à nouveau que si les tentatives se répètent réellement",
|
|
91
|
-
"it": "correzione: un retry su un blocco dati già finemente calibrato non aumenta più il ritardo dell'intero passo grossolano - ora corregge con lo stesso piccolo passo a cui la calibrazione era convergente, e aumenta di nuovo solo se i retry continuano davvero a ripetersi",
|
|
92
|
-
"es": "corrección: un reintento en un bloque de datos ya ajustado finamente ya no aumenta el retardo en el paso grueso completo - ahora corrige con el mismo paso pequeño al que había convergido el ajuste, y solo vuelve a escalar si los reintentos realmente siguen repitiéndose",
|
|
93
|
-
"pl": "poprawka: ponowna próba dla już precyzyjnie dostrojonego bloku danych nie zwiększa już opóźnienia o pełny, zgrubny krok - teraz koryguje o ten sam mały krok, do którego zbiegło dostrajanie, i eskaluje ponownie tylko wtedy, gdy próby faktycznie się powtarzają",
|
|
94
|
-
"zh-cn": "修复:对已经精细调优的数据块进行重试时,不再将延迟直接跳增整个粗略步长——现在会按照调优已收敛到的相同小步长进行修正,只有在重试确实反复发生时才会重新升级步长",
|
|
95
|
-
"uk": "виправлення: повторна спроба для вже точно налаштованого блока даних більше не збільшує затримку одразу на весь грубий крок - тепер вона коригується тим самим малим кроком, до якого зійшло налаштування, і знову зростає лише якщо повтори справді повторюються"
|
|
96
96
|
}
|
|
97
97
|
},
|
|
98
98
|
"titleLang": {
|
package/lib/idm-session.js
CHANGED
|
@@ -185,6 +185,18 @@ class IdmSession {
|
|
|
185
185
|
// finishSweep(), the only place this is read from and pushed to.
|
|
186
186
|
this.recentSweepDurationsMs = { sensor: [], settings: [] };
|
|
187
187
|
this.sweepAverageWindow = 10;
|
|
188
|
+
// Per-sweep completions are only logged at debug level (see finishSweep()) - in normal
|
|
189
|
+
// operation a sensor sweep finishes roughly every sensorMinIntervalMs, which is far too
|
|
190
|
+
// chatty for info level. Instead reportSweepStats() (started by start(), see
|
|
191
|
+
// statsReportTimer below) logs one small info-level summary every statsReportIntervalMs,
|
|
192
|
+
// built from the counters below - reset to zero each time it reports.
|
|
193
|
+
this.statsReportIntervalMs = 10 * 60 * 1000; // fixed, not configurable - see class comment
|
|
194
|
+
this.statsReportTimer = null;
|
|
195
|
+
/** @type {{sensor: {count: number, totalMs: number, minMs: number | null, maxMs: number | null}, settings: {count: number, totalMs: number, minMs: number | null, maxMs: number | null}}} */
|
|
196
|
+
this.sweepStatsSinceReport = {
|
|
197
|
+
sensor: { count: 0, totalMs: 0, minMs: null, maxMs: null },
|
|
198
|
+
settings: { count: 0, totalMs: 0, minMs: null, maxMs: null },
|
|
199
|
+
};
|
|
188
200
|
// Set by enqueueWrite(); consumed (and cleared) the next time chooseNextGroup() is asked -
|
|
189
201
|
// forces a settings turn right after the WHOLE write queue has fully drained (request_data()
|
|
190
202
|
// is only ever reached once nothing is left to send - see write_data_to_heatpump()'s
|
|
@@ -394,6 +406,8 @@ class IdmSession {
|
|
|
394
406
|
/** Starts (or restarts) the connection process. Call once the adapter is ready. */
|
|
395
407
|
start() {
|
|
396
408
|
this.connectAndRead();
|
|
409
|
+
if (this.statsReportTimer) this.hooks.clearInterval(this.statsReportTimer);
|
|
410
|
+
this.statsReportTimer = this.hooks.setInterval(this.reportSweepStats.bind(this), this.statsReportIntervalMs);
|
|
397
411
|
}
|
|
398
412
|
|
|
399
413
|
connectAndRead() {
|
|
@@ -724,13 +738,14 @@ class IdmSession {
|
|
|
724
738
|
/**
|
|
725
739
|
* Called once `group`'s sweep has actually covered every one of its blocks (see
|
|
726
740
|
* recordBlockRead(), the only caller) - logs the sweep's actual elapsed duration and a rolling
|
|
727
|
-
* average over the last sweepAverageWindow sweeps (
|
|
728
|
-
*
|
|
729
|
-
*
|
|
730
|
-
* the NEXT sweep starts counting from zero. Does NOT touch
|
|
731
|
-
* chooseNextGroup() restamps it fresh the moment this group's
|
|
732
|
-
* (beginGroupCollection() for a multi-block group, or the first
|
|
733
|
-
* round-robin lap), since only then is "now" meaningful as that sweep's
|
|
741
|
+
* average over the last sweepAverageWindow sweeps at debug level (a sensor sweep normally
|
|
742
|
+
* completes roughly every sensorMinIntervalMs, far too often for info - the periodic info-level
|
|
743
|
+
* summary is reportSweepStats() instead), feeds sweepStatsSinceReport for that summary, and
|
|
744
|
+
* clears blocksReadThisSweep so the NEXT sweep starts counting from zero. Does NOT touch
|
|
745
|
+
* sweepStartedAt[group] itself - chooseNextGroup() restamps it fresh the moment this group's
|
|
746
|
+
* next sweep actually begins (beginGroupCollection() for a multi-block group, or the first
|
|
747
|
+
* block request of a new round-robin lap), since only then is "now" meaningful as that sweep's
|
|
748
|
+
* start.
|
|
734
749
|
* @param {'sensor'|'settings'} group
|
|
735
750
|
*/
|
|
736
751
|
finishSweep(group) {
|
|
@@ -745,8 +760,34 @@ class IdmSession {
|
|
|
745
760
|
const avgMs = Math.round(recent.reduce((sum, ms) => sum + ms, 0) / recent.length);
|
|
746
761
|
// Short on purpose (see request_data_block()'s block-07 line for the same reasoning) -
|
|
747
762
|
// this only ever logs once per completed sweep, never more often than that group's own
|
|
748
|
-
// sweep actually takes.
|
|
749
|
-
|
|
763
|
+
// sweep actually takes. debug level only (see reportSweepStats() for the info-level
|
|
764
|
+
// summary) - too frequent for info in normal operation.
|
|
765
|
+
this.log.debug(group + ' sweep done in ' + elapsedMs + 'ms (avg of last ' + recent.length + ': ' + avgMs + 'ms)');
|
|
766
|
+
|
|
767
|
+
const stats = this.sweepStatsSinceReport[group];
|
|
768
|
+
stats.count++;
|
|
769
|
+
stats.totalMs += elapsedMs;
|
|
770
|
+
stats.minMs = stats.minMs == null ? elapsedMs : Math.min(stats.minMs, elapsedMs);
|
|
771
|
+
stats.maxMs = stats.maxMs == null ? elapsedMs : Math.max(stats.maxMs, elapsedMs);
|
|
772
|
+
}
|
|
773
|
+
|
|
774
|
+
/**
|
|
775
|
+
* Logs one small info-level summary of both groups' sweep activity over the last
|
|
776
|
+
* statsReportIntervalMs (fixed at 10 minutes - see the constructor), then resets
|
|
777
|
+
* sweepStatsSinceReport so the next summary only covers the following window. Started as a
|
|
778
|
+
* fixed interval by start() and cleared by stop(); this is the only info-level sweep-related
|
|
779
|
+
* log in normal operation now that finishSweep() logs at debug level.
|
|
780
|
+
*/
|
|
781
|
+
reportSweepStats() {
|
|
782
|
+
const describe = (group) => {
|
|
783
|
+
const stats = this.sweepStatsSinceReport[group];
|
|
784
|
+
if (stats.count === 0) return group + ': 0 sweeps';
|
|
785
|
+
const avgMs = Math.round(stats.totalMs / stats.count);
|
|
786
|
+
return group + ': ' + stats.count + ' sweep(s), ' + avgMs + 'ms avg (min ' + stats.minMs + 'ms, max ' + stats.maxMs + 'ms)';
|
|
787
|
+
};
|
|
788
|
+
this.log.info('last ' + Math.round(this.statsReportIntervalMs / 60000) + 'min - ' + describe('sensor') + '; ' + describe('settings'));
|
|
789
|
+
this.sweepStatsSinceReport.sensor = { count: 0, totalMs: 0, minMs: null, maxMs: null };
|
|
790
|
+
this.sweepStatsSinceReport.settings = { count: 0, totalMs: 0, minMs: null, maxMs: null };
|
|
750
791
|
}
|
|
751
792
|
|
|
752
793
|
/**
|
|
@@ -1355,6 +1396,7 @@ class IdmSession {
|
|
|
1355
1396
|
if (this.sendSetValueMessageTimeout1) { this.hooks.clearTimeout(this.sendSetValueMessageTimeout1); this.sendSetValueMessageTimeout1 = null; }
|
|
1356
1397
|
if (this.sendSetValueMessageTimeout2) { this.hooks.clearTimeout(this.sendSetValueMessageTimeout2); this.sendSetValueMessageTimeout2 = null; }
|
|
1357
1398
|
if (this.pollScheduleTimer) { this.hooks.clearTimeout(this.pollScheduleTimer); this.pollScheduleTimer = null; }
|
|
1399
|
+
if (this.statsReportTimer) { this.hooks.clearInterval(this.statsReportTimer); this.statsReportTimer = null; }
|
|
1358
1400
|
if (this.client) this.client.destroy();
|
|
1359
1401
|
}
|
|
1360
1402
|
}
|
package/lib/idm-session.test.js
CHANGED
|
@@ -233,6 +233,16 @@ describe('idm-session (connection + request/response state machine)', () => {
|
|
|
233
233
|
expect(clock.countTimers()).to.equal(0);
|
|
234
234
|
});
|
|
235
235
|
|
|
236
|
+
it('also clears the periodic statsReportTimer started by start() on stop()', () => {
|
|
237
|
+
connect();
|
|
238
|
+
expect(session.statsReportTimer, 'start() should have scheduled the periodic stats report').to.be.ok;
|
|
239
|
+
|
|
240
|
+
session.stop();
|
|
241
|
+
|
|
242
|
+
expect(session.statsReportTimer, 'statsReportTimer').to.not.be.ok;
|
|
243
|
+
expect(clock.countTimers()).to.equal(0);
|
|
244
|
+
});
|
|
245
|
+
|
|
236
246
|
describe('sweep tracking reset on disconnect (see setConnected())', () => {
|
|
237
247
|
it('resets both groups\' sweepStartedAt and cancels a pending idle recheck when the connection drops', () => {
|
|
238
248
|
connect();
|
|
@@ -268,7 +278,7 @@ describe('idm-session (connection + request/response state machine)', () => {
|
|
|
268
278
|
// idm701100 has 4 sensor blocks (07,09,0A,0B) - sweepIncomplete() keeps every turn on
|
|
269
279
|
// sensor until all 4 have been read at least once (see the constructor's "sweep"
|
|
270
280
|
// comment), regardless of settings' own due-ness in the meantime.
|
|
271
|
-
const sensorSweepLogs = () => log.
|
|
281
|
+
const sensorSweepLogs = () => log.debug.getCalls().filter(c => /^sensor sweep done in /.test(c.args[0]));
|
|
272
282
|
|
|
273
283
|
socket.emit('data', versionResponse('idm701100')); // sensor block 07 in flight (1st of 4)
|
|
274
284
|
|
|
@@ -309,7 +319,7 @@ describe('idm-session (connection + request/response state machine)', () => {
|
|
|
309
319
|
it('logs the settings sweep the same way, and its rolling average reflects multiple completed sweeps', () => {
|
|
310
320
|
session.version = 'idm701100'; // 5 settings blocks: 03,04,05,06,08
|
|
311
321
|
const settingsBlocks = ['03', '04', '05', '06', '08'];
|
|
312
|
-
const settingsSweepLogs = () => log.
|
|
322
|
+
const settingsSweepLogs = () => log.debug.getCalls().filter(c => /^settings sweep done in /.test(c.args[0]));
|
|
313
323
|
|
|
314
324
|
session.sweepStartedAt.settings = Date.now();
|
|
315
325
|
clock.tick(1000);
|
|
@@ -328,7 +338,7 @@ describe('idm-session (connection + request/response state machine)', () => {
|
|
|
328
338
|
it('caps the rolling average window at the last 10 completed sweeps', () => {
|
|
329
339
|
session.version = 'idm701100';
|
|
330
340
|
const settingsBlocks = ['03', '04', '05', '06', '08'];
|
|
331
|
-
const settingsSweepLogs = () => log.
|
|
341
|
+
const settingsSweepLogs = () => log.debug.getCalls().filter(c => /^settings sweep done in /.test(c.args[0]));
|
|
332
342
|
const doSweep = (ms) => {
|
|
333
343
|
session.sweepStartedAt.settings = Date.now();
|
|
334
344
|
clock.tick(ms);
|
|
@@ -343,6 +353,58 @@ describe('idm-session (connection + request/response state machine)', () => {
|
|
|
343
353
|
});
|
|
344
354
|
});
|
|
345
355
|
|
|
356
|
+
describe('periodic sweep stats summary (info level, every statsReportIntervalMs, see reportSweepStats())', () => {
|
|
357
|
+
const statsLogs = () => log.info.getCalls().filter(c => /^last \d+min - /.test(c.args[0]));
|
|
358
|
+
|
|
359
|
+
it('is started by start() and logs nothing until statsReportIntervalMs has actually elapsed', () => {
|
|
360
|
+
session.start();
|
|
361
|
+
expect(session.statsReportTimer, 'start() should schedule the periodic report').to.be.ok;
|
|
362
|
+
|
|
363
|
+
clock.tick(session.statsReportIntervalMs - 1);
|
|
364
|
+
expect(statsLogs()).to.have.lengthOf(0);
|
|
365
|
+
|
|
366
|
+
clock.tick(1);
|
|
367
|
+
expect(statsLogs()).to.have.lengthOf(1);
|
|
368
|
+
});
|
|
369
|
+
|
|
370
|
+
it('reports "0 sweeps" for a group that never completed a sweep in the window', () => {
|
|
371
|
+
session.start();
|
|
372
|
+
clock.tick(session.statsReportIntervalMs);
|
|
373
|
+
|
|
374
|
+
expect(statsLogs()[0].args[0]).to.equal('last 10min - sensor: 0 sweeps; settings: 0 sweeps');
|
|
375
|
+
});
|
|
376
|
+
|
|
377
|
+
it('summarizes count/min/avg/max per group and resets the counters afterwards', () => {
|
|
378
|
+
session.version = 'idm701100'; // 5 settings blocks: 03,04,05,06,08
|
|
379
|
+
const settingsBlocks = ['03', '04', '05', '06', '08'];
|
|
380
|
+
session.start();
|
|
381
|
+
|
|
382
|
+
session.sweepStartedAt.settings = Date.now();
|
|
383
|
+
clock.tick(1000);
|
|
384
|
+
for (const b of settingsBlocks) session.recordBlockRead('settings', b);
|
|
385
|
+
|
|
386
|
+
session.sweepStartedAt.settings = Date.now();
|
|
387
|
+
clock.tick(3000);
|
|
388
|
+
for (const b of settingsBlocks) session.recordBlockRead('settings', b);
|
|
389
|
+
|
|
390
|
+
clock.tick(session.statsReportIntervalMs - 4000);
|
|
391
|
+
const logs = statsLogs();
|
|
392
|
+
expect(logs).to.have.lengthOf(1);
|
|
393
|
+
expect(logs[0].args[0]).to.equal('last 10min - sensor: 0 sweeps; settings: 2 sweep(s), 2000ms avg (min 1000ms, max 3000ms)');
|
|
394
|
+
|
|
395
|
+
// Counters must have been reset - a second full window with no further sweeps reports
|
|
396
|
+
// "0 sweeps" again rather than carrying the previous window's numbers forward.
|
|
397
|
+
clock.tick(session.statsReportIntervalMs);
|
|
398
|
+
expect(statsLogs()[1].args[0]).to.equal('last 10min - sensor: 0 sweeps; settings: 0 sweeps');
|
|
399
|
+
});
|
|
400
|
+
|
|
401
|
+
it('keeps firing every statsReportIntervalMs for as long as the session runs', () => {
|
|
402
|
+
session.start();
|
|
403
|
+
clock.tick(session.statsReportIntervalMs * 3);
|
|
404
|
+
expect(statsLogs()).to.have.lengthOf(3);
|
|
405
|
+
});
|
|
406
|
+
});
|
|
407
|
+
|
|
346
408
|
describe('adaptive per-data-block content delay (hill-climbs a sweet spot, not "wait forever")', () => {
|
|
347
409
|
const dataBlock07 = '00000000000000000000000000000000000000000B270000000000000000';
|
|
348
410
|
|