Tushar Pankaj | 47313c1 | 2024-03-16 17:03:53 -0700 | [diff] [blame] | 1 | #include <dirent.h> |
| 2 | #include <sys/statvfs.h> |
| 3 | |
| 4 | #include "absl/strings/numbers.h" |
| 5 | #include "absl/strings/str_format.h" |
| 6 | #include "gflags/gflags.h" |
| 7 | |
| 8 | #include "aos/events/shm_event_loop.h" |
| 9 | #include "aos/init.h" |
| 10 | #include "frc971/orin/hardware_stats_generated.h" |
| 11 | |
| 12 | DEFINE_string(config, "aos_config.json", "File path of aos configuration"); |
| 13 | |
| 14 | namespace frc971::orin { |
| 15 | namespace { |
| 16 | std::optional<std::string> ReadFileFirstLine(std::string_view file_name) { |
| 17 | std::ifstream file(std::string(file_name), std::ios_base::in); |
| 18 | if (!file.good()) { |
| 19 | VLOG(1) << "Can't read " << file_name; |
| 20 | return std::nullopt; |
| 21 | } |
| 22 | std::string line; |
| 23 | std::getline(file, line); |
| 24 | return line; |
| 25 | } |
| 26 | |
| 27 | std::string GetHwmonNumber(const char *dir_name) { |
| 28 | DIR *dirp = opendir(dir_name); |
| 29 | if (!dirp) { |
| 30 | VLOG(1) << "Can't open " << dir_name; |
| 31 | return ""; |
| 32 | } |
| 33 | struct dirent *directory_entry; |
| 34 | while ((directory_entry = readdir(dirp)) != NULL) { |
| 35 | std::string entry_name(directory_entry->d_name); |
| 36 | if (entry_name.starts_with("hwmon")) { |
| 37 | closedir(dirp); |
| 38 | return entry_name; |
| 39 | } |
| 40 | } |
| 41 | closedir(dirp); |
| 42 | return ""; |
| 43 | } |
| 44 | } // namespace |
| 45 | |
| 46 | // Periodically sends out the HardwareStats message with hardware statistics |
| 47 | // info. |
| 48 | class HardwareMonitor { |
| 49 | public: |
| 50 | HardwareMonitor(aos::EventLoop *event_loop) |
| 51 | : event_loop_(event_loop), |
| 52 | sender_(event_loop_->MakeSender<HardwareStats>("/hardware_monitor")), |
| 53 | fan_hwmon_( |
| 54 | GetHwmonNumber("/sys/devices/platform/39c0000.tachometer/hwmon/")), |
| 55 | electrical_hwmon_(GetHwmonNumber( |
| 56 | "/sys/devices/platform/c240000.i2c/i2c-1/1-0040/hwmon/")) { |
| 57 | periodic_timer_ = |
| 58 | event_loop_->AddTimer([this]() { PublishHardwareStats(); }); |
| 59 | event_loop_->OnRun([this]() { |
| 60 | periodic_timer_->Schedule(event_loop_->monotonic_now(), |
| 61 | std::chrono::seconds(5)); |
| 62 | }); |
| 63 | } |
| 64 | |
| 65 | private: |
| 66 | void PublishHardwareStats() { |
| 67 | aos::Sender<HardwareStats>::Builder builder = sender_.MakeBuilder(); |
| 68 | // Iterate through all thermal zones |
| 69 | std::vector<flatbuffers::Offset<ThermalZone>> thermal_zones; |
| 70 | for (int zone_id = 0; zone_id < 9; zone_id++) { |
| 71 | ThermalZone::Builder thermal_zone_builder = |
| 72 | builder.MakeBuilder<ThermalZone>(); |
| 73 | thermal_zone_builder.add_id(zone_id); |
| 74 | |
| 75 | std::optional<std::string> zone_name = ReadFileFirstLine(absl::StrFormat( |
| 76 | "/sys/devices/virtual/thermal/thermal_zone%d/type", zone_id)); |
| 77 | if (zone_name) { |
| 78 | thermal_zone_builder.add_name(builder.fbb()->CreateString(*zone_name)); |
| 79 | } |
| 80 | |
| 81 | std::optional<std::string> temperature_str = |
| 82 | ReadFileFirstLine(absl::StrFormat( |
| 83 | "/sys/devices/virtual/thermal/thermal_zone%d/temp", zone_id)); |
| 84 | uint64_t temperature = 0; |
| 85 | if (temperature_str && absl::SimpleAtoi(*temperature_str, &temperature)) { |
| 86 | thermal_zone_builder.add_temperature(temperature); |
| 87 | } |
| 88 | |
| 89 | thermal_zones.emplace_back(thermal_zone_builder.Finish()); |
| 90 | } |
| 91 | |
| 92 | // Get fan speed |
| 93 | std::optional<std::string> fan_speed_str = ReadFileFirstLine( |
| 94 | absl::StrFormat("/sys/class/hwmon/%s/rpm", fan_hwmon_)); |
| 95 | |
| 96 | // Iterate through INA3221 electrical reading channels |
| 97 | std::vector<flatbuffers::Offset<ElectricalReading>> electrical_readings; |
| 98 | for (int channel = 1; channel <= 3; channel++) { |
| 99 | ElectricalReading::Builder electrical_reading_builder = |
| 100 | builder.MakeBuilder<ElectricalReading>(); |
| 101 | electrical_reading_builder.add_channel(channel); |
| 102 | |
| 103 | std::optional<std::string> label = ReadFileFirstLine(absl::StrFormat( |
| 104 | "/sys/class/hwmon/%s/in%d_label", electrical_hwmon_, channel)); |
| 105 | if (label) { |
| 106 | electrical_reading_builder.add_label( |
| 107 | builder.fbb()->CreateString(*label)); |
| 108 | } |
| 109 | |
| 110 | std::optional<std::string> voltage_str = |
| 111 | ReadFileFirstLine(absl::StrFormat("/sys/class/hwmon/%s/in%d_input", |
| 112 | electrical_hwmon_, channel)); |
| 113 | uint64_t voltage = 0; |
| 114 | if (voltage_str && absl::SimpleAtoi(*voltage_str, &voltage)) { |
| 115 | electrical_reading_builder.add_voltage(voltage); |
| 116 | } |
| 117 | |
| 118 | std::optional<std::string> current_str = |
| 119 | ReadFileFirstLine(absl::StrFormat("/sys/class/hwmon/%s/curr%d_input", |
| 120 | electrical_hwmon_, channel)); |
| 121 | uint64_t current = 0; |
| 122 | if (current_str && absl::SimpleAtoi(*current_str, ¤t)) { |
| 123 | electrical_reading_builder.add_current(current); |
| 124 | } |
| 125 | |
| 126 | uint64_t power = voltage * current / 1000; |
| 127 | if (power != 0) { |
| 128 | electrical_reading_builder.add_power(power); |
| 129 | } |
| 130 | |
| 131 | electrical_readings.emplace_back(electrical_reading_builder.Finish()); |
| 132 | } |
| 133 | |
| 134 | HardwareStats::Builder hardware_stats_builder = |
| 135 | builder.MakeBuilder<HardwareStats>(); |
| 136 | hardware_stats_builder.add_thermal_zones( |
| 137 | builder.fbb()->CreateVector(thermal_zones)); |
| 138 | uint64_t fan_speed = 0; |
| 139 | if (fan_speed_str && absl::SimpleAtoi(*fan_speed_str, &fan_speed)) { |
| 140 | hardware_stats_builder.add_fan_speed(fan_speed); |
| 141 | } |
| 142 | hardware_stats_builder.add_electrical_readings( |
| 143 | builder.fbb()->CreateVector(electrical_readings)); |
| 144 | |
| 145 | builder.CheckOk(builder.Send(hardware_stats_builder.Finish())); |
| 146 | } |
| 147 | |
| 148 | aos::EventLoop *event_loop_; |
| 149 | |
| 150 | aos::Sender<HardwareStats> sender_; |
| 151 | |
| 152 | aos::TimerHandler *periodic_timer_; |
| 153 | |
| 154 | std::string fan_hwmon_; |
| 155 | |
| 156 | std::string electrical_hwmon_; |
| 157 | }; |
| 158 | |
| 159 | } // namespace frc971::orin |
| 160 | |
| 161 | int main(int argc, char **argv) { |
| 162 | aos::InitGoogle(&argc, &argv); |
| 163 | |
| 164 | aos::FlatbufferDetachedBuffer<aos::Configuration> config = |
| 165 | aos::configuration::ReadConfig(FLAGS_config); |
| 166 | |
| 167 | aos::ShmEventLoop shm_event_loop(&config.message()); |
| 168 | |
| 169 | frc971::orin::HardwareMonitor hardware_monitor(&shm_event_loop); |
| 170 | |
| 171 | shm_event_loop.Run(); |
| 172 | |
| 173 | return 0; |
| 174 | } |