RSSを読み込んで自動的に定期ツイートしてくれるDockerImageを作って公開した。

はじめに

RSS定期ツイートをiftttで実装してやっていたのですが、制限が厳しかったため自作しました。

完成品

https://hub.docker.com/r/hashito/tweetrss

docker run -it --rm \  
    -e RSS_URL=https://news.google.com/rss/search?hl=ja&gl=JP&ceid=JP:ja&q=twitter \  
    -e CONSUMER_KEY=x \  
    -e CONSUMER_SECRET=x \  
    -e ACCESS_TOKEN=x \  
    -e ACCESS_TOKEN_SECRET=x \  
    -e 'ADD_TEXT="#rss2tweet"' \  
    hashito/tweetrss  
  

上から

  • RSSのURL
  • TwitterのCONSUMER_KEY
  • TwitterのCONSUMER_SECRET
  • TwitterのACCESS_TOKEN
  • TwitterのACCESS_TOKEN_SECRET
  • 追加されるテキスト

を入れれば出来ます。
Tweet周期を変更したい場合はSCAN_SPANTWEET_DELAYをいじってください。(両方とも秒です)
あんまりツイートすると怒られるので注意…

あと、内部でcash値を持っていて再構築すると前回どこまでRSSを読んだか忘れてしまいます。

下記のようなファイルを作ってマウントさせてあげましょう。

{}  

-v {you chash file}:/root/cash

(この辺ダサいですね…いつか直します)

ソースコード

ソースは単純です。
定期的にRSSを読み出し、Tweetする。
RSSはIDを保持し同一IDは対象としない、一定期間同一IDの受信なければID破棄という感じ。

import feedparser  
import json  
import os  
import time  
import datetime  
import json  
from requests_oauthlib import OAuth1Session  
  
RSS_URL=os.environ["RSS_URL"]  
CONSUMER_KEY=os.environ["CONSUMER_KEY"]  
CONSUMER_SECRET=os.environ["CONSUMER_SECRET"]  
ACCESS_TOKEN=os.environ["ACCESS_TOKEN"]  
ACCESS_TOKEN_SECRET=os.environ["ACCESS_TOKEN_SECRET"]  
  
CASH_FILE=os.environ["CASH_FILE"]  
ADD_TEXT=os.environ["ADD_TEXT"]  
SCAN_SPAN=int(os.environ["SCAN_SPAN"])  
TWEET_DELAY=int(os.environ["TWEET_DELAY"])  
  
  
def cash_read():  
    with open(CASH_FILE) as f:  
        return json.loads(f.read())  
  
def cash_write(data):  
    with open(CASH_FILE,mode="w") as f:  
        f.write(json.dumps(data,ensure_ascii=False))  
  
def read_rss():  
    return feedparser.parse(RSS_URL)  
      
def send_tweet(text):  
    twitter = OAuth1Session(CONSUMER_KEY, CONSUMER_SECRET, ACCESS_TOKEN, ACCESS_TOKEN_SECRET) #認証処理  
    url = "https://api.twitter.com/1.1/statuses/update.json" #ツイートポストエンドポイント  
    params = {"status" : text}  
    res = twitter.post(url, params = params) #post送信  
    return res.status_code == 200  
  
if(__name__ == '__main__'):  
    cash=cash_read()  
    print("cash:",cash)  
    while(1):  
        tm=datetime.datetime.now().timestamp()  
        rss=read_rss()  
        for i in rss["entries"]:  
            if(not i["id"] in cash):  
                print("tweet:",f"{i['title']} {i['link']} {ADD_TEXT}")  
                send_tweet(f"{i['title']} {i['link']} {ADD_TEXT}")  
                time.sleep(TWEET_DELAY)  
            cash[i["id"]]=tm  
            cash_write(cash)  
        for k in list(cash.keys()):  
            if((cash[k]+60)<tm):  
                print("delete:",k,cash[k],tm)  
                cash.pop(k)  
        time.sleep(SCAN_SPAN)  

docker file

こちらも単純ですね。
python3のイメージを使ってrequirements.txtをインストール。
環境変数を準備して、起動してあげる感じです。

    
FROM python:3  
COPY . /root/  
  
RUN pip install -r /root/requirements.txt  
  
ENV RSS_URL=https://news.google.com/rss/search?hl=ja&gl=JP&ceid=JP:ja&q=twitter \  
    CONSUMER_KEY=x \  
    CONSUMER_SECRET=x \  
    ACCESS_TOKEN=x \  
    ACCESS_TOKEN_SECRET=x \  
    CASH_FILE=/root/cash \  
    ADD_TEXT="#rss2tweet" \  
    SCAN_SPAN=600 \  
    TWEET_DELAY=150  
  
CMD ["python","/root/main.py"]  

Riot.jsで蛇のゲーム

背景

個人的にSVGに興味が出てきた+ゲームを作りたかったので作ってみた。

完成したものは下記
http://hasito.com/snake/

仕様

  • 2Dゲーム
  • 蛇が餌を食べると長くなる
  • 壁/体にあたったら死ぬ
  • 少しづつ動きが早くなる
  • キーボードで操作

    こんな感じ…

開発

枠を組む

