PZEM-016 single phase modbus energy meter

Final version, now with IFTTT support for processing over/undervoltage alarms, see the shell script in the comment at the start.

/* PZEM-16 read code, currently set up to output for Thingspeak use.  You
   can use this code in whatever way you want, as long as you don't try to
   claim you wrote it.

   To install libmodbus:

	sudo apt-get install libmodbus5
	sudo apt-get install libmodbus-dev

   To build the code:

	gcc pzem16.c -o pzem16 `pkg-config --cflags --libs libmodbus`

   To post a single set of results to Thingspeak:

	wget -O/dev/null -q "https://api.thingspeak.com/update?api_key=XXXXX`./pzem16`"

   To trigger an IFTTT event, in this case based on voltage:

	wget -O/dev/null -q "https://maker.ifttt.com/trigger/voltage_alarm/with/key/XXXX?value1='./pzem16 -v'"

   To post on an ongoing basis, once a minute, as well as checking for over/
   undervoltages:

	#!/bin/sh
	#
	# Usage: pzem16 thingspeak_key ifttt_key

	if [ $# -ne 2 ] ; then
		echo "Usage: $0 thingspeak_key ifttt_key" >&2 ;
		exit 1 ;
	fi

	THINGSPEAK_KEY=$1
	IFTTT_KEY=$2
	MIN_VOLTAGE=230
	MAX_VOLTAGE=250

	while sleep 59 ;
	do
		OUTPUT=$(./pzem16)
		if [ $? -ne 0 ] ; then
			continue
		fi
		wget -O/dev/null -q "https://api.thingspeak.com/update?api_key=$THINGSPEAK_KEY$OUTPUT"
		OUTPUT=$(./pzem16 -v)
		if [ $OUTPUT -lt $MIN_VOLTAGE ] || [ $OUTPUT -gt $MAX_VOLTAGE ] ; then
			wget -O/dev/null -q "https://maker.ifttt.com/trigger/voltage_alarm/with/key/$IFTTT_KEY?value1=$OUTPUT"
		fi
	done

   Note that this has no rate-limiting, it'll keep triggering alerts for as
   long as the alarm condition is present.

   Command-line options:

	-d			Print debug output instead of Thingspeak output
	-u<number>	Set USB device to /dev/ttyUSBx, default is 0
	-v			Print voltage as an integer value */

#include <stdlib.h>
#include <stdio.h>
#include <ctype.h>
#include <errno.h>
#include <modbus.h>

/* Useful defines */

#ifndef TRUE
  #define FALSE	0
  #define TRUE	( !FALSE )
#endif /* TRUE */

/* Flags that control how to interpret the modbus read results */

#define DOUBLEWORD	0x01
#define DIV10		0x02
#define DIV100		0x04
#define DIV1000		0x08
#define END			0xFF

