自作の壁を越えろ(2):IoT見守りたまご、「Talk Egg」をRaspberry Pi, Soracom Air, AWS Iotで試作した

この企画は、IoTNEWSの小泉が体当たりでIoT製品を作る過程や、費用などを全部公開していくことで、IoT製品を作る人にとってよくある課題を実際にどう乗り越えていくかを追体験できるプロジェクトです。市場性がない、コストが高くつきすぎて続けられないなど、様々な理由でプロジェクトが中止になる可能性はありますが、それも含めてご容赦ください。

IoT技術部では、実際にハードウエアを作ってみようという企画があがり、「Talk Eggプロジェクト」というプロジェクトが立ち上がりました。インターネットの技術チームがハードウエアを作れるチームと一緒になって、IoT見守りたまご「Talk Egg」というモノを作ります。

このコーナーではその模様をレポートしていきます。

Talk Eggプロジェクトは、スマートフォンがなくても音声でのやり取りができるという企画で、本来はハードウエアを作りこんでいく必要があるのだが、今回はその前段階として、Raspberry Piを2つ使ってイメージの沸くものを作ってみました。

TalkEgg

できること

2組のRaspberry Piを利用して、試作します。それぞれを1号機、2号機と仮に名付けました。
Raspberry Pi 1号機と2号機間で音声を録音して、SORACOM Airを介してボイスデータを保存、AWS IoTを利用して相互に通知できるようにします。

実装の手順

Ⅰ.Raspberry Piのデバイスの作成

・電子工作の手順

Ⅱ.AWSの設定
・Amazon S3の設定
・AWS IoT(thing)の設定
・AWS IoT(certificate)の設定

Ⅲ.Raspberry Piの設定
・ソフトウェアのインストール

Ⅳ.プログラムの作成

Ⅴ.Raspberry Pi 起動時に、自動起動する

Ⅵ.動かしてみる

という流れになります。

Ⅰ.Raspberry Piのデバイスの作成

IMG_1323

用意したもの

  • Raspberry Pi 2(インストール済み) x 2
  • BUFFALO WIFI(設定済み)
  • ブレッドボード
  • タクトスイッチ× 2
  • LEDライト(赤)
  • LEDライト(黄)
  • 10kΩ抵抗器 × 2
  • ジャンパーワイヤー(オス-メス) × 6
  • ジャンパーワイヤー(オス-オス) × 5

電子工作の手順

1)タクトスイッチ(A)はeとf列の19と21に設置
2)タクトスイッチ(B)はeとf列の28と30に設置
3)10kΩ抵抗器(上から茶・黒・オレンジ・金)はブレッドボードのi列16と19・25と28
4)ジャンパーワイヤー(赤-1)はRaspberry:GPIO21/ブレッドボードのc列6
5)ジャンパーワイヤー(赤-2)はRaspberry:3.3v/ブレッドボード: + (プラス)
6)ジャンパーワイヤー(青-1)はブレッドボード:- (マイナス)の渡り線
7)ジャンパーワイヤー(青-2)はRaspberry:GROUND/ブレッドボード:- (マイナス)
8)ジャンパーワイヤー(白)はRaspberry:GPIO24/ブレッドボードのj列28
9)ジャンパーワイヤー(黒-1)はRaspberry:GPIO20/ブレッドボードのj列19
10)ジャンパーワイヤー(黒-2)はRaspberry:GPIO25/ブレッドボードのc列3
11)ジャンパーワイヤー(オレンジ-1)はブレッドボードのj列21/ブレッドボード: + (プラス)
12)ジャンパーワイヤー(オレンジ-2)はブレッドボードのj列30/ブレッドボード: + (プラス)
13)ジャンパーワイヤー(緑-1)はブレッドボードのg列16/ブレッドボード: + (プラス)
14)ジャンパーワイヤー(緑-2)はブレッドボードのg列25/ブレッドボード: + (プラス)
15)LED(赤)はカソード(足が短い)をブレッドボード:の– (マイナス)とアノード(足が長い)をa列の3
16)LED(黄)はカソード(足が短い)をブレッドボード:の– (マイナス)とアノード(足が長い)をa列の6

