2017-12-28

raspberry pi zero wとダイソーのリモートシャッターとrubyで遊ぶ

このエントリーをブックマークに追加 このエントリーを含むはてなブックマーク
raspberry pi zero wを買ったのは、bluetooth機器とつないで、ちょっと遊んでみたいなぁと思っていたことも一つの理由だったりします。

お手軽に遊ぶ材料として、ダイソーで売っているリモートシャッターがよさげかと思ってみました。以下と同じもののようです。



これを利用した例を以下のサイトを紹介していたので、参考にさせてもらいました。
https://qiita.com/vimyum/items/8b7548ca8cf45383c5b0

こちらでは、bluebuttonというツールを利用しているのですが、ソースは以下にあります。
https://github.com/kinnalru/bluebutton

こちらのソースを見て、ちょっと遊んでみました。
こちらでは、二つのボタンを特に区別してなくて、押したことと長押しを検出していたので、ボタンを識別できるよう改造してみました。

上のIOSボタンは、押すとVolumeUpが送られてきているようです。
下のandoridボタンは、押すとEnterが来て、さらにVolumeUpが送られてきてるようです。

これで識別できそうなので、改造したのは以下のものになります。
rootで実行する必要があります。
これは、二つのボタンとそれぞれの長押しを検出して、さらにLINEに特定のメッセージを送るようにしてみました。

require 'device_input'
require 'rest-client'
require 'json'
class LineBot
TOKEN = "Channel Access Token"
TO = "送信先のID"
def self.send(msg)
headers = {
"Content-Type" => "application/json; charser=UTF-8",
"Authorization" => "Bearer #{TOKEN}",
}
params = {
to: TO,
messages: [
{
type: "text",
text: msg,
}
]
}
RestClient.post "https://api.line.me/v2/bot/message/push", params.to_json, headers
end
end
class Bluebutton
attr_accessor :device
def initialize name
finded = Dir.glob("/sys/**/name").select do |file|
File.read(file).downcase[name.downcase]
end.first
raise "Can't find device info '#{name}' in /sys/**/*" if finded.nil?
@device = "/dev/" + Dir.glob("#{File.dirname(finded)}/**/uevent").map do |file|
File.read(file).split("\n").select{|s| s['DEVNAME']}.compact.first
end.compact.flatten.first.split("=")[1] rescue nil
@msg = {"VolumeUp" => "iOSボタンを押したよ",
"Enter" => "androidボタンを押したよ"}
@long_msg = {"VolumeUp" => "iOSボタンを長く押したよ",
"Enter" => "androidボタンを長く押したよ"}
puts "Device #{name} find at #{@device}"
end
def run
File.open(@device, 'rb' ) do |input|
puts "Connected"
DeviceInput.read_loop(input) do |event|
if event.type == 'EV_KEY'
if event.value > 0
key_down event
else
key_up event
end
end
end
end
end
def key_down event
if @pressed.nil?
@pressed = Time.now
@code = event.code
LineBot.send(@msg[@code])
puts "push #{@code}"
elsif @long.nil? && @pressed < (Time.now - 1)
LineBot.send(@long_msg[@code])
puts "long push #{@code}"
@long = true
end
end
def key_up event
@long = nil
@pressed = nil
@code = nil
end
end
device = "AB Shutter3"
puts "Try to find device #{device}..."
button = Bluebutton.new(device)
puts "Reading events from #{button.device}..."
button.run

とりあえずIoTな気分を感じてみました。


2017-12-26

raspberry pi zero wを買ったのでusb接続でセットアップした

このエントリーをブックマークに追加 このエントリーを含むはてなブックマーク

raspberry pi zero wを買いました。本当は単体で買いたかったのですが、なかなか単体で買えないのと、手ごろな手持ちのSDカードがなかったのと、電源もほどよいのが、ちょうどなかったので、ちょっと割高かなと思いましたが、セットで購入しました。

セットアップはusbでwindowsと接続して行うことができました。
手順は、
https://qiita.com/marron-akanishi/items/2ce4ae07c71dc8358093

https://qiita.com/gpsnmeajp/items/126fadd3e47cdd4a6c00
を見ればよさげです。
無線の設定の部分はraspi-configで設定したぐらいの違いしかないです。

