/* dracal-usb-get: A command-line tool for Dracal USB sensors.
 *
 * Copyright (C) 2018-2024  Dracal Technologies Inc.
 * Copyright (C) 2007-2017  Raphael Assenat <raph@raphnet.net>
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 3
 * of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>

#include <getopt.h>

#ifdef _WIN32
#include <windows.h>
#include <io.h>
#define usleep(t) Sleep((t)/1000)
#define F_OK 0
#define access _access
#else
#include <unistd.h>
#include <sys/time.h>
#endif

#include <math.h>

#include "usbtenki.h"
#include "usbtenki_provider.h"
#include "usbtenki_version.h"
#include "unit.h"
#include "chip.h"
#include "chip_conv.h"
#include "timestamp.h"

#define DEFAULT_CHANNEL_ID		0
#define DEFAULT_NUM_SAMPLES 	1
#define DEFAULT_LOG_INTERVAL	1000
#define MAX_CHANNELS			256
#define DEFAULT_DECIMAL_DIGITS	2
#define MAX_EXTRA_ARGS	8

#ifndef ARRAY_SIZE
    #define ARRAY_SIZE(arr) (sizeof((arr)) / sizeof((arr)[0]))
#endif


int g_verbose = 0;
int g_deep_trace = 0;

unit_t g_unit_prefs[UNIT_CATEGORIES] = {
	[UNIT_CATEGORY_UNKNOWN] =  UNIT_SENSOR_DEFAULT,
	[UNIT_CATEGORY_TEMPERATURE] = UNIT_CELSIUS,
	[UNIT_CATEGORY_PRESSURE] = UNIT_KPA,
	[UNIT_CATEGORY_FREQUENCY] = UNIT_HZ,
	[UNIT_CATEGORY_VOLTAGE] =  UNIT_VOLT,
	[UNIT_CATEGORY_CURRENT] = UNIT_AMP,
	[UNIT_CATEGORY_POWER] = UNIT_WATT,
	[UNIT_CATEGORY_LENGTH] = UNIT_METER,
	[UNIT_CATEGORY_CONCENTRATION] = UNIT_SENSOR_DEFAULT,
	[UNIT_CATEGORY_RELATIVE_HUMIDITY] = UNIT_RH,
};

char g_pretty = 0;
char g_full_display_mode = 0;
char g_decimal_digits = DEFAULT_DECIMAL_DIGITS;
char g_7bit_clean = 0;
char g_log_mode = 0;
int g_log_interval = DEFAULT_LOG_INTERVAL;
int g_num_rows = 0;
char g_legacy_errors = 0;
char g_user_calibration = 1;
const char *g_log_file = NULL;
FILE *g_log_fptr = NULL;

int g_num_attempts = 1;

int n_extra_args=0;
char *eargv[MAX_EXTRA_ARGS];

static void printUsage(void)
{
	printf("Usage: ./dracal-usb-get [arguments]\n");
	printf("\nValid arguments:\n");
	printf("    -V           Display version information\n");
	printf("    -v           Verbose mode\n");
	printf("    -h           Displays help\n");
	printf("    -l           List and display info about available sensors\n");
	printf("    -s serial    Use USB sensor with matching serial number. Default: Use first found\n");
	printf("    -i id<,id,id...>  Use specific channel(s) id(s) or 'a' for all. Default: %d\n", DEFAULT_CHANNEL_ID);
	printf("    -x num       Set number of fractional digits [0-6]. Default: %d\n", DEFAULT_DECIMAL_DIGITS);
	printf("    -R num       If a USB command fails, retry it num times before bailing out\n");
	printf("    -T unit      Select the temperature unit to use. Default: Celsius\n");
	printf("    -P unit      Select the pressure unit to use. Default: kPa\n");
	printf("    -F unit      Select the frequency unit to use. Default: Hz\n");
	printf("    -M unit      Select the length unit to use. Default: m\n");
	printf("    -C unit      Select the concentration unit to use. Default: Sensor default\n");
	printf("    -p           Enable pretty output\n");
	printf("    -7           Use 7-bit ASCII output (no Unicode degree symbols)\n");
	printf("    -u           Print uncalibrated values, i.e. do not apply user calibration configured by usbtenkical\n");
	printf("    -L logfile   Log to specified file (use - for console)\n");
	printf("    -I interval  Log interval. In milliseconds. Default: %d\n", DEFAULT_LOG_INTERVAL);
	printf("    -r rows      Number of log rows. Default: 0, i.e. run continuously\n");
	printf("    -S value     Set standard sea level pressure (Pascals) used to compute altitude. Default: 101325\n");
	printf("    -o option    Enable specified option (see below). You may use -o multiple times.\n");


	printf("\nOptions:\n");
	printf("    no_humidex_range     Calculate humidex even if input values are out of range.\n");
	printf("    no_heat_index_range  Calculate heat index even if the input values are out of range.\n");
	printf("    legacy_errors        Output channel errors in the old (unspecific) way.\n");
	printf("                         For instance: The string 'err' instead of 'ProbeDisconnected'\n");

	printf("\nValid temperature units:\n");
	printf("    Celsius, C, Fahrenheit, F, Kelvin, K\n");
	printf("\nValid pressure units:\n");
	printf("    kPa, hPa, Pa, bar, at (98.0665 kPa), atm (101.325 kPa), Torr, psi, inHg\n");
	printf("\nValid frequency units:\n");
	printf("    mHz, Hz, kHz, MHz, rpm\n");
	printf("\nValid length units:\n");
	printf("    mm, cm, dm, m, mil, in, ft, yd\n");
	printf("\nValid concentration units:\n");
	printf("    ppb, ppm, percent\n");

	printf("\nErrors:\n");
	printf("\nWhen an error occurs reading a channel, the value is replaced by an error string:\n");
	printf("    Undefined            Unknown/undefined error.\n");
	printf("    Saturated            Sensor (or resulting value) is saturated and unusable.\n");
	printf("    SensorError          The physical sensor or interface circuitry is not working properly\n");
	printf("    ProbeDisconnected    Indicates that the probe is disconnected or cable is cut/open\n");
	printf("    OutOfRange           The reading falls outside the sensor possible or supported range\n");
	printf("    InvalidData          The data received from the sensor did not make sense or was incomplete\n");
	printf("\n");

	printf("Note: If pretty output is enabled (see -p) there will be spaces in the error messages. See also\n");
	printf("the 'legacy_errors' option to restore the old behaviour of returning 'err', regarless\n");
	printf("of what the specific error was.\n");

	printf("\nReturn value:\n");
	printf(" - On success, dracal-usb-get returns 0.\n");
	printf(" - If the requested serial number (see -s) was not found, or if no devices were found (-f and -l) a non-zero value is returned.\n");
}

static const char COPYRIGHT[] =
	"\n"
	"Copyright (C) 2018-2025, Dracal Technologies Inc.\n"
	"Copyright (C) 2007-2017, Raphael Assenat\n"
	"\n"
	"This software is free software: you can redistribute it and/or modify it under the\n"
	"terms of the GNU General Public License as published by the Free Software Foundation,\n"
	"either version 3 of the License, or (at your option) any later version.\n"
	"\n"
	"This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;\n"
	"without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n"
	"See the GNU General Public License for more details.\n"
	"\n"
	"This software uses libusb, which can be found at: https://libusb.info\n"
	"libusb is Copyright (C) 2001 Johannes Erdfelt <johannes@erdfelt.com>, and other contributors.\n"
	"libusb is licensed under the GNU Lesser General Public License (LGPL), version 2.1.\n"
	"You can find a copy of this license at: https://www.gnu.org/licenses/lgpl-2.1.en.html\n"
	"\n"
;

static void printVersion(void)
{
	printf("\ndracal-usb-get version %s\n", USBTENKI_VERSION);
	fputs(COPYRIGHT, stdout);
}

int processChannels(Source *source, int *requested_channels, int num_req_chns);

static void printTimeStamp(FILE *stream)
{
	time_t t;
	struct timeval tv_now;
	struct tm *tm;

//	t = time(NULL);
	
	gettimeofday(&tv_now, NULL);
	t = tv_now.tv_sec;

	tm = localtime(&t);

	fprintf(stream, "%d-%02d-%02d %02d:%02d:%02d.%03ld", 
		tm->tm_year + 1900, tm->tm_mon+1, tm->tm_mday,
			tm->tm_hour, tm->tm_min, tm->tm_sec,
				(long)tv_now.tv_usec / 1000);

}

int main(int argc, char **argv)
{
	int res, i;

	char *use_serial = NULL;
	int list_mode = 0;

	int requested_channels[MAX_CHANNELS];
	int num_requested_channels= 1;
	int use_all_channels = 0;

	requested_channels[0] = DEFAULT_CHANNEL_ID;

#ifdef _WIN32
	/* Under windows, line buffering (_IOLBF) has fullly buffered behaviour.. No
	 * choice but to completely disable buffering... */
	setvbuf(stdout, NULL, _IONBF, 0);