▼ハード構成
・Raspberry Pi 2 x2
・マイクx 2
・スピーカー x 2
・3G用ドングル x 1
・soracom SIM x 1
・無線LAN(BUFFALO) x 1

構成は、下図のような感じになります。
スクリーンショット 2015-12-02 11.30.47

Ⅱ.AWSの設定

▼AWSで使用するもの
・Amazon S3
・AWS IoT(thing, certificate)

▼Amazon S3用のアカウント設定
・Raspberry Pi用のS3アカウントを作成します
※AWS IAMコンソールより、作業します(設定値は以下の通り)

グループ : rasp-s3[ arn:aws:iam::aws:policy/AmazonS3FullAccess ](※権限をフルアクセスにしておきます)

ユーザ名 : rasp
アクセスキー ID : AK*********X2******* (コンソールで作成したユーザに付与されているID)
シークレットアクセスキー : Z****************40******************Be* (コンソールで作成したユーザに付与されているアクセスキー)

▼Amazon S3用のバケット設定
・録音したファイルをアップロードするバケットを作成します
※AWS S3コンソールより、作業します(設定値は以下の通り)

バケット名:rg-iot-test
リージョン:Oregon

▼AWS IoT(thing, certificate)の作成
thingやcertificateの作成方法については
AWS IoTを使って、IoT Buttonを自作してみた-①」を参考に作成してください。

・thingの作成(1号機用、2号機用の2つを作成)
今回の設定では、以下の値を使用します。
1.thing(1号機) Name:IoTEgg
2.thing(2号機) Name:IoTEgg2

・作成したthingのプロパティ「Update Shadow」をクリックして
以下の値に更新します。

{
  "desired": {
    "voices": [],
    "connected_at": "2015-12-01 00:00:00 +0900"
  },
  "reported": {
    "voices": [
      "dummy.wav"
    ],
    "connected_at": "2015-12-01 00:00:00 +0900"
  }
}

・certificateの作成(1号機用、2号機用の2種類を作成)
今回作成したcertificateを以下の値とします。

d1f****************029**************************b4************7a 

作成したcertificateのkeyは忘れずにダウンロードしてください。

  ・certificate:d1f*****fb-certificate.pem.crt
  ・private key:d1f*****fb-private.pem.key
  ・public key :d1f*****fb-public.pem.key

・作成したcertificateに、先に作成したthingをattachします。1号機用、2号機用のそれぞれのthingに、1号機用、2号機用の両方のcertificateをattachしてください)

Ⅲ.Raspberry Piの設定

▼ソフトウェアのインストール

・AWSCLIのインストール(AWS command line interface)

1. pipをインストールします

# easy_install pip

2. AWS CLIをインストールします

# pip install awscli

3. AWS CLI コマンドの所在を確認します

#which aws

4. AWS CLIの設定を行います

#aws configure
AWS Access Key ID [None]: AK*********X2******* (S3アカウント作成時に付与されたID)
AWS Secret Access Key [None]: Z****************40******************Be* (S3アカウント作成時に付与されたアクセスキー)
Default region name [None]: us-west-2(オレゴンへ設定)
Default output format [None]: json(json, text, tableが選択可能)

5. configureを確認します

# ls -la ~/.aws
# cat -la ~/.aws/config

 

Ⅳ.プログラムの作成

予め、Raspberry Piには、Ruby 2.2.3 (rbenv)が導入済みであるとします。また、

# gem install pi_piper
# gem install mqtt

で、pi_piper(Raspberry Pi用のRubyライブラリ)とmqtt(Ruby用のmqttライブラリ)がインストールしてください。

1.シェルスクリプト

・/opt/connect_air.sh(soracom airの接続用) (パーミッションは0755に設定してください)

#!/bin/bash
lsusb

