--- /dev/null
+/*#############################################################################
+# #
+# telemetryd - The IPFire Telemetry Collection Service #
+# Copyright (C) 2025 IPFire Development Team #
+# #
+# This program is free software: you can redistribute it and/or modify #
+# it under the terms of the GNU General Public License as published by #
+# the Free Software Foundation, either version 3 of the License, or #
+# (at your option) any later version. #
+# #
+# This program is distributed in the hope that it will be useful, #
+# but WITHOUT ANY WARRANTY; without even the implied warranty of #
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
+# GNU General Public License for more details. #
+# #
+# You should have received a copy of the GNU General Public License #
+# along with this program. If not, see <http://www.gnu.org/licenses/>. #
+# #
+#############################################################################*/
+
+#include <ctype.h>
+#include <errno.h>
+
+#include <sensors/error.h>
+#include <sensors/sensors.h>
+
+#include "../ctx.h"
+#include "../metrics.h"
+#include "../source.h"
+#include "../string.h"
+#include "sensors.h"
+
+#define sensors_make_name(name, chip, label) \
+ __sensors_make_name(name, sizeof(name), chip, label)
+
+static int __sensors_make_name(char* name, size_t length, const char* chip, const char* label) {
+ int r;
+
+ // Copy everything info the buffer
+ r = __td_string_format(name, length, "%s-%s", chip, label);
+ if (r < 0)
+ return r;
+
+ // Sanitize the name
+ for (char* p = name; *p; p++) {
+ // Convert everything to lower-case
+ if (isupper(*p)) {
+ *p = tolower(*p);
+ continue;
+
+ // Replace whitespace with dashes
+ } else if (isspace(*p)) {
+ *p = '-';
+ continue;
+ }
+
+ // Replace a couple of special characters, too
+ switch (*p) {
+ case '/':
+ case ':':
+ *p = '-';
+ break;
+
+ default:
+ break;
+ }
+ }
+
+ return 0;
+}
+
+typedef int (*find_sensors_callback)(td_ctx* ctx, td_source* source,
+ const sensors_chip_name* chip, const sensors_feature* feature, td_metrics* metrics);
+
+static int find_sensors(td_ctx* ctx, td_source* source,
+ sensors_feature_type type, find_sensors_callback callback) {
+ const sensors_feature* feature = NULL;
+ const sensors_chip_name* chip = NULL;
+ td_metrics* metrics = NULL;
+ const char* label = NULL;
+ char chip_name[NAME_MAX];
+ char name[NAME_MAX];
+ int feature_index = 0;
+ int chip_index = 0;
+ int r;
+
+ // Initialize sensors
+ r = sensors_init(NULL);
+ if (r) {
+ ERROR(ctx, "Failed to initialize sensors: %s\n", sensors_strerror(r));
+ return -ENOTSUP;
+ }
+
+ // Iterate over all chips
+ for (;;) {
+ chip = sensors_get_detected_chips(NULL, &chip_index);
+ if (!chip)
+ break;
+
+ // Make the chip name
+ r = sensors_snprintf_chip_name(chip_name, sizeof(chip_name), chip);
+ if (r < 0)
+ goto ERROR;
+
+ // Reset the feature index
+ feature_index = 0;
+
+ // Iterate over all features
+ for (;;) {
+ feature = sensors_get_features(chip, &feature_index);
+ if (!feature)
+ break;
+
+ // Skip anything that does not match
+ if (feature->type != type)
+ continue;
+
+ // Fetch the label of the chip
+ label = sensors_get_label(chip, feature);
+
+ // Format the whole name
+ r = sensors_make_name(name, chip_name, label);
+ if (r < 0)
+ goto ERROR;
+
+ // Create metrics
+ r = td_source_create_metrics(source, &metrics, name);
+ if (r < 0)
+ goto ERROR;
+
+ // Call the callback
+ r = callback(ctx, source, chip, feature, metrics);
+ if (r < 0)
+ goto ERROR;
+
+ // Submit the metrics
+ r = td_source_submit_metrics(source, metrics);
+ if (r < 0)
+ goto ERROR;
+
+ // Free the metrics
+ if (metrics) {
+ td_metrics_unref(metrics);
+ metrics = NULL;
+ }
+ }
+ }
+
+ERROR:
+ if (metrics)
+ td_metrics_unref(metrics);
+ sensors_cleanup();
+
+ return r;
+}
+
+typedef struct sensors_value {
+ // Field
+ const char* field;
+
+ // Type
+ sensors_subfeature_type type;
+} sensors_value;
+
+static int read_sensors(td_ctx* ctx, const sensors_chip_name* chip,
+ const sensors_feature* feature, td_metrics* metrics, sensors_value* values) {
+ const sensors_subfeature* subfeature = NULL;
+ double value = 0;
+ int index = 0;
+ int r;
+
+ // Iterate over all subfeatures
+ for (;;) {
+ subfeature = sensors_get_all_subfeatures(chip, feature, &index);
+ if (!subfeature)
+ break;
+
+ // Search for any matching values and read them
+ for (sensors_value* v = values; v->field; v++) {
+ if (subfeature->type == v->type) {
+ // Read the value
+ r = sensors_get_value(chip, subfeature->number, &value);
+ if (r < 0)
+ return r;
+
+ // Convert some temperature values from Celsius to Kelvin
+ switch (v->type) {
+ case SENSORS_SUBFEATURE_TEMP_INPUT:
+ case SENSORS_SUBFEATURE_TEMP_MAX:
+ case SENSORS_SUBFEATURE_TEMP_MAX_HYST:
+ case SENSORS_SUBFEATURE_TEMP_MIN:
+ case SENSORS_SUBFEATURE_TEMP_CRIT:
+ case SENSORS_SUBFEATURE_TEMP_CRIT_HYST:
+ case SENSORS_SUBFEATURE_TEMP_LCRIT:
+ case SENSORS_SUBFEATURE_TEMP_EMERGENCY:
+ case SENSORS_SUBFEATURE_TEMP_EMERGENCY_HYST:
+ case SENSORS_SUBFEATURE_TEMP_LOWEST:
+ case SENSORS_SUBFEATURE_TEMP_HIGHEST:
+ case SENSORS_SUBFEATURE_TEMP_ALARM:
+ case SENSORS_SUBFEATURE_TEMP_MAX_ALARM:
+ case SENSORS_SUBFEATURE_TEMP_MIN_ALARM:
+ case SENSORS_SUBFEATURE_TEMP_CRIT_ALARM:
+ case SENSORS_SUBFEATURE_TEMP_EMERGENCY_ALARM:
+ case SENSORS_SUBFEATURE_TEMP_LCRIT_ALARM:
+ case SENSORS_SUBFEATURE_TEMP_FAULT:
+ case SENSORS_SUBFEATURE_TEMP_TYPE:
+ case SENSORS_SUBFEATURE_TEMP_OFFSET:
+ case SENSORS_SUBFEATURE_TEMP_BEEP:
+ value += 273.15;
+ break;
+
+ default:
+ break;
+ }
+
+ // Push the value
+ r = td_metrics_push_float(metrics, v->field, value);
+ if (r < 0)
+ return r;
+
+ // We are done for this subfeature
+ break;
+ }
+ }
+ }
+
+ return 0;
+}
+
+
+static int read_temp_sensor(td_ctx* ctx, td_source* source, const sensors_chip_name* chip,
+ const sensors_feature* feature, td_metrics* metrics) {
+ sensors_value values[] = {
+ { "current", SENSORS_SUBFEATURE_TEMP_INPUT },
+ { "max", SENSORS_SUBFEATURE_TEMP_MAX },
+ { "max_hyst", SENSORS_SUBFEATURE_TEMP_MAX_HYST },
+ { "min", SENSORS_SUBFEATURE_TEMP_MIN },
+ { "min_hyst", SENSORS_SUBFEATURE_TEMP_MIN_HYST },
+ { "crit", SENSORS_SUBFEATURE_TEMP_CRIT },
+ { "crit_hyst", SENSORS_SUBFEATURE_TEMP_CRIT_HYST },
+ { "lcrit", SENSORS_SUBFEATURE_TEMP_LCRIT },
+ { "emergency", SENSORS_SUBFEATURE_TEMP_EMERGENCY },
+ { "emergency_hyst", SENSORS_SUBFEATURE_TEMP_EMERGENCY_HYST },
+ { "lowest", SENSORS_SUBFEATURE_TEMP_LOWEST },
+ { "highest", SENSORS_SUBFEATURE_TEMP_HIGHEST },
+ { "alarm", SENSORS_SUBFEATURE_TEMP_ALARM },
+ { "alarm_max", SENSORS_SUBFEATURE_TEMP_MAX_ALARM },
+ { "alarm_min", SENSORS_SUBFEATURE_TEMP_MIN_ALARM },
+ { "alarm_crit", SENSORS_SUBFEATURE_TEMP_CRIT_ALARM },
+ { "alarm_emergency", SENSORS_SUBFEATURE_TEMP_EMERGENCY_ALARM },
+ { "alarm_lcrit", SENSORS_SUBFEATURE_TEMP_LCRIT_ALARM },
+ { "fault", SENSORS_SUBFEATURE_TEMP_FAULT },
+ { "type", SENSORS_SUBFEATURE_TEMP_TYPE },
+ { "offset", SENSORS_SUBFEATURE_TEMP_OFFSET },
+ { "beep", SENSORS_SUBFEATURE_TEMP_BEEP },
+ { NULL },
+ };
+
+ // Read values
+ return read_sensors(ctx, chip, feature, metrics, values);
+}
+
+static int sensors_temp_heartbeat(td_ctx* ctx, td_source* source) {
+ int r;
+
+ // Find temperature sensors
+ r = find_sensors(ctx, source, SENSORS_FEATURE_TEMP, read_temp_sensor);
+ if (r < 0)
+ return r;
+
+ return 0;
+}
+
+const td_source_impl sensors_temp_source = {
+ .name = "sensors-temp",
+
+ // RRD Data Sources
+ .rrd_dss = {
+ { "current", "GAUGE", 0, -1, },
+ { "max", "GAUGE", 0, -1, },
+ { "max_hyst", "GAUGE", 0, -1, },
+ { "min", "GAUGE", 0, -1, },
+ { "crit", "GAUGE", 0, -1, },
+ { "crit_hyst", "GAUGE", 0, -1, },
+ { "lcrit", "GAUGE", 0, -1, },
+ { "emergency", "GAUGE", 0, -1, },
+ { "emergency_hyst", "GAUGE", 0, -1, },
+ { "lowest", "GAUGE", 0, -1, },
+ { "highest", "GAUGE", 0, -1, },
+ { "alarm", "GAUGE", 0, -1, },
+ { "alarm_max", "GAUGE", 0, -1, },
+ { "alarm_min", "GAUGE", 0, -1, },
+ { "alarm_crit", "GAUGE", 0, -1, },
+ { "alarm_emergency", "GAUGE", 0, -1, },
+ { "alarm_lcrit", "GAUGE", 0, -1, },
+ { "fault", "GAUGE", 0, -1, },
+ { "type", "GAUGE", 0, -1, },
+ { "offset", "GAUGE", 0, -1, },
+ { "beep", "GAUGE", 0, -1, },
+ { NULL },
+ },
+
+ // Methods
+ .heartbeat = sensors_temp_heartbeat,
+};