SVGで下記のように書くと格子状にブロックを配置できる様子

    <svg width="30" height="60">  
      <g transform={('translate(0,0)')}>  
        <rect x="0" y="0" width="15" height="15" title=""  fill="#000000"></rect>  
        <rect x="0" y="15" width="15" height="15" title=""  fill="#000000"></rect>  
        <rect x="0" y="30" width="15" height="15" title=""  fill="#000000"></rect>  
        <rect x="0" y="45" width="15" height="15" title=""  fill="#000000"></rect>  
      </g>  
      <g transform={('translate(15,0)')}>  
        <rect x="0" y="0" width="15" height="15" title=""  fill="#000000"></rect>  
        <rect x="0" y="15" width="15" height="15" title=""  fill="#000000"></rect>  
        <rect x="0" y="30" width="15" height="15" title=""  fill="#000000"></rect>  
        <rect x="0" y="45" width="15" height="15" title=""  fill="#000000"></rect>  
      </g>  
    </svg>  

これをriot.jsで記載

    <svg riot-width={ w*15 } riot-height={ h*15 } >  
      <g each={d,i in view} transform={('translate('+(i*15)+',0)')}>  
        <rect each={d2,i2 in d} x="0" riot-y={i2*15} data-point='{(JSON.stringify({x:i,y:i2}))}' width="15" height="15" title=""  fill="#{('000000'+d2.toString(16)).slice(-6)}"></rect>  
      </g>  
    </svg>  

注意点

  • viewには多次元配列が入っている予定
  • fillは16進指定。6桁必要なので0で頭をうめています。
  • xやらwidthは頭にriot-をつける必要あり

全体の流れ

全体の処理の流れとしては下記な感じを想定して作りました。

  1. 初期化処理
  2. メイン処理
  3. 蛇の死
  4. 初期化処理(2回目)
  5. メイン処理(2回目)
    …こんな感じ

初期化処理

    /* タイマーセット関数(一定ごとに時を進める用)  
    */  
    var tm_set=()=>{  
      if(self.tme)clearInterval(self.tme);  
      self.tme=setInterval(()=>{  
        self.tm-=1;  
        tm_set2(self.tm);  
      },1000);//とりあえず1秒単位で更新固定  
    };  
    /* タイマーセット関数(メイン処理用)  
    */  
    var tm_set2=(v)=>{  
      if(self.tme2)clearInterval(self.tme2);  
      self.tme2=setInterval(()=>{  
        _main_();  
      },v);  
    };  
    /* 初期化処理  
    */  
    var _init_=()=>{  
// 変数の初期化  
      self.st=1;//ゲームステータス  
      self.w=40;//横の長さ  
      self.h=40;//縦の長さ  
      self.view=new Array(self.h);//表示用変数初期化  
      for(i=0;i<self.h;i++){  
        self.view[i] = new Array(self.w);  
        self.view[i].fill(0);  
      }  
      self.map=new Array(self.h);//マップ変数の初期化(今は餌しか置かれない…)  
      for(i=0;i<self.h;i++){  
        self.map[i] = new Array(self.w);  
        self.map[i].fill(0);  
      }  
      self.snake={//蛇の初期化  
        hed:{x:Math.floor(self.w/2),y:Math.floor(self.h/2)},//頭の位置  
        body:[],//体  
        point:0,//ポイント  
        move:{x:1,y:0}//移動属性(キーダウンイベントで変化)  
      }  
      self.tm=300;//時間更新間隔(初期値)  
//キーダウンイベント  
      document.onkeydown = function (e){  
        var k = e.keyCode;  
        var s = self.snake;  
        console.log(k)  
        if(k==37)s.move={x: 0,y:-1};//左  
        if(k==38)s.move={x:-1,y: 0};//上  
        if(k==39)s.move={x: 0,y: 1};//右  
        if(k==40)s.move={x: 1,y: 0};//下  
        if(k==32)self.st=2;//game開始  
      };  
//タイマー設定  
      tm_set();  
      console.log("_init_");  
    }  

移動処理

  
    /* run処理  
    */  
    var _run_=()=>{  
      var s = self.snake;  
      // --move-- 移動処理  
      // body  
      s.body.unshift(JSON.parse(JSON.stringify(s.hed)));  
      s.body.pop();  
      // hed  
      s.hed.x += s.move.x;  
      s.hed.y += s.move.y;  
      // --dead-- 死亡判定  
      var b1 = (s.hed.x<self.w);  
      var b2 = (s.hed.y<self.h);  
      var b3 = (s.hed.x>=0);  
      var b4 = (s.hed.y>=0);  
      var b5 = (s.body.filter((v)=>{return (s.hed.x==v.x)&&(s.hed.y==v.y);}).length==0);  
      if(!(b1&&b2&&b3&&b4&&b5)){  
        return false;  
      }  
      // --eat-- お食事判定  
      if(self.map[s.hed.y][s.hed.x]=="food"){  
        if(s.body.length==0){  
          s.body.push(JSON.parse(JSON.stringify(s.hed)));  
        }else{  
          s.body.push(JSON.parse(JSON.stringify(s.body[s.body.length-1])));  
        }  
        s.point+=1/self.tm;  
      }  
      self.map[s.hed.y][s.hed.x]=0;  
      // --food-- 餌をRANDOMに置く処理  
      var foody=Math.floor(Math.random()*self.h);  
      var foodx=Math.floor(Math.random()*self.w);  
      var fb1=(s.body.filter((v)=>{return (foodx==v.x)==(foody==v.y);}).length==0);  
      var fb2=(!((s.hed.x==foodx)&&(s.hed.y==foody)));  
      if(fb1&&fb1){  
        self.map[foody][foodx]="food";  
      }  
      // --view-- 表示を更新する処理  
      for(i=0;i<self.h;i++){self.view[i].fill(0);}  
      self.view[s.hed.y][s.hed.x]=0x0000ff;  
      s.body.forEach((v)=>{  
        self.view[v.y][v.x]=0x5555ff;  
      });  
      self.map.forEach((y,yi)=>{  
        y.forEach((x,xi)=>{  
          if(x=="food"){  
            self.view[yi][xi]=0xff00ff;  
          }  
        });          
      })  
        
      return true;  
    }  
  
  

