SORACOM Air のメタデータとLEDを連動させる

 引き続きIoTエンジニア養成読本のハンズオンの内容を実践中なわけですが、今度はSORACOM AirのメタデータとLEDの点灯を連動させてる処理をRubyで実装してみます。

gihyo.jp

ユーザーコンソールからの設定

 メタデータサービスを使うにはまずユーザコンソールから、グループ設定とメタデータサービスの使用設定をしておく必要があります。

 ユーザコンソールのメニューからグループを作成した後、そのグループのSORACOM Airの設定で、メタデータサービスの使用設定をONにし、Readonlyのチェックを外して設定を保存します。

f:id:akanuma-hiroaki:20170504175133p:plain

 次にSORACOM AirのSim管理画面から、該当のSimのグループを先ほど作成したグループに変更して設定を保存します。

f:id:akanuma-hiroaki:20170504175303p:plain

f:id:akanuma-hiroaki:20170507161630p:plain:w500

SORACOM Air のメタデータサービスに接続

 まずはSORACOM Airで3Gネットワークに接続します。

pi@raspberrypi:~ $ sudo /usr/local/sbin/connect_air.sh &
[1] 30291
pi@raspberrypi:~ $ Found AK-020
waiting for modem device
--> WvDial: Internet dialer version 1.61
--> Cannot get information for serial port.
--> Initializing modem.
--> Sending: ATZ
ATZ
OK
--> Sending: ATQ0 V1 E1 S0=0 &C1 &D2 +FCLASS=0
ATQ0 V1 E1 S0=0 &C1 &D2 +FCLASS=0
OK
--> Sending: AT+CGDCONT=1,"IP","soracom.io"
AT+CGDCONT=1,"IP","soracom.io"
OK
--> Modem initialized.
--> Sending: ATD*99***1#
--> Waiting for carrier.
ATD*99***1#
OK
CONNECT 21000000
--> Carrier detected.  Starting PPP immediately.
--> Starting pppd at Thu May  4 05:36:43 2017
--> Pid of pppd: 30314
--> Using interface ppp0
--> local  IP address 10.247.81.162
--> remote IP address 10.64.64.64
--> primary   DNS address 100.127.0.53
--> secondary DNS address 100.127.1.53

 続いてcurlで直接メタデータサービスに接続してみます。(レスポンスの内容は省略しています)

pi@raspberrypi:~ $ curl http://metadata.soracom.io/v1/subscriber
{...}

 SORACOM Airでネットワークに接続していないとメタデータサービスにはアクセスできません。

 レスポンスのjsonを整形してみると下記のようになります。(一部マスクしています)

{
    "apn": "soracom.io", 
    "createdAt": 1493055578551, 
    "createdTime": 1493055578551, 
    "expiredAt": null, 
    "expiryAction": null, 
    "expiryTime": null, 
    "groupId": "d79b3210-8d23-4a41-8003-6b6acdec5e55", 
    "iccid": "XXXXXXXXXXXXXXXXXX", 
    "imeiLock": null, 
    "imsi": "XXXXXXXXXXXXX", 
    "ipAddress": "10.XXX.XX.XXX", 
    "lastModifiedAt": 1493876507132, 
    "lastModifiedTime": 1493876507132, 
    "moduleType": "nano", 
    "msisdn": "XXXXXXXXXXX", 
    "operatorId": "OPXXXXXXXXX", 
    "plan": 0, 
    "serialNumber": "AXXXXXXXXXXXXX", 
    "sessionStatus": {
        "dnsServers": [
            "100.127.0.53", 
            "100.127.1.53"
        ], 
        "gatewayPublicIpAddress": "54.XXX.XXX.XX", 
        "imei": "XXXXXXXXXXXXX", 
        "lastUpdatedAt": 1493876215192, 
        "location": null, 
        "online": true, 
        "ueIpAddress": "10.XXX.XX.XXX"
    }, 
    "speedClass": "s1.slow", 
    "status": "active", 
    "tags": {}, 
    "terminationEnabled": false, 
    "type": "s1.slow"
}

 取得できるデータの特定の項目だけ取得したい場合は下記のようにURLの末尾に項目名を追加します。

pi@raspberrypi:~ $ curl http://metadata.soracom.io/v1/subscriber.type
s1.slow

メタデータの更新

 メタデータのタグはAPIで取得だけでなく更新も可能です。curlでJSON形式でPUTリクエストを送信してみます。

pi@raspberrypi:~ $ curl http://metadata.soracom.io/v1/subscriber.tags
{}
pi@raspberrypi:~ $ 
pi@raspberrypi:~ $ curl -X PUT -H content-type:application/json -d '[{"tagName":"led","tagValue" :"off"}]' http://metadata.soracom.io/v1/subscriber/tags
{...}
pi@raspberrypi:~ $ 
pi@raspberrypi:~ $ curl http://metadata.soracom.io/v1/subscriber.tags
{"led":"off"}
pi@raspberrypi:~ $ 
pi@raspberrypi:~ $ curl http://metadata.soracom.io/v1/subscriber.tags.led
off
pi@raspberrypi:~ $ 

メタデータのタグ情報によってLEDの状態を変更する

 それではRubyスクリプトからメタデータサービスにアクセスして、タグの内容によってLEDを点灯/消灯してみます。

