/*
 * modbus_examble.c
 * Copyright (C) 2022, Power Monitors, Inc.
 * 
 * compile with: cc modbus_examble.c -l modbus -o modbus_example
 */

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <arpa/inet.h>

#include <modbus/modbus.h>

#define min(a,b) (a < b ? a : b)

#define VHARMMAGADDR 1000
#define VHARMPHASEADDR 1104
#define IHARMMAGADDR 1210
#define IHARMPHASEADDR 1314

#define HARMONIC_CHANNEL_OFFSET 1000

static void usage(char *p_name)
{
	fprintf(stderr, "\n");
	fprintf(stderr, "usage: %s -i ip of modbus server [-h|-?]\n", p_name);
	fprintf(stderr, "\n");
	fprintf(stderr, "-h | -?             Show this help message.\n");
	fprintf(stderr, "\n");

	exit(1);
}

void parse_seconds(long in, int *days, int *hours, int *minutes, int *seconds)
{
	long remaining;
	*days = in / 86400;
	remaining = in % 86400;
	*hours = remaining / 3600;
	remaining = (remaining % 3600);
	*minutes = remaining / 60;
	remaining = (remaining % 60);
	*seconds = remaining;
}

union {
	unsigned char data[24 * sizeof(float)];
	struct {
		float voltage_min;
		float voltage_ave;
		float voltage_max;

		float current_min;
		float current_ave;
		float current_max;

		float watt_min;
		float watt_ave;
		float watt_max;

		float var_min;
		float var_ave;
		float var_max;

		float va_min;
		float va_ave;
		float va_max;

		float phase_min;
		float phase_ave;
		float phase_max;

		float pf_min;
		float pf_ave;
		float pf_max;

		float dpf_min;
		float dpf_ave;
		float dpf_max;
	}_channel_data;
} channel_data;

int readdata( modbus_t *ctx, unsigned int readaddr, unsigned int datalen, void* dst)
{
	int readsize = 400;
	unsigned int numread = 0;
	int i;
	int rc;
	uint16_t tab_reg[200];
	int reads = datalen / readsize;
	int readlen;
	int readregs;

	if (datalen % readsize != 0) reads++;

	for(i=0; i < reads; i++) {
		readlen = min(datalen - numread, readsize);
		readregs = readlen/2;

		printf("reading %d registers from readaddr %u\n", readregs, readaddr);
		rc = modbus_read_registers(ctx, readaddr, readregs, tab_reg);
		if (rc == -1) {
			fprintf(stderr, "%s\n", modbus_strerror(errno));
			return -1;
		}

		memcpy(dst+numread, tab_reg, readlen);
		numread += readlen;
		readaddr += rc;
	}

	return numread;
}

int readreg( modbus_t *ctx, unsigned int readaddr, unsigned int nb, uint16_t* dst){
	int rc = readdata(ctx, readaddr, nb*2, dst);
	return rc;
}