# USB modem device information
vendor=1c9e           # OMEGA TECHNOLOGY
product=6801          # 3G(FS01BU)
tty=/dev/ttyUSB2

init_modem()
{
  usb_modeswitch -t < /sys/bus/usb-serial/drivers/option1/new_id
}

dialup()
{

        cat > /etc/wvdial.conf << EOF
[Dialer Defaults]
Init1 = ATZ
Init2 = ATQ0 V1 E1 S0=0 &C1 &D2 +FCLASS=0
Init3 = AT+CGDCONT=1,"IP","$2"
Dial Attempts = 3
Stupid Mode = 1
Modem Type = Analog Modem
Dial Command = ATD
Stupid Mode = yes
Baud = 460800
New PPPD = yes
Modem = $1
ISDN = 0
APN = $2
Phone = *99***1#
Username = $3
Password = $4
Carrier Check = no
Auto DNS = 1
Check Def Route = 1
EOF
        echo waiting for modem device
        for i in {1..30}
        do
                [ -e $1 ] && break
                echo -n .
                sleep 1
        done
        [ $i = 30 ] && ( echo modem not found ; exit 1 )
        while [ 1 ] ; do wvdial ; sleep 60 ; done
}

lsusb | grep $vendor:$product && \
        init_modem $vendor $product && \
        dialup $tty soracom.io sora sora

・~/src/bin/unexport.sh(GPIO解放用)

#!/bin/bash

echo 20 > /sys/class/gpio/unexport 
echo 21 > /sys/class/gpio/unexport 
echo 24 > /sys/class/gpio/unexport 
echo 25 > /sys/class/gpio/unexport 

・/opt/iot_egg.sh(自動起動用)(パーミッションは0755に設定してください)

#!/bin/bash --login

echo "home_pre: $HOME" > /tmp/test.txt
export HOME=/root
echo "home_post: $HOME" >> /tmp/test.txt
export PATH="/root/.rbenv/bin:$PATH"

# extract by 'rbenv init -'
export PATH="/root/.rbenv/shims:${PATH}"
export RBENV_SHELL=bash
source '/root/.rbenv/libexec/../completions/rbenv.bash'
rbenv rehash 2>/dev/null
rbenv() {
  local command
  command="$1"
  if [ "$#" -gt 0 ]; then
    shift
  fi

  case "$command" in
  rehash|shell)
    eval "`rbenv "sh-$command" "$@"`";;
  *)
    command rbenv "$command" "$@";;
  esac
}


# exec iot_tamago
cd /root/src/iot_tamago
rbenv local 2.2.3
nohup bundle exec ruby iot_tamago.rb &

exit 0


2.自作ライブラリ

・~/src/lib/config.rb(設定ファイル)

  • 1号機に導入する設定ファイル
module IoTEggConfig
  @config = {
    my_thing: 'IoTEgg',
    companion_thing: 'IoTEgg2'
  }

  def self.config
    @config
  end
end
  • 2号機に導入する設定ファイル
module IoTEggConfig
  @config = {
    my_thing: 'IoTEgg2',
    companion_thing: 'IoTEgg'
  }

  def self.config
    @config
  end
end

 

・~/src/lib/aws_s3.rb(AWS S3ファイル送信用)

module IoTEgg
  class S3
    def upload_file(bucket_name, base_dir, file_name)
      res = system("aws", "s3", "cp", File.join(base_dir, file_name), "s3://#{bucket_name}/")
    end

    def download_file(bucket_name, base_dir, file_name)
      res = system("aws", "s3", "cp", "s3://#{bucket_name}/#{file_name}", File.join(base_dir, file_name))
    end
  end
end

・~/src/lib/led.rb(LED点灯用)