int main( int argc, char *argv[] )
	{
	static const char *configDeviceNameTemplate = "/dev/ttyUSB%d";
	static const int configBaudRate = 9600;
	static const int configSlaveAddress = 1;
	static const int modbusResultInfo[] = {
		DIV10,					/* Voltage in 0.1V */
		DOUBLEWORD | DIV1000,	/* Current in 0.001A */
		DOUBLEWORD | DIV10,		/* Power in 0.1W */
		DOUBLEWORD,				/* Energy in 1Wh */
		DIV10,					/* Frequency in 0.1Hz */
		DIV100,					/* Power factor in 0.01 */
		END
		};
	static const char *thingspeakFieldNames[] = {
		"field1", NULL, NULL, NULL, "field2", NULL, NULL
		};
	modbus_t *modbusCTX;
	uint16_t modbusResults[ 9 ];
	char deviceName[ 64 ];
	int moreArgs = TRUE, debugOutput = FALSE, showVoltage = FALSE;
	int usbDeviceNo = 0, i, resultIndex = 0, status;

	/* Skip the program name */
	argv++; argc--;

	/* Check for arguments */
	while( argc && *argv[ 0 ] == '-' && moreArgs )
		{
		char *argPtr = argv[ 0 ] + 1;

		while( *argPtr )
			{
			switch( toupper( *argPtr ) )
				{
				case '-':
					moreArgs = FALSE;	/* GNU-style end-of-args flag */
					break;

				case 'D':
					debugOutput = TRUE;
					break;

				case 'U':
					usbDeviceNo = atoi( argPtr + 1 );
					if( usbDeviceNo < 0 || usbDeviceNo > 20 )
						{
						fprintf( stderr, "Invalid USB device "
								 "number.\n" );
						exit( EXIT_FAILURE );
						}
					while( argPtr[ 1 ] )
						argPtr++;	/* Skip rest of arg */
					break;

				case 'V':
					showVoltage = TRUE;
					break;

				default:
					fprintf( stderr, "Unknown argument '%c'.\n", *argPtr );
					return( EXIT_FAILURE );
				}
			argPtr++;
			}

		argv++;
		argc--;
		}
	sprintf( deviceName, configDeviceNameTemplate, usbDeviceNo );

	/* Create a libmodbus context and connect to the remote device */
	modbusCTX = modbus_new_rtu( deviceName, configBaudRate, 'N', 8, 1 );
	if( modbusCTX == NULL )
		{
	    fprintf( stderr, "Couldn't create libmodbus context for %s.\n",
	    		 deviceName );
	    return( EXIT_FAILURE );
		}
	if( modbus_connect( modbusCTX ) == -1 )
		{
		fprintf( stderr, "modbus connect failed for %s: %s.\n",
				 deviceName, modbus_strerror( errno ) );
		modbus_free( modbusCTX );
		return( EXIT_FAILURE );
		}

	/* Set the slave ID of the device to connect to.  A value of 0x01 seems
	   to work, there's a PZEM command to change it to a value 0x01...0xF7
	   but we don't touch that */
	modbus_set_slave( modbusCTX, configSlaveAddress );

	/* If we're providing debug output, let the user know that we've
	   established a connection */
	if( debugOutput )
		{
		printf( "Connected to %s at %d 8N1, slave ID %d.\n",
				deviceName, configBaudRate, configSlaveAddress );
		}

	/* Try and read the first 9 registers, which is all the PZEM data
	   except the alarm status */
	if( modbus_read_input_registers( modbusCTX, 0, 9, modbusResults ) == -1 )
		{
		fprintf( stderr, "modbus register read failed for slave ID %d: %s.\n",
				 configSlaveAddress, modbus_strerror( errno ) );
		modbus_free( modbusCTX );
		return( EXIT_FAILURE );
		}

	/* Output the results */
	for( i = 0; modbusResultInfo[ i ] != END; i++ )
		{
		const int modbusInfo = modbusResultInfo[ i ];
		uint32_t value = modbusResults[ resultIndex++ ];
		float result, divisor;

		/* Get the high word if there is one */
		if( modbusInfo & DOUBLEWORD )
			value |= ( uint32_t ) modbusResults[ resultIndex ++ ] << 16;

		/* Scale the value to get the actual measurement */
		divisor = ( modbusInfo & DIV1000 ) ? 1000 : \
				  ( modbusInfo & DIV100 ) ? 100 : 10;
		result = value / divisor;

		/* If we're displaying the voltage only, print it and exit.  We
		   output it as an integer since it's going to be used by the
		   shell */
		if( showVoltage )
			{
			printf( "%d\n", ( int ) result );
			break;
			}

		if( debugOutput )
			printf( "%d %.2f\n", value, result );
		else
			{
			if( thingspeakFieldNames[ i ] != NULL )
				printf( "&%s=%.2f", thingspeakFieldNames[ i ], result );
			}
		}

	modbus_free( modbusCTX );

	return( EXIT_SUCCESS );
	}