#!/usr/bin/perl
#
# Aladin -> PC data transfer tool for Linux/FreeBSD
#    Copyright (C) 1999,2000  kuro@neko.ac
#
#    This program is free software; you can redistribute it and/or modify
#    it under the terms of the GNU General Public License as published by
#    the Free Software Foundation; either version 2 of the License, or
#    (at your option) any later version.
#
#    This program is distributed in the hope that it will be useful,
#    but WITHOUT ANY WARRANTY; without even the implied warranty of
#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#    GNU General Public License for more details.
#
#    You should have received a copy of the GNU General Public License
#    along with this program; if not, write to the Free Software
#    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
#
# Edition History:
#    Created 99/10/05 Distribution version 1.0 by kuro@neko.ac
#            99/10/09              Version 1.1 by kuro@neko.ac
#                     # modified:  check sum and end of profile data
#            99/10/13              Version 1.2 by kuro@neko.ac
#                     # modified:  sysread instead of read from Aladin
#            99/10/17              Version 1.3 by kuro@neko.ac
#                     # modified:  plot warning info below profile graph
#            99/11/30              Version 1.4 by kuro@neko.ac
#                     # modified:  lpr bug corrected
#            99/12/08              Version 1.5 by kuro@neko.ac
#                     # modified:  safe rm implemented
#            99/12/12              Version 1.6 by kuro@neko.ac
#                     # modified:  many typos corrected
#            99/12/18              Version 1.7 by kuro@neko.ac
#                     # modified:  bug of logbook disp. etc. corrected
#            00/01/10              Version 1.8 by kuro@neko.ac
#                     # modified:  bottom time: figure of hundreds fixed
#            00/01/12              Version 1.9 by kuro@neko.ac
#                     # modified:  put for datatrak thru dosemu impremented
#            00/01/20              Version 1.10 by kuro@neko.ac
#                     # modified:  autodetect Nitrox. several bug fix.
#            00/01/23              Version 1.11 by kuro@neko.ac
#                     # modified:  get bug removed.
#            00/02/06              Version 1.12 by kuro@neko.ac
#                     # modified:  filename naming scheme changed.
#            00/02/16              Version 1.13 by kuro@neko.ac
#                     # modified:  plot graph bug!!
#            00/02/29              Version 1.14 by kuro@neko.ac
#                     # modified:  Air comsumption changed for Aladin Air.
#            00/03/01              Version 1.15 by kuro@neko.ac
#                     # modified:  Bug on profile display for O2 corrected.
#            00/03/21              Version 1.16 by kuro@neko.ac
#                     # modified:  Msb of $PROFILEEND is garbage!
#            00/06/09              Version 1.17 by kuro@neko.ac
#                     # modified:  Last two received byte from Aladin are fake.
#                     #            Only serial ID is checked to identify
#                     #            each Aladin.
#            00/09/29              Version 1.18 by kuro@neko.ac
#                     # modified:  Plotting graph for O2 divecomputer
#                     #            has a wrong assumption on end of datum.
#            00/10/27              Version 1.19 by kuro@neko.ac
#                     # modified:  Bug in baudrate setting when output
#                     #            to $DOSEmuSerial is corrected.

##########################
# configurable parameters
#
$GnuPlotTmp     = "/tmp/gnuplottmp$$";
$GnuPlotPs      = "/tmp/gnuplotps$$";
$PagerTmp       = "/tmp/pager$$";
$PrintPsCommand = "system \"cp \$GnuPlotPs ../ps/\$DIVE\"";
$Pager          = "less";
$DiveNoOffset   = 0;
$Device         = "/dev/pilot";
$DataDir        = "~/datadir";
$Info           = ".info";
$BuggyPro       = 0;
#$DOSEmuSerial   = "/dev/dosemuserial";
$DOSEmuSerial   = "/dev/pilot";
#$DOSEmuSerial   = "/dev/ttyzf";
##########################

if ($ARGV[0]) {
    eval `cat $ARGV[0]`;
} elsif (-f "$ENV{HOME}/.aladinrc") {
    eval `cat $ENV{HOME}/.aladinrc`;
} else {
    print STDERR "Warning: .aladinrc missing.\n";
}

$DataDir =~ s{^~([^/]*)}{$1?(getpwnam($1))[7]:$ENV{HOME}}ex;
if (! -d $DataDir) {
    mkdir $DataDir, oct('0755') || die "Can't make datadir\n";
}
chdir $DataDir || die "Can't cd to datadir\n";

