読者です 読者をやめる 読者になる 読者になる

漆黒な技術メモ

管理人が必要に応じて自分のメモを好き勝手に投下するたまり場的ブログ

C++のstd::string::clear()はメモリの解放をしてくれない話

まあ簡単に言うと自分のC++に対する知識が浅かったって話ですが…
研究のソースでstd::string::clear()を何回か使ってメモリの解放をした「つもり」でしたが、こいつは長さを0にセットするだけでメモリ領域は食ったままらしく、結果的にメモリリーク( std::bad_alloc )を起こして死にました。 ちなみに書いたソースはこんな感じ

class Buffer {
public:
    Buffer();
    virtual ~Buffer();
  
    void setData(std::string data,int type);
  
    std::string getData();
    int getType();
  
    void clearData();
    bool isexistData();

private:
    std::string data;
    int type;
    bool existData;
};

Buffer::Buffer() :
        type(-1), existData(false) {
}

Buffer::~Buffer() {
}

void Buffer::setData(std::string data,
        int type) {
    this->data = data;
    this->type = type;
    existData = true;
}

std::string Buffer::getData() {
    return data;
}

int Buffer::getType() {
    return type;
}

void Buffer::clearData() {
    data.clear();
    existData = false;
}

bool Buffer::isexistData() {
    return existData;
}

こういう状況で、いくら Buffer::clearData() を呼び出してもメモリリークを起こすので不思議だなあと思ってたら、stackoverflowにそんなことが書いてありました。

Calling std::string::clear() merely sets the size to zero. The capacity() won’t change (nor will reserve()ing less memory than currently reserved change the capacity).

stackoverflow.com

stackoverflowには、解放したいんだったらBuffer::clearData()

void Buffer::clearData() {
  std::string().swap(data);
    existData = false;
}

で行う(空文字を確保したstd::stringとのメモリ領域スワップ、スコープが終われば元々dataで確保した領域はデストラクタが呼ばれて解放される)と良いと書いてありました。
でも、この書き方だとなにやってるかわかりづらいですよね…

std::shared_ptrに変える

まあそもそもバッファなんだからshared_ptrにしろよという突っ込みが聞こえてきそうですが、その話は棚に上げて… std::shared_ptrはじめスマートポインタは reset() で所有権を放棄、つまるところ(shared_ptrの場合はカウンタが0であれば)明示的にメモリを開放することができます。 というわけで該当部を以下のようなコードに変更して事なきを得ましたとさ。

void Buffer::clearData() {
    data.reset();
    existData = false;
}

SQLで「グループごとにn件出力する」というSELECT文を書く

皆様あけましておめでとうございます。本年もどうぞよろしくお願いいたします。
さて、今日はSQLを久しぶりにいじっていて詰まったことを書きたいと思います。

下のようなテーブルがあって、「SSIDごとにステータスがONの最新の結果をn件出力する」ということを実行しなければならない場面に遭遇しました。 例を出すと、下のようなテーブルから「ステータスがONの最新の結果を1件出力したい」とします。

id time ssid status
1 12:00 ssid1 ON
2 12:00 ssid2 ON
3 12:00 ssid3 ON
4 12:10 ssid1 ON
5 12:10 ssid2 ON
6 12:10 ssid3 ON
7 12:20 ssid1 ON
8 12:20 ssid2 OFF
9 12:20 ssid3 ON

すると、上のテーブルから下のような結果が出てくるのが理想になります。

id time ssid status
7 12:20 ssid1 ON
5 12:10 ssid2 ON
9 12:20 ssid3 ON

しかしこのような抽出は、group by句、where句などの単純なものではできません…
さて、どうするかと思って検索するとこのような記事に引っかかりました。

blogs.wankuma.com

今回はこの記事を全力で参考にして解決しました。

解法:IN句を用いた副問い合わせ

