R筋

プログラミングと時々育児

【R】ログ解析するときの工夫

久々にRのTIPSを。僕がRでログデータを処理したときにかんがえたことを書いとく。

#ログデータが複数ファイルにある時、リスト化する。
files <- list.files(getwd(),full.names=TRUE)
#full.names=TRUEによりフルパスのリストができる。

#あとで使う系
access <- list()
library(readr)

#リストを順番に読み込む
for(i in 1:length(files)){
  tmp <- read.csv(files[i],sep="\n",colClasses = "character",encoding="UTF-8",header=FALSE)
  #sep="\n"により改行コードを区切り文字として読み込む
  #→ログデータは1行1ログであることが多いため、1行単位で読み込むと良い。
  #colClasses = "character"
  #→デフォではfactor型など、扱いづらい型になっているので文字列として取得する。
  
  tmp <-as.list(tmp$V1)
  #データフレームの第1列をリストに変換
       
  x  <- grepl("error-AAA",tmp)
    #”error-AAA”に該当する行番号を取得する。
      
    access <- c(access,subset(tmp,x))
  #tmpの中で抽出条件xに該当する行だけを取り出した上で、
  #accessというリストに追記する。
}

time <- str_extract(access,pattern="[0-9]{4}.[0-9]{2}.[0-9]{2}.[0-9]{2}.[0-9]{2}.[0-9]{2}"
#時間を取り出す
#stringrの関数str_extractは該当した正規表現を取り出してくれる。
url <- str_extract(access,pattern="(http)[^,]*")
#URLを取り出す(てきとう)

log <- cbind(time,url)
#時間とURLをセットにしたデータフレームを作成

write.csv(log,"log.csv")
#書き出す

Google Home でよく使う機能

Google Homeはなんだかんだ毎日使うようになっていて、買ってよかった家電である。
0歳の息子も、僕が「ok,google」と言うと、google homeの方向を向いて「何か言うのかな?」って顔をしているので、音が出るヤツっていう認識は持っているようだ。
またGoogleも最近継続して会話できるような機能の提供を始めたらしい。
Chatting up your Google Assistant just got easier
だから今はまだ家電というカテゴリだが、どんどんできることが増えていくと、息子からしたら自分と一緒に成長してく家族の一人みたいな感覚で、IOTであったり、人工知能であったりという技術にふれることができる機会になればいいなあと思ったり。
そこまでいかなくても、いずれコンピュータとのI/Oというのはキーボードを使う機会が減り、声だったり、もっと進化したら脳から直接通信したりとなっていくであろうから、音声入力で通信をするという体験は何らか役に立つこともあるのではないだろうか。
そんなわけで今回はGoogle Homeでよく使うor面白い機能を紹介したい。

・天気を聞く

ベタだが一番つかっているのはこれ。今まで、テレビの番組を変えたり、ネットで検索したりしていたのでが、「ok,google 今日の天気は?」で結果がわかるのはすごく便利。

スマホを鳴らしてもらう

これも地味にすごく便利。家のなかでよくスマホをなくすわけだが「ok,google スマホ鳴らして」というとマナーモード中であっても音量を最大にして鳴らしてくれる。
しかも声を登録しているので、僕が言うと僕のスマホ、妻が言うと妻のスマホが鳴るという天才っぷりである。

・指定時刻に読み上げを行う

以下を参考にさせていただき、ほぼそのまま実装した。
qiita.com
若干工夫した点としては、話す内容を書いたスプレッドシートのファイルはこんなカンジした。
f:id:anpontan382:20180630220754p:plain
command列に「en」「ja」など言語を設定できるようにした。
また、今日はなんの日かであったり、今日の英語ニュースであったりを決めた時間に話してくれるようになっている。
この辺はスプレッドシートスクレイピング関数「IMPORTXML」でできる。詳細は以下を参照。
qiita.com
朝バタバタしながらも妻と「今日はトランジスタの日か~」とか「今の英語なんて言ってた?」みたいな会話が生まれ、Google Homeが「いってらっしゃい」と言うと「あ、そろそろ行かな」という感じで、音で知らせてくれると言うのは忙しいときにはなかなか便利である。

・動物の鳴き声を聞く

「ok,google 〇〇(動物)の鳴き声は?」というと、犬とかライオンみたいなメジャーな動物から、恐竜、スカンクみたいなほんとにそうなの?みたいな
ものまで幅広く対応してくれる。息子に絵本を読んでいるときに、おじさんが「いぬワンワン~U^ェ^U」とかやった後、リアル犬の声でワンワンすることで、息子が犬を様々な角度から理解できるのはないだろうか。

・外国語でなんというか聞く

バナナは英語で、ドイツ語で、ロシア語で、ベトナム語で・・・と聞くと各国のバナナをリアルな発音で教えてくれる。バナナに関してはかなりいろいろな言語をカバーしているようだ。多分ほかもそうだけど。ちなみに、banana、、Banane、банановый、chuốiということがわかったので、赤ちゃん語おしゃべり絵本というヤツに書いておいた。f:id:anpontan382:20180630222223j:plain
当初バナナしか説明がなかったのに「チョリー」もバナナのことであるとGoogle Homeのおかげで一緒に学べるようになり、息子の多言語学習がはかどっているかもしれない。

・LINEと連携する

過去の記事で書いたヤツ。
anpontan382.hatenablog.com
LINE↔Google Homeで双方向にメッセージをやり取りできるようになっているが、手が空いてない時や、息子にもメッセージを伝えたい時など使いみちは結構あって便利である。

raspberry pi でローカルDNSサーバを作る

最近仕事で社内向けDNSサーバの設定を変えることがあり、実用性はあまりないんだけど、勉強を兼ねて、家でも内部DNSサーバを作ってみることにした。

毎度お馴染み、持っててよかったraspberry piにBINDを入れて運用することにする。
すでに報告してくれている方々がおり、参考にさせていただきました。
wordpress.zenmai.org
www.kotemaru.org

そもそも、家に内部DNSを置くメリットはなにかというと、こんなところだろう。
・前記事で作ったウェブページにIPアドレスではなく、自分で設定した名前「http://~」でアクセスできる。
NASの共有フォルダへもIPアドレスではなく「\\~」でアクセスできる。
・外部DNSを自分で設定できるので、プロバイダのDNSサーバでブロッキングが行われても回避できる。
DNSの仕組みについて詳しくなり、インターネットの根幹を支えるDNSという技術に感謝する心が生まれる。

思うに一番最後はすごく重要だろう。
「www.yahoo.co.jp」と「183.79.250.251」はどっちをアドレスバーに入れても、ヤフーのトップページにつながるにもかかわらず、それぞれgoogleで検索してみると、検索結果は、約 672,000,000 件と3,420 件 という差が生まれる。
この差、196,491倍!これはもはやDNSサーバが我々の生活を196,491倍便利にしたと言える(謎理論)
従来比約20万倍の恩恵をもたらしたにもかかわらず、DNSサーバは世間一般にはほとんどその存在が知られておらず、誰からも感謝されることはない。
家庭内に内部DNSサーバを作ること、それはその道程においてDNSサーバに感謝するという行為である。

とはいえここから先は自分用のメモなので、全然参考にならないと思う。
やりたい人は上のページを参考にしたほうがいいだよ。

→etc/bind/named.confの設定

include "/etc/bind/named.conf.options";
include "/etc/bind/named.conf.local";

named.confはBINDの基本設定ファイルである。
includeにより詳細設定のファイル、named.conf.optionsとnamed.conf.localを読み込む。

→etc/bind/named.conf.optionsの設定
これはconfの各種オプションを設定するファイル。

options {
        directory "/var/cache/bind";

         forwarders {
              1.1.1.1;
        };
	//このゾーンで名前解決出来ないときは、1.1.1.1に問い合わせ
	listen-on-v6 { none; };
	//IP6は無効
};

→/etc/bind/named.conf.localの設定
実際にゾーンをここで定義する。

//ホスト名myhome.jpの設定
zone "myhome.jp" {
    // Master DNS Serverであることを明示
    type master;
    // 設定ファイル名を記載
    file "/etc/bind/myhome.db";
};

// 192.168.100.* の逆引きの設定
zone "100.168.192.in-addr.arpa" {
    // Master DNS Serverであることを明示
    type master;
    // 設定ファイル名
    file "/etc/bind/100.168.192.db";
};

→myhome.dbの設定
正引きの設定を記載

$TTL 86400

@      IN SOA ns.myhome.jp.    root.myhome.jp. (          //最後に「.」を忘れない
                                      2018010204 ; Serial //更新時に値を増やす
                                      28800      ; Refresh
                                      14400      ; Retry
                                      3600000    ; Expire
                                      86400      ; Minimum TTL
)

@       IN NS ns.myhome.jp. ; 
ns      IN A 192.168.100.130  ;
raspi2  IN A 192.168.100.125  ;
NAS     IN A 192.168.100.101 ;

→100.168.192.dbの設定
逆引きの定義を記載

$TTL 86400    ;

@   IN SOA ns.myhome.jp.    root.myhome.jp. (
                        2011102301 ; Serial
                        3H         ; Refresh
                        1H         ; Retry
                        1W         ; Expire
                        1D )       ; Minimum

    IN NS  ns.myhome.jp.     ; //Name Server を指定
    IN PTR myhome.jp.        ; //解決するドメインを指定
    IN A 255.255.255.0 ; //サブネットマスクを指定

130     IN PTR ns.myhome.jp.
125     IN PTR raspi2.myhome.jp.
101     IN PTR NAS.myhome.jp.

設定後は、

sudo named-checkconf

で、設定ファイルが正しいか確認できる。問題なければ再起動

/etc/init.d/bind9 restart

確認は、linuxの場合は、named.confに書いてあるDNSサーバを変更。
windowsはコンパネで使用するDNSサーバを指定。
もしくはDHCPサーバで配信するDNSサーバを変更。
等の方法で、raspberry pi上のDNSサーバが正しく動いているかを調べるとよい。

raspberry piでWEBサーバを立ててブラウザから部屋の気温・湿度・気圧を確認する。

朝からダルくて一切のやる気が出ない日が周期的にやってくるわけだが、この「周期的に」というのが重要で、僕がダメ人間だったら常にやる気がないはずで、何らかの外部要因が影響していて、僕のやる気を奪っている可能性が高い。つまりこの外部要因を特定できれば、僕は悪くないということが証明されるわけだ。

実際、外部要因として、低気圧だと頭痛になったり、湿度が高いと「水毒」でダルくなるということはあるそうだ。
www.alsok.co.jp

この辺を知っていると、役立つことは多いだろう。例えば、会社を早退したいときも
「なんかダリーんで帰りますチョッチョチョリーッス」
「私梅雨の時期は、湿度・気圧の変化によって全身疲労・倦怠感を覚え、業務へ支障をきたす可能性があるため、本日は大事をとって、お休みさせていただきます」
という感じで、知っている人と知らない人ではここまで印象が違う。

そういうわけで、気温・湿度・気圧と体調の相関を常日頃から意識しておくといいことがあるんじゃないかと考え、ブラウザから取得できるボタンを作成した。
だるいなと思ったら、このボタンを押して、原因が外気にないかチェックすることにしよう!


上下にあるボタンは別途記事にするので気にしないでね。

仕組みは前の記事と同じで、ブラウザからPOSTされたら、センサーを実行させる。
anpontan382.hatenablog.com

結果はajaxによってブラウザに返させるようにして、結果が来るまでは「通信中」と表示する。
ajax部分はこんな感じ。

$(function(){
        $(document).on('click', '#env', function(){
            $('#env p').html("Last valid date:通信中<br>Temperature:通信中<br>Humidity:通信中<br>Pressure:通信中")
            $.ajax({
                url:"/env",
                type:"POST",
                success:function(res){
                            $('#env p').html(res);
                        },
                error:function(){
                            window.alert('error')
                        }
            })
    });
});

id=envのボタンがクリックされると、pタグ内を「通信中」に変更する。
ajax通信では「/env」をPOSTし、通信が成功したら、受信した内容でpタグないを書き換える。
通信失敗時はエラーのアラートを表示させる。

サーバではポストを受け取り、センサーを実行させる。
実行するセンサーは2つあり、湿度を取得するDHT11と、気温、気圧を取得するBMP180だ。この辺を参考にそれぞれを取得できるスクリプトを作成した。

qiita.com
qiita.com

取得した値は次のように改行でつないで出力する。

Last valid date:2018/06/15 22:36:11<br>
Temperature: 23.60 C<br>
Humidity: 79%<br>
Pressure:1010.75 hPa

これを、サーバから返せば、ウェブページ上で表示される。

app.post('/env',function(req,res){
    console.log('env');
    const result2 =  execSync('sudo python env.py').toString();
    res.send(result2);   
});

ちなみに今日はこんな感じ。
f:id:anpontan382:20180615225623p:plain
不快指数は71。湿度は高いけど、気温が低いからそこまででもないですね。
いわれてみると確かに今日は気分もぼちぼちだな。

raspberry piでWEBサーバを立ててプリンタのON/OFFを操作する

僕や妻は仕事の書類や子供の写真などで何かとプリンタで印刷することが多い。
だが、印刷しようと思ってから、実際に印刷が完了するまでは、
プリンタの前に行く→プリンタの電源を起動する→パソコンに戻る→プリンタがネットワークに繋がりオンラインになる→印刷する→プリンタへ印刷物を取りに行く→プリンタの電源を切る
というプロセスが発生しており、時間にして2~3分を要する作業である。

これは大変な無駄である。育児と仕事で超絶多忙を極める我々とっての3分の損失は、現在の時間価値で3分の損失である。
私が幸いにして有する超絶最先端技術の知見を駆使することがこの大問題を解決できる微かな光明と言われており、私が可及的速やかにこの問題解決に取り組むことは全人類の渇望するところとなっていた。

そんなわけで完成したのがこれだ。


システム構成図はこんなカンジ
f:id:anpontan382:20180611223608p:plain

処理の流れ順に説明すると、まずHTMLでスイッチを作成する。
「HTML スイッチ おしゃれ」などで検索するといろいろとスタイリッシュなスイッチが出てくるので、その中でこちらを参考にCSSを作成した。
blog.manaten.net

豊富なサンプルのおかげでセンスがない人もそれっぽいものが作れるのでありがたいなぁ。
f:id:anpontan382:20180611223824p:plain

スイッチができたら、サーバにPOSTするjqueryを書く。

    $(document).on('change', '#switch', function(){
            $.post('/printer','printer');
    });
//id=switchで指定したフォームがchngeしたときに「/printer」というurlをPOSTする。

これをstaticフォルダにindex.htmlとして保存する。

次はPOSTを受け取るサーバサイドを作る。
app.jsに以下を記述する。
express.jsを使用するので事前にインストールしておこう。

const execSync = require('child_process').execSync;
const express = require('express');
const app = express();

//staticフォルダのindex.htmlを読み込みport3333で表示
app.use(express.static('static'));
app.listen(3333,function(){
    console.log('server start')
});

//"/printer"がポストされた場合受け取る
app.post('/printer',function(req,res){
    console.log('POSTED')
 //pythonの実行を同期処理で行う
    const result =  execSync('sudo python servo_printer.py').toString();
});

次はサーボモータをプリンタに取り付ける。
サーボモータはSG-90という定番商品があり、一個400円で購入。
http://akizukidenshi.com/catalog/g/gM-08761/
微調整を重ねながら、ゼムクリップ2個でうまいことスイッチに当たる位置で固定することに成功。
f:id:anpontan382:20180611222502p:plain

続いてservo_printer.pyに実際にservoモータを動かすスクリプトを作成する。
コードは以下を参考にした。

import RPi.GPIO as GPIO
import time
GPIO.setmode(GPIO.BCM)
gp_out = 17
GPIO.setup(gp_out, GPIO.OUT)
servo = GPIO.PWM(gp_out, 50)
servo.start(0)
servo.ChangeDutyCycle(7)
time.sleep(0.5)
servo.ChangeDutyCycle(15)
time.sleep(0.5)
servo.stop()
GPIO.cleanup()

ChangeDutyCycle()に指定した値で、回転して止まる場所が変わるので、いろいろと値を変更させながら、ちょうど良く押す→戻すができる数値を探す。

各ファイルをこんな構成にしてraspberry piに保存。
www
|-app.js
|-servo_printer.py
|-static
-|-index.html

raspberry piでapp.jsを実行し、コンソールに「server start!」が表示されたら、準備OK。
ブラウザから「192.168.(raspberry piのアドレス):3333」を開きお気に入りに登録すればいつでもスマホやPCでプリンタの電源を入れられるようになった!
いやいや、そろそろ印刷しようかなーって言うときに、ブラウザ開いてボタン押しておけばすぐにプリンタが使えるわけだから意外と便利だと思うよ、うん。

今後の課題としては、webページのON/OFFは必ずしもプリンタのON/OFFとリンクしていないので、うまく電源が入らないときは、それらがズレてきてしまう問題の修正が必要だろう。
プリンタは電源が入ればネットワークに必ずつながるので、raspberry piでプリンタのIPアドレスを常時監視して、一定期間ごとにwebページのON/OFFを実際のプリンタのON/OFFと連動させるとよりスイッチらしくなるだろう。

が、プリンタのON/OFF如きにそこまでこだわれる情熱を持っていないため、やらない。
直接プリンタ見りゃいい話だし。←それを言ったらON/OFFも直接押せやという話になるが。

Raspberry pi + google homeで最近やったこと

結局ハマっちゃうわけです。楽しいから仕方ないことですね。

・現在のマーケット情報を確認する。

f:id:anpontan382:20180424220732p:plain
コマンド実行時に日経新聞社の世界の市況ページをスクレイピングし、読み上げる仕組みだ。
www.nikkei.com
概要はこちら。
f:id:anpontan382:20180424221027p:plain
朝の時間に準備しながら、世界中の通貨や指数をたれ流したかったのだが、google-home-notifierで読み上げられる文字数は100文字程度が上限のようで、指数にして3~4つが限界だった。
また、「+」「ー」をカタカナに、「-」を「から」に置換する等、読み上げをする上で自然になるよう置換が必要である。
スクレイピング部分のコードを載せておく。

var client = require('cheerio-httpcli')
var URL = 'https://www.nikkei.com/markets/worldidx/'

var data = [];
var data2 = [];
var target =["日経平均(円)","ドル・円","NYダウ工業株30種(ドル)","米10年国債"];
var str =""
//抽出対象の銘柄
client.fetch(URL,function(err,$,res){
    if(err){console.log(err);return;}

var table = $('.cmn-table_style1 tr')
//tr内を取り出す

    for(var i=0,l=table.length;i<l;i++){
        var cells=table.eq(i).children();
        data[i]=[];
        for( var j=0,m=cells.length;j<m-1;j++ ){//m-1とすると最終列(時間帯)は含めない
            data[i][j] = cells.eq(j).text();//i行目j列の文字列を取得
           }
           data2[i]=data[i].join('。')//列を結合
           data2[i]=data2[i].replace("+","プラス")//置換
           data2[i]=data2[i].replace("-","マイナス")        
           data2[i]=data2[i].replace("-","マイナス")
           data2[i]=data2[i].replace("-","から")
           data2[i]=data2[i].replace("(","ポイント。")
           data2[i]=data2[i].replace(")","")
           data2[i]=data2[i].replace("※","")
           //console.log(data2[i])
           for(var k=0,n=target.length;k<n;k++){
                if(data2[i].indexOf(target[k])==0){//targetに該当する行を取り出す
                    str+=data2[i]+"\n"
                } 
           }   
    }

    str=str.replace("?","")
    console.log(str)
   
googlehome.notify(str, function(res) {
    console.log(res);
  })
})
}

