mirror of
https://github.com/esphome/esphome.git
synced 2024-12-25 06:54:52 +01:00
sensors
This commit is contained in:
parent
85324a828e
commit
9bfde3b3ab
4 changed files with 428 additions and 0 deletions
|
@ -186,6 +186,7 @@ async def to_code(config):
|
|||
cg.add(var.enable_cmux())
|
||||
|
||||
if config[CONF_ENABLE_GNSS]:
|
||||
cg.add_define("USE_MODEM_GNSS")
|
||||
cg.add(var.enable_gnss())
|
||||
|
||||
if config[CONF_DEBUG]:
|
||||
|
|
118
esphome/components/modem/sensor/__init__.py
Normal file
118
esphome/components/modem/sensor/__init__.py
Normal file
|
@ -0,0 +1,118 @@
|
|||
import esphome.codegen as cg
|
||||
from esphome.components import sensor
|
||||
from esphome.components.modem import (
|
||||
CONF_ENABLE_GNSS,
|
||||
CONF_MODEM,
|
||||
final_validate_platform,
|
||||
)
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import (
|
||||
CONF_ALTITUDE,
|
||||
CONF_ID,
|
||||
CONF_LATITUDE,
|
||||
CONF_LONGITUDE,
|
||||
DEVICE_CLASS_SIGNAL_STRENGTH,
|
||||
STATE_CLASS_MEASUREMENT,
|
||||
UNIT_DECIBEL,
|
||||
UNIT_DEGREES,
|
||||
UNIT_METER,
|
||||
UNIT_PERCENT,
|
||||
)
|
||||
import esphome.final_validate as fv
|
||||
|
||||
CODEOWNERS = ["@oarcher"]
|
||||
|
||||
AUTO_LOAD = []
|
||||
|
||||
DEPENDENCIES = ["modem"]
|
||||
|
||||
# MULTI_CONF = True
|
||||
IS_PLATFORM_COMPONENT = True
|
||||
|
||||
CONF_BER = "ber"
|
||||
CONF_RSSI = "rssi"
|
||||
|
||||
|
||||
modem_sensor_ns = cg.esphome_ns.namespace("modem_sensor")
|
||||
ModemSensorComponent = modem_sensor_ns.class_("ModemSensor", cg.PollingComponent)
|
||||
|
||||
|
||||
CONFIG_SCHEMA = cv.All(
|
||||
cv.Schema(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(ModemSensorComponent),
|
||||
cv.Optional(CONF_RSSI): sensor.sensor_schema(
|
||||
unit_of_measurement=UNIT_DECIBEL,
|
||||
accuracy_decimals=0,
|
||||
device_class=DEVICE_CLASS_SIGNAL_STRENGTH,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
),
|
||||
cv.Optional(CONF_BER): sensor.sensor_schema(
|
||||
unit_of_measurement=UNIT_PERCENT,
|
||||
accuracy_decimals=0,
|
||||
device_class=DEVICE_CLASS_SIGNAL_STRENGTH,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
),
|
||||
cv.Optional(CONF_LATITUDE): sensor.sensor_schema(
|
||||
unit_of_measurement=UNIT_DEGREES,
|
||||
accuracy_decimals=5,
|
||||
# device_class=DEVICE_CLASS_SIGNAL_STRENGTH,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
),
|
||||
cv.Optional(CONF_LONGITUDE): sensor.sensor_schema(
|
||||
unit_of_measurement=UNIT_DEGREES,
|
||||
accuracy_decimals=5,
|
||||
# device_class=DEVICE_CLASS_SIGNAL_STRENGTH,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
),
|
||||
cv.Optional(CONF_ALTITUDE): sensor.sensor_schema(
|
||||
unit_of_measurement=UNIT_METER,
|
||||
accuracy_decimals=1,
|
||||
# device_class=DEVICE_CLASS_SIGNAL_STRENGTH,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
),
|
||||
}
|
||||
).extend(cv.polling_component_schema("60s"))
|
||||
)
|
||||
|
||||
|
||||
def _final_validate_gnss(config):
|
||||
if (
|
||||
config.get(CONF_LATITUDE, None)
|
||||
or config.get(CONF_LONGITUDE, None)
|
||||
or config.get(CONF_ALTITUDE, None)
|
||||
):
|
||||
if modem_config := fv.full_config.get().get(CONF_MODEM, None):
|
||||
if not modem_config[CONF_ENABLE_GNSS]:
|
||||
raise cv.Invalid(
|
||||
f"Using GNSS sensors require '{CONF_ENABLE_GNSS}' to be 'true' in '{CONF_MODEM}'."
|
||||
)
|
||||
return config
|
||||
|
||||
|
||||
FINAL_VALIDATE_SCHEMA = cv.All(final_validate_platform, _final_validate_gnss)
|
||||
|
||||
|
||||
async def to_code(config):
|
||||
var = cg.new_Pvariable(config[CONF_ID])
|
||||
if rssi := config.get(CONF_RSSI, None):
|
||||
rssi_sensor = await sensor.new_sensor(rssi)
|
||||
cg.add(var.set_rssi_sensor(rssi_sensor))
|
||||
|
||||
if ber := config.get(CONF_BER, None):
|
||||
ber_sensor = await sensor.new_sensor(ber)
|
||||
cg.add(var.set_ber_sensor(ber_sensor))
|
||||
|
||||
if latitude := config.get(CONF_LATITUDE, None):
|
||||
latitude_sensor = await sensor.new_sensor(latitude)
|
||||
cg.add(var.set_latitude_sensor(latitude_sensor))
|
||||
|
||||
if longitude := config.get(CONF_LONGITUDE, None):
|
||||
longitude_sensor = await sensor.new_sensor(longitude)
|
||||
cg.add(var.set_longitude_sensor(longitude_sensor))
|
||||
|
||||
if altitude := config.get(CONF_ALTITUDE, None):
|
||||
altitude_sensor = await sensor.new_sensor(altitude)
|
||||
cg.add(var.set_altitude_sensor(altitude_sensor))
|
||||
|
||||
await cg.register_component(var, config)
|
248
esphome/components/modem/sensor/modem_sensor.cpp
Normal file
248
esphome/components/modem/sensor/modem_sensor.cpp
Normal file
|
@ -0,0 +1,248 @@
|
|||
#ifdef USE_ESP_IDF
|
||||
|
||||
#include "esphome/core/defines.h"
|
||||
|
||||
#ifdef USE_MODEM
|
||||
#ifdef USE_SENSOR
|
||||
|
||||
#include "modem_sensor.h"
|
||||
|
||||
#include "esphome/core/log.h"
|
||||
#include "esphome/core/application.h"
|
||||
|
||||
#include "esphome/components/modem/modem_component.h"
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <sstream>
|
||||
|
||||
#define ESPHL_ERROR_CHECK(err, message) \
|
||||
if ((err) != ESP_OK) { \
|
||||
ESP_LOGE(TAG, message ": (%d) %s", err, esp_err_to_name(err)); \
|
||||
this->mark_failed(); \
|
||||
return; \
|
||||
}
|
||||
|
||||
#define ESPMODEM_ERROR_CHECK(err, message) \
|
||||
if ((err) != command_result::OK) { \
|
||||
ESP_LOGE(TAG, message ": %s", command_result_to_string(err).c_str()); \
|
||||
}
|
||||
|
||||
namespace esphome {
|
||||
namespace modem_sensor {
|
||||
|
||||
using namespace esp_modem;
|
||||
|
||||
void ModemSensor::setup() { ESP_LOGI(TAG, "Setting up Modem Sensor..."); }
|
||||
|
||||
void ModemSensor::update() {
|
||||
ESP_LOGD(TAG, "Modem sensor update");
|
||||
if (modem::global_modem_component->dce && modem::global_modem_component->modem_ready()) {
|
||||
this->update_signal_sensors_();
|
||||
this->update_gnss_sensors_();
|
||||
}
|
||||
}
|
||||
|
||||
void ModemSensor::update_signal_sensors_() {
|
||||
float rssi = NAN;
|
||||
float ber = NAN;
|
||||
if (this->rssi_sensor_ || this->ber_sensor_) {
|
||||
int modem_rssi, modem_ber;
|
||||
if (modem::global_modem_component->dce && modem::global_modem_component->modem_ready()) {
|
||||
modem::global_modem_component->dce->get_signal_quality(modem_rssi, modem_ber);
|
||||
if (this->rssi_sensor_ && modem_rssi != 99) {
|
||||
rssi = -113 + (modem_rssi * 2);
|
||||
this->rssi_sensor_->publish_state(rssi);
|
||||
}
|
||||
if (this->ber_sensor_ && modem_ber != 99) {
|
||||
ber = 0.1f * (modem_ber * modem_ber);
|
||||
this->ber_sensor_->publish_state(ber);
|
||||
}
|
||||
ESP_LOGD(TAG, "Modem sensor rssi: %d", modem_rssi);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef USE_MODEM_GNSS
|
||||
std::map<std::string, std::string> get_gnssinfo_tokens(const std::string &gnss_info, const std::string &module) {
|
||||
// for 7670 (18 tokens):
|
||||
// +CGNSSINFO: 3,12,,04,00,48.6167297,N,4.5600739,W,060824,101218.00,75.7,0.000,234.10,2.52,1.88,1.68,08
|
||||
// for 7600 (16 tokens):
|
||||
// +CGNSSINFO: 2,04,03,00,4836.989133,N,00433.611595,W,060824,102247.0,-13.8,0.0,70.4,1.7,1.4,1.0
|
||||
|
||||
std::string data = gnss_info.substr(12);
|
||||
|
||||
std::map<std::string, std::string> gnss_data;
|
||||
|
||||
if (data.find(",,,,,,") != std::string::npos) {
|
||||
// no data recieved
|
||||
return gnss_data;
|
||||
}
|
||||
|
||||
std::vector<std::string> parts;
|
||||
char delimiter = ',';
|
||||
std::istringstream token_stream(data);
|
||||
std::string part;
|
||||
while (std::getline(token_stream, part, delimiter)) {
|
||||
parts.push_back(part);
|
||||
}
|
||||
|
||||
switch (parts.size()) {
|
||||
case 16:
|
||||
gnss_data["mode"] = parts[0];
|
||||
gnss_data["sat_used_count"] = parts[1];
|
||||
gnss_data["sat_view_count"] = parts[2]; // Satellites in view
|
||||
gnss_data["fix_status"] = parts[3];
|
||||
gnss_data["latitude"] = parts[4];
|
||||
gnss_data["lat_dir"] = parts[5];
|
||||
gnss_data["longitude"] = parts[6];
|
||||
gnss_data["lon_dir"] = parts[7];
|
||||
gnss_data["date"] = parts[8];
|
||||
gnss_data["time"] = parts[9];
|
||||
gnss_data["altitude"] = parts[10];
|
||||
gnss_data["speed"] = parts[11];
|
||||
gnss_data["cog"] = parts[12];
|
||||
gnss_data["hdop"] = parts[13];
|
||||
gnss_data["vdop"] = parts[14];
|
||||
gnss_data["pdop"] = parts[15];
|
||||
gnss_data["lon_lat_format"] = "DDMM.MM"; // decimal degrees, float minutes
|
||||
break;
|
||||
|
||||
case 18:
|
||||
gnss_data["mode"] = parts[0];
|
||||
gnss_data["sat_used_count"] = parts[1];
|
||||
gnss_data["fix_status"] = parts[2]; // Primary fix status
|
||||
gnss_data["sat_view_count"] = parts[3]; // Satellites in view
|
||||
gnss_data["fix_status_2"] = parts[4]; // Additional fix status if needed
|
||||
gnss_data["latitude"] = parts[5];
|
||||
gnss_data["lat_dir"] = parts[6];
|
||||
gnss_data["longitude"] = parts[7];
|
||||
gnss_data["lon_dir"] = parts[8];
|
||||
gnss_data["date"] = parts[9];
|
||||
gnss_data["time"] = parts[10];
|
||||
gnss_data["altitude"] = parts[11];
|
||||
gnss_data["speed"] = parts[12];
|
||||
gnss_data["cog"] = parts[13];
|
||||
gnss_data["hdop"] = parts[14];
|
||||
gnss_data["vdop"] = parts[15];
|
||||
gnss_data["pdop"] = parts[16];
|
||||
gnss_data["sat_view_count_2"] = parts[17]; // Additional satellites in view if needed
|
||||
gnss_data["lon_lat_format"] = "DD.DD"; // 48.34567 (decimal)
|
||||
break;
|
||||
|
||||
default:
|
||||
ESP_LOGE(TAG, "Unknown gnssinfo len %d", parts.size());
|
||||
break;
|
||||
}
|
||||
|
||||
for (const auto &pair : gnss_data) {
|
||||
ESP_LOGV(TAG, "GNSSINFO token %s: %s", pair.first.c_str(), pair.second.c_str());
|
||||
}
|
||||
|
||||
return gnss_data;
|
||||
}
|
||||
|
||||
void ModemSensor::update_gnss_sensors_() {
|
||||
if (this->gnss_latitude_sensor_ || this->gnss_longitude_sensor_ || this->gnss_altitude_sensor_) {
|
||||
std::string gnss_info = modem::global_modem_component->send_at("AT+CGNSSINFO");
|
||||
|
||||
if (gnss_info != "ERROR") {
|
||||
std::map<std::string, std::string> parts = get_gnssinfo_tokens(gnss_info, "SIM7600");
|
||||
|
||||
if (parts["latitude"].empty() || parts["lat_dir"].empty() || parts["longitude"].empty() ||
|
||||
parts["lon_dir"].empty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
float lat = NAN;
|
||||
float lon = NAN;
|
||||
|
||||
if (parts["lon_lat_format"] == "DDMM.MM") {
|
||||
float lat_deg = std::stof(parts["latitude"].substr(0, 2));
|
||||
float lat_min = std::stof(parts["latitude"].substr(2));
|
||||
lat = lat_deg + (lat_min / 60.0);
|
||||
if (parts["lat_dir"] == "S")
|
||||
lat = -lat;
|
||||
|
||||
float lon_deg = std::stof(parts["longitude"].substr(0, 3));
|
||||
float lon_min = std::stof(parts["longitude"].substr(3));
|
||||
lon = lon_deg + (lon_min / 60.0);
|
||||
if (parts["lon_dir"] == "W")
|
||||
lon = -lon;
|
||||
} else if (parts["lon_lat_format"] == "DD.DD") {
|
||||
lat = std::stof(parts["latitude"]);
|
||||
if (parts["lat_dir"] == "S")
|
||||
lat = -lat;
|
||||
|
||||
lon = std::stof(parts["longitude"]);
|
||||
if (parts["lon_dir"] == "W")
|
||||
lon = -lon;
|
||||
}
|
||||
|
||||
float alt = std::stof(parts["altitude"]);
|
||||
float speed_knots = std::stof(parts["speed"]);
|
||||
float speed_kmh = speed_knots * 1.852; // Convert speed from knots to km/h
|
||||
float cog = std::stof(parts["cog"]);
|
||||
float pdop = std::stof(parts["pdop"]);
|
||||
float hdop = std::stof(parts["hdop"]);
|
||||
float vdop = std::stof(parts["vdop"]);
|
||||
int mode = std::stoi(parts["mode"]);
|
||||
int gps_svs = std::stoi(parts["sat_used_count"]);
|
||||
int glonass_svs = std::stoi(parts["sat_view_count"]);
|
||||
int beidou_svs = parts["sat_view_count_2"].empty() ? 0 : std::stoi(parts["sat_view_count_2"]);
|
||||
|
||||
// Parsing date
|
||||
int day = std::stoi(parts["date"].substr(0, 2));
|
||||
int month = std::stoi(parts["date"].substr(2, 2));
|
||||
int year = std::stoi(parts["date"].substr(4, 2)) + 2000;
|
||||
|
||||
// Parsing time
|
||||
int hour = std::stoi(parts["time"].substr(0, 2));
|
||||
int minute = std::stoi(parts["time"].substr(2, 2));
|
||||
int second = std::stoi(parts["time"].substr(4, 2));
|
||||
|
||||
ESP_LOGD(TAG, "Latitude: %f, Longitude: %f", lat, lon);
|
||||
ESP_LOGD(TAG, "Altitude: %f m", alt);
|
||||
ESP_LOGD(TAG, "Speed: %f km/h", speed_kmh);
|
||||
ESP_LOGD(TAG, "COG: %f degrees", cog);
|
||||
ESP_LOGD(TAG, "PDOP: %f", pdop);
|
||||
ESP_LOGD(TAG, "HDOP: %f", hdop);
|
||||
ESP_LOGD(TAG, "VDOP: %f", vdop);
|
||||
ESP_LOGD(TAG, "GPS SVs: %d", gps_svs);
|
||||
ESP_LOGD(TAG, "GLONASS SVs: %d", glonass_svs);
|
||||
ESP_LOGD(TAG, "BEIDOU SVs: %d", beidou_svs);
|
||||
ESP_LOGD(TAG, "Fix mode: %d", mode);
|
||||
ESP_LOGD(TAG, "Date: %04d-%02d-%02d", year, month, day);
|
||||
ESP_LOGD(TAG, "Time: %02d:%02d:%02d", hour, minute, second);
|
||||
|
||||
// Sensors update
|
||||
if (this->gnss_latitude_sensor_)
|
||||
this->gnss_latitude_sensor_->publish_state(lat);
|
||||
if (this->gnss_longitude_sensor_)
|
||||
this->gnss_longitude_sensor_->publish_state(lon);
|
||||
if (this->gnss_altitude_sensor_)
|
||||
this->gnss_altitude_sensor_->publish_state(alt);
|
||||
if (this->gnss_speed_sensor_)
|
||||
this->gnss_speed_sensor_->publish_state(speed_kmh);
|
||||
if (this->gnss_cog_sensor_)
|
||||
this->gnss_cog_sensor_->publish_state(speed_kmh);
|
||||
if (this->gnss_pdop_sensor_)
|
||||
this->gnss_pdop_sensor_->publish_state(pdop);
|
||||
if (this->gnss_hdop_sensor_)
|
||||
this->gnss_hdop_sensor_->publish_state(hdop);
|
||||
if (this->gnss_vdop_sensor_)
|
||||
this->gnss_vdop_sensor_->publish_state(vdop);
|
||||
if (this->gnss_mode_sensor_)
|
||||
this->gnss_mode_sensor_->publish_state(mode);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif // USE_MODEM_GNSS
|
||||
|
||||
} // namespace modem_sensor
|
||||
} // namespace esphome
|
||||
|
||||
#endif // USE_MODEM
|
||||
#endif // USE_SENSOR
|
||||
#endif // USE_ESP_IDF
|
61
esphome/components/modem/sensor/modem_sensor.h
Normal file
61
esphome/components/modem/sensor/modem_sensor.h
Normal file
|
@ -0,0 +1,61 @@
|
|||
#pragma once
|
||||
#ifdef USE_ESP_IDF
|
||||
|
||||
#include "esphome/core/defines.h"
|
||||
|
||||
#ifdef USE_MODEM
|
||||
#ifdef USE_SENSOR
|
||||
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/components/modem/modem_component.h"
|
||||
#include "esphome/components/sensor/sensor.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace modem_sensor {
|
||||
|
||||
static const char *const TAG = "modem_sensor";
|
||||
|
||||
class ModemSensor : public PollingComponent {
|
||||
public:
|
||||
void set_rssi_sensor(sensor::Sensor *rssi_sensor) { this->rssi_sensor_ = rssi_sensor; }
|
||||
void set_ber_sensor(sensor::Sensor *ber_sensor) { this->ber_sensor_ = ber_sensor; }
|
||||
|
||||
#ifdef USE_MODEM_GNSS
|
||||
void set_latitude_sensor(sensor::Sensor *latitude_sensor) { this->gnss_latitude_sensor_ = latitude_sensor; }
|
||||
void set_longitude_sensor(sensor::Sensor *longitude_sensor) { this->gnss_longitude_sensor_ = longitude_sensor; }
|
||||
void set_altitude_sensor(sensor::Sensor *altitude_sensor) { this->gnss_altitude_sensor_ = altitude_sensor; }
|
||||
#endif // USE_MODEM_GNSS
|
||||
|
||||
// ========== INTERNAL METHODS ==========
|
||||
// (In most use cases you won't need these)
|
||||
|
||||
float get_setup_priority() const override { return setup_priority::AFTER_WIFI; }
|
||||
void setup() override;
|
||||
void update() override;
|
||||
void dump_config() override {}
|
||||
|
||||
protected:
|
||||
sensor::Sensor *rssi_sensor_{nullptr};
|
||||
sensor::Sensor *ber_sensor_{nullptr};
|
||||
void update_signal_sensors_();
|
||||
|
||||
#ifdef USE_MODEM_GNSS
|
||||
sensor::Sensor *gnss_latitude_sensor_{nullptr};
|
||||
sensor::Sensor *gnss_longitude_sensor_{nullptr};
|
||||
sensor::Sensor *gnss_altitude_sensor_{nullptr};
|
||||
sensor::Sensor *gnss_speed_sensor_{nullptr};
|
||||
sensor::Sensor *gnss_cog_sensor_{nullptr};
|
||||
sensor::Sensor *gnss_pdop_sensor_{nullptr};
|
||||
sensor::Sensor *gnss_hdop_sensor_{nullptr};
|
||||
sensor::Sensor *gnss_vdop_sensor_{nullptr};
|
||||
sensor::Sensor *gnss_mode_sensor_{nullptr};
|
||||
void update_gnss_sensors_();
|
||||
#endif // USE_MODEM_GNSS
|
||||
};
|
||||
|
||||
} // namespace modem_sensor
|
||||
} // namespace esphome
|
||||
|
||||
#endif // USE_MODEM
|
||||
#endif // USE_SENSOR
|
||||
#endif // USE_ESP_IDF
|
Loading…
Reference in a new issue