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