Sniff is a "Scratch-like" programming language that's designed to help Scratchers move gently from Scratch to more conventional languages. They can start writing programs, without having to learn a new language because Sniff is based on Scratch. They learn a little more about variables, compiling, syntax errors (!), and they can have fun controlling real hardware while they're doing it.

Saturday, 11 March 2017

Cheerlights (and a bit of MQTT)

One of the things we added in the last release is a simple cheerlights client. Cheerlights is a system that scans twitter looking for posts are sent to it, using the tag @cheerlights, or simply including the word cheerlights, and a colour. Whenever anyone does that, every cheer light in the world switches over to showing that colour. What's the point? None at all, but its lots of fun knowing that you can control lights all over the world with just a tweet, and that you can build something that's part of a giant network.
The Sniff cheer light client runs on your computer and all you need to do is make a cheer light device:

make server cheerlight device

make red number
make green number
make blue number

make hue number
make saturation number
make brightness number
make shade number

make name string

Rather than just telling you the name of the colour (as it was in the tweet), it can find the numeric representation in lots of different formats, making it easy to use with lots of different hardware. Here's some simple diagnostic code to check in with the server, and print out the latest values.


when start
.forever
..tell server to "update"
..say name
..say join "red:" [red]
..say join "green:" [green]
..say join "blue:" [blue]
..say ""
..say join "hue:" [hue]
..say join "saturation:" [saturation]
..say join "brightness:" [brightness]
..say join "shade:" [shade]
..say ""
..say ""
..wait 10 secs


This tells the server to update every 10 seconds. You might want to think a little about how often up call update, as each time it sends a message to the central cheer lights server. While every 10 seconds isn't exactly going to generate a lot of traffic, and is a reasonable value for testing purposes, you might want to increase that a little if you decide to run a cheerlight client full time. After all checking once every minute or two isn't really going to make much difference if its just running in the corner of a room somewhere.

Now we need to get some coloured lights going, and the easiest way is with a flotilla rainbow:

make server cheerlight device

make hue number
make shade number
make name string

when start
.forever
..tell server to "update"
..wait 30 secs

We can start with the code from above, but chop it down quite a lot, as we don't need to know all those different formats.

make flotilla device
make lights flotillaRainbow device
make rainbowColor list of numbers
make rainbowShade list of numbers

when start
.repeat 5
..add 0 to rainbowColor
..add 0 to rainbowShade
.tell lights to "update"
.
.forever
..wait 0.5 secs
..delete 1 of rainbowShade
..add shade to rainbowShade
..delete 1 of rainbowColor
..add hue to rainbowColor
..tell lights to "update"


Next we make the flotilla rainbow device, and initialise the 5 LED's to be black. Then every half second we shuffle up the colours by deleting the first colour, and then add the current cheer light colour to the end of the list. This means when the lights change we get a nice animation effect.

That's pretty neat, but what if we want to control some more interesting hardware, perhaps attached to an Arduino. Well we could have re-writtten the cheer lights client to run on Arduino, but instead we wrote a cheer lights to MQTT bridge:

when start
.forever
..tell cheerServer to "update"
..broadcast display and wait
..wait 30 secs

We need to create a cheerlights device as before, and then check the status every 30 seconds.

make mqtt device
make clientid string
make message string
make topic string

make networkConnected boolean
make networkPeer string

when start
.set clientid to "sniffSendClient"
.set networkPeer to "raspberrypi.local."
.tell mqtt to "connect"
.
.if not networkConnected
..say "connect failed"
..stop script
.say "connected"
.
.forever
..tell mqtt to "loop"
..wait 1 secs

We also set up an Mqtt device, and tell it to connect to a server on raspberrypi.local (which is running Mosquito).

when display
.set topic to "cheerlights/red"
.set message to [red]
.tell mqtt to "publish"
.
.set topic to "cheerlights/green"
.set message to [green]
.tell mqtt to "publish"
.
.set topic to "cheerlights/blue"
.set message to [blue]
.tell mqtt to "publish"
.
.
.set topic to "cheerlights/hue"
.set message to [hue]
.tell mqtt to "publish"
.
.set topic to "cheerlights/saturation"
.set message to [saturation]
.tell mqtt to "publish"
.
.set topic to "cheerlights/brightness"
.set message to [brightness]
.tell mqtt to "publish"
.
.set topic to "cheerlights/shade"
.set message to [shade]
.tell mqtt to "publish"
.
.set topic to "cheerlights/name"
.set message to name
.tell mqtt to "publish"


