Загрузка данных
#!/usr/bin/env python3
import time
import math
from smbus import SMBus
import board
import adafruit_bmp280
# ============================================================
# НАСТРОЙКИ
# ============================================================
BMP280_ADDRESS = 0x76
I2C_BUS = 1
# Сколько измерений АЦП усреднять
SAMPLES = 20
# Пауза между измерениями АЦП
SAMPLE_DELAY = 0.01
# Пауза после вывода результата
DISPLAY_DELAY = 2.0
# Как часто пытаться заново найти BMP280 после ошибки
BMP_RETRY_DELAY = 3.0
# Делитель напряжения
R1 = 51000.0
R2 = 10000.0
# Коэффициент расхода
K = 0.0004022 # 0.0004022 для 91 мм; 0.0003021 для 80 мм
# ============================================================
# ИНИЦИАЛИЗАЦИЯ I2C
# ============================================================
bus = None
bmp280 = None
bmp_ok = False
last_bmp_retry = 0.0
def init_bmp280(force=False):
"""
Инициализация BMP280.
Если датчик временно недоступен — программа НЕ завершается.
"""
global bmp280, bmp_ok, last_bmp_retry
now = time.monotonic()
if not force and (now - last_bmp_retry) < BMP_RETRY_DELAY:
return False
last_bmp_retry = now
try:
i2c = board.I2C()
sensor = adafruit_bmp280.Adafruit_BMP280_I2C(
i2c,
address=BMP280_ADDRESS
)
# Проверяем, что датчик реально отвечает
_ = sensor.pressure
_ = sensor.temperature
bmp280 = sensor
bmp_ok = True
print("\n[BMP280] Датчик найден, данные снова доступны.\n")
return True
except Exception as e:
bmp280 = None
bmp_ok = False
print(
"\n[BMP280] Ожидание данных, подождите..."
f"\n[BMP280] Причина: {e}\n"
)
return False
def read_bmp280():
"""
Чтение давления и температуры BMP280.
Возвращает (Patm, temperature) либо None при ошибке.
"""
global bmp_ok, bmp280
if not bmp_ok or bmp280 is None:
if not init_bmp280():
return None
try:
pressure = float(bmp280.pressure) * 100.0
temperature = float(bmp280.temperature)
# Проверяем явно невалидные значения
if not math.isfinite(pressure) or not math.isfinite(temperature):
raise ValueError(
f"Некорректные данные BMP280: P={pressure}, T={temperature}"
)
return pressure, temperature
except Exception as e:
bmp_ok = False
bmp280 = None
print(
"\n[BMP280] Данные временно недоступны."
"\n[BMP280] Ожидание данных, подождите..."
f"\n[BMP280] Ошибка: {e}\n"
)
# Сразу пытаемся восстановить датчик.
init_bmp280(force=True)
return None
def init_bus():
"""
Инициализация SMBus.
При проблеме возвращает False вместо падения программы.
"""
global bus
try:
bus = SMBus(I2C_BUS)
return True
except Exception as e:
bus = None
print(
"\n[I2C] Шина недоступна."
"\n[I2C] Ожидание данных, подождите..."
f"\n[I2C] Ошибка: {e}\n"
)
return False
# ============================================================
# ЧТЕНИЕ АЦП
# ============================================================
CHANNELS = {
0x48: {
"select": [0x41, 0x42, 0x43, 0x40],
"names": ["B1", "B2", "B3", "B4"],
},
0x49: {
"select": [0x41, 0x42, 0x43, 0x40],
"names": ["B5", "B6", "B7", "B8"],
},
0x4A: {
"select": [0x41, 0x42, 0x43, 0x40],
"names": ["B9", "B10", "B11", "B12"],
},
0x4B: {
"select": [0x41, 0x42, 0x43, 0x40],
"names": ["B13", "B14", "B15", "B0"],
},
0x4C: {
"select": [0x41, 0x42, 0x43, 0x40],
"names": ["B21", "B22", "B23", "B20"],
},
}
def read_adc_channels():
"""
Читает все используемые каналы.
Возвращает словарь B1...B15/B0/B21 либо None.
"""
global bus
if bus is None:
if not init_bus():
return None
result = {}
try:
for address, cfg in CHANNELS.items():
for select, name in zip(cfg["select"], cfg["names"]):
bus.write_byte(address, select)
result[name] = bus.read_byte(address)
return result
except Exception as e:
print(
"\n[I2C ADC] Ошибка чтения АЦП."
"\n[I2C ADC] Ожидание данных, подождите..."
f"\n[I2C ADC] Ошибка: {e}\n"
)
# SMBus иногда восстанавливается только после повторного открытия.
try:
bus.close()
except Exception:
pass
bus = None
return None
# ============================================================
# ВСПОМОГАТЕЛЬНЫЕ ФУНКЦИИ
# ============================================================
def clear_screen():
print("\033[2J\033[H", end="")
def safe_sqrt(value):
"""
Защита от sqrt() отрицательного значения из-за
временно некорректного сигнала датчика.
"""
if not math.isfinite(value) or value < 0:
return 0.0
return math.sqrt(value)
def average_to_flow(B, Patm, P, TK):
"""
Расчёт расходов q1...q12.
"""
u = R2 / (R2 + R1)
vout = {}
vin = {}
dp = {}
D = {}
G = {}
q = {}
for n in range(1, 13):
vout[n] = 3.3 * B[f"B{n}"] / 255.0
vin[n] = vout[n] / u
dp[n] = 500.0 * vin[n]
D[n] = dp[n] * P / TK
G[n] = K * safe_sqrt(D[n])
q[n] = int(G[n] * 3600.0 / 1.292)
return q, G
# ============================================================
# ОСНОВНАЯ ПРОГРАММА
# ============================================================
def main():
global bus
# Начальная инициализация.
init_bus()
init_bmp280(force=True)
sums = {
"B1": 0, "B2": 0, "B3": 0, "B4": 0,
"B5": 0, "B6": 0, "B7": 0, "B8": 0,
"B9": 0, "B10": 0, "B11": 0, "B12": 0,
"B13": 0, "B14": 0, "B15": 0,
"B0": 0, "B21": 0,
}
x = 0
while True:
try:
# ------------------------------------------------
# Считываем АЦП
# ------------------------------------------------
adc = read_adc_channels()
if adc is None:
# Не падаем и не продолжаем на старых данных.
x = 0
for key in sums:
sums[key] = 0
time.sleep(1.0)
continue
# ------------------------------------------------
# Накопление среднего
# ------------------------------------------------
for key in sums:
sums[key] += adc[key]
x += 1
if x < SAMPLES:
time.sleep(SAMPLE_DELAY)
continue
# ------------------------------------------------
# Получаем средние значения
# ------------------------------------------------
B = {
key: sums[key] / SAMPLES
for key in sums
}
# Сбрасываем накопитель сразу после формирования
# среднего, чтобы ошибка дальше не испортила следующий цикл.
x = 0
for key in sums:
sums[key] = 0
# ------------------------------------------------
# BMP280
# ------------------------------------------------
bmp_data = read_bmp280()
if bmp_data is None:
clear_screen()
print("\n")
print("================================================")
print(" ОЖИДАНИЕ ДАННЫХ BMP280")
print(" Подождите, идёт восстановление...")
print("================================================")
print("\nАЦП продолжает работать.")
print("Расчёт давления/расхода временно приостановлен.")
print("\n")
time.sleep(1.0)
continue
Patm, t = bmp_data
PatmHg = int(0.00750063 * Patm)
# ------------------------------------------------
# Расчёт параметров
# ------------------------------------------------
u = R2 / (R2 + R1)
# Давление перед холодной шайбой
vout0 = 3.3 * B["B0"] / 255.0
vin0 = vout0 / u
dp0 = 700.0 * vin0
# Давление формирующего коллектора
vout13 = 3.3 * B["B13"] / 255.0
vin13 = vout13 / u
Pst = 2500.0 * vin13
Pdv = Pst + Patm
# Температура перед холодной шайбой
vout21 = 3.3 * B["B21"] / 255.0
vin21 = vout21 / u
Tdv = int(15.0 * vin21 - 50.0)
TdvK = Tdv + 273
if TdvK <= 0:
print("\n[WARN] Некорректная Tdv. Ожидание следующего измерения...")
time.sleep(1.0)
continue
D0 = dp0 * Pdv / TdvK
G0 = 0.002038 * safe_sqrt(D0)
# Давление в форкамере
vout14 = 3.3 * B["B14"] / 255.0
vin14 = vout14 / u
Pfp = 2500.0 * vin14
P = int(Pfp + Patm)
PH2O = int(0.1 * Pfp)
Pfp = int(Pfp)
# Температура
vout15 = 3.3 * B["B15"] / 255.0
vin15 = vout15 / u
T = int(94.0 * vin15 - 40.0)
TK = T + 273
if TK <= 0 or P <= 0:
print("\n[WARN] Некорректные P/T. Ожидание следующего измерения...")
time.sleep(1.0)
continue
# ------------------------------------------------
# Расходы q1...q12
# ------------------------------------------------
q, G = average_to_flow(B, Patm, P, TK)
q1 = q[1]
q2 = q[2]
q3 = q[3]
q4 = q[4]
q5 = q[5]
q6 = q[6]
q7 = q[7]
q8 = q[8]
q9 = q[9]
q10 = q[10]
q11 = q[11]
q12 = q[12]
Sf = (
q1 + q2 + q3 + q4 + q5 + q6 +
q7 + q8 + q9 + q10 + q11 + q12
)
Q = int(G0 * 3600.0 / 1.292)
Q = Q + 1
AQ = Q - Sf
# Защита от деления на ноль
if Q != 0:
DQ = int(100 * AQ / Q)
else:
DQ = 0
# ------------------------------------------------
# Вывод
# ------------------------------------------------
clear_screen()
print(
" "
f"Pfp:{Pfp:0.0f} Pa"
" = "
f"{PH2O} mmH2O"
f" T={T}*C"
)
print(
" "
f"Sf = {Sf} nm3/h"
f" Tdv={Tdv}*C"
)
print()
print(
" q1 q2 q3 q4 q5 q6 "
"q7 q8 q9 q10 q11 q12"
)
print()
print(
f" {q1:4d} {q2:4d} {q3:4d} {q4:4d} "
f"{q5:4d} {q6:4d} {q7:4d} {q8:4d} "
f"{q9:4d} {q10:4d} {q11:4d} {q12:4d}"
)
print()
print(
f" Q = {Q} nm3/h"
f" AQ = {AQ} nm3/h"
f" DQ = {DQ}%"
f" Patm:{PatmHg}mmHg"
f" t:{t:0.0f}*C"
)
print()
print(" BMP280: OK")
print()
time.sleep(DISPLAY_DELAY)
except KeyboardInterrupt:
print("\nПрограмма остановлена пользователем.")
break
except Exception as e:
# Самая важная защита:
# любая неожиданная ошибка больше НЕ убивает программу.
print("\n================================================")
print(" НЕОЖИДАННАЯ ОШИБКА — ПРОГРАММА ПРОДОЛЖИТ РАБОТУ")
print("================================================")
print(f"Ошибка: {type(e).__name__}: {e}")
print("Ожидание данных, подождите...")
print("")
# Начинаем новое окно усреднения,
# чтобы старые данные не смешивались с новыми.
x = 0
for key in sums:
sums[key] = 0
time.sleep(2.0)
# Корректное закрытие SMBus
try:
if bus is not None:
bus.close()
except Exception:
pass
if __name__ == "__main__":
main()