ラベル ruby の投稿を表示しています。 すべての投稿を表示
ラベル ruby の投稿を表示しています。 すべての投稿を表示

2013年6月13日木曜日

ruby gem 'aruduino_firmata'のsysexを用いたI2C通信(温度センサと気圧センサ)でのlinda-arduino-sensor的なもの

2013/06/14-18:11JST u2sで負の値の求め方の間違いを修正(緑色)

気圧の値も妥当なものになった(と思う).
2013/06/14-13:30JST 返り値のバイト数が要求バイト数と合っていないことがあるのでその場合スキップするように修正(赤色)
これまでの記事(APT7410MPL115A2)でのコードを利用したlinda-arduino-sensor的なもの
#!/usr/bin/env ruby 
require 'rubygems'
require 'sinatra/rocketio/linda/client'
require 'arduino_firmata'
$stdout.sync = true

url   = ENV["LINDA_BASE"]  || ARGV.shift || "http://localhost:5000"
space = ENV["LINDA_SPACE"] || "test"
linda = Sinatra::RocketIO::Linda::Client.new url
ts = linda.tuplespace[space]

linda.io.on :connect do  ## RocketIO's "connect" event
  puts "connect!! <#{linda.io.session}> (#{linda.io.type})"
end

linda.io.on :disconnect do
  puts "RocketIO disconnected.."
end

STRING_DATA= 0x71
I2C_REQUEST= 0x76
I2C_REPLY= 0x77
I2C_CONFIG= 0x78

_WRITE= 0x00
_READ= 0x08

ADDRESS_ADT7410= 0x48
ADDRESS_MPL115A2= 0x60

#MPL115A2 coefficients
a0= b1= b2= c12= 0.0

def readByte(data, pos)
  return data[pos]+data[pos+1]*(1<<7)
end

def readData(msb, lsb)
  return (msb<<8)+lsb
end

def u2s(v)
  return (v<(1<<15)) ? v : v-(1<<15)*2
end

def bytes2str(bytes)
  ret= ''
  bytes.each do |o|
    ret+= o.chr
  end
  return ret;
end

def decodeData(data)
  ret= {}
  ret[:address]= readByte(data, 0) if (data.length>1)
  ret[:register]= readByte(data, 2) if (data.length>3)
  ary= [];
  i= 4
  while i+1<data.length
    ary.push( readByte(data, i))
    i+= 2
  end
  ret[:data]= ary
  return ret;
end

arduino = ArduinoFirmata.connect ENV["ARDUINO"], :nonblock_io => true

arduino.on :sysex do |command, data|
  #puts "command : #{command}"
  #puts "data    : #{data.inspect}"
  data= (command == STRING_DATA) ? bytes2str(data) : decodeData(data)
  if command == STRING_DATA
    p data
  elsif command == I2C_REPLY
    if data[:address] == ADDRESS_ADT7410
      if data[:data].length == 2
        tout= u2s(readData(data[:data][0], data[:data][1])) / (1<<7).to_f
        ts.write ["sensor", "temperature", tout]
      end
    elsif data[:address] == ADDRESS_MPL115A2
      if data[:register] == 0x04
        if data[:data].length == 8
          a0 = u2s(readData(data[:data][0], data[:data][1]))/(1<< 3).to_f
          b1 = u2s(readData(data[:data][2], data[:data][3]))/(1<<13).to_f
          b2 = u2s(readData(data[:data][4], data[:data][5]))/(1<<14).to_f
          c12= u2s(readData(data[:data][6], data[:data][7]))/(1<<24).to_f
        end
      elsif data[:register] == 0x00
        if data[:data].length == 4
          padc= readData(data[:data][0], data[:data][1])>>6
          tadc= readData(data[:data][2], data[:data][3])>>6
          pcomp= a0 + ( b1 + c12 * tadc ) * padc + b2 * tadc
          pout= pcomp * 650.0/1023.0 + 500.0
          ts.write ["sensor", "pressure", pout] if padc>0
        end
      end
    end
  end
end

#i2c enable
arduino.sysex I2C_CONFIG, [0x00, 0x00]

#APT7410 16bit mode
arduino.sysex I2C_REQUEST, [ADDRESS_ADT7410, _WRITE, 0x03, 0x00, 0x00, 0x01]

