/**
 * NEWGETWATTS
 *
 * newgetwatts C command line tool.
 * Communicates with Midnite classic.
 *
 * Based of RossW's newmodbus code "1.0.19"
 *
 * Revision: $Rev: 33 $
 * License: GPLv3
 *
**/

#define Version "0.0.1"	// version

/* Changes.
21-Apr-15 0.0.1		New code
*/

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netdb.h>

// Easier to declare these here, I re-use several in various functions.
int len;               //length of received data
// communications with classic
int s=0;               //socket descriptor
struct sockaddr_in sa; //internet socket addr. structure
// communications to RW
int ranges;		//socket descriptor
struct sockaddr_in rangesa; //internet socket addr. structure
struct hostent *hp;	//result of host name lookup
char buf[302*2];	//buffer to read modbus information (202 16-bit registers)
char id[101*2];		// device ID and other scratchings.
char *host;            	//pointer to remote host name
char *replyptr;		// used in http reply stripping
int port=502;		// modbus port number on classic
char val2[20];		// value parsing
int addr;
int n;
int serialno=0;		// serial number (required if jumpers not set to unlock)
int debug=0;		// debug flag
int html=0;             // html flag
char storefour[4];	// when unlocking modbus, store data here

int xbattvolts;
int xtargetvolts;
int xwatts;
int xpvvolts;
int xbattamps;
int xkwhs;
int xstate;
int xpvamps;
int xpvvoc;
int xtotalkwhs;
int xtotalamphours;
int xbatttemp;
int xfettemp;
int xpcbtemp;
int xbatamplimit;
int xdailyamphours;
int xfloattime;
int xabsorbtime;
int xeqtime;
int xInfoFlagsBits;
int xAmpLimit;
int xAUX2on;
int xAUX1on;
int xAux2OffAutoOn;
int xAUX2status;
int xendamps;
int xdiffvolts;
int xcharge;
int State;
int charge;

short xdiff;
short xoffset;
int xcalcwatts;

char *StateString;
char *SpaceStatus;
char *WaterStatus;

/* Uses the "id" buffer to write (n) bytes */
void modbus_write_registers(int n, int dst)
{
	if(serialno)		// if we have a serial number, we should use it
	{
		storefour[0]=id[13]; storefour[1]=id[14]; storefour[2]=id[15]; storefour[3]=id[16];
		id[13]=(serialno>>24)&0xff;
		id[14]=(serialno>>16)&0xff;
		id[15]=(serialno>>8)&0xff;
		id[16]=(serialno>>0)&0xff;
		serialno=0;
		modbus_write_registers(4,20492);
		id[13]=storefour[0];id[14]=storefour[1];id[15]=storefour[2];id[16]=storefour[3];
  	}
	id[0]=0;		// Transaction ID MSB
	id[1]=2;		// Transaction ID LSB
	id[2]=0;		// modbus protocol 0 MSB
	id[3]=0;		// modbus protocol 0 LSB
	id[4]=0;		// bytes following MSB
	id[5]=n+7;		// bytes following LSB
	id[6]=255;		// ident
	id[7]=16;		// write multiple registers
	id[8]= (dst-1)>>8;	// address MSB
	id[9]= (dst-1)&0xff;	// address LSB
	id[10]=0;		// number of 16-bit registers MSB
	id[11]=n>>1;		// number of 16-bit registers LSB
	id[12]=n;		// byte count.

	// data already written to id[13] onwards.
	n=(id[5]&0xff)+6;	// number of bytes to write
	if(write(s,&id, n) != n)
	{
	      fprintf(stderr, "write error\n");
	      exit(1);
	}

	len = read(s, id, 7);
	len = read(s, id, 5);		// read response
	if(len<5 || id[0]!=16)
	{
		fprintf(stderr, "modbus write error reply=%d bytes, codes %02x %02x\n", len,id[0]&0xff,id[1]&0xff);
		exit(1);
	}
}

int modbus_read_registers(int addr, int number, int offset)	// starting *register*, number of 16-bit reg to read, offset to buffer
{
	id[0]=0;		// Transaction ID MSB
	id[1]=2;		// Transaction ID LSB
	id[2]=0;		// modbus protocol 0 MSB
	id[3]=0;		// modbus protocol 0 LSB
	id[4]=0;		// bytes following MSB
	id[5]=6;		// bytes following LSB
	id[6]=255;		// ident
	id[7]=3;		// read multiple registers/addresses
	id[8]= (addr-1)>>8;	// starting address (MSB)
	id[9]= (addr-1)&0xff;	// starting address (LSB)
	id[10]=0;		// Addresses to read (MSB)
	id[11]=number;		// Addresses to read (LSB)

	if(write(s,&id, 12) != 12)
	{
	      fprintf(stderr, "write error\n");
	      exit(1);
	}

	len = read(s, id, 7);		// read modbus encapsulation
	len = read(s, id, 2);		// read response code and bytecount
	if(len<2 || (id[0]) != 3 || (id[1]&0xff) != number<<1)
	{
		fprintf(stderr, "modbus reply error\n");
		return(1);
	}
	n=id[1]&0xff;			// number of bytes to read
	len=read(s, buf+(offset)*2, n);	// read response as advised
	return(0);
}

