Newer
Older
smart-home-server / devices / sensor / bme280_sensor.h
#pragma once

#include <Arduino.h>
#include <Wire.h>
#include <Adafruit_BME280.h>
#include <Adafruit_BMP280.h>

/*
	Конфигурация датчика BME280 / BMP280.
*/
struct Bme280Config {
	uint8_t sda_pin = 18;
	uint8_t scl_pin = 19;
	uint8_t i2c_address = 0x76;

	/*
		Через сколько миллисекунд без успешного чтения
		данные считаются устаревшими.
	*/
	uint32_t stale_after_ms = 3000;

	/*
		Интервал чтения датчика.
	*/
	uint32_t read_interval_ms = 1000;

	/*
		Коэффициенты сглаживания EMA.
	*/
	float temperature_ema_alpha = 0.25f;
	float pressure_ema_alpha    = 0.20f;
	float humidity_ema_alpha    = 0.20f;

	/*
		Окно тренда в минутах для каждого параметра.
		Минимум 2, максимум TREND_MAX (10).
		Тренд вычисляется по линейной регрессии скользящего
		окна минутных снимков.
	*/
	uint8_t temperature_trend_window_min = 10;
	uint8_t pressure_trend_window_min    = 10;
	uint8_t humidity_trend_window_min    = 10;

	/*
		Порог наклона линейной регрессии для классификации динамики.
		Значения изменения меньше порога считаются "constant".
		  temperature: °C/мин
		  pressure:    hPa/мин
		  humidity:    %/мин
	*/
	float temperature_trend_threshold = 0.05f;  // °C/мин
	float pressure_trend_threshold    = 0.10f;  // hPa/мин
	float humidity_trend_threshold    = 0.10f;  // %/мин

	/*
		Порог стандартного отклонения для классификации "variable".
		Если slope ≈ 0, но разброс велик — динамика непостоянная.
	*/
	float temperature_variable_std = 0.3f;  // °C
	float pressure_variable_std    = 0.5f;  // hPa
	float humidity_variable_std    = 1.0f;  // %
};

enum BmeSensorType {
	BME_SENSOR_TYPE_UNKNOWN = 0,
	BME_SENSOR_TYPE_BMP280,
	BME_SENSOR_TYPE_BME280
};

class Bme280Sensor {
public:
	Bme280Sensor();
	~Bme280Sensor();

	/*
		Инициализация собственной I2C-шины и попытка
		подключения BME280/BMP280.
	*/
	bool begin(TwoWire &wire, const Bme280Config &config);

	/*
		Периодическое обновление состояния датчика.
	*/
	void update();

	/*
		Статус датчика.
	*/
	bool is_online()       const;
	bool has_valid_data()  const;
	bool is_stale()        const;

	/*
		Тип найденного датчика.
	*/
	BmeSensorType get_sensor_type()        const;
	String        get_sensor_type_string() const;

	/*
		Текущие значения (EMA-сглаженные).
	*/
	float get_temperature_c()     const;
	float get_pressure_hpa()      const;
	float get_humidity_percent()  const;

	/*
		Есть ли влажность.
		Для BMP280 всегда false.
	*/
	bool has_humidity() const;

	/*
		Динамика параметра:
		  "constant" | "increasing" | "decreasing" | "variable"
	*/
	const char* get_temperature_dynamics() const;
	const char* get_pressure_dynamics()    const;
	const char* get_humidity_dynamics()    const;

	/*
		Среднее изменение параметра в минуту за период тренда
		(наклон линейной регрессии, единицы параметра/мин).
	*/
	float get_temperature_dynamics_val() const;
	float get_pressure_dynamics_val()    const;
	float get_humidity_dynamics_val()    const;

	/*
		Три отдельных JSON-объекта состояния.
	*/
	String get_temperature_json() const;
	String get_pressure_json()    const;
	String get_humidity_json()    const;

	/*
		Единый JSON (обратная совместимость).
	*/
	String get_state_json() const;

private:
	Bme280Config _config;

	TwoWire          *_wire   = nullptr;
	Adafruit_BME280  *_bme280 = nullptr;
	Adafruit_BMP280  *_bmp280 = nullptr;

	BmeSensorType _sensor_type = BME_SENSOR_TYPE_UNKNOWN;

	bool     _initialized    = false;
	bool     _online         = false;
	bool     _has_valid_data = false;

	uint32_t _last_read_attempt_ms = 0;
	uint32_t _last_success_read_ms = 0;

	float _temperature_c      = 0.0f;
	float _pressure_hpa       = 0.0f;
	float _humidity_percent   = 0.0f;

	float _filtered_temperature_c     = 0.0f;
	float _filtered_pressure_hpa      = 0.0f;
	float _filtered_humidity_percent  = 0.0f;

private:
	/* ---- Инициализация чипов ---- */
	bool init_bme280();
	bool init_bmp280();

	/* ---- EMA-фильтры ---- */
	void update_filtered_temperature(float value);
	void update_filtered_pressure(float value);
	void update_filtered_humidity(float value);

	/* ====================================================
	   ТРЕНД (минутный, линейная регрессия)
	   По аналогии с Ld2420Radar / Max4466Mic.
	   ==================================================== */

	static constexpr uint8_t TREND_MAX = 10;

	/* -- температура -- */
	float    _temp_trend_buf[TREND_MAX] = {};
	uint8_t  _temp_trend_idx            = 0;
	uint8_t  _temp_trend_count          = 0;
	uint32_t _temp_trend_last_ms        = 0;
	float    _temp_trend_slope          = 0.0f;   // °C/мин (сырой наклон)
	float    _temp_trend_std_dev        = 0.0f;
	int8_t   _temp_trend_dir            = 0;       // -1 / 0 / +1

	/* -- давление -- */
	float    _pres_trend_buf[TREND_MAX] = {};
	uint8_t  _pres_trend_idx            = 0;
	uint8_t  _pres_trend_count          = 0;
	uint32_t _pres_trend_last_ms        = 0;
	float    _pres_trend_slope          = 0.0f;
	float    _pres_trend_std_dev        = 0.0f;
	int8_t   _pres_trend_dir            = 0;

	/* -- влажность -- */
	float    _humi_trend_buf[TREND_MAX] = {};
	uint8_t  _humi_trend_idx            = 0;
	uint8_t  _humi_trend_count          = 0;
	uint32_t _humi_trend_last_ms        = 0;
	float    _humi_trend_slope          = 0.0f;
	float    _humi_trend_std_dev        = 0.0f;
	int8_t   _humi_trend_dir            = 0;

	/* ---- Тики ---- */
	void _tick_temp_trend(uint32_t now_ms);
	void _tick_pres_trend(uint32_t now_ms);
	void _tick_humi_trend(uint32_t now_ms);

	/*
		Общая функция вычисления линейной регрессии.
		buf/win/count/idx — кольцевой буфер.
		out_slope    — наклон (единицы/мин).
		out_std_dev  — стандартное отклонение остатков.
		out_dir      — знак: -1 / 0 / +1 (с учётом порога threshold).
	*/
	static void _compute_trend(
		const float* buf, uint8_t win, uint8_t count, uint8_t idx,
		float threshold, float variable_std,
		float &out_slope, float &out_std_dev, int8_t &out_dir);

	/*
		Перевод dir + std в строку динамики.
	*/
	static const char* _dynamics_string(
		int8_t dir, float std_dev, float variable_std, uint8_t count);
};