After checking the cheer light serverver the code calls runs display, which pushes all of the data out to different topics.

Then we can run an mitt client on Arduino:

make spi device
make ethernet device D10
make networkMAC string "b6:ee:63:ed:95:cb"
make networkIP string "192.168.0.200"
make networkConnected boolean
make networkPort number
make networkPeer string

make mqtt device
make clientid string
make message string
make topic string


make neoPixels ws2811 device A1
make neoColor list of numbers
make neoShade list of numbers

make hue number
make shade number

when start
.set clientid to networkMAC
.set networkPeer to "192.168.0.108" #No DNS on Arduino!
.tell mqtt to "connect"
.if not networkConnected
..say "connection failed"
..stop all
.
.set topic to "cheerlights/hue"
.tell mqtt to "subscribe"
.set topic to "cheerlights/shade"
.tell mqtt to "subscribe"
.
.forever
..tell mqtt to "loop"
..if not topic = ""
...if topic = "cheerlights/hue"
....set hue to value of message
...if topic = "cheerlights/shade"
....set shade to value of message
..wait 1 secs

when start
.repeat 10
..add 0 to neoColor
..add 0 to neoShade
.
.forever
..delete item 1 of neoShade
..add shade to neoShade
..delete item 1 of neoColor
..add hue to neoColor
..tell neoPixels to "show"
..wait 0.25 secs

It connects to the server, and subscribes to the cheer lights topics for hue and shade. Then the neoPixel part of the code scrolls the colours just as we did for flotilla!


Wednesday, 1 March 2017

Fun with Goats

I was chatting with some kids at the weekend, and they were asking each other riddles. However these were more interesting than the usual puns and trick questions. Rather they were logic puzzles - "This statement is false" type of thing. Interestingly the kids were figuring them out faster than some of the adults...

Which got me thinking about the Monty Hall Problem.  You may or may not know that Monty Hall was an American game show host, and while he may be pretty famous for being on TV, he is immortalised in the maths puzzle named after him. Mythbusters studied it, and its even been explain  in movies.

You have three doors. Behind 1 is a car and the other two are goats. Pick a door and the prize is yours... but wait... Before you open the door let me show you another door. Behind this other door is a goat. Maybe there's a goat behind your door too! It's not too late to change your mind? Stick with your door, or switch to the final unopened door?

Stick you say? Thought you might. Most people do. So lets open then door and see what you won!

Now of course there's an element of luck in here - you might have picked correctly, or you could be wrong. The interesting question is should you have switched?

Well let's write a program to find out!

Let's start by setting up some variables:

make doorCount number 3
make switch boolean no

make prizes list of strings
make open list of boolean

make pickedDoor number


We've got a couple of lists - prizes which tells us what's behind each door and open which tells us if the door is open yet. PickedDoor is going to be the number of our selection. I've also set up a couple of constants - we have 3 doors, and we're not going to switch.

Now lets bring out some prizes:
when hideCar
.delete all of prizes
.delete all of open
.repeat doorCount
..add "goat" to prizes
..add no to open
.replace item pick random 1 to doorCount of prizes  with "car"


when showDoors
.make counter number
.repeat length of prizes using counter
..if item counter of open
...say join "Door" join [counter] join ":" item counter of prizes
..else
...say join "Door" join [counter] join ":" "Closed"
.say ""

hideCar, clears out the lists (just in case), puts a goat behind every door, and sets the door to closed. It then picks a random door and replaces the goat with a car. Next we have showDoors which goes through the doors and either prints out that the door is closed or (if its open) then it prints the prize that was behind it.

Now we can write a start script to begin putting these together.

when start
.broadcast hideCar and wait
.set pickedDoor to pick random 1 to doorCount
.say join "you pick door number  " [ pickedDoor ]
.replace item pickedDoor of open with yes
.broadcast showDoors and wait

In this simplified version we hide the car, pick a random door, then open that door and show the results. It's pretty obvious that with three doors we should win about 1/3 of the time.

Now lets start opening some doors.

when openRandom
.make counter number
.forever
..set counter to pick random 1 to doorCount
..if not counter=pickedDoor and not item counter of open
...if item counter of prizes="goat"
....replace item counter of open with yes
....
....say join "Behind door " join [counter] join " is a " item counter of prizes
....stop script

This script tries to open a random door. Except its not totally random. The gameshow host never opens the door you picked, or the door with the car - he always shows an unpicked door with a goat. Therefore, having picked a random door we check that its not the one picked, its not already open, and it does have a goat behind it. Only when all those conditions are true can the door be opened to reveal a goat.