class Led

  attr_accessor :current_state
  def initialize(gpio)
    @pin = PiPiper::Pin.new pin: gpio, direction: :out
    @current_thread = nil
    @current_state = { state: :off }
    @blink_count = 0
  end

  def apply_state(state)
    case state[:state]
    when :off
      off
    when :on
      on
    when :blink
      blink(state[:blink_count])
    end
  end
  def blink(count)
    kill_current_thread
    if count == 0
      @pin.off
      @current_state = { state: :off }
    else
      @current_state = { state: :blink, blink_count: count }
      @current_thread = Thread.new do
        loop do
          @pin.off
          count.times do
            @pin.on
            sleep 0.2
            @pin.off
            sleep 0.2
          end
          sleep 1
        end
      end
    end
  end

  def on
    kill_current_thread
    @pin.on
    @current_state = { state: :on }
  end

  def off
    kill_current_thread
    @pin.off
    @current_state = { state: :off }
  end

  private
  def kill_current_thread
    @current_thread && @current_thread.kill
  end
end

・~/src/lib/play_button.rb(再生用ボタン用)

require 'pi_piper'

require File.join(File.expand_path(File.dirname(__FILE__)), 'player')
require File.join(File.expand_path(File.dirname(__FILE__)), 'config')

class PlayButton
  attr_accessor :status, :latest_filename, :red, :yellow, :gpio_port, :voices

  def initialize(args = {  })
    @voices = args[:voices]
    @status = nil
  end

  def perform_loop
    Thread.new do
      PiPiper.watch pin: @gpio_port do |pin|
        if pin.value.to_i == 1
          Thread.new do
            perform_on_pressed
          end
        end
      end
      PiPiper.wait
    end
  end

  def perform_on_pressed
    case @status
    when :playing
      stop_play
    else
      if @voices.size > 0
        filename = @voices.shift

        # downloading is proceeded in the main process just after delta has been noticed
        # download(filename)
        full_path = File.join(File.expand_path(File.dirname(__FILE__)), 'plays', filename)
        if FileTest.exist?(full_path)
          @player = Player.new
          @player.filename = full_path
          @status = :playing
          @latest_filename = filename
          @red.on
          @player.async_start do |pid|
            @red.off
            IoTEgg::ThingShadow.connect do |client|
              IoTEgg::ThingShadow.publish_desired(client, IoTEggConfig.config[:my_thing], @voices.to_a)
            end
            @player = @status = nil
          end
        end
      end
    end
  end

  private

  def download(filename)
    @yellow.on
    IoTEgg::S3.new.download_file("rg-iot-test", File.join(File.expand_path(File.dirname(__FILE__)), 'plays'), filename)
    @yellow.off
  end

  def stop_play
    @player.stop
  end
end

・~/src/lib/player.rb(再生管理用)

class Player
  attr_accessor :filename

  def initialize(args = {})
    @filename = args[:filename]
  end

  def async_start
    # プロセスグループの子プロセス全てにkillを送る必要が有るため、pgroupを指定する必要がある
    @pid = spawn("aplay #{@filename}", pgroup: true)
    Thread.new do
      Process.waitpid @pid
      yield(@pid)
      @pid = nil
    end
  end

  def stop
    if @pid
      begin
        Process.kill('INT', -@pid)
      rescue Errno::ECHILD
        # do nothing
      end
    end
  end
end

・~/src/lib/record_button.rb(録音ボタン用)

require 'securerandom'
require 'pi_piper'
require File.join(File.expand_path(File.dirname(__FILE__)), 'recorder')
require File.join(File.expand_path(File.dirname(__FILE__)), 'aws_s3')
require File.join(File.expand_path(File.dirname(__FILE__)), 'thing_shadow')
require File.join(File.expand_path(File.dirname(__FILE__)), 'config')