これを見た瞬間「ああ、副問い合わせとかそんなものあったなあ」となりました…(←ダメな奴)
最初に示した結果を抽出するには以下のようなSQLを書きます。

select * from table as t1
where id in 
(select id from table as t2 where t1.ssid=t2.ssid and status='ON' order by id desc limit 1)
order by ssid, id desc;

主問い合わせ文のwhere句にはid in の形で指定し、副問い合わせ文のwhere句の中でグループにしたいカラムを結合します(この場合は t1.ssid=t2.ssid )。
また、「ステータスがONの」などの他の条件や「N研抽出する」などのLIMIT句も副問い合わせ文の中に記述します。
あとは適宜並び替えなどをすれば完了です。

今日はここまで!

【ちょっと技術的なことAdventCalender】 hackU実装振り返り-3. nodejsでpostgresに接続-

この記事はちょっと技術的なことAdventCalenderの24日目です。
クリスマスに風邪をこじらせてしまい書くのが遅れました…

先週あたりからhackU開発後記としてつらつら書いていますが、今日は可視化システムで必要だったnodejsとDBの連携についてお話ししようと思います。

nodejs×postgreSQL

恐らく座布団が定期的に送ってくるデータをため込んで可視化する…という作業にはNoSQLであるmongoDBとかの方がパフォーマンス的には正しい選択肢なのですが、 今回は開発時間をあまりとれなかったので、早めにシステムを組み上げるため研究室内での運用実績が多いpostgresを利用しました。
nodejsからpostgresを利用するにはpgというパッケージを利用します。いつものように$npm install pgでインストールします。
パッケージがインストールできたらあとはコードを書くだけです。

記述コード

ここでは、引数で渡された任意のsql文を実行し、結果をコンソールに出力するというプログラムを紹介します。

var pg = require('pg');
var DBhost = "postgres://username:password3@www.example.jp:5432/dbname";

sql_custom = function (query,socket) {
    var result;
    var client = new pg.Client(DBhost);
    client.connect(function (err) {
        if (err) throw err;
        client.query(query, function (err, value) {
            if (err) throw err;
            console.log("get data");
            console.log(value);
            client_select.end();
        });
    });
});

pg.Client型の変数を使いまわして2回以上接続切断を行うと例外を吐いて止まってしまったので、毎回接続前に新しいインスタンスを生成して 局所変数として扱うのが一番よさそうです。
このコードでも何回も接続と切断を繰り返すと”too many clients”的なエラーが出てしまうので、少しこの部分は改良の余地がありそうですが…
今日はここまで!ありがとうございました。

【ちょっと技術的なことAdventCalender】 hackU実装振り返り-2. mbed2台を直結する-

この記事はちょっと技術的なことAdventCalenderの21日目です。
今日は、以前に引き続きhack Uで苦労したことにうちの1つをつらつら書いていこうと思います。

前回「mbed×websocketをやろうとしたらライブラリの都合でmbed2台を直結することになった」というお話をしたと思います。
今日は、そこで苦労した「意外とちゃんとわかってなかった電気回路のお話し」をしようと思います。

mbed2台を直結する