$Nitrox         = 0;
$NormalAir      = 0;
$TIMECORR = 0;
if (-f $Info) {
    open(INFO, "<$Info");
    read(INFO, $MACHINEID, 52);  # specific values for an Aladin
    $u = substr($MACHINEID, 0, 1);
    $u = unpack('C', $u);
    if ($u == 0xa4) {
        $Nitrox = 3;
    }
    if ($u == 0xf4 || $u == 0xff) {
        $Nitrox = 2;
    }
    if ($u == 0x1c) {
        $NormalAir = 1;
    }
    read(INFO, $b, 4);
    $t = unpack('N', $b) >> 1;
    read(INFO, $b, 4);
    $u = unpack('N', $b);
    $TIMECORR = $u - $t;
    close(INFO);
}

$NEXT = "";
while (1) {
    print STDERR "> ";
    if (! ($_ = <STDIN>)) {
        exit(0);
    }
    /^\s*(.*?)\s*$/;
    $_ = $1;
    if (/^plot\s+([0-9]+)$/) {
        $PrintPs = 0;
        plotgraph($1);
        next;
    } elsif (/^lpr\s+([0-9]+)$/) {
        $PrintPs = 1;
        plotgraph($1);
        next;
    } elsif (/^get$/) {
        receive();
    } elsif (/^([0-9]+)$/) {
        logdata($1);
        next;
    } elsif (/^dump\s+(.+?)$/) {
        readwithpager($1);
    } elsif (/^ls$/) {
        listdir();
    } elsif (/^rm\s+(.+?)$/) {
        remove($1);
    } elsif (/^put\s+(.+?)$/) {
        dosemusend($1);
    } elsif (/^help$/) {
        help();
    } elsif (/^quit$/ or /^exit$/ or /^bye$/) {
        exit(0);
    } elsif (/^$/) {
        if ($NEXT) {
            if ($LOG == 0) {
                plotgraph($NEXT);
            } else {
                logdata($NEXT);
            }
            next;
        }
    } else {
        print STDERR "Illegal command. Type \"help\" to see usage.\n"; 
    }
    $NEXT = "";
}

sub help {
    print STDERR "plot <num>     Display a depth graph of No. <num> dive.\n";
    print STDERR "lpr <num>      Print out a depth graph to PS Printer.\n";
    print STDERR "get            Receive data from Aladin.\n";
    print STDERR "<num>          Logbook for No. <num> dive.\n";
    print STDERR "dump <file>    Show all the contents of <file> (for debug).\n";
    print STDERR "ls             List file names.\n";
    print STDERR "rm <file>      Remove <file>.\n";
    print STDERR "put <file>     Send <file> to DataTrak running under dosemu.  (for debug)\n";
    print STDERR "help           Print this usage.\n";
    print STDERR "quit           Exit the program.\n";
}

sub max {
    return ($_[0] > $_[1]) ? $_[0] : $_[1];
}

sub listdir {
    open(LS, "ls -1 -a |");
    @ls = <LS>;
    close(LS);
    @contents = ();
    foreach (@ls) {
        chop;
        $file = $_;
        if (/^\.{1,2}$/) {
            next;
        }
        if ($file eq $Info) {
            $line = sprintf "0     %-26s", "(information file)";
            open(INFO, "<$Info");
            seek(INFO, 56, 0);
            read(INFO, $b, 4);
            close(INFO);
            $line .= mylocaltime(unpack('N', $b)) . "  $Info\n";
            push(@contents, $line);
            next;
        } 
        if (-s $file != 2048) {
            next;
        }
        analyze0($file);
        $end = $TOTALDIVES;
        $logbegin = max($end - 36, 1);
        $end += $DiveNoOffset;
        $logbegin += $DiveNoOffset;
        $profilebegin = $end - $PROFILES + 1;
        $slogbegin = sprintf "%d", $logbegin;
        $send = sprintf "%d", $end;
        $sprofilebegin = sprintf "%d", $profilebegin;
        $line = sprintf "%5d ", $TOTALDIVES;
        $line .= sprintf "%-11s ", $slogbegin . ' - ' . $send;
        if ($PROFILES == 0) {
            $line .= "no profiles";
        } else {
            $line .= sprintf "%-11s   ", $sprofilebegin . ' - ' . $send;
        }
        $line .= "$RECVTIME  $file\n";
        push(@contents, $line);
    }
    @contents = sort numerically @contents;
    open PAGERTMP, ">$PagerTmp";
    print PAGERTMP "LOGS        PROFILES      UPDATE TIME        FILENAME\n";
    foreach (@contents) {
        print PAGERTMP substr $_, 6;
    }
    close(PAGERTMP);
    if (fork) {
        # parent here.
        wait();
    } else {
        # child here.
        exec "$Pager $PagerTmp";
    }
    unlink $PagerTmp;
}