class RecordButton
  attr_accessor :status, :latest_filename, :red, :yellow, :gpio_port, :voices

  def initialize(args = {  })
    @voices = args[:voices]
  end

  def perform_loop
    Thread.new do
      PiPiper.watch pin: @gpio_port do |pin|
        if pin.value.to_i == 1
          Thread.new do
            perform_on_pressed
          end
        end
      end
      PiPiper.wait
    end
  end

  def perform_on_pressed
    case @status
    when :recording
      stop_record
    else
      filename = generate_unique_filename
      @recorder = Recorder.new
      @recorder.output = File.join(File.expand_path(File.dirname(__FILE__)), 'records', filename)
      @status = :recording
      @latest_filename = filename
      @red.on
      @recorder.async_start do |pid|
        @red.off
        upload
        @recorder = @status = nil
      end
    end
  end

  private

  def upload
    puts @latest_filename
    yellow_state = @yellow.current_state
    if @latest_filename && FileTest.exist?(File.join(File.expand_path(File.dirname(__FILE__)), 'records', @latest_filename))
      @yellow.on
      IoTEgg::S3.new.upload_file("rg-iot-test", File.join(File.expand_path(File.dirname(__FILE__)), 'records'), @latest_filename)
      @voices.push @latest_filename
      IoTEgg::ThingShadow.connect do |client|
        IoTEgg::ThingShadow.publish_desired(client, IoTEggConfig.config[:companion_thing], @voices.to_a)
      end
      @yellow.off
      @yellow.apply_state(yellow_state)
    end
  end

  def stop_record
    @recorder.stop
  end

  def generate_unique_filename
    SecureRandom.hex(8) + '.wav'
  end
end

・~/src/lib/recorder.rb(録音管理用)

require "open3"
class Recorder
  attr_accessor :hardware, :format, :channel, :rate, :duration, :file_type, :output

  def initialize(*args)
    @hardware = 'plughw:1,0'
    @format = 'S16_LE'
    @channel = '1'
    @rate = '8000'
    @duration = '10'
    @file_type = 'wav'
    @output = 'hogehoge.wav'
  end

  def async_start
    # プロセスグループの子プロセス全てにkillを送る必要が有るため、pgroupを指定する必要がある
    @pid = spawn("AUDIODEV=hw:1 rec -q -r #{@rate} -c #{@channel} #{@output}", pgroup: true)

    Thread.new do
      Process.waitpid @pid
      yield(@pid)
      @pid = nil
    end
  end

  def stop
    if @pid
      sleep 1
      begin
        #Process.kill('INT', -pid)
        Process.kill('KILL', (-1) * @pid)
      rescue Errno::ECHILD
      end
    end
  end
end

・~/src/lib/thing_shadow.rb(AWS IoT Thing Shadow管理用)

require 'rubygems'
require "mqtt"
require "json"
module IoTEgg
  class ThingShadow

    def self.connect(&block_connect)
      key_dir = File.join(File.expand_path(File.dirname(__FILE__)), 'keys')
      puts "Connect to MQTT Server..."
      MQTT::Client.connect(host: 'A7******WE**.iot.us-west-2.amazonaws.com',
                           port: 8883,
                           ssl: true,
                           cert_file: File.join(key_dir, 'd1f*****fb-certificate.pem.crt'),
                           key_file: File.join(key_dir, 'd1f*****fb-private.pem.key'),
                           ca_file: File.join(key_dir, 'rootCA.pem')) do |client|
        puts "Connected to MQTT Server."
        block_connect.call client
      end
    rescue => ex
      puts ex.message
      retry
    end

    def self.subscribe_to_delta(client, thing)
      puts "Subscribe to delta: #{thing}"
      client.subscribe("$aws/things/#{thing}/shadow/update/delta")

      client.get do |topic, message|
        message = JSON.parse(message)
        state = message["state"]["voices"]
        puts "#{Time.now} : recv(delta) : #{state}"
        yield state
      end
    end

    def self.publish_desired(client, thing, state)
      puts "Start console mode: #{thing} state: #{state}"
      client.publish("$aws/things/#{thing}/shadow/update",
                     { state:
                       {
                         desired: { voices: state }
                       }
                     }.to_json)
    end

    def self.publish_connection_status(client, thing)
      puts "Update connected_at: #{thing}"
      client.publish("$aws/things/#{thing}/shadow/update",
                     { state:
                       {
                         reported: { connected_at: Time.now.to_s },
                         desired: { connected_at: Time.now.to_s }
                       }
                     }.to_json)
    end

    def self.subscribe_get_accepted(client, thing)
      puts "Start tracking mode: #{thing}"
      client.subscribe("$aws/things/#{thing}/shadow/get/accepted")

      client.get do |topic, message|
        message = JSON.parse(message)
        if message['state']['desired']
          puts "#{Time.now} : recv(desired) : #{message['state']['desired']['voices']}"
        end
        if message['state']['reported']
          puts "#{Time.now} : recv(reported) : #{message['state']['reported']['voices']}"
        end

        yield message
      end
    end

    def self.publish_get(client, thing)
      client.publish("$aws/things/#{thing}/shadow/get", '')
    end
  end
