Advertisement
Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- #!/usr/bin/env perl
- use strict;
- use warnings;
- use IO::Socket::INET;
- use Time::HiRes qw/time/;
- use IO::Select;
- use Data::Dumper;
- # The networkport this daemon listens on
- my $listenport = 5000; # The networkport this daemon listens on
- # IP and port of the esp-link module.
- # Do not use the programming port for this. (This would require some small codechanges below)
- my $esplink = "192.168.3.104:23";
- ###########################################################################
- ### Only modify things below this line if you know what you're doing... ###
- ###########################################################################
- # Some STK500 protocol strings
- my $STK500_SYNCCMD = chr(0x30) . chr(0x20);
- my $STK500_SYNCREPLY = chr(0x14) . chr(0x10);
- # Make sure we flush socketbuffers after every write (not strictly needed, but it doesn't hurt :-)
- $| = 1;
- # Data structures for connections from client and to mcu.
- my $Client = {
- fh => undef, # Client network socket
- state => 'UNKNOWN', # State for the client statemachine
- buff => undef # Contains unprocessed incoming data from the client
- };
- my $Mcu = {
- fh => undef, # Client network socket
- state => 'UNKNOWN', # State for the mcu statemachine
- buff => undef, # Contains unprocessed incoming data from the mcu
- };
- my $timeNewConnection = 0; # Contains the absolute timestamp of a new connection. (only used for logging)
- ###############################################################################
- #
- # 'Lowlevel' Socket and watchdog handling
- #
- # Implements:
- # - Accepting new connections
- # - (de)multiplexing of incoming data
- # - Watchdog functionality
- # - Detecting closed connections
- # - ...
- #
- # For the more interesting code, go to the section 'Callback functions' below
- #
- # Create the socket sets to watch for with select()
- my $read_watch = new IO::Select();
- my $write_watch = new IO::Select();
- # Create server socket that listens to incoming connection on port 5000
- my $server = new IO::Socket::INET(
- LocalHost => '0.0.0.0',
- LocalPort => $listenport,
- Proto => 'tcp',
- Listen => 5,
- Reuse => 1
- ) or die "ERROR in Socket Creation : $!\n";
- print "SERVER Waiting for client connection on port $listenport\n";
- # A listening socket becomes readable on new connection... therefore, it belongs in the read_watch set
- $read_watch->add($server);
- my $watchdogExpiry = -1;
- my $watchdogCallback = undef;
- my $watchdogInterval = 1;
- # Enter the endless loop...
- while (1) {
- my ( $read_ready, $write_ready ) = IO::Select->select( $read_watch, $write_watch, undef, 0.05 );
- # Process all sockets that are ready for reading...
- foreach my $fh (@$read_ready) {
- if ( $fh == $server ) {
- # This is a new connection... accept it
- my $fh = $server->accept(); # Get the client socket
- setsockopt( $fh, SOL_SOCKET, SO_KEEPALIVE, 1 ); # Active KEEPALIVE to detect broken connections
- $read_watch->add($fh); # Keep an eye on this socket...
- $timeNewConnection = time(); # All timestamps are relative to client's new connection
- client_open($fh);
- }
- elsif ( $fh == $Client->{fh} ) {
- $fh->recv( my $data, 512 );
- if ( length($data) == 0 ) {
- client_close();
- next;
- }
- # print "Clientrcv:\n";
- # DumpString($data);
- client_recv($data);
- }
- elsif ( $Mcu->{fh} && $fh == $Mcu->{fh} ) {
- $fh->recv( my $data, 512 );
- #DumpString($data);
- if ( length($data) == 0 ) {
- mcu_close();
- next;
- }
- mcu_recv($data);
- }
- }
- # Watchdog functionality
- if ( ( $watchdogExpiry > 0 ) && ( time() > $watchdogExpiry ) ) {
- &$watchdogCallback();
- $watchdogExpiry += $watchdogInterval;
- }
- }
- ### WATCHDOG ###
- sub watchdog_start {
- my ( $interval, $callback ) = @_;
- $watchdogInterval = $interval;
- $watchdogExpiry = time() + $watchdogInterval;
- $watchdogCallback = $callback;
- }
- sub watchdog_stop {
- $watchdogExpiry = -1;
- $watchdogInterval = 1;
- $watchdogCallback = undef;
- }
- sub watchdog_ping {
- $watchdogExpiry = time() + $watchdogInterval;
- }
- ###############################################################################
- #
- # Callback functions
- #
- #####
- # client_open
- #
- # Called when there is a new client connection.
- #
- # In:
- # $fh The socket belonging to the new client connection
- #
- sub client_open {
- my ($fh) = shift;
- &log( "Client->Proxy: New connection from %s\n", $fh->peerhost() );
- $Client->{fh} = $fh;
- $Client->{state} = 'EXPECT_SYNC1';
- $Client->{buff} = undef;
- &log("Proxy->MCU: Initiating MCU sync\n");
- $Mcu->{fh} = IO::Socket::INET->new($esplink) || die "Cannot connect to esp-link ($esplink): $!\n";
- $read_watch->add( $Mcu->{fh} ); # needed for select loop
- $Mcu->{state} = 'SENDSYNC';
- $Mcu->{buff} = undef;
- $Mcu->{fh}->send($STK500_SYNCCMD); # We send it asap to initiate a mcu reset by esp-link
- # Fire watchdog every 100ms. On expiry, resend the sync command
- watchdog_start(
- 0.1,
- sub {
- # Resending sync cmd
- &log("Proxy->MCU: Resending sync\n");
- $Mcu->{state} = 'SENDSYNC';
- $Mcu->{fh}->send($STK500_SYNCCMD);
- $Mcu->{buff} = ''; # Everything we already received cannot be a reply to this command
- }
- );
- }
- #####
- # client_close
- #
- # Called when the client closes the connection
- #
- sub client_close {
- &cleanup("Client socket read");
- }
- #####
- # mcu_close
- #
- # Called when the client closes the connection
- #
- sub mcu_close {
- &cleanup("Mcu socket read");
- }
- #####
- # client_recv
- #
- # Called on reception of new data from the client
- #
- # In:
- # $data The new data that has arrived
- #
- sub client_recv {
- my ($data) = shift;
- $Client->{buff} .= $data; # Append data to buffer
- # We use a while loop here because windows avrdude can send 2 commands in 1 network packet...
- while ( length( $Client->{buff} ) != 0 ) {
- if ( $Client->{state} eq 'EXPECT_SYNC1' ) {
- &log("Client->Proxy: Ignoring 1st sync cmd\n");
- $Client->{state} = 'EXPECT_SYNC2';
- substr( $Client->{buff}, 0, 2, '' ); # Remove 2 bytes from beginning of buffer
- }
- elsif ( $Client->{state} eq 'EXPECT_SYNC2' ) {
- &log("Client->Proxy: Ignoring 2nd sync cmd\n");
- substr( $Client->{buff}, 0, 2, '' ); # Remove 2 bytes from beginning of buffer
- $Client->{state} = 'EXPECT_SYNCEXTRA';
- }
- elsif ( $Client->{state} eq 'EXPECT_SYNCEXTRA' ) {
- my $cmd = substr( $Client->{buff}, 0, 2 ); # Get the 1st 2 bytes from the buffer
- if ( $cmd eq $STK500_SYNCCMD ) {
- &log("Proxy->Client: Sending sync reply to client\n");
- substr( $Client->{buff}, 0, 2, '' ); # Remove 2 bytes from beginning of buffer
- $Client->{fh}->send($STK500_SYNCREPLY);
- }
- else {
- # Client sent something different than sync cmd... we can only continue once the MCU has sync
- &log("Client->Proxy: Client wants to continue...\n");
- if ( $Mcu->{state} eq 'SYNC' ) {
- bridge_start();
- }
- else {
- # We get here if the client has sent it's first real command and the mcu is not yet in sync
- $Client->{state} = 'MCUWAIT';
- last; # Escape the while loop while there is still data in the buffer
- }
- }
- }
- elsif ( $Client->{state} eq 'MCUWAIT' ) {
- last; # Escape the while loop while there is still data in the buffer
- }
- elsif ( $Client->{state} eq 'BRIDGE' ) {
- $Mcu->{fh}->send( $Client->{buff} );
- $Client->{buff} = '';
- }
- }
- }
- #####
- # mcu_recv
- #
- # Called on reception of new data from the mcu
- #
- # In:
- # $data The new data that has arrived
- #
- sub mcu_recv {
- my ($data) = shift;
- $Mcu->{buff} .= $data; # Append data to buffer
- if ( $Mcu->{state} eq 'SENDSYNC' ) {
- # We use a while loop here to deal with the fact the mcu could still be busy
- # sending something else. So we need discard any non relevant response
- # In esp-link this is easier since there is a direct serial connection
- while ( length( $Mcu->{buff} ) != 0 ) {
- # We got a reply to our sync command, let's check if it is the correct one
- my $reply = substr( $Mcu->{buff}, 0, 2 ); # Get the 1st 2 bytes from the buffer
- if ( $reply eq $STK500_SYNCREPLY ) {
- # We got a valid sync reply. Stop the sync watchdog and invoke a slower keep-alive watchdog
- watchdog_stop();
- watchdog_start(
- 0.5,
- sub {
- &log("Proxy->MCU: Timeout... sending keepalive\n");
- $Mcu->{state} = 'SENDSYNC';
- $Mcu->{buff} = undef; # Everything we already revceived cannot be a reply on this command
- $Mcu->{fh}->send($STK500_SYNCCMD);
- }
- );
- substr( $Mcu->{buff}, 0, 2, '' ); # Remove 2 response bytes from beginning of buffer
- # We are synced! :-)
- &log("MCU->Proxy: MCU is in sync!\n");
- $Mcu->{state} = 'SYNC';
- if ( $Client->{state} eq 'MCUWAIT' ) {
- # Client was faster than us and is waiting.. let'r loose!
- bridge_start();
- }
- }
- else {
- &log("MCU->Proxy: Unexpected reply from MCU!... shifting 1 byte\n");
- #remove 1 character from the buffer and see if the remaining bytes are a valid reply
- substr( $Mcu->{buff}, 0, 1, '' ); # Remove 1 byte from beginning of buffer
- }
- }
- }
- elsif ( $Mcu->{state} eq 'SYNC' ) {
- # We're not supposed to receive anything now! We go to bridge mode whenever the client
- # is ready to also go to bridge mode. Watchdog sync timeout keeps running...
- }
- elsif ( $Mcu->{state} eq 'BRIDGE' ) {
- $Client->{fh}->send( $Mcu->{buff} );
- $Mcu->{buff} = '';
- }
- }
- ###############################################################################
- #
- # Helper functions
- #
- #
- #####
- # bridge_start
- #
- # Called when all parties are ready to switch into simple bridging mode between client and mcu
- #
- sub bridge_start {
- &log("Clienti<->MCU: Entering bridging mode...\n");
- watchdog_stop();
- $Mcu->{fh}->send( $Client->{buff} ); # Send everything we have from client to the mcu
- $Client->{state} = 'BRIDGE';
- $Client->{buff} = '';
- $Mcu->{state} = 'BRIDGE';
- $Mcu->{buff} = '';
- }
- #####
- # cleanup
- #
- # Called when a connection is closed. This function will do all cleanup required and leave the
- # system in a state ready to accept new connections
- #
- sub cleanup {
- my ($location) = shift;
- # Connection closed... abort averything
- watchdog_stop();
- $read_watch->remove( $Client->{fh} );
- $read_watch->remove( $Mcu->{fh} );
- $Client->{fh}->close();
- $Mcu->{fh}->close();
- $Client->{fh} = undef;
- $Mcu->{fh} = undef;
- $Client->{state} = 'UNKNOWN';
- $Mcu->{state} = 'UNKNOWN';
- &log( "That's all folks... A message brought to you from '" . $location . "'\n" );
- }
- #####
- # DumpString
- #
- # Used to print a binary string in hex to the console. For debugging purposes
- #
- sub DumpString {
- my $s = shift || "";
- my @a = unpack( 'C*', $s );
- my $o = 0;
- my $i = 0;
- print "\tb0 b1 b2 b3 b4 b5 b6 b7\n";
- print "\t-- -- -- -- -- -- -- --\n";
- while (@a) {
- my @b = splice @a, 0, 8;
- my @x = map sprintf( "%02x", $_ ), @b;
- my $c = substr( $s, $o, 8 );
- $c =~ s/[[:^print:]]/ /g;
- printf "w%02d", $i;
- print " " x 5, join( ' ', @x ), "\n";
- $o += 8;
- $i++;
- }
- }
- #####
- # log
- #
- # Used by the code above to print info on the console. It prepends the printed information
- # with a timestamp
- #
- sub log {
- my ( $format, @list ) = @_;
- my $time = time();
- printf( '%5f ' . $format, $time - $timeNewConnection, @list );
- }
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement