/* dracal-usb-set: A command-line tool for Dracal sensors.
 *
 * Copyright (C) 2018-2024  Dracal Technologies Inc.
 * Copyright (C) 2007-2016  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 <getopt.h>

#include "usbtenki.h"
#include "usbtenki_provider.h"
#include "usbtenki_cmds.h"
#include "usbtenki_version.h"
#include "chip.h"
#include "source.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


int g_verbose = 0;
int g_deep_trace = 0;

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-set version %s\n", USBTENKI_VERSION);
	fputs(COPYRIGHT, stdout);
}

static void printUsage(void)
{
	printf("Usage: ./dracal-usb-set [arguments] command [command arguments ...]\n");

	printf("\nValid arguments:\n");
	printf("    -V          Display version information\n");
	printf("    -v          Run in verbose mode\n");
	printf("    -h          Displays help\n");
	printf("    -s serial   Operate on the device matching the given serial number.\n");
	printf("    -f          Operate on the first device found.\n");

	printf("\nValid commands:\n");
	printf("    setadcchip  adc_id chip\n");
	printf("    setserial   serial (6 characters)\n");
	printf("    setref      ref_id (0=AVCC, 1=AREF)\n");
	printf("    set_rtd_cal	value (-32768 to 32767)\n");
	printf("    set_dxc120_frc  value (400 to 2000)\n");
	printf("    set_dxc120_asc  value (ON or OFF)\n");
	printf("    em1_config  max_current  calibration\n");
	printf("    do_zero          Device specific effect.\n");
	printf("    dxc200_kz ppm    Set zero in known gas concentration (for DXC-200)\n");
	printf("    sht31_rate	rate (0: 0.5 MPS, 1: 1 MPS, 2: 2 MPS, 3: 4 MPS, 4: 10 MPS)\n");
	printf("                (MPS is measurements per second)\n");
	printf("    set_thermocouple_type channel type\n");
	printf("       (where channel is the port number [counted from 0])\n");
	printf("       (where type is the thermocouple type K, J, T, N, S, E, B, or R)\n");
	printf("    get_thermocouple_type channel\n");
	printf("    bootloader                 Enter bootloader mode\n");
	printf("                               (not supported by all devices)\n");
	printf("    set_protocol  protocol_id  Switch the device to another protocol.\n");
	printf("                               0 or USB  : Regular mode (usbtenki)\n");
	printf("                               1 or NMEA : NMEA 0183 over CDC-ACM (Virtual COM Port)\n");
	printf("                                           (not supported by all devices)\n");
	printf("                               2 or VCP  : Text over CDC-ACM (Virtual COM Port)\n");
	printf("                                           (not supported by all devices)\n");
	printf("    reset           Restart the firmware (not supported by all devices)\n");
}

#define MAX_EXTRA_ARGS	8

//////////////////////////////////////////
/// Helper functions for DXC320 recalibration

static int dxc_calibrate(USBTenki_dev_handle hdl, int ppm, int offset);

// shift_f
//
// Given a quadratic f1 with known coefficients and a reference crossing
// f1(x0) ≈ level, compute new coefficients for
//
//   f2(x) = s * (f1(x - delta) - level) + level,   delta = x1 - x0
//
// so that f2(x1) = level.
//
// Expanding:
//   b2 =  s * a2
//   b1 =  s * (a1 - 2*a2*delta)
//   b0 =  s * (a2*delta^2 - a1*delta + a0)  +  (1 - s)*level
//
// Parameters:
//   coeffs     [in]  original coefficients {a0, a1, a2}
//   x0         [in]  x where f1(x0) = level  (reference crossing)
//   x1         [in]  x where f2(x1) = level  (target crossing)
//   s          [in]  vertical scale factor (1.0 = no scaling)
//   level      [in]  crossing level (1.0 in the real-world example)
//   new_coeffs [out] shifted coefficients {b0, b1, b2}
//
static void shift_f(const double coeffs[3],
             double x0, double x1,
             double s, double level,
             double new_coeffs[3])
{
    double a0 = coeffs[0], a1 = coeffs[1], a2 = coeffs[2];
    double delta = x1 - x0;

    new_coeffs[2] =  s * a2;
    new_coeffs[1] =  s * (a1 - 2.0f * a2 * delta);
    new_coeffs[0] =  s * (a2 * delta*delta - a1 * delta + a0)
                     + (1.0f - s) * level;
	if (g_verbose) {
		printf("shift: ");
		printf("%g %g %g -> ", coeffs[0], coeffs[1], coeffs[2]);
		printf("%g %g %g\n", new_coeffs[0], new_coeffs[1], new_coeffs[2]);
	}
}

//////////////////////////////////////////

int main(int argc, char **argv)
{
	int res, i;
	int use_first = 0;
	char *use_serial = NULL;
	int n_extra_args=0;
	char *eargv[MAX_EXTRA_ARGS];
	USBTenki_dev_handle hdl = NULL;
	unsigned char repBuf[32];
	int retval = 0;

	while (-1 != (res=getopt(argc, argv, "Vvhfs:")))
	{
		switch (res)
		{
			case 'f':
				use_first = 1;
				break;
			case 'v':
				g_verbose = 1;
				break;
			case 'V':
				printVersion();
				return 0;
			case 'h':
				printUsage();
				return 0;
			case 's':
				use_serial = optarg;
				break;
		}
	}

	n_extra_args = argc-optind;
	if (!use_serial && !use_first) {
		fprintf(stderr, "Serial number is required.\n");
		return 1;
	}

	if (g_verbose) {
		printf("Extra args: %d\n", n_extra_args);
	}

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

	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 (!found_source) {
		fprintf(stderr, "Could not locate device with serial '%s'. Try usbtekiget -l\n",
							use_serial);
		return 1;
	}

	if (n_extra_args<1) {
		fprintf(stderr, "No command specified\n");
		return 1;
	}

	hdl = usbtenki_provider_find_handle(found_source->device->serial_number);
	if (!hdl) {
		fprintf(stderr, "Cannot open device\n");
		return 2;
	}

	/**************** Setref ****************/
	if (strcmp(eargv[0], "setref")==0) {
		int ref_id;
		char *e;

		/* printf("    setadcchip  adc_id chip\n"); */

		if (n_extra_args<2) {
			fprintf(stderr, "Missing arguments to command\n");
			retval = 1;
			goto cleanAndExit;
		}

		ref_id = strtol(eargv[1], &e, 0);
		if (e==eargv[1]) {
			fprintf(stderr, "Bad ref id\n");
			retval = 1;
			goto cleanAndExit;
		}

	if (g_verbose)
			printf("Setting adc ref to %d\n", ref_id);

		res = usbtenki_command(hdl, USBTENKI_SET_ADC_REF,
								(ref_id & 0xff), repBuf, sizeof(repBuf));
		if (res!=0) {
			fprintf(stderr, "Error setting adc ref to %d\n",
								ref_id);
			retval = 2;
		}

		goto cleanAndExit;
	}

	/**************** Do zero ****************/
	if (strcmp(eargv[0], "do_zero")==0) {

		res = usbtenki_command(hdl, USBTENKI_ZERO, 0, repBuf, sizeof(repBuf));
		if (res!=0) {
			fprintf(stderr, "Error doing zero.n");
			retval = 2;
		}

		goto cleanAndExit;
	}



	/**************** Setref ****************/
	if (strcmp(eargv[0], "set_rtd_cal")==0) {
		int ref_id;
		char *e;

		/* printf("    setadcchip  adc_id chip\n"); */

		if (n_extra_args<2) {
			fprintf(stderr, "Missing arguments to command\n");
			retval = 1;
			goto cleanAndExit;
		}

		ref_id = strtol(eargv[1], &e, 0);
		if (e==eargv[1]) {
			fprintf(stderr, "Bad ref id\n");
			retval = 1;
			goto cleanAndExit;
		}
		
	if (g_verbose) 
			printf("Setting rtd calibration to %d\n", ref_id);

		res = usbtenki_command(hdl, USBTENKI_SET_RTD_CORR, 
								(ref_id & 0xffff), repBuf, sizeof(repBuf));
		if (res!=0) {
			fprintf(stderr, "Error setting adc calibration to %d\n",
								ref_id);
			retval = 2;
		}

		goto cleanAndExit;
	}

	/**************** Setadcchip ****************/
	if (strcmp(eargv[0], "setadcchip")==0) {
		int adc_id, chip_id;
		char *e;

		/* printf("    setadcchip  adc_id chip\n"); */

		if (n_extra_args<3) {
			fprintf(stderr, "Missing arguments to command\n");
			retval = 1;
			goto cleanAndExit;
		}

		adc_id = strtol(eargv[1], &e, 0);
		if (e==eargv[1]) {
			fprintf(stderr, "Bad adc id\n");
			retval = 1;
			goto cleanAndExit;
		}

		chip_id = strtol(eargv[2], &e, 0);
		if (e==eargv[1]) {
			fprintf(stderr, "Bad chip id\n");
			retval = 1;
			goto cleanAndExit;
		}

		if (g_verbose) 
			printf("Setting adc channel %d to chip_id %d\n", adc_id, chip_id);

		res = usbtenki_command(hdl, USBTENKI_SET_ADC_CHIP, 
								(adc_id & 0xff) | ((chip_id&0xff)<<8), repBuf, sizeof(repBuf));
		if (res!=0) {
			fprintf(stderr, "Error setting channel %d to chip_id %d\n",
								adc_id, chip_id);
			retval = 2;
		}

		goto cleanAndExit;
	}


	/***************** Setserial ***************/
	if (strcmp(eargv[0], "setserial")==0) {
		int i, len;

		if (n_extra_args<2) {
			fprintf(stderr, "No serial number specified\n");
			retval = 1;
			goto cleanAndExit;
		}

		len = strlen(eargv[1]);
		if (len != 6) {
			fprintf(stderr, "Serial number must be 6 character long\n");
			retval = 1;
			goto cleanAndExit;
		}

		for (i=0; i<len; i++) {	
			if (g_verbose) 
				printf("Setting serial number character '%c'\n", eargv[1][i]);

			res = usbtenki_command(hdl, USBTENKI_SET_SERIAL, 
									(i&0xff) | (eargv[1][i]<<8), repBuf, sizeof(repBuf));
			if (res!=0) {
				fprintf(stderr, "Error writing character '%c'. (%d)\n",
								eargv[1][i], res);
				retval = 2;
				goto cleanAndExit;
			}
		}
		/* index 0xff means store to eeprom. */
		res = usbtenki_command(hdl, USBTENKI_SET_SERIAL, 
								(0xff) | (eargv[1][i]<<8), repBuf, sizeof(repBuf));
		if (res != 0) {
			retval = 2;
			goto cleanAndExit;
		}
		
		goto cleanAndExit;
	}

	/*************** EM1 config ******************/
	if (strcmp(eargv[0], "em1_config")==0) {
		int max_current;
		int calibration;
		char *e;
		
		/* printf("    em1_config  max_current  calibration\n"); */

		if (n_extra_args<3) {
			fprintf(stderr, "Missing arguments to command\n");
			retval = 1;
			goto cleanAndExit;
		}

		max_current = strtol(eargv[1], &e, 0);
		if (e==eargv[1]) {
			fprintf(stderr, "Invalid max current\n");
			retval = 1;
			goto cleanAndExit;
		}

		calibration = strtol(eargv[2], &e, 0);
		if (e==eargv[1]) {
			fprintf(stderr, "Invalid calibration\n");
			retval = 1;
			goto cleanAndExit;
		}

		if (g_verbose) 
			printf("Setting em1 config: Max current=%d,  calibration=0x%04x\n", max_current, calibration);

		res = usbtenki_command(hdl, USBTENKI_SET_EM1_CALIBRATION, 
								calibration, repBuf, sizeof(repBuf));
		if (res!=0) {
			fprintf(stderr, "Error setting calibration\n");
			retval = 2;
		}

		res = usbtenki_command(hdl, USBTENKI_SET_EM1_MAX_CURRENT, 
								max_current, repBuf, sizeof(repBuf));
		if (res!=0) {
			fprintf(stderr, "Error setting calibration\n");
			retval = 2;
		}

		goto cleanAndExit;
	}

	/**************** SHT31 rate *****************/
	if (strcmp(eargv[0], "sht31_rate")==0) {
		int rate_value;
		char *e;

		if (n_extra_args<2) {
			fprintf(stderr, "Missing arguments to command\n");
			retval = 1;
			goto cleanAndExit;
		}

		rate_value = strtol(eargv[1], &e, 0);
		if (e==eargv[1] || rate_value < 0 || rate_value > 4) {
			fprintf(stderr, "Bad rate value\n");
			retval = 1;
			goto cleanAndExit;
		}

		if (g_verbose)
			printf("Setting SHT31 poll rate to value %d\n", rate_value);

		res = usbtenki_command(hdl, USBTENKI_SET_SHT31_RATE, rate_value, repBuf, sizeof(repBuf));
		if (res!=0) {
			fprintf(stderr, "Error setting SHT31 poll rate to value %d\n",
								rate_value);
			retval = 2;
		}

		goto cleanAndExit;
	}

	/************ Configuring thermocouple type **************/
	if (strcmp(eargv[0], "set_thermocouple_type")==0) {
		int chn, type;
		char *e;

		if (n_extra_args<3) {
			fprintf(stderr, "Missing arguments to command\n");
			retval = 1;
			goto cleanAndExit;
		}

		chn = strtol(eargv[1], &e, 0);
		if (e == eargv[1] || chn < 0) {
			fprintf(stderr, "Bad channel value\n");
			retval = 1;
			goto cleanAndExit;
		}

		type = chip_thermocouple_string_to_type(eargv[2]);
		if (type < THERMOCOUPLE_TYPE_MIN || type > THERMOCOUPLE_TYPE_MAX) {
			fprintf(stderr, "Bad thermocouple type\n");
			retval = 1;
			goto cleanAndExit;
		}

		if (g_verbose) {
			printf("Setting thermocouple[%d] to type %d\n", chn, type);
		}

		res = usbtenki_command(hdl, USBTENKI_SET_THERMOCOUPLE_TYPE, chn | type << 8, repBuf, sizeof(repBuf));
		if (res != 0) {
			fprintf(stderr, "Error configuring thermocouple\n");
			retval = 2;
		}

		goto cleanAndExit;
	}

	/************ Reading back the configured thermocouple type ***************/
	if (strcmp(eargv[0], "get_thermocouple_type")==0) {
		int chn;
		char *e;

		if (n_extra_args < 2) {
			fprintf(stderr, "Missing arguments to command\n");
			retval = 1;
			goto cleanAndExit;
		}

		chn = strtol(eargv[1], &e, 0);
		if (e == eargv[1] || chn < 0) {
			fprintf(stderr, "Bad channel value\n");
			retval = 1;
			goto cleanAndExit;
		}

		res = usbtenki_command(hdl, USBTENKI_GET_THERMOCOUPLE_TYPE, chn, repBuf, sizeof(repBuf));
		if (res < 1) {
			fprintf(stderr, "Error reading thermocouple configuration\n");
			retval = 2;
			goto cleanAndExit;
		}

		printf("Thermocouple type configured for channel %d: Type %s\n", chn,
				chip_thermocouple_type_to_string(repBuf[0]));

		goto cleanAndExit;
	}

	/**************** Set zero in known gas concentration (DXC-200 CO2 Sensor) ****************/
	if (strcmp(eargv[0], "dxc200_kz")==0) {
		int ppm;
		char *e;

		if (n_extra_args<2) {
			fprintf(stderr, "Missing arguments to command\n");
			retval = 1;
			goto cleanAndExit;
		}

		ppm = strtol(eargv[1], &e, 0);
		if (e == eargv[1]) {
			fprintf(stderr, "Bad PPM value\n");
			retval = 1;
			goto cleanAndExit;
		}

		printf("Setting zero in known gas concentration of %d ppm\n", ppm);

		res = usbtenki_set_dxc220_kz(hdl, ppm);
		if (res < 0) {
			fprintf(stderr, "Error setting zero in known concentration\n");
			retval = 2;
		}

		goto cleanAndExit;
	}

	/**************** Set zero in known gas concentration (DXC-300 CO2 Sensor) ****************/
	if (strcmp(eargv[0], "dxc300_kz")==0) {
		int ppm;
		int offset = 0;
		char *e;

		if (n_extra_args<2) {
			fprintf(stderr, "Missing arguments to command\n");
			retval = 1;
			goto cleanAndExit;
		}

		ppm = strtol(eargv[1], &e, 0);
		if (e == eargv[1]) {
			fprintf(stderr, "Bad PPM value\n");
			retval = 1;
			goto cleanAndExit;
		}

		if (n_extra_args == 3) {
			offset = strtol(eargv[2], &e, 0);
			if (e == eargv[2]) {
				fprintf(stderr, "Bad offset value\n");
				retval = 1;
				goto cleanAndExit;
			}
		}

		printf("Setting zero in known gas concentration of %d ppm with offset %d\n", ppm, offset);

		int res = dxc_calibrate(hdl, ppm, offset);

		if (res < 0) {
			fprintf(stderr, "Error setting zero in known concentration\n");
			retval = 2;
		}

		goto cleanAndExit;
	}

	/**************** set_protocol *****************/
	if (strcmp(eargv[0], "set_protocol")==0) {
		int protocol_id;
		char *protocol_name;
		char *e;

		if (n_extra_args<2) {
			fprintf(stderr, "Missing arguments to command\n");
			retval = 1;
			goto cleanAndExit;
		}

		if (!strcasecmp(eargv[1], "USB")) {
			protocol_id = USBTENKI_PROTOCOL_ID_STANDARD;
			protocol_name = "USB";
		}
		else if (!strcasecmp(eargv[1], "NMEA")) {
			protocol_id = USBTENKI_PROTOCOL_ID_NMEA;
			protocol_name = "NMEA";
		}
		else if (!strcasecmp(eargv[1], "VCP")) {
			protocol_id = USBTENKI_PROTOCOL_ID_VCP;
			protocol_name = "VCP";
		}
		else {
			protocol_id = strtol(eargv[1], &e, 0);
			if (e==eargv[1]) {
				protocol_id = -1;
			}
		}

		switch (protocol_id) {

			case USBTENKI_PROTOCOL_ID_STANDARD:
				protocol_name = "USB";
				break;

			case USBTENKI_PROTOCOL_ID_NMEA:
				protocol_name = "NMEA";
				break;

			case USBTENKI_PROTOCOL_ID_VCP:
				protocol_name = "VCP";
				break;

			default:
				fprintf(stderr, "Unknown protocol\n");
				retval = 1;
				goto cleanAndExit;

		}

		if (g_verbose)
			printf("Setting protocol to %s (id=%d)\n", protocol_name, protocol_id);

		res = usbtenki_command(hdl, USBTENKI_SET_PROTOCOL, protocol_id, repBuf, sizeof(repBuf));
		if (res!=0) {
			fprintf(stderr, "Error setting protocol to %s (id=%d)\n", protocol_name, protocol_id);
			retval = 2;
		}

		goto cleanAndExit;
	}

	/**************** Reset *****************/
	if (strcmp(eargv[0], "reset")==0) {
		usbtenki_command(hdl, USBTENKI_RESET_FIRMWARE, 0, repBuf, sizeof(repBuf));

		goto cleanAndExit;
	}

	/**************** Bootloader *****************/
	if (strcmp(eargv[0], "bootloader") == 0) {
		usbtenki_command(hdl, USBTENKI_BOOTLOADER, 0xB007, repBuf, sizeof(repBuf));

		goto cleanAndExit;
	}

	/****************** DXC120 AUTO CALIBRATION ***********/

	if (strcmp(eargv[0], "set_dxc120_asc") == 0) {
		int value=0;
		char *setoption;

		if (n_extra_args<2) {
		     fprintf(stderr, "Missing arguments to command\n");
			 retval = 1;
			goto cleanAndExit;
		}

		if (!strcasecmp(eargv[1], "ON")) {
			 value = 1 ;
			 setoption="ON";
		} else if (!strcasecmp(eargv[1], "OFF")) {
			 value = 0 ;
			 setoption="OFF";
		} else {
             fprintf(stderr, "Invalid argument: %s\n", eargv[1]);
             retval = 1;
              goto cleanAndExit;
        }

		if (g_verbose)
		     printf("Setting DXC120 auto-calibration to %s\n", setoption);

		
		res = usbtenki_command(hdl,USBTENKI_SCD30_ENABLE_ASC , value, repBuf, sizeof(repBuf));
		if (res!=0) {
		     fprintf(stderr, "Error setting DXC120 auto-calibration to %s\n", setoption);
			 retval = 2;
		}

		goto cleanAndExit;
	}

	/****************** DXC120 MANUAL CALIBRATION ***********/
    if (strcmp(eargv[0], "set_dxc120_frc")==0) {
		
		int value = 0;
		char *e;
		int min = 400;
		int max = 2000;  // C02 concentration range

        if (n_extra_args<2) {
		     fprintf(stderr, "Missing arguments to command\n");
			 retval = 1;
			 goto cleanAndExit;
		}
        
		value = strtol(eargv[1], &e, 0);

		if ( value < min || value> max) {
			 fprintf(stderr, "Bad PPM value\n");
			 retval = 1;
			 goto cleanAndExit;
	    }
       
		printf("Setting DXC120 calibration value to %d \n", value);
		
		res = usbtenki_command(hdl,USBTENKI_SCD30_SET_FRC,  value , repBuf, sizeof(repBuf));

		if (res!=0) {
			 fprintf(stderr, "Error setting DXC120 calibration value to %d\n",value);
			retval = 2;
		}

		goto cleanAndExit;
	}

	fprintf(stderr, "Unknow command '%s'\n", eargv[0]);

	return 1;