unsigned int modbus_register(int reg)
{
	if(reg<=4350 && reg>4100)
		return((buf[(reg-4101)<<1]&0xff)<<8 | (buf[((reg-4101)<<1)+1] & 0xff));
	else	// otherwise it's a special register read not from the buffer
	{
		if(modbus_read_registers(reg, 1, 201))		// read device serial number
		{
			printf("Failed to read serial number\n");
			exit(1);
		}
		return((buf[(201)<<1]&0xff)<<8 | (buf[((201)<<1)+1] & 0xff));
	}
}

int main(int argc, char **argv)
{
        extern char *optarg;
        extern int optind, opterr;
        register int op;

	while ((op = getopt(argc, argv, "dh?")) != EOF)
	{
		switch (op)
		{
			case 'd':	/* debug information */
				debug++;
                                break;
			case 'h':	/* html output */
				html++;
				break;
			case '?':	/* help */
			default:
				printf("Usage: %s [-dh?] ip\n",argv[0]);
				printf("  -d       debugging info\n");
				printf("  -h       html output\n");
				exit(0);
		}
	}

	if(argc<2)
	{
		printf("Incorrect Usage: %s -? for help\n",argv[0]);
		exit(0);
	}

	if(!s)
	{
		host=argv[optind];
		if(host==0)
		{
			printf("No host specified\n");
			exit(0);
		}
		if((hp = gethostbyname(host)) == NULL)
		{
       			fprintf(stderr,"%s: no such host?\n", host);
       			exit(1);
		}
		bcopy((char *)hp->h_addr, (char *)&sa.sin_addr, hp->h_length);
		sa.sin_family = hp->h_addrtype;
		sa.sin_port=port>>8 | (port<<8 & 0xffff);
		if((s = socket(hp->h_addrtype, SOCK_STREAM,0)) < 0)
		{
		        perror("socket");
		        exit(1);
		}

		n=50;	// timeout/retry counter
		while(connect(s, &sa, sizeof(sa)) < 0)
		{
			if(!--n)
			{
	        		perror("connect");
	        		exit(1);
			}
			usleep(200000);		// 100,000 us = 100ms
			// if connect failed, we need to re-create the socket
			if((s = socket(hp->h_addrtype, SOCK_STREAM,0)) < 0)
			{
	        		perror("socket");
	      		 	exit(1);
			}
		}
	}

	serialno=modbus_register(28673)<<16 | modbus_register(28674);	// read device serial number

	modbus_read_registers(4101, 50, 0);
	modbus_read_registers(4151, 50, 50);
	modbus_read_registers(4201, 50, 100);
	modbus_read_registers(4251, 50, 150);
	modbus_read_registers(4301, 50, 200);

	for(n=0; n<8; n++) id[n]=buf[(4210-4101)*2+n^1];	// read ID string
	id[8]=0;						// force termination in case it wasn't already

// Read all required modbus registers here
	xbattvolts     = modbus_register(4115);
	xtargetvolts   = modbus_register(4244);

        if(html); {
        xwatts         = modbus_register(4119);
        xpvvolts       = modbus_register(4116);
        xbattamps      = modbus_register(4117);
        xkwhs          = modbus_register(4118);
        xstate         = modbus_register(4120);
        State          = (xstate>>8)&31;
        xpvamps        = modbus_register(4121);
        xpvvoc         = modbus_register(4122);
        xtotalkwhs     = modbus_register(4126);
        xtotalamphours = modbus_register(4128)+65536-92206; // old battery life
        xdailyamphours = modbus_register(4125);
        xInfoFlagsBits = modbus_register(4130);
        xAmpLimit      = (xInfoFlagsBits&0x0200);
        xAUX2on        = (xInfoFlagsBits&0x8000);
        xAUX1on        = (xInfoFlagsBits&0x4000);
        xAux2OffAutoOn = modbus_register(4165);
        xAUX2status    = (xAux2OffAutoOn & 0xc000) >> 14;
        xbatttemp      = modbus_register(4132);
        xfettemp       = modbus_register(4133);
        xpcbtemp       = modbus_register(4134);
        xbatamplimit   = modbus_register(4148);
        xendamps       = modbus_register(4246);
        xfloattime     = modbus_register(4138);
        xabsorbtime    = modbus_register(4139);
        xeqtime        = modbus_register(4143);

        if (State == 0)       StateString = "RESTING";
        else if (State == 3)  StateString = "ABSORB";
        else if (State == 4)  StateString = "BULK";
        else if (State == 5)  StateString = "FLOAT";
        else if (State == 6)  StateString = "FLOATM";
        else if (State == 7)  StateString = "EQ";
        else if (State == 18) StateString = "EQ MPPT";
        else                  StateString = "UNDEFINED";

        if (State == 0)
            if   (xbattvolts>=508) charge=100;
            else if (xbattvolts>=507) charge=98;
            else if (xbattvolts>=506) charge=97;
            else if (xbattvolts>=505) charge=96;
            else if (xbattvolts>=504) charge=95;
            else if (xbattvolts>=502) charge=92;
            else if (xbattvolts>=501) charge=91;
            else if (xbattvolts>=500) charge=90;
            else if (xbattvolts>=499) charge=87;
            else if (xbattvolts>=498) charge=85;
            else if (xbattvolts>=497) charge=83;
            else if (xbattvolts>=496) charge=80;
            else if (xbattvolts>=495) charge=77;
            else if (xbattvolts>=494) charge=75;
            else if (xbattvolts>=493) charge=72;
            else if (xbattvolts>=492) charge=70;
            else if (xbattvolts>=491) charge=67;
            else if (xbattvolts>=490) charge=65;
            else if (xbattvolts>=489) charge=62;
            else if (xbattvolts>=488) charge=60;
            else if (xbattvolts>=486) charge=57;
            else if (xbattvolts>=484) charge=55;
            else if (xbattvolts>=482) charge=50;
            else if (xbattvolts>=479) charge=45;
            else if (xbattvolts>=476) charge=40;
            else if (xbattvolts>=473) charge=35;
            else if (xbattvolts>=470) charge=30;
            else if (xbattvolts>=463) charge=20;
            else if (xbattvolts>=452) charge=10;
            else if (xbattvolts>=420) charge=0;
            else if (xbattvolts>=0) charge=0;
            else charge=0;

        if(html) if(xAUX1on>0)     SpaceStatus = "ON"; else SpaceStatus = "OFF";
        if(html) if(xAUX2status>0) WaterStatus = "ON"; else WaterStatus = "OFF";
};

	xdiffvolts=xtargetvolts-xbattvolts;
    	xoffset=modbus_register(4177);

        if(debug) printf("1>%i/%u\n",xoffset,xoffset&0xFFFF);

        if (xdiffvolts<3) xdiff=-((xdiffvolts+1)/2);
        if (xdiffvolts>2) xdiff=+(xdiffvolts/00002);

//        if (xdiffvolts<2) xdiff=-xdiffvolts;
//        if (xdiffvolts=2) xdiff=-1;
//        if (xdiffvolts>2) xdiff=+xdiffvolts;

        xoffset = xoffset + xdiff;

        if(debug) printf("2>%i/%i\n",xdiff,xoffset);

	if (xoffset<-32) xoffset=-32;
        if (xoffset>+03) xoffset=+03;

        if(debug) printf("3>%i/%u\n",xoffset,xoffset&0xFFFF);

        xcalcwatts = abs((xoffset-3)*42);

        if(xcalcwatts>xwatts) {xcalcwatts=-1;}

	id[13]=xoffset>>8;
	id[14]=xoffset&0xff;
	modbus_write_registers(2, 4177);

        if(html) printf("CC,%s,",SpaceStatus);
        if(html) printf("%s,",WaterStatus);
	if(html) printf("%s,",StateString);
	if(html) printf("%1.0f,",(float)xfloattime/60);
	if(html) printf("%1.0f,",(float)xabsorbtime/60);
	if(html) printf("%1.0f,",(float)xeqtime/60);
	if(html) printf("%1.0f,",(float)charge);
	if(html) printf("%1.0f,",(float)xwatts);
	if(html) printf("%1.0f,",(float)xcalcwatts);
	if(html) printf("%1.1f,",(float)xoffset/10);
	if(html) printf("%1.1f,",(float)xpvvoc/10);
	if(html) printf("%1.1f,",(float)xpvvolts/10);
	if(html) printf("%1.1f,",(float)xpvamps/10);
	if(html) printf("%1.1f,",(float)xbattvolts/10);
	if(html) printf("%1.1f,",(float)xtargetvolts/10);
	if(html) printf("%1.1f,",(float)xbattamps/10);
	if(html) printf("%1.1f,",(float)xendamps/10);
	if(html) printf("%1.1f,",(float)xbatamplimit/10);
	if(html) printf("%1.1f,",(float)xbatttemp/10*1.8+32);
	if(html) printf("%1.1f,",(float)xfettemp/10*1.8+32);
	if(html) printf("%1.1f,",(float)xpcbtemp/10*1.8+32);
	if(html) printf("%1.1f,",(float)xkwhs/10);
	if(html) printf("%1.3f,",(float)xtotalkwhs/10000);
	if(html) printf("%1.0f,",(float)xdailyamphours);
	if(html) printf("%1.0f,\n",(float)xtotalamphours);
}