You might note that this code is a little more complex than it needs to be. If we only open one random door then we don't actually need to check  that the door is closed (they all are), and when we print out the prize behind the door its always a goat. However its worth writing the code this way as it actually represents what's going on in the real world, and it provides a double check that what we're going is correct.

The final piece of code we need is to switch doors:

when switchDoors
.make counter number
.forever
..set counter to pick random 1 to doorCount
..if not counter = pickedDoor and not item counter of open
...set pickedDoor to counter
...stop script

To switch, we pick a random door, and check that its different to our current pick, and that its currently closed. Again this is more complex that we need, but it accurately represents the puzzle. It means it will keep working if we start adding extra doors or playing with the other parameters.

when start
.say "New Game:"
.broadcast hideCar and wait
.set pickedDoor to pick random 1 to doorCount
.say join "you pick door number  " [ pickedDoor ]
.say ""
.broadcast openRandom and wait
.say ""
.broadcast showDoors and wait
.if switch
..broadcast switchDoors and wait
..say join "You switch to door " [pickedDoor]
.else
..say join "You Stick with door " [pickedDoor]
.say join "You won a " item pickedDoor of prizes

Here's the whole game put together. Remember switch is set to no, so we're never switching, and when I run it I get:

you pick door number  2

Behind door 1 is a goat

Door1:goat
Door2:Closed
Door3:Closed

You stick with door 2
You won a car

Yay for me!!! I win a car!

But wait - maybe I was just lucky. What if we played 100 games:

make score number

when start
.repeat 100
..say "New Game:"
..broadcast hideCar and wait
..set pickedDoor to pick random 1 to doorCount
..say join "you pick door number  " [ pickedDoor ]
..say ""
..repeat doorCount-2
...broadcast openRandom and wait
..say ""
..broadcast showDoors and wait
..if switch
...broadcast switchDoors and wait
...say join "You switch to door " [pickedDoor]
..else
...say join "You Stick with door " [pickedDoor]
..say join "You won a " item pickedDoor of prizes
..say ""
..say ""
..if item pickedDoor of prizes ="car"
...change score by 1
.say join "You won " join [ score ] " times"


You won 33 times

That's not so good. I won 33 times out of 100, which is exactly(ish) 1/3 of the time. I guess with three doors that's about as good as it gets.

Or maybe not - lets set switch to yes.

You won 65 times

Wow! Thats a lot more interesting. Now we're winning about 2/3 of the time! But how come? We still get to take home one prize from three, and all three doors have an equal change of being a good prize, so why should we be better at picking the wrong door first time and the right door second? 

Well actually you just summed it up... On your first pick you're probably going to pick the wrong door because there are three doors - you get the right one 1/3 and wrong one 2/3. If you stick then that's what you end up with - probably wrong, but it you switch then being wrong the first time means you'll definitely be right the second.

The way I like to think about it is to image there are 100 doors, and I open 98 of them (in fact I've already tweaked the code so the number of doors opened is the total-2).

make doorCount number 100
make switch boolean no

What happens when we run this:

New Game:
you pick door number  23

Behind door 30 is a goat
Behind door 73 is a goat
Behind door 42 is a goat
Behind door 74 is a goat
Behind door 47 is a goat
lots of goats...
Behind door 21 is a goat
Behind door 19 is a goat
Behind door 10 is a goat
Behind door 4 is a goat
Behind door 69 is a goat
Behind door 72 is a goat
Behind door 50 is a goat
Behind door 44 is a goat

Door1:goat
Door2:goat
Door3:goat
Door4:goat
Door5:goat
Door6:goat
Door7:Closed
Door8:goat
Door9:goat
Door10:goat
Door11:goat
Door12:goat
lots of goats...
Door22:goat
Door23:Closed
Door24:goat
lots of goats...
Door96:goat
Door97:goat
Door98:goat
Door99:goat
Door100:goat

you stick with door 23

you won a goat


Think about it - what was the chances that the goat was behind door 23? Well 1 in 100, so its pretty obvious we're probably not going to win. Now look at all those doors I opened for you - do you really think I can open 98 doors and accidentally only find goats? every time? I know something you don't - where the car is! and I mysteriously avoided door number 7. As the number of doors goes up it becomes pretty obvious that switching is a good deal.

Yet it turns out most people don't switch. Even mathematicians who should know better fall for it. Unless you've seen the trick before you'll probably stick, because there's doesn't seem to be an obvious reason to switch, and people don't like to change their mind. I picked door 23, and its as good as any other door, so I'll stay with 23... and probably take home a goat.