sub plotgraph {
    sub makedata {
        open(IN, "<$_[0]");
        read(IN, $buf, 2048);
        close(IN);
        @buf = unpack('C2048', $buf);
        open(TMP1, ">$GnuPlotTmp");
        select TMP1;
        if ($PrintPs) {
            print "set terminal postscript\n";
            print "set output \"$GnuPlotPs\"\n";
        }
        print "set nokey\n";
        print "set multiplot\n";
        print "set size 1,0.85\n";
        print "set origin 0,0.15\n";
        print "set title \"Dive No.$_[1]\"\n";
        print "set grid y x2\n";
        print "set noxtics\n";
        print "set x2tics nomirror 0,5,240\n";
        print "set format x2 \"\"\n";      
        print "set ytics -100,5,0\n";
        print "set format y \"%4.0f\"\n";
        print "set bmargin 0\n";
        print "plot '-' with lines\n";
    
        $i = $PROFILEEND + 1; $i %= 0x600;
        for ($j = 0; $j < $_[1]-$DiveNoOffset-$TOTALDIVES+$PROFILES; $j++) {
            while($buf[$i++] != 0xff) {
                $i %= 0x600;
            }
        }
        $startaddr = $i;
        $i += 22 + $Nitrox;             # skip decompression info (22 bytes)
        $i %= 0x600;
        $base = $i;
        print "0 0\n";
        for ($j = 1; ; $j++) {
            # there are 3 data a minute.
            last if ($i == ($PROFILEEND +1)%0x600 || $i == $PROFILEEND);
            $u = $buf[$i++]; $i %= 0x600;
            last if ($u == 0xff);
            $u = $u * 256 + $buf[$i++]; $i %= 0x600;
            printf "%f %f\n", $j/3.0, -($u >> 6) * 5.0 / 32.0; # depth (m)
            if ($j % 3 == 0 && $i != ($PROFILEEND +1)%0x600) {
                $u = $buf[$i++]; $i %= 0x600;
                last if $u == 0xff; 
                if ($Nitrox == 3 && $i != ($PROFILEEND +1)%0x600) {
                    $i++; $i %= 0x600;
                }
            }
        }
        $xrange = $j / 3.0;
        printf "%f 0\n", $xrange;
        print "e\n";
        print "set size 1,0.15\n";
        print "set origin 0,0\n";
        print "set title\n";
        print "set grid\n";
        print "set nox2tics\n";
        print "set xtics nomirror 0,5,240\n";
        print "set format x\n";
        print "set ytics 0,1,5\n";
        print "set ytics (\"work\" 1, \"rbt \" 2, \"asnt\" 3, \"deco\" 4)\n";
        print "set grid y x\n";
        print "set xrange [0:$xrange]\n";
        print "set yrange [0.4:4.6]\n";
        print "set bmargin\n";
        print "set tmargin 0\n";
        print "plot '-' with linespoints 1 3\n";
    
        $i = $startaddr;
        $i += 22 + $Nitrox;          # skip decompression info (22 bytes)
        $i %= 0x600;
        print "\n";
        for ($j = 1; ; $j++) {
            # there are 3 data a minute.
            last if ($i == ($PROFILEEND + 1)%0x600 || $i == $PROFILEEND);
            $u = $buf[$i++]; $i %= 0x600;
            last if ($u == 0xff);
            $u = $buf[$i++]; $i %= 0x600;
            # print bcheck($u & 0x3f, '  thcard'); 
            if ($u & 0x10) {
                printf "%f %f\n", $j/3.0, 1;
            } else {
                print "\n";
            }
            if ($j % 3 == 0&&$i!=($PROFILEEND+1)%0x600) {
                $u = $buf[$i++]; $i %= 0x600;
                last if $u == 0xff; 
                if ($Nitrox == 3 && $i != ($PROFILEEND +1)%0x600) {
                    $i++; $i %= 0x600;
                }
            }
        }
        $i = $startaddr;
        $i += 22 + $Nitrox;            # skip decompression info (22 bytes)
        $i %= 0x600;
        print "\n";
        for ($j = 1; ; $j++) {
            # there are 3 data a minute.
            last if ($i == ($PROFILEEND + 1)%0x600 || $i == $PROFILEEND);
            $u = $buf[$i++]; $i %= 0x600;
            last if ($u == 0xff);
            $u = $buf[$i++]; $i %= 0x600;
            if ($u & 2) {
                printf "%f %f\n", $j/3.0, 2;
            } else {
                print "\n";
            }
            if ($j % 3 == 0 && $i != ($PROFILEEND +1)%0x600) {
                $u = $buf[$i++]; $i %= 0x600;
                last if $u == 0xff; 
                if ($Nitrox == 3 && $i != ($PROFILEEND +1)%0x600) {
                    $i++; $i %= 0x600;
                }
            }
        }
        $i = $startaddr;
        $i += 22 + $Nitrox;            # skip decompression info (22 bytes)
        $i %= 0x600;
        print "\n";
        for ($j = 1; ; $j++) {
            # there are 3 data a minute.
            last if ($i == ($PROFILEEND + 1)%0x600 || $i == $PROFILEEND);
            $u = $buf[$i++]; $i %= 0x600;
            last if ($u == 0xff);
            $u = $buf[$i++]; $i %= 0x600;
            if ($u & 4) {
                printf "%f %f\n", $j/3.0, 3;
            } else {
                print "\n";
            }
            if ($j % 3 == 0 && ($i != ($PROFILEEND+1)%0x600)) {
                $u = $buf[$i++]; $i %= 0x600;
                last if $u == 0xff; 
                if ($Nitrox == 3 && $i != ($PROFILEEND +1)%0x600) {
                    $i++; $i %= 0x600;
                }
            }
        }
        $i = $startaddr;
        $i += 22 + $Nitrox;            # skip decompression info (22 bytes)
        $i %= 0x600;
        print "\n";
        for ($j = 1; ; $j++) {
            # there are 3 data in a minute.
            last if ($i == ($PROFILEEND + 1)%0x600 || $i == $PROFILEEND);
            $u = $buf[$i++]; $i %= 0x600;
            last if ($u == 0xff);
            $u = $buf[$i++]; $i %= 0x600;
            if ($u & 8) {
                printf "%f %f\n", $j/3.0, 4;
            } else {
                print "\n";
            }
            if ($j % 3 == 0 && ($i != ($PROFILEEND +1)%0x600)) {
                $u = $buf[$i++]; $i %= 0x600;
                last if $u == 0xff; 
                if ($Nitrox == 3 && $i != ($PROFILEEND +1)%0x600) {
                    $i++; $i %= 0x600;
                }
            }
        }
        print "e\n";
        print "set nomultiplot\n";
        if ((!$PrintPs) && $ENV{DISPLAY}) {
            print "pause -1 \"Hit return key to dismiss\"\n";
        }
        close(TMP1);
        select STDOUT;
    }
    
    if (!$_[0]) {
        print STDERR "Please specify a Dive No.\n";
        $NEXT = "";
        return;
    }
    open(LS, "ls -1 |");
    @ls = <LS>;
    close(LS);
    foreach (@ls) {
        chop;
        $file = $_;
        if (-s $file != 2048) {
            next;
        }
        analyze0($file);
        $end = $TOTALDIVES + $DiveNoOffset;
        $profilebegin = $end - $PROFILES + 1;
        if ($profilebegin <= $_[0] and $_[0] <= $end) {
            makedata($file, $_[0]);
            system "gnuplot $GnuPlotTmp";
            unlink $GnuPlotTmp;
            if ($PrintPs) {
                $DIVE = $_[0];
                eval $PrintPsCommand;
                unlink $GnuPlotPs;
            }
            $NEXT = $_[0] + 1; $LOG = 0;
            return; 
        }
    }
    print STDERR "No data\n";
    $NEXT = "";
}

