script for me
さて、今回は、何かをトリガーに
しゃべるって仕組みを作ってみる。
トリガーは以下を用意する予定。
- When I Want(時間で)
- If She Felt Temp/Humidity(温湿度センサから)
- When She Saw(カメラから)
- If She Felt Someone(人感センサから)
今回は1番目のやつを実装する。
say something when I want her to tell me
しゃべってほしい時間にしゃべる仕組みが
1番簡単そうなので小手調べ。
どんな風にスケジュールを管理しようかな?
例えばGoogleカレンダみたいなクラウドから
持ってくるか。そうなると、例えば目覚ましとかの
ルーティンワークも登録する必要がある。
むしろ予定はこっちから聞くことが多いから
雑多でありながら忘れがちなものを
教えてくれたほうが気が利くってもんだ。
- ○月○日○時です。天気は○です。おはようございます。
- もう○時です、寝る時間ですよ。
- 家を出る時間まであと○分です。
- お誕生日おめでとうございます。
- 今日は○さんのお誕生日です。
みたいなの。平日と休日も理解して
繰り返してくれる。
本当は誰かの誕生日の○週間前の週末ぐらいに
教えてくれるのが親切なんだろうな。
これは後でやろう。
大まかな作りとしては
rpi+2s.jsというメインのモジュールを作って
サブモジュールとしてScheduleCaller.jsなんてのを
用意して、ここで実装する。
write down the recipe
ScheduleCaller.jsを書いてみる。
今回はJSON形式のファイルを解析して
喋らせようかなと思う。構造はこんな感じ。
[
{"no": no,
"name": schedule name,
"freq": executed frequency,
"date": executed date,
"time": executed time,
"message": what she speaks
},
{"no": no,
.
.
.
},
.
.
.
]
こんな感じにした。
noとnameは今は特に意味はなし。
freqは次の値から選べる。
- m : 毎時間同分に実行
- h : 同時刻に実行
- d : 同日同時刻に実行
- w : 同曜日同時刻に実行
dateはfreqによって入れる値が変わる。
freqがmとhの場合はemptyでOK。
dはMM/DD形式の日付。
wは曜日の英語表記を英小文字3文字で。
(例:monとかsatとか)
timeは常に時刻を入れる。HH24:MI形式で。
ただしfreqがmの場合は分の部分しか評価しない。
messageは喋らせたい言葉を入れる。
これをschedule.jsonってファイルに保存する。
どのくらいのタイミングでチェックするかは
まだ決めてないけど、予定時刻の
30分以内か1分以内であればトリガーを
発動するようにしたい。
さてやっとモジュールを書いてみる。
const fs = require('fs');
const now = require('date-utils');
const schedJSON = './schedule.json';
const chkWeekly = 'w';
const chkDaily = 'd';
const chkHouly = 'h';
const chkMinutely = 'm';
const endTimeInterval1 = 1000 * 60 * 30; // 1800000msec = 30min
const endTimeInterval2 = 1000 * 60; // 60000msec = 1min
class ScheduleCaller {
constructor(){
this.now = new Date();
this.load();
}
load(){
var promise=Promise.resolve();
promise
.then(this.parseJSON())
.then(this.loadJSON())
.catch(this.onRejected);
}
parseJSON(){
this.rs = JSON.parse('{"result": false, "message": "", "weather": "" }');
}
loadJSON(){
this.sc = JSON.parse(fs.readFileSync(schedJSON, 'utf8'));
}
onRejected(err){
console.log(err);
}
isInTime(validTime, validDate, checkType) {
var isCorrect=false;
var validtime=new Date('1990/01/01 '+validTime);
switch(checkType){
case chkDaily:
var validtime=new Date('1990/'+validDate+' '+validTime);
var nexttime=new Date(validtime.getTime() + endTimeInterval1);
if(this.now.toFormat("MM/DD")==validtime.toFormat("MM/DD")){
if((this.now.toFormat("HH24:MI")>=validtime.toFormat("HH24:MI"))&&
nexttime.toFormat("HH24:MI")>=this.now.toFormat("HH24:MI")){
if(!isCorrect)isCorrect=!isCorrect;
}
}
break;
case chkWeekly:
var day = new Date().getDay();
var weekday = 'sunmontuewedthufrisat'.substring((day*3),(day*3)+3);
var nexttime=new Date(validtime.getTime() + endTimeInterval1);
if(weekday==validDate.toLowerCase()){
if((this.now.toFormat("HH24:MI")>=validtime.toFormat("HH24:MI"))&&
nexttime.toFormat("HH24:MI")>=this.now.toFormat("HH24:MI")){
if(!isCorrect)isCorrect=!isCorrect;
}
}
break;
case chkHouly:
var nexttime=new Date(validtime.getTime() + endTimeInterval2);
if(this.now.toFormat("HH24")==validtime.toFormat("HH24")){
if((this.now.toFormat("MI")>=validtime.toFormat("MI"))&&
nexttime.toFormat("MI")>=this.now.toFormat("MI")){
if(!isCorrect)isCorrect=!isCorrect;
}
}
break;
case chkMinutely:
var nexttime=new Date(validtime.getTime() + endTimeInterval2);
if((this.now.toFormat("MI")>=validtime.toFormat("MI"))&&
nexttime.toFormat("MI")>=this.now.toFormat("MI")){
if(!isCorrect)isCorrect=!isCorrect;
}
break;
}
return isCorrect;
}
hasTimeCome(){
var message='';
var needSpeak=false;
for(var i=0;i<this.sc.length;i++){
if(this.isInTime(this.sc[i].time, this.sc[i].date, this.sc[i].freq)){
message+=" "+this.sc[i].message;
if(!needSpeak)needSpeak=!needSpeak;
}
}
if(needSpeak){
this.rs.result=true;
this.rs.message=message;
}else{
this.rs.result=false;
this.rs.message=message;
}
return this.rs;
}
}
module.exports=ScheduleCaller;
まだまだ改造の余地があるが、ひとまず先に進みたいので
この辺にしておく。
動作としては、JSONファイルに従って動くこととして
もし同時刻の予定が重なった場合も考えて
喋らせる言葉を直列につなぐようにした。
喋らせる言葉には変数を入れられるようにして
$HOURとか$WEATHERを入れておくと
勝手に変換されるようにする。
そのうち$FORTUNEとか$TEMPみたいなのも
追加してみたい。
$WEATHERで天気を変換したいのでTenkiモジュールを作ってみる。
天気の情報をOpen Weather Map APIを使うことにする。
取得する情報の同期処理をかけたいので
sync-requestモジュールを使っている。これは大変便利。
さて書いてみる。
const request=require('sync-request');
const mycity="Sapporo-shi,JP";
const units='metric';
const openweatherapi="XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX";
class Tenki {
constructor(){
this.URL = 'http://api.openweathermap.org/data/2.5/weather?q='+ mycity +'&units='+ units +'&appid='+ openweatherapi;
this.res;
this.fetch();
this.localize();
}
fetch(){
var response=request('GET', this.URL);
var body=JSON.parse(response.body);
this.res=body;
}
localize(){
var result='';
if(this.res){
for(var i=0;i<this.res.weather.length;i++){
switch(this.res.weather[i].main){
case 'Rain':
this.res.weather[i].wamei='雨';
break;
case 'Mist':
this.res.weather[i].wamei='霧';
break;
case 'Clear':
this.res.weather[i].wamei='晴';
break;
case 'Clouds':
this.res.weather[i].wamei='曇';
break;
default:
this.res.weather[i].wamei='不明';
break;
}
}
}else{
console.error(err);
}
}
getWeather(){
return this.res;
}
}
module.exports=Tenki;
天気の和名翻訳を自前で作っているのだが
まだ間に合ってない。
ま、これで
const tenki=require('./Tenki');
var tk=new tenki();
var w=tk.getWeather();
console.log(w.weather[0].wamei);
こんな感じで取得できる。
んで、スケジュール管理と天気の変換を行う
メインのモジュールを書いてみる。
どんどんやっつけになってきたかな?
const googleSpeak = require('./GoogleSpeak');
const scheduleCaller = require('./ScheduleCaller');
const Tenki = require('./Tenki');
class Rpi2s {
constructor() {
}
howling(){
this.isHowling=false;
this.ret;
this.sc = new scheduleCaller();
this.fc = new Tenki();
this.ret=this.sc.hasTimeCome();
if(this.ret.result){
this.message=this.changeVariables(this.ret.message);
if(!this.isHowling)this.isHowling=!this.isHowling;
}else{
console.log(this.ret);
}
if(this.isHowling){
this.bowwow();
}
}
bowwow(){
var gs=new googleSpeak();
gs.meow(this.message);
}
changeVariables(txt){
var message=txt;
var forecast=this.fc.getWeather();
return message
.replace(/\$MONTH/i, new Date().toFormat("M"))
.replace(/\$DAY/i, new Date().toFormat("D"))
.replace(/\$HOUR/i, new Date().toFormat("H"))
.replace(/\$MINUTE/i, new Date().toFormat("MM"))
.replace(/\$SECOND/i, new Date().toFormat("SS"))
.replace(/\$WEATHER/i, forecast.weather[0].wamei);
}
}
module.exports=Rpi2s;
まだトリガーが一つしかないので寂しいが
これから数珠つなぎにいろんなトリガーを
くっつけていってrπ+2sを賢くしてみたい。
const Rpi2s=require('./Rpi+2s');
var rpi2s=new Rpi2s();
rpi2s.howling();
このファイルをrpi2s.jsみたいなファイル名で保存して
foreverに渡せばいいのかな?
ちょっとまだ良くわかってないから調べてみよう。
例えば、こんなJSONファイルを用意する。
[
{"no": "1",
"name": "good morning",
"freq": "h",
"date": "",
"time": "07:00",
"message": "$MONTH月$DAY日$HOUR時です。天気は$WEATHERです。おはようございます。"}
]
ってファイルを作ってみる。
rpi2s.jsを実行してみると
8がつ19にち7じです。 てんきはあめです。 おはようございます。
なんて朝の情報番組のキャスターみたいなことを言ってくれる。
Next
さて次は、やっとラズパイっぽいことを
してみようかな?
センサをトリガーにして
なんかしゃべらせるってやつ。