Smart Farm
Issiqxona uchun real-time IoT monitoring va avtomatik sug'orish: ESP32 sensor → MQTT → pipeline → qoida → nasos → yana qurilmaga yopiladigan kontur
Bu loyiha nimani isbotlaydi
- IoT ma'lumotlari yo'qolmasligini o'lchab isbotlash: MongoDB 132 s o'chganda 24/24, backend 20 s to'xtaganda 7/7; eski koddagi yo'qotish (12 dan 1, deploy'da 1) avval o'lchangan
- Xavfsizlik auditi va tuzatish: signup'da ADMIN bo'lish, ixtiyoriy maydon yozish, boshqa a'zo resursiga kirish, OTP'ni cheksiz taxmin qilish, kodga yozilgan JWT kaliti kabi 12+ zaiflik production'dagi suiiste'mol izlari tekshirilgach yopildi
- Qurilma va backend o'rtasidagi halqani yopish: RELAY_ON/OFF MQTT orqali yuboriladi, nasos vaqti server (30 daq) va firmware (1800 s) tomonida cheklangan, avtomatik qoida bir vaqtda ikki marta ishlamaydi
- Muhandislik intizomi: 89 ta yangi testning har biri eski kodda yiqilishi tekshirilgan; noto'g'ri gipoteza va yetarli bo'lmagan testlar ham hujjatlangan
- Rol
- Yakka: backend, frontend, ESP32 firmware, deploy va operatsiya (171 commit, 2026-10-04 dan production'da)
- Muddat
- 2026.01 – 2026.10
- Texnologiyalar
- NestJS
- TypeScript
- GraphQL
- MongoDB
- Redis
- BullMQ
- MQTT
- Socket.IO
- React
- Vite
- Leaflet
- ESP32
- MicroPython
- Jest
- Docker
- Caddy
Asosiy raqamlar
| Ko'rsatkich | Qiymat | Turi | Dalil |
|---|---|---|---|
| MongoDB 132 s o'chganda saqlangan xabarlar (DLQ 0) | 24 / 24 | O'lchangan | Dalilni ko'rish |
| Backend 20 s to'xtaganda yetib kelgan xabarlar | 7 / 7 | O'lchangan | Dalilni ko'rish |
| IoT deploy'dan keyingi 10 daqiqada saqlangan qatorlar (rad etilgan 0) | 320 | O'lchangan | Dalilni ko'rish |
| Rad etilgan xavfsizlik probe'lari | 5 / 5 | O'lchangan | Dalilni ko'rish |
| Yangi testlar (har biri eski kodda yiqilishi tekshirilgan) | 89 | Koddan | Dalilni ko'rish |
| Nasosning maksimal yoniq vaqti (server) | 30 min | Loyiha sharti | Dalilni ko'rish |
| Eng uzun qayta urinish oralig'i (30 / 60 / 120 s, 3 tadan keyin DLQ) | 120 s | Loyiha sharti | Dalilni ko'rish |
| MongoDB kolleksiyalari | 40 | Koddan | Dalilni ko'rish |
Hal qilinadigan muammo
Issiqxona sensor qiymatlari uzilib yoki sakrab turadi, qurilma vaqti noto'g'ri bo'lishi mumkin, DB yoki backend qisqa to'xtasa ham o'qishlar yo'qolmasligi kerak. Tuproq quriganda odam ekranni ko'rishidan oldin nasos yoqilishi, tarmoq uzilsa ham nasos to'xtamay suv toshib ketmasligi kerak. Boshqa a'zoning fermasiga kirib bo'lmasligi esa asosiy talab.
Arxitektura
Haqiqiy ESP32 (DHT22, capacitive tuproq namligi, BH1750 yorug'lik, suv sathi, rele + 12 V nasos) va production demo uchun simulyator bir xil yo'l bilan Mosquitto'ga (TLS 8883, QoS 1) yuboradi. NestJS 11 IoT pipeline qurilma API kaliti, payload shakli, topic ↔ deviceId mosligi, sensor egaligi va kalibrovkani tekshiradi, Redis bufer (qayta urinish, DLQ) orqali MongoDB'ga xom qiymat va 90 kunlik TTL time-series yozadi, soatlik, kunlik va oylik agregatlar quradi. Har sensor uchun Z-score anomaliya (≥ 2 warning, ≥ 3 critical), o'simlik salomatligi indeksi va Open-Meteo ob-havosi (10 daqiqalik kesh) hisoblanadi; avtomatlashtirish qoidasi aktuatorni o'zgartirsa, qurilmaga MQTT orqali RELAY_ON/OFF ketadi. Dashboard GraphQL (51 resolver) va Socket.IO orqali jonli yangilanadi, koreys, ingliz va o'zbek tillarida.
Ma'lumotlar modeli
Asosiy cheklovlar va indekslar
- 소유권 체인: sensor → device → greenHouse → farm → member (farms.memberId)
- deviceApiKeys.apiKey UNIQUE, deviceId UNIQUE
- sensor_data {sensorId:1, recordedAt:-1}, {recordedAt:-1}
- timeSeriesSensorData: MongoDB time series, granularity seconds, TTL 90일
- automationRules: findOneAndUpdate 필터의 cooldown 조건으로 원자적 점유
- passwordReset / emailVerifications.attempts: 최대 5회, crypto.randomInt 6자리
- Redis: iot:buffer:messages(hash), inflight / retry(zset), dead(list)
Asosiy loyihaviy qarorlar
Xabar buferi: Redis list o'rniga hash + ikki zset
- Vaziyat
- Eski bufer bitta list edi; ack butun listni lrange bilan o'qib, JSON solishtirib o'chirardi, qayta urinish esa umuman yo'q edi — xabar abadiy pending'da qolardi.
- Qaror
- iot:buffer:messages (hash), iot:buffer:inflight (zset, ishlov boshlangan vaqt), iot:buffer:retry (zset, keyingi urinish vaqti), iot:buffer:dead (DLQ); ack — id bo'yicha O(1) hdel + zrem.
- Evaziga
- Retry mantiqi o'zimizniki — shuning uchun 10 ta test va haqiqiy redis-server bilan tekshiruv.
Har bir o'qish uchun done[] ro'yxati
- Vaziyat
- Bir xabarda 8 ta o'qish bor; 7 tasi saqlanib 1 tasi yiqilsa, butun xabarni qayta yuborish 7 ta dublikat yaratadi.
- Qaror
- Promise.allSettled natijasidan muvaffaqiyatli sensorlar done[] ga yoziladi va qayta urinishda o'tkazib yuboriladi; qayta urinish asl receivedAt'ni saqlaydi.
- Evaziga
- —
Ruxsat xatosi va DB xatosini ajratish
- Vaziyat
- validateApiKey DB ishlamay qolganda ham xato tashlardi va kod buni "noto'g'ri kalit" deb xabarni tashlardi.
- Qaror
- Faqat UnauthorizedException tashlab yuboriladi, qolgan har qanday xato qayta urinishga tushadi.
- Evaziga
- —
MQTT doimiy sessiya + ulanish onApplicationBootstrap'da
- Vaziyat
- clean:true va har safar yangi clientId bilan broker backend o'chiq paytdagi xabarlarni saqlamasdi; onModuleInit'da ulanganda navbatdagi xabarlar handler ro'yxatdan o'tmasidan oldin kelib yo'qolardi.
- Qaror
- clean:false va barqaror clientId (ikkinchi instansga MQTT_CLIENT_ID orqali alohida id), ulanish barcha onModuleInit'lardan keyin — onApplicationBootstrap'da.
- Evaziga
- —
Xato logini yozish best-effort
- Vaziyat
- DB o'chiq paytida handleSensorSaveError ham DB'ga yozishga urinib yiqilardi va bitta urinish ikki marta sanalardi (DLQ 4 o'rniga 2 urinishdan keyin).
- Qaror
- Log yozish try/catch ichida, yiqilsa faqat warn — retry hisobiga ta'sir qilmaydi.
- Evaziga
- —
Egalik tekshiruvi: global interceptor + @OwnedArgs
- Vaziyat
- Har bir resolverga qo'lda tekshiruv yozilsa, yangisida unutib qoldirish oson.
- Qaror
- Global OwnershipInterceptor argument nomidan resursni topib, zanjir bo'ylab farm.memberId gacha chiqadi; ko'p ma'noli argumentlar uchun @OwnedArgs({ 'input.fieldId': 'fieldMap' }).
- Evaziga
- Har bir so'rovga qo'shimcha egalik tekshiruvi qo'shiladi.
Nasos cheklovi ikki tomonda
- Vaziyat
- Wi-Fi yoki broker uzilsa, RELAY_OFF qurilmaga yetmasligi mumkin — suv toshqinini faqat qurilma o'zi to'xtata oladi.
- Qaror
- Server autoOffAt'ni maksimal 30 daqiqaga cheklaydi; firmware ticks_ms asosida 1800 s + 60 s grace'dan keyin o'zi o'chiradi.
- Evaziga
- —
Avtomatik qoida: cooldown'ni atomik egallash
- Vaziyat
- Ikki sensor xabari bir vaqtda kelsa, ikkala ishlov ham qoidani bajarardi.
- Qaror
- findOneAndUpdate filtrida cooldown sharti bor — faqat bittasi qoidani egallaydi.
- Evaziga
- —
recordedAt uchun ehtiyot nusxa
- Vaziyat
- NTP sinxronlanmagan ESP32 1970-yil vaqtini yuborardi.
- Qaror
- Qurilma vaqti 24 soatdan eski yoki 5 daqiqadan kelajakda bo'lsa, server qabul vaqtidan foydalanadi, soniyadagi vaqt ham avtomatik aniqlanadi; firmware NTP'ni har soatda qayta urinadi.
- Evaziga
- —
Oldin va keyin
- Nima o'zgartirildi
- hash + zset bufer, 30/60/120 s backoff, DLQ va qotganlarni tiklash (e49f6d32), DB xatosini "noto'g'ri kalit"dan ajratish (e49f6d32), MQTT doimiy sessiya va onApplicationBootstrap'da ulanish (c4010d09), best-effort xato logi (9ddb7dbb). DB o'chganda handler xatolari 4 tadan 0 ga tushdi. O'lchov production'da sf-outage-check.sh va sf-restart-check.sh bilan, simulyator yuklamasida (~2 xabar / 30 s).
Nosozlik tarixi
- Belgi
- Signup mutation'iga memberRole: "ADMIN" yuborilsa, javob ADMIN a'zo qaytarardi (probe before).
- Sabab
- CreateMemberInput'da memberRole maydoni bor edi va memberModel.create(input) butun inputni yozardi; profil yangilashda MemberUpdateInput ham rol, status, email va parolni qabul qilardi. Shu auditda @Roles(MANAGER) bor, lekin @UseGuards(RolesGuard) yo'qligi sabab a'zolar ro'yxati tokensiz ochiq edi ({"total":7}), guard klass darajasidagi @Roles'ni o'qimagani uchun bufer resolverlari ham ochiq edi.
- Yechim
- Maydon inputdan olib tashlandi, create va $set faqat ruxsat etilgan maydonlar bilan (22a760bd); guard qo'shildi, getAllAndOverride bilan handler + klass o'qiladi, noto'g'ri token → 401.
- Takrorlanmasligi uchun
- Tuzatishdan oldin production'da suiiste'mol izlari tekshirildi (7 a'zo, hammasi WORKER/ACTIVE, imtiyozli 0, shubhali chaqiruv 0). member-security.spec.ts'dagi 13 testdan 12 tasi eski kodda yiqiladi; deploy'dan keyin 5 ta tashqi probe'ning hammasi rad etildi.
Test va sifat
- Testlar soni
- 89
- Qamrov
- —
- CI
- —
- Test vositalari
- Jest 30, ts-jest, 실제 redis-server, Mosquitto 2.0.18, MicroPython stub harness
Deploy va ekspluatatsiya
- Deploy muhiti
- Bitta VPS'da docker-compose.prod.yml: mongo (768m), redis (128m, requirepass, AOF), mosquitto (64m, tashqariga faqat 8883 TLS, Caddy sertifikati read-only, allow_anonymous false, password file + ACL, persistence), backend (768m, faqat internal tarmoq, mongo/redis healthy bo'lgach), web (64m), device-simulator (96m, demo profili). Umumiy Caddy HTTPS'ni boshqaradi. Deploy: git pull --ff-only → build backend → up -d backend. Sirlar (MONGO_PASSWORD, REDIS_PASSWORD, MQTT_BACKEND_PASSWORD, SECRET_TOKEN) faqat serverdagi .env'da; ular yo'q bo'lsa compose va backend ishga tushmaydi.
- Backup
- —
- Rollback
- —
- Monitoring
- Bufer holati (pending / retrying / deadLetter) admin uchun GraphQL orqali, DLQ xabarlarini ko'rish va tozalash, Message retry scheduled / Message moved to DLQ loglari. O'zim yozgan skriptlar: sf-incident-check.sh, sf-security-probe-v2.ps1, sf-outage-check.sh, sf-restart-check.sh
Skrinshotlar
Cheklovlar va xulosalar
Hozirgi cheklovlar
- Production'dagi oqim device-simulator'dan; haqiqiy ESP32 hozircha mahalliy brokerga (1883, TLS'siz) ulanadi, production uchun firmware'da TLS va alohida hisob kerak
- Barcha qurilmalar bitta smartfarm-device hisobidan foydalanadi (ACL sf/devices/#) — qurilma boshqa qurilma topic'iga yoza oladi; backend buni API kalit, topic–deviceId va sensor egaligi bilan ushlaydi, lekin to'g'ri yechim qurilma bo'yicha hisob va sf/devices/%u/# pattern
- Qurilma API kalitlari DB'da hash qilinmagan; repoga tushib qolgan bitta test kalitini almashtirish kerak
- Mosquitto Caddy sertifikatini ishga tushganda o'qiydi; yangilangach broker'ni qayta yuklash avtomatlashtirilmagan
- Login/signup uchun rate limit yo'q (faqat OTP cheklangan), GraphQL subscription'larda autentifikatsiya yo'q, social login'da mavjud email bilan bog'lash qoidasi aniq emas
- Bitta backend instansi: bufer retry zrem bilan atomik, lekin AI sug'orish taymeri va global rejim flagi xotirada — ikkinchi instansda ikki marta ishlashi mumkin
- Audit'da topilgan, hali tuzatilmagan: anomaliya statistikasi rolling emas, oylik agregatsiya, setSpeed, heartbeat'ning online/offline o'rtasida tebranishi
- CI yo'q, testlar qo'lda; account-context-module'dagi Nest CLI yaratgan 15 ta eski spec dependency'larsiz modul yaratgani uchun yiqiladi (tuzatishlar bilan bog'liq emas, alohida tozalanadi)
- Isbotdagi bo'shliqlar: ikki tuzatishda production "before" probe o'tkazib yuborildi, "oldin" holat faqat eski kodda yiqilgan test bilan isbotlangan; outage testlari simulyator yuklamasida (~2 xabar / 30 s), yuqori yuklamada bufer hajmi (10 000 limit) va retry batch (50) o'lchanmagan
Asosiy kod
backend/apps/smart-farm/src/iot/iot-pipeline/iot-pipeline.service.tsMQTT qabul, qurilma auth, buferlash, har o'qishni saqlash, anomaliya, qoida baholash
backend/apps/smart-farm/src/iot/iot-pipeline/sensor-payload.tsTopic'dan deviceId, envelope tekshiruvi, recordedAt oynasi
backend/apps/smart-farm/src/mid-iot/message-buffers/message-buffers.service.tsRedis bufer: enqueue/ack/markFailed, backoff, DLQ, qotganlarni tiklash
backend/apps/smart-farm/src/iot/mqtt/mqtt.service.tsDoimiy sessiya, onApplicationBootstrap'da ulanish
backend/apps/smart-farm/src/actuator/actuator.service.tsRele buyrug'i, nasos cheklovi, qoidani atomik egallash
backend/apps/smart-farm/src/ownership/ownership.interceptor.tsGlobal egalik tekshiruvi, argument → resurs xaritasi, @OwnedArgs
backend/apps/smart-farm/src/ownership/ownership.service.tsResurs → ota zanjiri farm.memberId gacha
backend/apps/smart-farm/src/account-context-module/auth/guards/roles.guard.tsHandler + klass rollari, 401/403
backend/apps/smart-farm/src/account-context-module/member/member.service.tsSignup va profil — faqat ruxsat etilgan maydonlar
backend/apps/smart-farm/src/account-context-module/password-reset/password-reset.service.tsOTP: randomInt, atomik urinish hisobi
firmware/esp32/main.pySensorlar, rele, nasos cheklovi, NTP, QoS 1 resubscribe
deploy/mosquitto/mosquitto.confTLS 8883, persistence, ACL
Hozirgi holat va keyingi qadamlar
2026-10-10–11 auditida 9 ta tuzatish commit'i chiqarildi, har biri oldin/keyin o'lchovi bilan. MongoDB 132 s o'chirilganda 24/24 xabar saqlandi (6 qayta urinish, DLQ 0); backend 20 s to'xtatilganda 7/7 yetib keldi, backend o'chiq paytida yuborilgani broker navbatidan keldi. Eski kodda 58 s outage'da 12 dan 1 tasi, deploy restartida 1 tasi yo'qolgan edi. Xavfsizlik tekshiruvida 12+ zaiflik topilib yopildi: signup'da memberRole bilan ADMIN bo'lish (22a760bd), profil orqali ixtiyoriy maydonlarni yozish, boshqa a'zoning fermasi, sektori va alertiga kirish (c2c5ca26), OTP'ni cheksiz taxmin qilish va Math.random OTP, kodga yozilgan JWT kaliti (a5a1b62e), production'da ochiq GraphQL playground. Tuzatishdan oldin production'da suiiste'mol izlari tekshirildi (7 a'zo, imtiyozli 0, shubhali chaqiruv 0). Backend nasosni faqat DB'da yoqib, qurilmaga buyruq yubormagani (3f3eacb2) va NTP sinxronlanmagan ESP32 1970-yil vaqtini yuborgani ham tuzatildi. Birinchi gipotezam (xatolar heartbeat'dan) noto'g'ri chiqqanda log haqiqiy sababni (/sensors) ko'rsatdi; 45 s outage driver'ning 30 s kutishi sabab xato yo'lini umuman ishga tushirmagani ham hujjatlandi.