EKO-NAZORAT — Integratsiya API
O'zbekiston Respublikasi Ekologiya va iqlim o'zgarishi milliy qo'mitasi

Uskunani integratsiya qilish — API

Korxonalarning fon monitoring va sanoat manbalari gaz-analizatori uskunalarini EKO-NAZORAT tizimiga ulash bo'yicha texnik qo'llanma. Integratsiya ikki bosqichdan iborat: uskunani sertifikatlash va API orqali ma'lumotlarni uzatish.

1 Uskunani sertifikatlashtirish

Integratsiyadan oldin o'lchov uskunasi (gaz-analizator / fon monitoring stansiyasi) O'zbekiston Respublikasi Ekologiya va iqlim o'zgarishi milliy qo'mitasi huzuridagi "Davlat ekologik sertifikatlashtirish va standartlashtirish markazi" tomonidan sertifikatlanishi shart. Sertifikat uskunaning metrologik yaroqliligini va o'lchovlar haqiqiyligini tasdiqlaydi.

Huquqiy asoslar

Quyidagi normativ-huquqiy hujjatlar talablari ijrosini ta'minlash maqsadida sanoat korxonalarining atrof-muhitga salbiy ta'sirini kamaytirish va aholining xabardorligini oshirish uchun ustuvor manbalarga o'rnatilgan avtomatik monitoring stansiyalari Ekologiya vazirligining Situatsion markaziga integratsiya qilinadi.

Qonunlar

Prezident farmonlari va hukumat qarorlari

Standartlar

Javobgarlik to'g'risida ogohlantirish Yuqoridagi majburiyatlarni bajarmagan xo'jalik subyektlariga nisbatan atrof-muhitni ifloslantirganlik uchun kompensatsiya to'lovlari 5 baravarga oshirilgan holda qo'llaniladi. Ekologiya organining qonuniy ko'rsatmalarini bajarmaslik esa MJtKning 1976-moddasi asosida chora ko'rishga hamda korxona faoliyatini to'xtatishga olib kelishi mumkin.
Eslatma Hujjatlarning to'liq va amaldagi matnini rasmiy manbadan (lex.uz) tasdiqlab oling — raqam va bandlar yangilanishi mumkin.

Natija

  • Uskunaga ekologik sertifikat beriladi (raqam va amal muddati bilan).
  • Ushbu sertifikat 2-bosqichdagi arizaga biriktiriladi.

2 API orqali integratsiya (PULL)

EKO-NAZORAT tizimi ma'lumotni o'zi tortib oladi (PULL). Korxona o'z tarafida ochiq JSON API endpointini taqdim etadi; qo'mita serveri unga davriy (odatda har 15 daqiqada) murojaat qilib, oxirgi o'lchovlarni o'qiydi va tizimga saqlaydi.

  1. Korxona ma'lumotlarni API spetsifikatsiyasi bo'yicha JSON qaytaradigan endpoint tayyorlaydi.
  2. Endpoint manzili va kirish ma'lumotlari center@uznature.uz ga yuboriladi — ariza formasi orqali.
  3. Qo'mita administratori endpointni tekshirib, tizimga Fetcher sifatida ulaydi.
  4. Ulangach, uskuna avtomatik ravishda xaritalar, hisobotlar va nazoratda ko'rinadi.
Muhim Serial (manba raqami) barcha korxonalar bo'ylab unikal bo'lishi kerak. Shu sababli JSON'da manbaraqami — takrorlanmaydigan davlat manba raqamini — berish tavsiya etiladi. Aks holda faqat stansiya nomi ishlatiladi va boshqa korxona bilan to'qnashishi mumkin.
Situatsion markazga ulash (VPN) Avtomatik monitoring stansiyasi hamda sanitar himoya zonasidagi avtomat statsionar kuzatuv postlarini Ekologiya vazirligining Situatsion markaziga to'g'ridan-to'g'ri ulash uchun, himoyalangan VPN tarmog'i orqali uskunaning IP-manzili, login va paroli, hamda ulanish qo'llanmasi (instruksiya) taqdim etiladi. Bularni quyidagi ariza formasi orqali yuboring — vazirlik mutaxassislari amaliy yordam ko'rsatishga tayyor.

