I learned event-based programming recently / 2018-02-18

2018-02-18 I learned event-based programming recently 8 months ago
On 8 and 9 February last week I attended the Surf Security and Privacy conference. SURFcert, the incident response team of SURF, had its own 'side event' within this conference, an escape room. Since the members of SURFcert like to visit escape rooms themselves, the idea was to build our own escape room. A simple one as teams of 2 or 3 people had to solve it within 15 minutes. The best scores were indeed just over 5 minutes so it was doable.

The escape room clock
The escape room clock
The theme of this escape room was the trip Snowden made: from the US to Hongkong to Moscow. Each location had a puzzle and like Snowden the only thing you could take to the next location was knowledge. In this case a 4-digit code to open a lock. Someone else in the SURFcert team did most of the hardware work and I decided to dive into some programming to support this effort. The escape room needed a countdown clock that could only be stopped by the right code. My idea was to use a barcode scanner to link the stop action to scanning the barcode on an object.

So I installed a Raspberry Pi with a raspbian desktop and found out how to set up the autorun on the Pi so my program would be started at startup when the user 'pi' logs in automatically. This was done by starting it from ~/.config/lxsession/LXDE-pi/autorun.

The program I wrote had three inputs:
  • A reset switch connected to GPIO pin 11 and ground
  • A start button connected to GPIO pin 03 and ground
  • Entering the right barcode to stop the time. In the end this was the barcode of a real Russian bottle of vodka, so my program needed vodka as input
For the barcodes I used an usb barcode scanner I have lying around. It behaves like a usb keyboard so scanning a barcode will cause the code to be entered as keystrokes with an enter key at the end,

But all programming I do is sequential. This is different, I needed to write an event-based program. It needs to react to time events, enter events and needs to check the state of gpio bits on time events. And on certain events it needs to change the global state (reset, running, stopped). The last time I did any event-based programming was an irc-bot written in Perl 4.

So with a lot of google searches, copypasting bits of code, searching a lot for which input bits would be default high and go low when connected to earth and a lot of trying I wrote a program. It uses WxPerl to have a graphical interface and use events. I'm not saying its a good program, but it did the job.

Notable things:
  • The OnInit function sets up everything: a window with minimal decorations, tries to set it full-screen, a text box that will show the time and starts at 15:00 as static text. A handler for time events that will be called 10 times per second. And an input box and a handler for when the enter key is pressed.
  • The onTimer function that looks at global state and decides which inputs are valid in that state and handles them
  • The onenter function that calculates a sha256 hash of the input line and checks which inputs can change the global state. The hash was to make sure that someone who could have a look at the source still had no idea what the commands were to control it all via keyboard. And no keyboard was connected anyway. The input for a shutdown is the barcode from one of the loyalty cards I carry around.

#!/usr/bin/env perl

use warnings;
use strict;

use Wx;
use Wx::Event qw(EVT_TIMER EVT_TEXT_ENTER);

package theclock;

use Digest::SHA qw(sha256 sha256_hex);
use Device::BCM2835;

Device::BCM2835::init() || die "Can't init lib";

# set pins 11 and 16 as input with default high, pull low

Device::BCM2835::gpio_fsel(&Device::BCM2835::RPI_BPLUS_GPIO_J8_11,
	&Device::BCM2835::BCM2835_GPIO_FSEL_INPT);
Device::BCM2835::gpio_fsel(&Device::BCM2835::RPI_BPLUS_GPIO_J8_11,
	&Device::BCM2835::BCM2835_GPIO_PUD_UP);

Device::BCM2835::gpio_fsel(&Device::BCM2835::RPI_BPLUS_GPIO_J8_03,
	&Device::BCM2835::BCM2835_GPIO_FSEL_INPT);
Device::BCM2835::gpio_fsel(&Device::BCM2835::RPI_BPLUS_GPIO_J8_03,
	&Device::BCM2835::BCM2835_GPIO_PUD_UP);

my $pps = 10;
my $playseconds = 900;

use base 'Wx::App';

