- Rasbianで動作確認済み。Linuxとして動くようにしておこう
- Raspberry Piのroot権限で動く。液晶の通信のためにやむなし
mkdir /root/pl; cd /root/pl
して、fileを全部ぶっこむ。サウンドファイルとかは自分で調達しよう- Raspberry PiのシステムPerlに
HiPi
を入れた後cpanm
入れてCarton
入れたあと、cd /root/pl
してcarton install
すると、必要なモジュールが/root/pl/local
以下にわんさか入る。 /root/.lcd.yaml
に、Twitterから取得したAPIキーなどを入れる。あと自分のアカウントのscreen_name
も入れる。/root/pl/lcd.pl
で液晶にドットが表示されて、タイムラインで反応があったら1が表示される。/etc/init.d/skelton
を/etc/init.d/lcd
にコピーして、起動スクリプトを作ろう。- なにやら設定すると、次回から自動的にLCDにタイムラインの様子が表示されるぞ!
Last active
August 29, 2015 14:04
-
-
Save CLCL/dca6b9963a1deff9e76a to your computer and use it in GitHub Desktop.
Raspberry Pi+Adafruit RGB CharLCD Plate用Twitter Stream APIクライアント習作
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package Adafruit_CharLCDPlate; | |
# Raspberry Pi用Adafruit RGB-backlit LCD plateのPythonライブラリ | |
# https://github.com/adafruit/Adafruit-Raspberry-Pi-Python-Code/blob/e28539734dfbd6965395f0f0478ecad44ca0eaea/Adafruit_CharLCDPlate/Adafruit_CharLCDPlate.py | |
# から文字の表示とバックライトの変更の部分だけ最低限移植 | |
# Perl用I2C/SMBusアクセスライブラリHiPiを使用 | |
# http://raspberry.znix.com/p/install.html | |
# | |
# This is essentially a complete rewrite, but the calling syntax | |
# and constants are based on code from lrvick and LiquidCrystal. | |
# lrvic - https://github.com/lrvick/raspi-hd44780/blob/master/hd44780.py | |
# LiquidCrystal - https://github.com/arduino/Arduino/blob/master/libraries/LiquidCrystal/LiquidCrystal.cpp | |
use strict; | |
use warnings; | |
use HiPi::Device::I2C; | |
use HiPi::BCM2835::I2C; | |
use constant { | |
# Port expander registers, | |
# ポートエキスパンダ(MCP23017) のレジスタ | |
MCP23017_IOCON_BANK0 => 0x0A, # IOCON when Bank 0 active / Bank 0がアクティブ時のIOCONレジスタアドレス | |
MCP23017_IOCON_BANK1 => 0x15, # IOCON when Bank 1 active / Bank 1がアクティブ時のIOCONレジスタアドレス | |
# These are register addresses when in Bank 1 only:, | |
# 以下は、Bank 1がアクティブのみ有効なレジスタアドレス | |
MCP23017_GPIOA => 0x09, | |
MCP23017_IODIRB => 0x10, | |
MCP23017_GPIOB => 0x19, | |
# Port expander input pin definitions | |
# ポートエキスパンダ(MCP23017) の入力ピンの定義 | |
SELECT => 0, | |
RIGHT => 1, | |
DOWN => 2, | |
UP => 3, | |
LEFT => 4, | |
# LED colors | |
# LCDパネルのバックライトLEDの色 | |
OFF => 0x00, | |
RED => 0x01, | |
GREEN => 0x02, | |
BLUE => 0x04, | |
}; | |
use constant { | |
YELLOW => RED + GREEN, | |
TEAL => GREEN + BLUE, | |
VIOLET => RED + BLUE, | |
WHITE => RED + GREEN + BLUE, | |
ON => RED + GREEN + BLUE, | |
# LCD Commands | |
# LCD(ポートエキスパンダの先につながっているキャラクタLCDドライバHD44780用の命令) | |
LCD_CLEARDISPLAY => 0x01, | |
LCD_RETURNHOME => 0x02, | |
LCD_ENTRYMODESET => 0x04, | |
LCD_DISPLAYCONTROL => 0x08, | |
LCD_CURSORSHIFT => 0x10, | |
LCD_FUNCTIONSET => 0x20, | |
LCD_SETCGRAMADDR => 0x40, | |
LCD_SETDDRAMADDR => 0x80, | |
# Flags for display on/off control | |
# ディスプレイOn/Offコントロールフラグ(HD44780用)3bits | |
LCD_DISPLAYON => 0x04, | |
LCD_DISPLAYOFF => 0x00, | |
LCD_CURSORON => 0x02, | |
LCD_CURSOROFF => 0x00, | |
LCD_BLINKON => 0x01, | |
LCD_BLINKOFF => 0x00, | |
# Flags for display entry mode | |
# ディスプレイエントリモードフラグ(HD44780用)2bits | |
LCD_ENTRYRIGHT => 0x00, | |
LCD_ENTRYLEFT => 0x02, | |
LCD_ENTRYSHIFTINCREMENT => 0x01, | |
LCD_ENTRYSHIFTDECREMENT => 0x00, | |
# Flags for display/cursor shift | |
# カーソル/ディスプレイシフトフラグ(HD44780用)2bits | |
LCD_DISPLAYMOVE => 0x08, | |
LCD_CURSORMOVE => 0x00, | |
LCD_MOVERIGHT => 0x04, | |
LCD_MOVELEFT => 0x00, | |
}; | |
sub new { | |
my $class = shift; | |
my %args = ( @_ ); | |
my $self = {}; | |
$self->{addr} = 0x20; | |
$self->{i2c} = HiPi::Device::I2C->new( address => $self->{addr}); | |
$self->{color} = { | |
OFF => OFF, | |
RED => RED, | |
GREEN => GREEN, | |
BLUE => BLUE, | |
YELLOW => YELLOW, | |
TEAL => TEAL, | |
VIOLET => VIOLET, | |
WHITE => WHITE, | |
ON => ON, | |
}; | |
# The LCD data pins (D4-D7) connect to MCP pins 12-9 (PORTB4-1), in | |
# that order. Because this sequence is 'reversed,' a direct shift | |
# won't work. This table remaps 4-bit data values to MCP PORTB | |
# outputs, incorporating both the reverse and shift. | |
# LCDモジュールのDATAピン(D4-D7)は、MCP23017の12-9ピン(PORTB4-1) | |
# にこの順番で結線されている。逆順になっているので、通常のシフト | |
# はうまく動かない。次のテーブルは、シフト・反転用に対応したMCP23017 | |
# のPORTBに出力する値を再定義します。 | |
$self->{flip} = [ | |
0b00000000, 0b00010000, 0b00001000, 0b00011000, | |
0b00000100, 0b00010100, 0b00001100, 0b00011100, | |
0b00000010, 0b00010010, 0b00001010, 0b00011010, | |
0b00000110, 0b00010110, 0b00001110, 0b00011110, | |
]; | |
# The speed of LCD accesses is inherently limited by I2C through the | |
# port expander. A 'well behaved program' is expected to poll the | |
# LCD to know that a prior instruction completed. But the timing of | |
# most instructions is a known uniform 37 mS. The enable strobe | |
# can't even be twiddled that fast through I2C, so it's a safe bet | |
# with these instructions to not waste time polling (which requires | |
# several I2C transfers for reconfiguring the port direction). | |
# The D7 pin is set as input when a potentially time-consuming | |
# instruction has been issued (e.g. screen clear), as well as on | |
# startup, and polling will then occur before more commands or data | |
# are issued. | |
# LCDアクセスの速度は、I2Cで接続されるポートエキスパンダ(MCP23017) | |
# で制限されます。 | |
# 「行儀のよいプログラム」は先に実行した命令の完了をLCDからポーリング | |
# します。しかし、ほとんどのプログラムは37msのウェイトで済ませています。 | |
# enable信号は、I2Cによってそれほど速く切り替えることができません。 | |
# したがって、それは時間ポーリング(それはポート方向の再構成のために、 | |
# いくつかのI2Cが移ることを必要とする)を浪費しないためにこれらの指 | |
# 示を備えた安全策です。 | |
# D7ピンは、潜在的に時間を消費する命令が出された場合(例えばスクリー | |
# ンクリア)だけでなく、起動時などに入力に設定され、複数のコマンド | |
# あるいはデータが出される場合、ポーリングの必要が生じます。 | |
$self->{pollables} = [ LCD_CLEARDISPLAY, LCD_RETURNHOME ]; | |
# I2C is relatively slow. MCP output port states are cached | |
# so we don't need to constantly poll-and-change bit states. | |
# self.porta, self.portb, self.ddrb = 0, 0, 0b00010000 | |
# I2Cのレスポンスは比較的遅い。MCP23017の出力ポートの変更状態 | |
# はキャッシュ($self->{poata}, $self->{portb}, $self->{ddrb}) | |
# で管理するので、書き込み後いちいちポーリングして確認する | |
# 必要はありません。 | |
$self->{porta} = 0; | |
$self->{portb} = 0; | |
$self->{ddrb} = 0b00010000; | |
# Set MCP23017 IOCON register to Bank 0 with sequential operation. | |
# If chip is already set for Bank 0, this will just write to OLATB, | |
# which won't seriously bother anything on the plate right now | |
# (blue backlight LED will come on, but that's done in the next | |
# step anyway). | |
# MCP23017のIOCONレジスタをBank0、シーケンシャル処理を有効に設定し | |
# ます。Bank 1になっている想定で決め打ちのアドレスで書き込むため、 | |
# すでにMCP23017チップがBank 0に切り替わっていた場合、OLATBに書き | |
# 込まれることになりますが、特に問題はありません(青いバックライ | |
# トが点灯しますが、次のステップで解決します)。 | |
# 0x80 BANK 0=bank 0を選択 | |
# 0x20 SEQOP シーケンシャルオペレーション 0=アドレスのオートインクリメントをする | |
$self->{i2c}->smbus_write_byte_data( MCP23017_IOCON_BANK1, 0 ); | |
# Brute force reload ALL registers to known state. This also | |
# sets up all the input pins, pull-ups, etc. for the Pi Plate. | |
# 総当たりですべてのレジスタに設定を読み込ませます。また、 | |
# Pi Plateの入力ピン設定や、プルアップ設定も行います。 | |
$self->{i2c}->smbus_write_i2c_block_data(0, [ | |
0b00111111, # IODIRA R+G LEDs=outputs, buttons=inputs | |
$self->{ddrb} , # IODIRB LCD D7=input, Blue LED=output | |
0b00111111, # IPOLA Invert polarity on button inputs | |
0b00000000, # IPOLB | |
0b00000000, # GPINTENA Disable interrupt-on-change | |
0b00000000, # GPINTENB | |
0b00000000, # DEFVALA | |
0b00000000, # DEFVALB | |
0b00000000, # INTCONA | |
0b00000000, # INTCONB | |
0b00000000, # IOCON | |
0b00000000, # IOCON | |
0b00111111, # GPPUA Enable pull-ups on buttons | |
0b00000000, # GPPUB | |
0b00000000, # INTFA | |
0b00000000, # INTFB | |
0b00000000, # INTCAPA | |
0b00000000, # INTCAPB | |
$self->{porta}, # GPIOA | |
$self->{portb}, # GPIOB | |
$self->{porta}, # OLATA 0 on all outputs; side effect of | |
$self->{portb} # OLATB turning on R+G+B backlight LEDs. | |
]); | |
# Switch to Bank 1 and disable sequential operation. | |
# From this point forward, the register addresses do NOT match | |
# the list immediately above. Instead, use the constants defined | |
# at the start of the class. Also, the address register will no | |
# longer increment automatically after this -- multi-byte | |
# operations must be broken down into single-byte calls. | |
# MCP23017をBank 1に切り替えて、シーケンシャル処理を無効にします。 | |
# これ以降は、レジスタのアドレスはこのメソッドの最初のレジスタの順番と | |
# 一致しません。このメソッドの最初で定義した定数を使うこと。 | |
# 更に、アドレスレジスタは、これ以降は自動的にインクリメントしません。 | |
# マルチバイトの操作は、シングルバイトの呼び出しに分割処理する必要 | |
# があります。 | |
# 0x80 BANK 1=bank 1を選択 | |
# 0x20 SEQOP シーケンシャルオペレーション 1=アドレスのオートインクリメントしない | |
$self->{i2c}->smbus_write_byte_data( MCP23017_IOCON_BANK0, 0b10100000); | |
$self->{displayshift} = (LCD_CURSORMOVE | LCD_MOVERIGHT); | |
$self->{displaymode} = (LCD_ENTRYLEFT | LCD_ENTRYSHIFTDECREMENT); | |
$self->{displaycontrol} = (LCD_DISPLAYON | LCD_CURSOROFF | LCD_BLINKOFF); | |
return bless $self, $class; | |
} | |
sub init { | |
my $self = shift; | |
$self->write(0x33); # Init | |
$self->write(0x32); # Init | |
$self->write(0x28); # 2 line 5x8 matrix | |
# 0x20 Function set | |
# 0x10 DL データ長 0=4-bit length | |
# 0x08 NL 行数 1=2行 | |
# 0x04 F フォント 0=5x8 dotsフォント | |
$self->write(LCD_CLEARDISPLAY); | |
$self->write(LCD_CURSORSHIFT | $self->{displayshift}); | |
$self->write(LCD_ENTRYMODESET | $self->{displaymode}); | |
$self->write(LCD_DISPLAYCONTROL | $self->{displaycontrol}); | |
$self->write(LCD_RETURNHOME); | |
} | |
# ---------------------------------------------------------------------- | |
# Write operations | |
# Low-level 4-bit interface for LCD output. This doesn't actually | |
# write data, just returns a byte array of the PORTB state over time. | |
# Can concatenate the output of multiple calls (up to 8) for more | |
# efficient batch write. | |
# LCD出力用のローレベル4bitインタフェース。配列にあるbyteデータをPORTBへ | |
# 直接書き込めないので、4bitデータの4回書き込みに変換する。 | |
# まとめて書き込む場合最大8個連結することができる。 | |
sub out4 { | |
my $self = shift; | |
my $bitmask = shift; | |
my $value = shift; | |
my $hi = $bitmask | $self->{flip}->[ $value >> 4 ]; | |
my $lo = $bitmask | $self->{flip}->[ $value & 0x0F]; | |
return [ $hi | 0b00100000, $hi, $lo | 0b00100000, $lo]; | |
} | |
# Write byte, list or string value to LCD | |
# LCDに設定値の配列または文字列を書き込む | |
sub write { | |
my $self = shift; | |
my $value= shift; | |
my $char_mode = shift || 0; | |
# """ Send command/data to LCD """ | |
# If pin D7 is in input state, poll LCD busy flag until clear. | |
# もしD7ピンがinputになってるなら、LCDのbusyフラグ(D7ピン)がCLEARになるまでポーリングする。 | |
if ($self->{ddrb} & 0b00010000 ) { | |
my $lo = ( $self->{portb} & 0b00000001) | 0b01000000; | |
my $hi = $lo | 0b00100000; # E=1 (strobe) | |
$self->{i2c}->smbus_write_byte_data( MCP23017_GPIOB, $lo); | |
while (1) { | |
# Strobe high (enable) | |
$self->{i2c}->smbus_write_byte_data( MCP23017_GPIOB, $hi ); | |
my $bits = $self->{i2c}->smbus_read_byte(); | |
# Strobe low, high, low. Second nybble (A3) is ignored. | |
$self->{i2c}->smbus_write_block_data( MCP23017_GPIOB, [$lo, $hi, $lo]); | |
last if ( $bits & 0b00000010) == 0; # D7=0, not busy | |
} | |
$self->{portb} = $lo; | |
# Polling complete, change D7 pin to output | |
# ポーリングが終わったら、D7ピンをoutputに変更する | |
$self->{ddrb} &= 0b11101111; | |
$self->{i2c}->smbus_write_byte_data( MCP23017_IODIRB, $self->{ddrb}); | |
} | |
my $bitmask = $self->{portb} & 0b00000001; | |
$bitmask |= 0b10000000 if $char_mode; # Set data bit if not a command | |
# If string or list, iterate through multiple write ops | |
# 引数が文字列または配列のリファレンスの場合、複数書き込みとして処理する | |
if ( ( $value ^ $value ) ne '0' ) { # 文字列 | |
my $last = length($value) - 1; | |
my $data = []; | |
my $i = 0; | |
foreach my $v ( split (//, $value) ) { | |
# Append 4 bytes to list representing PORTB over time. | |
# First the high 4 data bits with strobe (enable) set | |
# and unset, then same with low 4 data bits (strobe 1/0). | |
# 表わすPORTBを時間にわたってリストするために4バイトを追加してください。 | |
# 最初に、strobe(Enable)を1/0に設定した上位4bitデータ、 | |
# その後、下位4bitデータ(strobe 1/0) | |
# (つまり8bitデータを4回に分けて送る:MCPとLCDのデータ線が4bitなため) | |
push @$data, @{$self->out4($bitmask, ord($v))}; | |
# I2C block data write is limited to 32 bytes max. | |
# If limit reached, write data so far and clear. | |
# Also do this on last byte if not otherwise handled. | |
# I2Cブロック・データ書き込みは最大32バイトに制限されています。 | |
# 限界が達した場合は、ここまでのデータを書いて空にしてください。 | |
# 限界でない場合でも、処理されていないデータがあれば、最後のバイトで | |
# 書き込みを行ってください。 | |
if ( ( $#{$data} >= 31 ) || ($i == $last) ) { | |
$self->{i2c}->smbus_write_block_data( MCP23017_GPIOB, $data ); | |
$self->{portb} = $data->[-1]; # Save state of last byte out | |
$data = []; # Clear list for next iteration | |
} | |
$i++; | |
} | |
} | |
elsif ( ref $value eq 'ARRAY' ) { # 配列のリファレンス | |
## Same as above, but for list instead of string | |
## 上と同じルーチンだけど配列用の処理として用意 | |
my $last = length($value) - 1; | |
my $data = []; | |
my $i = 0; | |
foreach my $v ( @$value ) { | |
push @$data, @{$self->out4($bitmask, $v)}; | |
if ( ( $#{$data} >= 31) || ($i == $last) ) { | |
$self->{i2c}->smbus_write_block_data( MCP23017_GPIOB, $data ); | |
$self->{portb} = $data->[-1]; # Save state of last byte out | |
$data = []; # Clear list for next iteration | |
} | |
$i++; | |
} | |
} | |
else { | |
# Single byte | |
# シングルバイト | |
my $data = $self->out4($bitmask, $value); | |
$self->{i2c}->smbus_write_block_data( MCP23017_GPIOB, $data ); | |
$self->{portb} = $data->[-1]; | |
} | |
# If a poll-worthy instruction was issued, reconfigure D7 | |
# pin as input to indicate need for polling on next call. | |
# ポーリング結果で、ポーリング指示が出された場合は、次の呼び出しでポーリングする必要 | |
# があるので、D7ピンを入力に設定する。 | |
if ( (!$char_mode) && (grep {$_ eq $value } @{$self->{pollables}} ) ) { | |
$self->{ddrb} |= 0b00010000; | |
$self->{i2c}->smbus_write_byte_data( MCP23017_IODIRB, $self->{ddrb} ); | |
} | |
} | |
# ---------------------------------------------------------------------- | |
# Utility methods | |
sub begin { | |
my $self = shift; | |
my $cols = shift; | |
my $lines = shift; | |
$self->{currline} = 0; | |
$self->{numlines} = $lines; | |
$self->clear(); | |
} | |
# Puts the MCP23017 back in Bank 0 + sequential write mode so | |
# that other code using the 'classic' library can still work. | |
# Any code using this newer version of the library should | |
# consider adding an atexit() handler that calls this. | |
sub stop { | |
my $self = shift; | |
$self->{porta} = 0b11000000; # Turn off LEDs on the way out | |
$self->{portb} = 0b00000001; | |
HiPi::BCM2835::I2C->delay(15); | |
$self->{i2c}->smbus_write_byte_data( MCP23017_IOCON_BANK1, 0); | |
$self->{i2c}->smbus_write_i2c_block_data(0, [ | |
0b00111111, # IODIRA | |
$self->{ddrb}, # IODIRB | |
0b00000000, # IPOLA | |
0b00000000, # IPOLB | |
0b00000000, # GPINTENA | |
0b00000000, # GPINTENB | |
0b00000000, # DEFVALA | |
0b00000000, # DEFVALB | |
0b00000000, # INTCONA | |
0b00000000, # INTCONB | |
0b00000000, # IOCON | |
0b00000000, # IOCON | |
0b00111111, # GPPUA | |
0b00000000, # GPPUB | |
0b00000000, # INTFA | |
0b00000000, # INTFB | |
0b00000000, # INTCAPA | |
0b00000000, # INTCAPB | |
$self->{porta}, # GPIOA | |
$self->{portb}, # GPIOB | |
$self->{porta}, # OLATA | |
$self->{portb}, | |
]); # OLATB | |
} | |
sub clear { | |
my $self = shift; | |
$self->write(LCD_CLEARDISPLAY); | |
} | |
sub home { | |
my $self = shift; | |
$self->write(LCD_RETURNHOME); | |
} | |
sub setCursor { | |
my $self = shift; | |
my $col = shift; | |
my $row = shift; | |
$self->{row_offsets} = [ 0x00, 0x40, 0x14, 0x54 ]; | |
if ( $row > $self->{numlines} ) { | |
$row = $self->{numlines} - 1; | |
} | |
elsif ( $row < 0 ) { | |
$row = 0; | |
} | |
$self->write(LCD_SETDDRAMADDR | ($col + $self->{row_offsets}->[$row])); | |
} | |
sub display { | |
my $self = shift; | |
#""" Turn the display on (quickly) """ | |
$self->{displaycontrol} |= LCD_DISPLAYON; | |
$self->write( LCD_DISPLAYCONTROL | $self->{displaycontrol}); | |
} | |
sub noDisplay { | |
my $self = shift; | |
#""" Turn the display off (quickly) """ | |
$self->{displaycontrol} &= ~LCD_DISPLAYON; | |
$self->write(LCD_DISPLAYCONTROL | $self->{displaycontrol}); | |
} | |
sub cursor { | |
my $self = shift; | |
#""" Underline cursor on """ | |
$self->{displaycontrol} |= LCD_CURSORON; | |
$self->write(LCD_DISPLAYCONTROL | $self->{displaycontrol}); | |
} | |
sub noCursor { | |
my $self = shift; | |
#""" Underline cursor off """ | |
$self->{displaycontrol} &= ~LCD_CURSORON; | |
$self->write(LCD_DISPLAYCONTROL | $self->{displaycontrol}); | |
} | |
sub autoscroll { | |
my $self = shift; | |
#""" This will 'right justify' text from the cursor """ | |
$self->{displaymode} |= LCD_ENTRYSHIFTINCREMENT; | |
$self->write(LCD_ENTRYMODESET | $self->{displaymode}); | |
} | |
sub noAutoscroll { | |
my $self = shift; | |
#""" This will 'left justify' text from the cursor """ | |
$self->{displaymode} &= ~LCD_ENTRYSHIFTINCREMENT; | |
$self->write(LCD_ENTRYMODESET | $self->{displaymode}); | |
} | |
sub message { | |
my $self = shift; | |
my $text = shift; | |
# """ Send string to LCD. Newline wraps to second line""" | |
my @lines = split(/\n/, $text); # Split at newline(s) | |
my $i = 0; | |
foreach my $line ( @lines ) { # For each substring... | |
if ( $i > 0 ) { # If newline(s), | |
$self->write( 0xC0 ); # set DDRAM address to 2nd line | |
} | |
$self->write( $line, 1 ); # Issue substring | |
$i++; | |
} | |
} | |
sub backlight { | |
my $self = shift; | |
my $color = shift; | |
my $c = ~$color; | |
$self->{porta} = ($self->{porta} & 0b00111111) | (($c & 0b011) << 6); | |
$self->{portb} = ($self->{portb} & 0b11111110) | (($c & 0b100) >> 2); | |
# Has to be done as two writes because sequential operation is off. | |
$self->{i2c}->smbus_write_byte_data( MCP23017_GPIOA, $self->{porta}); | |
$self->{i2c}->smbus_write_byte_data( MCP23017_GPIOB, $self->{portb}); | |
} | |
# Read state of single button | |
sub buttonPressed { | |
my $self = shift; | |
my $b = shift; | |
return ($self->{i2c}->smbus_read_byte_data(MCP23017_GPIOA) >> $b) & 1; | |
} | |
# Read and return bitmask of combined button state | |
sub buttons { | |
my $self = shift; | |
return $self->{i2c}->smbus_read_byte_data(MCP23017_GPIOA) & 0b11111; | |
} | |
1; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
requires 'Proc::Daemon'; | |
requires 'AnyEvent::Twitter::Stream'; | |
requires 'Net::OAuth'; | |
requires 'YAML'; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env perl | |
use strict; | |
use warnings; | |
use FindBin; | |
use File::Spec; | |
use lib File::Spec->catdir($FindBin::Bin, 'local/lib/perl5'); | |
use lib $FindBin::Bin; | |
use Adafruit_CharLCDPlate; | |
use AnyEvent::Twitter::Stream; | |
use Encode; | |
use HiPi::Device::I2C; | |
use HiPi::BCM2835::I2C; | |
use Proc::Daemon; | |
use Time::Piece; | |
use utf8; | |
use YAML; | |
my $script_dir = $FindBin::Bin; | |
binmode STDOUT => ':utf8'; | |
# デバッグ用定数 | |
my $TALK = 0; | |
my $DAEMON = 1; | |
my $log_file="/root/log.txt"; | |
my $err_file="/root/err.txt"; | |
# Twitter設定読み込み | |
my $config = YAML::LoadFile('/root/.lcd.yaml') or die $!; | |
# Twitter変数初期化 | |
my $myname = $config->{username}; | |
my $tweet_count = 0; | |
my $last_tweet = ''; | |
my $screen; # Daemon化でうまくいくようにあらかじめ変数宣言しておく | |
# | |
# AnyEvent関連 | |
# | |
my $cv = AE::cv; | |
my $done = AnyEvent->condvar; | |
my $event; | |
my $listener; | |
# Twitter Streaming APIイベントリスナー設定関数 | |
$event->{twitter} = sub { | |
$listener->{twitter} = AnyEvent::Twitter::Stream->new( | |
consumer_key => $config->{consumer_key}, | |
consumer_secret => $config->{consumer_secret}, | |
token => $config->{access_token}, | |
token_secret => $config->{access_token_secret}, | |
method => 'userstream', | |
on_tweet => sub { | |
my $tweet = shift; | |
my $user = $tweet->{user}->{screen_name}; | |
my $text = ($tweet->{text} || ''); | |
return unless $user && $text; | |
my $tp = localtime Time::Piece->strptime( $tweet->{ created_at }, "%a %b %d %T %z %Y")->epoch; | |
my $date = $tp->strftime('%H%M%S'); | |
print "$date $user: "; | |
$tweet_count++; | |
$screen->write_byte($tweet_count); | |
$last_tweet = $text; | |
if ( $text =~ m/\@${myname}/i ) { | |
sound('ICQ15.wav'); | |
talk2('メンションです'); | |
talk2( $text ); | |
$screen->show_dialog("mentionデス", 'GREEN'); | |
} else { | |
sound('CURSOR.WAV'); | |
talk( $text ); | |
} | |
}, | |
on_event => sub { | |
my $tweet = shift; | |
my $event = $tweet->{event}; | |
my $source = $tweet->{source}; | |
my $target = $tweet->{target}; | |
my $target_object = $tweet->{target_object}; | |
sound('ICQ15.wav'); | |
if ( $event eq 'access_revoked *') { | |
#$source eq 'Deauthorizing user'; | |
#$target eq 'App owner'; | |
#$target_object eq 'client_application'; | |
talk('User deauthorizes stream'); | |
print Dump $tweet; | |
} | |
elsif ( $event eq 'block') { | |
#$source eq 'Current user'; | |
#$target eq 'Blocked user'; | |
#$target_object eq 'Null'; | |
talk('User blocks someone'); | |
talk('【自分がブロックした】'); | |
talk('ブロックしたユーザ: '. $target->{screen_name}); | |
} | |
elsif ( $event eq 'unblock') { | |
#$source eq 'Current user'; | |
#$target eq 'Unblocked user'; | |
#$target_object eq 'Null'; | |
talk('User blocks someone'); | |
talk('【自分がブロック解除した】'); | |
talk('ブロック解除したユーザ: '. $target->{screen_name}); | |
} | |
elsif ( $event eq 'unblock') { | |
#$source eq 'Current user'; | |
#$target eq 'Unblocked user'; | |
#$target_object eq 'Null'; | |
talk('User removes a block'); | |
print Dump $tweet; | |
} | |
elsif ( $event eq 'favorite') { | |
if ( $source->{screen_name} eq $myname ) { | |
#$source eq 'Current user'; | |
#$target eq 'Tweet author'; | |
#$target_object eq 'Tweet'; | |
#talk('User favorites a Tweet'); | |
talk('【自分がファボした】'); | |
talk( $target_object->{user}->{screen_name} .": ". $target_object->{text}); | |
$screen->show_dialog("ジブンガfavシタ\n$target_object->{user}->{screen_name}", 'YELLOW'); | |
} | |
else { | |
#$source eq 'Favoriting user'; | |
#$target eq 'Current user'; | |
#$target_object eq 'Tweet'; | |
#talk('User's Tweet is favorited'); | |
talk2('【自分のツイートを誰かがファボした】'); | |
talk( $source->{user}->{screen_name} ); | |
talk( $target_object->{user}->{screen_name} .": ". $target_object->{text}); | |
$screen->show_dialog("ダレカガfavシタ\n$source->{user}->{screen_name}", 'YELLOW'); | |
} | |
} | |
elsif ( $event eq 'unfavorite') { | |
if ( $source->{screen_name} eq $myname ) { | |
#$source eq 'Current user'; | |
#$target eq 'Tweet author'; | |
#$target_object eq 'Tweet'; | |
#talk('User unfavorites a Tweet'); | |
talk('【自分がファボ取り消した】'); | |
talk( $target_object->{user}->{screen_name} .": ". $target_object->{text}); | |
$screen->show_dialog("ジブンガfavトリケシタ\n$target_object->{user}->{screen_name}", 'YELLOW'); | |
} | |
else { | |
#$source eq 'Unfavoriting user'; | |
#$target eq 'Current user'; | |
#$target_object eq 'Tweet'; | |
#talk("User's Tweet is unfavorited"); | |
talk('【自分のツイートを誰かがファボ取り消した】'); | |
talk( $source->{user}->{screen_name} ); | |
$screen->show_dialog("ダレカガfavトリケシタ\n$source->{user}->{screen_name}", 'VIOLET'); | |
} | |
print Dump $tweet; | |
} | |
elsif ( $event eq 'follow') { | |
if ( $source->{screen_name} eq $myname ) { | |
#$source eq 'Current user'; | |
#$target eq 'Followed user'; | |
#$target_object eq 'Null'; | |
#talk('User follows someone'); | |
talk('【自分がほかのユーザーをフォローした】'); | |
talk2( $target->{screen_name} ." / ". $target->{user}->{screen_name} ); | |
$screen->show_dialog("フォローシマシタ\n$target->{user}->{screen_name}", 'VIOLET'); | |
} | |
else { | |
#$source eq 'Following user'; | |
#$target eq 'Current user'; | |
#$target_object eq 'Null'; | |
#talk('User is followed'); | |
talk2('あなたが誰かにフォローされました。'); | |
talk2( $source->{user}->{screen_name} ); | |
$screen->show_dialog("フォローサレマシタ\n$source->{user}->{screen_name}", 'VIOLET'); | |
} | |
} | |
elsif ( $event eq 'unfollow') { | |
#$source eq 'Current user'; | |
#$target eq 'Followed user'; | |
#$target_object eq 'Null'; | |
talk('User unfollows someone'); | |
print Dump $tweet; | |
} | |
else { | |
talK('何かイベントがあったみたい'); | |
print Dump $tweet; | |
} | |
}, | |
on_delete => sub { | |
my $id = shift; | |
sound('W_39-.wav'); | |
talk("誰かがツイ消ししたみたい"); | |
print "$id\n"; | |
$screen->show_dialog("ダレカガ「ツイケシ」シタ\n$id", 'VIOLET'); | |
}, | |
on_error => sub { # 何らかのエラー発生(再接続をしたい) | |
my $error = shift; | |
warn "ERROR: $error"; | |
$screen->backlight( $screen->{color}->{RED} ); | |
undef $listener->{twitter}; # Twitterのウォッチャー消す | |
$event->{timer_reconnection}->(5); # 〔Twitterのウォッチャー〕のウォッチャー再設定 | |
}, | |
on_eof => sub { | |
warn "EOF"; | |
$screen->backlight( $screen->{color}->{RED} ); | |
$done->send; | |
}, | |
); | |
}; | |
# Twitter再接続タイマーループ | |
$event->{timer_reconnection} = sub { | |
my $after = shift || 0; | |
$listener->{timer_reconnection} = AnyEvent->timer( | |
after => $after, | |
cb => sub { | |
unless ( $listener->{twitter} ) { | |
$screen->clear(); | |
$screen->backlight( $screen->{color}->{WHITE} ); | |
$screen->message("\n(re)connected"); | |
print "(re)connected\n"; | |
sound('ED5WV003.WAV'); | |
undef $listener->{timer_reconnection}; | |
$event->{twitter}->(); | |
} | |
}, | |
); | |
}; | |
# 0.05秒タイマーループ(キーセンス) | |
$event->{timer_keysense} = sub { | |
$listener->{timer_keysense} = AnyEvent->timer( | |
interval => 0.05, | |
cb => sub { | |
# 最後のツイートをしゃべる | |
if ( $screen->buttonPressed(1) ) { # RIGHT | |
unless ($last_tweet eq '') { | |
sound('CURSOR.WAV'); | |
talk2( $last_tweet); | |
$last_tweet = ''; | |
} | |
} | |
# テスト表示:緑バックライト | |
if ( $screen->buttonPressed(2) ) { # DOWN | |
$screen->show_dialog('test!', 'GREEN'); | |
} | |
# 5秒長押しで電源OFF | |
if ( $screen->buttonPressed(0) ) { # SELECT | |
if ( ref $listener->{timer_hidedialog} ne 'ARRAY' ) { | |
$listener->{timer_hidedialog} = AnyEvent->timer( | |
after => 5, | |
cb => sub { | |
if ( $screen->buttonPressed(0) ) { # SELECT | |
$screen->clear(); | |
$screen->backlight( $screen->{color}->{BLUE} ); | |
$screen->message("Shutdown..."); | |
$cv->send; | |
system('shutdown -h now'); | |
} | |
else { | |
undef $listener->{timer_hidedialog}; | |
} | |
}, | |
); | |
} | |
else { | |
undef $listener->{timer_hidedialog}; | |
} | |
} | |
}, | |
); | |
}; | |
# 1秒ループ(画面フレーム進行) | |
$event->{timer_tick} = sub { | |
$listener->{timer_tick} = AnyEvent->timer( | |
interval => 1, | |
cb => sub { # イベント発生時にこの関数が呼ばれる | |
$screen->{pos}++; | |
$screen->{pos} %= 32; | |
$tweet_count = 0; | |
$screen->write_byte( encode('cp932', "・" ) ); | |
} | |
); | |
}; | |
# Proc::Daemon デーモン化 | |
&init; # Proc::Daemon のイニシャライズ | |
&run; # Proc::Daemon 実行開始 | |
sub init { # Proc::Daemon の初期化ルーチン | |
$SIG{INT } = 'interrupt'; # Ctrl-C | |
$SIG{HUP } = 'interrupt'; # HUP SIGNAL | |
$SIG{QUIT} = 'interrupt'; # QUIT SIGNAL | |
$SIG{KILL} = 'interrupt'; # KILL SIGNAL | |
$SIG{TERM} = 'interrupt'; # TERM SIGNAL | |
if ($DAEMON) { # as a daemon | |
Proc::Daemon::Init( { | |
work_dir => '/var/run', | |
pid_file => 'lcd.pid', | |
child_STDOUT => ">$log_file", | |
child_STDERR => ">$err_file", | |
}); | |
#open STDERR, '>', $err_file or die $!; | |
#open STDOUT, '>', $log_file or die $!; | |
$|=1; # 標準出力をauto flush | |
} | |
$screen = SCREEN->new(); | |
$screen->init(); | |
} | |
sub run { # Proc:Daemon の実行ルーチン。イベントループを発生 | |
while(1) { | |
&action; | |
} | |
} | |
sub action { # run内のイベントループから呼ばれるルーチン | |
$screen->begin(16, 2); | |
$screen->backlight( $screen->{color}->{RED} ); | |
$screen->clear(); | |
$screen->cursor(); | |
$screen->message(encode('cp932', "Twitterニセツゾクチュウ\nPlease wait...")); | |
sleep 3; | |
# 〔Twitterのウォッチャー〕のウォッチャーを起動 | |
$event->{timer_reconnection}->(); | |
$event->{timer_tick}->(); | |
$event->{timer_keysense}->(); | |
$cv->recv; # ここでイベントループ成立:後ろにはいかない | |
$screen->clear(); | |
$screen->stop(); | |
exit; | |
} | |
sub interrupt { # Proc:Daemon の割り込み処理ルーチン | |
my $sig = shift; | |
setpgrp; # I *am* the leader | |
$SIG{$sig} = 'IGNORE'; | |
kill $sig, 0; # death to all-comers | |
$screen->clear(); | |
$screen->stop(); | |
die "killed by $sig"; | |
exit(0); | |
} | |
# | |
# その他のサブルーチン | |
# | |
sub sound { | |
my $file = shift; | |
system('play -q ' . "$script_dir/$file" . ' 2> /dev/null &'); | |
} | |
sub talk { | |
my $mes = shift; | |
$mes =~ s/</</g; | |
$mes =~ s/>/>/g; | |
$mes =~ s/&/&/g; | |
print "$mes\n"; | |
$mes =~ s/'/\\'/g; | |
$mes =~ s/\n/ /g; | |
if ( $TALK ) { | |
system('./aquestalkpi/AquesTalkPi -v f2 "' .$mes .'" | aplay -q'); | |
} | |
} | |
sub talk2 { | |
my $mes = shift; | |
$mes =~ s/</</g; | |
$mes =~ s/>/>/g; | |
$mes =~ s/&/&/g; | |
print "$mes\n"; | |
$mes =~ s/'/\\'/g; | |
$mes =~ s/\n/ /g; | |
return if $mes eq ''; | |
system('./aquestalkpi/AquesTalkPi "' .$mes .'" | aplay -q'); | |
} | |
# | |
# SCREENパッケージ | |
# Adafruit_CharLCDPlateを継承 | |
# | |
package SCREEN; | |
use base qw( Adafruit_CharLCDPlate ); | |
use strict; | |
use warnings; | |
use Encode; | |
sub new { | |
my $class = shift; | |
my %args = ( @_ ); | |
my $self = Adafruit_CharLCDPlate->new; | |
$self = { | |
%$self, | |
buffer => [], | |
pos => -1, | |
dialog => 0, | |
}; | |
return bless $self, $class; | |
} | |
sub write_byte { | |
my $self = shift; | |
my $mes = shift; | |
unless ($self->{dialog}) { | |
$self->set_pos( $self->{pos} ); | |
$self->message( $mes ); | |
$self->set_pos( $self->{pos} ); | |
} | |
$self->{buffer}->[$self->{pos}] = $mes; | |
} | |
sub show_dialog { | |
my $self = shift; | |
my $message = shift; | |
my $color = shift; | |
$message = encode('cp932', $message); | |
$self->{dialog} = 1; | |
$self->noCursor(); | |
$self->clear(); | |
$self->backlight( $self->{color}->{$color} ); | |
$self->message($message); | |
$listener->{timer_hidedialog} = AnyEvent->timer( | |
after => 3, | |
cb => sub { | |
$self->{dialog} = 0; | |
$self->backlight( $self->{color}->{WHITE} ); | |
for (my $i = 0; $i < 32; $i++) { | |
my $var = $self->{buffer}->[$i]; | |
if ( $var ) { | |
$self->set_pos( $i ); | |
$self->message( $var ); | |
} | |
} | |
$self->set_pos( $self->{pos} ); | |
$self->cursor(); | |
}, | |
); | |
} | |
sub set_pos { | |
my $self = shift; | |
my $pos = shift; | |
$self->setCursor( $pos % 16, int($pos / 16) ); | |
} | |
1; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment