Using the Intel® Energy Checker SDK at Home

For my third blog entry on the Intel® Energy Checker SDK, I will take on a two-part DIY and super fun project. I always wanted to extend the use of the SDK into my home and be able to monitor my personal energy consumption. As an engineer, I live by the motto: “you cannot manage what you cannot measure”. Isn’t the electric bill all about that, one may ask? Sure, it is a good year-to-year and month-to-month trend indicator and it will likely fit the needs of most of us for a while. However, using my bill, I cannot break down my energy consumption per function. What is the cost of running my lab equipment in the garage? How much does the entertainment system cost us per month? Etc. To be honest, I do not know if this information will trigger some good changes in the way I run my electric equipments – I sincerely wish so –, but at least I will have the knowledge.
So what do I need to use the Intel® EC SDK at home? A computer – until I fall for an Apple iPad and port pl_gui_monitor for it :) – to log and monitor the readings provided by … as many power analyzers as functions I want to monitor :(. Ugh! This is the painful part. I do not own any of the power analyzers we are supporting in the SDK … and for sure, I will never have one per power outlet I have at home. In a perfect world, each of my power outlets would have an embedded power meter and energy integrator that I could query via a standard TCP/IP stack using power line carrier technology as its PHY layer. I believe that there is a market there! But for now, I am living in an imperfect world, and I need a cheap alternative solution. In this blog entry, I will demonstrate the set-up I came up with to solve this problem and I will use this opportunity to demonstrate the crafting of yet another ESRV support library.



Energy monitoring is not a prerogative of the industry. Our personal budgets can definitely benefit from it too, assuming that some actions are taken based upon the collected data. The DIY and super fun project - at least I hope so - I will describe in my third blog entry on the Intel® Energy Checker SDK, will consist of building an energy monitoring proof of concept that could be used in a house to monitor the energy consumption of some equipment scattered all over the place. After thinking about the problem for a while, I came up with a list of requirements for this project. In order of decreasing importance, the list is:

    • #1 Reasonably cheap

    • #2 Ubiquitous

    • #3 Able to share data remotely – contrarily to my cats, my spouse will not like wires running all over the place

    • #4 Reasonably accurate – I plan for long-run here so a few Watts off is acceptable for me



Obviously, #1 is key since I will pay out of my pocket for this hobby. #2 is also important as I want to be able to discriminate the measurements at least by main functions (living room, office, kitchen, lab, etc.) and not just measuring energy consumption at the PG&E* meter. On #4, I can be flexible since I am more interested in the trends over time rather than seeking the absolute precision. In addition, a few Watts off may save me big bucks! #3 is really important and was fighting for the pole position with #1. Obviously, if I cannot log the data, then all the project's foundation is shaking! Of course, I knew that I will have to write the ESRV support library for the chosen device so I could use the pl_gui_monitor and pl_csv_logger SDK companion applications to display and log our energy readings.

What to use?


I was aware of the existence of a cool little device called Kill A Watt produced by P3 International (P4400). This power analyzer actually fit requirements #1, #2 and #4 out of the box. Unfortunately it is a closed device and it cannot share its readings with the outside world. Luckily, there is also a cool DIY kit from Adafruit Industries – called Tweet-a-Watt – which precisely allows you to turn a P4400 into a wireless power analyzer using Digi’s XBee® 802.15.4 RF modules. With this kit, requirement #3 can be met. Sure, there are many other devices and kits available in the market, but for my project, this was the best pick. If you decide to take on similar project using different device(s), please share your experience with us!

The plan


I decided to proceed in two steps. First steps – described in this blog entry – consists of: a) building a first wireless measurement point (P4400 + wireless transmitter) and a wireless receiver module; and b) interface the receiver with ESRV. As a second step - to be disclosed in a future blog entry - I will add extra measurement points and amend the ESRV support library if necessary.
So I bought three P4400 (~$20 each), a Tweet-a-Watt starter pack ($90) and placed an order for two Tweet-a-Watt add-on outlet kits ($40 each). This brings me to $110 for a single measurement point and ~$60 per extra outlet I want to add to the wireless personal area network (WPAN). It is a fairly reasonable price and has the advantage of a good expandability.

Building the hardware


Building the kit is straight forward and is very well described, step by step - with lots of pictures - on Adafruit Industries web page. Really nothing difficult. The sole concern I had was soldering the XBee module to the op-amp that senses the power line usage (current and voltage). With newer P4400s, the op-amp is now located on the other side of the PCB, right under the LCD display. Not a big deal, just a smaller chip, less accessible. Similarly, the receiver module build is very easy and well documented. Last step is to configure the XBee modules. As soon as this was done, the modified P4400 plugged-in and the receiver module attached to a USB port, I got a link and data feed every two seconds. Kudos to Adafruit team!









Writing the software


Adafruit Industries provides a python script to read and tweet the readings collected via their kit. Since I want to use ESRV, I wrote my own support library in C using one of the templates shipped in the SDK. As I stated in the introduction, this will be a good opportunity to demonstrate again how to write an ESRV support library. The P4400 current and voltage sensors are read by the kit and the data is transmitted by the XBee transmitter module to the receiver. From these current and voltage sensors readings, we compute the power data. The power data is then reported to ESRV. Because we collect voltage and current data, we can implement the read_device_current() and read_device_voltage() functions. Because the read_device_power() function is called first by ESRV (c.f. SDK documentation), I am using a trick here: the read_device_power() function sets the voltage and current readings when it is called - à-la read_device_all_readings() function. The read_device_current() and read_device_voltage() functions are always returning ESRV_SUCCESS. This works even with multiple transmitters, since the voltage and current measurement functions are called in sequence per channel. So there is no mix-and-match possibility here. Energy integration from power readings will be done by ESRV. The code snippet lists the implemented functions in this first support library. In the remainder of this blog I will provide details on some of them.


//-----------------------------------------------------------------------------
// function prototypes
//-----------------------------------------------------------------------------
ESRV_API int init_device_extra_data(PESRV);
ESRV_API int delete_device_extra_data(PESRV);
ESRV_API int open_device(PESRV, void *);
ESRV_API int close_device(PESRV, void *);
ESRV_API int parse_device_option_string(PESRV, void *);
ESRV_API int read_device_power(PESRV, void *, int);
ESRV_API int read_device_energy(PESRV, void *, int, int);
ESRV_API int read_device_current(PESRV, void *, int);
ESRV_API int read_device_voltage(PESRV, void *, int);


Options


Before diving into implementation details, I will discuss the options needed for the library. In general, each device may have specificities that the ESRV user want to leverage. For this, ESRV provides a mechanism to pass options to the support library during the start-up sequence. As explained in the documentation, the string following the --device_options argument is passed to the parse_device_option_string() function between the two consecutive calls to the init_device_extra_data() function. As per today, these are the options I’ve decided to implement:

    • channels=<n>

    • offsets=<o1>,...,[ <on>]

    • aggregate_channels

    • calibrate

    • calibrate_samples=<n>



Of course, this list can be expanded in the future, but it seems to be enough for now. Let’s review their use. Channels=<n> (with n a positive integer) specify the number of Kill A Watts in the WPAN. By default n is equal to one and is capped at 32 (for now). The aggregate_channels option allows accumulation of the power, the voltage and the current readings of all Kill A Watts in the WPAN into a single ESRV channel. All other options are related to the Kill A Watts calibration. I will come back to this important aspect later on. For now, it is enough to note that there are two calibration methods. The first one, using the offsets= option, is manual and requires the user to provide offset values (one for each Kill A Watt in the WPAN). The second one, using the calibrate and/or calibrate_samples= option(s) is semi-automatic. Both methods are mutually exclusive. The following code show two examples of how to use some of these options.


esrv ... --device_options "channels=3 offsets=500,498,502 aggregate_channels"
esrv ... --device_options "calibrate calibrate_samples=5"


Since none of the previously shown ESRV support libraries offered user options, the parse_device_option_string() function was always void. This example will be a good opportunity to show how I decided to handle the options parsing. This is purely informational and is targeted to those who do not know where to start. I hope it is helpful. The code is listed at the end of the blog.

