Аналіз коду
Repository: https://github.com/Lucosiar/geo_Cloudtainer
Author: Lucosiar (Lucía + псевдонім?)
Branch: master
Latest tag: v1.0.0 argentina
Дата аналізу: 2026-04-24
Local copy: client_repo/manual/ (файли витягнуті через raw.github, не через git clone через мережеві проблеми)
1. Структура репо
Section titled “1. Структура репо”Xiao_Cloudtainer/├── CMakeLists.txt — Zephyr build config├── README.md — інструкції west build для XIAO nRF54L15├── prj.conf — Kconfig опції (BLE, I2C, ADC, PWM, Notecard, memory)├── note-zephyr — git submodule (Blues Notecard driver для Zephyr)├── nrf54l15dk_nrf54l15_cpuapp.overlay — overlay для офіційного Nordic DK├── xiao_nrf54l15_nrf54l15_cpuapp.overlay — overlay для Seeed XIAO nRF54L15├── build/ — build artifacts (не комітити, це як node_modules)└── src/ ├── main.c — точка входу, init sequence ├── thread.c — 4 threads + BLE peripheral ├── thread.h — macros NOTECARD_CALL, stacks ├── notecard.c — Blues Notecard I2C driver (port з Python) ├── notecard.h — API driver ├── notecard_variables.c — event init defaults ├── notecard_variables.h — ⚠️ всі константи і hardcoded secrets ├── request.c — high-level Notecard requests ├── request.h — API requests ├── pwm_servo.c — PWM керування servo ├── pwm_servo.h — servo positions + pulse widths ├── ds3231.c — RTC driver на I2C ├── ds3231.h — RTC API ├── adc.c — battery voltage через SAADC ├── adc.h — ADC API ├── led.c — GPIO LEDs керування ├── led.h — LED API └── variables.h — event structs, інтервали2. Hardware конфігурація (з overlay)
Section titled “2. Hardware конфігурація (з overlay)”Board target
Section titled “Board target”xiao_nrf54l15/nrf54l15/cpuappЧіп: Nordic nRF54L15 — новий Cortex-M33 @ 128 MHz з BLE 5.4, 1.5 MB Flash, 256 KB RAM. Анонсований 2024, це наступник nRF52 серії, не має вбудованого LTE (тому cellular виконує Blues Notecard).
Це змінює наше попереднє припущення про nRF9160 — у попередніх документах я помилково писав nRF9160, треба поправити.
Піни XIAO nRF54L15
Section titled “Піни XIAO nRF54L15”| Функція | Pin XIAO | Розводка в overlay |
|---|---|---|
| I2C SDA (Notecard + RTC) | P1.10 | i2c22 SDA |
| I2C SCL (Notecard + RTC) | P1.11 | i2c22 SCL |
| PWM servo signal | P1.06 | pwm20 channel 0 |
| ADC battery (через voltage divider) | P1.05 | AIN1 (SAADC) |
I2C devices
Section titled “I2C devices”i2c22 (400 kHz FAST mode) ──┬── Notecard (addr 0x17) └── DS3231 RTC (addr 0x68)Voltage divider для ADC (з adc.c)
Section titled “Voltage divider для ADC (з adc.c)”Battery ──┬── R1 = 220 kΩ ──┬── ADC input (P1.05) │ R2 = 100 kΩ │ ──────────────── GNDScale factor: (220+100)/100 = 3.2 — тобто ADC читає V_pin = V_bat / 3.2. Для 4.2V battery → 1.3125V на ADC pin.
PWM servo (з pwm_servo.h)
Section titled “PWM servo (з pwm_servo.h)”| Position | Pulse width | Функція |
|---|---|---|
SERVO_POS_OFF | 0 µs | PWM disabled, servo не тримає позицію |
SERVO_POS_REF / CLOSED | 1000 µs | замок закритий |
SERVO_POS_OPEN | 1450 µs | замок відкритий |
Hold time: 7 секунд відкрито, потім автоматично повертається на closed.
3. Software архітектура
Section titled “3. Software архітектура”3.1 Sequence запуску (з main.c)
Section titled “3.1 Sequence запуску (з main.c)”1. k_sleep(10 SEC) ── дати напруги стабілізуватись2. pwm_servo_init() ── init PWM20, встановити servo в closed3. ble_init() ── enable BT, start advertising4. bat_adc_init() ── init SAADC channel для battery5. notecard_configure() ── відправити hub.set, card.voltage, note.template6. k_sleep(15 SEC) ── дати Notecard підключитись7. ds3231_get_datetime() ── перевірити RTC час └── якщо рік < 2020 → sync_rtc_from_notecard()8. threads_start() ── запустити 4 робочі threads9. main loop: k_sleep(10 SEC) в нескінченному циклі3.2 Потоки (threads) з notecard_variables.h + thread.c
Section titled “3.2 Потоки (threads) з notecard_variables.h + thread.c”| Thread | Priority | Stack | Period | Функція |
|---|---|---|---|---|
sat_init | 3 | 2048 | 60s poll, 30min timeout | Детектує чи Notecard має satellite session (через hub.status). Коли знайшов → дає семафор SAT_READY. |
collector | 4 | 2048 | 30s | Збирає: voltage через ADC (fallback Notecard), lat/lng/gps_status через Notecard, timestamp через DS3231. Пише в глобальну g_event під mutex’ом. |
gsm | 5 | 2048 | 60s | Шле g_event у cloudtainer-event.qos Note через cellular. До 5 retries з 10s затримкою. ПАУЗА якщо BLE client підключений. |
sat | 5 | 2048 | 300s | Шле g_event (зменшений format) у cloudtainer-event-sat.qos Note через satellite. Чекає на SAT_READY семафор. |
3.3 Event structure (variables.h)
Section titled “3.3 Event structure (variables.h)”Для cellular (cloudlock_event_body):
{ float voltage; // напруга батареї (4.2V max) float battery_percentage; // 0-100% float uptime; // uptime пристрою bool vusb; // USB підключений double lat; // GPS широта double lng; // GPS довгота uint64_t timestamp; // Unix time з RTC char gps_status[256]; // текстовий GPS стан char motion_status[32]; // "idle", "face-down", etc. float motion_time; // скільки часу в поточному стані char event_type[32]; // "monitor" або "open-door" char user_id[32]; // хто відчинив (або "unknown")}Для satellite (cloudlock_event_body_sat) — скорочений варіант:
{ float voltage; double lat; double lng; uint64_t uptime; char gps_status[256];}Satellite пакет менше щоб зекономити дорогу satellite транзакцію.
3.4 BLE інтерфейс (з thread.c)
Section titled “3.4 BLE інтерфейс (з thread.c)”Service UUID: b1d00001-630a-4c52-952a-4ba220000001
Characteristics:
- Open (write)
b1d00004-...— приймає 8-символьний token - Status (read)
b1d00005-...— повертає строку “closed”
Unlock tokens (hardcoded):
BLE_UNLOCK_TOKEN_1 = "jsnf9233" → reports user_id "SUP0012025"BLE_UNLOCK_TOKEN_2 = "kqtm4816" → reports user_id "SUP0022025"Flow при відкриванні:
1. Mobile app connect BLE2. Write token → ble_open_write()3. parse_unlock_token() — validate token4. k_work_submit(&ble_open_event_work)5. pwm_servo_activate() — OPEN → hold 7s → CLOSED6. send_cloudlock_event(snapshot, OPEN_DOOR_EVENT) — через Notecard7. request_hub_sync() — примусово синхронізувати3.5 Транспорти та стандартизована класифікація
Section titled “3.5 Транспорти та стандартизована класифікація”#define TRANSPORT_CELLULAR "cell"#define TRANSPORT_SATELLITE "ntn" // Non-Terrestrial Network#define TRANSPORT_WIFI_CELL_NTN "wifi-cell-ntn"#define TRANSPORT_CELL_NTN "cell-ntn"#define TRANSPORT_WIFI_CELL "wifi-cell"Це пояснює суперечність на зустрічі: коли Francisco казав “передає GCM у no-coverage зонах”, можливо Notecard рапортував cell-ntn (що означає “cellular з fallback на satellite”) — але Notehub UI спрощує до просто “cellular”. Треба перевірити _session.qo events на точний transport.
4. ⚠️ КРИТИЧНІ ЗНАХІДКИ
Section titled “4. ⚠️ КРИТИЧНІ ЗНАХІДКИ”4.1 🔴 Security — hardcoded credentials у публічному репо
Section titled “4.1 🔴 Security — hardcoded credentials у публічному репо”У notecard_variables.h у відкритому вигляді:
#define WIFI_NETWORK_NAME "asimetrixIOA"#define WIFI_NETWORK_PASSWORD "4s1m4tr1x" // ⚠️ PLAINTEXT#define BLE_UNLOCK_TOKEN_1 "jsnf9233" // ⚠️ Будь-хто може відкрити замок#define BLE_UNLOCK_TOKEN_2 "kqtm4816" // ⚠️#define NOTECARD_DEVICE_ID "351077454557425"Репо публічне (Public label на GitHub). Це означає:
- WiFi мережа
asimetrixIOAскомпрометована — хто завгодно може до неї приєднатись - BLE tokens
jsnf9233іkqtm4816скомпрометовані — хто завгодно з Bluetooth-діапазону може відкрити будь-який замок Cloudlock в Аргентині - IMEI розкритий — потенційне spoofing
Рекомендація:
- Зробити repo Private негайно
- Ротувати обидва unlock tokens (зробити їх per-device або OTP)
- Змінити WiFi password
- Використовувати environment variables / Kconfig secrets або KConfig options через CMD замість hardcoded в .h
- Або краще: public key crypto для BLE unlock — app підписує challenge своїм private key, device перевіряє сигнатуру через embedded public key
4.2 🔴 Energy — усе в “continuous” mode
Section titled “4.2 🔴 Energy — усе в “continuous” mode”#define MODE_HUB_SET "continuous"#define MODE_GPS "continuous"#define MODE_GSM "continuous"#define MODE_SAT "continuous"Жодного duty cycling! Це вбиває автономність:
- GPS continuous: ~18 mA × 24 год = 432 mAh/день (тільки GPS)
- Cellular continuous: attach завжди active = ~50-150 mA periodic = ~200 mAh/день
- Satellite continuous: Starnote завжди on = додаткові 20+ mAh/день
Рекомендація:
#define MODE_HUB_SET "periodic"#define MODE_GPS "periodic" // з periodic mode// + voutbound/vinbound voltage-variable syncПлюс використання accelerometer (зараз немає!) для wake-on-motion.
4.3 🔴 Відсутні компоненти в коді
Section titled “4.3 🔴 Відсутні компоненти в коді”| Компонент | Що нема | Наслідок |
|---|---|---|
| Accelerometer | Нема drivers, нема I2C device у overlay | Немає wake-on-motion → ~400+ mAh/день витрачається на GPS коли контейнер стоїть |
| Fuel gauge IC (MAX17048) | Battery читається тільки через ADC + voltage divider | Точність ±5% SOC замість ±1% з fuel gauge; залежить від температури і load |
| MOSFET power gate для servo | Servo фізично завжди підключений до VBAT | (невідомо, може бути на PCB, не відображено в коді/overlay) |
| MOSFET power gate для Notecard | Notecard завжди включений | PSM mode не виражено use, continuous attach |
| Reverse polarity protection | Нема references у коді | MCU-level нема, може бути на PCB hardware |
4.4 🟡 APN hardcoded для Argentina
Section titled “4.4 🟡 APN hardcoded для Argentina”#define APN_NAME "igprs.claro.com.ar" // Claro Argentina#define EXTERNAL_APN_CLARO "igprs.claro.com.ar"#define EXTERNAL_APN_MOVISTAR "igprs.claro.com.ar" // ⚠️ BUG! Це теж Claro, не MovistarBug: Movistar APN прописано як Claro. Якщо device перемикається на Movistar SIM — APN буде невірний.
Правильний Movistar APN у Аргентині: internet.gprs.unifon.com.ar або wap.gprs.unifon.com.ar.
Також — якщо device deploy’итиметься в інших країнах (Європа, Paraguay, etc.) — потрібна динамічна APN detection через card.wireless.
4.5 🟡 Battery math нестабільна
Section titled “4.5 🟡 Battery math нестабільна”float pct = (voltage - LIPO_MIN_V) / (LIPO_MAX_V - LIPO_MIN_V) * 100.0f;Лінійна інтерполяція voltage → %. Це дуже неточно для LiPo — SOC curve нелінійна (плато 3.7V-3.9V де voltage майже не змінюється при зміні SOC з 80% до 20%). Краще:
- Використати lookup table з SOC curve виробника
- Або поставити fuel gauge MAX17048 який робить це апаратно через impedance tracking
4.6 🟡 GPS mode “continuous” + ADC “continuous” без фільтрації
Section titled “4.6 🟡 GPS mode “continuous” + ADC “continuous” без фільтрації”collector_thread_entry() читає ADC кожні 30 секунд, але:
- Без усереднення (single sample)
- Без medianного фільтра
- Ніяких sanity checks
Якщо servo активується під час ADC read → сильна просадка напруги → неправильне значення battery → bogus report у Notehub. Це може бути ще одна причина “battery level не передається” — value невалідний.
4.7 🟢 Mutex-захищене I2C — добре
Section titled “4.7 🟢 Mutex-захищене I2C — добре”Всі звернення до Notecard через NOTECARD_CALL(call) macro:
#define NOTECARD_CALL(call) \ do { \ k_mutex_lock(¬ecard_mutex, K_FOREVER); \ (call); \ k_mutex_unlock(¬ecard_mutex); \ } while (0)Це правильно — I2C bus шарається між RTC і Notecard, і кілька threads (collector, gsm, sat_init, BLE work) його використовують одночасно.
4.8 🟢 Factory reset toggle — добре для debug
Section titled “4.8 🟢 Factory reset toggle — добре для debug”#define FACTORY_RESET_NOTECARD_ON_BOOT 0Коли =1 — при кожному boot’ring очищує Notecard templates/notefiles. Корисно для dev.
5. Виявлені баги та code smells
Section titled “5. Виявлені баги та code smells”5.1 Memory alloc для main stack
Section titled “5.1 Memory alloc для main stack”CONFIG_HEAP_MEM_POOL_SIZE=32768 (32KB)CONFIG_MAIN_STACK_SIZE=8192 (8KB)CONFIG_SYSTEM_WORKQUEUE_STACK_SIZE=4096Це порядково. XIAO nRF54L15 має 256 KB RAM, тому вистачає.
5.2 Default values в g_event — “magic numbers”
Section titled “5.2 Default values в g_event — “magic numbers””static cloudlock_event_body g_event = { .voltage = 14.1f, // завищене значення .battery_percentage = 14.1f, .uptime = 14.1f, .lat = 14.1f, // очевидно placeholder .lng = 14.1f, ...};14.1 — не валідне значення для кожного поля (voltage 14.1V ≠ реальності, lat 14.1 — десь в Caraibes). Це тому що Notehub отримує 14.1 і показує “дивне місцезнаходження” — можливо якраз те що Esteban бачив як “strange coordinates”.
Фікс: ініціалізувати всі поля нулями або sentinel NaN, і не слати event поки collector не заповнив валідними значеннями.
5.3 FACTORY_RESET_NOTECARD_ON_BOOT = 0 (хардкод)
Section titled “5.3 FACTORY_RESET_NOTECARD_ON_BOOT = 0 (хардкод)”Треба зробити через Kconfig або environment для зручного toggle без recompile.
5.4 Немає error handling якщо Notecard не відповідає взагалі
Section titled “5.4 Немає error handling якщо Notecard не відповідає взагалі”notecard_configure() викликається раз при старті. Якщо Notecard dead — main повертає error, пристрій зависне (нема retry). Краще: k_timer з периодичним повторним init.
6. Що робити в найближчих роботах
Section titled “6. Що робити в найближчих роботах”Критично (до серійного виробництва)
Section titled “Критично (до серійного виробництва)”- Repo → Private + ротація credentials (5 хв роботи)
- Фікс Movistar APN bug (1 рядок коду)
- Фікс default values 14.1 → nulls (5 хв)
- Впровадити periodic modes замість continuous (2 год refactor + тестування)
- Додати accelerometer LIS2DH12 на I2C + wake-on-motion logic (1 день firmware)
- Додати MOSFET gate для servo power rail (hardware + 10 рядків коду)
- Додати fuel gauge MAX17048 (hardware + driver)
- Секуризувати BLE (public-key crypto, не plaintext tokens)
Важливо (короткострокові оптимізації)
Section titled “Важливо (короткострокові оптимізації)”- Battery SOC lookup table замість лінійної формули
- ADC усереднення (64-128 samples + median filter)
- Dynamic APN detection через Notecard
- Proper error handling в
notecard_configure()з retry - Зробити hardcoded values через Kconfig (device ID, WiFi, tokens)
Довгостроково
Section titled “Довгостроково”- OTA firmware update через Notecard DFU
- Secure boot (nRF54L15 має built-in TrustZone)
- Tamper detection (accelerometer + magnetic switch)
- Перехід з XIAO dev-board на raw nRF54L15 chip для production PCB
7. Як це міняє попередні документи
Section titled “7. Як це міняє попередні документи”| Документ | Що треба поправити |
|---|---|
10_how_device_works_UA.md | nRF9160 → nRF54L15 скрізь, додати BLE опис, додати XIAO board info |
09_meeting_notes_cleaned.md | Francisco згадував “Nordic” — тепер знаємо точно що це XIAO nRF54L15 |
02_low_power_autonomy.md | Оновити §1 — чіп не nRF9160, немає intergal LTE/GNSS, LTE через Notecard |
07_schematic_review_UA.md | Підтвердити: один servo (confirmed by code), не два |
08_client_whatsapp_reply.md | Можна додати: “I saw the repo, nice code! A few security and power concerns — quick call?“ |
8. Quick wins для суботи в Gijón
Section titled “8. Quick wins для суботи в Gijón”На основі code analysis, найшвидші фікси які можна зробити в лабі:
- Додати
#define FACTORY_RESET_NOTECARD_ON_BOOT 1на один boot і перевірити чи битих templates немає → потім назад на 0. - Змінити
MODE_GPSз “continuous” на “periodic” — одразу ~400 mAh/день економія. - Фікс Movistar APN — один рядок.
- Default values 14.1 → 0.0 — можливо пояснить “strange coordinates”.
- Збільшити
GPS_TIME_INTERVAL_SECONDSз 120 до 600 — GPS fetch раз на 10 хв замість 2. - Додати ADC averaging — зробити 16 samples перед усередненням.
- Поставити repo в Private і ротувати BLE tokens.
9. Посилання
Section titled “9. Посилання”- Repo: https://github.com/Lucosiar/Xiao_Cloudtainer
- Tag
v1.0.0 argentina: поточна версія що їде в Аргентину - Note-zephyr submodule: https://github.com/blues/note-zephyr
- nRF54L15 datasheet: https://docs.nordicsemi.com/bundle/ps_nrf54L15
- Seeed XIAO nRF54L15: https://wiki.seeedstudio.com/xiao_nrf54l15/