end

・~/src/lib/voices.rb(受信済み、送信済みの音声ファイル情報を格納しておくためのクラス)

class Voices
  def initialize(voices = [])
    @voices = voices
  end

  def update(voices)
    @voices = voices || []
  end

  def push(voice)
    @voices << voice
  end

  def shift
    @voices.shift
  end

  def to_a
    @voices
  end

  def size
    @voices.size
  end
end

3.メインプログラム

・~/src/iot_tamago.rb(プログラム本体)

#!/usr/bin/ruby

Process.daemon(true, false) if ARGV.any? {|i| i == '-D' }

require 'rubygems'
require 'pi_piper'
require File.join(File.expand_path(File.dirname(__FILE__)), 'lib', 'record_button')
require File.join(File.expand_path(File.dirname(__FILE__)), 'lib', 'play_button')
require File.join(File.expand_path(File.dirname(__FILE__)), 'lib', 'thing_shadow')
require File.join(File.expand_path(File.dirname(__FILE__)), 'lib', 'voices')
require File.join(File.expand_path(File.dirname(__FILE__)), 'lib', 'led')
require File.join(File.expand_path(File.dirname(__FILE__)), 'lib', 'config')
system File.join(File.expand_path(File.dirname(__FILE__)), 'bin', 'unexport.sh')

#################################################################
GPIO_PORTS = {
  led_red: 25,
  led_yellow: 21,
  record_button: 20,
  play_button: 24,
}

THING_NAME = IoTEggConfig.config[:my_thing]
#################################################################


def download(filename, led_yellow)
  local_path = File.join(File.expand_path(File.dirname(__FILE__)), 'lib', 'plays', filename)
  unless FileTest.exist?(local_path)
    led_yellow.on
    IoTEgg::S3.new.download_file("rg-iot-test", File.join(File.expand_path(File.dirname(__FILE__)), 'lib', 'plays'), filename)
    led_yellow.off
  end
end

my_voices = Voices.new
companion_voices = Voices.new

led_red = Led.new(GPIO_PORTS[:led_red])
led_yellow = Led.new(GPIO_PORTS[:led_yellow])

led_red.blink 100

th_shadow = Thread.new do
  record_button = RecordButton.new(voices: companion_voices)
  record_button.red = led_red
  record_button.yellow = led_yellow
  record_button.gpio_port = GPIO_PORTS[:record_button]

  play_button = PlayButton.new(voices: my_voices)
  play_button.red = led_red
  play_button.yellow = led_yellow
  play_button.gpio_port = GPIO_PORTS[:play_button]

  th_delta = Thread.new do
    IoTEgg::ThingShadow.connect do |client|
      IoTEgg::ThingShadow.subscribe_to_delta(client, THING_NAME) do |state|
        my_voices.update(state)
        my_voices.to_a.each do |f|
          download(f, led_yellow)
        end
        led_yellow.blink(state.nil? ? 0 : state.size)
      end
    end
  end

  th_companion_delta = Thread.new do
    IoTEgg::ThingShadow.connect do |client|
      IoTEgg::ThingShadow.subscribe_to_delta(client, IoTEggConfig.config[:companion_thing]) do |state|
        companion_voices.update(state)
      end
    end
  end

  sleep 5
  IoTEgg::ThingShadow.connect do |client|
    IoTEgg::ThingShadow.publish_connection_status(client, THING_NAME)
  end

  th_rb = record_button.perform_loop
  th_pb = play_button.perform_loop

  led_red.off

  puts "**** READY ****"

  th_rb.join
  th_pb.join
  th_delta.join