Making sense of the readings


The overall structure of the read_device_power() function is the following:

    • Read an XBee message

    • Decode current and voltage sensors readings embedded in the message

    • Compute power data



In anticipation of the project's next episode, I am repeating these three steps for each modified Kill A Watt participating into the WPAN. Each modified Kill A Watt transmits a data frame every two seconds. The 16-bit address of the transmitting XBee modules is used to assign a message to a given ESRV channel. There are no guaranties regarding the order or the timing of the message's reception. This behavior is different compared to other power analyzers. ESRV uses the sampling interval time specified by the support library to check the timely arrival of the data, and emits a warning message in case of delay. This message is informational, and in this case I will always ignore it since the read_device_power() function does a blocking read on the serial port. As a consequence of the blocking read, even if the sampling interval is set as shown below, it is almost certain that we will return from the function, right at the time interval edge and likely over the allowed margin (set to 10ms for one second interval - 1% skew). Note that the --kernel_priority_boost option will not help here, contrary to the ESRV hint message.


//---------------------------------------------------------------------
// Set device sampling interval time.
//---------------------------------------------------------------------
p->pause_time = 2 * px->kill_a_watts_count;


I decided to resort to dynamic memory allocation as much as possible to demonstrate the use of init_device_extra_data() and delete_device_extra_data() functions. Many of the data structures used by the library depend on the number of Kill A Watts in the WPAN. For example, the size of the memory required to store the sensors readings and any management data is a function of the number of modified Kill A Watts used. This number is provided by the user via the --device_options ESRV option. Memory is mainly allocated during the second call to the init_device_extra_data() function. Few extra memory blocks are used and allocated - in addition of the ones needed for the device options string parsing - in the parse_device_option_string() function for convenience. Nonetheless, all the memory is de-allocated in the delete_device_extra_data() function. The code of the delete_device_extra_data() function as listed below, gives you an idea of the allocations required.

/*-----------------------------------------------------------------------------
Function: delete_device_extra_data
Purpose : free the device's dynamically allocated data
In : pointer to an esrv data structure
Out : none
Return : status code

History
-------------------------------------------------------------------------------
Date : Author Modification
-------------------------------------------------------------------------------
04/07/2010 Jamel Tayeb Creation.
*/
ESRV_API int delete_device_extra_data(PESRV p) {

PDEVICE_DATA px = NULL;

if(p == NULL) {
goto delete_device_extra_data_error;
}

px = p->device_data.p_device_data;
assert(px != NULL);

//-------------------------------------------------------------------------
// Free memory used to store voltage sensor offset data.
// Note:
// DC offset data memory was allocated in the cli perser.
//-------------------------------------------------------------------------
if(px->f_dc_offsets_provided == 1) {
if(px->dc_offsets_in_amperes != NULL) {
free(px->dc_offsets_in_amperes);
px->dc_offsets_in_amperes = NULL;
}
}

//-------------------------------------------------------------------------
// Free memory used to store sensors data.
//-------------------------------------------------------------------------
if(px->data_buffers != NULL) {
free(px->data_buffers);
px->data_buffers = NULL;
}

//-------------------------------------------------------------------------
// Free memory used to manage aggregation mode.
//-------------------------------------------------------------------------
if(px->f_aggregrate == 1) {
if(px->f_kill_a_watts_data_frame_received != NULL) {
free(px->f_kill_a_watts_data_frame_received);
px->f_kill_a_watts_data_frame_received = NULL;
}
}

//-------------------------------------------------------------------------
// Free memory used to manage calibration data.
//-------------------------------------------------------------------------
if(px->f_calibrate == 1) {
if(px->dc_offsets_in_amperes != NULL) {
free(px->dc_offsets_in_amperes);
px->dc_offsets_in_amperes = NULL;
}
if(px->f_kill_a_watts_calibration_done != NULL) {
free(px->f_kill_a_watts_calibration_done);
px->f_kill_a_watts_calibration_done = NULL;
}
if(px->kill_a_watts_dc_calibrations_count != NULL) {
free(px->kill_a_watts_dc_calibrations_count);
px->kill_a_watts_dc_calibrations_count = NULL;
}
if(px->kill_a_watts_sum_of_dc_samples_sums != NULL) {
free(px->kill_a_watts_sum_of_dc_samples_sums);
px->kill_a_watts_sum_of_dc_samples_sums = NULL;
}
if(px->kill_a_watts_sum_of_dc_samples_avg != NULL) {
free(px->kill_a_watts_sum_of_dc_samples_avg);
px->kill_a_watts_sum_of_dc_samples_avg = NULL;
}
}

return(ESRV_SUCCESS);
delete_device_extra_data_error:
return(ESRV_FAILURE);
}



Let’s spend some time now on the read_device_power() function. The core of this function performs a blocking read of one byte on the serial port used by ESRV (the receiver module is connected to this port). When a byte is read, it is compared to the XBee message header magic number (#define XBEE_DATA_FRAME_START_IDENTIFIER 0x7E). When such a byte is found, the two following bytes are read. These bytes are respectively the MSB and LSB of the message size, including the checksum. Once the size is known, the remainder of the message is read-in. A few checks are then performed. Is it a valid IO packet with a 16-bit address? (#define XBEE_IO16 0x83). Is the address equal to the virtual device (or channel) ESRV is looking for? Is the address in the range, etc. Other fields of the message are not decoded to speed up the function. The next byte of interest is the samples count. This count tells us how many current and voltage analog sensor readings we will find in the message. For each sample, we find the MSB and LSB of the voltage reading followed by the MSB and LSB of the current reading. We decode and store them, and maintain on-the-fly a min and max voltage value for each message. Finally, a data normalization is performed and the power (volt * amps) is computed and averaged. Only 17 samples are used and the very first sample is rejected. Up to now, I have not addressed the calibration issue. I will discuss it next, but whatever calibration method is chosen, the DC offsets are used during the normalization. Aggregation, if requested, is finally done. The code of the function is given at the end of the blog (with the definitions extracted from the header file at the very end).

Precision and calibration


Remember requirement #4? Ok, so this is where one could criticize the project. With just 17 samples and no filtering done, the power readings are a bit shaky. As is, without using calibration, it is very likely that your Kill A Watt will report a substantial power draw when nothing is connected to it. With my first Kill A Watt, it goes up to 20 Watts. Of course, even if I am ready to sacrifice some precision to save cost, I am not ready to accept a 20 Watt error. In fact, there is a way to improve the quality of the readings. This is what we call calibration. The offsets=<o1>,...,[ <on>] option allows manually specifying the DC offset to be used by the support library as shown below.


//---------------------------------------------------------------------
// Normalize current sensor data.
//---------------------------------------------------------------------
py->data_buffers[index].current_reading_buffer[i] -= dc_offset;
py->data_buffers[index].current_reading_buffer[i] /= P3_KILL_A_WATT_ADAFRUIT_CURRENT_NORMALIZATION;


With the manual calibration, the offset is provided by the user. It is straight forward. With the semi-automatic calibration method, the support library will do it for you. However, it is still a semi-automatic method and it will require that you execute four steps. Step #1: switch off – or unplug – any device connected to the Kill A Watt(s). Step #2: start ESRV and specify the calibrate and/or calibrate_samples=<n> options. Calibrate used alone will collect by default 10 samples (#define P3_KILL_A_WATT_ADAFRUIT_DEFAULT_DC_CALIBRATION_FRAMES 10) and will take the average of the DC readings as the DC offset for that Kill A Watt. It is possible to take more or less samples using the calibrate_samples option. Step #3: once all the Kill A Watts are calibrated, a message is dumped to stdout (a good practice is to save those offset values so you can re-use them later with the manual calibration method) and the measurements goes on. Step #4: it is then time to plug-in or switch on the devices.
So what is the precision after calibration? Based on my setup, it is in average ~2 Watts – but can sometime reach ~6 Watts. In the scope of this project and with the long term analysis approach, this is acceptable to me.

What next?


First of all, I am very pleased by the outcome of this first step of my project. Second, it is always interesting to use a tool – the Intel® Energy Checker SDK – in an unintended targeted area. Home energy monitoring is gaining momentum and using our SDK may motivate some end users and professionals to build their own solutions. As per this project, I will definitively continue with the second step and start adding extra modified Kill A Watts to my WPAN (still nothing in the mail, I may receive my extra kits this weekend :)). Likely I will look into the feasibility of having multiple WPANs so I can better segregate the monitored functions. This last addition will certainly bring some modifications to the support library code, so it will continue to evolve. So stay tuned! Last but not least, I noticed that our house’s old analog meter was replaced with a brand new SmartMeter™ by PG&E! The question is: can I interface it with ESRV? If yes, then how?

