hirax.net::Keywords::「ComThread」のブログ



2008-11-19[n年前へ]

Rubyで「シリアル通信スレッドクラス」を作る 

 Rubyで「(Rubyシリアル通信ライブラリ(Windows用)TEXCELL を使った)シリアル通信スレッドクラス」を作りました。ソースコードとサンプルはここに置いておきます。”Microsoft Windows VISTAではほとんど見捨てられているような”シリアルポートでの送受信をRubyでスレッドを使って行うクラスです。Queueにデータを突っ込めば「シリアル通信スレッドクラス」から自動的に送信されます。また、受信した文字列が「(指定した改行コードで)一行になるたびに」receiveイベントが呼ばれるので(また、その際にQueueを指定しておけば受信行が自動的にそのQueueに追加されていきます)、読み込みのタイミング・必要な情報がまだ途中までしか読み込まれていない場合などの処理を気にすることなく使いたい、と考えながら作ってみた「シリアル通信スレッドクラス」です。
 たとえば、COM3で受信した内容をコンソールに出力するだけであれば、このようなコードで動くはずです。

require 'comThread'
receiveComThread=ComThread.new({:icomno=>3})
receiveComThread.start({:receive=>true, 
        :receiveMonitor=>true})
sleep 60
receiveComThread.stop
 シリアル通信モニタプログラム(シリアルポート間で送受信されている内容を眺めるプログラム)も、多分10行くらいで書けると思います。チェックせずに書いてしまうと、こんな感じになると思います。
  require 'comThread'
  q=Queue.new
  receiveComThread=ComThread.new({:icomno=>3,:rq=>q})
  sendComThread=ComThread.new({:icomno=>4,:sq=>q})
  receiveComThread.start({:receive=>true,
    :receiveMonitor=>true})
  sendComThread.start({:send=>true})  
  sleep 60
  sendComThread.stop
  receiveComThread.stop

 以前、こんなことを書きました

 「計測・解析ソフトウェア/ハードウェアのハック」が実験系技術者の一番のLifeHackかもしれない…と思っています。その思いを逆に言うならば、実験系技術者が費やす多くの時間を、計測・解析処理が消費していると思っているからです。そして、一番時間を消費している部分の高速化をすることが、全体の高速化に効果的だろう、と思っているわけです。

 そんなこんなで、何を今更…という、Perlで「シリアル通信とユーザインターフェース自動制御」のやり方を整理しておくことにしました。なぜかというと、経験的に(既成機器をを使わざるえないことが多い)「計測・解析ソフトウェア/ハードウェアのハック」は、シリアル通信制御とユーザインターフェース自動制御でほとんどの場合対応できる、からです。
 というわけで、先週末はこの「Perlでシリアル通信とユーザインターフェース自動制御のやり方を整理しておくことにしました」の部分を「Rubyでシリアル通信とユーザインターフェース自動制御を書いて整理しておくことにしました」ということをしてみたわけです。この「シリアル通信クラス」と「ユーザインターフェース自動制御」があると、結構便利な実験屋さんもいるかもしれません。

2009-07-26[n年前へ]

シリアルポートで受信した内容を最前面アプリにキー送信するRubyスクリプト 

 10年以上前、自分の勉強がてら、シリアル・ポートで受信した内容をエクセルに貼り付けるプログラムをC++で作りました。確か、Windows 98が出た頃で、Windows 2000が出る前だったと思います。

 今日、久しぶりに、Windowsでシリアル・ポートで受信した内容をエクセルに貼り付けるプログラムを作り直したくなりました。そこで、Rubyで「シリアル・ポートで受信した内容を最前面アプリにキー送信するスクリプト」を書いてみました。

 といっても、スレッドを使いシリアル・ポート送受信を行うRubyのクラス"ComThread"は、少し前に書いています。また、Windowsの(キー操作やマウス操作などを扱う)各種APIを使うためのRubyクラス"Win32GuiTest"も、同じように書いてあります。

 ということは、その2つを使うと、こんな風に「シリアル・ポートで受信した内容を最前面アプリにキー送信するRubyスクリプト」を簡単に書くことができます。

require 'comThread'
require 'win32GuiTest'
  
class SendKeyComThread < ComThread
  def receive(data)
    @gui=Win32GuiTest.new
    @gui.sendKeys data.strip+"{ENTER}"
  end
end

skCom=SendKeyComThread.new(1,
        Queue.new,nil,0x1807, 9600) 
