Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

MCP39F511N metrics provider #429

Merged
merged 4 commits into from
Oct 20, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions config.yml.example
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,8 @@ measurement:
# resolution: 100
# psu.energy.ac.powerspy2.machine.provider.PsuEnergyAcPowerspy2MachineProvider:
# resolution: 250
# psu.energy.ac.mcp.machine.provider.PsuEnergyAcMcpMachineProvider:
# resolution: 100
# psu.energy.ac.ipmi.machine.provider.PsuEnergyAcIpmiMachineProvider:
# resolution: 100
#--- Sensors - these providers need the lm-sensors package installed
Expand Down
6 changes: 6 additions & 0 deletions metric_providers/psu/energy/ac/mcp/machine/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
CFLAGS = -o3 -Wall -lm

metric-provider-binary: source.c
gcc $< $(CFLAGS) -o $@
sudo chown root $@
sudo chmod u+s $@
3 changes: 3 additions & 0 deletions metric_providers/psu/energy/ac/mcp/machine/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# Information

TODO
10 changes: 10 additions & 0 deletions metric_providers/psu/energy/ac/mcp/machine/mcp_com.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
#ifndef __MCP_COM_H
#define __MCP_COM_H

enum mcp_types { f501, f511 };

int f511_init(const char *port);
/* Power in 10mW for channel 1 and 2 */
int f511_get_power(int *ch1, int *ch2, int fd);

#endif
14 changes: 14 additions & 0 deletions metric_providers/psu/energy/ac/mcp/machine/provider.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import os

#pylint: disable=import-error, invalid-name
from metric_providers.base import BaseMetricProvider

class PsuEnergyAcMcpMachineProvider(BaseMetricProvider):
def __init__(self, resolution):
super().__init__(
metric_name='psu_energy_ac_mcp_machine',
metrics={'time': int, 'value': int},
resolution=resolution,
unit="mJ",
current_dir=os.path.dirname(os.path.abspath(__file__)),
)
263 changes: 263 additions & 0 deletions metric_providers/psu/energy/ac/mcp/machine/source.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,263 @@
#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdint.h>
#include <string.h>
#include <termios.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/time.h>

#include "mcp_com.h"

/*
This file is mostly copied from https://github.com/osmhpi/pinpoint/blob/master/src/data_sources/mcp_com.c
Credits to Sven Köhler and the OSM group from the HPI

In case the file is not original work: Possible prior origins of the file are unknown. However it is an implementation
of the protocol defined here:
- https://www.microchip.com/en-us/development-tool/ADM00706
- https://ww1.microchip.com/downloads/aemDocuments/documents/OTH/ProductDocuments/DataSheets/20005473B.pdf
*/

// Set address pointer to 0xa (active power), read 32 bits
const unsigned char f501_read_active_power[] = { 0x41, 0x0, 0xa, 0x44 };
const unsigned char f501_read_apparent_power_divisor[] =
{ 0x41, 0x00, 0x40, 0x52 };
const unsigned char f501_set_apparent_power_divisor[] =
{ 0x41, 0x00, 0x40, 0x57, 0x00, 0x03 };
const unsigned char f501_set_accumulation_interval[] =
{ 0x41, 0x00, 0x5A, 0x57, 0x00, 0x00 };
const unsigned char f501_read_range[] = { 0x41, 0x00, 0x48, 0x44 };

const unsigned char f511_read_active_power[] = { 0x41, 0x0, 0x16, 0x4E, 8 };
const unsigned char f511_read_active_power1[] = { 0x41, 0x0, 0x16, 0x4E, 4 };
const unsigned char f511_read_active_power2[] = { 0x41, 0x0, 0x1a, 0x4E, 4 };
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is the difference between these read power chars?

const unsigned char f511_set_accumulation_interval[] =
{ 0x41, 0x00, 0xA8, 0x4D, 2, 0x00, 0x00 };

/* This variable ist just global for consitency with our other metric_provider source files */
static unsigned int msleep_time=1000;

enum mcp_states { init, wait_ack, get_len, get_data, validate_checksum };

enum mcp_states mcp_state = wait_ack;

int init_serial(const char *port, int baud)
{
struct termios tty;
int fd;

fd = open(port, O_RDWR | O_NOCTTY | O_SYNC);
if (fd < 0) {
return -1;
}

if (tcgetattr(fd, &tty) < 0) {
return -1;
}

cfsetospeed(&tty, (speed_t) baud);
cfsetispeed(&tty, (speed_t) baud);

tty.c_cflag |= (CLOCAL | CREAD); /* ignore modem controls */
tty.c_cflag &= ~CSIZE;
tty.c_cflag |= CS8; /* 8-bit characters */
tty.c_cflag &= ~PARENB; /* no parity bit */
tty.c_cflag &= ~CSTOPB; /* only need 1 stop bit */
tty.c_cflag &= ~CRTSCTS; /* no hardware flowcontrol */

/* setup for non-canonical mode */
tty.c_iflag &=
~(IGNBRK | BRKINT | PARMRK | ISTRIP | INLCR | IGNCR | ICRNL | IXON);
tty.c_lflag &= ~(ECHO | ECHONL | ICANON | ISIG | IEXTEN);
tty.c_oflag &= ~OPOST;

/* fetch bytes as they become available */
tty.c_cc[VMIN] = 1;
tty.c_cc[VTIME] = 1;

if (tcsetattr(fd, TCSANOW, &tty) != 0) {
return -1;
}
return fd;
}