int main(int argc, char *argv[])
{
	modbus_t *ctx;
	uint16_t tab_reg[200];
	int rc;
	int i;
	int harm;
	long lval;
	int chan;

	int opt = 0;
	char *ip = NULL;
	long up_seconds;
	int days, hours; int minutes; int seconds;
	float harmonic_data[52];
	
	while((opt = getopt(argc, argv, "i:h?")) != -1) {
		switch(opt) {
		case 'i':
			ip = (char *)optarg;
			break;
		default:
			usage(argv[0]);
			break;
		}
	}
	
	if (ip == NULL) {
		fprintf(stderr, "Modbus server address required\n");
		usage(argv[0]);
		return -2;
	}
	
	/* create a libmodbus context for TCP  and the connect.*/
	ctx = modbus_new_tcp(ip, MODBUS_TCP_DEFAULT_PORT);  // defaults to port 502

	printf("\nConnecting to modbus server at %s\n\n", ip);
	if (modbus_connect(ctx) == -1) {
		fprintf(stderr, "Connection failed: %s\n", modbus_strerror(errno));
		modbus_free(ctx);
		return -1;
	}

	/* 
	 * The first data points are used for information about the
	 * device.  
	 */
	rc = readreg(ctx, 0, 8, tab_reg);
	if (rc == -1) {
		fprintf(stderr, "%s\n", modbus_strerror(errno));
		modbus_close(ctx);
		modbus_free(ctx);
		return -1;
	}

	int vchans = ntohs(tab_reg[0]);
	int ichans = ntohs(tab_reg[1]);
	printf("Number of votage channels = %d\n", vchans);
	printf("Number of current channels = %d\n", ichans);

	printf("Firmware Version = %.2f\n", ntohs(tab_reg[2])/100.0);

	memcpy(&lval, &(tab_reg[3]), sizeof(long));
	printf("serial number = %u\n", ntohl(lval));

	memcpy(&lval, &(tab_reg[5]), sizeof(long));
	up_seconds = ntohl(lval);	
	parse_seconds(up_seconds, &days, &hours, &minutes, &seconds);

	printf("Device up for %lu seconds (%d days, %d hours, %d minutes, %d seconds)\n", 
		up_seconds, days, hours, minutes, seconds);

	/* 
	 * The first data points are used for information about the
	 * device.  The power data starts at point 100.
	 */
	for(chan=0; chan < vchans; chan++) {
		printf("\nFetching channel %d\n", chan + 1);
		int readaddr = 100 * (chan+1);

		rc = readdata(ctx, readaddr, sizeof(channel_data.data), tab_reg);
		if (rc == -1) {
			fprintf(stderr, "%s\n", modbus_strerror(errno));
			return -1;
		}
		memcpy(&(channel_data.data), &tab_reg, rc);

		printf("Voltage min/ave/max, %2.1f, %2.1f, %2.1f\n", 
			channel_data._channel_data.voltage_min, 
			channel_data._channel_data.voltage_ave,
			channel_data._channel_data.voltage_max);

		if (chan < ichans) {
			printf("Current min/ave/max, %2.2f, %2.2f, %2.2f\n", 
				channel_data._channel_data.current_min, 
				channel_data._channel_data.current_ave,
				channel_data._channel_data.current_max);

			printf("WATT min/ave/max, %2.2f, %2.2f, %2.2f\n", 
				channel_data._channel_data.watt_min, 
				channel_data._channel_data.watt_ave,
				channel_data._channel_data.watt_max);

			printf("VAR min/ave/max, %2.2f, %2.2f, %2.2f\n", 
				channel_data._channel_data.var_min, 
				channel_data._channel_data.var_ave,
				channel_data._channel_data.var_max);

			printf("VA min/ave/max, %2.2f, %2.2f, %2.2f\n", 
				channel_data._channel_data.va_min, 
				channel_data._channel_data.va_ave,
				channel_data._channel_data.va_max);

			printf("Phase min/ave/max, %2.2f, %2.2f, %2.2f\n", 
				channel_data._channel_data.phase_min, 
				channel_data._channel_data.phase_ave,
				channel_data._channel_data.phase_max);

			printf("PF min/ave/max, %2.2f, %2.2f, %2.2f\n", 
				channel_data._channel_data.pf_min, 
				channel_data._channel_data.pf_ave,
				channel_data._channel_data.pf_max);

			printf("DPF min/ave/max, %2.2f, %2.2f, %2.2f\n", 
				channel_data._channel_data.dpf_min, 
				channel_data._channel_data.dpf_ave,
				channel_data._channel_data.dpf_max);

		}

		/* The power data is followed by the harmonic data */
		/* vharm mag */
		rc = readdata(ctx, VHARMMAGADDR + chan * HARMONIC_CHANNEL_OFFSET, sizeof(harmonic_data), harmonic_data);
		if (rc == -1) {
			fprintf(stderr, "%s\n", modbus_strerror(errno));
			modbus_close(ctx);
			modbus_free(ctx);
			return -1;
		}

		printf("V harmonic Mag (1-52)");

		for(harm=0; harm<52; harm++) {
			if (harm % 13 == 0) printf("\n\t");
			printf("%2.2f ", harmonic_data[harm]);

		}
		printf("\n");

		/* vharm phase */
		rc = readdata(ctx, VHARMPHASEADDR + chan * HARMONIC_CHANNEL_OFFSET, sizeof(harmonic_data), harmonic_data);
		if (rc == -1) {
			fprintf(stderr, "%s\n", modbus_strerror(errno));
			modbus_close(ctx);
			modbus_free(ctx);
			return -1;
		}

		printf("V Harmonic phase (1-52)");
		for(harm=0; harm<52; harm++) {
			if (harm % 13 == 0) printf("\n\t");
			printf("%2.2f ", harmonic_data[harm]);

		}
		printf("\n");

		/* iharm mag */
		rc = readdata(ctx, IHARMMAGADDR +  chan * HARMONIC_CHANNEL_OFFSET, sizeof(harmonic_data), harmonic_data);
		if (rc == -1) {
			fprintf(stderr, "%s\n", modbus_strerror(errno));
			modbus_close(ctx);
			modbus_free(ctx);
			return -1;
		}

		printf("I Harmonic Mag (1-52)");

		for(harm=0; harm<52; harm++) {
			if (harm % 13 == 0) printf("\n\t");
			printf("%2.2f ", harmonic_data[harm]);

		}
		printf("\n");

		/* vharm phase */
		rc = readdata(ctx, IHARMPHASEADDR + chan * HARMONIC_CHANNEL_OFFSET, sizeof(harmonic_data), harmonic_data);
		if (rc == -1) {
			fprintf(stderr, "%s\n", modbus_strerror(errno));
			modbus_close(ctx);
			modbus_free(ctx);
			return -1;
		}

		printf("I Harmonic Phase (1-52)");

		for(harm=0; harm<52; harm++) {
			if (harm % 13 == 0) printf("\n\t");
			printf("%2.2f ", harmonic_data[harm]);

		}
		printf("\n");

	}

	printf("\n");

	modbus_close(ctx);
	modbus_free(ctx);
}