skCom.start(:receive=>true)
sleep 60
skCom.stop
 これだけで?という感じですが、これだけです。このスクリプトを走らせれば、COM1に(9600bpsで)受信した内容を最前面ウィンドーに送信することが(60秒間)できます。

 このサンプル・ソースと必要なファイルは、ここに置きました(wincom.rbも必要です)。

 計測器等を使う人であれば、この手のスクリプトは結構便利に感じるのではないでしょうか?こうしたスクリプトを書き、自分が使っている計測器や機器の送出コマンドに合わせたデータ加工正規表現を書き、"receive"メソッドをオーバーライドするのがシェフのお勧めメニューになります。もちろん、RubyScript2exeで、アプリケーション化しておけば、さらに便利だと思います。

2009-07-29[n年前へ]

wincom.rbのCOM10以上対応 

 以前、スレッドを使い、Rubyでシリアル通信をするクラスComThreadを書きました。このクラスは、wincom.rbを利用しています。

 少し前、wincom.rbが、COM10以上の番号のシリアル・ポートを開けないことに気づきました。ソースを眺め調べてみると、WindowsAPIのCreateFile()を使った場合の、COM10 以上のシリアルポートを指定方法に沿っていないことが原因でした。対処としては、wincom.rbの71行目を

comno = "\\\\.\\COM#{icomno}\0"
というように変えれば、COM10以上のポートにも(もちろんCOM10以下のポートにも)対応するようになります。

 最近の(各種I/Fがたくさん備えている)PCに、USB接続のシリアルポートI/Fを繋いだ場合、自動的にCOM10以上になる場合も多いと思います。そんな状況下で、wincom.rbを使うためのTipsとして、ここにメモを書いておきます。

2009-07-30[n年前へ]

ComThreadを使った「制御プログラムの作り方」 

 比較的単純なハードウェア制御、たとえば、(シリアル・ポートから値を得ることができる)センサからの入力を使い、(シリアル・ポートからコマンドを送ることで制御することができる)ハードウェアの制御をしたくなることがあります。実例としては、温度センサの値を使い、扇風機のモーターON/OFFを行いたい、というような場合があります。そんな場合のために、(以前作った)RubyのComThreadを使った「制御プログラムの作り方」を、自分のためのメモがてらここに書いておこうと思います。

 まずは、センサーやモーターの各ハードウェア、つまり、各シリアル・ポート毎に個別のクラスを作っておきます。たとえば、センサーの値にアクセスするクラスをこんな感じで作り、

require 'comThread'

class SensorComThread < ComThread
  def start(condition={:receive=>false,:send=>false,
    :receiveMonitor=>false,
    :sendMonitor=>false})
    @info={}
    super
  end
  attr_reader :info
  def receive(data)
     @info=data.gsub(' ','').split(',')
  end
end
次に、たとえば、モーターを操作するクラスをこんな感じで作ります。
require 'comThread'

class MotorComThread < ComThread
  def sendStart
     send("start")
  end
  def sendStop
     send("stop")
  end
end
 各シリアル・ポートに接続された機器に対する「値解釈」や「送信コマンド作成」といった部分は、上のような具合で、各ポート(に接続された各種機器)を扱うクラスを個別に定義しておく方が頭が整理しやすいように思います。

 そして、次は制御用のメイン・コントローラ・クラスを書きます。

require "thread"

class ControlerThread
  def initialize
  end
  def start(inf)
    @sensor=inf[:sensor]
    @motor=inf[:motor]
    @thread=Thread.new do
      while true
         @motor.sendStop if @sensor.info[0].to_f<hoge
         @motor.sendStart if @sensor.info[0].to_f>=hoge
      end
    end
  end
  def stop
    Thread.kill(@thread)
  end
end
 

 最後に、センサ用コントローラ/モータ用コントローラ/メイン制御コントローラを、それぞれ起動する制御スクリプトを、次のように書きます。

require "sensorThread"
require "motorThread"
require "controlerThread"

sendorRq=Queue.new; 
MotorSq=Queue.new;
sensor=SensorComComThread.new(1,sensorRq,nil,
    0x1807,38400) 
sensor.start(:receive=>true)
motor=MotorThread.new(2, nil,motorSq,0x1807,38400) 
motor.start(:send=>true)
  controler=ControlerThread.new
  controler.start({:sensor=>motor,:motor=>motor})
    sleep ARGV[0].to_i
  controler.stop