sub readwithpager {
    if (analyze($_[0])) {
        return;
    }
    if (fork) {
        # parent here.
        wait();
    } else {
        # child here.
        exec "$Pager $PagerTmp";
    }
    unlink $PagerTmp;
}

sub analyze0 {
    open(IN, "<$_[0]");
    read(IN, $b, 2048);
    close(IN);
    $buf = "\0".substr($b,2031,1).substr($b,2030,1).substr($b,2029,1);
    $SERIAL = unpack('N', $buf);
    $buf = substr($b, 2032, 1);
    $BUTTERY = unpack('C', $buf) * 99 / 255;
    $buf = "\0" . substr($b, 2033, 3);
    $TOTALDIVES = unpack('N', $buf);
    $buf = substr($b, 2036, 1);
    $NEWESTLOG = unpack('C', $buf);
    $buf = substr($b, 2037, 1);
    $PROFILES = unpack('C', $buf);
    $PROFILES-- if $BuggyPro;
    $buf = substr($b, 2039, 1);
    $PROFILEEND = unpack('C', $buf) * 128;
    $buf = substr($b, 2038, 1);
    $PROFILEEND += unpack('C', $buf);
    $PROFILEEND = $PROFILEEND & 0x7ff;
    $buf = substr($b, 2040, 4);
    $RECVTIME = mylocaltime((unpack('N', $buf) >> 1) + $TIMECORR);
    $buf = substr($b, 2044, 2);
    $CHECKSUM = unpack('v', $buf);
}

