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