mirror of
https://github.com/meshtastic/firmware.git
synced 2026-11-07 06:44:33 -05:00
portduino: read environment telemetry from shell commands (Sensors:)
Add a Sensors: list to config.yaml. Each entry names an EnvironmentMetrics
field and a shell command that prints one number:
Sensors:
- Type: temperature
Command: cat /sys/bus/iio/devices/iio:device0/in_temp_input
Scale: 0.001
- CommandSensor plugs into EnvironmentTelemetryModule like the T1000x
sensor (no I2C device). Readings are cached for 30s, since getMetrics()
also runs for screen refreshes and phone requests. Output that is not
exactly one finite number is logged and the field is left unset.
Integer fields are rounded and clamped.
- Type matches the protobuf field name, case-insensitively. An unknown
Type or missing Command stops meshtasticd, as Security.AdminKeys does.
Entries append across config.d files.
- exec() now has a timeout: commands run in their own process group,
stdin is /dev/null, and the whole group is killed at the deadline, so
a hung script stalls the main loop for a bounded time. Command sensors
get 2s; HostMetrics and other exec() callers get 10s. Output is capped
at 4 KiB. Windows keeps popen() without a timeout.
- --check validates the section (type, command, scale, unknown keys,
duplicate types) and notes that sensors need environment measurement
enabled. Boot logs a warning when sensors are configured but
environment measurement is off. The section does not enable telemetry
itself, because enabling it also broadcasts to the mesh.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011C7D9dE4aUYBrGdpPyHs25
This commit is contained in:
19 files changed
+1019
-15
No files matched your search
@@ -0,0 +1,154 @@
|
||||
#include "CommandSensorFields.h"
|
||||
|
||||
#if ARCH_PORTDUINO
|
||||
|
||||
#include <cctype>
|
||||
#include <cerrno>
|
||||
#include <cmath>
|
||||
#include <cstdlib>
|
||||
#include <limits>
|
||||
|
||||
namespace CommandSensorFields
|
||||
{
|
||||
namespace
|
||||
{
|
||||
|
||||
using Setter = void (*)(meshtastic_EnvironmentMetrics &, float);
|
||||
|
||||
struct Field {
|
||||
const char *name;
|
||||
Setter set;
|
||||
};
|
||||
|
||||
template <typename T> T roundAndClamp(float value)
|
||||
{
|
||||
const double rounded = std::round(static_cast<double>(value));
|
||||
if (rounded <= static_cast<double>(std::numeric_limits<T>::min()))
|
||||
return std::numeric_limits<T>::min();
|
||||
if (rounded >= static_cast<double>(std::numeric_limits<T>::max()))
|
||||
return std::numeric_limits<T>::max();
|
||||
return static_cast<T>(rounded);
|
||||
}
|
||||
|
||||
#define FLOAT_FIELD(f) \
|
||||
{ \
|
||||
#f, [](meshtastic_EnvironmentMetrics &m, float v) { \
|
||||
m.f = v; \
|
||||
m.has_##f = true; \
|
||||
} \
|
||||
}
|
||||
#define INT_FIELD(f) \
|
||||
{ \
|
||||
#f, [](meshtastic_EnvironmentMetrics &m, float v) { \
|
||||
m.f = roundAndClamp<decltype(m.f)>(v); \
|
||||
m.has_##f = true; \
|
||||
} \
|
||||
}
|
||||
|
||||
const Field kFields[] = {
|
||||
FLOAT_FIELD(temperature),
|
||||
FLOAT_FIELD(relative_humidity),
|
||||
FLOAT_FIELD(barometric_pressure),
|
||||
FLOAT_FIELD(gas_resistance),
|
||||
FLOAT_FIELD(voltage),
|
||||
FLOAT_FIELD(current),
|
||||
INT_FIELD(iaq),
|
||||
FLOAT_FIELD(distance),
|
||||
FLOAT_FIELD(lux),
|
||||
FLOAT_FIELD(white_lux),
|
||||
FLOAT_FIELD(ir_lux),
|
||||
FLOAT_FIELD(uv_lux),
|
||||
INT_FIELD(wind_direction),
|
||||
FLOAT_FIELD(wind_speed),
|
||||
FLOAT_FIELD(weight),
|
||||
FLOAT_FIELD(wind_gust),
|
||||
FLOAT_FIELD(wind_lull),
|
||||
FLOAT_FIELD(radiation),
|
||||
FLOAT_FIELD(rainfall_1h),
|
||||
FLOAT_FIELD(rainfall_24h),
|
||||
INT_FIELD(soil_moisture),
|
||||
FLOAT_FIELD(soil_temperature),
|
||||
FLOAT_FIELD(adc_voltage_ch0),
|
||||
FLOAT_FIELD(adc_voltage_ch1),
|
||||
FLOAT_FIELD(adc_voltage_ch2),
|
||||
FLOAT_FIELD(adc_voltage_ch3),
|
||||
FLOAT_FIELD(adc_voltage_ch4),
|
||||
FLOAT_FIELD(adc_voltage_ch5),
|
||||
FLOAT_FIELD(adc_voltage_ch6),
|
||||
FLOAT_FIELD(adc_voltage_ch7),
|
||||
FLOAT_FIELD(one_wire_temperature_ch0),
|
||||
FLOAT_FIELD(one_wire_temperature_ch1),
|
||||
FLOAT_FIELD(one_wire_temperature_ch2),
|
||||
FLOAT_FIELD(one_wire_temperature_ch3),
|
||||
FLOAT_FIELD(one_wire_temperature_ch4),
|
||||
FLOAT_FIELD(one_wire_temperature_ch5),
|
||||
FLOAT_FIELD(one_wire_temperature_ch6),
|
||||
FLOAT_FIELD(one_wire_temperature_ch7),
|
||||
INT_FIELD(lightning_strike_count_1h),
|
||||
FLOAT_FIELD(lightning_distance_km),
|
||||
};
|
||||
|
||||
#undef FLOAT_FIELD
|
||||
#undef INT_FIELD
|
||||
|
||||
const Field *findField(const std::string &type)
|
||||
{
|
||||
std::string lower = type;
|
||||
for (auto &c : lower)
|
||||
c = static_cast<char>(std::tolower(static_cast<unsigned char>(c)));
|
||||
for (const auto &field : kFields)
|
||||
if (lower == field.name)
|
||||
return &field;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
std::string canonicalType(const std::string &type)
|
||||
{
|
||||
const Field *field = findField(type);
|
||||
return field ? field->name : "";
|
||||
}
|
||||
|
||||
std::string knownTypes()
|
||||
{
|
||||
std::string out;
|
||||
for (const auto &field : kFields)
|
||||
out += (out.empty() ? "" : ", ") + std::string(field.name);
|
||||
return out;
|
||||
}
|
||||
|
||||
bool apply(meshtastic_EnvironmentMetrics &metrics, const std::string &type, float value)
|
||||
{
|
||||
const Field *field = findField(type);
|
||||
if (!field)
|
||||
return false;
|
||||
field->set(metrics, value);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool parseValue(const std::string &output, float &value)
|
||||
{
|
||||
const char *start = output.c_str();
|
||||
while (*start && std::isspace(static_cast<unsigned char>(*start)))
|
||||
start++;
|
||||
if (!*start)
|
||||
return false;
|
||||
|
||||
char *end = nullptr;
|
||||
errno = 0;
|
||||
const float parsed = std::strtof(start, &end);
|
||||
if (end == start || errno == ERANGE || !std::isfinite(parsed))
|
||||
return false;
|
||||
while (*end && std::isspace(static_cast<unsigned char>(*end)))
|
||||
end++;
|
||||
if (*end)
|
||||
return false;
|
||||
|
||||
value = parsed;
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace CommandSensorFields
|
||||
|
||||
#endif
|
||||
Reference in new issue
Block a user