sub logdata {
    sub makelog {
        open(IN, "<$_[0]");
        seek(IN, 0x600, 0);
        read(IN, $buf, 444);
        close(IN);
        @buf = unpack('C444', $buf);
    
        $i = ($_[1]+$NEWESTLOG-$TOTALDIVES + 36 - $DiveNoOffset)*12 % 444;
        $w = $buf[$i];                 # dive mode
        $v = $buf[$i+1];               # bottom time (bcd)
        printf "Logbook No. %5d  ", $_[1];
        print bcheck($w, '21Swd ra');
        print "\n";
        if ($w & 4) {
            printf "bottom time   = 1%s (min)\n", 
                pack('c2', $v / 16 + 0x30, $v % 16 + 0x30);
        } else {
            printf "bottom time   = %s (min)\n", 
                pack('c2', $v / 16 + 0x30, $v % 16 + 0x30);
        }
        $u = $buf[$i+2]*256+$buf[$i+3];                 # max depth
        printf "maximum depth = %3.1f (m)\n", ($u >> 6) * 10 / 64;
        if (($w & 2) == 0) {
            print "interval      = infty\n";
        } else {
            $u = $buf[$i+4];                 # interval time (bcd)
            printf "interval      = %s:", # hour
            pack('c2', $u / 16 + 0x30, $u % 16 + 0x30);
            $u = $buf[$i+5];
            printf "%s\n",                # minute
            pack('c2', $u / 16 + 0x30, $u % 16 + 0x30);
        }
        $u = $buf[$i+6];                 # used air
        if ($u == 0) {
            print "used air      = unknown\n";
        } else {
            printf "used air      = %d (bar)\n", $NormalAir ? $u * 1.37634409: $u;
        }
        $s = mylocaltime(
             (($buf[$i+7]*16777216+$buf[$i+8]*65536
               +$buf[$i+9]*256+$buf[$i+10])>>1) + $TIMECORR);
        printf "start time    = %s\n", $s;
        printf "temperature   = %2.1f (C)\n", $buf[$i+11] / 4;
    }

    if (!$_[0]) {
        print STDERR "Please specify a Dive No.\n";
        $NEXT = "";
        return;
    }
    open(LS, "ls -1 |");
    @ls = <LS>;
    close(LS);
    foreach (@ls) {
        chop;
        $file = $_;
        if (-s $file != 2048) {
            next;
        }
        analyze0($file);
        $end = $TOTALDIVES;
        $logbegin = max($end - 36, 1);
        $logbegin += $DiveNoOffset;
        $end += $DiveNoOffset;
        if ($logbegin <= $_[0] and $_[0] <= $end) {
            makelog($file, $_[0]);
            $NEXT = $_[0] + 1; $LOG = 1;
            return; 
        }
    }
    print STDERR "No data\n";
    $NEXT = "";
}

sub bcheck {
    my(@p, $i, $j, $v);
    @p = unpack('C8', $_[1]);
    $j = 0; $v = '';
    for ($i = 128; $i != 0; $i >>= 1) {
        $v .= ($_[0] & $i) ? chr($p[$j]) : ' ';
        $j++;
    }
    $v;
}