#MPL115A2 coefficients
arduino.sysex I2C_REQUEST, [ADDRESS_MPL115A2, _READ, 0x04, 0x00, 0x08, 0x00]

loop do
  arduino.sysex I2C_REQUEST, [ADDRESS_ADT7410, _READ, 0x00, 0x00, 0x02, 0x00]

  arduino.sysex I2C_REQUEST, [ADDRESS_MPL115A2, _WRITE, 0x12, 0x00, 0x01, 0x00]
  sleep 0.02
  arduino.sysex I2C_REQUEST, [ADDRESS_MPL115A2, _READ, 0x00, 0x00, 0x04, 0x00]

  sleep 1
end

ruby gem 'arduino_firmata' の sysex を使ってMPL115A2使用の圧力モジュールから値を取得する

2013/06/14-18:11JST u2sで負の値の求め方の間違いを修正(緑色)

気圧の値も妥当なものになった(と思う).
2013/06/14-13:30JST 返り値のバイト数が要求バイト数と合っていないことがあるのでその場合スキップするように修正(青色)
2013/06/13-16:10JST ADT7410(温度)/"arduino.sysex I2C_REQUEST, [ADDRESS_ADT7410, _WRITE, 0x03, 0x00, 0x00, 0x001]"で16bit-modeで動くこともあるが, ケースによっては13bitのままだったりする.

  • 0.1度の中に複数の計測点があるので16bit modeであることがわかる.
  • 気圧/MPL115A2側はパスコン入れたら安定した
2013/06/13-15:36JST ADT7410(温度)/16bit mode指定がやっぱり正しく動いてないので削除
2013/06/13-15:06JST ADT7410(温度)/16bit mode指定の間違いを訂正
結果:

ブレッドボードをMBPの上からおろしたらみるみる温度が下がっていく図.
気圧が安定していない…やっぱりなにか間違っているのかもしれない.

これを使って,やってみた.のでメモ.
#!/usr/bin/env ruby -Ku
require 'rubygems'
require 'arduino_firmata'
$stdout.sync = true

STRING_DATA= 0x71
I2C_REQUEST= 0x76
I2C_REPLY= 0x77
I2C_CONFIG= 0x78

_WRITE= 0x00
_READ= 0x08

ADDRESS_ADT7410= 0x48
ADDRESS_MPL115A2= 0x60

#MPL115A2 coefficients
a0= b1= b2= c12= 0.0

def readData(data, shift)
  msb= data[4+4*shift]+data[5+4*shift]*128
  lsb= data[6+4*shift]+data[7+4*shift]*128
  return (msb<<8)+lsb
end

def readDataS(data, shift)
  v= readData(data, shift)
  return (v<(1<<15)) ? v : v-(1<<15)*2
end

def data2string(data)
  i=0
  msg= ''
  while i+1<data.length
    msg+= data[i].chr
    i+= 2
  end
  return msg;
end

arduino = ArduinoFirmata.connect ENV["ARDUINO"], :nonblock_io => true

arduino.on :sysex do |command, data|
  puts "command : #{command}"
  puts "data    : #{data.inspect}"
  if command == I2C_REPLY
    if data[0] == ADDRESS_ADT7410
      if data.length == 8
        tout= readDataS(data, 0)/(1<<7).to_f
        puts "気温 #{tout} ℃"
      end
    elsif data[0] == ADDRESS_MPL115A2
      if data[2] == 0x04
        if data.length == 20
          a0 = readDataS(data, 0)/(1<<3).to_f
          b1 = readDataS(data, 1)/(1<<13).to_f
          b2 = readDataS(data, 2)/(1<<14).to_f
          c12= readDataS(data, 3)/(1<<24).to_f
        end
      elsif data[2] == 0x00
        if data.length == 12
          padc= readData(data, 0)>>6
          tadc= readData(data, 1)>>6
          pcomp= a0 + ( b1 + c12 * tadc ) * padc + b2 * tadc
          pout= pcomp * 650.0/1023.0 + 500.0
          puts "気圧 #{pout} hPa"
        end
      end
    end
  else
    if command == STRING_DATA
      p data2string(data);
    end
  end
end

#i2c enable
arduino.sysex I2C_CONFIG, [0x00, 0x00]