mbedをはじめとしたマイコン関係のモノを繋ぐときには、p9,p10などのピンにジャンパ線を差し込み、UARTを利用したシリアル通信を行うのが一般的かと思います。 f:id:igbt3116redtrain:20161223050117p:plain
(https://www.switch-science.com/catalog/250/より引用)
しかし我々、シリアルポートを繋ぎ、サンプルプログラムを動かしても、通信できない…

なんかよくわからないけどシリアル通信はうまくいかないと判断し、仕方なく複数の空きピンを利用して並列送信をすることにしました。
しかしそれでもうまくいかない…

原因はソフトウェアではなく電気回路

単純な1,0のDigitalOutの通信がうまくいかないのはいくらなんでもおかしい、と思い、DigitalInをいろんなピンにつないで動きを見てみました。
すると、DigitalInと同じmbedのVCCでは入力を1とみなすのに、もう片方のmbedのVCCでは入力を0と判定しました。
ここでもしかしてと思い、一方のmbedのVCCからもう片方のmbedのGNDの電圧を測定すると…0Vでした。

つまり、ただVCCとGNDを用意すればいいだけではなく、きちんと「回路」を形成してあげる必要があるようです。
電気をきちんとわかってる人からしたら「何当たり前のことを言ってんだこいつは」と思われるかもしれませんが、ソフトウェアだけをいじってる人間はこの辺が意外とわかってなかったりするようです(byわかってなかった人)

回路を形成する

形成するといってもそんな難しいことではありません。通信用のピンの他に、互いのmbedのGNDをジャンパ線で直結するだけです。
f:id:igbt3116redtrain:20161223051742j:plain
これを行うだけで、2台のmbedの信号を無事やり取りすることができました!

今日はここまで

【ちょっと技術的なことAdventCalender】 hackU実装振り返り-1. mbed×websocket-

この記事はちょっと技術的なことAdventCalenderの21日目です。 いよいよ最後も見えてきました。自分の担当分はこれを含めてあと4回です。

今日は、前回お話ししたhack Uで苦労したことにうちの1つをつらつら書いていこうと思います。

mbed × Webscoketは難しい

今回の残業防止IoT座布団では、座布団側のマイコンにmbedというARMマイコンを、サーバ側にnode.jsを利用しました。 この業界だと今流行りのマイコンは圧倒的にArduinoで、普通ならそっちを使うんですが、今回はチームメンバーが研究室でmbedを使っていること、 僕も昔に少し触れたことがあることから、mbedを採用しました。

で、mbedにもいくらかwebsoket関係のライブラリはあり、その中にはSocket.IOのラッパも存在したので、「これで問題なくSocket.IOを使ったwebSocketが使えるだろう」… って考えてた僕が馬鹿でした。 そのSokcet.IOのライブラリはどうもうまく動きませんでした。 パケットとライブラリ実装を確認すると、ライブラリのLast Commit Date が2012年になっており、そのころからSocket.IOの仕様が大きく変わってしまったため通信ができませんでした。 恐らくやるとしたらSocket.IOの仕様とライブラリの実装を読みくだきながらアレンジする必要があると思ったので、断念しました… 他にもWebsocketという名前そのままのライブラリがありました。しかしこれも最終コミットが2011年とまあまあ古い状態でした。ただRFC準拠実装みたいなので、何とかなるかなあと思い、サーバ(node.js)側でsocket.IO以外のライブラリを検討しました。

実装

結果node.js側で用意したのはwsというパッケージです。 インストールは $npm install ws で普通にインストールできます。 mbed側は先ほどお話ししたWebscoketライブラリを利用します。 フルソースは後日gitにアップするんで、一番要になる部分の実装だけ抜き出して…

mbed側

#include "mbed.h"
#include "Websocket.h"
#include "EthernetNetIf.h"

void init(char * hostAndPort){
    eth = new EthernetNetIf();
    EthernetErr ethErr = eth->setup();
    if (ethErr) {
        printf("\r\nERROR %d in setup.\r\n", ethErr);
    }
    printf("hostandport %s\n", hostAndPort);
    
    ws = new Websocket(hostAndPort, eth);
    printf("websock init\n");
}
    
bool connect(){
    printf("connecting...");
    int failcount=0;
    
    while(! ws->connect()) {
        failcount++;
        if(failcount>10){
            printf("cannot connect failed.\n");
            return false;
        }
        printf("cannot connect websocket, retrying...\n");
        wait(2);
    }
     return true;
}

int mess_send(char * msg){
    ws->send(msg);
    printf("data send\n");
    return 1;
} 

int mess_recv(char * msg){
    if (ws->read(msg)) {
        return 1;
    }else{
        return -1;
    }
}

int main() {
    init("ws://www.example.jp");
    printf("connect start\n");
    connect();
    printf("connect");

    char msg[64];
    while(1) {
        Net::poll();
        mess_send("hello");

        int ret = mess_recv(msg);
        if(ret)
            printf("recv: %s\n",msg);
        
        Net::poll();
    }
}

サーバ側

var wss = require('ws').Server;
var mport = 12020;
var ws_Mbed = new wss({ port: 12020 });

ws_Mbed.on('connection', function (ws) {
    wsm_groval = ws;
    countor = 0;
    console.log("connect from mbed");

    ws.on('message', function (message) {
        console.log('received:'+ message);
        var type;
    });
});

こんな感じで実装すればmbedでもwebsocketで通信できます!!

欠点

ただ、このmbedライブラリは大きな欠点を抱えています。イーサネットのライブラリに古いライブラリであるErthernetIFライブラリを利用しています。 現在はErthenetInterfaceというライブラリを利用するのが普通です。 古いErtherNetIFライブラリには大きな欠点があります。それはmbedのRTOS(非同期処理)ライブラリとすこぶる相性が悪いということです。 我々は新しいイーサネットライブラリへの付け替えにチームメンバが挑んでくれましたが、残念ながらうまくいきませんでした。
というわけで、たまたまmbedが2台あった我々は、mbed2台で通信側、センサ側と分け、2台のmbedをジャンパ線で直結してなんとか乗り切ろうという発想に至りました。
次回はそこでの苦労をお話ししたいと思います。

追記

mbed側のライブラリにWebSocketClientというライブラリがありました。こちらはErthenetInterfaceをライブラリに使っていたので、こちらを使えば次回に書くような苦労はしなくてもよかったのかもしれません。。。

【ちょっと技術的なことAdventCalender】Hack U(ハッカソンイベント)に参加してきました

この記事はちょっと技術的なことAdventCalenderの19日目です。
今日は日曜日に参加したHack U 2016 大阪会場 Student Hackathon - Yahoo! JAPANについてお話ししようと思います。 hacku.yahoo.co.jp

Hack Uとは

Hack U(ハック・ユー)は、限られた期間の中で、学生がプロダクトを自ら企画・開発・発表するイベントです。ものづくりの楽しさを体現できるよい機会となるよう、ヤフーの現役社員が学生のみなさんを全力でサポートいたします。

(公式サイトより)
まあすなわち、学生限定の相当大規模なハッカソンイベントと言ったところです。
今回のテーマは「自動○○」となっています。とはいうものの、何かプログラムを作ると大抵は何かを自動化しているので、結構何でもありの色彩が強い大会ではあります。
勿論優秀賞など、賞もありますが、残念ながら今回自分たちはじゅしょいうすることができませんでした。

自分たちの作ったもの

今回自分たちは、ネタに走りに行く気満々で「ブラックな人たちを助けよう」というお題目(当然このお題目の決め方もノリ)で、「残業防止座布団:‡漆黒からの解放‡」という作品をつくりました。
座布団の中に圧力センサ、加速度センサを埋め込み、長時間座っていることや貧乏ゆすりを検出して離席を促す作品です。 f:id:igbt3116redtrain:20161220052620j:plain この座布団の中にセンサとスピーカを仕込ませてあります。一見するとただの座布団(というかクッション)ですが
f:id:igbt3116redtrain:20161220052844j:plain 座面下にマイコン(mbed)、背もたれにバッテリーと通信モジュールを付加してあります(椅子は会場のモノです)
これらを利用して、

  • 離席を促すべき時に音楽を鳴らす機能
  • イライラ度可視化
  • 着席などのイベント発生に合わせたツイッターへの投稿機能
  • 可視化画面からの座布団の動作制御

を行っています。
f:id:igbt3116redtrain:20161220053343p:plain
この作品の主目的からは外れるためあまり注目はされませんでしたが、「webブラウザからの座布団動作」ができるようになっています。
自分はここに一番こだわりを持っていました。両方向のIoTを何とか形にしてみたかったからです。

利用した技術と自分の担当範囲

アーキテクチャ図にするとだいたいこんな感じです。 f:id:igbt3116redtrain:20161220055101p:plainバイス、マイコン、データベース、比較的新し目の通信規格&サーバ実行環境、可視化、他システムとの連携などIoTエコシステムと言われそうな部分を一通り実装しました。
(本当はAndroid連携をやりたかったのだけど実装に使える時間の確保ができませんでした…)
自分はデバイスを少しと、マイコンの通信部分、サーバサイド処理を担当しました。

次以降の記事の話

今回HackUの作品について書くのはここまでにしておいて、次回以降はこの実装で苦労した部分のことを自分のメモを兼ねて何回かに分け書いていきたいと思います。
またアドベントカレンダー期間が終わるころまでに今回のソースをgithub上に公開したいと思っています。
それでは!

【ちょっと技術的なことAdventCalender】改行区切りのログ出力をExcelとかで使えるようにCSVに変形するPythonスクリプト

この記事はちょっと技術的なことAdventCalenderの15日目です。
気づいたらもう折り返しを過ぎていますね…

プログラムを実行したときのログ出力

って、大体こんな感じで出力するようにプログラム組んでいる方が大半だと思います。

time : 10:00
power : on
value : 1

time : 10:10
power : off
value : 0

time : 10:20
power : on
value : 2

...

まあ実装上この形式で書くのが一番楽ですし、ログとして書く分には何も問題ないのですが… ある時、このログをExcelなどで集計しなくてはならない…!!なんてことになったら、この形式でやるのは超面倒ですね…

time,power,value
10:00,on,1
10:10,off,0
10:20,on,2

とかにできるのが一番理想です。
と、そんなことを実際にしなくてはならない場面に遭遇してしまったので、スクリプトを書きました

実際に作ったpythonスクリプト

引数でファイル名を指定します。空白行をCSVの行の分け目と認識するようにしています。
また、カラムはブロックごとに異なっても(途中から"Battery"カラムが増えたり、消えたり) 大丈夫なようになっています

#coding: utf-8
import sys

if len(sys.argv) < 2:
    print "usage filename"
    sys.exit(1)
filename = sys.argv[1]

f=open(filename)
lines=f.readlines()
valueText=""
columnTitles=[]
textBuf={}
for line in lines:
    if '\n' in line[0]:
        for col in columnTitles:
            if col in textBuf:
                valueText += textBuf[col]+","
            valueText+=","
        valueText += "\n"
        textBuf.clear()
    else:
        sptline=line.replace("\n","").split(":",1)
        if sptline[0] not in columnTitles:
            columnTitles.append(sptline[0])
        textBuf[sptline[0]] = sptline[1]

for col in columnTitles:
    print col+"," ,
print ""
print valueText ,

これを使って

$ python parser.py log.log > log.csv

とかやれば、変形できます。

実行結果

こんな感じです

$ cat status.log
time:2016/12/11 23:32:32
SSID:gp30
Mode:MultiShot
SubMode:TimeLapse
Recoding:True
Num. of taken:268
Num. of remaining:11794
SDcard:True
Battery:3

time:2016/12/11 23:40:25
SSID:gp29
Mode:MultiShot
SubMode:TimeLapse
Recoding:True
Recode:test
Num. of taken:278
Num. of remaining:11794
SDcard:True
Battery:3

$ python genlogparser.py status.log
time, SSID, Mode, SubMode, Recoding, Num. of taken, Num. of remaining, SDcard, Battery, Recode,
2016/12/11 23:32:32,gp30,MultiShot,TimeLapse,True,268,11794,True,3,
2016/12/11 23:40:25,gp29,MultiShot,TimeLapse,True,278,11794,True,3,test,

今日はここまで、それでは!!