added timeout to 3s for librfid tools
[Biblio-RFID.git] / scripts / RFID-JSONP-server.pl
1 #!/usr/bin/perl
2
3 =head1 NAME
4
5 RFID-JSONP-server - simpliest possible JSONP server which provides local web interface to RFID readers
6
7 =head1 USAGE
8
9   ./scripts/RFID-JSONP-server.pl [--debug] [--listen=127.0.0.1:9000] [--reader=filter]
10
11 =cut
12
13 use strict;
14 use warnings;
15
16 use Data::Dump qw/dump/;
17
18 use JSON::XS;
19 use IO::Socket::INET;
20 use LWP::UserAgent;
21 use URI;
22
23 my $debug = 1;
24 my $listen = '127.0.0.1:9000';
25 $listen = ':9000';
26 my $reader;
27
28 use Getopt::Long;
29
30 GetOptions(
31         'debug!'    => \$debug,
32         'listen=s', => \$listen,
33         'reader=s', => \$reader,
34 ) || die $!;
35
36 our $rfid_sid_cache;
37
38 sub rfid_borrower {
39         my $hash = shift;
40         if ( my $json = $rfid_sid_cache->{ $hash->{sid} } ) {
41                 return $json;
42         }
43         my $ua = LWP::UserAgent->new;
44         my $url = URI->new('http://ffzg.koha-dev.rot13.org:8080/cgi-bin/koha/ffzg/rfid-borrower.pl');
45         $url->query_form(
46                   RFID_SID => $hash->{sid}
47                 , OIB => $hash->{OIB}
48                 , JMBAG => $hash->{JMBAG}
49         );
50         warn "GET ",$url->as_string;
51         my $response = $ua->get($url);
52         if ( $response->is_success ) {
53                 my $json = decode_json $response->decoded_content;
54                 $rfid_sid_cache->{ $hash->{sid} } = $json;
55                 return $json;
56         } else {
57                 warn "ERROR ", $response->status_line;
58         }
59 }
60
61 use lib 'lib';
62 use Biblio::RFID::RFID501;
63 use Biblio::RFID::Reader;
64 my $rfid = Biblio::RFID::Reader->new( shift @ARGV );
65
66 my $index_html;
67 {
68         local $/ = undef;
69         $index_html = <DATA>;
70 }
71
72 my $server_url;
73
74 sub http_server {
75
76         my $server = IO::Socket::INET->new(
77                 Proto     => 'tcp',
78                 LocalAddr => $listen,
79                 Listen    => SOMAXCONN,
80                 Reuse     => 1
81         );
82                                                                   
83         die "can't setup server: $!" unless $server;
84
85         $server_url = 'http://' . $listen;
86         print "Server $0 ready at $server_url\n";
87
88         while (my $client = $server->accept()) {
89                 $client->autoflush(1);
90                 my $request = <$client>;
91
92                 warn "WEB << $request\n" if $debug;
93                 my $path;
94
95                 if ($request =~ m{^GET (/.*) HTTP/1.[01]}) {
96                         my $method = $1;
97                         my $param;
98                         if ( $method =~ s{\?(.+)}{} ) {
99                                 foreach my $p ( split(/[&;]/, $1) ) {
100                                         my ($n,$v) = split(/=/, $p, 2);
101                                         $param->{$n} = $v;
102                                 }
103                                 warn "WEB << param: ",dump( $param ) if $debug;
104                         }
105                         $path = $method;
106
107                         if ( $path eq '/' ) {
108                                 print $client "HTTP/1.0 200 OK\r\nContent-Type: text/html\r\n\r\n$index_html";
109                         } elsif ( $path =~ m{^/(examples/.+)} ) {
110                                 $path = $1; # FIXME prefix with dir for installation
111                                 my $size = -s $path;
112                                 warn "static $path $size bytes\n";
113                                 my $content_type = 'text/plain';
114                                 $content_type = 'application/javascript' if $path =~ /\.js/;
115                                 print $client "HTTP/1.0 200 OK\r\nContent-Type: $content_type\r\nContent-Length: $size\r\n\r\n";
116                                 {
117                                         local $/ = undef;
118                                         open(my $fh, '<', $path) || die "can't open $path: $!";
119                                         while(<$fh>) {
120                                                 print $client $_;
121                                         }
122                                         close($fh);
123                                 }
124                         } elsif ( $method =~ m{/scan} ) {
125                                 my @tags = $rfid->tags;
126                                 my $json = { time => time() };
127                                 foreach my $tag ( @tags ) {
128                                         my $hash = $rfid->to_hash( $tag );
129                                         $hash->{sid}  = $tag;
130                                         if ( $hash->{tag_type} eq 'SmartX' ) {
131                                                 my $borrower = rfid_borrower $hash;
132                                                 if ( exists $borrower->{error} ) {
133                                                         warn "ERROR ", dump($borrower);
134                                                 } else {
135                                                         $hash->{borrower} = $borrower->{borrower};
136                                                 }
137                                         } else {
138                                                 $hash->{security} = uc unpack 'H*', $rfid->afi( $tag );
139                                         }
140                                         push @{ $json->{tags} }, $hash;
141                                 };
142                                 warn "#### ", encode_json($json);
143                                 print $client "HTTP/1.0 200 OK\r\nContent-Type: application/json\r\n\r\n",
144                                         $param->{callback}, "(", encode_json($json), ")\r\n";
145                         } elsif ( $method =~ m{/program} ) {
146
147                                 my $status = 501; # Not implementd
148
149                                 foreach my $p ( keys %$param ) {
150                                         next unless $p =~ m/^(E[0-9A-F]{15})$/;
151                                         my $tag = $1;
152                                         my $content = Biblio::RFID::RFID501->from_hash({ content => $param->{$p} });
153                                         $content    = Biblio::RFID::RFID501->blank if $param->{$p} eq 'blank';
154                                         $status = 302;
155
156                                         warn "PROGRAM $tag $content\n";
157                                         $rfid->write_blocks( $tag => $content );
158                                         $rfid->write_afi(    $tag => chr( $param->{$p} =~ /^130/ ? 0xDA : 0xD7 ) );
159                                 }
160
161                                 print $client "HTTP/1.0 $status $method\r\nLocation: $server_url\r\n\r\n";
162
163                         } elsif ( $method =~ m{/secure(.js)} ) {
164
165                                 my $json = $1;
166
167                                 my $status = 501; # Not implementd
168
169                                 foreach my $p ( keys %$param ) {
170                                         next unless $p =~ m/^(E[0-9A-F]{15})$/;
171                                         my $tag = $1;
172                                         my $data = $param->{$p};
173                                         $status = 302;
174
175                                         warn "SECURE $tag $data\n";
176                                         $rfid->write_afi( $tag => hex($data) );
177                                 }
178
179                                 if ( $json ) {
180                                         print $client "HTTP/1.0 200 OK\r\nContent-Type: application/json\r\n\r\n",
181                                                 $param->{callback}, "({ ok: 1 })\r\n";
182                                 } else {
183                                         print $client "HTTP/1.0 $status $method\r\nLocation: $server_url\r\n\r\n";
184                                 }
185
186                         } else {
187                                 print $client "HTTP/1.0 404 Unkown method\r\n\r\n";
188                         }
189                 } else {
190                         print $client "HTTP/1.0 500 No method\r\n\r\n";
191                 }
192                 close $client;
193         }
194
195         die "server died";
196 }
197
198 http_server;
199
200 __DATA__
201 <html>
202 <head>
203 <title>RFID JSONP</title>
204 <script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.4.2/jquery.min.js"></script>
205 <style type="text/css">
206 .status {
207         background: #ff8;
208 }
209
210 .da {
211         background: #fcc;
212 }
213
214 .d7 {
215         background: #cfc;
216 }
217
218 label[for=pull-reader] {
219         position: absolute;
220         top: 1em;
221         right: 1em;
222         background: #eee;
223 }
224
225 </style>
226 <script type="text/javascript">
227
228 // mock console
229 if(!window.console) {
230         window.console = new function() {
231                 this.info = function(str) {};
232                 this.debug = function(str) {};
233         };
234 }
235
236
237 function got_visible_tags(data,textStatus) {
238         var html = 'No tags in range';
239         if ( data.tags ) {
240                 html = '<ul class="tags">';
241                 $.each(data.tags, function(i,tag) {
242                         console.debug( i, tag );
243                         html += '<li><tt class="' + tag.security + '">' + tag.sid;
244                         var borrowernumber = tag.content || tag.borrower.cardnumber;
245
246                         if ( borrowernumber ) {
247                                 html += ' <a href="http://ffzg.koha-dev.rot13.org:8080/cgi-bin/koha/members/member.pl?member=' + borrowernumber + '" title="lookup in Koha" target="koha-lookup">' + borrowernumber + '</a>';
248                                 html += '</tt>';
249 /*
250                                 html += '<form method=get action=program style="display:inline">'
251                                         + '<input type=hidden name='+tag.sid+' value="blank">'
252                                         + '<input type=submit value="Blank" onclick="return confirm(\'Blank tag '+tag.sid+'\')">'
253                                         + '</form>'
254                                 ;
255 */
256                         } else {
257                                 html += '</tt>';
258                                 html += ' <form method=get action=program style="display:inline">'
259                                         + '<!-- <input type=checkbox name=secure value='+tag.sid+' title="secure tag"> -->'
260                                         + '<input type=text name='+tag.sid+' size=12>'
261                                         + '<input type=submit value="Program">'
262                                         + '</form>'
263                                 ;
264                         }
265                 });
266                 html += '</ul>';
267         }
268
269         var arrows = Array( 8592, 8598, 8593, 8599, 8594, 8600, 8595, 8601 );
270
271         html = '<div class=status>'
272                 + textStatus
273                 + ' &#' + arrows[ data.time % arrows.length ] + ';'
274                 + '</div>'
275                 + html
276                 ;
277         $('#tags').html( html );
278         window.setTimeout(function(){
279                 scan_tags();
280         },200); // re-scan every 200ms
281 };
282
283 function scan_tags() {
284         console.info('scan_tags');
285         if ( $('input#pull-reader').attr('checked') )
286                 $.getJSON("/scan?callback=?", got_visible_tags);
287 }
288
289 $(document).ready(function() {
290                 $('input#pull-reader').click( function() {
291                         scan_tags();
292                 });
293                 $('input#pull-reader').attr('checked', true); // force check on load
294
295                 $('div#tags').click( function() {
296                         $('input#pull-reader').attr('checked', false);
297                 } );
298
299                 scan_tags();
300 });
301 </script>
302 </head>
303 <body>
304
305 <h1>RFID tags in range</h1>
306
307 <label for=pull-reader>
308 <input id=pull-reader type=checkbox checked=1>
309 active
310 </label>
311
312 <div id="tags">
313 RFID reader not found or driver program not started.
314 </div>
315
316 </body>
317 </html>