#MPL115A2 coefficients
arduino.sysex I2C_REQUEST, [ADDRESS_MPL115A2, _READ, 0x04, 0x00, 0x08, 0x00]

loop do
  arduino.sysex I2C_REQUEST, [ADDRESS_ADT7410, _READ, 0x00, 0x00, 0x02, 0x00]

  arduino.sysex I2C_REQUEST, [ADDRESS_MPL115A2, _WRITE, 0x12, 0x00, 0x01, 0x00]
  sleep 0.02
  arduino.sysex I2C_REQUEST, [ADDRESS_MPL115A2, _READ, 0x00, 0x00, 0x04, 0x00]

  sleep 1
end
  • 前の記事の最後に書いた,「うまくいかない.」の原因は,1バイト(8ビット)の内容を送るのにも7bitの通信なので2バイト(7ビットのバイト+1ビットのバイト)送らないといけない(受信も)という点を全く理解していなかったのに挑んでいたのが原因.…だったと思っている(結局よくわかっていない).

2013年6月12日水曜日

ruby gem 'arduino_firmata' の sysex を使ってADT7410使用の温度モジュールから値を取得する

2013/06/14-18:11JST u2sで負の値を求め方の間違いを修正(緑色)
2013/06/14-13:30JST 返り値のバイト数が要求バイト数と合っていないことがあるのでその場合スキップするように修正(青色)
2013/06/13-16:34JST "arduino.sysex I2C_REQUEST, [ADDRESS_ADT7410, _WRITE, 0x03, 0x00, 0x00, 0x001]"で16bit-modeで動くこともあるが, ケースによっては13bitのままだったりする.

  • 0.1度の中に複数の計測点があるので16bit modeであることがわかる.
2013/06/13-15:36JST 16bit mode指定がやっぱり正しく動いてないので削除
2013/06/13-15:06JST 16bit mode指定の間違いを訂正
2013/06/13-01:53JST readDataの間違いを訂正(奇数バイトは128倍(8bit目?))
2013/06/12-23:27JST readDataの間違いを訂正(shiftの係数が2だった)
これを使って,やってみた.のでメモ.
#!/usr/bin/env ruby -Ku
require 'rubygems'
require 'arduino_firmata'
$stdout.sync = true

ADDRESS_ADT7410= 0x48
I2C_READ= 0x08

def readData(data, shift)
  msb= data[4+4*shift]+data[5+4*shift]*128
  lsb= data[6+4*shift]+data[7+4*shift]*128
  return (msb*256+lsb)
end

def readDataS(data, shift)
  v= readData(data, shift)
  return (v<(1<<15)) ? v : v-(1<<15)*2
end

def data2string(data)
  i=0
  msg= ''
  while i+1<data.length
    msg+= data[i].chr
    i+= 2
  end
  return msg;
end

arduino = ArduinoFirmata.connect ENV["ARDUINO"], :nonblock_io => true
puts "firmata version #{arduino.version}"

arduino.on :sysex do |command, data|
  puts "command : #{command}"
  puts "data    : #{data.inspect}"
  if command == 0x77
    if data[0] == ADDRESS_ADT7410
      if data.length == 8
        t= readDataS(data, 0) / (1<<7).to_f #デフォルトは13bit modeで下位三ビットが1固定で無効ですが割っちゃいます.
        puts "#{t} [度]"
      end
    end
  else
    if command == 0x71
      p data2string(data);
    end
  end
end

#i2c enable
arduino.sysex 0x78, [0x00, 0x00]

loop do
  arduino.sysex 0x76, [ADDRESS_ADT7410, I2C_READ, 0x00, 0x00, 0x02, 0x00]
  sleep 1
end

2013年6月4日火曜日

「現在の気温は23度です」を読み上げるまでのアレコレ

lindaで遊んでいます

linda-temperature-alertで温度をlinda-mac-sayに読み上げさせると数字21が“にい・いち”のようにひとつずつ読み上げられてしまい,“にじゅういちど”とかにならないのが悲しかった.

