1 /*
2 KSysGuard, the KDE System Guard
3
4 Copyright (c) 2003 Stephan Uhlmann <su@su2.info>
5 Copyright (c) 2005 Sirtaj Singh Kang <taj@kde.org> -- Battery fixes and Thermal
6 Copyright (c) 2020 Jose Jorge <lists.jjorge@free.fr> -- Add energy sensor
7
8 This program is free software; you can redistribute it and/or
9 modify it under the terms of version 2 of the GNU General Public
10 License as published by the Free Software Foundation.
11
12 This program is distributed in the hope that it will be useful,
13 but WITHOUT ANY WARRANTY; without even the implied warranty of
14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 GNU General Public License for more details.
16
17 You should have received a copy of the GNU General Public License
18 along with this program; if not, write to the Free Software
19 Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
20 */
21
22 #include <dirent.h>
23 #include <fcntl.h>
24 #include <stdio.h>
25 #include <string.h>
26 #include <stdlib.h>
27 #include <sys/stat.h>
28 #include <sys/types.h>
29 #include <unistd.h>
30 #include <assert.h>
31
32 #include "Command.h"
33 #include "ksysguardd.h"
34
35 #include "acpi.h"
36
37 #define ACPIFILENAMELENGTHMAX 64
38
39 /*
40 ================================ public part =================================
41 */
42
initAcpi(struct SensorModul * sm)43 void initAcpi(struct SensorModul* sm)
44 {
45 initAcpiBattery(sm);
46 initAcpiThermal(sm);
47 }
48
exitAcpi(void)49 void exitAcpi( void )
50 {
51 }
52
53
54 /************ ACPI Battery **********/
registerBatteryCharge(const char * name,int number,struct SensorModul * sm)55 void registerBatteryCharge(const char *name, int number, struct SensorModul *sm)
56 {
57 char sensorName [ ACPIFILENAMELENGTHMAX ];
58 snprintf(sensorName, sizeof(sensorName), "acpi/Battery/%d-%s/Charge", number, name);
59
60 registerMonitor(sensorName, "float", printSysBatteryCharge,
61 printSysBatteryChargeInfo, sm);
62 }
63
registerBatteryChargeDesign(const char * name,int number,struct SensorModul * sm)64 void registerBatteryChargeDesign(const char *name, int number, struct SensorModul *sm)
65 {
66 char sensorName [ ACPIFILENAMELENGTHMAX ];
67 snprintf(sensorName, sizeof(sensorName), "acpi/Battery/%d-%s/ChargeDesign", number, name);
68
69 registerMonitor(sensorName, "float", printSysBatteryChargeDesign,
70 printSysBatteryChargeDesignInfo, sm);
71 }
72
registerBatteryEnergy(const char * name,int number,struct SensorModul * sm)73 void registerBatteryEnergy(const char *name, int number, struct SensorModul *sm)
74 {
75 char sensorName [ ACPIFILENAMELENGTHMAX ];
76 snprintf(sensorName, sizeof(sensorName), "acpi/Battery/%d-%s/Energy", number, name);
77
78 registerMonitor(sensorName, "float", printSysBatteryEnergy,
79 printSysBatteryEnergyInfo, sm);
80 }
81
registerBatteryEnergyDesign(const char * name,int number,struct SensorModul * sm)82 void registerBatteryEnergyDesign(const char *name, int number, struct SensorModul *sm)
83 {
84 char sensorName [ ACPIFILENAMELENGTHMAX ];
85 snprintf(sensorName, sizeof(sensorName), "acpi/Battery/%d-%s/EnergyDesign", number, name);
86
87 registerMonitor(sensorName, "float", printSysBatteryEnergyDesign,
88 printSysBatteryEnergyDesignInfo, sm);
89 }
90
registerBatteryRate(const char * name,int number,struct SensorModul * sm)91 void registerBatteryRate(const char *name, int number, struct SensorModul *sm)
92 {
93 char sensorName [ ACPIFILENAMELENGTHMAX ];
94 snprintf(sensorName, sizeof(sensorName), "acpi/Battery/%d-%s/Rate", number, name);
95
96 registerMonitor(sensorName, "integer", printSysBatteryRate,
97 printSysBatteryRateInfo, sm);
98 }
99
registerBatteryRatePower(const char * name,int number,struct SensorModul * sm)100 void registerBatteryRatePower(const char *name, int number, struct SensorModul *sm)
101 {
102 char sensorName [ ACPIFILENAMELENGTHMAX ];
103 snprintf(sensorName, sizeof(sensorName), "acpi/Battery/%d-%s/RatePower", number, name);
104
105 registerMonitor(sensorName, "integer", printSysBatteryRatePower,
106 printSysBatteryRatePowerInfo, sm);
107 }
108
initAcpiBattery(struct SensorModul * sm)109 void initAcpiBattery( struct SensorModul* sm )
110 {
111 DIR *d;
112 struct dirent *de;
113 char name[ ACPIFILENAMELENGTHMAX ];
114
115 d = opendir("/sys/class/power_supply/");
116 if (d != NULL) {
117 while ( (de = readdir(d)) != NULL ) {
118 if (!de->d_name || de->d_name[0] == '.')
119 continue;
120 if (strncmp( de->d_name, "BAT", sizeof("BAT")-1) == 0) {
121 int number = atoi(de->d_name + (sizeof("BAT")-1));
122 readTypeFile("/sys/class/power_supply/BAT%d/type", number, name, sizeof(name));
123 registerBatteryCharge(name, number, sm);
124 registerBatteryChargeDesign(name, number, sm);
125 registerBatteryEnergy(name, number, sm);
126 registerBatteryEnergyDesign(name, number, sm);
127 registerBatteryRate(name, number, sm);
128 registerBatteryRatePower(name, number, sm);
129 }
130 }
131 closedir( d );
132 }
133 }
134
printSysBatteryCharge(const char * cmd)135 void printSysBatteryCharge(const char *cmd)
136 {
137 int zone = 0;
138 if (sscanf(cmd, "acpi/Battery/%d", &zone) <= 0) {
139 output("-1\n");
140 return;
141 }
142
143 int charge = getSysFileValue("power_supply", "BAT", zone, "charge_now");
144 int maximum = getSysFileValue("power_supply", "BAT", zone, "charge_full");
145 float state = 0;
146 if ( maximum > 0) {
147 state = charge/((float)maximum/100);/* to get 0.1% changes */
148 }
149 if (state > 100) {
150 state = 100; /* prevent insane numbers with bad hardware */
151 } else if (state < 0) {
152 state = 0; /* prevent insane numbers with bad hardware */
153 }
154 output( "%f\n", state);
155 }
156
printSysBatteryChargeInfo(const char * cmd)157 void printSysBatteryChargeInfo(const char *cmd)
158 {
159 char name [ 200 ];
160 if (sscanf(cmd, "acpi/Battery/%199[^/]", name) > 0) {
161 output( "%s charge\t0\t100\t%%\n", name);
162 } else {
163 output( "Current charge\t0\t100\t%%\n");
164 }
165 }
166
printSysBatteryChargeDesign(const char * cmd)167 void printSysBatteryChargeDesign(const char *cmd)
168 {
169 int zone = 0;
170 if (sscanf(cmd, "acpi/Battery/%d", &zone) <= 0) {
171 output("-1\n");
172 return;
173 }
174
175 int charge = getSysFileValue("power_supply", "BAT", zone, "charge_now");
176 int maximum = getSysFileValue("power_supply", "BAT", zone, "charge_full_design");
177 float state = 0;
178 if (maximum > 0) {
179 state = charge/((float)maximum/100);/* to get 0.1% changes */
180 }
181 if (state > 100) {
182 state = 100; /* prevent insane numbers with bad hardware */
183 } else if (state < 0) {
184 state = 0; /* prevent insane numbers with bad hardware */
185 }
186 output( "%f\n", state);
187 }
188
printSysBatteryChargeDesignInfo(const char * cmd)189 void printSysBatteryChargeDesignInfo(const char *cmd)
190 {
191 char name [ 200 ];
192 if (sscanf(cmd, "acpi/Battery/%199[^/]", name) > 0) {
193 output( "%s charge (by design)\t0\t100\t%%\n", name);
194 } else {
195 output( "Current charge (by design)\t0\t100\t%%\n");
196 }
197 }
198
printSysBatteryEnergy(const char * cmd)199 void printSysBatteryEnergy(const char *cmd)
200 {
201 int zone = 0;
202 if (sscanf(cmd, "acpi/Battery/%d", &zone) <= 0) {
203 output("-1\n");
204 return;
205 }
206
207 int charge = getSysFileValue("power_supply", "BAT", zone, "energy_now");
208 int maximum = getSysFileValue("power_supply", "BAT", zone, "energy_full");
209 float state = 0;
210 if ( maximum > 0) {
211 state = charge/((float)maximum/100);/* to get 0.1% changes */
212 }
213 if (state > 100) {
214 state = 100; /* prevent insane numbers with bad hardware */
215 } else if (state < 0) {
216 state = 0; /* prevent insane numbers with bad hardware */
217 }
218 output( "%f\n", state);
219 }
220
printSysBatteryEnergyInfo(const char * cmd)221 void printSysBatteryEnergyInfo(const char *cmd)
222 {
223 char name [ 200 ];
224 if (sscanf(cmd, "acpi/Battery/%199[^/]", name) > 0) {
225 output( "%s energy\t0\t100\t%%\n", name);
226 } else {
227 output( "Current energy\t0\t100\t%%\n");
228 }
229 }
230
printSysBatteryEnergyDesign(const char * cmd)231 void printSysBatteryEnergyDesign(const char *cmd)
232 {
233 int zone = 0;
234 if (sscanf(cmd, "acpi/Battery/%d", &zone) <= 0) {
235 output("-1\n");
236 return;
237 }
238
239 int charge = getSysFileValue("power_supply", "BAT", zone, "energy_now");
240 int maximum = getSysFileValue("power_supply", "BAT", zone, "energy_full_design");
241 float state = 0;
242 if (maximum > 0) {
243 state = charge/((float)maximum/100);/* to get 0.1% changes */
244 }
245 if (state > 100) {
246 state = 100; /* prevent insane numbers with bad hardware */
247 } else if (state < 0) {
248 state = 0; /* prevent insane numbers with bad hardware */
249 }
250 output( "%f\n", state);
251 }
252
printSysBatteryEnergyDesignInfo(const char * cmd)253 void printSysBatteryEnergyDesignInfo(const char *cmd)
254 {
255 char name [ 200 ];
256 if (sscanf(cmd, "acpi/Battery/%199[^/]", name) > 0) {
257 output( "%s energy (by design)\t0\t100\t%%\n", name);
258 } else {
259 output( "Current energy (by design)\t0\t100\t%%\n");
260 }
261 }
262
printSysBatteryRate(const char * cmd)263 void printSysBatteryRate(const char *cmd)
264 {
265 int zone = 0;
266 if (sscanf(cmd, "acpi/Battery/%d", &zone) <= 0) {
267 output("-1\n");
268 return;
269 }
270
271 output( "%d\n", getSysFileValue("power_supply", "BAT", zone, "current_now") / 1000);
272 }
273
printSysBatteryRateInfo(const char * cmd)274 void printSysBatteryRateInfo(const char *cmd)
275 {
276 char name [ 200 ];
277 if (sscanf(cmd, "acpi/Battery/%199[^/]", name) > 0) {
278 output( "%s rate\t0\t0\tmA\n", name);
279 } else {
280 output( "Current rate\t0\t0\tmA\n");
281 }
282 }
283
printSysBatteryRatePower(const char * cmd)284 void printSysBatteryRatePower(const char *cmd)
285 {
286 int zone = 0;
287 if (sscanf(cmd, "acpi/Battery/%d", &zone) <= 0) {
288 output("-1\n");
289 return;
290 }
291
292 output( "%d\n", getSysFileValue("power_supply", "BAT", zone, "power_now") / 1000);
293 }
294
printSysBatteryRatePowerInfo(const char * cmd)295 void printSysBatteryRatePowerInfo(const char *cmd)
296 {
297 char name [ 200 ];
298 if (sscanf(cmd, "acpi/Battery/%199[^/]", name) > 0) {
299 output( "%s rate\t0\t0\tmA\n", name);
300 } else {
301 output( "Current power rate\t0\t0\tmA\n");
302 }
303 }
304
305
306 /************** ACPI Thermal *****************/
307
308 #define OLD_THERMAL_ZONE_DIR "/proc/acpi/thermal_zone"
309 #define OLD_TEMPERATURE_FILE "temperature"
310 #define OLD_TEMPERATURE_FILE_MAXLEN 255
311
312 #define OLD_FAN_DIR "/proc/acpi/fan"
313 #define OLD_FAN_STATE_FILE "state"
314 #define OLD_FAN_STATE_FILE_MAXLEN 255
315
316
317 /*static char **zone_names = NULL;*/
318
319 /** Find the thermal zone name from the command.
320 * Assumes the command is of the form acpi/thermal_zone/<zone name>/...
321 * @p startidx is set to the start of the zone name. May be set to an
322 * undefined value if zone name is not found.
323 * @return length of found name, or 0 if nothing found.
324 */
extract_zone_name(char ** startidx,const char * cmd)325 static int extract_zone_name(char **startidx, const char *cmd)
326 {
327 char *idx = NULL;
328 idx = strchr(cmd, '/');
329 if (idx == NULL) return 0;
330 idx = strchr(idx+1, '/');
331 if (idx == NULL) return 0;
332 *startidx = idx+1;
333 idx = strchr(*startidx, '/');
334 if (idx == NULL) return 0;
335 return idx - *startidx;
336 }
337
readTypeFile(const char * fileFormat,int number,char * buffer,int bufferSize)338 void readTypeFile(const char *fileFormat, int number, char *buffer, int bufferSize)
339 {
340 char filename[ ACPIFILENAMELENGTHMAX ];
341 snprintf(filename, sizeof(filename), fileFormat, number);
342
343 int typeFile = open(filename, O_RDONLY);
344 if (typeFile < 0) {
345 print_error( "Cannot open file \'%s\'!\n"
346 "Unable to fetch ACPI type", filename);
347 snprintf(buffer, bufferSize, "unknown-%d", number);
348 return;
349 }
350
351 int readBytes = read( typeFile, buffer, bufferSize - 1 );
352 assert(readBytes > 1);
353 assert(readBytes < bufferSize);
354 buffer[readBytes-1] = '\0'; /* strip newline */
355 }
356
registerThermalZone(int number,struct SensorModul * sm)357 void registerThermalZone(int number, struct SensorModul *sm)
358 {
359 char name[ ACPIFILENAMELENGTHMAX ];
360 readTypeFile("/sys/class/thermal/thermal_zone%d/type", number, name, sizeof(name));
361
362 char sensorName [ ACPIFILENAMELENGTHMAX ];
363 snprintf(sensorName, sizeof(sensorName), "acpi/Thermal_Zone/%d-%s/Temperature", number, name);
364
365 registerMonitor(sensorName, "integer", printSysThermalZoneTemperature,
366 printSysThermalZoneTemperatureInfo, sm);
367 }
368
registerCoolingDevice(int number,struct SensorModul * sm)369 void registerCoolingDevice(int number, struct SensorModul *sm)
370 {
371 char name[ ACPIFILENAMELENGTHMAX ];
372 readTypeFile("/sys/class/thermal/cooling_device%d/type", number, name, sizeof(name));
373
374 char sensorName [ ACPIFILENAMELENGTHMAX ];
375 snprintf(sensorName, sizeof(sensorName), "acpi/Cooling_Device/%d-%s/Activity", number, name);
376
377 registerMonitor(sensorName, "integer", printCoolingDeviceState,
378 printCoolingDeviceStateInfo, sm);
379 }
380
initAcpiThermal(struct SensorModul * sm)381 void initAcpiThermal(struct SensorModul *sm)
382 {
383 char th_ref[ ACPIFILENAMELENGTHMAX ];
384 DIR *d = NULL;
385 struct dirent *de;
386
387 d = opendir("/sys/class/thermal/");
388 if (d != NULL) {
389 while ( (de = readdir(d)) != NULL ) {
390 if (!de->d_name || de->d_name[0] == '.')
391 continue;
392 if (strncmp( de->d_name, "thermal_zone", sizeof("thermal_zone")-1) == 0) {
393 int number = atoi(de->d_name + (sizeof("thermal_zone")-1));
394 registerThermalZone(number, sm);
395
396 /*For compatibility, register a legacy sensor*/
397 int zone_number;
398 if (sscanf(de->d_name, "thermal_zone%d", &zone_number) > 0) {
399 snprintf(th_ref, sizeof(th_ref),
400 "acpi/thermal_zone/TZ%02d/temperature", zone_number);
401 registerLegacyMonitor(th_ref, "integer", printSysCompatibilityThermalZoneTemperature,
402 printThermalZoneTemperatureInfo, sm);
403 }
404 } else if (strncmp( de->d_name, "cooling_device", sizeof("cooling_device")-1) == 0) {
405 int number = atoi(de->d_name+( sizeof("cooling_device")-1));
406 registerCoolingDevice(number, sm);
407 }
408 }
409 closedir( d );
410 } else {
411 d = opendir(OLD_THERMAL_ZONE_DIR);
412 if (d != NULL) {
413 while ( (de = readdir(d)) != NULL ) {
414 if (!de->d_name || de->d_name[0] == '.')
415 continue;
416
417 snprintf(th_ref, sizeof(th_ref),
418 "acpi/thermal_zone/%s/temperature", de->d_name);
419 registerMonitor(th_ref, "integer", printThermalZoneTemperature,
420 printThermalZoneTemperatureInfo, sm);
421 }
422 closedir( d );
423 }
424
425 d = opendir(OLD_FAN_DIR);
426 if (d != NULL) {
427 while ( (de = readdir(d)) != NULL ) {
428 if (!de->d_name || de->d_name[0] == '.')
429 continue;
430
431 snprintf(th_ref, sizeof(th_ref),
432 "acpi/fan/%s/state", de->d_name);
433 registerMonitor(th_ref, "integer", printFanState,
434 printFanStateInfo, sm);
435 }
436 closedir( d );
437 }
438 }
439
440 return;
441 }
442
getSysFileValue(const char * className,const char * group,int value,const char * file)443 static int getSysFileValue(const char *className, const char *group, int value, const char *file) {
444 static int shownError = 0;
445 char th_file[ ACPIFILENAMELENGTHMAX ];
446 char input_buf[ 100 ];
447 snprintf(th_file, sizeof(th_file), "/sys/class/%s/%s%d/%s", className, group, value, file);
448 int fd = open(th_file, O_RDONLY);
449 if (fd < 0) {
450 return -1;/* ignore failures, as sys files disappear when battery is removed */
451 }
452 int read_bytes = read( fd, input_buf, sizeof(input_buf) - 1 );
453 if ( read_bytes == sizeof(input_buf) - 1 ) {
454 if (!shownError)
455 log_error( "Internal buffer too small to read \'%s\'", th_file );
456 shownError = 1;
457 close( fd );
458 return -1;
459 }
460 close(fd);
461
462 int result=0;
463 sscanf(input_buf, "%d", &result);
464 return result;
465 }
466
printSysThermalZoneTemperature(const char * cmd)467 void printSysThermalZoneTemperature(const char *cmd) {
468 int zone = 0;
469 if (sscanf(cmd, "acpi/Thermal_Zone/%d", &zone) <= 0) {
470 output("-1\n");
471 return;
472 }
473
474 output( "%d\n", getSysFileValue("thermal", "thermal_zone", zone, "temp") / 1000);
475 }
printSysCompatibilityThermalZoneTemperature(const char * cmd)476 void printSysCompatibilityThermalZoneTemperature(const char *cmd) {
477 int zone = 0;
478 if (sscanf(cmd, "acpi/thermal_zone/TZ%d", &zone) <= 0) {
479 output( "-1\n");
480 return;
481 }
482 output( "%d\n", getSysFileValue("thermal", "thermal_zone", zone, "temp")/1000);
483 }
484
printCoolingDeviceStateInfo(const char * cmd)485 void printCoolingDeviceStateInfo(const char *cmd)
486 {
487 (void)cmd;
488 char name [ 200 ];
489 if (sscanf(cmd, "acpi/Cooling_Device/%199[^/]", name) > 0) {
490 output( "%s Cooling Activity\t0\t100\t%%\n", name);
491 } else {
492 output( "Cooling Device Activity\t0\t100\t%%\n");
493 }
494 }
495
printCoolingDeviceState(const char * cmd)496 void printCoolingDeviceState(const char *cmd) {
497 int fan = 0;
498 if (sscanf(cmd, "acpi/Cooling_Device/%d", &fan) <= 0) {
499 output( "-1\n");
500 return;
501 }
502 int current = getSysFileValue("thermal", "cooling_device", fan, "cur_state");
503 int maximum = getSysFileValue("thermal", "cooling_device", fan, "max_state");
504 int state = 0;
505 if (current > 0 && maximum > 0) {
506 state = (current * 100) / maximum; /* state is a percentage */
507 }
508 output( "%d\n", state);
509 }
510
getCurrentTemperature(const char * cmd)511 static int getCurrentTemperature(const char *cmd)
512 {
513 char th_file[ ACPIFILENAMELENGTHMAX ];
514 char input_buf[ OLD_TEMPERATURE_FILE_MAXLEN ];
515 char *zone_name = NULL;
516 int read_bytes = 0, fd = 0, len_zone_name = 0;
517 int temperature=0;
518
519 len_zone_name = extract_zone_name(&zone_name, cmd);
520 if (len_zone_name <= 0) return -1;
521
522 snprintf(th_file, sizeof(th_file),
523 OLD_THERMAL_ZONE_DIR "/%.*s/" OLD_TEMPERATURE_FILE,
524 len_zone_name, zone_name);
525
526 fd = open(th_file, O_RDONLY);
527 if (fd < 0) {
528 print_error( "Cannot open file \'%s\'!\n"
529 "Load the thermal ACPI kernel module or\n"
530 "compile it into your kernel.\n", th_file );
531 return -1;
532 }
533
534 read_bytes = read( fd, input_buf, sizeof(input_buf) - 1 );
535 if ( read_bytes == sizeof(input_buf) - 1 ) {
536 log_error( "Internal buffer too small to read \'%s\'", th_file );
537 close( fd );
538 return -1;
539 }
540 close(fd);
541
542 sscanf(input_buf, "temperature: %d C", &temperature);
543 return temperature;
544 }
545
printThermalZoneTemperature(const char * cmd)546 void printThermalZoneTemperature(const char *cmd) {
547 int temperature = getCurrentTemperature(cmd);
548 output( "%d\n", temperature);
549 }
550
printSysThermalZoneTemperatureInfo(const char * cmd)551 void printSysThermalZoneTemperatureInfo(const char *cmd)
552 {
553 char name [ 200 ];
554 if (sscanf(cmd, "acpi/Thermal_Zone/%199[^/]", name) > 0) {
555 output( "%s temperature\t0\t0\tC\n", name);
556 } else {
557 output( "Current temperature\t0\t0\tC\n");
558 }
559 }
560
printThermalZoneTemperatureInfo(const char * cmd)561 void printThermalZoneTemperatureInfo(const char *cmd)
562 {
563 (void)cmd;
564
565 output( "Current temperature\t0\t0\tC\n");
566 }
567
568 /********** ACPI Fan State***************/
569
getFanState(const char * cmd)570 static int getFanState(const char *cmd)
571 {
572 char fan_state_file[ ACPIFILENAMELENGTHMAX ];
573 char input_buf[ OLD_FAN_STATE_FILE_MAXLEN ];
574 char *fan_name = NULL;
575 int read_bytes = 0, fd = 0, len_fan_name = 0;
576 char fan_state[4];
577
578 len_fan_name = extract_zone_name(&fan_name, cmd);
579 if (len_fan_name <= 0) {
580 return -1;
581 }
582
583 snprintf(fan_state_file, sizeof(fan_state_file),
584 OLD_FAN_DIR "/%.*s/" OLD_FAN_STATE_FILE,
585 len_fan_name, fan_name);
586
587 fd = open(fan_state_file, O_RDONLY);
588 if (fd < 0) {
589 print_error( "Cannot open file \'%s\'!\n"
590 "Load the fan ACPI kernel module or\n"
591 "compile it into your kernel.\n", fan_state_file );
592
593 return -1;
594 }
595
596 read_bytes = read( fd, input_buf, sizeof(input_buf) - 1 );
597 if ( read_bytes == sizeof(input_buf) - 1 ) {
598 log_error( "Internal buffer too small to read \'%s\'", fan_state_file );
599 close( fd );
600 return -1;
601 }
602 close(fd);
603
604 sscanf(input_buf, "status: %2s", fan_state);
605 return (fan_state[1] == 'n') ? 1 : 0;
606 }
607
printFanState(const char * cmd)608 void printFanState(const char *cmd) {
609 int fan_state = getFanState(cmd);
610 output( "%d\n", fan_state);
611 }
612
printFanStateInfo(const char * cmd)613 void printFanStateInfo(const char *cmd)
614 {
615 (void)cmd;
616
617 output( "Fan status\t0\t1\tboolean\n");
618 }
619