So'rov (Request)

Qo'mita serveri korxona endpointiga quyidagicha murojaat qiladi:

GET https://korxona-domeni-yoki-api-manzili/api/device-data
ParametrQiymat
MetodGET
Javob turiapplication/jsonmassiv (array) bo'lishi shart
Timeout20 soniya
Autentifikatsiya *MajburiyBasic Auth yoki Bearer token
Davriylik~15 daqiqada bir marta

Autentifikatsiya (majburiy)

Endpoint himoyalangan bo'lishi shart — ochiq (autentifikatsiyasiz) manzillar qabul qilinmaydi. Quyidagi ikki usuldan birini qo'llang:

HTTP
# Basic Auth
Authorization: Basic base64(login:parol)

# yoki Bearer token
Authorization: Bearer <token>

Javob strukturasi

Endpoint stansiyalar massivini qaytaradi. Har bir element — bitta manba (stansiya):

MaydonTuriIzoh
manbaraqamistringTavsiya — unikal manba raqami (serial). Bo'lmasa name ishlatiladi.
name *stringStansiya / manba nomi
organization_namestringKorxona (tashkilot) nomi
tashkilot_innstringTashkilot STIR/INN (9 raqam)
addressstringManzil
latitude / longitudenumberKoordinatalar
stansiyastringStansiya turi (CEMS — sanoat manbasi, AQMS — fon)
trubadiametristringQuvur diametri (masalan "500mm")
timestamp *stringO'lchov vaqti, ISO 8601 (2026-07-28T11:00:00+05:00)
devices[].data[] *arrayO'lchovlar: har biri {"parameter":"...","value":...,"unit":"..."}
data[].parameter *stringModda kaliti (masalan so2)
data[].value *numberO'lchangan qiymat
data[].unitstringO'lchov birligi (masalan mg/m³, %, °C)
data[].namestringModdaning to'liq nomi — jadvalda yo'q (nostandart) moddalar uchun tavsiya etiladi

* — majburiy maydonlar.

Parametrlar — PDV asosida

Korxona o'zining PDV (ruxsat etilgan chiqindilar normativi) hujjatidan kelib chiqib, manbada o'lchanadigan barcha ifloslantiruvchi moddalarni yuboradi. Havoga chiqindilar juda ko'p turda bo'lishi mumkin — parametrlar ro'yxati sizning PDV'ingiz bilan belgilanadi, qat'iy chegaralanmaydi.

Har bir modda alohida { "parameter": "<kalit>", "value": <son>, "unit": "<birlik>" } juftligi sifatida beriladi — har bir o'lchovda uning birligi (unit) ham ko'rsatiladi. Qiymatlar mg/m³ da kutiladi (PM uchun µg/m³ ham sozlanishi mumkin — arizada belgilang).

Standart parametr kalitlari

Keng tarqalgan moddalar uchun quyidagi standart parameter kalitlaridan foydalaning:

