Skip to content

Commit

Permalink
MCP39F511N metrics provider
Browse files Browse the repository at this point in the history
  • Loading branch information
ArneTR committed Aug 16, 2023
1 parent 698e42f commit 9aacd85
Show file tree
Hide file tree
Showing 6 changed files with 274 additions and 0 deletions.
2 changes: 2 additions & 0 deletions config.yml.example
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,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
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__)),
)
239 changes: 239 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,239 @@
#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 };
const unsigned char f511_set_accumulation_interval[] =
{ 0x41, 0x00, 0xA8, 0x4D, 2, 0x00, 0x00 };

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 fd;

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

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 0;
}

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

command_packet[0] = 0xa5;
command_packet[1] = cmd_length + 3;
memcpy(command_packet + 2, cmd, cmd_length);
for (i = 0; i < cmd_length + 2; i++) {
checksum += command_packet[i];
}
command_packet[i] = checksum;
tcflush(fd, TCIOFLUSH);
len = write(fd, command_packet, cmd_length + 3);
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;

if (init_serial(port, B115200) < 0) {
return -1;
}
res =
mcp_cmd((unsigned char *)f511_set_accumulation_interval,
sizeof(f511_set_accumulation_interval),
(unsigned char *)&reply, fd);
if(res < 0) return res;
return fd;
}



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

int c;
struct timeval now;

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);
break;
default:
fprintf(stderr,"Unknown option %c\n",c);
exit(-1);
}
}

setvbuf(stdout, NULL, _IONBF, 0);

long fd = f511_init("/dev/ttyACM0");
int data[2]; // The MCP has two outlets where you can measure.

while (1) {
int result = f511_get_power(&data[0], &data[1], fd);
if(result != 0) {
printf("Error. Result was not 0\n");
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;
}

0 comments on commit 9aacd85

Please sign in to comment.