/* TS-1400 utility.  Compile with
 *      gcc ts1400ctl.c -o ts1400ctl `pkg-config --libs --cflags libmodbus`
 * Requires patched modbus from here:
 * ftp://ftp.embeddedarm.com/ts-modbus/
 */

#include <stdio.h>
#include <stdlib.h>
#include <getopt.h>
#include <modbus.h>
#include <string.h>
#include <errno.h>
#include <assert.h>

static void usage(char **argv) {
	fprintf(stderr, "Usage: %s [OPTION] ...\n"
	  "Technologic Systems TS-1400 utility\n"
	  "\n"
	  "General options:\n"
	  "  -p, --port=<uart>          Accepts either XUART as \"127.0.0.1:7350\", "
	  "as a single XUART port number for localhost, or a /dev/tty device (default 0)\n"
	  "  -b, --baud=<baudrate>      Specify a baudrate (default 115200)\n"
	  "  -m, --mode=<mode>          Serial mode (default 8n1)\n"
	  "  -a, --addr=<address>       Modbus Slave ID (default 0xf7)\n"
	  "  -g, --getrelays            Display the status of all relays\n"
	  "  -s, --setrelay=<relays>    Sets one or more relays\n"
	  "  -c, --clrrelay=<relays>    Clears one or more relays\n"
	  "  -i, --info                 Reads TS-1400 temp sensor, VIN ADC, etc\n"
	  "  -h, --help                 This help\n",
	  argv[0]);
}

int main(int argc, char **argv) {
	char *opt_mode = "8n1", *opt_port = "0";
	int opt_addr = 0xf7, opt_baud = 115200, opt_info = 0, opt_getrelays = 0;
	int opt_setrelay = 0, opt_clrrelay = 0, c;
	char *setrelay, *clrrelay;
	modbus_t *mb;

	struct option long_options[] = {
	  { "port", 1, 0, 'p' },
	  { "baud", 1, 0, 'b' },
	  { "mode", 1, 0, 'm' },
	  { "addr", 1, 0, 'a' },
	  { "getrelays", 0, 0, 'g' },
	  { "setrelay", 1, 0, 's' },
	  { "clrrelay", 1, 0, 'c' },
	  { "info", 0, 0, 'i' },
	  { "help", 0, 0, 'h' },
	  { 0, 0, 0, 0 }
	};

	if(argc == 1) {
		usage(argv);
		return 1;
	}

	while((c = getopt_long(argc, argv,
	  "b:m:a:gs:c:ihp:",
          long_options, NULL)) != -1) {
		switch (c) {
		case 'i':
			opt_info = 1;
			break;
		case 'm':
			opt_mode = strdup(optarg);
			break;
		case 'a':
			opt_addr = strtoull(optarg, NULL, 0);
			break;
		case 'g':
			opt_getrelays = 1;
			break;
		case 's':
			opt_setrelay = 1;
			setrelay = strdup(optarg);
			break;
		case 'c':
			opt_clrrelay = 1;
			clrrelay = strdup(optarg);
			break;
		case 'p':
			opt_port = strdup(optarg);
			break;
		case 'h':
		default:
			usage(argv);
			return(1);
		}
	}

	if(atoi(opt_port) < 8) {
		char p[22];
		snprintf(p, 22, "127.0.0.1:%d", 7350 + atoi(opt_port));
		opt_port = strdup(p);
	}

	mb = modbus_new_rtu(opt_port, 
		opt_baud, 
		opt_mode[1] < 97 ? opt_mode[1] : opt_mode[1] - 32, // N
		opt_mode[0] - 48,  // 8
		opt_mode[2] - 48); // 1

	if (mb == NULL) {
		fprintf(stderr, "Unable to create the libmodbus context\n");
		return 1;
	}
	modbus_set_slave(mb, opt_addr);
	if (modbus_connect(mb) == -1) {
		fprintf(stderr, "Connection failed: %s\n", modbus_strerror(errno));
		return 1;
	}

	if(opt_getrelays) {
		int i;
		uint16_t rly;
		int rc = modbus_read_registers(mb, 0x40, 1, &rly);
		if (rc == -1) {
			fprintf(stderr, "Read Failed: %s\n", modbus_strerror(errno));
			return 1;
		}

		for (i = 0; i < 6; i++) {
			printf("relay%i=%i\n", i+1, rly & (1 << i) ? 1 : 0);
		}
	}

	if(opt_setrelay) {
		int j = 0, sz = strlen(setrelay);
		char *st = 0, *en = 0;
		for(st = setrelay; j < sz; st = en + 1) {
			int rc;
			int rly = strtoul(st, &en, 0) ;
			j += (en - st) + 1;
			rc = modbus_write_bit(mb, rly - 1, 1);
			if (rc == -1) {
				fprintf(stderr, "Write Failed: %s\n", modbus_strerror(errno));
				return 1;
			}
		}
	}

	if(opt_clrrelay) {
		int j = 0, sz = strlen(clrrelay);
		char *st = 0, *en = 0;
		for(st = clrrelay; j < sz; st = en + 1) {
			int rc;
			int rly = strtoul(st, &en, 0);
			j += (en - st) + 1;
			rc = modbus_write_bit(mb, rly - 1, 0);
			if (rc == -1) {
				fprintf(stderr, "Write Failed: %s\n", modbus_strerror(errno));
				return 1;
			}
		}
	}

	if(opt_info) {
		uint16_t holding[9];
		int rc = modbus_read_registers(mb, 0x70, 9, holding);
		if (rc == -1) {
			fprintf(stderr, "Read Failed: %s\n", modbus_strerror(errno));
			return 1;
		}

		fprintf(stdout,
			"rs485_termination=%d\n"
			"model=0x%X\n"
			"fpga_version=%d\n"
			"zpu_version=%d\n"
			"vin=%.4f\n"
			"temperature=%.4f\n",
			holding[1],
			holding[4],
			holding[5],
			holding[6],
			(float)holding[7]/1000,
			(float)holding[8]/10
		);
	}
}