cleanAndExit:
	if (hdl)
		usbtenki_closeDevice(hdl);
		
	usbtenki_provider_shutdown();
	return retval;
}

static int dxc_calibrate(USBTenki_dev_handle hdl, int ppm, int offset)
{
	int res;
	unsigned char repBuf[32];

	float pre_co2[1];
	float pre_p[4];
	float pre_t[4];
	float pre_rh[4];

	res = 0;
	res = res < 0 ? res : usbtenki_getCalibration(hdl, 0, (uint8_t *)pre_co2, sizeof(pre_co2));
	res = res < 0 ? res : usbtenki_getCalibration(hdl, 7, (uint8_t *)pre_p, sizeof(pre_p));
	res = res < 0 ? res : usbtenki_getCalibration(hdl, 8, (uint8_t *)pre_t, sizeof(pre_t));
	res = res < 0 ? res : usbtenki_getCalibration(hdl, 9, (uint8_t *)pre_rh, sizeof(pre_rh));

	if (g_verbose) {
		printf("precal:\n");
		printf("c: %g\n", pre_co2[0]);
		printf("p: %g %g %g %g\n", pre_p[0], pre_p[1], pre_p[2], pre_p[3]);
		printf("t: %g %g %g %g\n", pre_t[0], pre_t[1], pre_t[2], pre_t[3]);
		printf("h: %g %g %g %g\n", pre_rh[0], pre_rh[1], pre_rh[2], pre_rh[3]);
	}

	if (res < 0) {
		return -1;
	}


	uint16_t offset_16 = offset / 10;

	res = usbtenki_setCalibration(hdl, 4, (uint8_t *)&offset_16, sizeof(offset_16));
	if (res < 0) {
		usbtenki_command(hdl, USBTENKI_ROLLBACK, 0, repBuf, sizeof(repBuf));
		return -2;
	}

	usleep(500 * 1000);

	float post_co2[1];
	float post_p[4];
	float post_t[4];
	float post_rh[4];

	res = res < 0 ? res : usbtenki_set_dxc220_kz(hdl, ppm);

	if (res < 0) {
		return -2;
	}

	usleep(1500 * 1000);

	res = res < 0 ? res : usbtenki_getCalibration(hdl, 0, (uint8_t *)post_co2, sizeof(post_co2));
	res = res < 0 ? res : usbtenki_getCalibration(hdl, 7, (uint8_t *)post_p, sizeof(post_p));
	res = res < 0 ? res : usbtenki_getCalibration(hdl, 8, (uint8_t *)post_t, sizeof(post_t));
	res = res < 0 ? res : usbtenki_getCalibration(hdl, 9, (uint8_t *)post_rh, sizeof(post_rh));


	if (g_verbose) {
		printf("postcal:\n");
		printf("c: %g\n", post_co2[0]);
		printf("p: %g %g %g %g\n", post_p[0], post_p[1], post_p[2], post_p[3]);
		printf("t: %g %g %g %g\n", post_t[0], post_t[1], post_t[2], post_t[3]);
		printf("h: %g %g %g %g\n", post_rh[0], post_rh[1], post_rh[2], post_rh[3]);
	}

	if (res < 0) {
		return -3;
	}

	double coeffs[3];
	double x0;
	double x1;
	double scale;
	double level;
	double new_coeffs[3];


	int n_valid = 0;
	for (int i = 0; i < 3; i++) {
		coeffs[i] = (double)pre_p[i+1];
		n_valid += isfinite(pre_p[i+1]) ? 1 : 0;
	}
	if (n_valid == 3) {
		x0 = (double)pre_p[0];
		x1 = (double)post_p[0];
		// scale = post_co2[0] / pre_co2[0];
		scale = 1.0;
		level = 1.0;
		shift_f(coeffs, x0, x1, scale, level, new_coeffs);
		for (int i = 0; i < 3; i++) {
			post_p[i+1] = (float)new_coeffs[i];
		}
		res = res < 0 ? res : usbtenki_setCalibration(hdl, 7, (uint8_t *)post_p, sizeof(post_p));
	}

	n_valid = 0;
	for (int i = 0; i < 3; i++) {
		coeffs[i] = (double)pre_t[i+1];
		n_valid += isfinite(pre_t[i+1]) ? 1 : 0;
	}
	if (n_valid == 3) {
		x0 = (double)pre_t[0];
		x1 = (double)post_t[0];
		// scale = post_co2[0] / pre_co2[0];
		scale = 1.0;
		level = 1.0;
		shift_f(coeffs, x0, x1, scale, level, new_coeffs);
		for (int i = 0; i < 3; i++) {
			post_t[i+1] = (float)new_coeffs[i];
		}
		res = res < 0 ? res : usbtenki_setCalibration(hdl, 8, (uint8_t *)post_t, sizeof(post_t));

	}

	n_valid = 0;
	for (int i = 0; i < 3; i++) {
		coeffs[i] = (double)pre_rh[i+1];
		n_valid += isfinite(pre_rh[i+1]) ? 1 : 0;
	}
	if (n_valid == 3) {
		x0 = (double)pre_rh[0];
		x1 = (double)post_rh[0];
		// scale = post_co2[0] / pre_co2[0];
		scale = 1.0;
		level = 1.0;
		shift_f(coeffs, x0, x1, scale, level, new_coeffs);
		for (int i = 0; i < 3; i++) {
			post_rh[i+1] = (float)new_coeffs[i];
		}
		res = res < 0 ? res : usbtenki_setCalibration(hdl, 9, (uint8_t *)post_rh, sizeof(post_rh));
	}

	if (res < 0) {
		if (g_verbose) {
			printf("rollback\n");
		}
		usbtenki_command(hdl, USBTENKI_ROLLBACK, 0, repBuf, sizeof(repBuf));
		return -4;
	}

	if (g_verbose) {
		printf("postrecal:\n");
		printf("c: %g\n", post_co2[0]);
		printf("p: %g %g %g %g\n", post_p[0], post_p[1], post_p[2], post_p[3]);
		printf("t: %g %g %g %g\n", post_t[0], post_t[1], post_t[2], post_t[3]);
		printf("h: %g %g %g %g\n", post_rh[0], post_rh[1], post_rh[2], post_rh[3]);
	}

	if (g_verbose) {
		printf("commit\n");
	}
	res = usbtenki_command(hdl, USBTENKI_COMMIT, 0, repBuf, sizeof(repBuf));
	return res;
}
