co2minimon

Get temperature and CO2 concentration data from a CO2Mini sensor.
git clone git://git.amin.space/co2minimon.git
Log | Files | Refs | README | LICENSE

commit d294eb5fd63dba16487fbb20d8603d97cbe78691
parent 35602793a52e494da3b52f3c4167467fb24a877b
Author: amin <dev@aminmesbah.com>
Date:   Sun, 29 May 2022 06:09:13 +0000

Rewrite main loop as a state machine

FossilOrigin-Name: 04eccf37524b16c78111a89006df45cfa1963290e91f15cb8731b3e10914a2ef
Diffstat:
Mmain.c | 303++++++++++++++++++++++++++++++++++++++++++++++++++-----------------------------
1 file changed, 193 insertions(+), 110 deletions(-)

diff --git a/main.c b/main.c @@ -17,6 +17,42 @@ // https://www.kernel.org/doc/Documentation/hid/hidraw.txt #include <linux/hidraw.h> +#define UNREACHABLE assert(false) + +enum State +{ + DEVICE_HANDLE_INACCESSIBLE, + DEVICE_HANDLE_ACCESSIBLE, + DEVICE_HANDLE_OPEN, + DEVICE_SENDING_DATA, + FATAL_ERROR, +}; + +void print_state(enum State state) +{ + switch (state) + { + case DEVICE_HANDLE_INACCESSIBLE: + printf("DEVICE_HANDLE_INACCESSIBLE\n"); + break; + case DEVICE_HANDLE_ACCESSIBLE: + printf("DEVICE_HANDLE_ACCESSIBLE\n"); + break; + case DEVICE_HANDLE_OPEN: + printf("DEVICE_HANDLE_OPEN\n"); + break; + case DEVICE_SENDING_DATA: + printf("DEVICE_SENDING_DATA\n"); + break; + case FATAL_ERROR: + printf("FATAL_ERROR\n"); + break; + default: + assert(false); + break; + } +} + volatile static bool running = true; static void stop_running(int signal) @@ -43,149 +79,196 @@ int main(void) } } - // NOTE: Included udev rules create this symlink to the appropriate hidraw // entry. const char *hid_file_path = "/dev/co2mini0"; const char *temperature_file_path = "/tmp/co2minimon_temp"; const char *co2_file_path = "/tmp/co2minimon_co2"; - bool error_occurred = false; + enum State state = DEVICE_HANDLE_INACCESSIBLE; int device_handle = -1; - bool need_send_feature_report = true; while (running) { - if (access(hid_file_path, F_OK) != 0) + print_state(state); + switch (state) { - if (close(device_handle) == 0) + case DEVICE_HANDLE_INACCESSIBLE: { - device_handle = -1; + if (access(hid_file_path, F_OK) == 0) + { + state = DEVICE_HANDLE_ACCESSIBLE; + } + else + { + sleep(30); + } } - - unlink(co2_file_path); - unlink(temperature_file_path); - - sleep(30); - continue; - } - - if (device_handle == -1) - { - device_handle = open(hid_file_path, O_RDWR); - if (device_handle < 0) + break; case DEVICE_HANDLE_ACCESSIBLE: { - printf("ERROR: Failed to open HID.\n"); - goto error; + errno = 0; + device_handle = open(hid_file_path, O_RDWR); + if (device_handle >= 0) + { + state = DEVICE_HANDLE_OPEN; + } + else if (errno == ENOENT) + { + state = DEVICE_HANDLE_INACCESSIBLE; + } + else if (errno != EINTR) + { + printf("ERROR: Failed to open HID.\n"); + state = FATAL_ERROR; + } } - - need_send_feature_report = true; - } - - if (need_send_feature_report) { - uint8_t key[8] = {0}; - int result = ioctl(device_handle, HIDIOCSFEATURE(sizeof(key)), key); - if (result < 0 || result != sizeof(key)) + break; case DEVICE_HANDLE_OPEN: { - printf("ERROR: Failed to send feature report.\n"); - goto error; + uint8_t key[8] = {0}; + errno = 0; + int result = ioctl(device_handle, HIDIOCSFEATURE(sizeof(key)), key); + if (result == sizeof(key)) + { + state = DEVICE_SENDING_DATA; + } + else if (result < 0) + { + if (errno == ENODEV) + { + // TODO: do we need to do this in any other cases? + close(device_handle); + unlink(co2_file_path); + unlink(temperature_file_path); + state = DEVICE_HANDLE_ACCESSIBLE; + } + else if (errno != EINTR) + { + printf("ERROR: Failed to send feature report.\n"); + state = FATAL_ERROR; + } + } + else + { + // TODO: when does this happen + printf("ERROR: Failed to send feature report.\n"); + state = FATAL_ERROR; + } } - need_send_feature_report = false; - } - - fd_set read_fds; - FD_ZERO(&read_fds); - FD_SET(device_handle, &read_fds); - errno = 0; - int ready = select(device_handle + 1, &read_fds, NULL, NULL, &(struct timeval){ .tv_sec = 15 }); - if (ready == -1 && errno != EINTR) - { - printf("ERROR: Failed to select() device handle.\n"); - goto error; - } - else if (ready == 0) - { - // select() timed out - // - // If we go too long with an empty pipe, the device may have - // stopped sending data. One situation where I've observed this can - // happen is when the system resumes from hibernation. In this - // case, all calls to read() will block until we resend the feature - // report. - // - // I'm not sure why this happens, but my guess would be that the - // device stops sending new data if the data hasn't been read in a - // while. - need_send_feature_report = true; - } - else - { - uint8_t data[8] = {0}; - int bytes_read = read(device_handle, &data, sizeof(data)); - - uint8_t item = data[0]; - uint8_t msb = data[1]; - uint8_t lsb = data[2]; - uint8_t checksum = data[3]; - uint8_t end = data[4]; - - if (bytes_read == sizeof(data) - && end == 0x0d - && (item == 0x42 || item == 0x50) - && ((item + msb + lsb) & 0xFF) == checksum) + break; case DEVICE_SENDING_DATA: { - need_send_feature_report = false; - uint16_t value = (((uint16_t)msb) << 8) | lsb; - char buf[1024] = {0}; - int str_len = 0; - mode_t create_mode = S_IRUSR | S_IWUSR; - int open_mode = O_WRONLY | O_CREAT | O_TRUNC; - - switch(item) + fd_set read_fds; + FD_ZERO(&read_fds); + FD_SET(device_handle, &read_fds); + errno = 0; + int ready = select(device_handle + 1, &read_fds, NULL, NULL, &(struct timeval){ .tv_sec = 15 }); + if (ready == -1) + { + if (errno != EINTR) + { + printf("ERROR: Failed to select() device handle.\n"); + state = FATAL_ERROR; + } + } + else if (ready == 0) + { + // select() timed out + // + // If we go too long with an empty pipe, the device may have + // stopped sending data. One situation where I've observed this can + // happen is when the system resumes from hibernation. In this + // case, all calls to read() will block until we resend the feature + // report. + // + // I'm not sure why this happens, but my guess would be that the + // device stops sending new data if the data hasn't been read in a + // while. + state = DEVICE_HANDLE_OPEN; + //TODO: why do we ooscillate? + } + else { - case 0x42: + uint8_t data[8] = {0}; + errno = 0; + int bytes_read = read(device_handle, &data, sizeof(data)); + if (bytes_read < 0 && errno != EINTR) { - printf("bytes T\n"); - double t_celsius = value / 16.0 - 273.15; - str_len = snprintf(buf, sizeof(buf), "%.2f", t_celsius); - assert(str_len > 0); - int f = open(temperature_file_path, open_mode, create_mode); - if (f == -1) + if (errno == EIO) { - printf("ERROR: Failed to open output file for temperature.\n"); - goto error; + state = DEVICE_HANDLE_OPEN; + break; } - write(f, buf, str_len); - close(f); - } break; + else + { + printf("ERROR: Failed to read device handle\n"); + state = FATAL_ERROR; + break; + } + } + + uint8_t item = data[0]; + uint8_t msb = data[1]; + uint8_t lsb = data[2]; + uint8_t checksum = data[3]; + uint8_t end = data[4]; - case 0x50: + if (bytes_read == sizeof(data) + && end == 0x0d + && (item == 0x42 || item == 0x50) + && ((item + msb + lsb) & 0xFF) == checksum) { - printf("bytes C\n"); - str_len = snprintf(buf, sizeof(buf), "%d", value); - assert(str_len > 0); - int f = open(co2_file_path, open_mode, create_mode); - if (f == -1) + uint16_t value = (((uint16_t)msb) << 8) | lsb; + char buf[1024] = {0}; + int str_len = 0; + mode_t create_mode = S_IRUSR | S_IWUSR; + int open_mode = O_WRONLY | O_CREAT | O_TRUNC; + + switch(item) { - printf("ERROR: Failed to open output file for CO2.\n"); - goto error; + case 0x42: + { + double t_celsius = value / 16.0 - 273.15; + str_len = snprintf(buf, sizeof(buf), "%.2f", t_celsius); + assert(str_len > 0); + int f = open(temperature_file_path, open_mode, create_mode); + if (f == -1) + { + printf("ERROR: Failed to open output file for temperature.\n"); + state = FATAL_ERROR; + } + printf("write T\n"); + write(f, buf, str_len); + close(f); + } break; + + case 0x50: + { + str_len = snprintf(buf, sizeof(buf), "%d", value); + assert(str_len > 0); + int f = open(co2_file_path, open_mode, create_mode); + if (f == -1) + { + printf("ERROR: Failed to open output file for CO2.\n"); + state = FATAL_ERROR; + } + printf("write CO2\n"); + write(f, buf, str_len); + close(f); + } break; } - write(f, buf, str_len); - close(f); - } break; + } } } + break; case FATAL_ERROR: + running = false; + break; default: + UNREACHABLE; + break; } } - goto cleanup; - -error: - error_occurred = true; -cleanup: unlink(co2_file_path); unlink(temperature_file_path); close(device_handle); - return error_occurred; + return state == FATAL_ERROR; }