こちら「M5StickCで家庭用スマートメーターをハックする」の記事
を参考に、Wi-SUN モジュールに BP35C1-J11-T01 を使用して作成した。
#背景
先の記事で家の機器工事無しで、電力メーターを読み取ることが出来るんだと知り、早速トライしてみることにしました。
無線のWi-SUN モジュールには高価な「BP35A1」が使われていて、調べてみると新製品「BP35C1-J11-T01」が出ている。しかも少し安い!見た目や必要なピンの配置も同じだから、どうせなら新製品が良いだろう(何が良いのかも分からず)と安易に考え、これを選択しました。
結果、動きませんでした。ショック!
記事をよく読むと、親切にBP35C1-J11-T01では動かないだろうとコメント有りました。
またコマンドがバイナリー形式になり全く使えないと知り、「BP35A1」の購入も考えましたが、そこまでするかぁ・・・。
結果、幸いにもハード的には問題なさそうなので、自力でプログラムすることにしました。
#作成手順
作成手順は、先の記事に詳しく書かれているので参照ください。
今回の作成では、
- 「BP35A1」の代わりに「BP35C1-J11-T01」を使用
- Pythonは分からないので、Arduinoでプログラミング
- 子機には対応していません
必要部品、組み立て方法は、「BP35A1」の代わりに「BP35C1-J11-T01」を使うこと以外は同じ。
#プログラム
機能としては
- 1分毎、又はBボタン押下で瞬間電力、電流値を表示。
- 30分毎に受信する積算電力(正・逆)を表示
- Ambient登録
- 任意のクラウドにPOSTする方法
###注意
- とりあえず、私家では動いていますが、全ての環境で動作するとは限りません。
- ほぼ正常ルートしか考慮していません。規定外の値は来ないものとして動いています(値の正常性チェックは有りません)。
- スマートメーターに悪影響を及ぼしているかもしれません。
- 信号シーケンス通り書き下しているだけなので分かりやすいと思います。
- 「Wi-SUN HAT」はRev0.2を使いました。Rev0.1を使用する場合は、PIN番号の割当変更が必要です。
- 30分毎の積算電力通知に、応答を返しているのですが、20秒後に1度だけ再送があります。理解不足で、うまく応答できていないのかもしれません。
- M5StickCディスプレイの表示にゴミが残ることがあります。スマートに消したい・・・。
- 私家の環境では発生しない状況も考慮はしていますが、実機テストできないので動作しないところがあるかもしれません。
以上、中途半端な所(誰か直して!)がある点や、スマートメータなど外部機器への悪影響や損害が発生しても一切保証できませんので、ご理解の上ご参考にしてください。
13行目までの、xxxxxは適宜ご自分の環境に合わせ変更してください。
#include <M5StickCPlus.h>
#include "Ambient.h"
const char j11authid[] = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"; // 電力メーター情報発信サービス認証ID
const char j11pass[] = "xxxxxxxxxxxx"; // 電力メーター情報発信サービスパスワード
WiFiClient client;
Ambient ambient;
const char* ssid = "xxxxxxxxxxxx"; //your wifi ssid
const char* password = "xxxxxxxxxxx"; //your wifi password
unsigned int channelId = 00000; // AmbientのチャネルID
const char* writeKey = ""; // ライトキー(ambientにUPしない場合は""(空白))
const char* HOST = ""; // 任意のクラウドのホスト名(例:www.hogehoge.com)(任意のクラウドにUPしない場合は""(空白)
const char* PATH = "xxxxxxxxxxxxx"; // 任意のクラウドのパス名(例:/m5recive/postrcv.php)
typedef struct
{
char UDPPort[2] ;
char scnch ;
char scnn ;
char maca[8] ;
char panid[2] ;
char IPv6Addr[16] ;
char instans[3] ;
} M_SigInf;
M_SigInf SigInf ;
typedef struct
{
unsigned short YYYY ;
unsigned char MM ;
unsigned char DD ;
unsigned char hh ;
unsigned char mm ;
unsigned char ss ;
unsigned long kwh ;
} M_Totalkwh ;
typedef struct
{
String SerNo ; //製造番号
unsigned long xc ;//係数:積算電力量計測値、履歴を実使用量に換算する係数を10 進表記において6 桁で示す。
unsigned char keta ; //積算電力量有効桁数:積算電力量計測値の有効桁数を示す。
unsigned char tani ; //積算電力量単位:積算電力量計測値、履歴の単位(乗率)を示す。(0x00:1kWh、0x01:0.1kWh、0x02:0.01kWh など)
float tanibairitsu ; //積算電力量値に掛ける
M_Totalkwh kwhinf[2] ; //定時積算電力量計測値(0:正方向計測値,1:逆方向計測値):最新の30 分毎の計測時刻における積算電力量
long momentW ;
short momentRA ;
short momentTA ;
} M_MeartInf;
M_MeartInf MeartInf ;
unsigned int TIDinc =7 ;
unsigned long StClMillis;
#define STCLTIMING 60000 //60秒毎に瞬間データ収集
/********************************************************/
/*
/********************************************************/
bool TWD_WebPOST(){
const int httpPort = 80;
if (!client.connect(HOST, httpPort)) {
#ifdef DEBUG
Serial.println("接続に失敗しました。次回再試行");
#endif
return false;
}
// サーバにリクエストを送信(POST)
client.println(String("POST ") + PATH + " HTTP/1.1");
client.print("HOST: ");
client.println(HOST);
client.println("Content-Type: application/x-www-form-urlencoded");
client.println("User-Agent: Arduino Post Client");
client.println("Content-Type: application/x-www-form-urlencoded");
client.println("User-Agent: Arduino Post Client");
String body = "kwhinfdate=" + String(MeartInf.kwhinf[0].YYYY) + "/" + String(MeartInf.kwhinf[0].MM) + "/" + String(MeartInf.kwhinf[0].DD) + " " + String(MeartInf.kwhinf[0].hh) + ":" + String(MeartInf.kwhinf[0].mm) + ":" + String(MeartInf.kwhinf[0].ss)
+ "&buytotalkwh=" + String((int)MeartInf.kwhinf[0].kwh * MeartInf.tanibairitsu) + "&selltotalkwh=" + String((int)MeartInf.kwhinf[1].kwh * MeartInf.tanibairitsu)
+ "&momentw=" + String((int)MeartInf.momentW) + "&momentRA=" + String(MeartInf.momentRA * 0.1) + "&momentTA=" + String(MeartInf.momentTA * 0.1);
client.print("Content-Length: ");
client.println(body.length());
client.println("Connection: close");
client.println();
client.print(body);
unsigned long timeout = millis();
while (client.available() == 0) {
if (millis() - timeout > 10000) {
return false;
}
}
// HTTPヘッダを含むデータの読み取り(1文字毎)
String raw = "";
while (client.available()) {
char c = client.read();
raw = raw +c;
}
// HTTPヘッダの削除
int index = raw.indexOf("\r\n\r\n");
raw = raw.substring(index + 4);
return true ;
}
/********************************************************/
/*
/********************************************************/
int j11rcv(char *rccvbuf ){
int rcvcnt = 0 ;
int msglength = 0 ;
int alllength ;
for(;;){
if (Serial1.available() > 0 ) {
rccvbuf[rcvcnt] = Serial1.read();
if ( rcvcnt == 0 ) {
if ( rccvbuf[0]!=0xD0 ) continue ;
}
if ( rcvcnt == 1 ) {
if ( rccvbuf[1]!=0xF9 ) { rcvcnt = 0 ; continue ; }
}
if ( rcvcnt == 2 ) {
if ( rccvbuf[2]!=0xEE ) { rcvcnt = 0 ; continue ; }
}
if ( rcvcnt == 3 ) {
if ( rccvbuf[3]!=0x5D ) { rcvcnt = 0 ; continue ; }
}
if ( rcvcnt == 7 ) {
msglength = ctoint(&rccvbuf[6]) ;
alllength = msglength + 8 ; //ヘッダ部分加算
}
if ( rcvcnt >= 8 ) {
if ( alllength <= ( rcvcnt + 1 )) {
rcvcnt = rcvcnt + 1 ;
return rcvcnt ;
}
}
rcvcnt = rcvcnt + 1 ;
}else{
if ( alllength > ( rcvcnt )) {
delay(100);
continue ;
}
break;
}
}
return rcvcnt ;
}
/********************************************************/
/*
/********************************************************/
long ctolong(char *inchar ) {
long outlong ;
outlong = inchar[0] ;
for ( int m=1;m<4;m++ ) {
outlong = outlong << 8 ;
outlong = outlong + inchar[m] ;
}
return outlong ;
}
/********************************************************/
/*
/********************************************************/
int ctoint(char *inchar ) {
int outint ;
outint = inchar[0] ;
outint = outint << 8 ;
outint = outint + inchar[1] ;
return outint ;
}
/********************************************************/
/*
/********************************************************/
void inttoc(int inint ,char *outchar ) {
int outint ;
outchar[0] = highByte(inint) ;
outchar[1] = lowByte(inint) ;
}
/********************************************************/
/*
/********************************************************/
void checksum_set(char *rccvbuf) {
int headsum = 0 ;
int datasum = 0 ;
int i ;
for ( i=0;i<8;i++ ) {
headsum = headsum + rccvbuf[i] ;
}
int lngth = ctoint(&rccvbuf[6]) ;
for ( i=0;i<lngth-4;i++ ) {
datasum = datasum + rccvbuf[12+i] ;
}
inttoc( headsum , &rccvbuf[8] ) ;
inttoc( datasum , &rccvbuf[10] ) ;
}
/********************************************************/
/* F1 ハードウェアリセット
/********************************************************/
void send_hdreset() {
char cmd_F1_hdreset[12] = { 0xD0,0xEA,0x83,0xFC,0x00,0xd9,0x00,0x04,0x00,0x00,0x00,0x00 };
int j ;
checksum_set(cmd_F1_hdreset) ;
for ( j=0;j<sizeof(cmd_F1_hdreset);j++ ){
Serial1.write(cmd_F1_hdreset[j]);
}
Serial.println("ハードウェアリセット 送信");
}
/********************************************************/
/* F3 初期設定要求
/********************************************************/
void send_initsetreq(char chnno) {
char cmd_F3_initsetreq[16] = { 0xD0,0xEA,0x83,0xFC,0x00,0x5f,0x00,0x08,0x00,0x00,0x00,0x00,0x05,0x00,0x04,0x00 };
int j ;
cmd_F3_initsetreq[14] = chnno ;
checksum_set(cmd_F3_initsetreq) ;
for ( j=0;j<sizeof(cmd_F3_initsetreq);j++ ){
Serial1.write(cmd_F3_initsetreq[j]);
}
Serial.println("初期設定要求 送信");
}
/********************************************************/
/* F5 B ルートPANA 認証情報設定要求
/********************************************************/
void send_BPANAsetreq() {
char cmd_F5_BPANAsetreq[66] = { 0xD0,0xEA,0x83,0xFC,0x00,0x54,0x00,0x30,0x03,0xbd,0x00,0x00 };
for ( int i=0;i<sizeof(j11authid)-1;i++ ) { //sizeof(j11authid)は、\0分が含まれて1バイト多いので減算する
cmd_F5_BPANAsetreq[12+i]=j11authid[i];
}
for ( int i=0;i<sizeof(j11pass)-1;i++ ) { //同じく減算する
cmd_F5_BPANAsetreq[12+sizeof(j11authid)-1+i]=j11pass[i];
}
//メッセージ長設定
inttoc( 4+(sizeof(j11authid)-1)+(sizeof(j11pass)-1) , &cmd_F5_BPANAsetreq[6] ) ;
checksum_set(cmd_F5_BPANAsetreq) ;
for ( int j=0;j<12+(sizeof(j11authid)-1)+(sizeof(j11pass)-1);j++ ){
Serial1.write(cmd_F5_BPANAsetreq[j]);
}
Serial.println("BルートPANA認証情報設定要求 送信");
}
/********************************************************/
/* F7 アクティブスキャン実行要求
/********************************************************/
void send_actscnreq() {
char cmd_F7_actscnreq[26] = { 0xD0,0xEA,0x83,0xFC,0x00,0x51,0x00,0x12,0x03,0x9c,0x00,0x00,0x06,0x00,0x03,0xff,0xf0,0x01 };
//Bルート認証IDの末尾8桁
for ( int i=0;i<8;i++ ) {
cmd_F7_actscnreq[18+i]=j11authid[(sizeof(j11authid)-1)-8+i];
}
checksum_set(cmd_F7_actscnreq) ;
delay(100);
for ( int j=0;j<26;j++ ){
Serial1.write(cmd_F7_actscnreq[j]);
}
Serial.println("アクティブスキャン実行要求 送信");
}
/********************************************************/
/* F12 Bルート動作開始要求
/********************************************************/
void send_Bstartreq() {
char cmd_F12_Bstartreq[22] = { 0xD0,0xEA,0x83,0xFC,0x00,0x53,0x00,0x04,0x03,0x90,0x00,0x00 };
int j ;
checksum_set(cmd_F12_Bstartreq) ;
for ( j=0;j<sizeof(cmd_F12_Bstartreq);j++ ){
Serial1.write(cmd_F12_Bstartreq[j]);
}
Serial.println("Bルート動作開始要求 送信");
}
/********************************************************/
/* F14 UDPポートOPEN要求
/********************************************************/
void send_UDPopenreq() {
char cmd_F14_UDPopenreq[24] = { 0xD0,0xEA,0x83,0xFC,0x00,0x05,0x00,0x06,0x03,0x44,0x00,0x28,0x0e,0x1a };
int j ;
//UDPポート番号:3610を使用(0x0e1a)
SigInf.UDPPort[0] = 0x0e;
SigInf.UDPPort[1] = 0x1a;
cmd_F14_UDPopenreq[12] = SigInf.UDPPort[0] ;
cmd_F14_UDPopenreq[13] = SigInf.UDPPort[1] ;
checksum_set(cmd_F14_UDPopenreq) ;
for ( j=0;j<sizeof(cmd_F14_UDPopenreq);j++ ){
Serial1.write(cmd_F14_UDPopenreq[j]);
}
Serial.println("UDPポートOPEN要求 送信");
}
/********************************************************/
/* F16 BルートPANA開始要求
/********************************************************/
void send_BPANAstartreq() {
char cmd_F16_BPANAstartreq[22] = { 0xD0,0xEA,0x83,0xFC,0x00,0x56,0x00,0x04,0x03,0x93,0x00,0x00 };
int j ;
checksum_set(cmd_F16_BPANAstartreq) ;
for ( j=0;j<sizeof(cmd_F16_BPANAstartreq);j++ ){
Serial1.write(cmd_F16_BPANAstartreq[j]);
}
Serial.println("BルートPANA開始要求 送信");
}
/********************************************************/
/* データ送信要求
/********************************************************/
void send_Getreq(char datalen , char *Property) { //OPC以降のバイト数 と ECHONET-Lite INFのOPC以降
char cmd_Getreq[250] = { 0xD0,0xEA,0x83,0xFC,0x00,0x08,0x00,0x32,0x00,0x00,0x00,0x00,0xFE,0x80,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x18,0x10,0x81,0x00,0x00,0x05,0xFF,0x01,0x02,0x88,0x01,0x62,0x06,0x8D,0x00,0xD3,0x00,0xD7,0x00,0xE1,0x00,0xEA,0x00,0xEB,0x00 };
//データ送信要求
int j ;
delay(1000);
//メッセージ長設定
inttoc( 45 - 8 + datalen , &cmd_Getreq[6] ) ;
//macアドレス設定
cmd_Getreq[20] = SigInf.maca[0] ^ 0x02 ;
for ( int i=1;i<8;i++ ){
cmd_Getreq[20+i] = SigInf.maca[i] ;
}
//送信元、送信先ポート番号設定
cmd_Getreq[28] = SigInf.UDPPort[0] ;
cmd_Getreq[29] = SigInf.UDPPort[1] ;
cmd_Getreq[30] = SigInf.UDPPort[0] ;
cmd_Getreq[31] = SigInf.UDPPort[1] ;
//データサイズ設定
inttoc( 11 + datalen , &cmd_Getreq[32] ) ;
//TID設定
TIDinc = TIDinc + 1 ;
cmd_Getreq[36] = highByte(TIDinc) ;
cmd_Getreq[37] = lowByte(TIDinc) ;
//OPC以降設定
for ( j=0;j<datalen;j++ ){
cmd_Getreq[45+j] = Property[j] ;
}
checksum_set(cmd_Getreq) ;
for ( j=0;j<45+datalen;j++ ){
Serial1.write(cmd_Getreq[j]);
}
Serial.println("データ送信要求 送信");
}
/********************************************************/
/* データ受信通知
/********************************************************/
void send_Infans(char datalen , char *Property , char *rcvbuf) { //バイト数 と ECHONET-Lite INFのProperty と 受信データの内容も必要なためrcvbufも
char cmd_Infans[250] = { 0xD0,0xF9,0xEE,0x5D,0x60,0x18,0x00,0x00,0x00,0x00,0x00,0x00,0xFE,0x80,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00 };
//データ受信通知
int j ;
//メッセージ長設定
inttoc( 50 - 8 + datalen , &cmd_Infans[6] ) ;
//macアドレス設定
cmd_Infans[20] = SigInf.maca[0] ^ 0x02 ;
for ( int i=1;i<8;i++ ){
cmd_Infans[20+i] = SigInf.maca[i] ;
}
//送信元、送信先ポート番号設定
cmd_Infans[28] = SigInf.UDPPort[0] ;
cmd_Infans[29] = SigInf.UDPPort[1] ;
cmd_Infans[30] = SigInf.UDPPort[0] ;
cmd_Infans[31] = SigInf.UDPPort[1] ;
//送信元PANID アクティブスキャンで受信したPANID
cmd_Infans[32] = SigInf.panid[0] ;
cmd_Infans[33] = SigInf.panid[1] ;
//ad種別 暗号化 RSSI 受信と同じでいいだろう
for ( int i=34;i<37;i++ ){
cmd_Infans[i] = rcvbuf[i] ;
}
//データサイズ設定
inttoc( 11 + datalen , &cmd_Infans[37] ) ;
//EHD設定
cmd_Infans[39] = 0x10 ;
cmd_Infans[40] = 0x81 ;
//TID設定
// TIDinc = TIDinc + 1 ;
// inttoc( TIDinc , &cmd_Infans[41] ) ;
//TID設定
cmd_Infans[41] = rcvbuf[41] ;
cmd_Infans[42] = rcvbuf[42] ;
//SEOJ設定
cmd_Infans[43] = rcvbuf[46] ;
cmd_Infans[44] = rcvbuf[47] ;
cmd_Infans[45] = rcvbuf[48] ;
//DEOJ設定
cmd_Infans[46] = rcvbuf[43] ;
cmd_Infans[47] = rcvbuf[44] ;
cmd_Infans[48] = rcvbuf[45] ;
//ESV設定
cmd_Infans[49] = 0x7A ;
//OPC以降設定
for ( j=0;j<datalen;j++ ){
cmd_Infans[50+j] = Property[j] ;
}
checksum_set(cmd_Infans) ;
Serial.println("");
for ( j=0;j<50+datalen;j++ ){
Serial1.write(cmd_Infans[j]);
Serial.print(cmd_Infans[j],HEX);
Serial.print(",");
}
Serial.println("");
Serial.println("データ受信通知 送信");
}
/********************************************************/
/*
/********************************************************/
void MPowerreq() {
int rcvcnt ;
char rcvbuf[250] ;
Serial.println("瞬時電力計測値,瞬時電流計測値取得開始");
delay(100);
char Property[5] = { 0x02,0xE7,0x00,0xE8,0x00 } ;
send_Getreq(5,Property) ;
for (;;) {
if ( rcvex(rcvbuf)== 100 ) break ;
delay(100);
}
}
/********************************************************/
/*
/********************************************************/
void M5lcdup() {
M5.Lcd.setTextColor(DARKGREY,BLACK);
M5.Lcd.setCursor(0, 0) ;
M5.Lcd.printf("Use: %.1fKwh\n",MeartInf.kwhinf[0].kwh * MeartInf.tanibairitsu);
M5.Lcd.printf("Sun: %.1fKwh\n",MeartInf.kwhinf[1].kwh * MeartInf.tanibairitsu);
M5.Lcd.print("Now: ");
M5.Lcd.setTextColor(GREENYELLOW,BLACK);
M5.Lcd.printf("%dW\n",MeartInf.momentW);
M5.Lcd.setTextColor(DARKGREY,BLACK);
M5.Lcd.print("R:");
M5.Lcd.setTextColor(GREENYELLOW,BLACK);
M5.Lcd.printf("%.1f A",(int)MeartInf.momentRA * 0.1);
M5.Lcd.setTextColor(DARKGREY,BLACK);
M5.Lcd.print(" T:");
M5.Lcd.setTextColor(GREENYELLOW,BLACK);
M5.Lcd.printf("%.1f A",MeartInf.momentTA * 0.1);
}
/********************************************************/
/*
/********************************************************/
void MeartInfPrint() {
Serial.println("データ受信通知(INF)2 受信");
Serial.printf("バージョン番号:%s\n",MeartInf.SerNo);
Serial.printf("瞬間電力:%dW\n",MeartInf.momentW);
Serial.printf("瞬間電流:R相=%.1fA T相=%.1fA\n",MeartInf.momentRA * 0.1,MeartInf.momentTA * 0.1);
Serial.printf("%4d年%02d月%02d日 %02d時%02d分%02d秒:%.1fkwh\n",MeartInf.kwhinf[0].YYYY,MeartInf.kwhinf[0].MM,MeartInf.kwhinf[0].DD,MeartInf.kwhinf[0].hh,MeartInf.kwhinf[0].mm,MeartInf.kwhinf[0].ss,MeartInf.kwhinf[0].kwh * MeartInf.tanibairitsu);
Serial.printf("%4d年%02d月%02d日 %02d時%02d分%02d秒:%.1fkwh\n",MeartInf.kwhinf[1].YYYY,MeartInf.kwhinf[1].MM,MeartInf.kwhinf[1].DD,MeartInf.kwhinf[1].hh,MeartInf.kwhinf[1].mm,MeartInf.kwhinf[1].ss,MeartInf.kwhinf[1].kwh * MeartInf.tanibairitsu);
}
/********************************************************/
/*
/********************************************************/
int rcvex( char *rcvbuf ) {
int rcvcnt ;
int dtindex = 0 ;
int i,j,k,m,pid;
int kwhindex ;
for (;;) {
delay(100);
rcvcnt = j11rcv(rcvbuf) ;
if ( rcvcnt == 0 ) return 0 ;
if ( rcvcnt < 6 ) {
return 999 ;
}
switch ( rcvbuf[4] ) {
case 0x60 :
if ( rcvbuf[5]==0x19 ){
//起動完了通知
Serial.println("起動完了通知 受信");
return 10 ; //成功
}else if ( rcvbuf[5]==0x18 ){
if (( rcvbuf[39]==0x10 ) && ( rcvbuf[40]==0x81 )) {
switch ( rcvbuf[49] ) {
case 0x72 :
case 0x74 :
dtindex = 51 ;
for ( k=0;k<rcvbuf[50];k++ ) {
pid = rcvbuf[dtindex] ;
switch ( pid ) {
case 0x8d : //製造番号
MeartInf.SerNo = "" ;
for ( m=0;m<rcvbuf[dtindex+1];m++ ) {
MeartInf.SerNo = MeartInf.SerNo + rcvbuf[dtindex+2+m] ;
}
dtindex = dtindex + rcvbuf[dtindex+1] + 2 ;
break ;
case 0xd3 : //係数
MeartInf.xc = ctolong(&rcvbuf[dtindex+2]) ;
dtindex = dtindex + rcvbuf[dtindex+1] + 2 ;
break ;
case 0xd7 : //積算電力量有効桁数
MeartInf.keta = rcvbuf[dtindex+2] ;
dtindex = dtindex + rcvbuf[dtindex+1] + 2 ;
break ;
case 0xe1 : //積算電力量単位(正方向、逆方向計測値)
MeartInf.tani = rcvbuf[dtindex+2] ;
if (MeartInf.tani < 10) {
MeartInf.tanibairitsu = pow(10, ( MeartInf.tani * -1 ));
}else{
MeartInf.tanibairitsu = pow(10, ( MeartInf.tani - 9 ));
}
dtindex = dtindex + rcvbuf[dtindex+1] + 2 ;
break ;
case 0xe7 : //瞬時電力計測値
MeartInf.momentW = ctolong(&rcvbuf[dtindex+2]) ;
dtindex = dtindex + rcvbuf[dtindex+1] + 2 ;
break ;
case 0xe8 : //瞬時電流計測値
MeartInf.momentRA = ctoint(&rcvbuf[dtindex+2]) ;
MeartInf.momentTA = ctoint(&rcvbuf[dtindex+4]) ;
dtindex = dtindex + rcvbuf[dtindex+1] + 2 ;
break ;
case 0xea : //定時積算電力量計測値(正方向計測値)
kwhindex = 0 ;
case 0xeb : //定時積算電力量計測値(逆方向計測値)
if (pid==0xeb) kwhindex = 1 ;
MeartInf.kwhinf[kwhindex].YYYY = ctoint(&rcvbuf[dtindex+2]) ;
MeartInf.kwhinf[kwhindex].MM = rcvbuf[dtindex+4] ;
MeartInf.kwhinf[kwhindex].DD = rcvbuf[dtindex+5] ;
MeartInf.kwhinf[kwhindex].hh = rcvbuf[dtindex+6] ;
MeartInf.kwhinf[kwhindex].mm = rcvbuf[dtindex+7] ;
MeartInf.kwhinf[kwhindex].ss = rcvbuf[dtindex+8] ;
MeartInf.kwhinf[kwhindex].kwh = ctolong(&rcvbuf[dtindex+9]) ;
dtindex = dtindex + rcvbuf[dtindex+1] + 2 ;
break ;
}
}
MeartInfPrint();
//必要になったら繋ぐ
if ( WiFi.status() != WL_CONNECTED ) {
WiFi.begin(ssid, password); // Wi-Fi APに接続 ----A
while (WiFi.status() != WL_CONNECTED) { // Wi-Fi AP接続待ち
delay(500);
Serial.print(".");
}
Serial.print("WiFi connected\r\nIP address: ");
Serial.println(WiFi.localIP());
if ( writeKey != "" ) { //ライトキーが記入されていたら送信
ambient.begin(channelId, writeKey, &client); // チャネルIDとライトキーを指定してAmbientの初期化
}
}
if ( MeartInf.momentW != 0 ) { //瞬間電力が0の場合(起動時直後、積算のみ)、クラウド登録はしない
if ( writeKey != "" ) { //ライトキーが記入されていたら送信
ambient.set(1, (int)MeartInf.kwhinf[0].kwh * MeartInf.tanibairitsu);
ambient.set(2, (int)MeartInf.kwhinf[1].kwh * MeartInf.tanibairitsu);
ambient.set(3, (int)MeartInf.momentW);
ambient.set(4, (short)MeartInf.momentRA * 0.1);
ambient.set(5, (int)MeartInf.momentTA * 0.1);
ambient.send();
Serial.println("ambientに送信");
}
if ( HOST != "" ) { //HOSTが記入されていたら送信
TWD_WebPOST();
Serial.println("Webに送信");
}
}
M5lcdup() ;
if ( rcvbuf[49] == 0x72 ) return 110;
//*************** 0x74は応答が必要 *****************
Serial.println("0x74受信したので7A応答を送信する") ;
for ( int i=0;i<77;i++ ){
Serial.print(rcvbuf[i],HEX) ;
Serial.print(",") ;
}
Serial.println("") ;
char Property[13] = { 0x02,0xE7,0x00,0xE8,0x00 } ; //max 13 = 6組+1
//OPC設定
Property[0] = rcvbuf[50] ;
//EPC,PDC n個分設定
dtindex = 51 ;
for ( int i=0;i<Property[0];i++ ){
Property[1+i*2] = rcvbuf[dtindex] ;
Property[2+i*2] = 0x00 ;
dtindex = dtindex + rcvbuf[dtindex+1] + 2 ;
}
send_Infans( Property[0] * 2 + 1 , Property , rcvbuf) ;
Serial.println("定期受診の応答 送信");
return 120 ;
}
}
}else if ( rcvbuf[5]==0x28 ){
if ( rcvbuf[12]==0x01 ) {
Serial.println("PANA認証結果通知(正常) 受信");
return 90 ; //成功
}else{
Serial.println("PANA認証結果通知(失敗) 受信");
Serial.println(rcvbuf[12],HEX);
return 91 ; //失敗
}
}
break ;
case 0x20 :
if ( rcvbuf[5]==0x5F ) {
if ( rcvbuf[12]==0x01 ) {
Serial.println("初期設定応答(正常) 受信");
return 20 ; //成功
}else{
Serial.println("初期設定応答(失敗) 受信");
Serial.println(rcvbuf[12],HEX);
return 21 ; //失敗
}
}else if ( rcvbuf[5]==0x54 ){
if ( rcvbuf[12]==0x01 ) {
Serial.println("BルートPANA認証情報設定応答(正常) 受信");
return 30 ; //成功
}else{
Serial.println("BルートPANA認証情報設定応答(失敗) 受信");
Serial.println(rcvbuf[12],HEX);
return 31 ; //失敗
}
}else if ( rcvbuf[5]==0x51 ){
if ( rcvbuf[12]==0x01 ) {
Serial.println("アクティブスキャン実行応答(正常) 受信");
return 50 ; //成功
}else{
Serial.println("アクティブスキャン実行応答(失敗) 受信");
Serial.println(rcvbuf[12],HEX);
return 51 ; //失敗
}
}else if ( rcvbuf[5]==0x53 ){
if ( rcvbuf[12]==0x01 ) {
Serial.println("Bルート動作開始応答(正常) 受信");
return 60 ; //成功
}else{
Serial.println("Bルート動作開始応答(失敗) 受信");
Serial.println(rcvbuf[12],HEX);
return 61 ; //失敗
}
}else if ( rcvbuf[5]==0x05 ){
if ( rcvbuf[12]==0x01 ) {
Serial.println("UDPポートOPEN応答(正常) 受信");
return 70 ; //成功
}else{
Serial.println("UDPポートOPEN応答(失敗) 受信");
Serial.println(rcvbuf[12],HEX);
return 71 ; //失敗
}
}else if ( rcvbuf[5]==0x56 ){
if ( rcvbuf[12]==0x01 ) {
Serial.println("BルートPANA開始応答(正常) 受信");
return 80 ; //成功
}else{
Serial.println("BルートPANA開始応答(失敗) 受信");
Serial.println(rcvbuf[12],HEX);
return 81 ; //失敗
}
}else if ( rcvbuf[5]==0x08 ){
if (( rcvbuf[12]==0x01 ) && ( rcvbuf[13]==0x00 )) {
Serial.println("データ送信応答(正常) 受信");
return 100 ; //成功
}else{
Serial.println("データ送信応答(応答or送信失敗) 受信");
Serial.print(rcvbuf[12],HEX);
Serial.print(",");
Serial.println(rcvbuf[13],HEX);
return 101 ; //失敗
}
}
break ;
case 0x40 :
if ( rcvbuf[5]==0x51 ){
if ( rcvbuf[12]==0x01 ) {
Serial.println("アクティブスキャン結果通知(応答なし) 受信");
continue ; //スキャン応答なし
}else{
if ( rcvbuf[12]==0x00 ) {
//スキャン応答あり
SigInf.scnch = rcvbuf[13];
SigInf.scnn = rcvbuf[14];
for ( int j=0;j<8;j++ ){
SigInf.maca[j] = rcvbuf[15+j] ;
}
SigInf.panid[0] = rcvbuf[23] ;
SigInf.panid[1] = rcvbuf[24] ;
Serial.println("アクティブスキャン結果通知(応答あり) 受信");
return 40 ; //成功
}
Serial.println("アクティブスキャン結果通知(その他) 受信");
continue ; //その他
}
}
break ;
default :
Serial.println("何か受信");
for ( int i=0;i<rcvcnt;i++ ) {
Serial.print(rcvbuf[i],HEX);
Serial.print(",");
}
return 998 ; //失敗
}
}
}
/********************************************************/
/*
/********************************************************/
void setup(){
int rcvcnt ;
char rcvbuf[250] ;
M5.begin(); // Initialize M5StickC Plus.
M5.Lcd.setRotation(3); //Rotate the screen.画面の向き
M5.Lcd.setTextFont(2);
M5.Lcd.setTextColor(DARKGREY);
M5.Lcd.setTextSize(2); //Set font size.
Serial.begin(115200);
Serial1.begin(115200, SERIAL_8N1, 26, 0); // EXT_IO
delay(100);
//ハードウェアリセット****************************************
send_hdreset();
for (;;) {
if ( rcvex(rcvbuf)== 10 ) break ;
delay(100);
}
//初期設定要求**********************************************
delay(100);
send_initsetreq( 4 ) ; //チャネル番号は仮に4ch。後で再設定する
for (;;) {
if ( rcvex(rcvbuf)== 20 ) break ;
delay(100);
}
//BルートPANA認証情報設定要求****************************
delay(100);
send_BPANAsetreq() ;
for (;;) {
if ( rcvex(rcvbuf)== 30 ) break ;
delay(100);
}
//アクティブスキャン実行要求****************************
delay(100);
send_actscnreq() ;
int fst = 0 ;
int sec = 0 ;
int e40 = 0 ;
int e50 = 0 ;
for (;;) {
fst = rcvex(rcvbuf) ;
delay(100);
if ( fst == 40 ) {
e40 = 1 ;
if ( e50 == 1 ) break ;
if (rcvex(rcvbuf) == 50 ) break ;
}else if ( fst == 50 ) {
e50 = 1 ;
if ( e40 == 1 ) break ;
if (rcvex(rcvbuf) == 40 ) break ;
}
delay(100);
}
//初期設定要求(スキャン結果を利用)*******************************
delay(100);
send_initsetreq( SigInf.scnch ) ; //チャネル番号はスキャン結果
for (;;) {
if ( rcvex(rcvbuf)== 20 ) break ;
delay(100);
}
//Bルート動作開始要求******************************************
delay(100);
send_Bstartreq( ) ;
for (;;) {
if ( rcvex(rcvbuf)== 60 ) break ;
delay(100);
}
//UDPポートOPEN要求******************************************
delay(100);
send_UDPopenreq( ) ;
for (;;) {
if ( rcvex(rcvbuf)== 70 ) break ;
delay(100);
}
//BルートPANA開始要求****************************
delay(100);
send_BPANAstartreq() ;
fst = 0 ;
sec = 0 ;
int e80 = 0 ;
int e90 = 0 ;
for (;;) {
fst = rcvex(rcvbuf) ;
delay(100);
if ( fst == 80 ) {
e80 = 1 ;
if ( e90 == 1 ) break ;
if (rcvex(rcvbuf) == 90 ) break ;
}else if ( fst == 90 ) {
e90 = 1 ;
if ( e80 == 1 ) break ;
if (rcvex(rcvbuf) == 80 ) break ;
}
delay(100);
}
//データ送信要求(スマート電力量メータ属性情報等取得)***********
delay(100);
char Property[13] = { 0x06,0x8D,0x00,0xD3,0x00,0xD7,0x00,0xE1,0x00,0xEA,0x00,0xEB,0x00 } ;
send_Getreq(13,Property) ;
for (;;) {
if ( rcvex(rcvbuf)== 100 ) break ;
//データ受信通知(Get_Res)はメインの中で受信する
delay(100);
}
StClMillis = millis();
}
/********************************************************/
/********************************************************/
/*
/********************************************************/
void loop() {
int rcvcnt ;
char rcvbuf[350] ;
M5.update();
rcvcnt = rcvex(rcvbuf) ;
if ( rcvcnt == 0 ) {
unsigned long currentMillis = millis();
if ( currentMillis - StClMillis > STCLTIMING ) {
MPowerreq() ;
StClMillis = currentMillis ;
}
if ( M5.BtnA.wasPressed() ) {
MPowerreq() ;
}
return ;
}
}
#あとがき
今回、初めてM5StickCを使ってみましたが、小さくてディスプレイも付いてケースに入っているので手軽に使えて便利でした。
しかし、この表示のためだけに占有するのは勿体ないので、データはクラウドに、ディスプレイは無しで安価なwroom02などに変えたいと思いました。
[Wi-SUN HATキット](https://booth.pm/ja/items/1650727,"Wi-SUN HATキット")のおかげで、BP35C1-J11-T01取り回しの自由度が広がりました。素晴しい!