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.
Showing posts with label Pi. Show all posts
Showing posts with label Pi. Show all posts

Tuesday, 21 March 2017

Speak and Spell and Sniff

Everyone loves spelling tests right? No? Well at least parents and teachers love spelling tests? They must do - they always want to do them with their kids! If kids hate taking them, and adults hate setting them, that would make no sense!

What if we wrote a program to test your spellings? Well at least kids could practise, and us adults could do something more fun. No reason for us all to be bored!

Of course the problem is that you can't just print "spell pointless:" on the screen, as it pretty much gives the game away - hence the traditional spelling test where an adult reads out the list of words. Fortunately that's really easy in Sniff using the "speech" device (which should work "out of the box" on Mac and Windows. On Linux you need to install a speech synthesiser).

make words list of strings

make voice speech device
make message string

when loadWords
.delete all of words
.add "soldier" to words
.add "necessary" to words
.add "legend" to words
.add "northern" to words


We're going to need the speech device (which we're calling voice), and a list of words. Just to get started I've added a few good spelling word to the list.


make counter number
when start
.broadcast loadWords and wait

.set message to join "There are " join [length of words] " words"
.tell voice to "speak"
.wait 3 secs
.


To get us started, we just run the loadWords script, and to check its working we say how many words are in the test.

We could go through the list in order and just read them out, but to make it more fun(!) we'll mix them up.


.
.repeat until length of words=0
..set counter to pick random 1 to length of words
..set message to item counter of words
..tell voice to "speak"
..wait 1 secs
..ask "How do you spell that?" and wait
..if answer=message
...set message to "Correct"
...delete item counter of words
..else
...set message to "Sorry, Incorrect"
..tell voice to "speak"
..wait 3 secs

.
.stop all

This is more for practising than a proper test, so we pick a random word from the list, and say it. The user then has to type it, and if its correct we delete it from the list. If its incorrect we leave it in the list, so that you have to spell every word correctly to finish the program.

The program just loops until there are no more words to spell. Alternatively for a regular spelling test, we'd delete the word even if it was wrong, and keep a score (left as an exercise for the reader).

This all works pretty well, but its not ideal (depending on your love of spelling tests!) that it only knows four words, and that you have to edit the program to change them so instead we can load the words from a file:


make nativeFile device
make fileData string
make fileOK boolean
when loadFile
.delete all of words
.set fileData to "spellings.txt"
.tell nativeFile to "start read"
.repeat until not fileOK
..tell nativeFile to "read string"
..if fileOK
...add fileData to words
.tell nativeFile to "end read"

This just copies a list of words from the file spellings.txt. A quick search turned up a list of KS2 (age 7-11) spelling words and dropped them in the file. Unfortunately the program now starts by telling the there are 208 words in the test, which takes rather a long time to complete! To fix this I added a script to delete most of the words and leave a sensible number:


when deleteWords
.repeat until length of words=10
..delete item pick random 1 to length of words of words

This works, but because of the way random numbers work generates the same list off words every time. To get round that we need to start throwing away random numbers, so we get something closer to real randomness.

when start
.make dummy number
.forever
..set dummy to pick random 1 to 10

With this script in place, the beginning of our main script just needs to be tweaked:


when start
.broadcast loadFile and wait
.ask "Press Return to start" and wait
.broadcast deleteWords and wait
.set message to join "There are " join [length of words] " words"
.
.

Now it loads all of the words, and then asks to confirm you're ready. Because everyone will take a slightly different length of time to press return we'll get a truly random set of words.

It turns out writing code to set spelling tests is a lot more fun than doing spelling tests!

Monday, 13 March 2017

Cheerlights meet the Hue

In the pervious post we hooked cheerlights up to first Flotilla, and then NeoPixels on an Arduino using MQTT. However for some reason we didn't implement the most obvious thing - Cheerlights controlling Philips Hue bulbs! It turns out this is really simple...

make server cheerlight device

make brightness number
make hue number
make saturation number


make targetHue number
make targetSaturation number
make targetBrightness number


when start
.forever
..tell server to "update"
..set targetHue to hue
..set targetSaturation to saturation
..set targetBrightness to brightness
..wait 30 secs


The cheerlights bit is pretty simple, and standard... I just read the colour from the cheer light server, and store it in the "target" variables. We need to be a bit careful, as at any time, the value displayed on the actual lights might be different from the desired colour, and if we want to cross fade gently, then we need to record the target colour and the current colour separately.

make bridge hueBridge device
make bridgeAddress string "192.168.0.107"
make authorisation string "87XNzAMNY23423423408O61sF66FyqvHFLq0j"

make light number
make state boolean

make currentHue number
make currentSaturation number
make currentBrightness number

make speed number 0.02

when start
.set light to 5
.set state to on
.tell bridge to "set state"
.set light to 6
.set state to on
.tell bridge to "set state"
.
.set currentBrightness to 1
.set currentHue to 0
.set currentSaturation to 1

Now we can start adding a second script to start talking to the Hue. We make a hue Bridge device, which in this case corresponds to the one on my home network. You need the IP address (or name) of your brigdge and an authorisation key, which you can get from the Hue Bridge (see previous posts). The two coloured bulbs I want to control are numbered 5 and 6 in my system - again you can find the number of your bulbs either by talking to your bridge, or running the hueInfo.sniff program, which will list out your bulbs.

I turn on my two bulbs, and choose an initial value of Red to get them started. 

.forever
..set currentHue to currentHue*(1-speed)+targetHue*speed
..set currentSaturation to currentSaturation*(1-speed)+targetSaturation*speed
..set currentBrightness to currentBrightness*(1-speed)+targetBrightness*speed
..
..set hue to currentHue
..set saturation to currentSaturation
..set brightness to currentBrightness
..
..set brightness to brightness * 0.5
..set light to 5
..tell bridge to "set color"
..set light to 6
..tell bridge to "set color"
..wait 2 secs

Now this slightly clever bit... I want the bulbs to change slowly so I take the current colour and mix it with a small amount of the target colour. If speed was 0 then they would never change, and if speed was 1 they'd change instantly. It only takes a very small value of speed (0.02) to produce a gentle change over about a minute.

Now we know the colour the bulbs should be right now, we send that to the hue bridge. My bulbs are in the TV room which gets used in the late evening, so I also half the brightness to create a more relaxed environment.

The result is really great - though cheerlight traffic is quite slow some evenings, and people pick some boring colours (white? and pink looks boring). Normally the Hue lights look great when you turn them on, but then they're just  static. Having them change over the evening is really fun, and because they change gradually they're not distracting. It's also quite cool knowing that someone random somewhere in the world has changed the colours in your TV room!

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!


Friday, 13 January 2017

Round and Round (rotary encoder)

Pots are fun, but they have two limitations - firstly they have a position, which is great feedback, but if it gets out of sync with the "real" value because you've changed it for some other reason then you need a motorised pot (as we saw list time) to get it back to the correct position. The second issue is that they have limited travel - when you reach the end, then you can't go any further.

Both of these are easily fixed using a rotary encoder (which also have the advantage of being digital, so you don't need an analog input, so on a Pi they're ideal).



Our encoder has 5 pins: Gnd and +ve are obvious. SW refers to an inbuilt switch, you can press down separately from the actual rotation. The other two are clk and dt, which are the interesting ones.

make dt digital input D5
make clk digital input D6

when start
.forever
..wait until clk
..say [clk]
..wait until not clk
..say [clk]

Clock will go from high to low and back repeatedly as you turn the shaft, so to get started we wait for it to change.

when start
.forever
..wait until clk
..wait until not clk
..say [dt]

The next thing to do is to check "dt" - direction of turn. Here I've set the code up to check dt whenever the clock goes low and we conveniently find that it is true when we turn clockwise and false when we turn anticlockwise.

All that's left is to add something to keep track of the number of times we turn in each direction:

make counter number
when start
.forever
..wait until clk
..wait until not clk
..if dt
...change counter by 1
..else
...change counter by -1
..say [counter]

This works perfectly well but we can do a little better. For the encoder I got we get 15 clocks per full turn, but we can look a little closer, and get 30 half clocks by checking dt when clock goes high as well as when it goes low. When we check this we see that when we check dt at this point in the cycle it has the opposite value - its low when we go clockwise, and high anti-clockwise. Adding this in doubles the resolution of our encoder:

make counter number
when start
.forever
..wait until clk
..if not dt
...change counter by 1
..else
...change counter by -1
..say [counter]
..wait until not clk
..if dt
...change counter by 1
..else
...change counter by -1
..say [counter]

This works really well and reliably, with one gotcha. It assumes that we're actually running this code regularly. If we go off and run another script at the same time we might miss a clock change. We will spot it eventually, but by that time DT may have changed to an incorrect value - its important to check DT immedialtly the clock changes. If you're running a lot of other code at the same time, then you may need to switch back to the other version which is a little more reliable.