途中でsshでraspberrypi.localと指定して接続するのですが、
うちにはすでに旧型のraspberry piがいて、
すでにraspberrypi.localを使っていたので、旧のほうにつながってしまいます。
新しいの方のを最初から名前を変える方法を探したのですが、
ちょっと見つからなかったので、最初に使っているraspberry piの電源を落として
raspberrypi.localで新しい方につながるようにしました。

その後、以下を参考に名前を変えました。
https://www.1ft-seabass.jp/memo/2015/04/21/raspberry-pi-hostname-memo/
名前を変える際は、無線LAN接続できるようにして、
無線でIPでも接続できるようになったのを確認してから変えたほうが良いです。
最初、上記を参考にせず間で名前を変えて、
無線設定も適当に確認しただけでやったら失敗して、
接続できなくなって最初からやりなおしになったりしたので、ご注意を。

2017-11-03

Rubyで暗号化した文字列をJavaScriptで復号化してみた

このエントリーをブックマークに追加 このエントリーを含むはてなブックマーク
Qiitaにある
JavaScriptで暗号化した文字列をRubyで復号化してみた
という記事の逆をやってみたいと思ってみました。

JavaScript側では、CryptJSを使ってRuby側で暗号されたものを
var text = CryptoJS.AES.decrypt(data, "pass").toString(CryptoJS.enc.Utf8);
で複合化をできるようにRuby側で暗号化したいと思います。
Ruby側で暗号化された値が上記のdataに入る感じです。

Ruby側は以下のような感じです。

require "openssl"
require "base64"
def encrypt(data, passwd)
enc = OpenSSL::Cipher.new("AES-256-CBC")
enc.encrypt
salt = OpenSSL::Random.random_bytes(8)
enc.pkcs5_keyivgen(passwd, salt, 1)
enc_data = enc.update(data) + enc.final
ret = "Salted__" + salt + enc_data
ret = Base64.encode64(ret).encode("utf-8").chomp
return ret
end
view raw encrypt.rb hosted with ❤ by GitHub

これを使ってruby側で
enc_data = encrypt(”hogehoge”,"pass")
としてできた、enc_dataの内容をJavascript側のdataに入れてやれば、hogehogeと戻せる感じでした。

2017-10-07

awsのlambdaとAPI Gatewayを使って、ちょっとはまったこと

このエントリーをブックマークに追加 このエントリーを含むはてなブックマーク
ほとんど使っている人はいないのですが、
barcotterというバーコードから、そのバーコードに対応するamazonのリンクを作成するandroidアプリを作っています。
これのバーコードからamazonのリンクを生成する部分は、サーバを用意して、このサーバ側で行っています。

最初は、サーバは
Google App Engine(python)
で作っていたのですが、なんかのサポートが切れることをきっかけに
Open Shift(Ruby on Rails)
に乗り換えたのですが、こちらも最近サポートが切れる部分があったので、これをきっかけに、今回サーバ側を
aws lambda(python)
に乗り換えてみました。

今回、lambdaに乗り換えて、いざアプリを利用してみると
type java.lang.String cannot be converted to JSONObject
というエラーが出てjsonのパースができない状態になりました。

原因は、すごく些細でlamdbaでjsonを返すには
handler内で辞書オブジェクトをreturnしてやればよかったのに、
わざわざ辞書オブジェクトをjson.dumpsして文字列にしたものをreturnしてしまって、
これが原因でパースできなかったというものでした。

普通に辞書オブジェクトでreturnすると、ちゃんとbodyに以下のようにjsonが
{ 'aaa': 'bbb', 'ccc': 'ddd'}
となるのですが、文字列で返すとbodyに
"{ 'aaa': 'bbb', 'ccc': 'ddd'}"
とダブルコーテーションがついてしまって、これのせいでパースに失敗していました。

これに気づくまで、無駄に時間がかかってしまいました。

あと、結果的には関係ないのかもしれませんが
パースできないのが、文字コードがうまく処理できないのかと思い、
API Gatewayのメソッドレスポンスの
HTTP のステータス200 の
レスポンス本文のコンテンツタイプが
application/json
だったものを
application/json;charset=UTF-8
とcharsetを追加しました。

とりあえず無事に移行できました。

2017-08-31

sinatraでベーシック認証した時のユーザ名とパスワードを取得する