A short video showing ESRV monitoring a garage heater while changing the temperature. Look at the power gauge!

Jamel Tayeb



*: Since I live in Oregon, PG&E (Pacific Gas and Electric Company) is my utility provider.

Links of interest


P3 International Kill A Watt™
Adafruit Industries Tweet-A-Watt
Digi® XBee® Modules
PG&E SmartMeter™

read_device_power() function

/*-----------------------------------------------------------------------------
Function: read_device_power.
Purpose : read all measurements from P3 Kill-A-Watt device with Adafruit
Tweet-a-watt kit.
In : pointers to an esrv and a DEVICE_DATA data structure
and the virtual device's id
Out : none
Return : status code

History
-------------------------------------------------------------------------------
Date : Author Modification
-------------------------------------------------------------------------------
04/07/2010 Jamel Tayeb Creation.
*/
ESRV_API int read_device_power(PESRV p, void *px, int vd) {

//-------------------------------------------------------------------------
// Note:
// Since ESRV calls read_device_power first, the current and voltage
// readings collected in this function will be used by subsequent
// read_device_voltage and read_device_current. Indeed, the power
// is computed for the P3 Kill-A-Watt device with Adafruit Tweet-a-watt
// kit by reading voltage and current sensor readings and computing VA.
//-------------------------------------------------------------------------

int i = 0;
int j = 0;
int index = 0;
unsigned short length = 0;
ESRV_STATUS ret = ESRV_FAILURE;
PDEVICE_DATA py = NULL;

//-------------------------------------------------------------------------
// Variables used to collect packed info data -- not used are commented out.
//-------------------------------------------------------------------------
unsigned short address = 0;
unsigned char samples_count = 0;
//int f_address_broadcast = 0;
//int f_pan_broadcast = 0;
//unsigned char rssi = 0;
//unsigned char channel_indicator_high = 0;
//unsigned char channel_indicator_low = 0;

//-------------------------------------------------------------------------
// Variables used to decode sensor samples data.
//-------------------------------------------------------------------------
unsigned char msb = 0;
unsigned char lsb = 0;
unsigned short int value = 0;

//-------------------------------------------------------------------------
// Variables used to calibarte DC sensor samples.
//-------------------------------------------------------------------------
int count = 0;
unsigned short int dc_samples_sum = 0;
unsigned short int dc_samples_avg = 0;
//unsigned short int sum_of_dc_samples_sums = 0;
//unsigned short int sum_of_dc_samples_avg = 0;

//-------------------------------------------------------------------------
// Variables used in sensor samples collection and filtering.
//-------------------------------------------------------------------------
double watt_buffer[P3_KILL_A_WATT_ADAFRUIT_SAMPLES_PER_DATA_FRAME_COUNT] =
{ 0.0 };
double sum_voltage = 0.0;
double sum_amperage = 0.0;
double sum_power = 0.0;

//-------------------------------------------------------------------------
// Variables used in sensor reading normalization.
//-------------------------------------------------------------------------
double min_voltage = P3_KILL_A_WATT_ADAFRUIT_MAX_NON_NORMALIZED_VOLTAGE;
double max_voltage = 0.0;
double mid_voltage = 0.0;
double delta_voltage = 0.0;
double dc_offset = (double)P3_KILL_A_WATT_ADAFRUIT_DEFAULT_DC_OFFSET;
double used_samples = 0.0;

//-------------------------------------------------------------------------
// Variables used to manage aggregate mode.
//-------------------------------------------------------------------------
int kill_a_watts_received_count = 0;

#ifdef _DEBUG

//-------------------------------------------------------------------------
// Variables used for debug messaging.
//-------------------------------------------------------------------------
static int f_calibration_warning_done = 0;
char debug_message_buffer[MAX_PATH] = { '' };
char debug_buffer[MAX_PATH] = { '' };
#endif // _DEBUG

//-------------------------------------------------------------------------

if(p == NULL) { goto read_device_power_error; }
py = p->device_data.p_device_data;
assert(py != NULL);
if((vd <= 0) || (vd > p->device_data.virtual_devices)) {
goto read_device_power_error;
}
index = vd - 1;

if(py->f_aggregrate == 1) {
kill_a_watts_received_count = 0;
memset(
py->f_kill_a_watts_data_frame_received,
0,
(sizeof(int) * py->kill_a_watts_count)
);
}

//-------------------------------------------------------------------------
// Clear serial buffer.
//-------------------------------------------------------------------------
ret = purge_serial(p);
assert(ret == ESRV_SUCCESS);

//-------------------------------------------------------------------------
// If calibration is requested, print calibration message to stdout and
// initialize calibration data.
// Note:
// py->f_kill_a_watts_calibration_done,
// py->kill_a_watts_dc_calibrations_count,
// py->kill_a_watts_sum_of_dc_samples_sums,
// and py->kill_a_watts_sum_of_dc_samples_avg are alreay initialized.
//-------------------------------------------------------------------------
if(py->f_calibrate == 1) {
assert(py->f_kill_a_watts_calibration_done != NULL);
assert(py->kill_a_watts_dc_calibrations_count != NULL);
assert(py->kill_a_watts_sum_of_dc_samples_sums != NULL);
assert(py->kill_a_watts_sum_of_dc_samples_avg != NULL);
dc_samples_sum = 0;
dc_samples_avg = 0;
if(f_calibration_warning_done == 0) {
esrv_diagnostic(p, P3_KILL_A_WATT_ADAFRUIT_STARS);
esrv_diagnostic(p, P3_KILL_A_WATT_ADAFRUIT_CALIBRATION_MESSAGE_LINE1);
esrv_diagnostic(p, P3_KILL_A_WATT_ADAFRUIT_CALIBRATION_MESSAGE_LINE2);
sprintf(
debug_buffer,
P3_KILL_A_WATT_ADAFRUIT_CALIBRATION_MESSAGE_LINE3,
py->kill_a_watts_count,
py->calibrate_frames
);
esrv_diagnostic(p, debug_buffer);
esrv_diagnostic(p, P3_KILL_A_WATT_ADAFRUIT_STARS);
f_calibration_warning_done = 1;
}
}

read_device_power_get_next_data_frame:

//-------------------------------------------------------------------------
// Search for an XBEE_DATA_FRAME_START_IDENTIFIER byte in the data flow
// received via the serial port. Finding such byte may indicate the
// reception of a data frame.
//-------------------------------------------------------------------------
do {
p->interface_data.bytes_to_read =
XBEE_DATA_FRAME_START_IDENTIFIER_SIZE_IN_BYTES;
ret = read_serial(p);
if(ret != ESRV_SUCCESS) {
goto read_device_power_error;
}
} while(
p->interface_data.input_buffer[XBEE_DATA_FRAME_START_OFFSET] !=
XBEE_DATA_FRAME_START_IDENTIFIER
);

//-------------------------------------------------------------------------
// Read-in data frame's length MSB and LSB.
//-------------------------------------------------------------------------
p->interface_data.bytes_to_read =
XBEE_DATA_FRAME_LENGTH_MSB_SIZE_IN_BYTES +
XBEE_DATA_FRAME_LENGTH_LSB_SIZE_IN_BYTES;
ret = read_serial(p);
if(ret != ESRV_SUCCESS) {
goto read_device_power_error;
}

//-------------------------------------------------------------------------
// Decode data frame's length.
//-------------------------------------------------------------------------
length =
(p->interface_data.input_buffer[XBEE_DATA_FRAME_LENGTH_MSB_OFFSET] << 8) +
p->interface_data.input_buffer[XBEE_DATA_FRAME_LENGTH_LSB_OFFSET] +
XBEE_DATA_FRAME_CHECKSUM_SIZE_IN_BYTES
;

//-------------------------------------------------------------------------
// Read-in the data frame based on length computed (including checksum).
//-------------------------------------------------------------------------
p->interface_data.bytes_to_read = length;
ret = read_serial(p);
if(ret != ESRV_SUCCESS) {
goto read_device_power_error;
}

//-------------------------------------------------------------------------
// Check that this is a valid data frame. Start with the data frame type.
//-------------------------------------------------------------------------
if(
p->interface_data.input_buffer[XBEE_DATA_FRAME_START_OFFSET] !=
(char)XBEE_IO16
) {
goto read_device_power_get_next_data_frame;
}

//-------------------------------------------------------------------------
// Decode the 16-bit address.
//-------------------------------------------------------------------------
address =
(p->interface_data.input_buffer[XBEE_DATA_FRAME_16BIT_ADDRESS_MSB_OFFSET] << 8) +
p->interface_data.input_buffer[XBEE_DATA_FRAME_16BIT_ADDRESS_LSB_OFFSET]
;

//-------------------------------------------------------------------------
// Check if the 16-bit address of the data frame matches the requested vd.
//-------------------------------------------------------------------------
if(vd != address) {
goto read_device_power_get_next_data_frame;
}

//-------------------------------------------------------------------------
// Decode Received Signal Strength Indication (RSSI) -- not used
//-------------------------------------------------------------------------
//rssi = p->interface_data.input_buffer[XBEE_DATA_FRAME_RSSI_OFFSET];

//-------------------------------------------------------------------------
// Decode address broadcast flag -- not used
//-------------------------------------------------------------------------
//if(((p->interface_data.input_buffer[XBEE_DATA_FRAME_BROADCAST_OFFSET] >> 1) & 0x01) == 1) {
// f_address_broadcast = 1;
//}

//-------------------------------------------------------------------------
// Decode pan broadcast flag -- not used
//-------------------------------------------------------------------------
//if(((p->interface_data.input_buffer[XBEE_DATA_FRAME_BROADCAST_OFFSET] >> 2) & 0x01) == 1) {
// f_pan_broadcast = 1;
//}

//-------------------------------------------------------------------------
// Decode samples count in the data frame.
//-------------------------------------------------------------------------
samples_count =
p->interface_data.input_buffer[XBEE_DATA_FRAME_TOTAL_SAMPLES_OFFSET];

//-------------------------------------------------------------------------
// Decode channel high and low indicators -- not used
//-------------------------------------------------------------------------
//channel_indicator_high =
// p->interface_data.input_buffer[XBEE_DATA_FRAME_CHANNEL_INDICATOR_HIGH_OFFSET];
//channel_indicator_low =
// p->interface_data.input_buffer[XBEE_DATA_FRAME_CHANNEL_INDICATOR_LOW_OFFSET];

//-------------------------------------------------------------------------
// Prepare calibration round for this virtual device.
//-------------------------------------------------------------------------
if(
(py->f_calibrate == 1) &&
(py->kill_a_watts_dc_calibrations_count[index] <= py->calibrate_frames)
) {
dc_samples_sum = 0;
#ifdef _DEBUG
memset(debug_message_buffer, 0, sizeof(debug_message_buffer));
sprintf(debug_message_buffer, P3_KILL_A_WATT_ADAFRUIT_DC_VECTOR_MESSAGE_BEGIN, address);
#endif // _DEBUG
}

//-------------------------------------------------------------------------
// Decode, compute and store analog sensors data.
//-------------------------------------------------------------------------
for(
i = 0, j = XBEE_DATA_FRAME_SENSORS_DATA_OFFSET;
i < (int)samples_count;
i++, j += 4
) {

//---------------------------------------------------------------------
// Compute and store voltage sensor data.
//---------------------------------------------------------------------
msb = p->interface_data.input_buffer[j];
lsb = p->interface_data.input_buffer[j + 1];
value = ((msb << 8) + lsb);
py->data_buffers[index].voltage_reading_buffer[i] = (double)value;

//---------------------------------------------------------------------
// Update min and max voltage sensor data on the flight.
// Note:
// We drop the first sample since it is known to be too noisy.
//---------------------------------------------------------------------
if(i != 0) {
if(py->data_buffers[index].voltage_reading_buffer[i] < min_voltage) {
min_voltage = py->data_buffers[index].voltage_reading_buffer[i];
}
if(max_voltage < py->data_buffers[index].voltage_reading_buffer[i]) {
max_voltage = py->data_buffers[index].voltage_reading_buffer[i];
}
}

//---------------------------------------------------------------------
// Compute and store current sensor data.
//---------------------------------------------------------------------
msb = p->interface_data.input_buffer[j + 2];
lsb = p->interface_data.input_buffer[j + 3];
value = ((msb << 8) + lsb);
py->data_buffers[index].current_reading_buffer[i] = (double)value;

//---------------------------------------------------------------------
// Collect calibration data for this virtual device.
//---------------------------------------------------------------------
if((i != 0) && (py->f_calibrate == 1)) {
dc_samples_sum += value;
#ifdef _DEBUG
sprintf(
debug_buffer,
P3_KILL_A_WATT_ADAFRUIT_DC_VECTOR_MESSAGE_VALUE,
value
);
strncat(debug_message_buffer, debug_buffer, strlen(debug_buffer));
#endif // _DEBUG
}
} // for i, j

//-------------------------------------------------------------------------
// Compute average calibration data for this virtual device.
//-------------------------------------------------------------------------
if(py->f_calibrate == 1) {
dc_samples_avg = dc_samples_sum / (samples_count - 1);
py->kill_a_watts_sum_of_dc_samples_sums[index] += dc_samples_avg;
#ifdef _DEBUG
sprintf(
debug_buffer,
P3_KILL_A_WATT_ADAFRUIT_DC_VECTOR_MESSAGE_END,
dc_samples_avg
);
strncat(debug_message_buffer, debug_buffer, strlen(debug_buffer));
esrv_diagnostic(p, debug_message_buffer);
#endif // _DEBUG
py->kill_a_watts_dc_calibrations_count[index]++;
if(py->f_kill_a_watts_calibration_done[index] == 0) {
if(
py->kill_a_watts_dc_calibrations_count[index] ==
py->calibrate_frames
) {

//-------------------------------------------------------------
// All calibration data for this virtual device is not yet
// known. Store this virtual devices calibration data and
// continue.
//-------------------------------------------------------------
py->kill_a_watts_sum_of_dc_samples_avg[index] =
py->kill_a_watts_sum_of_dc_samples_sums[index] /
(unsigned short int)py->calibrate_frames
;
py->dc_offsets_in_amperes[index] =
(double)py->kill_a_watts_sum_of_dc_samples_avg[index];
py->f_kill_a_watts_calibration_done[index] = 1;
count++;
if(count == py->kill_a_watts_count) {

//---------------------------------------------------------
// All calibration data for all virtual devices is known.
// Deactivate calibration mode and switch-on the offseted
// mode. Offseted mode will use the calibration data as if
// it was provided by user via the offsets option.
//---------------------------------------------------------
py->f_calibrate = 0;
py->f_calibrate_frames = 0;
py->f_calibrate_frames = 0;
py->f_dc_offsets_provided = 1;
#ifdef _DEBUG
esrv_diagnostic(p, P3_KILL_A_WATT_ADAFRUIT_STARS);
sprintf(
debug_message_buffer,
P3_KILL_A_WATT_ADAFRUIT_CALIBRATION_END_MESSAGE_LINE1,
py->kill_a_watts_count
);
esrv_diagnostic(p, debug_message_buffer);
for(i = 0; i < py->kill_a_watts_count; i++) {
sprintf(
debug_message_buffer,
P3_KILL_A_WATT_ADAFRUIT_CALIBRATION_END_MESSAGE_LINEN,
i + 1,
py->kill_a_watts_sum_of_dc_samples_avg[i]
);
esrv_diagnostic(p, debug_message_buffer);
}
esrv_diagnostic(p, P3_KILL_A_WATT_ADAFRUIT_CALIBRATION_MESSAGE_LINEEND);
esrv_diagnostic(p, P3_KILL_A_WATT_ADAFRUIT_STARS);
#endif // _DEBUG
}
}
}
}

//-------------------------------------------------------------------------
// Compute voltage sensor data normalization values. Also take in account
// offset data provided by user if any -- and if not calibrating.
//-------------------------------------------------------------------------
mid_voltage = (max_voltage + min_voltage) / 2;
delta_voltage = max_voltage - min_voltage;
if(py->f_calibrate == 1) {
dc_offset = 0.0;
} else {
if(py->f_dc_offsets_provided == 1) {
dc_offset = py->dc_offsets_in_amperes[index];
}
}

//-------------------------------------------------------------------------
// Note:
// Only data for 1 60 Hz cycle is used (~17 samples).
//-------------------------------------------------------------------------
for(i = 1; i <= P3_KILL_A_WATT_ADAFRUIT_SAMPLES_IN_ONE_60HZ_CYCLE; i++) {

//---------------------------------------------------------------------
// Normalize voltage sensor data.
//---------------------------------------------------------------------
py->data_buffers[index].voltage_reading_buffer[i] -= mid_voltage;
py->data_buffers[index].voltage_reading_buffer[i] = (
py->data_buffers[index].voltage_reading_buffer[i] *
P3_KILL_A_WATT_ADAFRUIT_TWO_TIMES_MAX_VOLTAGE
) / delta_voltage;

//---------------------------------------------------------------------
// Normalize current sensor data.
//---------------------------------------------------------------------
py->data_buffers[index].current_reading_buffer[i] -= dc_offset;
py->data_buffers[index].current_reading_buffer[i] /=
P3_KILL_A_WATT_ADAFRUIT_CURRENT_NORMALIZATION;

//---------------------------------------------------------------------
// Compute VxA (Watt) for each sample.
//---------------------------------------------------------------------
watt_buffer[i] =
py->data_buffers[index].voltage_reading_buffer[i] *
py->data_buffers[index].current_reading_buffer[i]
;

//---------------------------------------------------------------------
// Accumulate sensors data for averages.
//---------------------------------------------------------------------
sum_voltage += fabs(py->data_buffers[index].voltage_reading_buffer[i]);
sum_amperage += fabs(py->data_buffers[index].current_reading_buffer[i]);
sum_power += fabs(watt_buffer[i]);
}

//-------------------------------------------------------------------------
// Kill-a-watts aggregation logic -- if applicable.
//-------------------------------------------------------------------------
if(py->f_aggregrate == 1) {
if(py->f_kill_a_watts_data_frame_received[index] == 0) {
py->f_kill_a_watts_data_frame_received[index] = 1;
kill_a_watts_received_count++;
if(kill_a_watts_received_count <= py->kill_a_watts_count) {

//-------------------------------------------------------------
// average normalized sensors reading
//-------------------------------------------------------------
if(py->f_calibrate == 1) {
p->double_power = 0.0;
p->double_voltage = 0.0;
p->double_current = 0.0;
} else {
p->double_voltage +=
sum_voltage /
(double)P3_KILL_A_WATT_ADAFRUIT_SAMPLES_IN_ONE_60HZ_CYCLE
;
p->double_current +=
sum_amperage /
(double)P3_KILL_A_WATT_ADAFRUIT_SAMPLES_IN_ONE_60HZ_CYCLE
;
p->double_power +=
sum_power /
(double)P3_KILL_A_WATT_ADAFRUIT_SAMPLES_IN_ONE_60HZ_CYCLE
;
}
goto read_device_power_get_next_data_frame;

} else {

//-------------------------------------------------------------
// received frames from all virtual devices (channels)
//-------------------------------------------------------------
kill_a_watts_received_count = 0;
memset(
py->f_kill_a_watts_data_frame_received,
0,
(sizeof(int) * py->kill_a_watts_count)
);

//-------------------------------------------------------------
// average normalized sensors reading
//-------------------------------------------------------------
if(py->f_calibrate == 1) {
p->double_power = 0.0;
p->double_voltage = 0.0;
p->double_current = 0.0;
} else {
p->double_voltage +=
sum_voltage /
(double)P3_KILL_A_WATT_ADAFRUIT_SAMPLES_IN_ONE_60HZ_CYCLE
;
p->double_current +=
sum_amperage /
(double)P3_KILL_A_WATT_ADAFRUIT_SAMPLES_IN_ONE_60HZ_CYCLE
;
p->double_power +=
sum_power /
(double)P3_KILL_A_WATT_ADAFRUIT_SAMPLES_IN_ONE_60HZ_CYCLE
;
}
goto read_device_power_aggregation_done;
}
} else {

//-----------------------------------------------------------------
// Note:
// The frame for this virtual device (channel) was already rece-
// -ived for this measurement. Do nothing.
//-----------------------------------------------------------------
;
}
} else {

//---------------------------------------------------------------------
// No aggregation requested, just average normalized sensors reading.
//---------------------------------------------------------------------
if(py->f_calibrate == 1) {
p->double_power = 0.0;
p->double_voltage = 0.0;
p->double_current = 0.0;
} else {
p->double_voltage =
sum_voltage /
(double)P3_KILL_A_WATT_ADAFRUIT_SAMPLES_IN_ONE_60HZ_CYCLE
;
p->double_current =
sum_amperage /
(double)P3_KILL_A_WATT_ADAFRUIT_SAMPLES_IN_ONE_60HZ_CYCLE
;
p->double_power =
sum_power /
(double)P3_KILL_A_WATT_ADAFRUIT_SAMPLES_IN_ONE_60HZ_CYCLE
;
}
}

read_device_power_aggregation_done:

#ifdef _DEBUG
if(py->f_calibrate == 0) {
sprintf(
debug_message_buffer,
P3_KILL_A_WATT_ADAFRUIT_READING,
address,
p->double_power,
p->double_voltage,
p->double_current
);
esrv_diagnostic(p, debug_message_buffer);
}
#endif // _DEBUG
return(ESRV_SUCCESS);

read_device_power_error:
return(ESRV_FAILURE);
}

parse_device_option_string() function

/*-----------------------------------------------------------------------------
Function: parse_device_option_string
Purpose : parse the device option string and set the esrv structure
In : pointers to an esrv and a DEVICE_DATA data structure
Out : none
Return : status code

History
-------------------------------------------------------------------------------
Date : Author Modification
-------------------------------------------------------------------------------
04/07/2010 Jamel Tayeb Creation.
*/
ESRV_API int parse_device_option_string(PESRV p, void *pd) {

int i = 0;
int j = 0;
int t = 0;
int f_error = 0;
double offset = 0.0;
PDEVICE_DATA py = NULL;
ESRV_STATUS ret = ESRV_FAILURE;

char *help_message[P3_KILL_A_WATT_ADAFRUIT_HELP_MESSAGE_LINES_COUNT] = {
P3_KILL_A_WATT_ADAFRUIT_HELP_MESSAGE_LINES
};

//-------------------------------------------------------------------------
// Variables used to tokenize the options string.
//-------------------------------------------------------------------------
char *px = NULL;
char *token = NULL;
char *sub_token = NULL;
char *buffer = NULL;
char *sub_token_buffer = NULL;
size_t length = 0;
size_t memory_size = 0;
int argc = 0;
char *argv[P3_KILL_A_WATT_ADAFRUIT_MAX_TOKENS] = { NULL };
char token_separators[] = " \t";
char sub_token_separators[] = " \t=,";

int sub_tokens_count = 0;
char *sub_tokens_array[P3_KILL_A_WATT_ADAFRUIT_MAX_SUB_TOKENS] = { NULL };

//-------------------------------------------------------------------------
// Variable holding authorized device options.
//-------------------------------------------------------------------------
static char *p3_kill_a_watt_adafruit_option_tokens[
P3_KILL_A_WATT_ADAFRUIT_OPTION_MAX_TOKENS
] = {
P3_KILL_A_WATT_ADAFRUIT_OPTIONS
};

//-------------------------------------------------------------------------

if(p == NULL) {
f_error = 1;
goto exit;
}
py = p->device_data.p_device_data;
assert(py != NULL);
px = p->device_option_string;
assert(px != NULL);

//-------------------------------------------------------------------------
// Tokenize options string and build arcg / argv data, start by allocating
// a buffer to copy the device options string.
//-------------------------------------------------------------------------
length = strlen(px);
if(length == 0) {
f_error = 1;
goto exit;
}
length++;
memory_size = sizeof(char) * length;
buffer = (char *)malloc(memory_size);
if(buffer == NULL) {
f_error = 1;
esrv_error(
p,
P3_KILL_A_WATT_ADAFRUIT_ERROR_MESSAGE_OUT_OF_MEMORY
);
goto exit;
}
memset(buffer, '', memory_size);
memcpy(buffer, px, length);

//-------------------------------------------------------------------------
// Tokenize options string in sub-tokens.
//-------------------------------------------------------------------------
token = strtok(buffer, token_separators);
while(token != NULL) {

//---------------------------------------------------------------------
// Allocate buffer to store sub-token.
//---------------------------------------------------------------------
length = strlen(token);
if(length == 0) {
f_error = 1;
esrv_error(
p,
P3_KILL_A_WATT_ADAFRUIT_ERROR_MESSAGE_INCONSISTENT_INPUT
);
goto exit;
}
length++;
memory_size = sizeof(char) * length;
sub_token_buffer = (char *)malloc(memory_size);
if(sub_token_buffer == NULL) {
f_error = 1;
esrv_error(
p,
P3_KILL_A_WATT_ADAFRUIT_ERROR_MESSAGE_OUT_OF_MEMORY
);
goto exit;
}
memset(sub_token_buffer, '', memory_size);
memcpy(sub_token_buffer, token, length);
sub_tokens_array[sub_tokens_count++] = sub_token_buffer;
token = strtok(NULL, token_separators);
}

//-------------------------------------------------------------------------
// Free memory to store options string copy.
//-------------------------------------------------------------------------
if(buffer != NULL) {
free(buffer);
buffer = NULL;
}

for(i = 0; i < sub_tokens_count; i++) {

//---------------------------------------------------------------------
// Tokenize sub-token.
//---------------------------------------------------------------------
sub_token = NULL;
sub_token = strtok(sub_tokens_array[i], sub_token_separators);
while(sub_token != NULL) {

//-----------------------------------------------------------------
// Allocate buffer to store final token.
//-----------------------------------------------------------------
length = strlen(sub_token);
if(length == 0) {
f_error = 1;
esrv_error(
p,
P3_KILL_A_WATT_ADAFRUIT_ERROR_MESSAGE_INCONSISTENT_INPUT
);
goto exit;
}
length++;
memory_size = sizeof(char) * length;
sub_token_buffer = (char *)malloc(memory_size);
if(sub_token_buffer == NULL) {
f_error = 1;
esrv_diagnostic(p, P3_KILL_A_WATT_ADAFRUIT_ERROR_MESSAGE_OUT_OF_MEMORY);
goto exit;
}
memset(sub_token_buffer, '', memory_size);
memcpy(sub_token_buffer, sub_token, length);
argv[argc++] = sub_token_buffer;
sub_token = strtok(NULL, sub_token_separators);
}

//---------------------------------------------------------------------
// Free sub-token buffer memory.
//---------------------------------------------------------------------
free(sub_tokens_array[i]);
sub_tokens_array[i] = NULL;

} // for i

//-------------------------------------------------------------------------
// Parse device options and setup PESRV device data option structure
//-------------------------------------------------------------------------
// [channels=<n>]
// [offsets=<o1>,...,[<on>]]
// [aggregate_channels]
// [calibrate]
// [calibrate_samples]
// Example:
// --device_option "channels=3 offsets=500,498,502 aggregate_channels"
//-------------------------------------------------------------------------
for(i = 0; i < argc; i++) { // for each argument
service_upper_string(argv[i]);
for(t = 0; t < P3_KILL_A_WATT_ADAFRUIT_OPTION_MAX_TOKENS; t++) {
if(
strncmp(argv[i],
p3_kill_a_watt_adafruit_option_tokens[t],
strlen(argv[i])
) == 0) {
switch(t) {

//---------------------------------------------------------
// Process [channels = <n>] option.
//---------------------------------------------------------
case P3_KILL_A_WATT_ADAFRUIT_CHANNELS_TOKEN_ID:
if(i + 1 >= argc) {
f_error = 1;
esrv_error(
p,
P3_KILL_A_WATT_ADAFRUIT_ERROR_MESSAGE_MISSING_CHANNEL_COUNT
);
goto exit;
}
py->kill_a_watts_count = atoi(argv[++i]);
if(
(py->kill_a_watts_count <
P3_KILL_A_WATT_ADAFRUIT_MIN_KILL_A_WATTS) ||
(py->kill_a_watts_count >
P3_KILL_A_WATT_ADAFRUIT_MAX_KILL_A_WATTS)
) {
f_error = 1;
esrv_error(
p,
P3_KILL_A_WATT_ADAFRUIT_ERROR_MESSAGE_WRONG_CHANNEL_COUNT
);
goto exit;
}
py->f_channels_count_provided = 1;
goto next_argument;
break;

//---------------------------------------------------------
// Process [offset = <o1>, ..., [<on>]] option.
//---------------------------------------------------------
case P3_KILL_A_WATT_ADAFRUIT_DC_OFFSETS_TOKEN_ID:
if(i + py->kill_a_watts_count >= argc) {
f_error = 1;
esrv_error(
p,
P3_KILL_A_WATT_ADAFRUIT_ERROR_MESSAGE_MISSING_OFFSET_VALUES
);
goto exit;
}
memory_size = sizeof(double) * py->kill_a_watts_count;
py->dc_offsets_in_amperes = (double *)malloc(memory_size);
if(py->dc_offsets_in_amperes == NULL) {
f_error = 1;
esrv_error(
p,
P3_KILL_A_WATT_ADAFRUIT_ERROR_MESSAGE_OUT_OF_MEMORY
);
goto exit;
}
memset(py->dc_offsets_in_amperes, 0, memory_size);
for(j = 0; j < py->kill_a_watts_count; j++) {
offset = atof(argv[++i]);
if(
(offset <
P3_KILL_A_WATT_ADAFRUIT_MIN_DC_OFFSET_IN_AMPERES) ||
(offset >
P3_KILL_A_WATT_ADAFRUIT_MAX_DC_OFFSET_IN_AMPERES)
) {
f_error = 1;
esrv_error(
p,
P3_KILL_A_WATT_ADAFRUIT_ERROR_MESSAGE_WRONG_OFFSET_VALUES
);
goto exit;
}
py->dc_offsets_in_amperes[j] = offset;
}
py->f_dc_offsets_provided = 1;
goto next_argument;
break;

//---------------------------------------------------------
// Process [aggregate_channels] option.
//---------------------------------------------------------
case P3_KILL_A_WATT_ADAFRUIT_AGGREGATE_CHANNELS_TOKEN_ID:
py->f_aggregate = 1;
goto next_argument;
break;

//---------------------------------------------------------
// Process [calibrate] option.
//---------------------------------------------------------
case P3_KILL_A_WATT_ADAFRUIT_CALIBRATE_TOKEN_ID:
py->f_calibrate = 1;
py->f_calibrate_frames = 1;
py->calibrate_frames =
P3_KILL_A_WATT_ADAFRUIT_DEFAULT_DC_CALIBRATION_FRAMES;
goto next_argument;
break;

//---------------------------------------------------------
// Process [calibrate_samples = <n>] option.
//---------------------------------------------------------
case P3_KILL_A_WATT_ADAFRUIT_CALIBRATE_FRAMES_TOKEN_ID:
if(i + 1 >= argc) {
f_error = 1;
esrv_error(
p,
P3_KILL_A_WATT_ADAFRUIT_ERROR_MESSAGE_MISSING_CALIBRATION_FRAMES_COUNT
);
goto exit;
}
py->calibrate_frames = atoi(argv[++i]);
if(
(py->calibrate_frames <
P3_KILL_A_WATT_ADAFRUIT_MIN_DC_CALIBRATION_FRAMES) ||
(py->calibrate_frames >
P3_KILL_A_WATT_ADAFRUIT_MAX_DC_CALIBRATION_FRAMES)
) {
f_error = 1;
esrv_error(
p,
P3_KILL_A_WATT_ADAFRUIT_ERROR_MESSAGE_WRONG_CALIBRATION_FRAMES_COUNT
);
goto exit;
}
py->f_calibrate_frames = 1;
py->f_calibrate = 1;
goto next_argument;
break;

default:
f_error = 1;
esrv_error(
p,
P3_KILL_A_WATT_ADAFRUIT_ERROR_MESSAGE_INCONSISTENT_INPUT
);
goto exit;
} // switch
} // if token recognized
} // for each allowed option token (t)
next_argument:
;
} // for for each argument (i)

//-------------------------------------------------------------------------
// Check for inconsistent user input.
//-------------------------------------------------------------------------
if(
(py->f_dc_offsets_provided == 1) &&
(
(py->f_calibrate == 1) ||
(py->f_calibrate_frames == 1)
)
) {
f_error = 1;
esrv_error(
p,
P3_KILL_A_WATT_ADAFRUIT_ERROR_MESSAGE_EXCLUSIVE_OFFSERT_CALIBRATE
);
}

//-------------------------------------------------------------------------
// set missing default values.
//-------------------------------------------------------------------------
if(py->f_channels_count_provided == 0) {
py->kill_a_watts_count = 1;
}

//-------------------------------------------------------------------------
// Allocate memory to store calibration data.
//-------------------------------------------------------------------------
if(
(py->f_calibrate == 1) ||
(py->f_calibrate_frames == 1)
) {
memory_size = sizeof(double) * py->kill_a_watts_count;
py->dc_offsets_in_amperes = (double *)malloc(memory_size);
if(py->dc_offsets_in_amperes == NULL) {
f_error = 1;
esrv_error(
p,
P3_KILL_A_WATT_ADAFRUIT_ERROR_MESSAGE_OUT_OF_MEMORY
);
goto exit;
}
memset(py->dc_offsets_in_amperes, 0, memory_size);
}

exit:

//-------------------------------------------------------------------------
// Clean-up and exit.
//-------------------------------------------------------------------------
for(i = 0; i < sub_tokens_count; i++) {
if(sub_tokens_array[i] != NULL) {
free(sub_tokens_array[i]);
sub_tokens_array[i] = NULL;
}
}
for(i = 0; i < argc; i++) {
if(argv[i] != NULL) {
free(argv[i]);
argv[i] = NULL;
}
}
if(buffer != NULL) {
free(buffer);
buffer = NULL;
}
if(f_error == 1) {
if(py->dc_offsets_in_amperes != NULL) {
free(py->dc_offsets_in_amperes);
py->dc_offsets_in_amperes = NULL;
}

//---------------------------------------------------------------------
// Dump support module help message.
//---------------------------------------------------------------------
esrv_diagnostic(p, P3_KILL_A_WATT_ADAFRUIT_STARS);
for(i = 0; i < P3_KILL_A_WATT_ADAFRUIT_HELP_MESSAGE_LINES_COUNT; i++) {
esrv_diagnostic(p, help_message[i]);
}
esrv_diagnostic(p, P3_KILL_A_WATT_ADAFRUIT_STARS);

return(ESRV_FAILURE);
} else {
return(ESRV_SUCCESS);
}
}

