#include "bme280_sensor.h"
#include <math.h>
Bme280Sensor::Bme280Sensor() {
}
Bme280Sensor::~Bme280Sensor() {
if (_bme280 != nullptr) {
delete _bme280;
_bme280 = nullptr;
}
if (_bmp280 != nullptr) {
delete _bmp280;
_bmp280 = nullptr;
}
}
bool Bme280Sensor::begin(TwoWire &wire, const Bme280Config &config) {
_config = config;
_wire = &wire;
/* Зажим параметров тренда */
if (_config.temperature_trend_window_min < 2) _config.temperature_trend_window_min = 2;
if (_config.temperature_trend_window_min > TREND_MAX) _config.temperature_trend_window_min = TREND_MAX;
if (_config.pressure_trend_window_min < 2) _config.pressure_trend_window_min = 2;
if (_config.pressure_trend_window_min > TREND_MAX) _config.pressure_trend_window_min = TREND_MAX;
if (_config.humidity_trend_window_min < 2) _config.humidity_trend_window_min = 2;
if (_config.humidity_trend_window_min > TREND_MAX) _config.humidity_trend_window_min = TREND_MAX;
_wire->begin(_config.sda_pin, _config.scl_pin, 400000);
/* На случай повторной инициализации */
if (_bme280 != nullptr) { delete _bme280; _bme280 = nullptr; }
if (_bmp280 != nullptr) { delete _bmp280; _bmp280 = nullptr; }
_sensor_type = BME_SENSOR_TYPE_UNKNOWN;
_initialized = false;
_online = false;
_has_valid_data = false;
_last_read_attempt_ms = 0;
_last_success_read_ms = 0;
_temperature_c = 0.0f;
_pressure_hpa = 0.0f;
_humidity_percent = 0.0f;
_filtered_temperature_c = 0.0f;
_filtered_pressure_hpa = 0.0f;
_filtered_humidity_percent = 0.0f;
/* Сброс трендов */
_temp_trend_count = 0; _temp_trend_idx = 0; _temp_trend_last_ms = 0;
_pres_trend_count = 0; _pres_trend_idx = 0; _pres_trend_last_ms = 0;
_humi_trend_count = 0; _humi_trend_idx = 0; _humi_trend_last_ms = 0;
_temp_trend_slope = 0.0f; _temp_trend_std_dev = 0.0f; _temp_trend_dir = 0;
_pres_trend_slope = 0.0f; _pres_trend_std_dev = 0.0f; _pres_trend_dir = 0;
_humi_trend_slope = 0.0f; _humi_trend_std_dev = 0.0f; _humi_trend_dir = 0;
/* Сначала пробуем BME280 */
if (init_bme280()) {
_sensor_type = BME_SENSOR_TYPE_BME280;
_initialized = true;
_online = true;
return true;
}
/* Если не вышло — пробуем BMP280 */
if (init_bmp280()) {
_sensor_type = BME_SENSOR_TYPE_BMP280;
_initialized = true;
_online = true;
return true;
}
_sensor_type = BME_SENSOR_TYPE_UNKNOWN;
return false;
}
/* ====================================================
UPDATE
==================================================== */
void Bme280Sensor::update() {
if (!_initialized) return;
uint32_t now_ms = millis();
if (now_ms - _last_read_attempt_ms < _config.read_interval_ms) return;
_last_read_attempt_ms = now_ms;
float temperature = 0.0f;
float pressure = 0.0f;
float humidity = 0.0f;
if (_sensor_type == BME_SENSOR_TYPE_BME280) {
if (_bme280 == nullptr) { _online = false; return; }
temperature = _bme280->readTemperature();
pressure = _bme280->readPressure() / 100.0f;
humidity = _bme280->readHumidity();
if (isnan(temperature) || isnan(pressure) || isnan(humidity)) {
_online = false;
return;
}
_temperature_c = temperature;
_pressure_hpa = pressure;
_humidity_percent = humidity;
update_filtered_temperature(temperature);
update_filtered_pressure(pressure);
update_filtered_humidity(humidity);
} else if (_sensor_type == BME_SENSOR_TYPE_BMP280) {
if (_bmp280 == nullptr) { _online = false; return; }
temperature = _bmp280->readTemperature();
pressure = _bmp280->readPressure() / 100.0f;
if (isnan(temperature) || isnan(pressure)) {
_online = false;
return;
}
_temperature_c = temperature;
_pressure_hpa = pressure;
_humidity_percent = 0.0f;
update_filtered_temperature(temperature);
update_filtered_pressure(pressure);
}
_online = true;
_has_valid_data = true;
_last_success_read_ms = now_ms;
/* Обновляем тренды */
_tick_temp_trend(now_ms);
_tick_pres_trend(now_ms);
if (_sensor_type == BME_SENSOR_TYPE_BME280) {
_tick_humi_trend(now_ms);
}
}
/* ====================================================
СТАТУС
==================================================== */
bool Bme280Sensor::is_online() const { return _online; }
bool Bme280Sensor::has_valid_data() const { return _has_valid_data; }
bool Bme280Sensor::is_stale() const {
if (!_has_valid_data) return true;
return (millis() - _last_success_read_ms) > _config.stale_after_ms;
}
BmeSensorType Bme280Sensor::get_sensor_type() const { return _sensor_type; }
String Bme280Sensor::get_sensor_type_string() const {
if (_sensor_type == BME_SENSOR_TYPE_BME280) return "bme280";
if (_sensor_type == BME_SENSOR_TYPE_BMP280) return "bmp280";
return "unknown";
}
float Bme280Sensor::get_temperature_c() const { return _filtered_temperature_c; }
float Bme280Sensor::get_pressure_hpa() const { return _filtered_pressure_hpa; }
float Bme280Sensor::get_humidity_percent() const { return _filtered_humidity_percent; }
bool Bme280Sensor::has_humidity() const { return _sensor_type == BME_SENSOR_TYPE_BME280; }
/* ====================================================
ДИНАМИКА — публичные геттеры
==================================================== */
const char* Bme280Sensor::get_temperature_dynamics() const {
return _dynamics_string(_temp_trend_dir, _temp_trend_std_dev,
_config.temperature_variable_std, _temp_trend_count);
}
const char* Bme280Sensor::get_pressure_dynamics() const {
return _dynamics_string(_pres_trend_dir, _pres_trend_std_dev,
_config.pressure_variable_std, _pres_trend_count);
}
const char* Bme280Sensor::get_humidity_dynamics() const {
return _dynamics_string(_humi_trend_dir, _humi_trend_std_dev,
_config.humidity_variable_std, _humi_trend_count);
}
float Bme280Sensor::get_temperature_dynamics_val() const { return _temp_trend_slope; }
float Bme280Sensor::get_pressure_dynamics_val() const { return _pres_trend_slope; }
float Bme280Sensor::get_humidity_dynamics_val() const { return _humi_trend_slope; }
/* ====================================================
JSON
==================================================== */
String Bme280Sensor::get_temperature_json() const {
String json = "{";
json += "\"online\":" + String(_online ? "true" : "false") + ",";
json += "\"current\":" + String(_filtered_temperature_c, 2) + ",";
json += "\"dynamics\":\"" + String(get_temperature_dynamics()) + "\",";
json += "\"dynamics_val\":" + String(get_temperature_dynamics_val(), 2);
json += "}";
return json;
}
String Bme280Sensor::get_pressure_json() const {
String json = "{";
json += "\"online\":" + String(_online ? "true" : "false") + ",";
json += "\"current\":" + String(_filtered_pressure_hpa, 2) + ",";
json += "\"dynamics\":\"" + String(get_pressure_dynamics()) + "\",";
json += "\"dynamics_val\":" + String(get_pressure_dynamics_val(), 2);
json += "}";
return json;
}
String Bme280Sensor::get_humidity_json() const {
String json = "{";
json += "\"online\":" + String(_online ? "true" : "false") + ",";
json += "\"current\":" + String(_filtered_humidity_percent, 2) + ",";
json += "\"dynamics\":\"" + String(get_humidity_dynamics()) + "\",";
json += "\"dynamics_val\":" + String(get_humidity_dynamics_val(), 2);
json += "}";
return json;
}
String Bme280Sensor::get_state_json() const {
String json = "{";
json += "\"sensor\":\"" + get_sensor_type_string() + "\",";
json += "\"online\":" + String(_online ? "true" : "false") + ",";
json += "\"has_valid_data\":" + String(_has_valid_data ? "true" : "false") + ",";
json += "\"stale\":" + String(is_stale() ? "true" : "false") + ",";
json += "\"i2c_address\":\"0x" + String(_config.i2c_address, HEX) + "\",";
json += "\"temperature\":" + get_temperature_json() + ",";
json += "\"pressure\":" + get_pressure_json();
if (has_humidity()) {
json += ",\"humidity\":" + get_humidity_json();
}
json += "}";
return json;
}
/* ====================================================
ИНИЦИАЛИЗАЦИЯ ЧИПОВ
==================================================== */
bool Bme280Sensor::init_bme280() {
if (_wire == nullptr) return false;
_bme280 = new Adafruit_BME280();
if (_bme280 == nullptr) return false;
bool ok = _bme280->begin(_config.i2c_address, _wire);
if (!ok) {
delete _bme280;
_bme280 = nullptr;
return false;
}
_bme280->setSampling(
Adafruit_BME280::MODE_NORMAL,
Adafruit_BME280::SAMPLING_X2,
Adafruit_BME280::SAMPLING_X16,
Adafruit_BME280::SAMPLING_X1,
Adafruit_BME280::FILTER_X4,
Adafruit_BME280::STANDBY_MS_500
);
return true;
}
bool Bme280Sensor::init_bmp280() {
if (_wire == nullptr) return false;
_bmp280 = new Adafruit_BMP280(_wire);
if (_bmp280 == nullptr) return false;
bool ok = _bmp280->begin(_config.i2c_address);
if (!ok) {
delete _bmp280;
_bmp280 = nullptr;
return false;
}
_bmp280->setSampling(
Adafruit_BMP280::MODE_NORMAL,
Adafruit_BMP280::SAMPLING_X2,
Adafruit_BMP280::SAMPLING_X16,
Adafruit_BMP280::FILTER_X4,
Adafruit_BMP280::STANDBY_MS_500
);
return true;
}
/* ====================================================
EMA-ФИЛЬТРЫ
==================================================== */
void Bme280Sensor::update_filtered_temperature(float value) {
if (!_has_valid_data || _filtered_temperature_c == 0.0f) {
_filtered_temperature_c = value;
return;
}
_filtered_temperature_c =
(_filtered_temperature_c * (1.0f - _config.temperature_ema_alpha)) +
(value * _config.temperature_ema_alpha);
}
void Bme280Sensor::update_filtered_pressure(float value) {
if (!_has_valid_data || _filtered_pressure_hpa == 0.0f) {
_filtered_pressure_hpa = value;
return;
}
_filtered_pressure_hpa =
(_filtered_pressure_hpa * (1.0f - _config.pressure_ema_alpha)) +
(value * _config.pressure_ema_alpha);
}
void Bme280Sensor::update_filtered_humidity(float value) {
if (!_has_valid_data || _filtered_humidity_percent == 0.0f) {
_filtered_humidity_percent = value;
return;
}
_filtered_humidity_percent =
(_filtered_humidity_percent * (1.0f - _config.humidity_ema_alpha)) +
(value * _config.humidity_ema_alpha);
}
/* ====================================================
ТИКИ ТРЕНДА (каждую минуту)
==================================================== */
void Bme280Sensor::_tick_temp_trend(uint32_t now_ms) {
if (_temp_trend_last_ms == 0) { _temp_trend_last_ms = now_ms; return; }
if (now_ms - _temp_trend_last_ms < 60000u) return;
_temp_trend_last_ms = now_ms;
uint8_t win = _config.temperature_trend_window_min;
_temp_trend_buf[_temp_trend_idx] = _filtered_temperature_c;
_temp_trend_idx = (_temp_trend_idx + 1) % win;
if (_temp_trend_count < win) _temp_trend_count++;
_compute_trend(
_temp_trend_buf, win, _temp_trend_count, _temp_trend_idx,
_config.temperature_trend_threshold, _config.temperature_variable_std,
_temp_trend_slope, _temp_trend_std_dev, _temp_trend_dir);
}
void Bme280Sensor::_tick_pres_trend(uint32_t now_ms) {
if (_pres_trend_last_ms == 0) { _pres_trend_last_ms = now_ms; return; }
if (now_ms - _pres_trend_last_ms < 60000u) return;
_pres_trend_last_ms = now_ms;
uint8_t win = _config.pressure_trend_window_min;
_pres_trend_buf[_pres_trend_idx] = _filtered_pressure_hpa;
_pres_trend_idx = (_pres_trend_idx + 1) % win;
if (_pres_trend_count < win) _pres_trend_count++;
_compute_trend(
_pres_trend_buf, win, _pres_trend_count, _pres_trend_idx,
_config.pressure_trend_threshold, _config.pressure_variable_std,
_pres_trend_slope, _pres_trend_std_dev, _pres_trend_dir);
}
void Bme280Sensor::_tick_humi_trend(uint32_t now_ms) {
if (_humi_trend_last_ms == 0) { _humi_trend_last_ms = now_ms; return; }
if (now_ms - _humi_trend_last_ms < 60000u) return;
_humi_trend_last_ms = now_ms;
uint8_t win = _config.humidity_trend_window_min;
_humi_trend_buf[_humi_trend_idx] = _filtered_humidity_percent;
_humi_trend_idx = (_humi_trend_idx + 1) % win;
if (_humi_trend_count < win) _humi_trend_count++;
_compute_trend(
_humi_trend_buf, win, _humi_trend_count, _humi_trend_idx,
_config.humidity_trend_threshold, _config.humidity_variable_std,
_humi_trend_slope, _humi_trend_std_dev, _humi_trend_dir);
}
/* ====================================================
ЛИНЕЙНАЯ РЕГРЕССИЯ (статическая)
==================================================== */
void Bme280Sensor::_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)
{
if (count < 2) {
out_slope = 0.0f;
out_std_dev = 0.0f;
out_dir = 0;
return;
}
uint8_t n = count;
float sx = 0.0f, sy = 0.0f, sxx = 0.0f, sxy = 0.0f, mean_y = 0.0f;
for (uint8_t i = 0; i < n; i++) {
/*
Элементы в кольцевом буфере идут от самого старого к новому.
idx указывает на следующую позицию записи, т.е. самый старый
элемент находится по смещению (idx - count + win) % win.
*/
uint8_t bi = (uint8_t)((idx - n + i + win) % win);
float x = (float)i;
float y = buf[bi];
sx += x;
sy += y;
sxx += x * x;
sxy += x * y;
mean_y += y;
}
float fn = (float)n;
float denom = fn * sxx - sx * sx;
if (fabsf(denom) < 1e-9f) {
out_slope = 0.0f;
out_dir = 0;
} else {
float slope = (fn * sxy - sx * sy) / denom;
out_slope = slope;
out_dir = (slope > threshold) ? 1 :
(slope < -threshold) ? -1 : 0;
}
/* Стандартное отклонение от среднего */
mean_y /= fn;
float var = 0.0f;
for (uint8_t i = 0; i < n; i++) {
uint8_t bi = (uint8_t)((idx - n + i + win) % win);
float d = buf[bi] - mean_y;
var += d * d;
}
out_std_dev = sqrtf(var / fn);
}
/* ====================================================
СТРОКА ДИНАМИКИ (статическая)
==================================================== */
const char* Bme280Sensor::_dynamics_string(
int8_t dir, float std_dev, float variable_std, uint8_t count)
{
if (count < 2) return "constant";
if (dir == 0 && std_dev > variable_std) return "variable";
if (dir > 0) return "increasing";
if (dir < 0) return "decreasing";
return "constant";
}