Skip to content

File temperature.hpp

File List > espos_devices > include > espos_devices > temperature.hpp

Go to the documentation of this file

// SPDX-FileCopyrightText: 2026 Dirk Wahrheit
// SPDX-License-Identifier: Apache-2.0
//
// OneWireTemperature — a DS18B20 on a Signal K temperature path.
//
//   espos::sensors::OneWireBus bus(4);
//   espos::devices::OneWireTemperature engine(g, bus, {
//       .id = "engrm", .addr = 0x28FF1234567890AB,
//       .path = "environment.inside.engineRoom.temperature",
//   });
//   bus.start(5000);            // one cycle drives every sensor on the wire
//
// The bus is the caller's, not the device's, because one wire carries many
// sensors and each conversion is a bus-wide operation: a device that owned
// its own bus would give you one GPIO per thermometer.
//
// **Pass the ROM address, not an index.** A 1-Wire search returns sensors in
// whatever order they answer, so an index renumbers every sensor after one
// that fails to reply -- and the engine-room reading quietly becomes the
// fridge. The address is what a sensor IS; find it once with
// OneWireBus::search() and write it into the firmware.
#pragma once

#include <cstddef>

#include "espos_devices/detail.hpp"
#include "espos_flow/graph.hpp"
#include "espos_sensors/onewire.hpp"
#include "espos_sk_flow/sk.hpp"

// 1-Wire is off by default (CONFIG_ESPOS_SENSORS_ONEWIRE), and the driver
// types do not exist without it -- so neither does this device. The guard is
// here rather than left to the caller because espos_devices.hpp includes
// every device: without it, a firmware that wants a tank sender would fail to
// compile over a thermometer it never asked for.
#if CONFIG_ESPOS_SENSORS_ONEWIRE

namespace espos::devices
{

struct OneWireTemperatureConfig {
    const char *id = "temp";
    // The sensor's 64-bit ROM id. 0 is legal only on a bus with exactly one
    // sensor; see the note above about why it is a bad habit on any other.
    uint64_t addr = 0;
    // A spec path, so no metadata is sent. Signal K temperatures are KELVIN,
    // which is what the driver emits -- no conversion here, deliberately.
    const char *path = "environment.inside.temperature";
};

// Templated on the bus's capacity because OneWireBus is: `Max` is how many
// sensors that wire can carry, and the array is inline. A caller writes
// OneWireTemperature(g, bus, {...}) and the deduction guide below picks it up
// -- the parameter never appears in application code.
template <std::size_t Max>
class OneWireTemperature
{
  public:
    OneWireTemperature(espos::flow::Graph &g,
                       espos::sensors::OneWireBus<Max> &bus,
                       const OneWireTemperatureConfig &cfg)
        : sensor_(g.make<espos::sensors::Ds18b20>(cfg.id, bus.handle(),
                                                  cfg.addr)),
          out_(g.make<espos::sk::Output<float>>(cfg.path))
    {
        sensor_ >> out_;
        // Registering with the bus is what makes it converted and read; a sensor
        // that is only wired into the graph produces nothing, silently.
        bus.add(sensor_);
    }

    // The reading, for anything else that should follow it -- a Hysteresis into
    // an over-temperature notification is the usual second consumer.
    espos::flow::Producer<float> &kelvin() { return sensor_; }

    espos::sensors::Ds18b20 &sensor() { return sensor_; }
    espos::sk::Output<float> &output() { return out_; }

    esp_err_t open_error() const { return sensor_.open_error(); }

  private:
    espos::sensors::Ds18b20 &sensor_;
    espos::sk::Output<float> &out_;
};

// So application code says OneWireTemperature(g, bus, {...}) and never names
// the bus's capacity twice.
template <std::size_t Max>
OneWireTemperature(espos::flow::Graph &, espos::sensors::OneWireBus<Max> &,
                   const OneWireTemperatureConfig &)
    -> OneWireTemperature<Max>;

}  // namespace espos::devices

#endif  // CONFIG_ESPOS_SENSORS_ONEWIRE