sub analyze {
    sub getn {
        my($i, $data);
        $data = 0;
        for ($i = 0; $i < $_[0]; $i++) {
            $data = $data * 256 + $Buf[$Offset];
            inc(1);
        }
        $data;
    }

    sub inc {
        if ($Offset < 0x600) {
            $Offset = ($Offset + $_[0] + 0x600) % 0x600;
        } elsif ($Offset < 0x600 + 444) {
            $Offset = ($Offset + $_[0] - 0x600 + 444) % 444 + 0x600;
        } else {
            $Offset += $_[0];
        }
    }

    if (! -f $_[0]) {
        printf STDERR "File %s not found.\n", $_[0];
        return -1;
    }
    
    open(IN, "<$_[0]");
    read(IN, $Buf, 2048);
    close(IN);
    @Buf = unpack('C2048', $Buf);

    open(PAGERTMP, ">$PagerTmp");
    select PAGERTMP;
    ##############
    # Misc infos #
    ##############
    $Offset = 0x7ed;
    # serial no. (does not match the 'external' serial)
    printf "hardware serial #%08d\n", getn(1) + getn(1) * 256 + getn(1) * 65536;
    # battery status
    printf "battery status = %2d(%%)\n", getn(1) * 99 / 255;
    # number of total dives the aladin have experienced
    $TOTALDIVES = getn(3);
    printf "total dives = %d\n", $TOTALDIVES;
    # location of the newest log data (in 12 bytes unit)
    $NEWESTLOG = getn(1);
    # number of profiles
    # Some Aladins have a bug!  They make this byte to be profiles + 1.
    $PROFILESSAVED = getn(1);
    # workaround (very slow)
    $PROFILES = 0;
    for ($i = 0; $i < 0x600; $i++) {
        $PROFILES++ if $Buf[$i] == 0xff;
    }
    if ($PROFILES != $PROFILESSAVED) {
        printf  "BUGGY. saved profiles = %d, actual profiles = %d\n", 
            $PROFILESSAVED, $PROFILES;
        $BuggyPro = 1;
    }
    printf "number of profiles = %d\n", $PROFILES;
    # end of profile data
    $PROFILEEND = getn(1);
    $tmp = getn(1);
    $PROFILEEND += $tmp*128;
    if ($tmp % 2) {
        print "UNEXPECTED!! Profile-end higher byte lsb = 1\n";    
    }
    if ($PROFILEEND & 0xf800) {
        print "Profile-end byte contains garbage in higher bits.\n";
    }
    $PROFILEEND = $PROFILEEND & 0x7ff;
    printf "end of profile data = 0x%04x\n", $PROFILEEND;
    # time of data reception.
    $RECVTIME = mylocaltime((getn(4) >> 1) + $TIMECORR);
    print "data reception time = $RECVTIME\n";
    # check sum
    $CHECKSUM = getn(1) + getn(1) * 256;
    printf "checksum = 0x%04x\n", $CHECKSUM;

    ######################################
    # dealing with depth and air profile #
    ######################################
    $Offset = $PROFILEEND; inc(1);
    while(getn(1) != 0xff) {
        ;
    }

ONE:
    for ($i = - $PROFILES; $i < 0; $i++) {
        printf "\n\nDepth Profile No.%d\n", 
            $TOTALDIVES + $i + 1 + $DiveNoOffset;
        inc(22 + $Nitrox);         # skip decompression info (22 bytes)
        for ($j = 20; ; $j += 20) {
            # there are 3 data a minute.
            $u = getn(1);
            next ONE if ($u == 0xff || $Offset == ($PROFILEEND+1)%0x600 
                                    || $Offset == ($PROFILEEND+2)%0x600);
            $u = $u * 256 + getn(1);
            printf "\n%1d:%02d:%02d %5.1f ", 
            $j / 3600, ($j % 3600) / 60, $j % 60, 
            ($u >> 6) * 10 / 64; # depth (m)
            # warning bits 
            # bit 5: tx error
            # bit 4: hard moving (Air series only)
            # bit 3: deco ceiling violation
            # bit 2: ascent speed
            # bit 1: remaining bottom time warning
            #        = approx. 5 min to 40 bar (Air series only)
            # bit 0: decompression
            print bcheck($u & 0x3f, '  thcard'); 
            if ($j % 60 == 0 && $Offset != $PROFILEEND) {
                $u = getn(1); # air and move
                next ONE if $u == 0xff;  # for Aladin AirX Nitrox...
                # work load (0 -- 7)
                # bit 6: higher bit
                # bit 5: |
                # bit 4: lower bit
                # skin cooling
                # bit 7: cold decrement 10%
                # bit 3: cold increment 10%
                # micro bubble (0 -- 7) if this value > 2 then 'Attention mode'
                # bit 2: higher bit
                # bit 1: |
                # bit 0: lower bit
                print bcheck($u, '+421-421');
                if ($Nitrox == 3 && $Offset != $PROFILEEND) {
                    getn(1);
                }
            }
        }
    }
    printf "\n";
    $Offset = 0x600;
    $logbook = max(1, $TOTALDIVES-36);
    inc(12 * ($NEWESTLOG - 1 - $TOTALDIVES + $logbook));
    for(; $logbook <= $TOTALDIVES; $logbook++) {
        $w = getn(1);                 # dive mode
        printf "\nLogbook No. %5d  ", $logbook + $DiveNoOffset;
        # high place diving flag (4 class) bits
        #            0m ---  900m (0)
        #          900m --- 1700m (1)
        #         1700m --- 2700m (2)
        #         2700m --- 4000m (3)
        # bit 7: higher bit
        # bit 6: lower bit
        #
        # bit 5: SOS flag bit
        # bit 4: work too hard (Air series only)
        # bit 3: decompression violation
        # bit 2: the figure of the number of the hundreds of bottom time. (0,1)
        # bit 1: repeated dive
        # bit 0: ascent speed warning
        print bcheck($w, '21Swd ra');
        print "\n";
        $v = getn(1);                 # bottom time (bcd)
        if ($w & 4) {
            printf "bottom time   = 1%s (min)\n",
            pack('c2', $v / 16 + 0x30, $v % 16 + 0x30);
        } else {
            printf "bottom time   = %s (min)\n",
            pack('c2', $v / 16 + 0x30, $v % 16 + 0x30);
        }
        $u = getn(2);                 # max depth
        printf "maximum depth = %3.1f (m)\n", ($u >> 6) * 10 / 64;
        printf "lower 6bits of depth %s\n", bcheck($u, '  543210');
        if (($w & 2) == 0) {
            $u = getn(1);
            printf "interval      = infty (garbage = %s:",
            pack('c2', $u / 16 + 0x30, $u % 16 + 0x30);
            $u = getn(1);
            printf "%s)\n",
            pack('c2', $u / 16 + 0x30, $u % 16 + 0x30); 
        } else {
            $u = getn(1);                 # interval time (bcd)
            printf "interval      = %s:", # hour
            pack('c2', $u / 16 + 0x30, $u % 16 + 0x30);
            $u = getn(1);
            printf "%s\n",                # minute
            pack('c2', $u / 16 + 0x30, $u % 16 + 0x30);
        }
        $u = getn(1);                 # used air
        if ($u == 0) {
            print "used air      = unknown\n";
        } else {
            printf "used air      = %d (bar)\n", $NormalAir ? $u * 1.37634409:  $u;
        }
        # start time
        $s = mylocaltime((getn(4) >> 1) + $TIMECORR);
        printf "start time    = %s\n", $s;
        # water temperature  
        printf "temperature   = %2.1f (C)\n", getn(1) / 4;
    }
    close(PAGERTMP);
    select STDOUT;
    return 0;
}

sub numerically { $a <=> $b; }

sub receive {
    sub readaladin {
        my($i);
### CONFIGURE: Please select either of following --
### (1)
#        require 'ioctl-types.ph';
### Note: In order to include ioctl-types.ph, some Linux system requires to do
### (cd /usr/include && h2ph *.h sys/*.h asm/*.h)
### (2)
        require 'sys/ioctl.ph';
### End CONFIGURE.
        use POSIX;
    
        $tio = POSIX::Termios->new;
        $tio->setiflag(0);
        $tio->setoflag(0);
        $tio->setlflag(0);
        $tio->setcflag(CS8|CLOCAL|CREAD);
        $tio->setispeed(B19200);
        $tio->setcc(VMIN, 1);
        $tio->setcc(VTIME, 0);
        $tio->setattr(fileno(ALADIN), TCSANOW);
        ioctl(ALADIN, &TIOCMBIC, pack('i', &TIOCM_RTS));
    
        for ($i = 0; $i < 3 ; $i++) {
            sysread(ALADIN, $buf, 1);
            if (unpack('C', $buf) != ord('U')) {
                $i = -1;
            }
        }
        sysread(ALADIN, $buf, 1);
        if (unpack('C', $buf) != 0) {
            return -1;
        }
        for ($i = 0; $i < 2046; $i++) {
            sysread(ALADIN, $buf, 1, $i);
        }
        @buf = unpack('C2046', $buf);
        # The following is for compatibility with data 
        # received by the older version of this script.
        $buf[2047] = 0; $buf[2046] = 255;
        # end
        for ($i = 0; $i < 2046; $i++) {
            $d = 0;
            $e = $buf[$i];
            for ($j = 0; $j < 8; $j++) {
                $d <<= 1;
                $d |= $e & 1;
                $e >>= 1;
            }
            $buf[$i] = $d;
        }
        $buf = pack('C2048', @buf);
        $checksum = unpack('%16C2044', $buf);
        $checksum += 0x01fe; $checksum %= 65536;
        $CHECKSUM = unpack('v', substr($buf, 2044, 2));
        if ($CHECKSUM != $checksum) {
            print STDERR "Checksum error\n";
            return -1;
        }
        return 0;
    }
    
    open(ALADIN, "<$Device");
    while (readaladin()) {
        print STDERR "Read error.  Please retry.\n";
    }
    close(ALADIN);
    
    unless (! -f $Info or substr($MACHINEID, 49, 3) eq substr($buf, 0x7ed, 3)) {
        print STDERR "panic: data from a different Aladin!\n";
        return;
    }
    open(LS, "ls -1 |");
    @ls = <LS>;
    close(LS);
    ($sec, $min, $hour, $mday, $mon, $year, $wday, $yday, $isdst)
        = localtime();
    $file = sprintf "%02d%02d%02d", $year % 100, $mon+1, $mday;
    @ls = grep(/^$file[0-9][0-9][0-9]\n$/, @ls);
    if (@ls) {
        @ls = sort numerically @ls;
        $ls[$#ls] =~ /^$file([0-9][0-9][0-9])\n$/;
        $file .= sprintf "%03d", $1 + 1;
    } else {
        $file .= '000';
    }
    open(WRITE, ">$file");
    print WRITE $buf;
    close(WRITE);
    $u = substr($buf, 1980, 1);
    $u = unpack('C', $u);
    if ($u == 0xa4) {
        $Nitrox = 3;
    }
    if ($u == 0xf4 || $u == 0xff) {
        $Nitrox = 2;
    }
    if ($u == 0x1c) {
        $NormalAir = 1;
    }
    open(INFO, ">$Info");
    print INFO substr($buf, 1980, 52);
    $t = substr($buf, 2040, 4);
    print INFO $t;
    print INFO pack('N', time());
    $TIMECORR = time() - (unpack('N', $t) >> 1);
    close(INFO);
    $MACHINEID = substr($buf, 1980, 52);
}

sub mylocaltime {
    my($s);
    $s = localtime($_[0]);
    $s =~ 
    /^[A-z]{3} ([A-z]{3} [ 0-9]{2} [0-9]{2}:[0-9]{2}):[0-9]{2} ([0-9]{4})$/;
    return "$1 $2";
} 

sub remove {
    if (! -f $_[0]) {
        printf STDERR "File %s not found.\n", $_[0];
        return;
    }
    analyze0($_[0]);
    $rmend = $TOTALDIVES;
    $rmpbegin = $rmend - $PROFILES + 1;
    $rmlbegin = $rmend - 36; $rmlbegin = 1 if $rmlbegin < 1;
    for ($i = 0; $i <= $rmend - $rmpbegin; $i++) {
        $pexists[$i] = 0;
    }
    for ($i = 0; $i <= $rmend - $rmlbegin; $i++) {
        $lexists[$i] = 0;
    }
    open(LS, "ls -1 |");
    @ls = <LS>;
    close(LS);
    foreach (@ls) {
        chop;
        $file = $_;
        if ($file eq $_[0]) {
            next;
        }
        if (-s $file != 2048) {
            next;
        }
        analyze0($file);
        $end = $TOTALDIVES;
        $pbegin = $end - $PROFILES + 1;
        $lbegin = $end - 36; $lbegin = 1 if $lbegin < 1;
        if ($rmpbegin <= $end and $pbegin <= $rmend) {
            $j = ($pbegin > $rmpbegin) ? $pbegin - $rmpbegin : 0;
            $k = $rmend - $rmpbegin - ($rmend > $end ? $rmend - $end : 0);
            for ($i = $j; $i <= $k; $i++) {
                $pexists[$i] = 1;
            }
        }
        if ($rmlbegin <= $end and $lbegin <= $rmend) {
            $j = ($lbegin > $rmlbegin) ? $lbegin - $rmlbegin : 0;
            $k = $rmend - $rmlbegin - ($rmend > $end ? $rmend - $end : 0);
            for ($i = $j; $i <= $k; $i++) {
                $lexists[$i] = 1;
            }
        }
    }
    for ($i = 0; $i <= $rmend - $rmpbegin; $i++) {
        if ($pexists[$i] == 0) {
            printf STDERR "Cannot remove %s or lose some profile.\n", $_[0];
            return;
        }
    }
    for ($i = 0; $i <= $rmend - $rmlbegin; $i++) {
        if ($lexists[$i] == 0) {
            printf STDERR "Cannot remove %s or lose some logbook data.\n", $_[0];
            return;
        }
    }
    unlink($_[0]);
}

sub dosemusend {
    use POSIX;

    if (! -f $_[0]) {
        printf STDERR "File %s not found.\n", $_[0];
        return;
    }
    open(OUT, ">$DOSEmuSerial");
    $tio = POSIX::Termios->new;
    $tio->setiflag(0);
    $tio->setoflag(0);
    $tio->setlflag(0);
    $tio->setcflag(CS8|CLOCAL|CREAD);
    $tio->setospeed(B19200);
    $tio->setcc(VMIN, 1);
    $tio->setcc(VTIME, 0);
    $tio->setattr(fileno(OUT), TCSANOW);
    $b = "UUU\0";
    syswrite(OUT, $b, 4);
    open(IN, "<$_[0]");
    read(IN, $buf, 2048);
    close(IN);
    substr($buf, 2040, 4) = pack('N', (time() - $TIMECORR) << 1);
    $c = unpack('%16C2044', $buf);
    $c += 0x01fe; $c %= 65536;
    substr($buf, 2044, 2) = pack('v', $c);
    @buf = unpack('C2048', $buf);
    for ($i = 0; $i < 2048; $i++) {
        $d = 0;
        $e = $buf[$i];
        for ($j = 0; $j < 8; $j++) {
            $d <<= 1;
            $d |= $e & 1;
            $e >>= 1;
        }
        $buf[$i] = $d;
    }
    $buf = pack('C2048', @buf);
    syswrite(OUT, $buf, 2048);
    close(OUT);
}
