The first step is to set up all if IO devices. In the Python library that's recommended for the board, there's a lot of functions, and code to handle the i/o, but as I've been finding out as I've written more Sniff programs, Sniff is a surprisingly self sufficient language. Things like audio, and controlling servos which need their own libraries in C/Python are actually really easy and fun to code in Sniff. The same here, we just need to define the pins, and we're good to go:
make redLight digital output 27 #21 on rev 1 Pi
make amberLight digital output 17
make greenLight digital output 4
make Ain digital input 9
make Bin digital input 7
make Cin digital input 8
make Din digital input 10
make Eout digital output 22
make Fout digital output 23
make Gout digital output 24
make Hout digital output 25
make button digital input 11
make buzzer digital output 18
We can just drive them straight from Sniff, without anything extra, so lets get on with our traffic light. There are a few slightly different sequences we could use, but lets choose the traditional Pelican Crossing sequence: Its green (until the button is pressed), then goes to Amber for a couple of seconds, then Red for slightly longer. It then goes to flashing Amber before starting round again. Leaving the timing aside we can see it moves through 4 states:
- Flashing Amber
so lets get them working first.
The great thing about Sniff is we can handle each light separately, by creating a bunch of separate scripts all with the same trigger:
make state number
.if state = 1
..set greenLight to on
..set greenLight to off
.if state = 3
..set redLight to on
..set redLight to off
We're going to broadcast an update message, and when that happens if we're in state 1 the green light is going to turn on, otherwise its off. If we're in state 3 the red light turns on. Amber is a little more compex:
.set amberLight to off
.if state = 2
..set amberLight to on
.if state = 4
...set amberLight to on
...wait 0.2 secs
...set amberLight to off
...wait 0.2 secs
We start by turning it off - that takes care of states 1 and 3. In state 2 we turn it on, while in state 4 we go into a loop blinking it on and off. You might be slightly worried that state 4 never finishes the script, but in Sniff (and Scratch) a new broadcast message restarts the script, so the next "update" will get us out of that infinite loop.
Handling the button is a little subtle: we're actually not interested in if the button is being pressed, but rather if it has been pressed, so we add another script to keep an eye on the button:
...set buttonPressed to yes
If the button is pressed we record that it has been pressed. The only problem with this is that the nice red button on the Pibrella board does't have a pull down resistor. In Python we can use the internal pull downs, but there's no way to code that in Sniff - its a concept which doesn't really fit in the Sniff model cleanly, so I made a simple mod to my board to add one. Without it the button may "press itself" without warning!
In the Cellular Automata post a few days ago, I talked about Top Down Design, where you write your code as if all the scripts you need already exist, then fill them in. In this case we've done the opposite - we've built the low level code first: Bottom Up Design, because we knew that we needed to be able to make the lights cycle, but exactly how is a bit trickier. Now we've got the low level stuff going we can worry about the control of the cycle.
..set state to 1
..set buttonPressed to no
..set startTime to timer
..wait until buttonPressed and (timer - startTime)>10
..set state to 2
..wait 2 secs
..set state to 3
..wait 5 secs
..set state to 4
..wait 2 secs
Now the low level stuff is in place, we can write a nice clean control script. We start by setting state to 1 (green light), and record that the button hasn't been pressed. We also record the time, and broadcast an update to kick off all the light scripts.
Now we "wait" until the button has been pressed, and the time since the light went green is greater than 10 seconds. This is quite nice, as it means if the lights have just gone green we keep them green for a minimum period, but if the traffic has been moving freely for a long time, we give priority to the pedestrian. In fact this is the most tricky bit, but because we've already got the little pieces working nicely we can just put them together to make it do exactly what we want.
From then on, we simply move through each of the states one at a time, before returning to state 1, and doing the whole thing over again.
Another important concept in Software Engineering is "programming in the problem domain". This is a fancy way of saying that if we're writing a program about traffic lights, at the top level (in the main script) is should look like we're writing about traffic lights. Looking at this script it does a pretty good job. How hard would it be it add a second button? Add a "fire engine override" which shortcuts the lights back to green? What if we wanted a red/amber state instead of flashing amber?
My finished version also includes code to handle the buzzer - check the Sparkfun Inventor Kit for a discussion on how to make sounds, and then combine that with another script rather like the Amber light script.
I could certainly code all of this in C or Python, but it wouldn't look as pretty. Too many programmers think that writing clever or difficult code makes them good programmers. It doesn't. Writing simple and obvious code to solve clever or difficult problems makes you a good programmer. Hopefully you can look at this code, and think its simple and obvious. In that case, I've done my job... Next time you look at someones code and can't figure out what it does or how it works, then don't assume they're better programmers than you - if they were then you'd look at their code and think "that's really clear and obvious"