end
th_shadow.join

Ⅴ.Raspberry Pi 起動時に、自動起動する

/etc/rc.local に以下の内容を追記します。

/opt/connect_air.sh &
/opt/iot_egg.sh

次に、先ほどIIでダウンロードした証明書、秘密鍵を ~/src/lib/keys 以下においてください。

また、 https://www.symantec.com/content/en/us/enterprise/verisign/roots/VeriSign-Class%203-Public-Primary-Certification-Authority-G5.pem からダウンロードしたルート証明書を同様に~/src/lib/keys/以下にファイル名 rootCA.pem としてにおいてください。

Ⅵ.メッセージを送ってみよう!

1号機での動作

1) タクトスィッチ1を押すと、赤のLEDが点灯し録音モードになります。
2) 送信するメッセージを録音します。
3) タクトスィッチ1を押すと、赤のLEDが消灯して録音を終了します。
4) 録音ファイルをS3へアップロードします。(転送中は黄色のLEDが点灯します)

2号機での動作

5)転送が完了すると、黄色のLEDが、1回ずつ点滅します。(未聴メッセージが1つなので、1回になります)
6)タクトスィッチ2を押すと、黄色のLEDが点灯し、録音ファイルがダウンロードされ、終了すると黄色のLEDが消灯します。
赤のLEDが点灯し、再生を開始します。
7)再生が終了すると、赤のLEDが消灯します。

※メッセージを2回送ります。

8)タクトスィッチ1を押すと、赤のLEDが点灯し、録音モードになります。
9)送信するメッセージ1件目を録音します。
10)タクトスイッチ1を押すと、赤のLEDを消灯し、録音を終了します。
11) 録音ファイル1件目をS3へアップロードします。(転送中は黄色のLEDが点灯します)
12)タクトスィッチ1を再び押すと、赤のLEDが点灯し録音モードになります。
13)送信するメッセージ2件目を録音します。
14)タクトスイッチ1を押すと、赤のLEDを消灯し、録音を終了します。
15) 録音ファイル2件目をS3へアップロードします。(転送中は黄色のLEDが点灯します)

1号機での動作

16)転送が終了すると、黄色のLEDが2回ずつ点滅します。(未聴メッセージが2つなので、2回になります)
17)タクトスィッチ2を押すと、黄色のLEDが点灯し、1件目の録音ファイルがダウンロードされ、終了すると黄色のLEDが消灯します。
続けて赤のLEDが点灯し、再生を開始します。
18)黄色のLEDが1回ずつの点滅に変わります。
19)もう1度、タクトスィッチ2を押すと、黄色のLEDが点灯し、2件目の録音ファイルがダウンロードされ、終了すると黄色のLEDが消灯します。
続けて赤のLEDが点灯し、再生を開始します。
20)再生が終了すると、赤のLEDが消灯します。

実際に動いている様子です!

Talk Egg Raspberry Pi version

現在の費用(概算・通信費除く)

・Raspberry Pi ¥6,390 x 2
・Raspberry Piスターターパック ¥3,240 x 2
(ブレッドボード、タクトスイッチ、LEDライト(赤)、LEDライト(黄)、10kΩ抵抗器、ジャンパーワイヤー(オス-メス)、ジャンパーワイヤー(オス-オス))
・BUFFALO WIFI ¥1,000
・マイク ¥1,500 x 2
・スピーカー ¥1,300 x 2
・3G用ドングル ¥8,880
・SORACOM Air ¥1,088
合計:¥36,828

Previous

トイレでのヘルスモニタリングサービス開発のサイマックス、資金調達を実施

NTTデータ、スマートシティの実現に向け、Imtech Traffic & Infra社との渋滞緩和技術に関する共同研究を開始

Next