#else
	setlinebuf(stdout);
#endif

	while (-1 != (res=getopt(argc, argv, "Vvhlfs:i:T:P:p7uR:L:I:r:F:o:x:S:M:C:")))
	{
		switch (res)
		{
			case 'R':
				g_num_attempts = atoi(optarg) + 1;
				break;
			case 'x':
				{
					char *e;
					double x;

					x = strtod(optarg, &e);
					if (e==optarg) {
						fprintf(stderr, "Invalid argument -x\n");
						return -1;
					}
					if (x < 0 || x > 6) {
						fprintf(stderr, "Argument -x must be between 0 and 6\n");
						return -1;
					}
                    g_decimal_digits = x;
				}
				break;
			case 'V':
				printVersion();
				return 0;
			case 'v':
				g_verbose = 1;
				break;
			case 'h':
				printUsage();
				return 0;
			case '7':
				g_7bit_clean = 1;
				break;
			case 'u':
				g_user_calibration = 0;
				break;
			case 'l':
				list_mode = 1;
				break;
			case 'f':
				// default. Accept for backward compatibility
				break;
			case 's':
				use_serial = optarg;
				break;
			case 'i':
				{
					char *p;
					char *e;

					if (*optarg == 'a')
					{
						num_requested_channels = 0;
						use_all_channels = 1;
						break;
					}


					num_requested_channels = 0;
					p = optarg;
					while(1) {
						if (num_requested_channels >= MAX_CHANNELS) {
							fprintf(stderr,"too many channels\n");
							return -1;
						}

						requested_channels[num_requested_channels] = strtol(p, &e, 0);
						if (e==p) {
							fprintf(stderr, "Error in channel list\n");
							return -1;
						}

						num_requested_channels++;

						if (*e==0)
							break;

						if (*e==',') {
							e++;
						}
						else {
							fprintf(stderr, "Error in channel list\n");
							return -1;
						}

						p = e;
					}
				}
				break;

			case 'T':
				if (strcasecmp(optarg, "Celsius")==0 ||
						strcasecmp(optarg, "Celcius")==0 ||  // backward-compatibility with old typo
						strcasecmp(optarg, "C")==0)
					g_unit_prefs[UNIT_CATEGORY_TEMPERATURE] = UNIT_CELSIUS;
				else if (strcasecmp(optarg, "Fahrenheit")==0 ||
						strcasecmp(optarg, "F")==0)
					g_unit_prefs[UNIT_CATEGORY_TEMPERATURE] = UNIT_FAHRENHEIT;
				else if (strcasecmp(optarg, "Kelvin")==0 ||
						strcasecmp(optarg, "K")==0)
					g_unit_prefs[UNIT_CATEGORY_TEMPERATURE] = UNIT_KELVIN;
				else {
					fprintf(stderr, "Unknown temperature format: '%s'\n",
										optarg);
					return -1;
				}
				break;

			case 'F':
				{
					struct {
						const char *name;
						int fmt;
					} tbl[] = {
						{ "mHz", UNIT_MILLIHZ },
						{ "Hz", UNIT_HZ },
						{ "kHz", UNIT_KHZ },
						{ "MHz", UNIT_MHZ },
						{ "rpm", UNIT_RPM },
					};

					for (i=0; i<ARRAY_SIZE(tbl); i++) {
						if (strcasecmp(tbl[i].name, optarg)==0) {
							g_unit_prefs[UNIT_CATEGORY_FREQUENCY] = tbl[i].fmt;
							break;
						}
					}
					if (i==ARRAY_SIZE(tbl)) {
						fprintf(stderr,
							"Unknown frequency unit: '%s'\n", optarg);
					}
				}
				break;

			case 'P':
				{
					struct {
						const char *name;
						int fmt;
					} tbl[] = {
						{ "pa", UNIT_PA },
						{ "kpa", UNIT_KPA },
						{ "hpa", UNIT_HPA },
						{ "bar", UNIT_BAR },
						{ "at", UNIT_AT },
						{ "atm", UNIT_ATM },
						{ "torr", UNIT_TORR },
						{ "psi", UNIT_PSI },
						{ "inhg", UNIT_INHG },
					};

					for (i=0; i<ARRAY_SIZE(tbl); i++) {
						if (strcasecmp(tbl[i].name, optarg)==0) {
							g_unit_prefs[UNIT_CATEGORY_PRESSURE] = tbl[i].fmt;
							break;
						}
					}
					if (i==ARRAY_SIZE(tbl)) {
						fprintf(stderr,
							"Unknown pressure format: '%s'\n", optarg);
					}
				}
				break;

			case 'M':
				{
					struct {
						const char *name;
						int fmt;
					} tbl[] = {
						{ "m", UNIT_METER },
						{ "dm", UNIT_DECIMETER },
						{ "cm", UNIT_CENTIMETER },
						{ "mm", UNIT_MILLIMETER },
						{ "mil", UNIT_MIL },
						{ "mils", UNIT_MIL },
						{ "in", UNIT_INCH },
						{ "ft", UNIT_FEET },
						{ "yd", UNIT_YARD },
					};

					for (i=0; i<ARRAY_SIZE(tbl); i++) {
						if (strcasecmp(tbl[i].name, optarg)==0) {
							g_unit_prefs[UNIT_CATEGORY_LENGTH] = tbl[i].fmt;
							break;
						}
					}
					if (i==ARRAY_SIZE(tbl)) {
						fprintf(stderr,
							"Unknown length unit: '%s'\n", optarg);
					}
				}
				break;

			case 'C':
				{
					struct {
						const char *name;
						int fmt;
					} tbl[] = {
						{ "ppb", UNIT_PPB },
						{ "ppm", UNIT_PPM },
						{ "percent", UNIT_PERCENT },
						{ "sensor", UNIT_SENSOR_DEFAULT },
					};

					for (i=0; i<ARRAY_SIZE(tbl); i++) {
						if (strcasecmp(tbl[i].name, optarg)==0) {
							g_unit_prefs[UNIT_CATEGORY_CONCENTRATION] = tbl[i].fmt;
							break;
						}
					}
					if (i==ARRAY_SIZE(tbl)) {
						fprintf(stderr,
							"Unknown concentration unit: '%s'\n", optarg);
					}
				}
				break;


			case 'o':
				if (!strcasecmp(optarg, "no_heat_index_range")) {
					usbtenki_virtual_options.flags |= VIRTUAL_FLAG_NO_HEAT_INDEX_RANGE;
				}
				else if (!strcasecmp(optarg, "no_humidex_range")) {
					usbtenki_virtual_options.flags |= VIRTUAL_FLAG_NO_HUMIDEX_RANGE;
				}
				else if (!strcasecmp(optarg, "legacy_errors")) {
					g_legacy_errors = 1;
				}
				else {
					fprintf(stderr, "Unknown option: '%s'\n", optarg);
				}
				break;

			case 'p':
				g_pretty = 1;
				break;

			case 'L':
				g_log_file = optarg;
				break;

			case 'I':
				{
					char *e;
					g_log_interval = strtol(optarg, &e, 0);
					if (e==optarg) {
						fprintf(stderr, "Invalid log interval\n");
						return -1;
					}
				}
				break;

			case 'r':
				{
					char *e;
					g_num_rows = strtol(optarg, &e, 0);
					if (e==optarg) {
						fprintf(stderr, "Invalid number of rows\n");
						return -1;
					}
				}
				break;

			case 'S':
				{
					char *e;
					double slp;

					slp = strtod(optarg, &e);
					if (e==optarg) {
						fprintf(stderr, "Invalid pressure\n");
						return -1;
					}
					usbtenki_virtual_options.standard_sea_level_pressure = slp;
				}
				break;

			case '?':
				fprintf(stderr, "Unknown argument specified\n");
				return -1;
		}
	}

	n_extra_args = argc-optind;
	for (i=optind; i<argc; i++) {
		eargv[i-optind] = argv[i];
		if (g_verbose)
			printf("  %d: %s\n", i-optind, eargv[i-optind]);
	}

	if (n_extra_args>0) {
		fprintf(stderr, "Unknown command: %s\n", eargv[0]);
		return 1;
	}

	if (g_verbose) {
		printf("Arguments {\n");
		printf("  verbose: yes\n");
		printf("  use_all_channels: %d\n", use_all_channels);
		printf("  channel id(s): ");
		for (i=0; i<num_requested_channels; i++) {
			printf("%d ", requested_channels[i]);
		}
		printf("\n");
		printf("  list_mode: %d\n", list_mode);
		if (use_serial)
			printf("  use_serial: %s\n", use_serial);
		if (g_log_file) {
			printf("  log file: %s\n", g_log_file);
			printf("  log interval (ms): %d\n", g_log_interval);
		}
		printf("Extra args: %d\n", n_extra_args);
		printf("}\n");
	}

	usbtenki_provider_init();

	uint8_t source_count = usbtenki_provider_get_source_count();
	Source *found_source = NULL;

	for (uint8_t i = 0; i < source_count; i++) {

		Source *source = usbtenki_provider_get_source(i);
		Device *device = source->device;

		if (use_serial) {
			if (DEVICE_SERIAL_NUMBERS_EQUAL(use_serial, device->serial_number)) {
				found_source = source;
			}
			else {
				continue;
			}
		}
		else {
			found_source = source; // last found will be used
		}

		if (list_mode || g_verbose) {

			printf("Found: '%s', ", device->product_name);
			printf("Serial: '%s', ", device->serial_number);
			printf("Version %d.%d, ", device->version.major,
									device->version.minor);
		
			if (list_mode)
			{

				int n_channels = device->channels.size;
				int n_virtual_channels = source->virtual_channels.size;

				printf("Channels: %d\n", n_channels + n_virtual_channels);

				// Physical channels

				for (int c = 0; c < n_channels; c++) {

					Channel *channel = LIST_GET(&device->channels, c, Channel);

					if (!g_full_display_mode && channel->chip_id == USBTENKI_CHIP_NONE)
						continue;

					printf("    Channel %d: %s [%s]\n",
							c,
							chip_description(channel->chip_id),
							chip_description_short(channel->chip_id));

				}

				// Virtual channels

				for (int c = 0; c < source->virtual_channels.size; c++) {

					VirtualChannel *vc = LIST_GET(&source->virtual_channels, c, VirtualChannel);
					Channel *channel = &vc->channel;

					printf("    Virtual Channel %d: %s [%s]\n",
							channel->chip_id,  // TODO
							chip_description(channel->chip_id),
							chip_description_short(channel->chip_id));
				}

			}
		}
	}

	if (list_mode) {
		return 0;
	}

	if (!found_source) {
		if (use_serial)
			fprintf(stderr, "Device with serial '%s' not found\n", use_serial);
		else {
			fprintf(stderr, "No device found\n");
		}
		return 1;
	}

	if (g_log_file)
	{
		if (!g_log_fptr) {
			printf("Log mode on.\n");
			if (strcmp(g_log_file, "-"))
			{
				g_log_fptr = fopen(g_log_file, "a");
				if (!g_log_fptr) {
					fprintf(stderr, "Failed to open log file\n");
					return -1;
				}
				printf("Opened file '%s' for logging. Append mode.\n", g_log_file);

#ifdef _WIN32
				setvbuf(stdout, NULL, _IONBF, 0);
#else
				setlinebuf(g_log_fptr);
#endif
			}
			else {
				printf("Logging to stdout\n");
			}
		}

		int curr_row = 0;
		while (1)
		{
			if (g_log_fptr) {
				printTimeStamp(g_log_fptr);
				fprintf(g_log_fptr, ", ");
			}

			res = processChannels(found_source, requested_channels, num_requested_channels);

			if (g_log_fptr) {
				fprintf(g_log_fptr, "\n");
				fflush(g_log_fptr);
			}

			curr_row += res == 0 ? 1 : 0;
			if (curr_row < g_num_rows || g_num_rows == 0) {
				usleep(g_log_interval * 1000);
			} 
			else {
				break;
			}
		}

		if (g_log_fptr) {
			if (g_log_fptr != stdout) {
				printf("Closing log file.\n");
			}
			fflush(g_log_fptr);
			fclose(g_log_fptr);
		}
	}
	else {
		processChannels(found_source, requested_channels, num_requested_channels);
		if (!g_pretty){
			printf("\n");
		}

	}

	usbtenki_provider_shutdown();

	return 0;

}