pm25PM2.5 chang zarrasi · mg/m³
pm10PM10 chang zarrasi · mg/m³
dustUmumiy chang · mg/m³
so2Oltingugurt dioksidi (SO₂) · mg/m³
noAzot oksidi (NO) · mg/m³
no2Azot dioksidi (NO₂) · mg/m³
coUglerod oksidi (CO) · mg/m³
co2Uglerod dioksidi (CO₂) · %
o2Kislorod (O₂) · %
o3Ozon (O₃) · mg/m³
nh3Ammiak (NH₃) · mg/m³
h2sVodorod sulfidi (H₂S) · mg/m³
hfVodorod ftoridi (HF) · mg/m³
temperatureHarorat · °C
humidityNamlik · %
pressureAtmosfera bosimi · hPa
wind_speedShamol tezligi · m/s
wind_directionShamol yo'nalishi · °
gas_flowGaz sarfi · m³/soat
gas_speedGaz tezligi · m/s
diff_pressureBosim farqi · Pa
Jadvalda yo'q moddani qanday berish kerak Agar PDV'ingizda yuqoridagi ro'yxatda yo'q modda bo'lsa (masalan benzol, fenol, formaldegid, qo'rg'oshin, uglevodorodlar, marganets, xrom va boshqalar), uni quyidagi qoida bo'yicha yuboring:
  • parameter — modda nomining lotincha, kichik harfli, bo'shliqsiz kaliti (masalan benzol, fenol, formaldegid);
  • value — o'lchangan qiymat, unit — birligi (majburiy);
  • name — moddaning to'liq nomi (ixtiyoriy, aniqlik uchun);
  • shu moddalar ro'yxatini arizada ham ko'rsating — qo'mita mutaxassislari kalitni tizimga moslaydi (mapping).

JSON namuna

Endpoint qaytaradigan to'liq javob namunasi:

JSON · javob
[
  {
    "manbaraqami": "TSH-0102-001",
    "name": "1-quvur (tutun mo'risi)",
    "organization_name": "\"Angren IES\" AJ",
    "tashkilot_inn": "301234567",
    "address": "Toshkent viloyati, Angren sh.",
    "stansiya": "CEMS",
    "trubadiametri": "1200mm",
    "latitude": 41.0167,
    "longitude": 70.1436,
    "timestamp": "2026-07-28T11:00:00+05:00",
    "devices": [
      {
        "data": [
          { "parameter": "so2",         "value": 0.42,   "unit": "mg/m³" },
          { "parameter": "no2",         "value": 0.18,   "unit": "mg/m³" },
          { "parameter": "co",          "value": 3.10,   "unit": "mg/m³" },
          { "parameter": "dust",        "value": 0.05,   "unit": "mg/m³" },
          { "parameter": "o2",          "value": 6.5,    "unit": "%" },
          { "parameter": "temperature", "value": 145.0,  "unit": "°C" },
          { "parameter": "gas_flow",    "value": 12000,  "unit": "m³/soat" },

          { "parameter": "benzol",      "value": 0.002,  "unit": "mg/m³", "name": "Benzol" },
          { "parameter": "fenol",       "value": 0.001,  "unit": "mg/m³", "name": "Fenol" }
        ]
      }
    ]
  }
]
Yuqoridagi namunada oxirgi ikki modda (benzol, fenol) — standart jadvalda yo'q moddalarga misol: ular ham xuddi shu tarzda parameter + value + unit (va ixtiyoriy name) bilan yuboriladi. Bir endpoint bir nechta manbani (massivda bir nechta element) qaytarishi mumkin. Har chaqiruvda oxirgi (joriy) o'lchovni bering — tizim timestamp bo'yicha takrorlarni o'zi filtrlaydi.

Namunaviy server (Python)

Quyida tayyor namunaviy (reference) dastur keltirilgan. U COM (serial) port orqali gaz-analizatordan ma'lumot o'qiydi va yuqoridagi formatda GET /api/device-data endpointini host qilib turadi. Autentifikatsiya majburiy (Basic/Bearer), uskuna ulanmagan bo'lsa — simulyatsiya rejimi ishlaydi. Kodni o'z uskunangiz protokoliga moslang.

1. O'rnatish

bash
pip install flask pyserial

2. Sozlash

Dasturdagi SOZLAMALAR bo'limini tahrirlang: autentifikatsiya (login/parol yoki token), manba metadatasi (manbaraqami, INN, koordinatalar), COM port (COM_PORT, masalan Windows: "COM3", Linux: "/dev/ttyUSB0") va PARAMETERS ro'yxati.

3. Ishga tushirish

bash
python eco_integration_server.py

4. Sinash

bash
# autentifikatsiyasiz — 401
curl -i http://127.0.0.1:8080/api/device-data

