Перейти до вмісту

Аналіз коду

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 через мережеві проблеми)


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)”
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, треба поправити.

ФункціяPin XIAOРозводка в overlay
I2C SDA (Notecard + RTC)P1.10i2c22 SDA
I2C SCL (Notecard + RTC)P1.11i2c22 SCL
PWM servo signalP1.06pwm20 channel 0
ADC battery (через voltage divider)P1.05AIN1 (SAADC)
i2c22 (400 kHz FAST mode) ──┬── Notecard (addr 0x17)
└── DS3231 RTC (addr 0x68)
Battery ──┬── R1 = 220 kΩ ──┬── ADC input (P1.05)
R2 = 100 kΩ
──────────────── GND

Scale factor: (220+100)/100 = 3.2 — тобто ADC читає V_pin = V_bat / 3.2. Для 4.2V battery → 1.3125V на ADC pin.

PositionPulse widthФункція
SERVO_POS_OFF0 µsPWM disabled, servo не тримає позицію
SERVO_POS_REF / CLOSED1000 µsзамок закритий
SERVO_POS_OPEN1450 µsзамок відкритий

Hold time: 7 секунд відкрито, потім автоматично повертається на closed.


1. k_sleep(10 SEC) ── дати напруги стабілізуватись
2. pwm_servo_init() ── init PWM20, встановити servo в closed
3. ble_init() ── enable BT, start advertising
4. bat_adc_init() ── init SAADC channel для battery
5. notecard_configure() ── відправити hub.set, card.voltage, note.template
6. k_sleep(15 SEC) ── дати Notecard підключитись
7. ds3231_get_datetime() ── перевірити RTC час
└── якщо рік < 2020 → sync_rtc_from_notecard()
8. threads_start() ── запустити 4 робочі threads
9. main loop: k_sleep(10 SEC) в нескінченному циклі

3.2 Потоки (threads) з notecard_variables.h + thread.c

Section titled “3.2 Потоки (threads) з notecard_variables.h + thread.c”
ThreadPriorityStackPeriodФункція
sat_init3204860s poll, 30min timeoutДетектує чи Notecard має satellite session (через hub.status). Коли знайшов → дає семафор SAT_READY.
collector4204830sЗбирає: voltage через ADC (fallback Notecard), lat/lng/gps_status через Notecard, timestamp через DS3231. Пише в глобальну g_event під mutex’ом.
gsm5204860sШле g_event у cloudtainer-event.qos Note через cellular. До 5 retries з 10s затримкою. ПАУЗА якщо BLE client підключений.
sat52048300sШле g_event (зменшений format) у cloudtainer-event-sat.qos Note через satellite. Чекає на SAT_READY семафор.

Для 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 транзакцію.

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 BLE
2. Write token → ble_open_write()
3. parse_unlock_token() — validate token
4. k_work_submit(&ble_open_event_work)
5. pwm_servo_activate() — OPEN → hold 7s → CLOSED
6. send_cloudlock_event(snapshot, OPEN_DOOR_EVENT) — через Notecard
7. 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). Це означає:

  1. WiFi мережа asimetrixIOA скомпрометована — хто завгодно може до неї приєднатись
  2. BLE tokens jsnf9233 і kqtm4816 скомпрометовані — хто завгодно з Bluetooth-діапазону може відкрити будь-який замок Cloudlock в Аргентині
  3. 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 для servoServo фізично завжди підключений до VBAT(невідомо, може бути на PCB, не відображено в коді/overlay)
MOSFET power gate для NotecardNotecard завжди включенийPSM mode не виражено use, continuous attach
Reverse polarity protectionНема references у кодіMCU-level нема, може бути на PCB hardware
#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, не Movistar

Bug: 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(&notecard_mutex, K_FOREVER); \
(call); \
k_mutex_unlock(&notecard_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”
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 “Критично (до серійного виробництва)”
  1. Repo → Private + ротація credentials (5 хв роботи)
  2. Фікс Movistar APN bug (1 рядок коду)
  3. Фікс default values 14.1 → nulls (5 хв)
  4. Впровадити periodic modes замість continuous (2 год refactor + тестування)
  5. Додати accelerometer LIS2DH12 на I2C + wake-on-motion logic (1 день firmware)
  6. Додати MOSFET gate для servo power rail (hardware + 10 рядків коду)
  7. Додати fuel gauge MAX17048 (hardware + driver)
  8. Секуризувати BLE (public-key crypto, не plaintext tokens)

Важливо (короткострокові оптимізації)

Section titled “Важливо (короткострокові оптимізації)”
  1. Battery SOC lookup table замість лінійної формули
  2. ADC усереднення (64-128 samples + median filter)
  3. Dynamic APN detection через Notecard
  4. Proper error handling в notecard_configure() з retry
  5. Зробити hardcoded values через Kconfig (device ID, WiFi, tokens)
  1. OTA firmware update через Notecard DFU
  2. Secure boot (nRF54L15 має built-in TrustZone)
  3. Tamper detection (accelerometer + magnetic switch)
  4. Перехід з XIAO dev-board на raw nRF54L15 chip для production PCB

7. Як це міняє попередні документи

Section titled “7. Як це міняє попередні документи”
ДокументЩо треба поправити
10_how_device_works_UA.mdnRF9160 → nRF54L15 скрізь, додати BLE опис, додати XIAO board info
09_meeting_notes_cleaned.mdFrancisco згадував “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, найшвидші фікси які можна зробити в лабі:

  1. Додати #define FACTORY_RESET_NOTECARD_ON_BOOT 1 на один boot і перевірити чи битих templates немає → потім назад на 0.
  2. Змінити MODE_GPS з “continuous” на “periodic” — одразу ~400 mAh/день економія.
  3. Фікс Movistar APN — один рядок.
  4. Default values 14.1 → 0.0 — можливо пояснить “strange coordinates”.
  5. Збільшити GPS_TIME_INTERVAL_SECONDS з 120 до 600 — GPS fetch раз на 10 хв замість 2.
  6. Додати ADC averaging — зробити 16 samples перед усередненням.
  7. Поставити repo в Private і ротувати BLE tokens.