Serial Port Code -
This version is my translation of Sean's code from parallel port and Linux to Serial port and Win2k. If you compare this to the parallel port version, you should find it true to form, but I've also puffed it up in several ways for a more flexible installation. And, rather than producing a manual for this, I try to always embed a lot of documentation. So, you'll also see that it has some options and tries to help you with usage instructions. If all you want is the executable image, I've provided that too.
// WattWatcher
//
// Copyright (c) 2003 by Dave Smart - Smartware Computing, all rights reserved.
//
// This program instruments a wattmeter and logs the resultant measurements
// in a format suitable for MRTG (www.mrtg.org).
//
// Usage
//
// This program may be freely used for private and educational purposes only.
//
// Commercial use is granted for a period not to exceed 30 days after which
// use must be discontinued, or appropriate compensation sent to the author.
// Contact Smartware@Smart-Family.net for more information.
//
// The author makes no warrant of fitness for any purpose, and shall be held
// harmless in the event this program does not function as you wish. Further
// should the circuit that is used with this program cause harm to you and/or
// your computer, you have taken that responsibility upon yourself by the use
// of this program.
//
#include <windows.h>
#include <sys/types.h>
#include <time.h>
#include <stdio.h>
const float version = (float)0.3; // Program Version
// Default configuration
float Kh = (float)7.2; /* "Disc constant" Watt-hours per turn, labeled on your meter */
unsigned int pulsesPerRev = 2; /* Pulses per turn of the disc i.e. mine has two holes */
int iComPort = 2; // Default to com 1
HANDLE hCommPort; // Let's move it to the com port
OVERLAPPED o;
int inlineMode = 0; // MRTG inline mode
int Once = 0; // Controls whether we run forever or once
char mrtgFile[255]; // Holds the filename
int verbose = 0;
int Duplicate = 0;
int AppendToFile = 0;
int ScaleFactor = 1000;
int CatchTwoEvents = 0;
#define PINCOUNT 4
struct
{
DWORD event;
DWORD mask;
char name[6];
} pins[4] =
{
{EV_RING, MS_RING_ON, "RING"}, // Pin 9/9, 22/25
{EV_RLSD, MS_RLSD_ON, "DCD"}, // Pin 8/9, 5/25
{EV_DSR, MS_DSR_ON, "DSR"}, // Pin 6/9, 6/25
{EV_CTS, MS_CTS_ON, "CTS"} // Pin 8/9, 5/25
};
int evMon = 0; // Which signal to watch from the "pins" table.
void ShowUsage(void)
{
printf(
"\n"
"WattWatcher [Options] v%3.1f by D.Smart\n"
"\n"
" This program waits for the selected signal on the selected serial port\n"
" to transition high. It then measures the time from one rising edge to\n"
" the next. With appropriate external circuitry, this can be used to\n"
" measure electric power consumption.\n"
" see www.smart-family.net/smartware/hardware for more details, including\n"
" a schematic for the companion circuit.\n"
"\n"
" Options:\n"
" -Com # Set to read Com port # [defaults to 2]\n"
" -Sig # Set to read signal [0=RING], 1=DCD, 2=DSR, 3=CTS\n"
" -TwoSigs Configure to read two signals (see below)\n"
" -InlineMRTG Writes the MRTG format inline (forces -Once)\n"
" -Duplicate Writes the Watt measurement twice (for MRTG only)\n"
" -MRTG file Writes the current sample to 'file' in MRTG format\n"
" -Append Modifier for -MRTG to append instead of create\n"
" -Once Run a single sample and exit [defaults to forever]\n"
" -Kh #.# Defines the Kilowatt hours per revolution [7.2]\n"
" -PPR # Defines the number pulses per revolution [2]\n"
" -Verbose Makes the program more chatty\n"
" -History Shows the program version history\n"
" -Errata Shows the program known errata\n"
"\n"
" Return codes:\n"
" 0 Normal program termination\n"
" 1 Command line error, or History, Errata requested\n"
" 2 Failed to open com port\n"
"\n"
" Pinout Sig Direction Signal\n"
" 3 2 -->> Transmit Data\n"
" 2 3 <<-- Receive Data\n"
" 7 4 -->> RTS - Request To Send\n"
" 8 5 3 <<-- CTS - Clear To Send\n"
" 6 6 2 <<-- DSR - Data Set Ready\n"
" 4 20 -->> DTR - Data Terminal Ready\n"
" 1 8 1 <<-- DCD - Data Carrier Detect\n"
" 9 22 0 <<-- RING - Ring Indicator\n"
" 5 7 ---- GND - Signal Ground\n"
"\n",
version);
}
void ShowFooter(void)
{
printf(
"\n"
" For more information, mailto:info@Smart-Family.net\n"
"\n");
}
void ShowHistory(void)
{
printf(
"\n"
" History\n"
"\n"
" v0.3 Added this history\n"
" Added -TwoSigs which is needed for USBGear's USBG-232 that I have.\n"
" Added -Sig option since the USBGear's USBG-232 RING doesn't behave\n"
" as a motherboard serial port does.\n"
" Added -Append modifier to -MRTG\n"
" v0.2 First version released into the wild.\n"
" Added many options -Com, -Inline, -Dup, -MRTG, -Once, -Kh, -PPR,\n"
" -Verbose.\n"
" v0.1 Initial port from Linux parallel port to Win32 serial port\n"
"\n");
}
void ShowErrata(void)
{
printf(
"\n"
" Errata\n"
"\n"
" - TwoSigs is sometimes necessary. For instance, with some adapters,\n"
" the standard wait for signal function will signal each edge of the\n"
" signal. Other adapters will only signal on the rising edge.\n"
" If you run the program with -Verbose, and you see that the time\n"
" measurement seems to alternate between two values, it is probably\n"
" reading the high period, and then the low period, rather than\n"
" the period of a full cycle. In this case, you probably want to use\n"
" the -TwoSigs option.\n"
"\n"
" - USBGear's USBG-232 with the Prolific USB-to-Serial Comm Port driver\n"
" version 1.5.0.0 can't be used on the RING input. Other signals work\n"
" as you would expect.\n"
"\n");
}
// GetPeriodInMicroSeconds
//
// This will pend, waiting for the Serial Port Signal of interest to go high.
//
// If it is already high at the entry of this function, it will wait for it
// to go low, then back high.
//
// When it goes high, then we'll grab a high resolution timer value
// Next, we'll compute the elapsed time (since the last activation)
// and return that elapsed time (in microseconds)
//
unsigned long GetPeriodInMicroSeconds (void)
{
static LARGE_INTEGER countsPerSec;
LARGE_INTEGER xTime;
static _int64 startTime;
_int64 stopTime, elapsedTime;
DWORD EvtMask;
if (0 == countsPerSec.QuadPart) // Initialization of the counter "constant"
{
QueryPerformanceFrequency(&countsPerSec);
if (verbose)
printf("countsPerSec: %u\n", countsPerSec);
}
if (CatchTwoEvents)
WaitCommEvent(hCommPort, &EvtMask, &o); // Wait for any RI change
WaitCommEvent(hCommPort, &EvtMask, &o); // Wait for any RI change
QueryPerformanceCounter(&xTime); // Get the timestamp
stopTime = xTime.HighPart; // Convert it to the int64 native type
stopTime <<= 32;
stopTime |= xTime.LowPart;
elapsedTime = stopTime - startTime;
startTime = stopTime;
return (unsigned long)((_int64)1000000 * (elapsedTime) / countsPerSec.QuadPart);
}
// StartCom
//
// This will try to open the selected Com port
//
int StartCom(void)
{
char szCommName[20];
int fSuccess;
sprintf(szCommName,"Com%i:", iComPort); // Create the COM 'filename'
if (verbose)
printf("Opening COM%i.\n", iComPort);
hCommPort = CreateFile( szCommName,
GENERIC_READ | GENERIC_WRITE,
0, // exclusive access
NULL, // no security attrs
OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL, // | FILE_FLAG_OVERLAPPED, // overlapped I/O
NULL );
if (hCommPort == INVALID_HANDLE_VALUE )
{
printf("Failed to open com port\n");
return 0;
}
// Set the event mask.
fSuccess = SetCommMask(hCommPort, pins[evMon].event);
if (!fSuccess)
{
printf("Failed to set comm mask\n");
CloseHandle(hCommPort);
return 0;
}
if (verbose)
{
printf("Monitoring Signal %s.\n", pins[evMon].name);
}
// Create an event object for use in WaitCommEvent
o.hEvent = CreateEvent(
NULL, // default security attributes
FALSE, // auto reset event
FALSE, // not signaled
NULL // no name
);
return 1;
}
void GetOptions(int argc, char *argv[])
{
int x;
int showUsage = 0;
int showHistory = 0;
int showErrata = 0;
for (x=1; x<argc; x++)
{
if (strnicmp(argv[x], "-Com", strlen(argv[x])) == 0)
{
if (x < argc)
{
x++;
iComPort = atoi(argv[x]);
}
else
{
printf("Missing option for %s\n", argv[x]);
showUsage = 1;
}
}
else if (strnicmp(argv[x], "-Sig", strlen(argv[x])) == 0)
{
if (x < argc)
{
x++;
evMon = atoi(argv[x]);
if (evMon < 0 || evMon >= PINCOUNT)
{
printf("Error in Signal Number %s\n", argv[x]);
showUsage = 1;
}
}
else
{
printf("Missing option for %s\n", argv[x]);
showUsage = 1;
}
}
else if (strnicmp(argv[x], "-Verbose", strlen(argv[x])) == 0)
{
verbose = 1;
}
else if (strnicmp(argv[x], "-Append", strlen(argv[x])) == 0)
{
AppendToFile = 1;
}
else if (strnicmp(argv[x], "-TwoSigs", strlen(argv[x])) == 0)
{
CatchTwoEvents = 1;
}
else if (strnicmp(argv[x], "-History", strlen(argv[x])) == 0)
{
showHistory = 1;
}
else if (strnicmp(argv[x], "-Errata", strlen(argv[x])) == 0)
{
showErrata = 1;
}
else if (strnicmp(argv[x], "-Once", strlen(argv[x])) == 0)
{
Once = 1;
}
else if (strnicmp(argv[x], "-Duplicate", strlen(argv[x])) == 0)
{
Duplicate = 1;
}
else if (strnicmp(argv[x], "-Kh", strlen(argv[x])) == 0)
{
if (x < argc)
{
x++;
Kh = (float)atof(argv[x]);
}
else
{
printf("Missing option for %s\n", argv[x]);
showUsage = 1;
}
}
else if (strnicmp(argv[x], "-PPR", strlen(argv[x])) == 0)
{
if (x < argc)
{
x++;
pulsesPerRev = atoi(argv[x]);
}
else
{
printf("Missing option for %s\n", argv[x]);
showUsage = 1;
}
}
else if (strnicmp(argv[x], "-INLINEMRTG", strlen(argv[x])) == 0)
{
inlineMode = 1;
Once = 1;
}
else if (strnicmp(argv[x], "-MRTG", strlen(argv[x])) == 0)
{
if (x < argc)
{
x++;
strcpy(mrtgFile, argv[x]);
}
else
{
printf("Missing option for %s\n", argv[x]);
showUsage = 1;
}
}
else
{
showUsage = 1;
}
}
if (showUsage)
{
ShowUsage();
}
if (showHistory)
{
ShowHistory();
}
if (showErrata)
{
ShowErrata();
}
if (showUsage || showHistory || showErrata)
{
ShowFooter();
exit(1);
}
}
// main
//
//
int main (int argc, char *argv[])
{
unsigned long pulses = 0;
unsigned long t;
double watts, avgwatts, watthours;
FILE *MRTG;
char buffer[1024];
int tries;
GetOptions(argc, argv);
// Try to open the Com port several times, in case it is busy
for (tries = 0; tries <= 5 && !StartCom(); tries++)
{
if (tries >= 5) // Give up
exit(2);
Sleep(1000);
}
(void)GetPeriodInMicroSeconds(); // This is here to sync it once, so we don't care about the return value
// You can see here that it runs forever. I think it could take a sample once every 5 minutes.
// It would then wait until it detects a hole, then GetPeriodInMicroSeconds the time to the next hole. This
// is then 1/2 revolution.
do
{
t = GetPeriodInMicroSeconds(); // GetPeriodInMicroSeconds since last time
pulses++;
watthours = Kh * pulses / pulsesPerRev ; /* total watt-hours */
watts = Kh * 60 * 60 * 1000 / t / pulsesPerRev; /* current watts */
avgwatts = watts; // Wish we could do more now...
// Compute the MRTG Output
sprintf(buffer, "%.0f\n%.0f\nawhile\nWatt Watcher\n",
ScaleFactor * watts,
(Duplicate) ? ScaleFactor * watts : watthours);
if (verbose)
printf("%8.2f W %8.2f WH %8.3f sec\n",
ScaleFactor * watts,
watthours,
(float)t/1000000);
else if (inlineMode)
printf("%s", buffer);
else
printf("%8.2f W %8.2f WH\n", ScaleFactor * watts, watthours);
if (mrtgFile[0] != '\0' && (MRTG=fopen(mrtgFile, (AppendToFile) ? "a": "w")))
{
fprintf(MRTG,"%s", buffer);
fclose(MRTG);
}
} while (! Once); // Do forever if so instructed
CloseHandle(hCommPort);
exit(0);
}