メイン処理

ほぼ、切り替えしかしてないけど…

    /* main  
    */  
    var _main_=()=>{  
      if(self.st==2){//st:0>初期 1>停止 2>ゲーム中  
        if(!_run_()){  
          _init_();  
        }  
        self.update();  
      }  
    };  

全体コード

index.html

<html>  
  <head>  
    <title>Hello Riot.</title>  
    <meta charset="UTF-8"/>  
  </head>  
  <body>  
    <sample></sample>  
    <script type="riot/tag" src="sample.tag"></script>  
   <!-- <script src="https://cdn.jsdelivr.net/npm/riot@3.9/riot+compiler.min.js"></script>  -->  
   <script src="riot/riot+compiler.min.js"></script>  
    <script>riot.mount('sample')</script>  
  </body>  
</html>  

sample.tag

<sample>  
    <h1>蛇のやつ</h1>  
    <h3>スペースキーで開始してください</h3>  
    <svg riot-width={ w*15 } riot-height={ h*15 } >  
      <g each={d,i in view} transform={('translate('+(i*15)+',0)')}>  
        <rect each={d2,i2 in d} x="0" riot-y={i2*15} data-point='{(JSON.stringify({x:i,y:i2}))}' width="15" height="15" title=""  fill="#{('000000'+d2.toString(16)).slice(-6)}"></rect>  
      </g>  
    </svg>  
    <h3>ポイント{snake.point}点</h3>  
    <h3>体の長さ{snake.body.length}ブロック</h3>  
    <h3>頭の位置{snake.hed.x}:{snake.hed.y}</h3>  
  <script>  
    var self=this;  
    self.st=0;  
    self.tme=false;  
    self.tme2=false;  
    /* タイマーセット関数(一定ごとに時を進める用)  
    */  
    var tm_set=()=>{  
      if(self.tme)clearInterval(self.tme);  
      self.tme=setInterval(()=>{  
        self.tm-=1;  
        tm_set2(self.tm);  
      },1000);  
    };  
    /* タイマーセット関数(メイン処理用)  
    */  
    var tm_set2=(v)=>{  
      if(self.tme2)clearInterval(self.tme2);  
      self.tme2=setInterval(()=>{  
        _main_();  
      },v);  
    };  
    /* main  
    */  
    var _main_=()=>{  
      if(self.st==2){  
        if(!_run_()){  
          _init_();  
        }  
        self.update();  
      }  
    };  
    /* run処理  
    */  
    var _run_=()=>{  
      var s = self.snake;  
      // --move--  
      // body  
      s.body.unshift(JSON.parse(JSON.stringify(s.hed)));  
      s.body.pop();  
      // hed  
      s.hed.x += s.move.x;  
      s.hed.y += s.move.y;  
      // --dead--  
      var b1 = (s.hed.x<self.w);  
      var b2 = (s.hed.y<self.h);  
      var b3 = (s.hed.x>=0);  
      var b4 = (s.hed.y>=0);  
      var b5 = (s.body.filter((v)=>{return (s.hed.x==v.x)&&(s.hed.y==v.y);}).length==0);  
      if(!(b1&&b2&&b3&&b4&&b5)){  
        return false;  
      }  
      // --eat--  
      if(self.map[s.hed.y][s.hed.x]=="food"){  
        if(s.body.length==0){  
          s.body.push(JSON.parse(JSON.stringify(s.hed)));  
        }else{  
          s.body.push(JSON.parse(JSON.stringify(s.body[s.body.length-1])));  
        }  
        s.point+=1/self.tm;  
      }  
      self.map[s.hed.y][s.hed.x]=0;  
      // --food--  
      var foody=Math.floor(Math.random()*self.h);  
      var foodx=Math.floor(Math.random()*self.w);  
      var fb1=(s.body.filter((v)=>{return (foodx==v.x)==(foody==v.y);}).length==0);  
      var fb2=(!((s.hed.x==foodx)&&(s.hed.y==foody)));  
      if(fb1&&fb1){  
        self.map[foody][foodx]="food";  
      }  
      // --view--  
      for(i=0;i<self.h;i++){self.view[i].fill(0);}  
      self.view[s.hed.y][s.hed.x]=0x0000ff;  
      s.body.forEach((v)=>{  
        self.view[v.y][v.x]=0x5555ff;  
      });  
      self.map.forEach((y,yi)=>{  
        y.forEach((x,xi)=>{  
          if(x=="food"){  
            self.view[yi][xi]=0xff00ff;  
          }  
        });          
      })  
        
      return true;  
    }  
    /* 初期化処理  
    */  
    var _init_=()=>{  
      self.st=1;  
      self.w=40;  
      self.h=40;  
      self.view=new Array(self.h);  
      for(i=0;i<self.h;i++){  
        self.view[i] = new Array(self.w);  
        self.view[i].fill(0);  
      }  
      self.map=new Array(self.h);  
      for(i=0;i<self.h;i++){  
        self.map[i] = new Array(self.w);  
        self.map[i].fill(0);  
      }  
      self.snake={  
        hed:{x:Math.floor(self.w/2),y:Math.floor(self.h/2)},  
        body:[],  
        point:0,  
        move:{x:1,y:0}  
      }  
      self.tm=300;  
      document.onkeydown = function (e){  
        var k = e.keyCode;  
        var s = self.snake;  
        console.log(k)  
        if(k==37)s.move={x: 0,y:-1};//左  
        if(k==38)s.move={x:-1,y: 0};//上  
        if(k==39)s.move={x: 0,y: 1};//右  
        if(k==40)s.move={x: 1,y: 0};//下  
        if(k==32)self.st=2;//game開始  
      };  
      tm_set();  
      console.log("_init_");  
    }  
    /* マウント前イベント  
    */  
    self.on("before-mount",(v)=>{  
      _init_();  
    });  
  </script>  
