phoenix-upshid/phoenix-upshid.c

539 lines
12 KiB
C

/*
MIT License
Copyright (c) 2022 DrMaxNix
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
Inspired by:
- https://github.com/torvalds/linux/blob/master/samples/hidraw/hid-example.c
- https://github.com/gregkh/usbutils/blob/master/usbreset.c
- https://askubuntu.com/questions/645/how-do-you-reset-a-usb-device-from-the-command-line
*/
#include <linux/usbdevice_fs.h>
#include <linux/hidraw.h>
#include <sys/ioctl.h>
#include <stdbool.h>
#include <unistd.h>
#include <dirent.h>
#include <string.h>
#include <stdint.h>
#include <stdlib.h>
#include <ctype.h>
#include <fcntl.h>
#include <stdio.h>
// MACROS //
// convert boolean to string (true/false)
#define bool_string(value) (value ? "true" : "false")
// FUNCTIONS WE WILL DEFINE LATER //
int find_device_hid(uint16_t vendor_id, uint16_t device_id);
int find_device_bus(uint16_t vendor_id, uint16_t device_id);
void output_data();
void usbreset();
void print_help();
void print_version();
// PUBLIC FLAGS //
bool auto_usbreset = false;
uint16_t vendor_id;
uint16_t product_id;
/*
Main
*/
int main(int argc, char **argv){
// PARSE FLAGGS //
int one_flag;
while((one_flag = getopt(argc, argv, "vha")) != -1){
switch(one_flag){
// automatically try usb reset
case 'a':
auto_usbreset = true;
break;
// version
case 'v':
print_version();
break;
// help
case 'h':
print_help(argv[0]);
break;
// option error
case '?':
exit(1);
break;
// should never reach this..
default:
abort();
break;
}
}
// OTHER ARGUMENTS //
// check count
if(argc - optind != 1){
fprintf(stderr, "[ERROR] parameter count must be 1; use -h flag for help\n");
exit(1);
}
// parse
for(int q = optind; q < argc; q++){
if(q == optind){
// make sure string has a collon in it
char *arg_id = argv[q];
if(!strstr(arg_id, ":")){
fprintf(stderr, "[ERROR] parameter 1 must contain a collon; use -h flag for help\n");
exit(1);
}
// devide by collon and do hex-string decode
vendor_id = strtol(strsep(&arg_id, ":"), NULL, 16);
product_id = strtol(strsep(&arg_id, ":"), NULL, 16);
}
}
// READ AND OUTPUT DATA //
output_data();
// EXIT //
return(0);
}
/*
HELPER: Find hid device file of form '/dev/hidrawx' for device with SEARCH-VENDOR-ID and SEARCH-PRODUCT-ID and return its handle
*/
int find_device_hid(uint16_t search_vendor_id, uint16_t search_product_id){
// OPEN /DEV AS DIR //
DIR *dir = opendir("/dev");
struct dirent *dirent;
// CHECK IF READABLE //
if(dir == NULL){
// not readable
fprintf(stderr, "[ERROR] /dev not readable\n");
exit(0);
}
// check every file in dir
while((dirent = readdir(dir)) != NULL){
// CHECK FILETYPE //
if(dirent->d_type != DT_CHR){
// not a char device, try next one
continue;
}
// CHECK FILENAME //
// check filename length
if(strlen(dirent->d_name) <= 6){
// filename too short, try next one
continue;
}
// check filename prefix
if(strncmp(dirent->d_name, "hidraw", 6) != 0){
// prefix not matching
continue;
}
// CHECK IDS //
// get full path by appending '/dev/'
char path[PATH_MAX];
snprintf(path, sizeof(path) - 1, "/dev/%s", dirent->d_name);
// open hid device (readonly, non-blocking)
int hid = open(path, O_RDONLY|O_NONBLOCK);
if(hid < 0){
// not able to open device, try next one
continue;
}
// prepare report descriptor buffers
struct hidraw_report_descriptor rpt_desc;
memset(&rpt_desc, 0x00, sizeof(rpt_desc));
struct hidraw_devinfo info;
memset(&info, 0x00, sizeof(info));
char buf[256];
memset(buf, 0x00, sizeof(buf));
// get size of report descriptor
int res;
int desc_size = 0;
res = ioctl(hid, HIDIOCGRDESCSIZE, &desc_size);
if(res < 0){
// problem reading, try next one
close(hid);
continue;
}
// get report descriptor
rpt_desc.size = desc_size;
res = ioctl(hid, HIDIOCGRDESC, &rpt_desc);
if(res < 0){
// problem reading, try next one
close(hid);
continue;
}
// get ids (read info section of report descriptor)
res = ioctl(hid, HIDIOCGRAWINFO, &info);
if(res < 0){
// problem reading, try next one
close(hid);
continue;
}
// compare ids
if((uint16_t)info.vendor == search_vendor_id && (uint16_t)info.product == search_product_id){
// found a device!
// close dir
closedir(dir);
// return file handle
return(hid);
}
}
// DIDN'T FIND ANYTHING //
// close dir
closedir(dir);
// error message
fprintf(stderr, "[ERROR] No hid device found\n");
exit(3);
}
/*
HELPER: Find bus device file of form '/dev/bus/usb/xxx/xxx' for device with SEARCH-VENDOR-ID and SEARCH-PRODUCT-ID and return its handle
*/
int find_device_bus(uint16_t search_vendor_id, uint16_t search_product_id){
// OPEN /SYS/BUS/USB/DEVICES AS DIR //
DIR *dir = opendir("/sys/bus/usb/devices");
struct dirent *dirent;
// CHECK IF READABLE //
if(dir == NULL){
// not readable
fprintf(stderr, "[ERROR] /sys/bus/usb/devices not readable\n");
exit(0);
}
// check every file in dir
char path[PATH_MAX];
char id_buf[129];
int file;
uint16_t our_vendor_id;
uint16_t our_product_id;
int our_bus_num;
int our_dev_num;
while((dirent = readdir(dir)) != NULL){
// ONLY TRY DIRS STARTING WITH DIGIT //
if(!isdigit(dirent->d_name[0])){
// try next device
continue;
}
// GET VENDOR-ID //
// get file path
snprintf(path, sizeof(path) - 1, "/sys/bus/usb/devices/%s/idVendor", dirent->d_name);
// open file
file = open(path, O_RDONLY);
if(file < 0){
// not able to open file, try next device
continue;
}
// read first 4 chars from file
memset(id_buf, 0x00, sizeof(id_buf));
read(file, id_buf, 4);
close(file);
// decode hex-string
our_vendor_id = strtol(id_buf, NULL, 16);
// GET PRODUCT-ID //
// get file path
snprintf(path, sizeof(path) - 1, "/sys/bus/usb/devices/%s/idProduct", dirent->d_name);
// open file
file = open(path, O_RDONLY);
if(file < 0){
// not able to open file, try next device
continue;
}
// read first 4 chars from file
memset(id_buf, 0x00, sizeof(id_buf));
read(file, id_buf, 4);
close(file);
// decode hex-string
our_product_id = strtol(id_buf, NULL, 16);
// COMPARE IDS //
if(our_vendor_id != search_vendor_id || our_product_id != search_product_id){
// no match, try next device
continue;
}
// GET BUS-NUM //
// get file path
snprintf(path, sizeof(path) - 1, "/sys/bus/usb/devices/%s/busnum", dirent->d_name);
// open file
file = open(path, O_RDONLY);
if(file < 0){
// not able to open file, try next device
continue;
}
// read first 128 chars from file
memset(id_buf, 0x00, sizeof(id_buf));
read(file, id_buf, 128);
close(file);
// decode string
our_bus_num = strtol(id_buf, NULL, 10);
// GET DEV-NUM //
// get file path
snprintf(path, sizeof(path) - 1, "/sys/bus/usb/devices/%s/devnum", dirent->d_name);
// open file
file = open(path, O_RDONLY);
if(file < 0){
// not able to open file, try next device
continue;
}
// read first 128 chars from file
memset(id_buf, 0x00, sizeof(id_buf));
read(file, id_buf, 128);
close(file);
// decode string
our_dev_num = strtol(id_buf, NULL, 10);
// GET BUS PATH //
// close dir
closedir(dir);
// format as usb-bus
char bus[PATH_MAX];
snprintf(bus, sizeof(bus) - 1, "/dev/bus/usb/%03d/%03d", our_bus_num, our_dev_num);
// return file handle
return(open(bus, O_WRONLY));
}
// DIDN'T FIND ANYTHING //
// close dir
closedir(dir);
// error message
fprintf(stderr, "[ERROR] No bus device found\n");
exit(3);
}
/*
HELPER: Read hid data and output as json on stdout
*/
void output_data(){
// FIND HID DEVICE //
int hid = find_device_hid(vendor_id, product_id);
// READ DATA //
// charge and remaining time
int values_res;
char values_buffer[64];
for(int q = 0; q <= 1; q++){
values_buffer[0] = 0x06;
values_res = ioctl(hid, HIDIOCGFEATURE(256), values_buffer);
if(values_res >= 4){
// success
break;
}
// max tries exceeded?
fprintf(stderr, "[WARN ] Unable to fetch report 0x06\n");
if(q >= 1){
close(hid);
exit(2);
}
// maybe try a usb reset
if(auto_usbreset){
fprintf(stderr, "[INFO ] Trying usb reset\n");
usbreset();
}
}
// flaggs (charging, discharging, line power, low battery)
int flaggs_res;
char flaggs_buffer[64];
for(int q = 0; q <= 1; q++){
flaggs_buffer[0] = 0x01;
flaggs_res = ioctl(hid, HIDIOCGFEATURE(256), flaggs_buffer);
if(flaggs_res >= 5){
// success
break;
}
// max tries exceeded?
fprintf(stderr, "[WARN ] Unable to fetch report 0x01\n");
if(q >= 1){
close(hid);
exit(2);
}
// maybe try a usb reset
if(auto_usbreset){
fprintf(stderr, "[INFO ] Trying usb reset\n");
usbreset();
}
}
// close file
close(hid);
// GET DATA //
// charge
uint8_t charge = values_buffer[1];
// runtime (little-endian)
uint16_t runtime = values_buffer[2] + (values_buffer[3] << 8);
// line powered?
uint8_t line = flaggs_buffer[1];
// battery low?
uint8_t battery_low = flaggs_buffer[2];
// charging?
uint8_t charging = flaggs_buffer[3];
// discharging?
uint8_t discharging = flaggs_buffer[4];
// OUTPUT AS JSON //
printf("{\"charge\":%d,\"runtime\":%d,\"line\":%s,\"battery_low\":%s,\"charging\":%s,\"discharging\":%s}", charge, runtime, bool_string(line), bool_string(battery_low), bool_string(charging), bool_string(discharging));
}
/*
HELPER: Do usb reset
*/
void usbreset(){
// FIND BUS DEVICE //
int bus = find_device_bus(vendor_id, product_id);
// DO RESET //
// send command
if(ioctl(bus, USBDEVFS_RESET, 0) < 0){
fprintf(stderr, "[WARN ] Unable to do usbreset\n");
return;
}
// success!
fprintf(stderr, "[INFO ] Usbreset successful\n");
// close file
close(bus);
}
/*
HELPER: Print version and exit
*/
void print_version(){
printf("phoenix-upshid v1.0.2 | (c) DrMaxNix 2022 | www.drmaxnix.de/phoenix-upshid\n");
exit(0);
}
/*
HELPER: Print help message and exit
*/
void print_help(char *cmd){
// usage
printf("Usage:\n");
printf(" %s [-a] <vendor-id>:<product-id>\n", cmd);
printf("\n");
// options
printf(" -a Automatically try usb reset on failed read\n");
printf(" \n");
printf(" -h Show this help message and exit\n");
printf(" -v Print version and exit\n");
printf(" \n");
printf(" <vendor-id> Vendor-id of the hid-device to search for\n");
printf(" <product-id> Product-id of the hid-device to search for\n");
printf(" \n");
printf(" <vendor-id> and <product-id> are 4-digit hexadecimal values (eg. '06da')\n");
printf("\n");
// example
printf("Example: print device data of device 06da:ffff (and try usb reset)\n");
printf(" %s -a 06da:ffff\n", cmd);
printf("\n");
// return values
printf("Return values:\n");
printf(" 0: Success (valid data will be returned on stdout)\n");
printf(" 1: User input error\n");
printf(" 2: Ups state unknown\n");
printf(" 3: Ups is dead (\"not found\")\n");
printf("\n");
exit(0);
}