#!/usr/bin/ruby $KCODE = 'e' require 'tk' class Integer #----------------------------------------------------------- # # 序数を返すメソッドを追加 # def to_s_th unit = ['th', 'st', 'nd', 'rd', 'th', 'th', 'th', 'th', 'th', 'th'][self % 10] unit = 'th' if(self / 10 % 10 == 1) self.to_s + unit end end #------------------------------------------------------------------------------- # # TkOsziStatus # class TkOsziStatus < TkLabel def initialize @coupling = ['GND', 'AC', 'DC', '???'] @range = ['1V', '10V', '100V', '???'] @timebase = ['50ns', '100ns', '0.5us', '1us', '5us', '10us', '50us', '0.1ms', '0.5ms', '1ms', '2ms'] @trigger = {0x40 => '+INTERN', 0x20 => '-INTERN', 0x10 => '+EXTERN', 0x08 => '-EXTERN', 0x00 => 'FREE RUN'} @level = {0x40 => '-0.3', 0x20 => '-0.1', 0x10 => '+0.1', 0x08 => '+0.3', 0x04 => '+0.5', 0x00 => '-0.5'} super end #----------------------------------------------------------- # # ステータスセット # def set(data) info = '' info += @coupling[(data[0] & 0x30) >> 4] + ' ' info += @range[(data[0] & 0x0C) >> 2] + ' ' info += @timebase[ data[1]] + ' ' info += @trigger[ data[2] & 0x78] + ' ' info += @level[ data[3]] + ' ' self.text(info) end end #------------------------------------------------------------------------------- # # TkSignalCanvas # class TkSignalCanvas < TkCanvas attr_reader :vx attr_reader :vy attr_reader :vy2 attr_reader :ymax def initialize @vx = 5; @vy = 2 @waves = Hash.new @waveStyles = { 'q' => ['blue', 1], 'w' => ['red', 1], 'e' => ['red', 1], 'r' => ['green', 1], 't' => ['cyan', 1], 'y' => ['yellow', 1], 'u' => ['white', 1], 'i' => ['green', 3], 'o' => ['yellow', 2], } @hideData = Array.new (0..127).each {|i| @hideData.push(-1) } super end #----------------------------------------------------------- # # スコープ画面の初期化 # def initScope(vx, vy) self.width( 127 * (@vx = vx) + 1 + 16) # 描画サイズ決定 self.height(126 * (@vy = vy) + 1 + 16) @ymax = 63 * (@vy2 = @vy * 2) + 8 self.find_all.each {|item| item.delete} # 全ての描画要素を削除 bg = TkcRectangle.new(self, 0, 0, self.width, self.height) { fill('#004000') } # 目盛り線を設定 [0, 10, 20, 30, 40, 50, 60, 70, 80, 90, 100, 110, 120, 127].each {|x| TkcLine.new(self, x * @vx + 8, 8, x * @vx + 8, @ymax) { width(1) fill('red') } } [0, 63, 126].each {|y| TkcLine.new(self, 8, @ymax - (y * @vy), 127 * @vx + 8, @ymax - (y * @vy)) { width(1) fill('red') } } # 波形を設定(下準備) ['q', 'w', 'e', 'r', 't', 'y', 'u', 'i', 'o'].each {|key| @waves[key] = TkcLines.new(self) @waves[key].fill(@waveStyles[key][0]) @waves[key].width(@waveStyles[key][1]) } # 米印が回る end #----------------------------------------------------------- # # 波形を変形 # def formWave(key, signalData) @waves[key].form(signalData) end #----------------------------------------------------------- # # 波形を隠す # def hideWave(key) @waves[key].form(@hideData) end end #------------------------------------------------------------------------------- # # TkcLines - 多節点の線分 # class TkcLines < Array #----------------------------------------------------------- # # 波形の部品を定義する # def initialize(canvas) @canvas = canvas # 各パラメタのみ受け取るよう修正 (0..126).each {|i| self.push(TkcLine.new(@canvas)) } # super end #----------------------------------------------------------- # # HSV -> RGB変換 # # 赤:000〜黄:060〜緑:120〜水:180〜青:240〜紫:300〜赤:000 # def hsv2rgb(h, s, v) # 359, 255, 255 h %= 360; s %= 256; v %= 256 f = (h * 6) % 360 i = (255 - s * f / 360) * v / 255 r = (255 - s ) * v / 255 d = (255 - s * (360 - f) / 360) * v / 255 return([v, d, r]) if(h < 60) return([i, v, r]) if(h < 120) return([r, v, d]) if(h < 180) return([r, i, v]) if(h < 240) return([d, r, v]) if(h < 300) return([v, r, i]) end #----------------------------------------------------------- # # 波形を変形する # def form(sample) (0..126).each {|i| j = i + 1 self[i].coords( i * @canvas.vx + 8, @canvas.ymax - (sample[i] * @canvas.vy2), j * @canvas.vx + 8, @canvas.ymax - (sample[j] * @canvas.vy2)) # self[i].fill(sprintf('#%02x%02x%02x', *hsv2rgb(120, 255, 255 - ((sample[i] - sample[j]) * 4).abs))) # self[i].width(4 - ((sample[i] - sample[j]).abs) / 8) } end #----------------------------------------------------------- # # 波形の色を設定する # def fill(color) self.each {|t| t.fill(color) } end #----------------------------------------------------------- # # 波形の太さ設定する # def width(width) self.each {|t| t.width(width) } end end #------------------------------------------------------------------------------- # # 波形の履歴キュークラス # class FormHistory < Array #----------------------------------------------------------- # # コンストラクタ # def initialize(size) @size = size # super end #----------------------------------------------------------- # # 過去xxx回分までの波形を履歴キューに溜める # def store(samples) self.push(samples) self.shift while(self.length > @size) end end #------------------------------------------------------------------------------- # # 波形のスナップショットエリアクラス # class FormSnap < Array #----------------------------------------------------------- # # 履歴キュー内の波形をスナップエリアにコピー # def shot(forms) self.clear forms.each {|samples| self.push(samples) } @cursor = self.length - 1 # カーソルを最後の波形に移動 end #----------------------------------------------------------- # # スナップエリア内のカーソル処理 # def cursorPrev() @cursor ? (@cursor = (@cursor + self.length - 1) % self.length) : nil end def cursorNext() @cursor ? (@cursor = (@cursor + 1) % self.length) : nil end def cursorOn() @cursor ? @cursor : nil end # @cursor ? (@cursor > 0 ? @cursor -= 1 : @cursor) : nil # @cursor ? (@cursor < (self.length - 1) ? @cursor += 1 : @cursor) : nil #----------------------------------------------------------- # # スナップエリアをファイルに保存 クラス外に出す # def writeFile(name) File.open(name, 'w') {|file| file.write(sprintf("==AnotherPenScopeSnapshotData==\n")) self.each {|samples| file.write(samples.join(',') + "\n") } } end #----------------------------------------------------------- # # スナップエリアをファイルから読む # # def readFile(name) # text = '' # File.open(name) {|file| # unless file.readline =~ /^==MasterZapperData=/ # # Ruby/Tk 内では raise が予約語になるので同義の fail を使う # fail "Signal file format error - #{name}" # end # text = file.read # } # text.tr!(" \r\n", '') # @signalData = text.split(',') # self.draw # end end #------------------------------------------------------------------------------- # # 比較参照用の波形クラス # class FormRefer < Hash #----------------------------------------------------------- # # キャンバスのインスタンスを受け取っておく # def initialize(canvas) @canvas = canvas # super end #----------------------------------------------------------- # # 比較参照用の波形を、強制表示 # def show(key, samples) self.delete(key) self[key] = ['show', samples] # ->登録表示 @canvas.formWave(key, self[key][1]) 'force store' end #----------------------------------------------------------- # # 比較参照用の波形を、強制非表示 # def hide(key) if(self[key]) self[key][0] = 'hide' # ->非表示 @canvas.hideWave(key) 'force hide' end end #----------------------------------------------------------- # # 比較参照用の波形を、表示中なら更新、非表示中なら無視 # def indicate(key, samples) if(self[key]) if(self[key][0] == 'show') # 表示中 self[key] = ['show', samples] # 登録表示 @canvas.formWave(key, self[key][1]) 'showin' elsif(self[key][0] == 'hide') # 非表示中 self[key] = ['hide', samples] @canvas.hideWave(key) 'hiden' else fail "Internal error.\n" end else self[key] = ['show', samples] # 登録表示 @canvas.formWave(key, self[key][1]) 'store' end end #----------------------------------------------------------- # # 比較参照用の波形を、登録表示->非表示<->再表示 # def action(key, samples) if(self[key]) if(self[key][0] == 'show') # ->非表示 self[key][0] = 'hide' @canvas.hideWave(key) 'hide' elsif(self[key][0] == 'hide') # ->再表示 self[key][0] = 'show' @canvas.formWave(key, self[key][1]) 'show' else fail "Internal error.\n" end else self[key] = ['show', samples] # ->登録表示 @canvas.formWave(key, self[key][1]) self.hide('o') # カーソルは非表示に 'store' end end #----------------------------------------------------------- # # 比較参照用の波形を削除 # def remove(key) if(self.delete(key)) @canvas.hideWave(key) 'remove' else 'not registered' end end end #=============================================================================== # # Main # system "stty 19200 raw -crtscts cs7 -echo < /dev/com1" rs232c = open("/dev/com1", "r") root = TkRoot.new { title('Another PenScope') } #------------------------------------------------------------------------------- # # Define Canvas for Signal # signalCanvas = TkSignalCanvas.new history = FormHistory.new(100) snap = FormSnap.new refer = FormRefer.new(signalCanvas) #------------------------------------------------------------------------------- # # Define StatusBar # statusBar = TkLabel.new { # TkFont.new # puts *TkFont.names # puts *TkFont.families text('Status') font TkFont.new(['osaka', 10, ['bold']]) relief('sunken') borderwidth(1) anchor('w') } #------------------------------------------------------------------------------- # # Define osziStatusBar # osziStatusBar = TkOsziStatus.new { text('Status') font TkFont.new(['osaka', 10, ['bold']]) relief('sunken') borderwidth(1) anchor('w') } #------------------------------------------------------------------------------- # # Define AboutBox # def aboutBox Tk.messageBox( 'icon'=>'info', 'type'=>'ok', 'title'=>'About Another PenScope', 'message'=><'Open Another PenScope snapshot file', 'filetypes'=>fileTypes, 'initialfile'=>'*.aps', 'defaultextension'=>'.aps' ) begin signalCanvas.readFile(fileName) statusBar.text(fileName) rescue statusBar.text($!) end unless fileName == '' }], '---', # separator ['Save', proc{ fileName = Tk.getSaveFile( 'title'=>'Save Another PenScope snapshot file', 'filetypes'=>fileTypes, 'initialfile'=>'Untitled.aps', # insert current filename 'defaultextension'=>'.aps' ) begin snap.writeFile(fileName) statusBar.text(fileName) rescue statusBar.text($!) end unless fileName == '' }], '---', ['Exit', proc{exit}] ], [['View', 0], # add with radio ], [['I/O', 0], # add with check ], [['Help', 0], ['About', proc{aboutBox}] ] ] mainMenu = TkMenubar.new(nil, menu_spec, 'tearoff'=>true) vx = TkVariable.new(signalCanvas.vx) [1, 2, 3, 4, 5, 6].each {|v| mainMenu[1][1].add('radio', 'label' =>['', 'x1','x2','x3','x4','x5','x6'][v], 'command' =>proc{ signalCanvas.initScope(vx.to_i, (vx.to_i) / 2 + 1) }, 'variable'=>vx, 'value' =>v) } isAutoZap = TkVariable.new(1) isAutoSave = TkVariable.new(1) mainMenu[2][1].add('command', {'label'=>'Zap', 'command'=>proc{aboutBox}}) mainMenu[2][1].add('check', {'label'=>'AutoZap', 'variable'=>isAutoZap}) mainMenu[2][1].add('separator') mainMenu[2][1].add('command', {'label'=>'Master', 'command'=>proc{aboutBox}}) mainMenu[2][1].add('check', {'label'=>'AutoSave', 'variable'=>isAutoSave}) root.bind('KeyRelease', proc{|e| if(e.char == 's' and snapCursor = snap.shot(history)) # 過去xxx波形をスナップ act = refer.show('o', snap[snapCursor]) statusBar.text('Snappshot last ' + snap.length.to_s + ' waves.' + act) elsif(e.char == 'k' and snapCursor = snap.cursorPrev()) # スナップ内を遡る act = refer.show('o', snap[snapCursor]) statusBar.text((snapCursor + 1).to_s_th + '/' + snap.length.to_s + ' wave in snapshot.' + act) elsif(e.char == 'j' and snapCursor = snap.cursorNext()) # スナップを進む act = refer.show('o', snap[snapCursor]) statusBar.text((snapCursor + 1).to_s_th + '/' + snap.length.to_s + ' wave in snapshot.' + act) elsif(e.char =~ /^[qwertyuio]$/ and snapCursor = snap.cursorOn()) # 参照波形の表示、非表示 act = refer.action(e.char, snap[snapCursor]) statusBar.text(act + ' refer wave to "' + e.char + '" area.') elsif(e.char =~ /^[QWERTYUIO]$/ and snapCursor = snap.cursorOn()) # 参照波形の削除 act = refer.remove(e.char.downcase) statusBar.text(act + ' refer wave at "' + e.char.downcase + '" area.') elsif(e.keysym == 'F1') aboutBox # aboutダイアログ end }) #------------------------------------------------------------------------------- # # Build Window # statusBar.pack('side'=>'bottom', 'fill'=>'x') mainMenu.pack('side'=>'top', 'fill'=>'x') osziStatusBar.pack('side'=>'top', 'fill'=>'x') signalCanvas.pack('fill'=>'both') if ARGV[0] begin signalCanvas.readFile(ARGV[0]) statusBar.text(ARGV[0]) rescue statusBar.text($!) end else signalCanvas.initScope(5, 2) end #------------------------------------------------------------------------------- # # 割り込み処理 # buf = String.new TkAfter.new(10, -1, proc { samples = String.new if(IO.select([rs232c], nil, nil, 0.01)) buf += rs232c.sysread(65536) # 読めるだけ読む s = 0; (0..(buf.length - 1)).each {|n| break if(buf[s = n] < 0x6f) } buf.slice!(0, s) # 頭のゴミを捨てる (0..(buf.length - 1)).each {|n| if(buf[n] > 0x6f) # 111_0000b 以上 samples = buf.slice!(0, n) # パケットを捕獲する break end } end if(samples.length == 0) # 受信中 elsif(samples.length == 4) osziStatusBar.set(samples[0, 4]) elsif(samples.length == 133) history.store(samples[0, 128]) refer.indicate('i', samples[0, 128]) elsif(samples.length == 137) osziStatusBar.set(samples[0, 4]) history.store(samples[4, 128]) refer.indicate('i', samples[4, 128]) else print "====protocol error====\n" # パケット長異常 end }).start Tk.mainloop