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