int processChannels(Source *source, int *requested_channels, int num_req_chns)
{

	if (!usbtenki_provider_is_connected(source)) {
		usbtenki_provider_detect_reconnections();
		if (!usbtenki_provider_is_connected(source)) {
			return -1;
		}
	}

	char fmt[16];
	sprintf(fmt, "%%.%df", g_decimal_digits);
	
	Device *device = source->device;
	int num_channels = device->channels.size;

	if (g_verbose)
		printf("Device has %d channels\n", num_channels + (int)source->virtual_channels.size);

	/* When user request all channels, num_req_chns is 0. Generate a
	 * list of requested channels using all available real and
	 * virtual channels */
	if (!num_req_chns) {
		if (g_verbose)
			printf("All channels requested\n");

		// Physical channels
		for (int i=0; i<num_channels; i++) {
			Channel *channel = LIST_GET(&device->channels, i, Channel);

			if (!g_full_display_mode &&
					channel->chip_id == USBTENKI_CHIP_NONE)
				continue; // skip unused channels unless in full list

			requested_channels[num_req_chns++] = i;
		}

		// Virtual channels
		LIST_FOR(&source->virtual_channels) {
			VirtualChannel *vc = LIST_CUR(VirtualChannel);
			requested_channels[num_req_chns++] = vc->channel.chip_id;
		}

	}

	if (!usbtenki_provider_is_connected(source)) {
		if (g_verbose)
			printf("Disconnected\n");
		return -1;
	}

	if (g_verbose)
		printf("Reading device...\n");
	
	uint16_t calibrated_channels;
	USBTenki_dev_handle handle = usbtenki_provider_find_handle(device->serial_number);

	if (handle) {
		int res = usbtenki_getUserCalibrationChannels(handle, &calibrated_channels);
		if (res == 0) {
			calibrated_channels = 0xFFFF;
		}
		usbtenki_closeDevice(handle);
	}
	else {
		if (g_verbose)
			printf("Cannot open device\n");
			return -1;
	}

	if (g_user_calibration) {
		memset(source->user_calibration_enabled, 0xff, sizeof(source->user_calibration_enabled));
		source->user_calibration_enabled[0] = source->user_calibration_enabled[0] & calibrated_channels;
	}
	else {
		memset(source->user_calibration_enabled, 0, sizeof(source->user_calibration_enabled));
	}

	usbtenki_provider_poll();

	if (g_verbose)
		printf("Device read successfully\n");

	for (int i=0; i<num_req_chns; i++) {

		int c = requested_channels[i];
		Channel *channel = NULL;

		if (c < DEVICE_MAX_CHANNELS) {
			// Physical channel
			if (c < num_channels) {
				List *channels = g_user_calibration ? &(source->calibrated_channels) : &(source->device->channels);
				channel = LIST_GET(channels, c, Channel);
			}
		}
		else {
			// Virtual channel
			LIST_FOR(&source->virtual_channels) {
				VirtualChannel *vc = LIST_CUR(VirtualChannel);
				if (vc->channel.chip_id == c) {
					channel = &vc->channel;
					break;
				}
			}
		}

		if (!channel) {
			fprintf(stderr, "Requested channel %d does not exist.\n", c);
			return -1;
		}

		if (g_pretty) {
			printf("%s: ", chip_description(channel->chip_id));
		}

		Quantity *quantity = &channel->quantity;

		if (quantity->type == QUANTITY_TYPE_ERROR) {
			if (g_pretty) {
				printf("%s\n", g_legacy_errors ? "err" : chip_error_to_string(quantity->value_error));
			}
			else {
				printf("%s", g_legacy_errors ? "err" : chip_error_to_string_no_spaces(quantity->value_error));
				if (i<num_req_chns-1)
					printf(", ");
			}
		}
		else {

			// convert channel to preferred unit
			unit_t channel_unit = quantity->unit;
			unit_category_t unitcat = unit_category(channel_unit);
			unit_t pref_unit = g_unit_prefs[unitcat];
			quantity_convert_to_unit(quantity, pref_unit);
			quantity_convert_to_float(quantity);

			printf(fmt, quantity->value_float);
			if (g_pretty) {
				printf(" %s\n", unit_to_string(quantity->unit, g_7bit_clean));
			}
			else {
				i < num_req_chns - 1 ? printf(", ") : printf("\n");
			}
		}

		if (g_log_fptr) {
			float value = quantity->type == QUANTITY_TYPE_FLOAT ? quantity->value_float : NAN;
			fprintf(g_log_fptr, fmt, value);
			if (i<num_req_chns-1){
				fprintf(g_log_fptr, ", ");
			}
		}

	}

	return 0;

}