Extract of the header file

//-----------------------------------------------------------------------------
// general definitions
//-----------------------------------------------------------------------------
#define P3_KILL_A_WATT_ADAFRUIT_MAX_TOKENS 32
#define P3_KILL_A_WATT_ADAFRUIT_MAX_SUB_TOKENS 64
#define P3_KILL_A_WATT_ADAFRUIT_DEVICE_NAME "P3 KILL-A-WATT ADAFRUIT"

//-----------------------------------------------------------------------------
// Error message definitions
//-----------------------------------------------------------------------------
#define P3_KILL_A_WATT_ADAFRUIT_ERROR_MESSAGE_OUT_OF_MEMORY "Out Of Memory. [KILL-A-WATT]"
#define P3_KILL_A_WATT_ADAFRUIT_ERROR_MESSAGE_INCONSISTENT_INPUT "Inconsistent Option(s) Provided. [KILL-A-WATT]"
#define P3_KILL_A_WATT_ADAFRUIT_ERROR_MESSAGE_MISSING_CHANNEL_COUNT "Missing Channels Count Value. [KILL-A-WATT]"
#define P3_KILL_A_WATT_ADAFRUIT_ERROR_MESSAGE_WRONG_CHANNEL_COUNT "Wrong Channel(s) Count Value Provided. [KILL-A-WATT]"
#define P3_KILL_A_WATT_ADAFRUIT_ERROR_MESSAGE_MISSING_OFFSET_VALUES "Missing Offset Value(s). [KILL-A-WATT]"
#define P3_KILL_A_WATT_ADAFRUIT_ERROR_MESSAGE_WRONG_OFFSET_VALUES "Wrong Offset Value(s) Provided. [KILL-A-WATT]"
#define P3_KILL_A_WATT_ADAFRUIT_ERROR_MESSAGE_MISSING_CALIBRATION_FRAMES_COUNT "Missing Calibration Sample(s) Count Value. [KILL-A-WATT]"
#define P3_KILL_A_WATT_ADAFRUIT_ERROR_MESSAGE_WRONG_CALIBRATION_FRAMES_COUNT "Wrong Calibration Sample(s) Count Provided. [KILL-A-WATT]"
#define P3_KILL_A_WATT_ADAFRUIT_ERROR_MESSAGE_EXCLUSIVE_OFFSERT_CALIBRATE "Use [offset = , ..., []] ; Or [calibrate] And/Or [calibrate_samples ] Options. [KILL-A-WATT]"