このエントリーをブックマークに追加 このエントリーを含むはてなブックマーク
apacheなどのwebサーバ側でベーシック認証させておいて、
sinatra側で、その時のユーザ名とパスワードを取得する方法です。


auth_info = Base64.decode64(request.env['HTTP_AUTHORIZATION'].split(' ')[1]).split(':')
username = auth_info[0]
password = auth_info[1]

2017-04-04

LINE BOT APIを使っていたものをLINE Messaging APIに書き直す

このエントリーをブックマークに追加 このエントリーを含むはてなブックマーク
すごく今更ですが、以前LINE BOT APIを使ってみたものを、LINE Messaging APIに直したものを紹介します。

ちなみにLINE Messaging APIを始めるには、以下が参考になりました。
http://milk0824.hatenadiary.jp/entry/2016/10/01/211555

まずは、
LINE BOT APIのcallbackにAWS Lambdaを使ってみる
を直したのは、以下のような感じです。
var https = require('https');
exports.handler = function(event, context) {
console.log('Received event:', JSON.stringify(event, null, 2));
var reply_token = event.events[0].replyToken;
var data = JSON.stringify({
replyToken: reply_token,
messages:[
{
"type": "text",
"text": event.events[0].message.text
}
]
});
var opts = {
host: 'api.line.me',
path: '/v2/bot/message/reply',
headers: {
"Content-type": "application/json; charset=UTF-8",
"Authorization": " Bearer " + "Channel Access Token"
},
method: 'POST'
};
var req = https.request(opts, function(res){
res.on('data', function(chunk){
console.log(res.statusCode + chunk.toString());
}).on('error', function(e){
console.log('ERROR: '+ e.stack);
});
});
req.write(data);
req.end();
};

お次は、rubyで書いたものです。
ruby2.0以上ならば、line-bot-apiというgemがあり、こっちを使うと、もうちょっと楽にかけそうなのですが、以下はruby1.9でも動くことを前提としているのでline-bot-apiは使っていません。

rubyで書いたものの最初の、
LINE BOT APIを利用して電車遅延情報の送信を改良した
を直したのは、以下のような感じです。
# coding: utf-8
require 'rest-client'
require 'json'
require 'date'
require 'holiday_jp'
require 'gmail'
class MyMail
ID = "GMail address"
PW = "GMail password"
TO = "mail send to address"
def self.send(sbj,msg)
gmail = Gmail.new(ID, PW)
message =
gmail.generate_message do
to TO
subject sbj
body msg
end
gmail.deliver(message)
gmail.logout
end
end
class LineBot
TOKEN = "Channel Access Token"
TO = "送信先のID"
def self.send(msg)
headers = {
"Content-Type" => "application/json; charser=UTF-8",
"Authorization" => "Bearer #{TOKEN}",
}
params = {
to: TO,
messages: [
{
type: "text",
text: msg,
}
]
}
RestClient.post "https://api.line.me/v2/bot/message/push", params.to_json, headers
end
end
class TrainChk
@@cache = nil
def self.delay?(company,line)
if @@cache.nil?
begin
response = RestClient.get "https://rti-giken.jp/fhc/api/train_tetsudo/delay.json"
rescue => e
p e.response
return
end
json = JSON.parser.new(response.to_str)
hash = json.parse()
@@cache = hash
end
hash = @@cache
ret = false
hash.each do |h|
ret = true if h["company"] == company and h["name"] == line
end
ret
end
end
if HolidayJp.holiday?(Date.today)
print "#{Time.now.to_s} holiday!\n"
exit
end
ret = []
[
["JR東日本","山手線"],
["JR東日本","東海道線"],
["JR東日本","中央線快速電"],
["東京メトロ","丸ノ内線"],
["東京メトロ","東西線"],
].each do |a|
ret.push(a) if TrainChk.delay?(a[0],a[1])
end
unless ret.size == 0
msg = "電車遅延が発生しています。"
ret.each do |a|
msg = msg + "\n #{a[1]}"
end
begin
LineBot.send(msg)
rescue => e
print "#{Time.now.to_s} line bot error raise!\n"
MyMail.send "line bot error raise",e.response
exit
end
print "#{Time.now.to_s} delay train!\n"
else
print "#{Time.now.to_s} no delay train.\n"
end
view raw train_chk3.rb hosted with ❤ by GitHub