# to'g'ri Basic Auth — 200 + JSON
curl -u ekonazorat:StrongParol123! http://127.0.0.1:8080/api/device-data
Xavfsizlik Parol/tokenni albatta o'zgartiring. Ishlab chiqarishda endpointni HTTPS (TLS) yoki himoyalangan VPN ortidan ishlating va faqat Situatsion markaz IP-manzillari uchun ruxsat bering.

To'liq kod — eco_integration_server.py

Namunaviy server kodini ko'rsatish (Python)
Python
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
EKO-NAZORAT — Integratsiya namunaviy serveri (REFERENCE / EXAMPLE)
=================================================================

Ushbu dastur korxona tomonida ishlaydi:

  1) COM (serial) port orqali gaz-analizator / monitoring stansiyasidan
     o'lchov ma'lumotlarini uzluksiz o'qiydi;
  2) o'qilgan oxirgi qiymatlarni EKO-NAZORAT hujjatidagi JSON formatida
     `GET /api/device-data` endpointida host qilib turadi;
  3) endpoint autentifikatsiya bilan himoyalangan (Basic Auth yoki Bearer).

Ekologiya vazirligining Situatsion markazi shu endpointdan (VPN/HTTPS orqali)
ma'lumotni davriy tortib oladi.

Bu — NAMUNA. `SOZLAMALAR` bo'limini va `parse_serial_line()` funksiyasini
o'z uskunangiz protokoliga moslang.