出来上がったのはこちら。
日経新聞のページに表示されている値なのでマーケットがオープン中なら現在値、クローズ後なら終値が読まれる。


この声での数字の読み上げはなんか頭に入ってこない…
それと、元から「日経平均は?」「ドル円は?」等へは解答してくれるので、全指数をまとめて読み上げることが文字数の制限で無理な時点で実装する意味があんまない機能でした。

NASの音楽ファイルを再生する。

f:id:anpontan382:20180424225100p:plain
NASに置いた音楽ファイルをランダムに再生してくれる。
概要はマーケットとほぼ同じ。
f:id:anpontan382:20180424225303p:plain
NASのマウントはこちらを参考に実施。
www.mana-cat.com
NAS上に音楽フォルダを作成し、その中に音楽ファイルを格納する。
後はパスを指定してgoogle-home-notifierに送ればいいのかと思いきや、google-home-notifierのファイルパスではなくhttp:~のURLを指定しないといけない。
そのため、音楽フォルダでミュージックサーバを立てる。
参考にしたのは次のサイト。
scrapbox.io
プログラムの流れは以下の通りとなる、①と②はnohupで常に実行しておく。
①nodeでミュージックサーバを立てる。
②firebaseのDBの更新があったら③を実行する
③ミュージックサーバの各ファイルへのURLをランダムに取得しGoogle homeで再生する。
①は上記サイト、②は過去の記事を参照。
anpontan382.hatenablog.com
③のコードはこんな感じで出来た。全体を関数としてexportしている。

exports.playmusic=function(){

var fs = require('fs');
const googlehome = require('google-home-notifier');
const language = 'ja';
googlehome.device("Google-Home", language);
googlehome.ip("192.168.***.***");//googlehomeのIPアドレス

fs.readdir('/home/pi/nas/music', function(err, fileList){
    if (err) throw err;
   
    //console.log(fileList);
    randomfile = fileList[Math.floor(Math.random() * fileList.length)];
    url ='http://192.168.***.***:3000/music/' + randomfile;
    //ラズパイのIPアドレス+ポート+ミュージックサーバのパス+NASからランダム
 //に取得した音楽ファイル

    console.log(url);

googlehome.play(url, function(res) {
  console.log(res);
});

僕は現在300曲程をNASに保存しているが、「再生」というとランダムにgooglehomeが再生してくれる。
曲が多すぎて聞きたい曲が流れることはない。そのため、今後の改良としてはフォルダ毎にプレイリストを作り「〇〇(フォルダ名)を再生」と言うと、フォルダ内の曲だけが流れるようにできるとより音楽プレイヤーとしての利用が捗りそう。