motor.stop
sensor.stop
 このように、各I/Fに対するコントローラと、メイン制御コントローラと、それらコンローラ・スレッド群を実行するシーケンス・スクリプトを個別に作る、というのが良いように思われます。

 というわけで、日曜大工的なハードウェア制御プログラムを書いたので、プログラムを書き連ねながら考えたことを、ここに『ComThreadを使った「制御プログラムの作り方」』として書いてみました。

2009-11-11[n年前へ]

Rubyシリアル通信用スレッドクラスで簡単なバイナリ受信処理をしてみよう 

 以前、Rubyでシリアルポート通信(いわゆるRS-232C)を楽に行うためのクラス"ComThread"を作りました(ここが関連ファイルの置き場です)。そのcomThread.rbを使えば、たとえば、COM3で受信した内容をコンソールに出力するだけであれば、このようなコードで動く、というようなものです。

require 'comThread'
receiveComThread=ComThread.new({:icomno=>3})
receiveComThread.start({:receive=>true, 
        :receiveMonitor=>true})
sleep 60
receiveComThread.stop
 これは、「シリアルポートを使って簡単に各種機器からの情報を取得したり、あるいは操作したりするためのクラス」です。もうひとつ例を挙げれば、「シリアルポートで受信した内容を最前面アプリにキー送信するアプリケーション」くらいであれば、以前書いたように十行程度のコードを書けば「はい、できあがり」という具合に(少なくとも私が良く見るシチュエーションにおいては)割に使いやすいクラスです。

 ところで、シリアルポートで送受信を行う機器は多いですが、ものによってはASCII(アスキー)コードでなく、バイナリでデータを送ってくるものもあります。小型の計測器などでは、そんな風にシリアルポート(見かけ上はUSB接続で)経由で出力をバイナリ送信するものも多いかもしれません(もちろん、テキスト送信するものも多いです)。そんな場合でも、簡単なものであれば、comThread.rb でも普通に処理を行うことができます(データが頻繁に大量に送られてくるようなものは扱えません)。

 そこで、バイナリデータをシリアルポートに送りつけてくる機器に対応するスクリプトを書いてみましょう。それは、たとえばこんな具合です。

require 'comThread'

class Comport
  
  def receive(size)
    rcv=@com.receive
    ret=nil
    if rcv!=nil
      if rcv.length==size
        aHigh=rcv[0] & 0b00001111
        aLow=rcv[1] 
        re+=(aHigh*255+aLow).to_s       
      end
    end
    ret
  end

end

period=ARGV[0]  # time(seconds)
Waint 10             # waint to activate teraterm
receiveComThread=ComThread.new(
   {:icomno=>4,
    :ibaud=>57600})
receiveComThread.start(
     {:receive=>true,
      :receiveMonitor=>true,
      :delimeter=>4})
sleep period
receiveComThread.stop
 これは「4バイトのデータ列を定期的に出力する機器のデータを(COM4に57600bpsで受信し)パース処理した上で、その結果をテキストに変換しコンソール出力する」というスクリプト例です。

 バイナリデータとしては、1バイト目のがハイバイトで、2バイト目がローバイトからなるデータ構成になっていて、さらに、ハイバイトは下位4ビットのみが使われる、というような処理がなされています。

 サンプル用に書いたので、本来なら書くべき処理をはしょっています。それでも、数バイト程度のデータが、数秒の時間間隔で送信されるような機器のデータ加工程度の用途であれば、(たまにデータを処理しないで無視してしまうこともあるでしょうが)こんなものでも、(プロトタイピング用途としては)十分使うことができることもあるのではないでしょうか。

 ところで、comThread.rbを少し手直ししました。そのため、(今日段階で置いてある)zipファイルは以前作ったものとサンプルソース類となっていて、comThread.rbの方は、今日少し作りなおしたものとなっています。異なるのは、ComThread.start の部分に、delimeter指定が入っていること・メンテナンスがしやすいように内部で使う関数の引数をHash(ハッシュ)で渡すようにした、ということくらいです。とはいえ、そのまま上位互換で使うことができるのではないか、(多分)と思います。動かない場合があれば、メールして頂ければ、時間を作って直しておきます。

 ふと気付くと、最近Perlを触っていないような気がします。来週あたりは、Perlでも使って何かしてみることにしましょうか。



■Powered by yagm.net