//-----------------------------------------------------------------------------
// Help message definitions
//-----------------------------------------------------------------------------
#define P3_KILL_A_WATT_ADAFRUIT_HELP_MESSAGE_LINES_COUNT 38
#define P3_KILL_A_WATT_ADAFRUIT_HELP_MESSAGE_LINES \
"", \
"P3 Kill-A-Watt* / Adafruit Industries* ESRV support module help.", \
"", \
"[channels=]:", \
" Specify the number of Kill-A-Watt(s) devices in the PAN.", \
" By default n=1. Minimum is 1 and maximum is 32.", \
"", \
"[aggregate_channels]:", \
" Aggregate all Kill-A-Watt(s) readings into a single channel.", \
"", \
"[calibrate]:", \
" Request automatic calibration of all Kill-A-Watt(s).", \
" Note:", \
" Automatic calibration requires that no current is drawn from", \
" any Kill-A-Watt. In other words, switch off any device", \
" connected to any Kill-A-Watt. The module will display a", \
" message when calibration of all Kill-A-Watts is done.", \
" At this stage, devices can be switched on.", \
"", \
"[calibrate_samples=]:", \
" Specify the number of sample(s) used to calibrate a Kill-A-Watt.", \
" By default, n = 10. Minimum is 1 and maximum is 1024.", \
" Note:", \
" If this option is specified then calibrate is optional.", \
"", \
"[offsets=,...,[]]:", \
" specifies for each Kill-A-Watt the DC offset to use.", \
" There must be as many offset values as Kill-A-Watt(s).", \
" Minimum is 0, maximum is 1023.", \
" Note:", \
" Provide offsets or request calibration, not both. (exclusive options).", \
"", \
"Exemples:", \
" esrv ... --device_options \"channels=3 offsets=500,498,502 aggregate_channels\"", \
" esrv ... --device_options \"calibrate calibrate_samples=5\"", \
"", \
" * Third-party trademarks are the property of their respective owners.", \
""