</sample>  

残課題

  • 死んだら死んだとか出てほしいけど出ない
  • ポイントが後半に食った餌ほど線形に高い雑仕様
  • 死んだら得たポイント見られない
  • 毎更新に餌がおかれるため、後半めっちゃ出る

Riot.jsでの注意

概要

自分的なメモついでに注意点をメモしておきます。

内容

class定義出来ない

課題

tag内でクラス定義しようとすると

<test>  
    <h1>{text}</h1>  
  <script>  
    var self=this;  
    class Hello{  
      constructor(v){  
        this.v = v  
      }  
      get(){  
        return v;  
      }  
    }  
    var h = new Hello("world")  
    self.text=h.get();  
  </script>  
</test>  
  

こんな感じで怒られます。

Uncaught SyntaxError: Unexpected token .  
    at jn (riot+compiler.min.js:2)  
    at o (riot+compiler.min.js:2)  
>      this.constructor = function(v){  

初期化関数のconstructorをriotの関数として処理している感

対策

定義を下記のように変更すると怒られません。

<test>  
    <h1>{text}</h1>  
  <script>  
    var self=this;  
    var Hello=(v)=>{  
        
      var _v = v  
      return {  
        get:()=>{  
          return _v;  
        }  
      }  
    }  
    var h = Hello("world")  
    self.text=h.get();  
  </script>  
</test>  
  

思ったように初期化されない(多分、javaScriptの仕様)

課題

下記のように変数定義すると、エラーも出ずに結果がこうなる。

var t=[[[1,2][3,4]],[[5,6][7,8]]];  
console.log(t);  
// --下記表示結果--  
// (2) [Array(1), Array(1)]  
//  >0:[undefined]  
//  >1:[undefined]  
//  >length:2  
//  >__proto__:Array(0)  

個人的にはエラーが出てほしい…

対策

ご存知の通り、下記のように,を追加すれば望んだように…
そもそもエラーが出てほしいのだけど…

var t=[[[1,2],[3,4]],[[5,6],[7,8]]];  

実はエラーではないというお話

@karak 様からコメントが有ったので追記させていただきます。
実は、javascriptの,の仕様によりエラー文ではなかったようです。

// この定義は  
var t1=[[[1,2][3,4]],[[5,6][7,8]]];  
// (2) [Array(1), Array(1)]  
//  >0:[undefined]  
//  >1:[undefined]  
//  >length:2  
  
// これと同じ  
var a = [1, 2];  
var b = [5, 6];  
var t2 = [a[4], b[8]];  
// (2) [Array(1), Array(1)]  
//  >0:[undefined]  
//  >1:[undefined]  
//  >length:2   

つまり,で区切られた手前側が参照するindexの値として処理されてしまっていたようです…
ということで、下記のような内容だとundefinedではなくなる。

var t=[[[1,2][3,0]],[[5,6][7,1]]];  
// (2) [1,6]  

非常に勉強になりました…
コメントありがとうございました!

Firebaseでゲームにランキング機能を付ける

概要

個人的にFirebaseを勉強中。
ラフでも良いので成果物を作りたくて…

大昔に作った蛇のゲームにランキング機能をつけてみました。

結論

少し雑ですが下記のようになりました。
https://hashito.biz/snake/

スクリーンショット 2019-10-12 23.14.48.png

##仕組み

  • ホームページ自体をFirebase Hostingに移動
  • ユーザはログインをしなくてもランキング情報を参照できる(参照のみ)
  • ログインしてゲームを行うと得点情報名前情報Firestoreに保存
    (このときの書き込み権限はユーザ毎にしか付与していません。)
  • ランキング情報はFirestore並び替えリミットで作成

実装

ここではゲーム自体ではなくランキング機能部分を重点的に説明していきます。
前提としてFirebaseの初期設定やHostingなどの解説は飛ばしています。
ご了承ください…

1.データ定義

今回はシンプルに名前と得点だけです。
こちらはFirestoreのUIにて操作して作成しました。
スクリーンショット 2019-10-12 23.25.19.png

基本的に、Firestoreはno sql形式で保存されているらしく…
一番端のコレクションというのがtable定義に近く、その中にドキュメントというkeyを持つフィールドが複数持てる仕組みなっています。
今回、私はsnake_usersというコレクションを定義し、ドキュメントにはユーザ固有IDを利用するようにしています。

こうしているのは次のルールの定義で「自IDと同じドキュメント(key)には書き込みできる!」と定義させるためです。

2.ルール定義

Firestoreではデータアクセスについてのルールを定義する事ができます。
これを利用すると「全員が参照できる!」とか「この人しか編集できない!」などが詳しく設定できます。

今回の定義はこんな感じです。
少し読めば分かると思うのですが…前提として全員不許可らしいです。
次に誰を許可するかと言うのを定義していく感じになります。

rules_version = '2';  
service cloud.firestore {  
  match /databases/{database}/documents {  
   match /snake_users/{userID} {  
      allow read: if true;     
      allow write: if request.auth.uid == userID;  
   }  
  }  
}  

このルールでは注目するべきは真ん中の二行ですね。

      allow read: if true;     
      allow write: if request.auth.uid == userID;  

一行目:誰でもすべてのデータを見ても良い
二行目:ログインしているユーザのみデータを加工して良い

ということです。
前準備はここまでで、コーディングになります。

3.コーディング

3.1.ログイン

今回、私はgoogle連携による認証のみを利用しています。
ですので、ボタンクリックなどに下記のコードを実行させるだけで実現できます

var provider = new firebase.auth.GoogleAuthProvider();  
firebase.auth().signInWithPopup(provider).then(function(result) {  
}).catch(function(error) {  
});  

3.2.ログイン状態の確認

javascriptなどの実行時に認証済みのユーザかを確認する必要があると思います。
その場合は下記のようなコードで確認することが出来ます。

firebase.auth().onAuthStateChanged(function(user) {  
    if (user) {  
        console.log("login")  
    }else{  
        console.log("no login")  
    }  
});  

3.3.データの参照

今回、ランキング情報を参照するのにFirestoreに書き込まれたデータを参照しています。
下記のような方法でこれを実現しています。

var db = firebase.firestore();  
var cnf = db.collection("snake_users")  
cnf.orderBy('point').limit(10).get()  
    .then(function(q) {  
    self.ranking=[]  
    var cnt = q.docs.length  
    q.forEach(function(doc) {  
        var data=doc.data();  
        data.rank = cnt--  
        self.ranking.unshift(data)  
    });  
})  
.catch(function(error) {  
    console.log("Error getting documents: ", error);  
});  

orderBy('point')で並び替えを行い、limit(10)で件数を制限してランキング情報を作成しているのが分かると思います。

3.4.データの登録

最後は登録です。
先程定義したルールでログインユーザーのみしか書き込みが出来ないため、「①ログインを確認」し、「②ログインユーザーIDでデータ書き込み」という手順でデータを書き込んでいます。

firebase.auth().onAuthStateChanged(function(user) {  
if (user) {  
    var cnf = db.collection("snake_users").doc(user.uid).set({  
    name  : self.username,  
    point : self.snake.point,  
    }).then(function(docRef) {  
    console.log("Document written with ID: ", docRef);  
    self.get_ranking()  
    }).catch(function(error) {  
    console.error("Error adding document: ", error);  
    });  
}else{  
    console.log("no login")  
}  
});  

4.最後に少しだけ詰まった点

少しだけ詰まったのですが、Firebase Authを利用する場合に該当のドメインとサービス(今回はGoogle)を有効にする必要があります。
ご注意を…

スクリーンショット 2019-10-12 23.53.44.png スクリーンショット 2019-10-12 23.53.38.png

また、FirestoreなどはGCP(google cloud platform)と関連性が強いサービスなのでGCP側の設定が必要があるのでご注意を…

感想

Firebaseはかなり強力に感じました!
サーバーレスでここまで出来るんですから、色々と利用できそう…

crontabではbinshで実行される

背景

朝からめちゃくちゃ詰まったのでここに備忘録

内容

問題

下記のようなスクリプトをcronで実行しようとしたところ。

while true; do  
    pin17bffn=/var/tmp/pin17  
    pin17=$(gpio -g read 17)  
    pin17bf=0  
  
    if [[ -e $pin17bffn ]]; then  
        pin17bf=$(cat $pin17bffn)  
    fi  
  
    if [[ $pin17 -ne $pin17bf ]]; then  
        curl -s -XPOST -d "$pin17" https://xxxx  
    fi  
  
    echo $pin17>$pin17bffn  
  
    sleep 1  
done  
  
@reboot nohup /home/pi/door.sh  &  

うまく動かない…

よく見るとログが出力されていました。

/home/pi/checkpin2.s: 10: /home/pi/checkpin2.s: [[: not found  
/home/pi/checkpin2.s: 6: /home/pi/checkpin2.s: [[: not found  
/home/pi/checkpin2.s: 10: /home/pi/checkpin2.s: [[: not found  
/home/pi/checkpin2.s: 6: /home/pi/checkpin2.s: [[: not found  
/home/pi/checkpin2.s: 10: /home/pi/checkpin2.s: [[: not found  
/home/pi/checkpin2.s: 6: /home/pi/checkpin2.s: [[: not found  
/home/pi/checkpin2.s: 10: /home/pi/checkpin2.s: [[: not found  
/home/pi/checkpin2.s: 6: /home/pi/checkpin2.s: [[: not found  
/home/pi/checkpin2.s: 10: /home/pi/checkpin2.s: [[: not found  

解決

どうやらシェルスクリプトがうまく解釈されていない?
このErrorで調べたところ

String comparison in bash. [[: not found
https://stackoverflow.com/questions/12230690/string-comparison-in-bash-not-found

そのまんまなページを発見
中身を読むと/bin/bashで実行されていないことが原因

では実際に何で実行されている…?

  
echo "$SHELL"  
>/bin/bash  
  
crontab -e  
>*/1 * * * * echo "$SHELL">/var/tmp/shell.log  
  
cat /var/tmp/shell.log   
>/bin/sh  

ということで、下記のコードをソースに追加して解決

#!/bin/bash  
...  

CSSでのvar + calcでの計算方法

背景

css 内で変数定義して計算をしたかったので動作などを確認した。
備忘録的な…
※Chromeでしか動作確認しておりません

はじめに

cssでも変数定義して計算ができる。
下記のような感じ。

変数定義と呼び出し

:root {  
  --w: 30px;/*変数定義*/  
}  
.c{  
  width:var(--w);/*呼び出し*/  
}  

計算

.c{  
  width:calc(100% - 10px);  
}  

実施

この2つを利用すれば変数計算で行けるはず!

:root {  
  --p1: 10px;/*変数定義*/  
  --n1: 10;/*変数定義*/  
}  
.c{  
  width:calc(var(--p1));        /*var:10px        ok*/  
  width:calc(var(--p1) * 10);   /*var:10px * 10   ok*/  
  width:calc(var(--p1) * 10px); /*var:10px * 10px ng*/  
  width:calc(var(--p1) + 10px); /*var:10px + 10px ok*/  
    
  width:calc(var(--n1));        /*var:10          ng*/  
  width:calc(var(--n1) * 10);   /*var:10   * 10   ng*/  
  width:calc(var(--n1) * 10px); /*var:10   * 10px ok*/  
  width:calc(var(--n1) + 10px); /*var:10   + 10px ng*/  
  
  width:calc(var(--n1) * var(--p1));           /*var:10 * var:10px ok*/  
  width:calc(var(--n1)*1px + var(--p1));       /*(var:10 * 1px) + var:10px ok*/  
  width:calc(calc(var(--n1)*1px) + var(--p1)); /*(var:10 * 1px) + var:10px ok*/  
  width:calc(var(--n1)*1px + var(--n1)*1px);             /*(var:10 * 1px) + (var:10 * 1px) ok*/  
  width:calc(calc(var(--n1)*1px) + calc(var(--n1)*1px)); /*(var:10 * 1px) + (var:10 * 1px) ok*/  
  
  width:calc(10%-10px);         /*    10% - 10px  ng*/  
  width:calc(10%+10px);         /*    10% - 10px  ng*/  
  width:calc(10px + 10px);      /*    10px + 10px ok*/  
  width:calc(10% + 10px);       /*    10% + 10px  ok*/  
  width:calc(10% - 10px);       /*    10% - 10px  ok*/  
}  

結論

  1. +,-をするときは前後にスペースが無いと上手く動かない
  2. pxなどの単位がなければ計算できない場合がある(+,-など)
  3. 計算はcalcで囲わなくてもOK

個人的にでめっちゃ詰まった…

Dockerで2台のコンテナを立ち上げてPythonで作成したWEB APIでやり取りさせる

はじめに

Pythonで作成したWEB APIを使って2台のサーバでやり取りさせるテストがややこしかったので、Dockerで色々試してみた、備忘録です。

環境

私の環境はこんな感じ
スクリーンショット 2019-06-23 23.08.33.png

スクリーンショット 2019-06-23 23.09.36.png

Editor

宗教戦争になる可能性もあるため、こちらに分けておきます…
私はVS Codeで下記のプラグインを利用しています。
スクリーンショット 2019-06-23 23.11.27.png

Visual Studio Code Remote Development

docker環境にVS Codeを接続して開発できるやつ
※ターミナルはうまく動かなくて使いにくい
https://github.com/Microsoft/vscode-remote-release
attach file.mov.gif

Docker Support for Visual Studio Code

左側にdockerの状態を表示できるやつ
https://github.com/microsoft/vscode-docker
attach.mov.gif

(1)docker

Dockerにnetworkを作成します。
これを作成してコンテナを接続することによりDocker内のコンテナ同士でIPによるやり取りを行えます。

docker network create localnetwork  

次に対象となるコンテナを作成します。
イメージは何でもいいのですが今回はubuntuでやってみたいと思います。

docker run -it --net="localnetwork" ubuntu  

このコマンドでlocalnetworkに接続されたコンテナが1つ生成されます。

(2)container in python (Server側)

このコンテナ内で下記のコマンド実行しpython環境を構築します。

apt update  
apt install python2.7 python-pip  
apt pip install simplejson  

パイソン環境を確認…

python -V  
>Python 2.7.15+  

パイソンの実行ファイルを作成します。
※コマンドでやる場合はapt install nanoなどでEditorをインストールして行ってください。

  
  
from BaseHTTPServer import BaseHTTPRequestHandler, HTTPServer  
import simplejson  
import SocketServer  
  
class api_server(BaseHTTPRequestHandler):  
    def _set_headers(self):  
        self.send_response(200)  
        self.send_header('Content-type', 'text/html')  
        self.end_headers()  
  
    def do_HEAD(self):  
        self._set_headers()  
  
    def do_POST(self):  
        self._set_headers()  
        print "in post method"  
        self.content_len = int(self.headers.getheader('content-length', 0))  
        print self.content_len  
        self.data_string = self.rfile.read(self.content_len)  
        print self.data_string  
        data=False  
        if(self.content_len>0):  
            data = simplejson.loads(self.data_string)  
        print self.path  
        self.send_response(200)  
        self.end_headers()  
        self.wfile.write(simplejson.dumps({"res":"true"}))  
  
        return  
  
httpd = HTTPServer(('', 80),api_server)  
httpd.serve_forever()  
  

自分のIPの確認

hostname -i  
>172.19.0.3  

サーバーを実行します。

python /home/s.py  

(3)container in python (client側)

まずは、新しいコンテナの作成…

docker run -it --net="localnetwork" ubuntu  

今回は簡単にcurlを使って接続確認をしたいと思いますのでcurlをダウンロードします。

apt update  
apt install curl  

curlにて動作確認

  
curl -X POST http://172.19.0.3  

メモ

以下、すこし詰まった部分で備忘録の中の備忘録として残しておきます。

つながらないとき

多分、コンテナがネットワークに接続できていない場合です。
まず、該当のネットワークの状態を確認します。

docker network inspect {ネットワーク名}  
>このコマンドの応答JSON内に接続したいコンテナ名があるか確認  

コンテナ名がない場合はうまく接続できていません。
下記のコマンドを利用し、ネットワーク接続をしてください。

network connect {ネットワーク名} {コンテナ名}  

疎通確認したいとき(ping install)

apt-get install iputils-ping net-tools  

ipconfigしたいとき(net-tools install)

apt-get install net-tools  

圧電スピーカー使ってみた

概要

圧電スピーカーを購入したので試してみました。
振動などを検知するセンサーです。

部品

圧電スピーカー:セラミック圧電振動センサーモジュール
84円
image.png

表示機:TM1637が組み込まれた7セグLED
73円
image.png

コンピュータ:Arduino UNO互換機
699円
img.png

#配線
スクリーンショット 2020-03-24 6.04.51.png

#ソースコード

アナログ信号を受け取り、10回の平均を表示するようにしています。

  
#include <Arduino.h>  
#include <Wire.h>  
#include <TM1637Display.h>  
#define SERIAL_BAUD 115200  
  
#define CLK 2  
#define DIO 3  
TM1637Display display(CLK, DIO);  
#define PIEZOELECTRIC_LEN 10  
int Piezoelectrics[PIEZOELECTRIC_LEN] = {0,0,0,0,0,0,0,0,0,0};  
int Piezoelectrics_idx = 0;  
  
void setup() {  
  Serial.begin(SERIAL_BAUD);  
  while(!Serial) {}  
  
  uint8_t data[] = { 0xff, 0xff, 0xff, 0xff };  
  display.setBrightness(0x0f);  
  display.setSegments(data);  
  delay(1000);  
}  
  
void loop() {  
  int ave = 0;  
  int Piezoelectric;  
  Piezoelectrics_idx++;  
  if(! (PIEZOELECTRIC_LEN > Piezoelectrics_idx) )  
    Piezoelectrics_idx = 0;  
  Piezoelectrics[Piezoelectrics_idx] = analogRead(A0);  
  for(int i=0 ; i<PIEZOELECTRIC_LEN;i++){  
    ave+=Piezoelectrics[i];  
  }  
  Piezoelectric = ave/PIEZOELECTRIC_LEN;  
  
  display.showNumberDec(Piezoelectric, false);   
  Serial.print("Piezoelectric: ");  
  Serial.print(Piezoelectric);  
  delay(100);  
}  

github

動作確認

gif動画で上げても分かりにくかったので、Youtubeにて確認いただけると幸いです。
IMAGE ALT TEXT HERE

vue+firebaseにて詰まった部分 v1.2.0

初めに

こちらの記事を元に色々と試していきます。

「Vue.js + Firebase を使って爆速でユーザ認証を実装する」
https://qiita.com/sin_tanaka/items/ea149a33bd9e4b388241

個人的に色々と細かい部分で詰まるためここに書き記す。

問題

コピペしたのにうまく動かない

vueではjavascriptやhtmlより記載方法が厳しいらしく、怒られる事がよくある。
とりあえず下記の二種類が発生。

最後に空の改行コードが必要

javascript部分で、最後に改行コードが無いと怒られる。
追加すると解決します。

空白を入れるなと怒られる

html部分で、空白が入っているのを怒られる。
削除すると解決します。

色々細かいエラーの根本(ESLintが有効)

怒られるのはVue.js環境構築時にESLintを有効にしてしまっているためだと思われます!
※[ESLint]はコーディング規則を細かく注意してくれる静的解析ツールのようです。(スペースはこう書いたほうが綺麗だよ!みたいな話を注意してくれます)

? Project name test  
? Project description A Vue.js project  
? Author hashito <xxxxx@gmail.com>  
? Vue build standalone  
? Install vue-router? Yes  
? Use ESLint to lint your code? (Y/n) <-これをnoに!  

初回、環境構築時に注意してください!

firebaseConfigの取得

当初、各ページからコピペしていたが、そもそも下記のような手順で手に入れるものらしい。
・FirebaseでJavaScript プロジェクトを作成
・作成するとコピペページが出てくる

参考:
https://firebase.google.com/docs/web/setup?authuser=0

firebaseConfigの場所

この情報はコンパイル時にコンパイルしてしまったり、サーバサイドで保持するものなのか不安でした。

export const firebaseConfig = {  
    apiKey: "xxxxx",  
    authDomain: "xxxx",  
    databaseURL: "https://xxxx.firebaseio.com",  
    projectId: "xxxx",  
    storageBucket: "xxxx.appspot.com",  
    messagingSenderId: "xxxx",  
    appId: "1:xxxxxxxxxxx"  
  };  

https://fir-ui-demo-84a6c.firebaseapp.com/
こちらのソースコードをみるとconfig.jsも公開しているようなので大丈夫な様子。

複数回firebaseをimportするとうまく動作しない

色々と作っていくとWEB画面が真っ白になって、うまく動作しないことがある。
consoleを確認すると下記のようなエラーが出ている。

Uncaught FirebaseError: Firebase: Firebase App named '[DEFAULT]' already exists (app/duplicate-app).  
  

原因は複数回initializeAppを呼び出している事らしい。
下記のような中間ファイルを作ってこちらを毎回呼び出せば解決します。

import * as firebase from "firebase";  
import "firebase/firestore"  
import {firebaseConfig} from '../config.jsx';  
!firebase.apps.length?firebase.initializeApp(firebaseConfig):firebase.app();  
export default firebase  
import firebase from '../firebase'  

権限の問題でFirestoreにアクセスできない

ブラウザ上で、DBへの書き込み・読み込みのテストをすると、失敗します。
consoleを確認すると下記のようなエラーが出ている。

Error adding document: FirebaseError: Missing or insufficient permissions.  
  

原因はFirebaseのdatabaseのアクセスルールが全拒否になっている。
しかし…私は2段階で間違えていたのでこちらも記載しておきます。

(1)Firestoredatabaseでありstorageとは別物

表記の通りで、私は懸命にstorage側の設定を変更していました。
ただしくはdatabaseの設定変更が必要です

スクリーンショット 2019-09-22 16.04.32.png

(2) GCP(GoogleCloudPlatform)と連携している場合にそちら側の有効化が必要

firebasedatabaseにアクセスしてみると分かるのですが、そちらの有効化が必要です。

スクリーンショット 2019-09-22 16.16.39.png

(3) 権限の問題でFirestoreにアクセスできない(本題)

こちらは公式の方でも切々と書いてあるので分かるかと思うのですが。
databaseルールのタブで権限を変更する必要があります。

スクリーンショット 2019-09-22 16.05.35.png

オール許可にすると許可のないユーザでもバシバシ来るっぽいので気をつけてください
各バージョンで変わりそうですが…参考に下記に記載しておきます。

rules_version = '2';  
service cloud.firestore {  
  match /databases/{database}/documents {  
    match /{document=**} {  
      allow read, write: if true;  
    }  
  }  
}  
rules_version = '2';  
service cloud.firestore {  
  match /databases/{database}/documents {  
    match /{document=**} {  
      allow read, write: if true;  
    }  
  }  
}  

参考:Cloud Firestore セキュリティ ルールを使ってみる

vue-cli環境でコンパイルに失敗する

環境構築時にbabelというコンパイルツールを利用していたようです。

こちらのサンプルコードを参照して構築したのですが、ここでコンパイルが通らなくなりました。
参考:Cloud Firestore を使ってみる:データを読み取る

db.collection("users").get().then((querySnapshot) => {  
    querySnapshot.forEach((doc) => {  
        console.log(`${doc.id} => ${doc.data()}`);  
    });  
});  

原因はbabelの環境設定の問題。
下記のツールをnpmでインストールした所、解決しました。

npm install --save core-js@2  
  

懸念点

ユーザ単位での設定値の操作

Googleなどの他サービスへの認証方法

今後の課題…

最後に

追記していきます…