そして、
LINE BOT APIを利用してGoogle Calendarの情報を通知する
を直したのは、以下のような感じです。
ちなみにキャンセルされた予定の処理を追加しています。
# coding: utf-8
require 'rest-client'
require 'json'
require 'date'
require 'gmail'
require 'yaml'
#google-api-clientはv0.6.4が必要です
require "google/api_client"
class MyMail
#GMail関連の設定
ID = "GMail address"
PW = "GMail password"
TO = "mail send to address"
def self.send(sbj,msg)
gmail = Gmail.new(ID, PW)
message =
gmail.generate_message do
to TO
subject sbj
body msg
end
gmail.deliver(message)
gmail.logout
end
end
class LineBot
#LINE関連の設定
TOKEN = "Channel Access Token"
TO = "送信先のID"
def self.send(msg)
headers = {
"Content-Type" => "application/json; charser=UTF-8",
"Authorization" => "Bearer #{TOKEN}",
}
params = {
to: TO,
messages: [
{
type: "text",
text: msg,
}
]
}
RestClient.post "https://api.line.me/v2/bot/message/push", params.to_json, headers
end
end
class GCal
def initialize(list)
@g_list = list
yaml_path = File.expand_path(".google-api.yaml",File.dirname(__FILE__))
oauth = YAML.load_file(yaml_path)
@client = Google::APIClient.new({:application_name => "line_bot_gcal",:application_version => "1.0"})
@client.authorization.client_id = oauth["client_id"]
@client.authorization.client_secret = oauth["client_secret"]
@client.authorization.scope = oauth["scope"]
@client.authorization.refresh_token = oauth["refresh_token"]
@client.authorization.access_token = oauth["access_token"]
if @client.authorization.refresh_token && @client.authorization.expired?
@client.authorization.fetch_access_token!
end
@service = @client.discovered_api('calendar', 'v3')
end
def get_gids
gcal_list = @client.execute(:api_method => @service.calendar_list.list)
gcal_ids = []
gcal_list.data.items.each do |c|
gcal_ids.push [c["summary"],c["id"]] if @g_list.include? c["summary"]
end
gcal_ids
end
def get_event(day)
@day = day
gcal_ids = get_gids
params = {}
params['timeMin'] = Time.utc(day.year, day.month, day.day, 0).iso8601
params['timeMax'] = Time.utc(day.year, day.month, day.day, 23, 59, 60).iso8601
@event = {}
gcal_ids.each do |gcal|
params['calendarId'] = gcal[1]
params.delete("pageToken") unless params["pageToken"].nil?
events = @client.execute(:api_method => @service.events.list,:parameters => params)
while true
events.data.items.each do |e|
@event[gcal[0]] = [] if @event[gcal[0]].nil?
@event[gcal[0]].push e
end
break if !(page_token = events.data.next_page_token)
params["pageToken"] = page_token
events = @client.execute(:api_method => @service.events.list,:parameters => params)
end
end
end
def make_msg
d = {}
d[:at] = {}
d[:from] = {}
d[:to] = {}
day = @day.strftime("%Y-%m-%d")
nday = (@day + 1).strftime("%Y-%m-%d")
@event.each do |k,v|
conf = []
canc = []
v.each do |e|
if e.status == "cancelled"
canc.push(e.recurringEventId)
else
conf.push(e.id)
end
end
ok = conf - canc
v.each do |e|
next if e.status == "cancelled"
next unless ok.include? e.id
if e.start.date.nil?
if e.recurrence.size == 0
next unless e.start.date_time.strftime("%Y-%m-%d") == day
end
d[:at][k] = [] if d[:at][k].nil?
msg = e.start.date_time.strftime("%H:%M")
msg = msg + " - "
if e.recurrence.size == 0
msg = msg + e.end.date_time.strftime("%m/%d ") unless e.end.date_time.strftime("%Y-%m-%d") == day
end
msg = msg + e.end.date_time.strftime("%H:%M")
msg = msg + " " + e.summary
d[:at][k].push msg
else
type = nil
case
when ((e.start.date == day) and (e.end.date == nday))
type = :at
when e.start.date == day
type = :from
when e.end.date == nday
type = :to
end
next if type.nil?
d[type][k] = [] if d[type][k].nil?
d[type][k].push e.summary
end
end
end
ret = nil
s = {:at => "",:from => "から",:to => "まで"}
["at" , "from" , "to"].each do |t|
t = t.to_sym
unless d[t].size == 0
ret = ret.nil? ? "" : ret + "\n"
ret = ret + day + s[t] + "の予定"
d[t].each do |k,v|
ret = ret + "\n [" + k + "]"
v.sort.each do |e|
ret = ret + "\n " + e
end
end
end
end
ret
end
end
#送信したいカレンダー名を列挙する
GCAL_LIST = [
"お仕事",
"記念日",
"遊び",
]
g = GCal.new(GCAL_LIST)
tmr = Date.today + 1
g.get_event(tmr)
msg = g.make_msg
unless msg.nil?
begin
LineBot.send(msg)
rescue => e
print "#{Time.now.to_s} line bot error raise!\n"
MyMail.send "line bot error raise",e.response
exit
end
print "#{Time.now.to_s} send event!\n"
else
print "#{Time.now.to_s} no event.\n"
end
view raw gcal_chk2.rb hosted with ❤ by GitHub

