core(M4): полировка по нитам финального ревью

- fglair-discover: fchmod 0600 и при перезаписи существующего --out.
- fglctl: #include <cstdlib>; точная проверка обрезки config
  (n == sizeof-1 && !feof); валидация 'monitor <sec>' (endptr/errno).
- docs: отчёт soak — формулировка «ответы на batch GET»; индекс reports.
Ревью M4 независимым агентом: 2 круга, APPROVED.
This commit is contained in:
2026-09-29 01:30:30 +03:00
parent 31cd56f082
commit 97842276c8
4 changed files with 18 additions and 3 deletions
+1
View File
@@ -35,6 +35,7 @@ docs/ # документация (ниже) + матер
| `../README.md` | README библиотеки: сборка POSIX/ESP-IDF, тесты, fglctl, `tools/fglair-discover`. |
| `../tools/fglair-discover` | Облачный provisioning (ключ устройства, `--format esphome-secrets`). |
| `../examples/cli/fglctl.cpp` | Пример CLI на публичном API. |
| `reports/M4_SOAK.md` | Отчёт приёмочного soak-прогона на приборе (60 мин, 0 рассинхронов). |
## Краткая выжимка протокола
+1 -1
View File
@@ -12,7 +12,7 @@
|---------|----------|
| Смены состояний | registering → online → idle (stop) |
| recovering / offline / KEY_ERROR | **0** |
| Push'ей свойств | 33 (все свойства шаблона A, вкл. строковые) |
| Свойств получено | 33 (ответы на стартовый batch GET, все свойства шаблона A, вкл. строковые) |
| Push'ей со статусом ≠ 200 | **0** |
| `pushes_ok` / `pushes_bad` | 33 / **0** |
| Опросов `commands.json` модулем | 273 (~каждые 13 с) |
+15 -2
View File
@@ -10,6 +10,7 @@
// "lanip_key_id": N, "model": "AP-WC1E", ...}
#include <cerrno>
#include <chrono>
#include <cstdlib>
#include <cstdio>
#include <cstring>
#include <thread>
@@ -70,7 +71,7 @@ bool load_config(const char* path, fgl::aircon::Config* cfg,
}
static char buf[4096];
size_t n = fread(buf, 1, sizeof(buf) - 1, f);
bool truncated = !feof(f);
bool truncated = n == sizeof(buf) - 1 && !feof(f);
fclose(f);
buf[n] = '\0';
if (truncated) {
@@ -181,7 +182,18 @@ int main(int argc, char** argv) {
const char* cmd = argv[2];
int rc = 0;
if (strcmp(cmd, "monitor") == 0) {
int secs = argc > 3 ? atoi(argv[3]) : 60;
int secs = 60;
if (argc > 3) {
char* end = nullptr;
errno = 0;
long v = strtol(argv[3], &end, 10);
if (errno != 0 || end == argv[3] || *end != '\0' || v < 0) {
fprintf(stderr, "Некорректная длительность: %s\n", argv[3]);
rc = 2;
goto cleanup;
}
secs = static_cast<int>(v);
}
if ((rc = wait_online(s, 15000)) == 0) {
s->batch_begin();
for (auto* info = fgl::aircon::prop_info_begin(tmpl);
@@ -249,6 +261,7 @@ int main(int argc, char** argv) {
rc = 2;
}
cleanup:
print_stats(s);
s->stop();
delete s;
+1
View File
@@ -208,6 +208,7 @@ def main():
if args.out:
# Ключ устройства — секрет: файл только для владельца.
fd = os.open(args.out, os.O_WRONLY | os.O_CREAT | os.O_TRUNC, 0o600)
os.fchmod(fd, 0o600) # и при перезаписи существующего файла
with os.fdopen(fd, "w") as f:
f.write(text)
print(f"Записано: {args.out} (права 0600)", file=sys.stderr)