Talablar:  Python 3.8+,  pip install flask pyserial
Ishga tushirish:  python eco_integration_server.py
"""

import base64
import json
import random
import threading
import time
from datetime import datetime, timezone, timedelta
from functools import wraps

from flask import Flask, Response, jsonify, request

try:
    import serial  # pyserial
except ImportError:
    serial = None  # COM port bo'lmasa — simulyatsiya rejimi ishlaydi


# ═══════════════════════════════════════════════════════════════════════
#  SOZLAMALAR  —  shu bo'limni o'zingizga moslang
# ═══════════════════════════════════════════════════════════════════════

# ── Server ──────────────────────────────────────────────────────────────
HOST = "0.0.0.0"     # barcha interfeyslar (VPN/tarmoq orqali kirish uchun)
PORT = 8080

# ── Autentifikatsiya (MAJBURIY) ─────────────────────────────────────────
# "basic"  → login/parol;   "bearer" → token
AUTH_TYPE = "basic"
BASIC_USER = "ekonazorat"
BASIC_PASS = "StrongParol123!"          # ← albatta o'zgartiring
BEARER_TOKEN = "almashtiring-uzun-tasodifiy-token"

# ── Vaqt mintaqasi (Toshkent = UTC+5) ───────────────────────────────────
TASHKENT_TZ = timezone(timedelta(hours=5))

# ── Manba (stansiya) metadatasi ─────────────────────────────────────────
STATION = {
    "manbaraqami":       "TSH-0102-001",          # unikal davlat manba raqami
    "name":              "1-quvur (tutun mo'risi)",
    "organization_name": '"Korxona" MChJ',
    "tashkilot_inn":     "301234567",
    "address":           "Toshkent viloyati, Angren sh.",
    "stansiya":          "CEMS",                   # CEMS=sanoat manbasi, AQMS=fon
    "trubadiametri":     "1200mm",
    "latitude":          41.0167,
    "longitude":         70.1436,
}

# ── COM (serial) port sozlamalari ───────────────────────────────────────
# COM_PORT = None  bo'lsa yoki port ochilmasa → SIMULYATSIYA rejimi yoqiladi
# Windows:  "COM3"      Linux:  "/dev/ttyUSB0"
COM_PORT = None
BAUDRATE = 9600
SERIAL_TIMEOUT = 2  # soniya

# ── Parametrlar xaritasi ────────────────────────────────────────────────
# (standart_kalit, birlik, analizator_liniyasidagi_xom_nom, toliq_nom)
# - Standart kalitlar uchun `name` ni bo'sh qoldiring.
# - Jadvalda YO'Q (nostandart) moddalar uchun `name` ni to'ldiring
#   (masalan benzol, fenol) — qo'mita mapping'ni shunga qarab sozlaydi.
PARAMETERS = [
    # kalit          birlik      xom nom     to'liq nom (nostandart uchun)
    ("so2",          "mg/m³",    "SO2",      ""),
    ("no2",          "mg/m³",    "NO2",      ""),
    ("co",           "mg/m³",    "CO",       ""),
    ("dust",         "mg/m³",    "DUST",     ""),
    ("o2",           "%",        "O2",       ""),
    ("temperature",  "°C",       "T",        ""),
    ("gas_flow",     "m³/soat",  "FLOW",     ""),
    # ── nostandart modda misoli (PDV'dan) ──
    ("benzol",       "mg/m³",    "BENZOL",   "Benzol"),
    ("fenol",        "mg/m³",    "FENOL",    "Fenol"),
]


# ═══════════════════════════════════════════════════════════════════════
#  Oxirgi o'lchov — thread-safe ombor
# ═══════════════════════════════════════════════════════════════════════

_latest_lock = threading.Lock()
_latest_values = {}          # {xom_nom: float}
_latest_ts = None            # datetime (o'lchov vaqti)


def _set_latest(values: dict):
    global _latest_values, _latest_ts
    with _latest_lock:
        _latest_values = dict(values)
        _latest_ts = datetime.now(TASHKENT_TZ)


def _get_latest():
    with _latest_lock:
        return dict(_latest_values), _latest_ts


# ═══════════════════════════════════════════════════════════════════════
#  COM port o'quvchi  —  fon oqimda uzluksiz ishlaydi
# ═══════════════════════════════════════════════════════════════════════

def parse_serial_line(line: str) -> dict:
    """
    Analizatordan kelgan bitta matn qatorini {xom_nom: float} ga aylantiradi.

    NAMUNA format (o'zingiznikiga moslang):
        SO2:0.42;NO2:0.18;CO:3.10;DUST:0.05;O2:6.5;T:145.0;FLOW:12000

    Ko'p analizatorlar Modbus RTU (RS-485) ishlatadi — u holda `pymodbus`
    kutubxonasidan foydalanib registrlarni o'qing va shu dict'ni qaytaring.
    """
    result = {}
    line = line.strip()
    if not line:
        return result
    for token in line.replace(",", ";").split(";"):
        if ":" not in token:
            continue
        raw_name, raw_val = token.split(":", 1)
        raw_name = raw_name.strip().upper()
        try:
            result[raw_name] = float(raw_val.strip().replace(",", "."))
        except ValueError:
            continue
    return result


def serial_reader_loop():
    """COM portni ochib, uzluksiz o'qiydi. Port bo'lmasa — simulyatsiya."""
    if serial is None or not COM_PORT:
        print("⚠  COM port sozlanmagan yoki pyserial yo'q — SIMULYATSIYA rejimi.")
        _simulation_loop()
        return

    while True:
        try:
            with serial.Serial(COM_PORT, BAUDRATE, timeout=SERIAL_TIMEOUT) as ser:
                print(f"✅ COM port ochildi: {COM_PORT} @ {BAUDRATE}")
                while True:
                    raw = ser.readline().decode("ascii", errors="ignore")
                    values = parse_serial_line(raw)
                    if values:
                        _set_latest(values)
        except Exception as e:
            print(f"🟥 COM port xatosi: {e} — 5s dan keyin qayta urinaman...")
            time.sleep(5)


def _simulation_loop():
    """Uskuna ulanmagan holda API'ni sinash uchun tasodifiy qiymatlar."""
    base = {
        "SO2": 0.40, "NO2": 0.18, "CO": 3.0, "DUST": 0.05, "O2": 6.5,
        "T": 145.0, "FLOW": 12000, "BENZOL": 0.002, "FENOL": 0.001,
    }
    while True:
        values = {k: round(v * random.uniform(0.85, 1.15), 4) for k, v in base.items()}
        _set_latest(values)
        time.sleep(5)


# ═══════════════════════════════════════════════════════════════════════
#  Autentifikatsiya
# ═══════════════════════════════════════════════════════════════════════

def _check_auth() -> bool:
    hdr = request.headers.get("Authorization", "")
    if AUTH_TYPE == "basic":
        if not hdr.startswith("Basic "):
            return False
        try:
            decoded = base64.b64decode(hdr[6:]).decode("utf-8")
            user, _, pwd = decoded.partition(":")
        except Exception:
            return False
        return user == BASIC_USER and pwd == BASIC_PASS
    elif AUTH_TYPE == "bearer":
        return hdr == f"Bearer {BEARER_TOKEN}"
    return False


def require_auth(fn):
    @wraps(fn)
    def wrapper(*args, **kwargs):
        if not _check_auth():
            resp = Response(
                json.dumps({"error": "Autentifikatsiya talab qilinadi"}),
                status=401, mimetype="application/json",
            )
            if AUTH_TYPE == "basic":
                resp.headers["WWW-Authenticate"] = 'Basic realm="eko-nazorat"'
            return resp
        return fn(*args, **kwargs)
    return wrapper


# ═══════════════════════════════════════════════════════════════════════
#  JSON javobni yig'ish
# ═══════════════════════════════════════════════════════════════════════

def build_payload():
    values, ts = _get_latest()
    if ts is None:
        ts = datetime.now(TASHKENT_TZ)

    data = []
    for key, unit, raw_name, full_name in PARAMETERS:
        if raw_name not in values:
            continue
        item = {"parameter": key, "value": values[raw_name], "unit": unit}
        if full_name:                       # nostandart modda — to'liq nom
            item["name"] = full_name
        data.append(item)

    station = dict(STATION)
    station["timestamp"] = ts.isoformat()
    station["devices"] = [{"data": data}]
    return [station]                        # hujjat bo'yicha — massiv (array)


# ═══════════════════════════════════════════════════════════════════════
#  Flask ilova
# ═══════════════════════════════════════════════════════════════════════

app = Flask(__name__)


@app.route("/api/device-data", methods=["GET"])
@require_auth
def device_data():
    payload = build_payload()
    return Response(
        json.dumps(payload, ensure_ascii=False),
        mimetype="application/json",
    )


@app.route("/health", methods=["GET"])
def health():
    _, ts = _get_latest()
    return jsonify({
        "status": "ok",
        "last_reading": ts.isoformat() if ts else None,
        "mode": "serial" if (serial and COM_PORT) else "simulation",
    })


def main():
    # COM port o'quvchini fon oqimda ishga tushiramiz
    t = threading.Thread(target=serial_reader_loop, daemon=True)
    t.start()

    print("─" * 60)
    print(f"  EKO-NAZORAT integratsiya serveri")
    print(f"  Endpoint : http://{HOST}:{PORT}/api/device-data")
    print(f"  Auth     : {AUTH_TYPE}")
    print(f"  Manba    : {STATION['manbaraqami']} — {STATION['name']}")
    print("─" * 60)

    # DIQQAT: ishlab chiqarishda HTTPS (TLS) yoki VPN ortidan ishlating.
    app.run(host=HOST, port=PORT, threaded=True)


if __name__ == "__main__":
    main()

Ariza yuborish

Maydonlarni to'ldiring va "Arizani yuborish" tugmasini bosing. Ma'lumotlar tayyor xat ko'rinishida center@uznature.uz ga yuborish uchun e-pochta ilovangizda ochiladi.

Qo'mita shu manzildan ma'lumot tortadi (HTTP GET, JSON) yoki VPN orqali IP-manzilga ulanadi.
Basic uchun login, Bearer uchun token.
Manbada o'lchanadigan barcha moddalarni PDV hujjatidan ko'chirib yozing.

Yuborilganda xat center@uznature.uz uchun tayyorlanadi. Agar e-pochta ilovasi ochilmasa — ma'lumotlarni qo'lda shu manzilga yuboring.