require 'bundler/setup'
require 'pi_piper'
require 'open-uri'

interval = 60.0
unless ARGV.empty?
  interval = ARGV[0].to_f
end

led_pin = PiPiper::Pin.new(pin: 22, direction: :out)

loop do
  print 'Connecting to Meta-data service... '
  begin
    res = OpenURI.open_uri('http://metadata.soracom.io/v1/subscriber.tags.led', read_timeout: 5)
    code = res.status.first.to_i

    if code != 200
      puts "ERROR: Invalid response code: #{code} message: #{res.status[1]}"
      break
    end

    led_tag = res.read.rstrip
    if led_tag.downcase == 'off'
      puts 'LED tag is OFF. Turn off the LED.'
      led_pin.off
    elsif led_tag.downcase == 'on'
      puts 'LED tag is ON. Turn on the LED.'
      led_pin.on
    end
  rescue => e
    puts e.message
    puts e.backtrace.join("\n")
    break
  end

  if interval > 0
    sleep(interval)
    next
  end

  break
end

 実行結果は下記のようになります。最初はonだったタグ情報をユーザコンソールからoffに変更すると、LEDが消灯されます。またonに変更すると、LEDが点灯されます。

pi@raspberrypi:~ $ sudo bundle exec ruby degital_twin1.rb 10
Connecting to Meta-data service... LED tag is ON. Turn on the LED.
Connecting to Meta-data service... LED tag is ON. Turn on the LED.
Connecting to Meta-data service... LED tag is ON. Turn on the LED.
Connecting to Meta-data service... LED tag is ON. Turn on the LED.
Connecting to Meta-data service... LED tag is OFF. Turn off the LED. ← この直前にユーザコンソールからタグの値をoffに変更
Connecting to Meta-data service... LED tag is OFF. Turn off the LED.
Connecting to Meta-data service... LED tag is ON. Turn on the LED. ← この直前にユーザコンソールからタグの値をonに変更
Connecting to Meta-data service... LED tag is ON. Turn on the LED.

LEDの状態をメタデータに反映する

 次は、スイッチが押されたらLEDのON/OFFを切り替え、その情報をメタデータの方にも反映します。

require 'bundler/setup'
require 'pi_piper'
require 'net/http'
require 'open-uri'

interval = 60.0
unless ARGV.empty?
  interval = ARGV[0].to_f
end

led_pin = PiPiper::Pin.new(pin: 22, direction: :out)
led_pin.off

switch_pin = PiPiper::Pin.new(pin: 23, direction: :in, pull: :up)

start_time = nil

loop do
  start_time = Time.now.to_i

  print '- Connecting to Meta-data service... '
  begin
    res = OpenURI.open_uri('http://metadata.soracom.io/v1/subscriber.tags.led', read_timeout: 5)
    code = res.status.first.to_i

    if code != 200
      puts "ERROR: Invalid response code: #{code} message: #{res.status[1]}"
      break
    end

    led_tag = res.read.rstrip
    if led_tag.downcase == 'off'
      puts 'LED tag is OFF. Turn off the LED.'
      led_pin.off
    elsif led_tag.downcase == 'on'
      puts 'LED tag is ON. Turn on the LED.'
      led_pin.on
    end
  rescue => e
    puts e.message
    puts e.backtrace.join("\n")
    break
  end

  puts "- Waiting input via the switch (%.1f sec)" % (start_time + interval - Time.now.to_i)
  loop do
    switch_pin.read
    led_pin.read
    if switch_pin.value == 0
      puts "The switch has been pushed. Turn %s the LED" % (led_pin.off? ? 'ON' : 'OFF')
      led_pin.off? ? led_pin.on : led_pin.off
      led_pin.read

      print 'Updating Meta-data... '
      payload = '[{"tagName":"led","tagValue":"%s"}]' % (led_pin.on? ? 'on' : 'off')
      uri = URI.parse('http://metadata.soracom.io/v1/subscriber/tags')
      http = Net::HTTP.new(uri.host, uri.port)
      req = Net::HTTP::Put.new(uri.path, initheader = { 'Content-Type' => 'application/json'})
      req.body = payload
      res = http.start {|http| http.request(req) }
      puts "response_code: #{res.code}"
    end

    if Time.now.to_i > start_time + interval
      break
    end

    sleep(0.1)
  end
end

 実行結果は下記の通りです。30秒ごとにメタデータサービスからタグ情報を取得していますが、その間にスイッチが押された場合はその情報をメタデータサービスに反映しています。

pi@raspberrypi:~ $ sudo bundle exec ruby degital_twin2.rb 30
- Connecting to Meta-data service... LED tag is ON. Turn on the LED.
- Waiting input via the switch (29.0 sec)
- Connecting to Meta-data service... LED tag is ON. Turn on the LED.
- Waiting input via the switch (29.0 sec)
The switch has been pushed. Turn OFF the LED ← スイッチを押下
Updating Meta-data... response_code: 200
- Connecting to Meta-data service... LED tag is OFF. Turn off the LED.
- Waiting input via the switch (29.0 sec)
The switch has been pushed. Turn ON the LED ← スイッチを押下
Updating Meta-data... response_code: 200
- Connecting to Meta-data service... LED tag is ON. Turn on the LED.
- Waiting input via the switch (29.0 sec)