int mcp_cmd(unsigned char *cmd, unsigned int cmd_length, unsigned char *reply, int fd)
{
int CMD_MAX_PACKET_LEN = 80;
unsigned char buf[80];
unsigned char command_packet[CMD_MAX_PACKET_LEN];
int rdlen;
uint8_t len;
uint8_t i;
uint8_t checksum = 0;
uint8_t datap = 0;

// the cmd has a length. Now we create a command_packet with an initializer (0xa5 + length + cmd + checksum), which
// makes it in the end cmd_length + 3
command_packet[0] = 0xa5;
command_packet[1] = cmd_length + 3; // cmd_length gets extended by 3 byte for the command packet

if (cmd_length > CMD_MAX_PACKET_LEN - 3) {
fprintf(stderr, "Error: cmd_length was %d but should be < %d\n", cmd_length, CMD_MAX_PACKET_LEN);
return -1;
}

// only write here cmd_length lenght as this is the actual length we have
// copy it in starting from the 2nd position in the char array, since first two are taken for initialize and length
memcpy(command_packet + 2, cmd, cmd_length);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

cmd and cmd_length are not checked if they fit into cmd

for (i = 0; i < cmd_length + 2; i++) { // here we do not need to iterate to cmd_length+3 since we are just bulding the last element
checksum += command_packet[i];
}
command_packet[i] = checksum;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why if cmd_length is > 80

tcflush(fd, TCIOFLUSH);
// here we still have to write the +3 lenght, as this is how we now sized the command_packet
len = write(fd, command_packet, cmd_length + 3);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is fd passed as a parameter if it is defined globally?

if (len != cmd_length + 3) {
return -1;
}
tcdrain(fd);
while (1) {
rdlen = read(fd, buf, 1);
if (rdlen == 0) {
return -1;
}
switch (mcp_state) {
case wait_ack:
if (buf[0] == 0x06) {
/* Only read commands will return more than an ACK */
if ((command_packet[5] == 0x44)
|| (command_packet[5] == 0x52)
|| (command_packet[5] == 0x4e)) {
mcp_state = get_len;
} else {
return 0;
}
}
break;
case get_len:
len = buf[0];
/* Workaround for sporadically broken packets, fix me! */
if(len != 11){
mcp_state = wait_ack;
return -1;
}
mcp_state = get_data;
break;
case get_data:
reply[datap++] = buf[0];
if ((datap + 2) == (len - 1)) {
mcp_state = validate_checksum;
}
break;
case validate_checksum:
mcp_state = wait_ack;
checksum = 0x06 + len;
for (i = 0; i < (len - 3); i++) {
checksum += reply[i];
}
if (checksum == buf[0]) {
return len - 3;
} else {
return -1;
}
break;
default:
mcp_state = wait_ack;
}

}
}



int f511_get_power(int *ch1, int *ch2, int fd)
{
int res;
unsigned char reply[40];
res = mcp_cmd((unsigned char *)&f511_read_active_power,
sizeof(f511_read_active_power), (unsigned char *)&reply, fd);
if (res > 0) {
*ch1 = (reply[3] << 24) + (reply[2] << 16)
+ (reply[1] << 8) + reply[0];
*ch2 = (reply[7] << 24) + (reply[6] << 16)
+ (reply[5] << 8) + reply[4];
return 0;
} else {
return -1;
}
}

int f511_init(const char *port)
{
unsigned char reply[80];
int res;
int fd;

fd = init_serial(port, B115200);

if (fd < 0) {
fprintf(stderr, "Error. init_serial was not 0 but %d\n", fd);
return -1;
}
res = mcp_cmd((unsigned char *)f511_set_accumulation_interval,
sizeof(f511_set_accumulation_interval),
(unsigned char *)&reply, fd);
if(res < 0) {
fprintf(stderr, "Error. res was not 0 but %d\n", res);
return -1;
}
return fd;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Where is fd defined? Or is this the global fd? Why return it then?

}



int main(int argc, char **argv) {

int c;
struct timeval now;
int fd;
int result;
int data[2]; // The MCP has two outlets where you can measure.


while ((c = getopt (argc, argv, "hi:d")) != -1) {
switch (c) {
case 'h':
printf("Usage: %s [-h] [-m]\n\n",argv[0]);
printf("\t-h : displays this help\n");
printf("\t-i : specifies the milliseconds sleep time that will be slept between measurements\n\n");
exit(0);
case 'i':
msleep_time = atoi(optarg);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is msleep_time global and not in main?

break;
default:
fprintf(stderr,"Unknown option %c\n",c);
exit(-1);
}
}

setvbuf(stdout, NULL, _IONBF, 0);

fd = f511_init("/dev/ttyACM0");
if(fd < 0) {
fprintf(stderr, "Error. Connection could not be opened\n");
return -1;
}

while (1) {
result = f511_get_power(&data[0], &data[1], fd);
if(result != 0) {
fprintf(stderr, "Error. Result was not 0 but %d\n", result);
break;
}
// The MCP returns the current power consumption in 10mW steps.
gettimeofday(&now, NULL);
printf("%ld%06ld %ld\n", now.tv_sec, now.tv_usec, (long)(data[0]*10*((double)msleep_time/1000)) );
usleep(msleep_time*1000);
}
close(fd);

return 0;
}
Loading