//-----------------------------------------------------------------------------
// A/D in the XBee is 10 bits, and will return values between 0 and 1023.
//-----------------------------------------------------------------------------
#define P3_KILL_A_WATT_ADAFRUIT_DEFAULT_DC_OFFSET 492.0
#define P3_KILL_A_WATT_ADAFRUIT_MIN_DC_CALIBRATION_FRAMES 1
#define P3_KILL_A_WATT_ADAFRUIT_MAX_DC_CALIBRATION_FRAMES 1024
#define P3_KILL_A_WATT_ADAFRUIT_DEFAULT_DC_CALIBRATION_FRAMES 10
#define P3_KILL_A_WATT_ADAFRUIT_MIN_DC_OFFSET_IN_AMPERES 0.0
#define P3_KILL_A_WATT_ADAFRUIT_MAX_DC_OFFSET_IN_AMPERES 1023.0
#define P3_KILL_A_WATT_ADAFRUIT_SAMPLES_PER_DATA_FRAME_COUNT 19
#define P3_KILL_A_WATT_ADAFRUIT_MIN_KILL_A_WATTS 1
#define P3_KILL_A_WATT_ADAFRUIT_MAX_KILL_A_WATTS 16
#define P3_KILL_A_WATT_ADAFRUIT_MAX_VOLTAGE +170.0
#define P3_KILL_A_WATT_ADAFRUIT_TWO_TIMES_MAX_VOLTAGE (P3_KILL_A_WATT_ADAFRUIT_MAX_VOLTAGE * 2.0)
#define P3_KILL_A_WATT_ADAFRUIT_CURRENT_NORMALIZATION 15.5
#define P3_KILL_A_WATT_ADAFRUIT_MAX_NON_NORMALIZED_VOLTAGE 1024.0
#define P3_KILL_A_WATT_ADAFRUIT_SAMPLES_IN_ONE_60HZ_CYCLE 17

//-----------------------------------------------------------------------------
// XBee comms defines -- unused defines are commented-out.
//-----------------------------------------------------------------------------
#define XBEE_IO16 0x83
#define XBEE_DATA_FRAME_START_OFFSET 0
#define XBEE_DATA_FRAME_SENSORS_DATA_OFFSET 8
#define XBEE_DATA_FRAME_START_IDENTIFIER 0x7E
#define XBEE_DATA_FRAME_START_IDENTIFIER_SIZE_IN_BYTES 1
#define XBEE_DATA_FRAME_LENGTH_MSB_OFFSET (XBEE_DATA_FRAME_START_OFFSET)
#define XBEE_DATA_FRAME_LENGTH_LSB_OFFSET (XBEE_DATA_FRAME_LENGTH_MSB_OFFSET + 1)
#define XBEE_DATA_FRAME_LENGTH_MSB_SIZE_IN_BYTES 1
#define XBEE_DATA_FRAME_LENGTH_LSB_SIZE_IN_BYTES 1
#define XBEE_DATA_FRAME_CHECKSUM_SIZE_IN_BYTES 1
#define XBEE_DATA_FRAME_16BIT_ADDRESS_MSB_OFFSET (XBEE_DATA_FRAME_START_OFFSET + 1)
#define XBEE_DATA_FRAME_16BIT_ADDRESS_LSB_OFFSET (XBEE_DATA_FRAME_LENGTH_MSB_OFFSET + 2)
#define XBEE_DATA_FRAME_TOTAL_SAMPLES_OFFSET 5
//#define XBEE_DATA_FRAME_RSSI_OFFSET 3
//#define XBEE_DATA_FRAME_BROADCAST_OFFSET 4
//#define XBEE_DATA_FRAME_CHANNEL_INDICATOR_HIGH_OFFSET 6
//#define XBEE_DATA_FRAME_CHANNEL_INDICATOR_LOW_OFFSET 7

//-----------------------------------------------------------------------------
// device option string authorized options.
//-----------------------------------------------------------------------------
#define P3_KILL_A_WATT_ADAFRUIT_CHANNELS "CHANNELS"
#define P3_KILL_A_WATT_ADAFRUIT_DC_OFFSETS "OFFSETS"
#define P3_KILL_A_WATT_ADAFRUIT_AGGREGATE_CHANNELS "AGGREGATE_CHANNELS"
#define P3_KILL_A_WATT_ADAFRUIT_CALIBRATE "CALIBRATE"
#define P3_KILL_A_WATT_ADAFRUIT_CALIBRATE_FRAMES "CALIBRATE_SAMPLES"

#define P3_KILL_A_WATT_ADAFRUIT_OPTION_MAX_TOKENS 5

#define P3_KILL_A_WATT_ADAFRUIT_OPTIONS \
P3_KILL_A_WATT_ADAFRUIT_CHANNELS, \
P3_KILL_A_WATT_ADAFRUIT_DC_OFFSETS, \
P3_KILL_A_WATT_ADAFRUIT_AGGREGATE_CHANNELS, \
P3_KILL_A_WATT_ADAFRUIT_CALIBRATE, \
P3_KILL_A_WATT_ADAFRUIT_CALIBRATE_FRAMES

typedef enum _p3_kill_a_watt_adafruit__option_token_id {

P3_KILL_A_WATT_ADAFRUIT_CHANNELS_TOKEN_ID = 0,
P3_KILL_A_WATT_ADAFRUIT_DC_OFFSETS_TOKEN_ID,
P3_KILL_A_WATT_ADAFRUIT_AGGREGATE_CHANNELS_TOKEN_ID,
P3_KILL_A_WATT_ADAFRUIT_CALIBRATE_TOKEN_ID,
P3_KILL_A_WATT_ADAFRUIT_CALIBRATE_FRAMES_TOKEN_ID

} P3_KILL_A_WATT_ADAFRUIT_OPTION_TOKEN_ID;

//-----------------------------------------------------------------------------
// device options structure
//-----------------------------------------------------------------------------
typedef struct _p3_kill_a_watt_adafruit_samples_buffer {
double current_reading_buffer[P3_KILL_A_WATT_ADAFRUIT_SAMPLES_PER_DATA_FRAME_COUNT];
double voltage_reading_buffer[P3_KILL_A_WATT_ADAFRUIT_SAMPLES_PER_DATA_FRAME_COUNT];
} P3_KILL_A_WATT_ADAFRUIT_SAMPLES_BUFFER, *PP3_KILL_A_WATT_ADAFRUIT_SAMPLES_BUFFER;

typedef struct _device_data {


//-------------------------------------------------------------------------
// device options
//-------------------------------------------------------------------------
int kill_a_watts_count;
double *dc_offsets_in_amperes;
int f_channels_count_provided;
int f_dc_offsets_provided;
int f_aggregate;
int f_calibrate;
int f_calibrate_frames;
int calibrate_frames;

//-------------------------------------------------------------------------
// data used to manage calibration.
//-------------------------------------------------------------------------
int *f_kill_a_watts_calibration_done;
int *kill_a_watts_dc_calibrations_count;
unsigned short int *kill_a_watts_sum_of_dc_samples_sums;
unsigned short int *kill_a_watts_sum_of_dc_samples_avg;

//-------------------------------------------------------------------------
// data used to track data frames received from kill a watts
//-------------------------------------------------------------------------
int *f_kill_a_watts_data_frame_received;

//-------------------------------------------------------------------------
// data frame buffers
//-------------------------------------------------------------------------
P3_KILL_A_WATT_ADAFRUIT_SAMPLES_BUFFER *data_buffers;

} DEVICE_DATA, *PDEVICE_DATA;

//-----------------------------------------------------------------------------
// functions prototype
//-----------------------------------------------------------------------------
ESRV_API int init_device_extra_data(PESRV);
ESRV_API int delete_device_extra_data(PESRV);
ESRV_API int open_device(PESRV, void *);
ESRV_API int close_device(PESRV, void *);
ESRV_API int parse_device_option_string(PESRV, void *);
ESRV_API int read_device_power(PESRV, void *, int);
ESRV_API int read_device_energy(PESRV, void *, int, int);
ESRV_API int read_device_current(PESRV, void *, int);
ESRV_API int read_device_voltage(PESRV, void *, int);
如需更全面地了解编译器优化,请参阅优化注意事项