Slight update, this is a bit of an improved version that outputs in a format where it can be submitted directly to Thingspeak via wget, so just:
wget -q "https://api.thingspeak.com/update?api_key=XXXXX
./pzem16"
All the instructions are now present as code comments. Next update will probably be to add output for IFTTT, since Thingspeak’s options for handling alarm conditions are pretty awkward to deal with.
/* 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 -q "https://api.thingspeak.com/update?api_key=XXXXX`./pzem16`"
To post on an ongoing basis, once a minute:
#!/bin/sh
while sleep 59 ;
do
wget -q "https://api.thingspeak.com/update?api_key=XXXXX`./pzem16`"
done
Command-line options:
-d Print debug output instead of Thingspeak output
-u<number> Set USB device to /dev/ttyUSBx, default is 0 */
#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, usbDeviceNo = 0;
int 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;
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( debugOutput )
printf( "%d %.2f\n", value, result );
else
{
if( thingspeakFieldNames[ i ] != NULL )
printf( "&%s=%.2f", thingspeakFieldNames[ i ], result );
}
}
modbus_free( modbusCTX );
return( EXIT_SUCCESS );
}