sub OnInit {
	my $self = shift;

	my $frame = Wx::Frame->new(
		undef,
		-1,
		'SURFcert Escape Room clock',
		[0,0],
		[1280,1024],
		[-1,-1],
		&Wx::wxFULLSCREEN_ALL | &Wx::wxMAXIMIZE_BOX | &Wx::wxCLOSE_BOX,
	);
	$self->{frame}=$frame;

	$frame->SetBackgroundColour(&Wx::wxBLACK); 

	my $timetext = Wx::StaticText->new( $frame, "-1", "15:00.00" );
	$timetext->SetFont( Wx::Font->new( 300));
	$timetext->SetForegroundColour(&Wx::wxWHITE); 

	$self->{timetext} = $timetext;
	&resettime($self);
	&showthetime($self);

	my $timer = Wx::Timer->new( $self );
	$timer->Start( 1000/$pps ); # in millisec
	&Wx::Event::EVT_TIMER($self, -1, \&onTimer);

	$self->{counting} = 0;
	my $textctrl = Wx::TextCtrl->new(
		$frame, 
		-1,
		"",
		[300,850],
		[600,10], 
		&Wx::wxTE_PROCESS_ENTER,
		);
	$self->{inputfornumber} = $textctrl;
	&Wx::Event::EVT_TEXT_ENTER($self, -1, \&onenter);
	$frame->Show;
	$frame->ShowFullScreen(1);
	return 1;
}

sub showthetime {
	my($self) = @_;
	my $readable = $self->{timeleft}/$pps;
	$self->{timetext}->SetLabel(sprintf("%02d:%02d",$readable / 60,$readable % 60));
}

sub resettime {
	my($self) = @_;
	$self->{timeleft} = $playseconds*$pps;
	$self->{counting} = 0;
}

sub onTimer {
	# draait $pps keer per seconde
	# taken
	# 1: controleer inputs
	#    sleutel: reset
	#    knopje: start tellen
	#    getal ingevoerd: is het het juiste?
	# 2: aftellen indien counting
	#    stoppen als tijd op

	my($self, $event) = @_;

	# pin 11 = reset
	if (Device::BCM2835::gpio_lev(&Device::BCM2835::RPI_BPLUS_GPIO_J8_11) == LOW){
		&resettime($self);
	}
	# pin 03 = start
	if (($self->{counting} == 0) and ($self->{timeleft} == $pps*$playseconds) and (Device::BCM2835::gpio_lev(&Device::BCM2835::RPI_BPLUS_GPIO_J8_03) == LOW)){
		$self->{counting} = 1;
	}
	if ($self->{counting} == 1){
		# en tel af
		$self->{timeleft}--;
		if ($self->{timeleft} > 0){
			$self->{timetext}->SetForegroundColour(&Wx::wxWHITE);
			&showthetime($self);
		} else {
			$self->{timetext}->SetForegroundColour(&Wx::wxRED);
			&showthetime($self);
# en stop
			$self->{counting} = 0;
		}
	} else {
		# niet aan het tellen, maar aan het begin?
		if ($self->{timeleft} == $pps*$playseconds){
			$self->{timetext}->SetForegroundColour(&Wx::wxGREEN);
			&showthetime($self);
		}
	}
}

sub onenter {
	my $self = shift;

	my $enteredtext = $self->{inputfornumber}->GetValue();
	my $entereddigest = sha256_hex($enteredtext);
	#print "Text : ".$enteredtext." digest ".$entereddigest."\n";
	if (($self->{counting} == 0) and ($self->{timeleft} == $pps*$playseconds) and ($entereddigest eq "cced28c6dc3f99c2396a5eaad732bf6b28142335892b1cd0e6af6cdb53f5ccfa")){
		# fake startknop is geldig
		$self->{counting} = 1;
	}
	if ($entereddigest eq "01be30bb4a27765c37462e6bf2a0bf8b6c109f9be9d81e6fd56455db1a736a43"){
		# fake resetknop
		&resettime($self);
	}
	if ($entereddigest eq "0d8c402fe9991f3e85487c244f2016704ee1ac53c855f1164033d06cef08a0a9"){
		system("sudo shutdown -h now");
	}
	if ($entereddigest eq "8577da2ea54085708b3b851bc50315a36bb740ba5135e747cfb12457b5d3060f"){
		$self->ExitMainLoop;
	}
	if ($self->{counting} == 1){
		# wel aan het tellen
		# controleer textinput
		if ($entereddigest eq "746665e59eb0b2cef285bf153ca8c31626e33275fecc1d3a259a6f1576d1a9c5"){
			# correcte textinvoer
			$self->{counting} = 0;
			$self->{timetext}->SetForegroundColour(&Wx::wxBLUE);
			&showthetime($self);
		}
	}
	$self->{inputfornumber}->SetValue("");
}

theclock->new->MainLoop;

Tags: ,

, reachable as koos+website@idefix.net. PGP encrypted e-mail preferred.

PGP key 5BA9 368B E6F3 34E4 local copy PGP key 5BA9 368B E6F3 34E4 via keyservers pgp key statistics for 0x5BA9368BE6F334E4 Koos van den Hout
RSS
Other webprojects: Camp Wireless, wireless Internet access at campsites, The Virtual Bookcase, book reviews
This page generated in 0.004384 seconds.