Barcha loyihalarIshlamoqda

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'rsatkichQiymatTuriDalil
MongoDB 132 s o'chganda saqlangan xabarlar (DLQ 0)24 / 24O'lchanganDalilni ko'rish
Backend 20 s to'xtaganda yetib kelgan xabarlar7 / 7O'lchanganDalilni ko'rish
IoT deploy'dan keyingi 10 daqiqada saqlangan qatorlar (rad etilgan 0)320O'lchanganDalilni ko'rish
Rad etilgan xavfsizlik probe'lari5 / 5O'lchanganDalilni ko'rish
Yangi testlar (har biri eski kodda yiqilishi tekshirilgan)89KoddanDalilni ko'rish
Nasosning maksimal yoniq vaqti (server)30 minLoyiha shartiDalilni ko'rish
Eng uzun qayta urinish oralig'i (30 / 60 / 120 s, 3 tadan keyin DLQ)120 sLoyiha shartiDalilni ko'rish
MongoDB kolleksiyalari40KoddanDalilni 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

OldinDB outage 58 s → 11 / 12 saved · restart → 1 lostDalilni ko'rish
KeyinDB outage 132 s → 24 / 24 saved, DLQ 0 · restart → 7 / 7Dalilni ko'rish
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

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.