--- /dev/null
+<html>
+<head>
+<meta charset="utf-8"></meta>
+<title>RFID SIP2 Selfcheck</title>
+<script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.10.2/jquery.min.js"></script>
+<style type="text/css">
+.status {
+ background: #ff8;
+}
+
+.da {
+ background: #fcc;
+}
+
+.d7 {
+ background: #cfc;
+}
+
+label[for=pull-reader] {
+ position: absolute;
+ top: 1em;
+ right: 1em;
+ background: #eee;
+}
+
+,page {
+ font-size: 300%;
+ border: 10px solid #ccc;
+
+}
+
+#debug {
+ position: fixed;
+ width: 20em;
+ top: 0;
+ right: 0;
+}
+
+input[type=button] {
+ padding-top: 2em;
+ display: block;
+ width: 100%;
+ height: 10em;
+}
+
+</style>
+<script type="text/javascript" src="/examples/selfcheck.js"></script>
+</head>
+<body>
+
+<div id="debug">
+
+<label for=pull-reader>
+<input id=pull-reader type=checkbox checked=1>
+active
+</label>
+
+<div id="tags">
+RFID reader not found or driver program not started.
+</div>
+
+</div>
+
+<h1>Knjižnica Filozofskog fakulteta</h1>
+
+<div class="page" id="start">
+
+<!--
+Put borrower card on a reader to borrow books
+-->
+
+Ako želite posuditi knjigu, najprije stavite iskaznicu na za to previđeno mjesto.
+
+<hr>
+
+<!--
+Select checkin button to return books.
+-->
+
+Ako želite vratiti knjigu, pristisnite ovaj gumb.
+
+<input name=checkin type=button value="Povrat" onClick="change_page('checkin')">
+</div>
+
+<div class="page" id="borrower_check">
+<!--
+Fetching borrower
+-->
+Provjera korisničnog računa...
+<p>
+Možete ukloniti iskaznicu.
+<span class="borrower_number"></span>
+</div>
+
+<div class="page" id="borrower_info">
+<!--
+Borrower:
+-->
+<h2>Informacije o članu</h2>
+<div class="borrower_name"></div>
+<!--
+<div class="borrower_email"></div>
+-->
+<div class="borrower_number"></div>
+
+<h2>Dokumenti</h2>
+
+<ul>
+<li>Posuđeno: <span class="hold_items">0</span>
+<li>Kašnjenje: <span class="overdue_items">0</span>
+<!-- Koha doesn't support charged items or fine items
+<li>Trenutno rezervacija: 0
+<li>Spremno za podizanje: 0
+-->
+</ul>
+
+<h2>Dugovanje</h2>
+
+Ostatak: 0,00 kn
+
+<h2>Poruke</h2>
+
+Greeting from Koha.
+
+<input type="button" value="OK" onClick="change_page(circulation_type)">
+
+
+</div>
+
+<div class="page" id="circulation">
+<!--
+Put books to borrow on reader pad.
+-->
+Postavite knjige na predviđeno mjesto ispred monitora.
+
+<div class="checkout">Posuđeno:</div>
+<div class="checkin">Vraćeno:</div>
+
+<ul id="books">
+</ul>
+
+Dokumenata: 0
+
+<div style="display: block">
+<input type="button" value="Završi" onClick="change_page('end')">
+<input class="checkout" type="button" value="Informacije o članu" onClick="change_page('borrower_info')">
+</div>
+
+</div>
+
+<div class="page" id="end">
+
+Hvala što ste koristili selfcheck sustav.
+
+</div>
+
+<div class="page" id="error">
+
+Greška. Odnesite knjige na pult.
+
+</div>
+
+</body>
+</html>
--- /dev/null
+
+// mock console
+if(!window.console) {
+ window.console = new function() {
+ this.info = function(str) {};
+ this.error = function(str) {};
+ this.debug = function(str) {};
+ };
+}
+
+var state;
+var scan_timeout;
+
+function change_page(new_state) {
+ if ( state != new_state ) {
+
+ if ( new_state == 'checkin' ) {
+ new_state = 'circulation'; // page has different name
+ $('.checkout').hide();
+ $('.checkin').show();
+ } else if ( new_state == 'checkout' ) {
+ new_state = 'circulation'; // page has different name
+ $('.checkout').show();
+ $('.checkin').hide();
+ }
+
+ state = new_state;
+
+ $('.page').each( function(i,el) {
+ if ( el.id != new_state ) {
+ $(el).hide();
+ } else {
+ $(el).show();
+ }
+ });
+ console.info('change_page', state);
+
+ if ( state == 'start' ) {
+ start_scan();
+ }
+ if ( state == 'error' ) {
+ // FIXME: implement timeout and go back to start
+ }
+ }
+}
+
+function got_visible_tags(data,textStatus) {
+ var html = 'No tags in range';
+ if ( data.tags ) {
+ html = '<ul class="tags">';
+ $.each(data.tags, function(i,tag) {
+ console.debug( i, tag );
+ html += '<li><tt class="' + tag.security + '">' + tag.sid;
+ var content = tag.content
+ if (typeof content === undefined && typeof tag.borrower !== undefined)
+ content = tag.borrower.cardnumber;
+
+ var is_book = false;
+ var is_borrower = false;
+
+ if ( content ) {
+ var link;
+ if ( content.length = 10 && content.substr(0,3) == 130 ) { // book
+ is_book = true;
+ link = 'catalogue/search.pl?q=';
+ } else if ( content.length == 12 && content.substr(0,2) == 20 ) {
+ is_borrower = true;
+ link = 'members/member.pl?member=';
+ } else {
+ html += '<b>UNKNOWN TAG</b> '+content;
+ }
+
+ if ( link ) {
+ html += ' <a href="http://koha.example.com:8080/cgi-bin/koha/'
+ + link + content
+ + '" title="lookup in Koha" target="koha-lookup">' + content + '</a>';
+ + '</tt>';
+ }
+
+ console.debug( 'calling', state, content );
+ window[state]( content ); // call function with barcode
+
+ }
+ });
+ html += '</ul>';
+ }
+
+ var arrows = Array( 8592, 8598, 8593, 8599, 8594, 8600, 8595, 8601 );
+
+ html = '<div class=status>'
+ + textStatus
+ + ' &#' + arrows[ data.time % arrows.length ] + ';'
+ + '</div>'
+ + html
+ ;
+ $('#tags').html( html );
+ scan_timeout = window.setTimeout(function(){
+ scan_tags();
+ },200); // re-scan every 200ms
+};
+
+function scan_tags() {
+ if ( $('input#pull-reader').attr('checked') ) {
+ console.info('scan_tags');
+ $.getJSON("/scan?callback=?", got_visible_tags);
+ }
+}
+
+function start_scan() {
+ $('input#pull-reader').attr('checked', true);
+ scan_tags();
+}
+
+function stop_scan() {
+ $('input#pull-reader').attr('checked', '');
+}
+
+$(document).ready(function() {
+ $('input#pull-reader').click( function() {
+ scan_tags();
+ });
+
+ $('div#tags').click( function() {
+ $('input#pull-reader').attr('checked', false);
+ } );
+
+ change_page('start');
+});
+
+function fill_in( where, value ) {
+ $('.'+where).each(function(i, el) {
+ $(el).html(value);
+ });
+
+}
+
+/* Selfcheck state actions */
+
+var borrower_cardnumber;
+var circulation_type;
+var book_barcodes;
+
+function start( cardnumber ) {
+
+ if ( cardnumber.length != 12 || cardnumber.substr(0,2) != "20" ) {
+ console.error(cardnumber, ' is not borrower card');
+ return;
+ }
+
+ borrower_cardnumber = cardnumber;
+ circulation_type = 'checkout';
+ book_barcodes = {};
+
+ change_page('borrower_check');
+}
+
+function borrower_check() {
+
+ stop_scan();
+
+ fill_in( 'borrower_number', borrower_cardnumber );
+
+ $.getJSON('/sip2/patron_info/'+borrower_cardnumber)
+ .done( function( data ) {
+ console.info('patron', data);
+ fill_in( 'borrower_name', data['AE'] );
+ fill_in( 'borrower_email', data['BE'] );
+ fill_in( 'hold_items', data['fixed'].substr( 2 + 14 + 3 + 18 + ( 0 * 4 ), 4 ) ) * 1;
+ fill_in( 'overdue_items', data['fixed'].substr( 2 + 14 + 3 + 18 + ( 1 * 4 ), 4 ) ) * 1;
+ change_page('borrower_info');
+ }).fail( function(data) {
+ change_page('error');
+ });
+}
+
+function borrower_info() {
+ // nop
+}
+
+function circulation( barcode ) {
+ if ( barcode
+ && barcode.length == 10
+ && barcode.substr(0,3) == 130
+ && ! book_barcodes[barcode]
+ ) { // book, not seen yet
+ $.getJSON('/sip2/'+circulation_type+'/'+borrower_cardnumber+'/'+barcode , function( data ) {
+ console.info( circulation_type, data );
+ $('ul#books').append('<li>' + data['AJ'] + ' <small>' + data['AF'] + '</small></li>');
+ book_barcodes[ barcode ] = 1;
+ console.debug( book_barcodes );
+ }).fail( function() {
+ change_page('error');
+ });
+ }
+}
+
use IO::Socket::INET;
use LWP::UserAgent;
use URI;
+use POSIX qw(strftime);
+use Encode;
my $debug = 1;
my $listen = '127.0.0.1:9000';
my $koha_url = 'http://ffzg.koha-dev.rot13.org:8080';
# internal URL so we can find local address of machine and vmware NAT
my $rfid_url = 'http://rfid.koha-dev.vbz.ffzg.hr';
+my $sip2 = {
+ server => '10.60.0.11:6002', # must be IP!
+# user => 'sip2-user',
+# password => 'sip2-passwd',
+ user => 'sip2user',
+ password => 'viva2koha',
+ loc => 'FFZG',
+};
use Getopt::Long;
}
}
+
+sub sip2_message {
+ my $send = shift;
+
+ my $sock = $sip2->{sock} || die "no sip2 socket";
+
+ local $/ = "\r";
+
+ $send .= "\r" unless $send =~ m/\r$/;
+ warn "SIP2 >>>> ",dump($send), "\n";
+ print $sock $send;
+ $sock->flush;
+
+ my $expect = substr($send,0,2) | 0x01;
+
+ my $in = <$sock>;
+ $in =~ s/^\n//;
+ warn "SIP2 <<<< ",dump($in), "\n";
+
+ die "expected $expect" unless substr($in,0,2) != $expect;
+
+ $in =~ s/\r$//;
+
+ my $hash;
+ if ( $in =~ s/^([0-9\s]+)// ) {
+ $hash->{fixed} = $1;
+ }
+ foreach ( split(/\|/, $in ) ) {
+ my ( $f, $v ) = ( $1, $2 ) if m/([A-Z]{2})(.+)/;
+ $hash->{$f} = decode('utf-8',$v);
+ }
+
+ warn "# sip2 hash response ",dump($hash);
+
+ return $hash;
+}
+
+if ( my $server = $sip2->{server} ) {
+ my $sock = $sip2->{sock} = IO::Socket::INET->new( $server ) || die "can't connect to $server: $!";
+ warn "SIP2 server ", $sock->peerhost, ":", $sock->peerport, "\n";
+
+ # login
+ if ( sip2_message("9300CN$sip2->{user}|CO$sip2->{password}|")->{fixed} !~ m/^941/ ) {
+ die "SIP2 login failed";
+ }
+
+}
+
use lib 'lib';
use Biblio::RFID::RFID501;
use Biblio::RFID::Reader;
my $size = -s $path;
warn "static $path $size bytes\n";
my $content_type = 'text/plain';
- $content_type = 'application/javascript' if $path =~ /\.js/;
+ $content_type = 'application/javascript' if $path =~ /\.js$/;
+ $content_type = 'text/html' if $path =~ /\.html$/;
print $client "HTTP/1.0 200 OK\r\nContent-Type: $content_type\r\nContent-Length: $size\r\n\r\n";
{
local $/ = undef;
print $client "HTTP/1.0 $status $method\r\nLocation: $server_url\r\n\r\n";
}
+ } elsif ( $method =~ m{/sip2/(\w+)/(.+)} ) {
+ my ( $method, $args ) = ( $1, $2 );
+ warn "SIP2: $method [$args]";
+
+ my $ts = strftime('%Y%m%d %H%M%S', localtime());
+ my $loc = $sip2->{loc} || die "missing sip->{loc}";
+ my $password = $sip2->{password} || die "missing sip->{password}";
+
+ my $hash;
+
+ if ( $method eq 'patron_info' ) {
+ my $patron = $args;
+ $hash = sip2_message("63000${ts} AO$loc|AA$patron|AC$password|");
+
+ } elsif ( $method eq 'checkout' ) {
+ my ($patron,$barcode) = split(/\//, $args, 2);
+ $hash = sip2_message("11YN${ts} AO$loc|AA$patron|AB$barcode|AC$password|BON|BIN|");
+
+ } elsif ( $method eq 'checkin' ) {
+ my $barcode = $args;
+ $hash = sip2_message("09N${ts}${ts}AP|AO${loc}|AB$barcode|AC|BIN|");
+ } else {
+ print $client "HTTP/1.0 500 $method not implemented\r\n\r\n";
+ }
+
+ if ( $hash ) {
+ print $client "HTTP/1.0 200 OK\r\nContent-Type: application/json\r\n\r\n",
+ encode_json( $hash );
+ }
+
} else {
print $client "HTTP/1.0 404 Unkown method\r\n\r\n";
}