/ firmware / src / sensors / solar_radiation.cpp
solar_radiation.cpp
  1  #include "solar_radiation.h"
  2  #include "registry.h"
  3  
  4  #include <config.h>
  5  #include "../hardware/rs485.h"
  6  #include "../networking/modbus.h"
  7  
  8  namespace {
  9  
 10  bool available = false;
 11  
 12  const config::ModbusSensorConfig *access_config() {
 13    for (size_t index = 0; index < config::modbus::DEVICE_COUNT; index++) {
 14      const config::ModbusSensorConfig &sensor_config = config::modbus::DEVICES[index];
 15      if (sensor_config.kind == config::ModbusSensorKind::SolarRadiation) {
 16        return &sensor_config;
 17      }
 18    }
 19    return nullptr;
 20  }
 21  
 22  hardware::rs485::Channel channel_from_config(const config::ModbusSensorConfig *sensor_config) {
 23    return sensor_config && sensor_config->channel == 0
 24        ? hardware::rs485::Channel::Bus0
 25        : hardware::rs485::Channel::Bus1;
 26  }
 27  
 28  }
 29  
 30  bool sensors::solar_radiation::access(SolarRadiationSensorData *sensor_data) {
 31    if (!sensor_data) return false;
 32    sensor_data->watts_per_square_meter = 0;
 33    sensor_data->ok = false;
 34    const config::ModbusSensorConfig *sensor_config = access_config();
 35    if (!sensor_config) return false;
 36  
 37    uint16_t output_words[1] = {0};
 38    ReadHoldingRegistersCommand command = {
 39      .channel = channel_from_config(sensor_config),
 40      .slave_id = sensor_config->slave_id,
 41      .start_register = sensor_config->register_address,
 42      .register_count = 1,
 43      .output_words = output_words,
 44      .error = ModbusError::NotInitialized,
 45    };
 46  
 47    if (!networking::modbus::readHoldingRegisters(&command)) return false;
 48  
 49    sensor_data->watts_per_square_meter = output_words[0];
 50    sensor_data->ok = true;
 51    return true;
 52  }
 53  
 54  bool sensors::solar_radiation::initialize() {
 55    if (!access_config()) {
 56      available = false;
 57      return true;
 58    }
 59    SolarRadiationSensorData sensor_data = {};
 60    available = sensors::solar_radiation::access(&sensor_data);
 61    if (available) {
 62      sensors::registry::add({
 63          .kind = SensorKind::SolarRadiation,
 64          .name = "Solar Radiation",
 65          .isAvailable = sensors::solar_radiation::isAvailable,
 66          .instanceCount = []() -> uint8_t { return 1; },
 67          .poll = [](uint8_t, void *out, size_t cap) -> bool {
 68              if (cap < sizeof(SolarRadiationSensorData)) return false;
 69              return sensors::solar_radiation::access(
 70                  static_cast<SolarRadiationSensorData *>(out));
 71          },
 72          .data_size = sizeof(SolarRadiationSensorData),
 73      });
 74    }
 75    return available;
 76  }
 77  
 78  bool sensors::solar_radiation::isAvailable() {
 79    return available;
 80  }
 81  
 82  #ifdef PIO_UNIT_TESTING
 83  
 84  #include <testing/utils.h>
 85  
 86  static void test_solar_radiation_config_lookup(void) {
 87    WHEN("the modbus topology is checked for solar radiation");
 88  
 89    const config::ModbusSensorConfig *cfg = nullptr;
 90    for (size_t i = 0; i < config::modbus::DEVICE_COUNT; i++) {
 91      if (config::modbus::DEVICES[i].kind == config::ModbusSensorKind::SolarRadiation) {
 92        cfg = &config::modbus::DEVICES[i];
 93        break;
 94      }
 95    }
 96  
 97    if (!cfg) {
 98      TEST_IGNORE_MESSAGE("no solar radiation sensor configured — skipping");
 99      return;
100    }
101  
102    char msg[64];
103    snprintf(msg, sizeof(msg), "channel=%d slave=%d register=%d",
104             cfg->channel, cfg->slave_id, cfg->register_address);
105    TEST_MESSAGE(msg);
106  }
107  
108  static void test_solar_radiation_rejects_null(void) {
109    WHEN("a null buffer is passed to access");
110    THEN("it returns false");
111    TEST_ASSERT_FALSE_MESSAGE(sensors::solar_radiation::access(nullptr),
112        "device: access should fail with null pointer");
113  }
114  
115  void sensors::solar_radiation::test() {
116    MODULE("Solar Radiation");
117    RUN_TEST(test_solar_radiation_config_lookup);
118    RUN_TEST(test_solar_radiation_rejects_null);
119  }
120  
121  #endif