最後に、
LINE BOT APIを利用して雨が降りそうな時は通知するようにしてみる
を直したのは、以下のような感じです。
# coding: utf-8
require 'rest-client'
require 'json'
require 'date'
require 'holiday_jp'
require 'gmail'
require 'rexml/document'
class MyMail
#GMail関連の設定
ID = "GMail address"
PW = "GMail password"
TO = "mail send to address"
def self.send(sbj,msg)
gmail = Gmail.new(ID, PW)
message =
gmail.generate_message do
to TO
subject sbj
body msg
end
gmail.deliver(message)
gmail.logout
end
end
class LineBot
#LINE関連の設定
TOKEN = "Channel Access Token"
TO = "送信先のID"
def self.send(msg)
headers = {
"Content-Type" => "application/json; charser=UTF-8",
"Authorization" => "Bearer #{TOKEN}",
}
params = {
to: TO,
messages: [
{
type: "text",
text: msg,
}
]
}
RestClient.post "https://api.line.me/v2/bot/message/push", params.to_json, headers
end
end
class RainChk
#降水確率を取得したい地域情報
#以下を確認して設定する
#http://www.drk7.jp/weather/
#以下は東京の例
PREF = "http://www.drk7.jp/weather/xml/13.xml"
AREA = "東京地方"
def self.get_info(day,limit)
begin
response = RestClient.get PREF
rescue => e
p e.response
end
doc = REXML::Document.new response.to_str
day = day.strftime("%Y/%m/%d")
ret = {}
ret[:rain] = false
ret[:info] = []
doc.elements.each("weatherforecast/pref/area[@id='#{AREA}']/info[@date='#{day}']/rainfallchance/period") do |element|
next if element.attributes["hour"] == "00-06"
ret[:info].push [element.attributes["hour"],element.text + "%"]
ret[:rain] = true if element.text.to_i >= limit
end
ret
end
end
if HolidayJp.holiday?(Date.today)
print "#{Time.now.to_s} holiday!\n"
exit
end
#確認したい日付と降水確率が何%以上で雨を降るとみなすかの閾値を渡す
ret = RainChk.get_info Date.today,50
if ret[:rain]
msg = "雨が降るかもしれません。"
ret[:info].each do |a|
msg = msg + "\n #{a[0]} #{a[1]}"
end
begin
LineBot.send(msg)
rescue => e
print "#{Time.now.to_s} line bot error raise!\n"
MyMail.send "line bot error raise",e.response
exit
end
print "#{Time.now.to_s} rainy day!\n"
else
print "#{Time.now.to_s} sunny day.\n"
end
view raw rain_chk2.rb hosted with ❤ by GitHub

2017-03-24

railsの開発環境のjavascriptがなんだかよくわからないことになった時の対策

このエントリーをブックマークに追加 このエントリーを含むはてなブックマーク
railsのdevelopment環境でいろいろいじっているうちに
なぜだかわからないのですが、jqueryの動きがあやしい感じになってしまいました。

xhrで投げた結果を受け取って、それを画面の置き換えるのが、うまく動かなかったり
imageの位置を変な感じで取得しようとして受け取れなかったり
みたいな感じになっていました。

原因は、もうわからなかったのですが、
rake assets:clean
で一回asset関連をクリーンしたら、うまく動くようになりました。