понедельник, 26 февраля 2001 г.

Serial communications with an external device through ttyS (perl)

#!/usr/bin/perl

# Демон отвечающий за общение с ридерами посредством серийного порта.
# Он занимается исключительно ридерами и ничем больше.
#
# Зап
#
# crdsrvz.pl [-d] [-l] [<ttyS>], где:
#
# -d стартовать как демон
# -l logged into syslogd
# <ttyS> устройство серийного порта обозначенного в /dev. По умолчанию: ttyS1 (COM2)

require 'getopts.pl';

Getopts ('dl');

# Our program name
( my $program = $0 ) =~ s/^.*\///;

# Стартуем как демон, если указан ключик -d
if ($opt_d) {
    $pid = fork();
    die "can't fork: $!" unless defined $pid;
    exit 0 if ($pid);
}

# use strict;
use Socket;
use Fcntl qw ( F_GETFL F_SETFL O_NONBLOCK );
use Device::SerialPort qw( :PARAM :STAT 0.07 );
use Time::HiRes qw( sleep usleep gettimeofday tv_interval );
use POSIX;
use Class::Struct qw( struct );
use Symbol;
use Sys::Syslog;
use vars qw( $PortObj @readers );

# Local vars
my $port = 'ttyS1';
my $baud = 1200;
my $parity = 'none';
my $facility = 'local1';
my $maxlen = 11; # Из сокета
my $start_tx = 0x02; my $end_tx = 0x03;
my $readers = 7;
my $quitphrase = "quit";
my $commandtimeout = 10;
# my $delaycycle = 100000;
my $delaycycle = 0.001;
my $skipcycles = 8;
my $red = 0x2;
my $green = 0x4;
my $soundalarm = $red + $green;
my $unlock = 0x8;
my $command_unlock = "1";
my $command_lock = "2";
my $command_spy = "3";
my $command_alarm = "4";
my $command_detect = "5";
my %msgs = (
    detect => "Detect reader",
    establ => "Connection established",
    stconn => "Starting wait connect",
    stcard => "Starting wait cards",
    opdoor => "Door is opened on reader",
    cldoor => "Door is closed on reader",
    intsig => "Got interrupted signal",
    slwarn => "WARNING! usleep not defined",
    busy   => "Reader still busy",
    overbusy => "Reader busy time is over",
    stop   => "Reader stopping person",
    alrm   => "Detect strange card"
);

