diff --git a/TODO b/TODO new file mode 100644 index 0000000..5c675de --- /dev/null +++ b/TODO @@ -0,0 +1,33 @@ +* Support for brmwiki update notifications. + Watch the RSS feed. + +* Potentially some prettier icons and richer javascript stubs. + + +* Have the brmlab sketch do bi-directional TCP communication. + Currently, it only sends stuff and cannot receive any + instructions. This is pre-req for features below. + Alternatively, we may decide we need to switch back to + USB communication (with reliable USB host) if the ethernet + shield proves to be too unreliable. + +* Way to override status from web page. + To keep status LED and web page in sync, bi-directional + communication will be required. + +* Way to unlock door remotely (through some secure channel). + Bi-directional communication will be required. + + +* Webcam support. + We will need to figure out where to connect it. + +* Picture snapshot support. + Add a button to the panel that will make the webcam + take a snapshot - useful for letting the world know + who came in, or to publish random stuff. Post to twitpic + or some-such. + +* Live-feed support. + Turn webcam on/off based on the second switch. Route the + stream to some public place. diff --git a/brmd.pl b/brmd.pl new file mode 100644 index 0000000..54e917c --- /dev/null +++ b/brmd.pl @@ -0,0 +1,245 @@ +#!/usr/bin/perl + +use strict; +use warnings; +use POE qw(Component::IRC Component::Client::TCP Component::Server::HTTP); +use HTTP::Status qw/RC_OK/; + +our $channel = "#brmlab"; +our ($status, $record, $topic) = (0, 0, 'BRMLAB OPEN'); + +my $irc = POE::Component::IRC->spawn( + nick => 'brmbot', + ircname => 'The Brmlab Automaton', + server => 'irc.freenode.org', +) or die "Oh noooo! $!"; + +my $door = POE::Component::Client::TCP->new( + RemoteAddress => "192.168.1.3", + RemotePort => 23, + ServerInput => \&brmdoor_input, +) or die "Oh naaaay! $!"; + +my $web = POE::Component::Server::HTTP->new( + Port => 8088, + ContentHandler => { + "/brmstatus.html" => \&web_brmstatus_html, + "/brmstatus.js" => \&web_brmstatus_js, + "/brmstatus.png" => \&web_brmstatus_png, + "/" => \&web_index + }, + Headers => {Server => 'brmd/xxx'}, +) or die "Oh neee! $!"; + + + +POE::Session->create( + package_states => [ + main => [ qw(_default _start irc_001 irc_public irc_332 irc_topic) ], + ], + heap => { irc => $irc }, +); + +$poe_kernel->run(); + + +sub _start { + my $heap = $_[HEAP]; + + # retrieve our component's object from the heap where we stashed it + my $irc = $heap->{irc}; + + $irc->yield( register => 'all' ); + $irc->yield( connect => { } ); +} + +sub _default { + my ($event, $args) = @_[ARG0 .. $#_]; + my @output = ( "$event: " ); + + for my $arg (@$args) { + if ( ref $arg eq 'ARRAY' ) { + push( @output, '[' . join(', ', @$arg ) . ']' ); + } + else { + push( @output, "'$arg'" ); + } + } + print join ' ', @output, "\n"; +} + +sub status_str { + $status ? 'OPEN' : 'CLOSED'; +} + +sub record_str { + $record ? 'ON AIR' : 'OFF AIR'; +} + + +## Brmdoor + +sub brmdoor_input { + my $input = $_[ARG0]; + $input =~ /^(\d) (\d) (.*)$/ or return; + my ($cur_status, $cur_record, $brm) = ($1, $2, $3); + if ($cur_status != $status) { + $status = $cur_status; + my $st = status_str(); + $irc->yield (privmsg => $channel => "[brmstatus] update: \002$st" ); + my $newtopic = $topic; + if ($status) { + $newtopic =~ s/BRMLAB CLOSED/BRMLAB OPEN/g; + } else { + $newtopic =~ s/BRMLAB OPEN/BRMLAB CLOSED/g; + } + if ($newtopic ne $topic) { + $topic = $newtopic; + $irc->yield (topic => $channel => $topic ); + } + } + if ($cur_record != $record) { + $record = $cur_record; + my $st = record_str(); + $irc->yield (privmsg => $channel => "[brmvideo] update (TODO): \002$st" ); + } + if ($brm =~ s/^CARD //) { + print "from brmdoor: $input\n"; + if ($brm =~ /^UNKNOWN/) { + $irc->yield (privmsg => $channel => "[brmdoor] unauthorized access denied!" ); + } else { + $irc->yield (privmsg => $channel => "[brmdoor] unlocked by: \002$brm" ); + } + } +} + + +## Web interface + +sub disable_caching { + my ($response) = @_; + $response->push_header("Cache-Control", "no-cache, must-revalidate"); + $response->push_header("Expires", "Sat, 26 Jul 1997 05:00:00 GMT"); +} + +sub web_index { + my ($request, $response) = @_; + + my $sts = status_str(); + my $str = record_str(); + + $response->code(RC_OK); + $response->push_header("Content-Type", "text/html"); + disable_caching($response); + + $response->content(< +brmd + +brmlab +