数字を漢数字にしてsaykanji記事(saykanjiコマンドを定義して漢字仮名混じり文を読み上げる - ザリガニが見ていた...。)のように'mecab -O yomi'すればいけるんじゃない?とか考えていろいろやりはじめた.その成果がこれ. 参考:漢数字に変換
n2k.rb
#! /usr/bin/env ruby -Ku
# coding: utf-8
def num2kanji(val)
  kanji_str= ':一二三四五六七八九'.split('')
  keta_str = ':十百千'.split('')
  keta_str2= ':万億兆京垓'.split('')
  return '零' if (val==0)
  val= val.to_s.reverse.gsub(/(\d{4})/, "\\1,").sub(/,$/, "").split(/,/)
  i= 0;
  for val_item in val
    str= ''
    chars= val_item.split('')
    j= chars.length-1
    for char_item in chars.reverse
      str+= kanji_str[char_item.to_i] + keta_str[j]
      j= j-1
    end
    str = str.gsub(/:./,'') + keta_str2[i]
    str= '' if (str.split(//u).length==1)
    val[i] = str.gsub(/:/,'');
    val[i] = val[i].gsub(/一(百)|一(十)/, "\\1\\2");
    i= i+1
  end
  return val.reverse.join('');
end
puts ARGV[0].gsub(/\d+/){ num2kanji($&.to_i) } if (ARGV[0] != nil)
でこれを
ruby n2k.rb 現在の気温は23度です|mecab -O yomi|say
と読み出せばいいんじゃないか?と思った. ただこれだと「気温は」の「は」が「ha」と発音されてしまう.
ruby n2k.rb 現在の気温は23度です|mecab -O yomi
ゲンザイノキオンハニジュウサンドデス
調べてみるとこれを解決されている方がいた. MeCab と KanateaTalk で漢字かな交じり文の読み上げ - 電脳スピーチ blog mecabをportsで入れたのでdicrcは/opt/local/lib/mecab/dic/ipadic-utf8/dicrcにあった. これに以下の三行を追加した.
node-format-talk = %pS%f[8]\s 
unk-format-talk = %M 
eos-format-talk  = \n
(ちなみにmecabで入れるとeucjpの辞書が入るので+utf8とかしていれないと色々困る) これで「は」は「wa」と発音されることになる
ruby n2k.rb 現在の気温は23度です|mecab -O talk
ゲンザイ  ノ  キオン  ワ  ニ  ジュー  サン  ド  デス  

で,ここまでやってフト,sayコマンドにKyokoさんがいたじゃないかと気がついた.

すっかり忘れてました.
(いまでもデフォルトでは使えなかったと思う.Kyokoさんのインストールについてはこちらの記事が参考になるかと思う.  「Kyoko」さんに優しく警告してもらう方法 - Macの手書き説明書)

ということで,以下のようにlinda-mac-say.rbを改造してみた.
(英語を読ませようとした時にKyokoさんだと冠詞「a」が「エイ」となったりするので全角があるときだけKyokoさんに登場いただくことにした.)
#!/usr/bin/env ruby
require 'rubygems'
require 'sinatra/rocketio/linda/client'
$stdout.sync = true

class String
  def sanitize
    self.gsub(/[`"'\r\n;\|><]/, '').strip
  end
end

url =   ENV["LINDA_BASE"]  || ARGV.shift || "http://localhost:5000"
space = ENV["LINDA_SPACE"] || "test"
puts "connecting.. #{url}"
linda = Sinatra::RocketIO::Linda::Client.new url
ts = linda.tuplespace[space]

linda.io.on :connect do  ## RocketIO's "connect" event
  puts "connect!! <#{linda.io.session}> (#{linda.io.type})"
  ts.watch ["say"] do |tuple|
    p tuple
    if tuple.size == 2 or (tuple.size == 3 and tuple[2].kind_of? Hash)
      str = tuple[1].sanitize
      opts = tuple[2] ? tuple[2].map{|k,v| "#{k} #{v}"}.join(' ').sanitize : ""
      vopt = (/[^ -~。-゚]/=~str)?"-v Kyoko -r 300":"-v Victoria -r 500"
      if system "say #{vopt} #{opts} \"#{str}\""
        ts.write ["say", str, "success"]  # write response
      else
        ts.write ["say", str, "fail"]  # write response
      end
    end
  end
end

linda.io.on :disconnect do
  puts "RocketIO disconnected.."
end

linda.wait