# Programs vars
$port = $ARGV[0] unless ($#ARGV < 0);
my $cardsock = "/tmp/card.$port";
my $lockfile = "/var/lock/LCK..$port";

struct Reader => {
    addr => '$',        # Адрес ридера
    online => '$',        # Подключен-ли физически
    broke => '$',        # Авария
    card => '$',        # Прочитанная карточка
    busy => '$',        # Занят - пропускаем
    blink8 => '$',        # Сигнал открытия замка
    blink4 => '$',        # Состояние лампочки green
    blink2 => '$',        # Состояние лампочки red
    green => '$',        # Можно проходить
    red => '$',            # Нельзя проходить
    alrm => '$',        # Сработал датчик
    command => '$',        # Поступившая комманда
    timer => '$'        # Таймер после последней комманды, карточки и т.п.
};

# Redefine __DIE__
sub die_quit {
    $warnmessage = shift;
    syslog_report ($warnmessage);
    # Part ...: Close UNIX socket
    # close ($server);
    close (SERVER);
    #
    unlink $cardsock if (-e $cardsock);

    # Part ...: Close serial device
    readyall_turn(0);
    #close ($PortObj);
    # $PortObj->close if (defined $PortObj);
    undef $PortObj if (defined $PortObj);
    exit;
}

# Redifine QUIT
sub QUIT {
    die_quit $msgs{intsig};
}

# Redefine interupt
$SIG{INT} = \&QUIT;
$SIG{HUP} = \&QUIT;
$SIG{__DIE__} = 'die_quit';

syslog_report ($msgs{slwarn}) unless defined &Time::HiRes::usleep;

# Init device
unlink $lockfile;
$PortObj = Device::SerialPort->new ("/dev/$port", TIOSDTR, $lockfile);
die "Can't open serial port $port: $!" unless (defined $PortObj);
$PortObj->read_const_time (10000);
$PortObj->baudrate ($baud);
$PortObj->parity ($parity);
$PortObj->databits (7);
$PortObj->handshake ('rst');
$PortObj->are_match (pack ("C", $end_tx));
# $PortObj->dtr_active(1);

# Start UNIX socket
socket (SERVER, PF_UNIX, SOCK_STREAM, 0);
# unlink $cardsock;
bind (SERVER, sockaddr_un ($cardsock)) or die "Can't create server: $!";
listen (SERVER, SOMAXCONN) or die "Couldn't listen on socket: $!";

# Allocate $readers listen servers;
@readers = ();
for ($id = 0; $id <= $readers; $id++) {
    $readers[$id] = Reader->new();
    $readers[$id]->addr($id * 0x10);
    $readers[$id]->online(0);
    $readers[$id]->blink8(0);
    $readers[$id]->blink4(0);
    $readers[$id]->blink2(0);
    $readers[$id]->command(0);
    $readers[$id]->broke(0);
    ($result) = ready_turn ($readers[$id], 0);
    if (defined $result) {
        syslog_report ("$id: " . $msgs{detect});
        $readers[$id]->online (1);
    }
}
# Система не обслуживается
# $readers[0]->online(0);
busyall_turn (1);

#############################################################################
# Primary cicle
#############################################################################
syslog_report ($msgs{stconn});
while (accept (CLIENT, SERVER)) {
    # Взводим в исходное состояние все лазеры и все датчики
    busyall_turn (0); resetall_turn (1);
    syslog_report ($msgs{establ});
    syslog_report ($msgs{stcard});
    # Делаем сокет неблокирующим
    Socket_Block (0);
    my $gotit = ""; my $time0; my $elapsed;
    while ($gotit ne $quitphrase) {
        foreach $reader (@readers) {
            # Проверяем - подключен ли ридер
            next unless ($reader->online);
            $id = $reader->addr / 16;
            # Если он в аварийном состоянии
            if ($reader->broke) {
                ($result) = broke_turn ($reader);
                if ($result) {
                    $reader->broke(0);
                }
                next;
            }
            # Проверяем комманду и пытаемся ее выполнить
            if ($reader->command) {
                syslog_report ("$id: Command is " . $reader->command);
                execute_command ($reader);
            }
            # Если пытаются пройти по "левой карточке"
            if ($reader->alrm) {
                alrm_turn ($reader);
                $time0 = [gettimeofday]; $elapsed = tv_interval ($reader->timer, $time0);
                $reader->alrm (0) if ($elapsed > $commandtimeout);
                syslog_report ("$id: " . $msgs{alrm} . " left $elapsed");
                next;
            }
            # Если ридер кого-то пропускает, то игнорируем датчик либо ждем срабатывания датчика,
            if (my $times=$reader->green) {
                unlock_door ($reader, 1) if ($times == $skipcycles);
                if ($times > 1) {
                    $result = unlock_door ($reader, 0);
                    $times = (result) ? $times - 1 : 1;
                    $reader->green($times);
                } else {
                    ($result) = lock_door ($reader);
                    # Если датчик сработал, то взводимся в исходное состояние
                    if ($result) {
                        $reader->green(0);
                        syslog_report ("$id: " . $msgs{cldoor});
                    }
                }
                next;
            }
            # Если нельзя проходить ?
            if ($reader->red) {
                ($result) = busy_turn ($reader, 1);
                $time0 = [gettimeofday]; $elapsed = tv_interval ($reader->timer, $time0);
                $reader->red(0) if ($elapsed > $commandtimeout);
                syslog_report ("$id: " . $msgs{stop} . " left $elapsed");
                # if ($result) {
                    # $reader->red (0); $reader->command (3);
                # }
                next;
            }
            my $card = "";
            # Если ридер занят
            if ($reader->busy) {
                $time0 = [gettimeofday]; $elapsed = tv_interval ($reader->timer, $time0);
                syslog_report ("$id: " . $msgs{busy} . " left $elapsed");
                # Если истекло время занятости - вызываем бригаду ремонтников
                if ($elapsed <= $commandtimeout) {
                    ($result) = busy_turn ($reader);
                } else {
                    syslog_report ("$id: " . $msgs{overbusy});
                    $reader->busy(0);
                    # $reader->broke(1);
                }
                next;
            }
            # Posle vseh proverok
            # ($result, $card) = ready_turn ($reader, 1);
            ($result, $card) = ready_turn ($reader);
            # Врубаем звуковой сигнал, если сработал датчик
            unless ($result) {
                $reader->command ($command_detect);
                syslog_report ("$id: " . $msgs{opdoor});
                next;
            }
            next unless (defined $card);
            next if ($card eq "");
            $card = $reader->card;
            $time0 = [gettimeofday];
            $reader->timer($time0);
            $reader->busy (1);
            # Пукнули для того, чтобы сказать, что карточка прочитана
            # cardreaded_turn ($reader, 1);
            my $msg = "Received $card(". length ($card) . ")";
            syslog_report ("$id: " . $msg);
            defined syswrite (CLIENT, "$id $card\n", $maxlen) or die "Can't send data: $!";
            # Если увидели карточку - перестаем моргать
            ready_turn ($reader, 0);
        } # foreach $reader (@readers)
        # Ждем либо карточку, либо команду с клиента
        $when = sysread (CLIENT, $gotit, $maxlen);
        if (defined $when) {
            next if ($gotit eq $quitphrase);
            # syslog_report ("Received answer: $gotit");
            ($id, $command) = split " ", $gotit;
            $readers[$id]->command ($command);
        } # if (defined $when)
        # print "...Sleeping $delaycycle sec...\n";
        # sleep ($delaycycle);
    } # while ($gotit ne "quit")
    # Тушим все лампочки и обнуляем все сигналы
    foreach $reader (@readers) {
        if ($reader->online) {
            ($result) = ready_turn ($reader, 0);
            $reader->online (1) if (defined $result);
        } else { next; }
        $reader->busy (0); ready_turn ($reader, 0);
        $reader->red(0); $reader->green (0); $reader->alrm (0); $reader->broke (0);
        $reader->card ("");
    }
    busyall_turn (1);
    # Делаем сокет блокирующим
    Socket_Block (1);
    syslog_report ($msgs{stconn});
}
exit 0;

#############################################################################
# All subroutines for this program
#############################################################################
sub execute_command {
    local $reader = shift;
    my $command = $reader->command;
    my $time0 = [gettimeofday];
    $reader->timer($time0);
    SWITCH: {
        # GREEN SIGNAL
        if ($command eq $command_unlock) {
            $reader->busy(0);
            $reader->green($skipcycles);
            last SWITCH;
        }
        # RED SIGNAL
        if ($command eq $command_lock) {
            $reader->busy(0);
            $reader->red(1);
            last SWITCH;
        }
        # SPY SIGNAL
        if ($command eq $command_spy) {
            $reader->busy(0);
            $reader->red(1);
            last SWITCH;
        }
        # ALARM SIGNAL
        if ($command eq $command_alarm) {
            $reader->busy(0);
            $reader->green(0);
            $reader->red(0);
            $reader->alrm(1);
            $reader->blink2(1);
            $reader->blink4(0);
            last SWITCH;
        }
        # DETECT SIGNAL
        if ($command eq $command_detect) {
            $reader->busy(0);
            $reader->green(1);
            last SWITCH;
        }
    }
    $reader->command(0);
    $reader->card("");
}

sub syslog_report {
    my @str = @_;
    if ($opt_l) {
        # print "...logging into syslog...\n";
        openlog($program, 'pid', $facility);
        syslog ('info', "@str ");
        closelog;
    }
    unless ($opt_d) {
        $timestr = strftime "%Y.%m.%d %H:%M:%S", localtime();
        print "$timestr @str \n";
    }
}

sub Buffer_Clear {
    my $verbose=shift;
    $verbose=1 unless defined $verbose;
    $PortObj->lookclear; $PortObj->input;
    syslog_report ("ERROR: received illegal symbols") if $verbose;
}

sub waitfor {
    my $timeout = $PortObj->get_tick_count + (1000 * shift);
    # Ощищаем буффер приема
    $PortObj->lookclear;
    my $gotit = ""; my $found; my $end;
  
    # Ждем ответ или вываливаемся по таймауту
    for (;;) {
        $gotit = $PortObj->lookfor;
        return unless defined $gotit;
        if ($gotit ne "") {
            ($found, $end) = $PortObj->lastlook;
            return $gotit.$found;
        }
        return if ($PortObj->reset_error);
        return if ($PortObj->get_tick_count > $timeout);
    } # for
} # sub waitfor

sub alrm_turn {
    local $reader = shift;
    my $turn;
    $turn1 = ($reader->blink2 == 1) ? 0 : 1;
    $turn2 = ($reader->blink4 == 1) ? 0 : 1;
    local $askreq = pack ("C", $reader->addr + $turn1 * $red + $turn2 * $green);
    ($status) = portexchange ($askreq);
    $reader->blink2($turn1);
    $reader->blink4($turn2);
    return ($status);
}

sub broke_turn {
    local $reader = shift;
    my $turn = shift;
    $turn = ($reader->blink4 == 1) ? 0 : 1 unless defined $turn;
    local $askreq = pack ("C", $reader->addr + $turn * $soundalarm);
    ($status) = portexchange ($askreq);
    $reader->blink4($turn);
    $reader->blink2($turn);
    return ($status);
}

sub reset_turn {
    local $reader = shift;
    my $turn = shift;
    $turn = ($reader->blink2 == 1) ? 0 : 1 unless defined $turn;
    local $askreq = pack ("C", $reader->addr + $turn * $unlock);
    ($status) = portexchange ($askreq);
    $reader->blink8($turn);
    return ($status);
}

sub busy_turn {
    local $reader = shift;
    my $turn = shift;
    $turn = ($reader->blink2 == 1) ? 0 : 1 unless defined $turn;
    local $askreq = pack ("C", $reader->addr + $turn * ($red));
    ($status) = portexchange ($askreq);
    $reader->blink2($turn);
    return ($status);
}

sub unlock_door {
    local $reader = shift;
    my $sound = shift;
    $sound = 0 unless defined $sound;
    my $turn = ($sound) ? $unlock + $soundalarm : $unlock + $green;
    # Включаем
    local $askreq = pack ("C", $reader->addr + $turn);
    ($status) = portexchange ($askreq);
    $reader->blink8(1);
    $reader->blink4($sound);
    $reader->blink4(1);
    return ($status);
}

sub lock_door {
    local $reader = shift;
    # Включаем
    local $askreq = pack ("C", $reader->addr + $green);
    ($status) = portexchange ($askreq);
    $reader->blink8(0);
    return ($status);
}

sub cardreaded_turn {
    local $reader = shift;
    my $turn = shift;
    $turn = ($reader->blink8 == 1) ? 0 : 1 unless defined $turn;
    local $askreq = pack ("C", $reader->addr + $turn * ($red + $green));
    ($status) = portexchange ($askreq);
    local $askreq = pack ("C", $reader->addr + $turn * ($red));
    ($status) = portexchange ($askreq);
    $reader->blink2($turn);
    return ($status);
}

sub ready_turn {
    local $reader = shift;
    my $turn = shift;
    my $card = "";
    $turn = ($reader->blink4 == 1) ? 0 : 1 unless defined $turn;
    # Переключаем зеленую лампочку на ридере
    local $askreq = pack ("C", $reader->addr + $turn * $green);
    ($status, $card) = portexchange ($askreq);
    # Если оба undef - ридер не отвечает
    return unless (defined ($card) and defined ($status));
    $reader->blink4($turn);
    # Если 8 пробелов - пропускаем
    return ($status, "") if ($card =~ /\s{8}/);
    $reader->card($card);
    return ($status, $card);
}

sub portexchange {
    my $askreq = shift;
    my $status = 0;
    my $verbose = 1;
    local $card = "";
    $PortObj->write ($askreq);
    sleep ($delaycycle);
    # read serial port
    my $inport = waitfor (1);
    # Проверяем состояние ридера на предмет наличия карточки
    return unless defined $inport;
    # Если $in < 0xb - у нас проблемы
    unless (length ($inport) == 12) { Buffer_Clear($verbose); return ($status); }
    # Получили каких-то 12 байт
    # Выдираем номер карточки и все служебные символы
    my @result = unpack ("C*", $inport);
    $echocomm = $result[0]; $stx = $result[1]; $etx = $result[11];
    unless ($echocomm == unpack ("C", $askreq)) { Buffer_Clear($verbose); return ($status); }
    unless (($stx == $start_tx) and ($etx == $end_tx)) { Buffer_Clear($verbose); return ($status); }
    $status = ($result[10] & 8) / 8;
    $card = pack ("C*", @result [2..9]);
    return (! $status, $card);
}

sub readyall_turn {
    my $turn = shift;
    local $reader;
    foreach $reader (@readers) {
        ready_turn ($reader, $turn) if $reader->online;
    }
}

sub busyall_turn {
    my $turn = shift;
    local $reader;
    foreach $reader (@readers) {
        busy_turn ($reader, $turn) if ($reader->online);
    }
}

sub resetall_turn {
    my $turn = shift;
    local $reader;
    foreach $reader (@readers) {
        reset_turn ($reader, $turn) if ($reader->online);
    }
}

sub Socket_Block {
    # Если $1 = 0 то сокет неблокирующий, иначе блокируется
    my $block = shift;
    my $flags = fcntl (CLIENT, F_GETFL, 0) or die "Can't get flags for the socket: $!";
    $flags = ($block) ? ($flags | O_NONBLOCK) : ($flags + O_NONBLOCK);
    $flags = fcntl (CLIENT, F_SETFL, $flags) or die "Can't set flags for the socket: $!";
    return $flags;
}

Комментариев нет:

Отправить комментарий