brmd web interface

+

Enjoy the view!

+ + +EOT + ); + + return RC_OK; +} + +sub web_brmstatus_html { + my ($request, $response) = @_; + + my $bg = $status ? 'lightgreen' : '#DEE7EC'; + my $st = $status ? 'OPEN' : 'CLOSED'; + + $response->code(RC_OK); + $response->push_header("Content-Type", "text/html"); + disable_caching($response); + + $response->content(< +brmstatus + +

brmlab is $st

+ +EOT + ); + + return RC_OK; +} + +sub web_brmstatus_js { + my ($request, $response) = @_; + + $response->code(RC_OK); + $response->push_header("Content-Type", "text/javascript"); + disable_caching($response); + + $response->content(<; + close $img; + + $response->code(RC_OK); + $response->push_header("Content-Type", "image/png"); + disable_caching($response); + + $response->content($imgdata); + + return RC_OK; +} + + +## IRC + +sub irc_001 { + my $sender = $_[SENDER]; + + # Since this is an irc_* event, we can get the component's object by + # accessing the heap of the sender. Then we register and connect to the + # specified server. + my $irc = $sender->get_heap(); + + print "Connected to ", $irc->server_name(), "\n"; + + $irc->yield( join => $channel ); +} + +sub irc_public { + my ($sender, $who, $where, $what) = @_[SENDER, ARG0 .. ARG2]; + my $nick = ( split /!/, $who )[0]; + my $channel = $where->[0]; + + if ( my ($rot13) = $what =~ /^rot13 (.+)/ ) { + $rot13 =~ tr[a-zA-Z][n-za-mN-ZA-M]; + $irc->yield( privmsg => $channel => "$nick: $rot13" ); + } +} + +sub irc_332 { + my ($sender, $server, $str, $data) = @_[SENDER, ARG0 .. ARG2]; + $topic = $data->[1]; + print "new topic: $topic\n" +} + +sub irc_topic { + my ($sender, $who, $where, $what) = @_[SENDER, ARG0 .. ARG2]; + my $channel = $where; + $topic = $what; + print "new topic: $topic\n" +} diff --git a/brmdoor.php b/brmdoor.php deleted file mode 100644 index 29567b8..0000000 --- a/brmdoor.php +++ /dev/null @@ -1,91 +0,0 @@ - - - - -Brmdoor - - -

brmlab is

-
-> -> -
- - diff --git a/status-closed.png b/status-closed.png new file mode 100644 index 0000000..cdd95ba Binary files /dev/null and b/status-closed.png differ diff --git a/status-open.png b/status-open.png new file mode 100644 index 0000000..c84e8d3 Binary files /dev/null and b/status-open.png differ