Skip to content

Instantly share code, notes, and snippets.

@NeoCat
Last active May 16, 2018 07:05
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save NeoCat/73f8ed86db468dd005a2 to your computer and use it in GitHub Desktop.
Save NeoCat/73f8ed86db468dd005a2 to your computer and use it in GitHub Desktop.
スマートメーターからの電力をBP35A1を使って取得・Webブラウザ上でリアルタイムにグラフ描画 (詳細: http://d.hatena.ne.jp/NeoCat/20160117/1453021993 )
<!DOCTYPE HTML>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<title>Power</title>
<script type="text/javascript" src="http://code.jquery.com/jquery-2.2.0.min.js"></script>
<script src="https://code.highcharts.com/highcharts.js"></script>
<script src="https://code.highcharts.com/highcharts-more.js"></script>
<script src="https://code.highcharts.com/modules/exporting.js"></script>
<style type="text/css">
</style>
<script type="text/javascript">
var websocket_url = 'ws://127.0.0.1:5000';
var chart2, chart3;
var daily = [];
$(function() {
$(document).ready(function () {
Highcharts.setOptions({global: { useUTC: false }});
var conf = {
chart: {
type: 'area',
animation: Highcharts.svg,
events: { load: function(){ load(this); } },
},
credits: { enabled: false },
title: { text: '' },
xAxis: {
type: 'datetime',
tickPixelInterval: 50,
labels: { rotation: -45 }
},
yAxis: {
title: {
text: 'Power [W]'
},
max: 3000,
min: 0,
},
tooltip: {
formatter: function () {
return '<b>' + this.series.name + '</b><br/>' +
Highcharts.dateFormat('%Y-%m-%d %H:%M:%S', this.x) + '<br/>' +
Math.round(this.y) + " W";
}
},
legend: { enabled: false },
exporting: { enabled: false },
series: [{
name: 'Power',
data: [],
color: 'null',
marker: { enabled: false },
fillColor: {
linearGradient: {x1: 0, y1: 0, x2: 0, y2: 1},
stops: [
[0.0, '#ff0000'],
[0.3, '#dddd00'],
[0.5, '#00ff00'],
[0.8, '#00ffff'],
[1.0, '#0000ff'],
]
},
}],
};
$('#container').highcharts(conf);
conf.chart.type = 'column';
conf.chart.events.load = function(){ chart2 = this; };
conf.series[0].color = 'null';
$('#container2').highcharts(conf);
conf.chart.type = 'area';
conf.xAxis.labels.rotation = 0;
conf.xAxis.tickPixelInterval = 100;
conf.yAxis.tickPixelInterval = 20;
var day = 24*60*60*1000;
var tz = new Date('1/1/1').getTime() % day;
for (var i = 0; i < 4; i++) {
var date = new Date().getTime() - i*day;
date -= (date - tz) % day;
conf.xAxis.min = date;
conf.xAxis.max = date + day;
conf.chart.events.load = function(){ daily[i] = this; };
$('#day' + i).highcharts(conf);
}
$('#container3').highcharts({
chart: {
type: 'gauge',
plotBackgroundColor: null,
plotBackgroundImage: null,
plotBorderWidth: 0,
plotShadow: false,
spacingTop: -100,
spacingLeft: 0,
spacingRight: 0,
spacingBottom: -50,
animation: { duration: 2500 },
events: { load: function(){ chart3 = this; } },
},
title: { text: 'Power' },
pane: {
center: ['50%', '75%'],
startAngle: -90,
endAngle: 90,
background: [{
shape: 'arc',
backgroundColor: {
linearGradient: { x1: 0, y1: 0, x2: 0, y2: 1 },
stops: [
[0, '#FFF'],
[1, '#333']
]
},
borderWidth: 0,
outerRadius: '109%'
}, {
shape: 'arc',
backgroundColor: {
linearGradient: { x1: 0, y1: 0, x2: 0, y2: 1 },
stops: [
[0, '#333'],
[1, '#FFF']
]
},
borderWidth: 1,
outerRadius: '107%'
}, {
// default background
shape: 'arc',
}, {
shape: 'arc',
backgroundColor: '#DDD',
borderWidth: 0,
outerRadius: '105%',
innerRadius: '103%'
}]
},
yAxis: {
min: -150,
max: 3150,
minorTickInterval: 'auto',
minorTickWidth: 1,
minorTickLength: 10,
minorTickPosition: 'inside',
minorTickColor: '#666',
tickPixelInterval: 30,
tickWidth: 2,
tickPosition: 'inside',
tickLength: 10,
tickColor: '#666',
labels: {
step: 2,
rotation: 'auto'
},
title: {
text: 'W'
},
plotBands: [{
from: 0,
to: 1500,
color: '#55BF3B' // green
}, {
from: 1500,
to: 2000,
color: '#DDDF0D' // yellow
}, {
from: 2000,
to: 3000,
color: '#DF5353' // red
}]
},
series: [{
name: 'Power',
dataLabels: { x: 0, y: -40 },
data: [0],
tooltip: {
valueSuffix: ' W'
}
}]
});
});
});
function color(val) {
if (val < 500) return '#0000ff';
if (val < 1000) return '#00ffff';
if (val < 1500) return '#00ff00';
if (val < 2000) return '#dddd00';
return '#ff0000';
}
var realtime = false, shift = false, shift2 = false;
var nr = 0, nr2 = 0;
var last_min = -1, sum = 0, per_min = 0;
function add_to_chart2(time, power) {
if (++nr2 == 30)
shift2 = true;
chart2.series[0].addPoint(
{color: color(power),
x: parseInt(time) * 1000, y: power},
false, shift2);
last_min = Math.floor(parseInt(time) / 60);
}
function load(chart) {
var ws = new WebSocket(websocket_url);
ws.onopen = function() {
console.log('ws opened');
}
ws.onmessage = function(message) {
var data = JSON.parse(message.data);
if (data.error) {
alert(data.error);
} else if (data.realtime == "start") {
realtime = true;
chart2.redraw();
} else if (data instanceof Array) {
var hist = data.map(function(d) {
return {x: parseInt(d.time)*1000, y: d.power}
});
for (var i = 0; i < 4; i++)
daily[i].series[0].setData(hist);
} else if (data.time) {
if (realtime) {
if (++nr == 25)
shift = true;
chart.series[0].addPoint(
[parseInt(data.time) * 1000, data.power], true, shift);
chart3.series[0].points[0].update(data.power);
min = Math.floor(parseInt(data.time) / 60);
if (last_min < min) {
add_to_chart2(min * 60, sum / per_min);
chart2.redraw();
sum = per_min = 0;
}
sum += data.power;
per_min += 1;
} else {
add_to_chart2(data.time, data.power);
}
}
}
ws.onerror = function() {
alert('Error!');
}
ws.onclose = function() {
alert('Connection closed!');
}
}
</script>
</head>
<body>
<div id="daily" style="width: 30%; height: 500px; margin: 0 auto; float: left;">
<div id="day3" style="width: 100%; height: 24%; margin: 0 auto;"></div>
<div id="day2" style="width: 100%; height: 24%; margin: 0 auto;"></div>
<div id="day1" style="width: 100%; height: 24%; margin: 0 auto;"></div>
<div id="day0" style="width: 100%; height: 24%; margin: 0 auto;"></div>
</div>
<div id="container2" style="width: 30%; height: 500px; margin: 0 auto; float: left;"></div>
<div id="container" style="width: 30%; height: 500px; margin: 0 auto; float: left;"></div>
<div id="container3" style="width: 400px; height: 200px; margin: 0 auto; float: left;"></div>
</body>
</html>
#!/usr/local/bin/node
var fs = require('fs');
var ws = require('websocket.io');
var net = require('net');
var server = ws.listen(5000, function () {
console.log('Server running');
});
var clients = [];
function send_obj(obj, client) {
var str = JSON.stringify(obj);
console.log(str);
if (client)
client.send(str);
else for (var c in clients)
clients[c].send(str);
}
var pow_sock_rt;
function open_pow_sock(client) {
var pow_sock = net.createConnection("/tmp/power.sock");
if (!client) pow_sock_rt = pow_sock;
pow_sock.setEncoding('utf8');
pow_sock.on('connect', function() {
console.log("connected to power socket");
});
pow_sock.on('data', function(d) {
if (d.match(/^error:\s+(.*)/)) {
send_obj({'error':RegExp.$1}, client);
return;
}
var data = d.replace(/\n$/,'').split(/\n/);
for (var i in data) {
if (data[i] == '') {
send_obj({'realtime': 'start'}, client);
continue;
}
var dat = data[i].split(/\s+/);
var time = dat[0];
var power = parseInt(dat[1]);
send_obj({'time': time, 'power': power}, client);
}
});
pow_sock.on('close', function(err) {
console.log('socket closed');
if (!client) {
for (var c in clients)
clients[c].close();
clients = [];
}
});
pow_sock.on('error', function(err){
console.log(err);
console.log(err.stack);
});
}
// クライアントからの接続イベントを処理
server.on('connection', function(client) {
console.log('connection start');
var fd = fs.openSync('/tmp/power.log', 'r');
var size = fs.fstatSync(fd).size;
var read_size = 18*6*24*4; // 末尾の概ね4日分のデータのみ読み込む
var offset = Math.max(0, size - read_size);
var buffer = new Buffer(read_size);
fs.readSync(fd, buffer, 0, read_size, offset);
fs.closeSync(fd);
var hist = buffer.toString('ascii');
hist = hist.replace(/^.*?\n|\n$/, '').split(/\n/).map((s)=>{
var dat = s.split(/\s+/);
return {'time': dat[0], 'power': parseInt(dat[1])};
});
send_obj(hist, client);
if (!clients.length)
open_pow_sock(); // open RT event sock
else
open_pow_sock(client);
clients.push(client);
// クライアントからのメッセージ受信イベントを処理
client.on('message', function(request) {
});
// クライアントが切断したときの処理
client.on('disconnect', function(){
console.log('connection disconnect');
});
// 通信がクローズしたときの処理
client.on('close', function(){
console.log('connection close');
var nclients = [];
for (var c in clients)
if (clients[c] != client)
nclients.push(clients[c]);
clients = nclients;
if (!clients.length) {
pow_sock_rt.end();
pow_sock_rt.destroy();
pow_sock_rt = null;
}
});
// エラーが発生した場合
client.on('error', function(err){
console.log(err);
console.log(err.stack);
client.close();
});
});
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <poll.h>
#include <termios.h>
#include <signal.h>
#include <time.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <pthread.h>
//-----------------------------------------------------------
// Configurations
//-----------------------------------------------------------
// B-route ID and Password (PANA authentication)
#define ID "0000XXXXXXXXXXXXXXXXXXXXXXXXXXXX"
#define PASSWORD "XXXXXXXXXXXX"
// Serial
#define BP35A1_path "/dev/ttyS1"
#define BP35A1_baud 115200
// Socket
#define SOCK_PATH "/tmp/power.sock"
// Log file name
#define LOG_FILENAME "/tmp/power.log"
//-----------------------------------------------------------
// data trasnfer buffer
//-----------------------------------------------------------
const int bufsize = 512; //122;
char buf[bufsize];
const int size = bufsize - 1;
//-----------------------------------------------------------
// debug message utilities
//-----------------------------------------------------------
//#define DEBUG
#define error0(fmt) fprintf(stderr, fmt "\n")
#define error1(fmt, str) fprintf(stderr, fmt "%s\n", str)
#define error(fmt, ...) fprintf(stderr, fmt "\n", __VA_ARGS__)
#define log0(fmt) fprintf(stderr, fmt "\n")
#define log1(fmt, str) fprintf(stderr, fmt "%s\n", str)
#define log(fmt, ...) fprintf(stderr, fmt "\n", __VA_ARGS__)
#ifdef DEBUG
#define debug0(fmt, ...) fprintf(stderr, fmt "\n")
#define debug1(fmt, str) fprintf(stderr, fmt "%s\n", str)
#define debug(fmt, ...) fprintf(stderr, fmt "\n", __VA_ARGS__)
#else
#define debug0(fmt)
#define debug1(fmt, str)
#define debug(fmt, ...)
#endif
//-----------------------------------------------------------
// BP35A1 communication class
//-----------------------------------------------------------
class BP35A1 {
int fd;
unsigned short tid;
bool found;
bool connected;
char channel[3];
char panid[5];
char addr[17];
char ipv6_addr[40];
public:
BP35A1(const char *path, int baud) : tid(0), found(0), connected(0) {
fd = open(path, O_RDWR);
struct termios tio;
tcgetattr(fd, &tio);
cfsetispeed(&tio, baud);
cfsetospeed(&tio, baud);
tcflush(fd, TCIFLUSH);
tcsetattr(fd, TCSANOW, &tio);
}
void listen() {
}
ssize_t write(const char *buf, size_t len) {
return ::write(fd, buf, len);
}
void write_ln(const char *cmd, const char *arg) {
if (cmd)
write(cmd, strlen(cmd));
if (arg)
write(arg, strlen(arg));
write("\r", 1);
if (cmd) debug1("> ", cmd);
if (arg) debug1("> ", arg);
}
int read() {
char d;
if (::read(fd, &d, 1) <= 0)
return -1;
return d;
}
int read_sync(int maxsec=-1) {
char ret;
struct pollfd p = {fd, POLLIN, 0};
while (poll(&p, 1, maxsec < 0 ? -1 : maxsec*1000) == 1) {
if (::read(fd, &ret, 1) < 1)
break;
if (ret == '\n')
continue;
return (int)(unsigned int)ret;
}
debug0("timeout");
return -1;
}
int readline(char *resbuf, int size, int maxsec=-1, bool need_flush=true) {
for (int p = 0; p < size; p++) {
resbuf[p] = read_sync(maxsec);
if (resbuf[p] == '\r') {
resbuf[p] = 0;
debug1("< ", resbuf);
return p;
}
if ((signed char)resbuf[p] <= 0) {
resbuf[p] = 0;
return -1;
}
}
debug1("<.. ", resbuf);
if (need_flush)
flush();
return size;
}
void flush() {
int ret;
do {
ret = read_sync();
} while (ret != '\r' && ret > 0);
}
int hex(char c) {
if (c >= '0' && c <= '9') return c - '0';
if (c >= 'A' && c <= 'F') return c - 'A' + 10;
if (c >= 'a' && c <= 'f') return c - 'a' + 10;
return -1;
}
bool startswith(const char *str, const char *search) {
while (*search)
if (*(search++) != *(str++))
return false;
return true;
}
int recv_udp(char *resbuf, int size, int len) {
unsigned short port, psize;
if (size < 7+40+40+5+5+17+2+5) {
error0("buffer is too short to handle ERXUDP!");
flush();
return -1;
}
int offset = 7+40+40+5;
port = (hex(resbuf[offset+0]) << 12) |
(hex(resbuf[offset+1]) << 8) |
(hex(resbuf[offset+2]) << 4) |
(hex(resbuf[offset+3]));
offset = 7+40+40+5+5+17+2;
psize = (hex(resbuf[offset+0]) << 12) |
(hex(resbuf[offset+1]) << 8) |
(hex(resbuf[offset+2]) << 4) |
(hex(resbuf[offset+3]));
log("UDP (port:%d size:%d): ", port, psize);
if (len > 7+40+40+5+5+17+2+5)
log1(" ", resbuf+7+40+40+5+5+17+2+5);
do {
len = readline(resbuf, size, -1, false);
if (len < 0)
break;
log1(" ", resbuf);
} while (size == len);
return psize;
}
enum {
WAIT_UDP = 0x01,
WAIT_OK = 0x02,
HANDLE_PAN = 0x04,
};
int wait_response(char *events, int elen, unsigned int flags, const char* cmd,
bool &ok, int maxsec, char *resbuf, int size) {
for (;;) {
int len = readline(resbuf, size>121?121:size, maxsec, false);
if (len == 0 || (len == 1 && resbuf[0] == 0))
continue;
if (len < 0)
return len;
if (startswith(resbuf, "ERXUDP")) {
int ret = recv_udp(resbuf, size, len);
if (flags & WAIT_UDP)
return ret;
continue;
}
if (len == size)
flush();
if ((cmd && startswith(resbuf, cmd)) || startswith(resbuf, "SK")) {
continue;
}
if (startswith(resbuf, "OK")) {
ok = true;
if (flags & WAIT_OK)
return 0;
continue;
}
if (startswith(resbuf, "FAIL")) {
error1("command failed -> ", resbuf);
return -1;
}
if (startswith(resbuf, "EVER")) {
log1("Version: ", resbuf + 5);
continue;
}
if (startswith(resbuf, "EVENT")) {
char id = (hex(resbuf[6]) << 4) | hex(resbuf[7]);
log("Event %x", id);
for (int i = 0; i < elen; i++)
if (id == events[i])
return id;
continue;
}
if (startswith(resbuf, "EPANDESC")) {
if (!(flags & HANDLE_PAN))
continue;
found = true;
readline(resbuf, size); // " Channel:xx"
strcpy(channel, resbuf + 10);
readline(resbuf, size); // " Channel Page:xx"
readline(resbuf, size); // " Pan ID:xxxx"
strcpy(panid, resbuf + 9);
readline(resbuf, size); // " Addr:xxxxxxxxxxxxxxxx"
strcpy(addr, resbuf + 7);
readline(resbuf, size); // " LQI:xx"
char lqi[3] = {resbuf[6],resbuf[7],0};
readline(resbuf, size); // " PairID:xxxx"
log("Pan ID %s (Channel:%s Addr:%s LQI:%s) found!",
panid, channel, addr, lqi);
continue;
}
error1("unknown response -> ", resbuf);
}
}
int wait_udp(char *resbuf, int size, int maxsec, bool &ok) {
if (size < 121) {
error0("buffer size is too short!");
return -1;
}
return wait_response(NULL, 0, WAIT_UDP, NULL, ok, maxsec, resbuf, size);
}
int wait_event(char *events, int elen, char *resbuf, int size) {
bool ok = false;
return wait_response(events, elen, HANDLE_PAN, NULL, ok, -1, resbuf, size);
}
int exec_command(const char *cmd, const char *arg, char *resbuf, int size) {
write_ln(cmd, arg);
bool ok = false;
return wait_response(NULL, 0, WAIT_OK, cmd, ok, -1, resbuf, size);
}
bool setup(const char *id, const char *password, char *buf, int size) {
listen();
disconnect(buf, size);
if (exec_command("SKVER", NULL, buf, size) < 0)
return false;
if (exec_command("ROPT", NULL, buf, size) < 0)
return false;
if (buf[5] == '0') { // "OK 00"
log0("enabling ASCII dump mode");
if (exec_command("WOPT 01", NULL, buf, size) < 0)
return false;
}
if (exec_command("SKSETPWD C " , password, buf, size)) // set password
return false;
if (exec_command("SKSETRBID ", id, buf, size)) // set RBID
return false;
return true;
}
void active_scan(char *buf, int size) {
log0("Scanning PAN ...");
if (exec_command("SKSCAN 2 FFFFFFFF 6", NULL, buf, size)) { // active scan
sleep(2);
return;
}
char events[] = {0x22};
wait_event(events, 1, buf, size);
if (!found) {
error0("PAN not found");
sleep(2);
}
}
bool connect(char *buf, int size) {
rescan:
while (!found)
active_scan(buf, size);
if (exec_command("SKSREG S2 ", channel, buf, size))
return false;
if (exec_command("SKSREG S3 ", panid, buf, size))
return false;
write_ln("SKLL64 ", addr);
readline(buf, size); // echo
if (readline(ipv6_addr, 40) != 39) {
error1("invalid ipv6 address: ", ipv6_addr);
return false;
}
log1("Connecting to ", ipv6_addr);
if (exec_command("SKJOIN ", ipv6_addr, buf, size))
return false;
char events[] = {0x24, 0x25};
if (wait_event(events, 2, buf, size) != 0x25) {
error0("connection failed");
found = false;
return false;
}
log1("Connected to ", ipv6_addr);
connected = true;
bool ok = false;
wait_udp(buf, size, 3, ok); // wait instance announce
return true;
}
void disconnect(char *buf, int size) {
exec_command("SKTERM", NULL, buf, size); // disconnect forcively
sleep(1);
}
bool epc_get(unsigned char epc, char *buf, int size) {
bool ok = false;
write("SKSENDTO 1 ", 11);
write(ipv6_addr, 39);
char msg[] = " 0E1A 1 000E \x10\x81\x00\x01\x05\xFF\x01\x02\x88\x01\x62\x01\xe7\x00";
tid++;
msg[15] = tid >> 8;
msg[16] = tid & 0xff;
msg[25] = epc;
write(msg, sizeof(msg)-1);
for (;;) {
int ret = wait_udp(buf, size, 5, ok);
if (ret < 0) {
error1("wait_udp failed! -> ", buf);
return false;
}
if (ret < 14) {
error1("response error: ", buf);
return false;
}
unsigned short rtid = (hex(buf[4]) << 12) | (hex(buf[5]) << 8) |
(hex(buf[6]) << 4) | hex(buf[7]);
if (rtid != tid)
continue;
if (buf[20] == '5' && buf[21] == '2') {
error0(" ** Get EPC not supported");
return false;
}
if (buf[20] != '7' || buf[21] != '2') {
error1("unknown message received: ", buf);
return false;
}
epc = (hex(buf[24]) << 4) | hex(buf[25]);
log(" ** EPC %x = ", epc);
log1(" ", buf+28);
if (!ok)
exec_command(NULL, NULL, buf, size);
return true;
}
}
void epc_set(unsigned char epc, char *data, int len, char *buf, int size) {
bool ok;
write("SKSENDTO 1 ", 11);
write(ipv6_addr, 39);
char msg[64] = " 0E1A 1 000E \x10\x81\x00\x01\x05\xFF\x01\x02\x88\x01\x61\x01\xe7";
msg[15] = tid >> 8;
msg[16] = tid & 0xff;
tid++;
msg[25] = epc;
msg[26] = len;
memcpy(msg+27, data, len);
sprintf(msg + 8, "%04X", len+14);
msg[12] = ' ';
write(msg, len+27);
int ret = wait_udp(buf, size, 3, ok);
if (ret < 0) {
error1("wait_udp failed! -> ", buf);
return;
}
if (ret < 14) {
error1("response error: ", buf);
return;
}
if (buf[20] == '5' && buf[21] == '1') {
error0(" ** Set EPC not supported");
return;
}
if (buf[20] != '7' || buf[21] != '1') {
error1("unknown message received: ", buf);
return;
}
log(" ** EPC %x =>", epc);
log1(" ", buf+28);
if (!ok)
exec_command(NULL, NULL, buf, size);
}
};
//-----------------------------------------------------------
// Main routine
//-----------------------------------------------------------
BP35A1 wisun(BP35A1_path, BP35A1_baud); // BP35A1 instance
char punit = 0;
const int HIST_MAX_PER_MINUTE = 60;
const int HIST_MIN_MAX = 60;
struct PowerLog {
time_t time;
int power;
} history[2][HIST_MAX_PER_MINUTE], history_min[HIST_MIN_MAX];
int hist_side = 0;
int hist_pos = 0, hist_pos2 = 0;
int hist_min_pos = 0;
int last_min = -1;
FILE * volatile client = NULL;
void client_close(int sig) {
if (!client)
return;
fclose(client);
client = NULL;
}
void record_power(int power) {
time_t t = time(NULL);
if (client) {
fprintf(client, "%lld %d\n", (unsigned long long)t, power);
if (fflush(client) < 0) {
client_close(0);
}
}
if (t/60 != last_min) {
if (last_min != -1) {
int sum = 0;
for (int i = 0; i < hist_pos; i++)
sum += history[hist_side][i].power;
history_min[hist_min_pos].time = t - t%60;
history_min[hist_min_pos++].power = sum/hist_pos;
hist_min_pos %= HIST_MIN_MAX;
hist_side = 1 - hist_side;
hist_pos2 = hist_pos;
hist_pos = 0;
}
last_min = t/60;
if (last_min % 10 == 0) {
FILE *f = fopen(LOG_FILENAME, "a");
if (!f) {
printf("failed to open "LOG_FILENAME"\n");
} else {
int sum = 0, n = hist_min_pos > 10 ? 10 : hist_min_pos;
if (n > 0) {
for (int i = 1; i <= n; i++)
sum += history_min[hist_min_pos - i].power;
fprintf(f, "%lld %d\n", (unsigned long long)(t - t%60), sum/n);
fclose(f);
}
}
}
}
if (hist_pos >= HIST_MAX_PER_MINUTE)
return;
history[hist_side][hist_pos].time = t;
history[hist_side][hist_pos++].power = power;
}
int sock_read() {
int len;
char buf[256];
struct sockaddr_un addr = {AF_UNIX, SOCK_PATH};
socklen_t addrlen = sizeof(addr);
int sock = socket(AF_UNIX, SOCK_STREAM, 0);
if (sock < 0) {
perror("socket");
return 1;
}
if (connect(sock, (struct sockaddr*)&addr, addrlen) < 0) {
perror("connect");
return 1;
}
for (;;) {
len = read(sock, buf, sizeof(buf));
if (len <= 0)
break;
int ret = write(1, buf, len);
}
return 0;
}
int sock = -1;
void quit(int sig) {
close(sock);
unlink(SOCK_PATH);
wisun.disconnect(buf, size);
exit(0);
}
void *wait_client(void *arg) {
int fd = (int)(long)arg;
char dummy;
while (read(fd, &dummy, 1) > 0); // wait close
client_close(0);
return NULL;
}
void *sock_thread(void *arg) {
int fd;
struct sockaddr_un addr = {AF_UNIX, SOCK_PATH};
socklen_t addrlen = sizeof(addr);
sock = socket(AF_UNIX, SOCK_STREAM, 0);
if (sock < 0) {
perror("socket");
exit(1);
}
if (bind(sock, (struct sockaddr*)&addr, addrlen) < 0) {
perror("bind");
exit(1);
}
signal(SIGPIPE, client_close);
signal(SIGTERM, quit);
signal(SIGINT, quit);
listen(sock, 1);
while ((fd = accept(sock, (struct sockaddr*)&addr, &addrlen)) >= 0) {
FILE *f = fdopen(fd, "w");
if (!f) {
close(fd);
continue;
}
for (int i = 0; i < HIST_MIN_MAX; i++) {
int p = (hist_min_pos + i) % HIST_MIN_MAX;
if (history_min[p].time)
fprintf(f, "%lld %d\n",
(unsigned long long)history_min[p].time,
history_min[p].power);
}
fprintf(f, "\n");
for (int i = 0; i < hist_pos2; i++)
fprintf(f, "%lld %d\n",
(unsigned long long)history[1-hist_side][i].time,
history[1-hist_side][i].power);
for (int i = 0; i < hist_pos; i++)
fprintf(f, "%lld %d\n",
(unsigned long long)history[hist_side][i].time,
history[hist_side][i].power);
fflush(f);
if (client) {
fclose(f);
continue;
}
client = f;
pthread_t wait_th;
pthread_create(&wait_th, NULL, wait_client, (void*)(long)fd);
}
return NULL;
}
void setup() {
buf[size] = 0;
if (!wisun.setup(ID, PASSWORD, buf, size)) {
error0("setup failed");
exit(1);
}
while (!wisun.connect(buf, size))
sleep(5); // reconnect on error after 5 sec
// check if the smart meter is in running state (EPC 0x80 = 0x30)
char val = 0;
if (wisun.epc_get(0x80, buf, size))
val = (wisun.hex(buf[28]) << 4) | wisun.hex(buf[29]);
if (val != 0x30)
error0("SmartMeter is not working!");
// check if the smart meter is not in error state (EPC 0x88 = 0x42)
val = 0;
if (wisun.epc_get(0x88, buf, size))
val = (wisun.hex(buf[28]) << 4) | wisun.hex(buf[29]);
if (val != 0x42)
error0("SmartMeter is in error state!");
#if 0
// get unit of integral power
if (wisun.epc_get(0xe1, buf, size))
punit = (wisun.hex(buf[28]) << 4) | wisun.hex(buf[29]);
// get history
char data = 0x01;
wisun.epc_set(0xe5, &data, 1, buf, size);
wisun.epc_get(0xe2, buf, size);
data = 0x00;
wisun.epc_set(0xe5, &data, 1, buf, size);
wisun.epc_get(0xe2, buf, size);
// get integral power
wisun.epc_get(0xea, buf, size);
#endif
}
void loop() {
static int err_cnt = 0;
log0("getting EPC 0xe7 ...");
bool ret = wisun.epc_get(0xe7, buf, size);
if (ret) {
// convert hex string into unsigned long
unsigned long val = 0;
for (int i = 28; buf[i]; i++) {
val <<= 4;
val |= wisun.hex(buf[i]);
}
// print immediate power value to Serial
printf("Power = %lu Watt\n", val);
record_power((int)val);
err_cnt = 0;
} else {
err_cnt++;
error0("EPC get failed");
if (err_cnt >= 5) {
err_cnt = 0;
log0("reconnecting ...");
wisun.disconnect(buf, size);
wisun.connect(buf, size);
}
}
bool ok;
wisun.wait_udp(buf, size, 3, ok); // wait 3 sec
}
int main(int argc, char *argv[])
{
if (argc > 1 && strcmp(argv[1], "-c") == 0) {
return sock_read();
}
pthread_t th;
pthread_create(&th, NULL, sock_thread, NULL);
setup();
for (;;)
loop();
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment