The Cuckoo clock were originated in Black Forest in Germany, where they begin to export to rest of the world around 1850s. The timely piece, has always been a people to use as an alarm. During LEGO REMIX Challenge, the Cuckoo clock was built from MindStorms EV3 (31313) and LEGO Technic Heavy Lift Helicopter (42052). The official build instructions are then being published by LEGO, in our project, we will make it work with Amazon Echo as well as writing python code with it.
Step 1: Build out the Cuckoo ClockLEGO has published the build instructions for Cuckoo Clock, in this article we will just focus on the clock, so it's easier and more manageable. The clock is actually a real clock, as we will be able see the time from the clock itself.
Since we will be only using the clock, we can get to Build Instructions page 222, or step 282. When all said and done it should look like following.
The MindStorms ev3 brick is attached from behind
The entire build instruction is listed in attachment section, you can download it and build your own clock.
We first need to setup the environment for our ev3dev, we can follow through the AlexaGadget's guide on how to get this done via
https://www.hackster.io/alexagadgets/lego-mindstorms-voice-challenge-setup-17300f
We need to setup the the basic environment so we can deploy python code on ev3dev, After going through Enviroment Setup we should be able to open Visual Studio Code and option to open SSH shell, it will look something like below
After that we will be able to access entire shell on the ev3dev
Since the clock only had build instructions and didn't really have the code with it, In this case we only have the minute hand and hour hand. The minute hand should be the red with yellow tip and hour hand is the one that's gray with white tip.
The gear is already setup that the minute hand turns around one round, hour hand would turn 1/12, so our first job is to spin minute hand 12 times, and see if the hour hand will be back at where it is. In this case we are spinning the medium motor, let's spin it at just 50% for now just so we don't break anything.
from ev3dev2.motor import OUTPUT_A, MediumMotor, SpeedPercent
minutehand = MediumMotor(OUTPUT_A)
minutehand.on_for_rotations(SpeedPercent(50), 12)
When all is done, we can download the code and run it straight from the shell.
When you execute the code, the clock should spin 12 cycles to get back to the center position.
Now we need to get the current time, we first need to setup locale time, can simply go through timezone setup in shell to do that. Otherwise its using default UTC time.
$sudo dpkg-reconfigure tzdata
After that, run following code we should get the hour in 12 hour format, and minute. And to convert that into clock rotation, we can do hours + minute/60
import datetime
from ev3dev2.motor import OUTPUT_A, MediumMotor, SpeedPercent
#time
current = datetime.datetime.now()
hour = current.strftime("%I")
minute = current.strftime("%M")
rotation = int(hour) + int(minute)/60
print(hour)
print(minute)
print(rotation)
#calibrate
minutehand = MediumMotor(OUTPUT_A)
minutehand.on_for_rotations(SpeedPercent(50), 12)
minutehand.on_for_rotations(SpeedPercent(50), rotation)
and another thread can handle every minute's movement.
def _auto_minute_thread(self):
while True:
minutehand.on_for_rotations(SpeedPercent(50), 1/60)
time.sleep(60)
And this point we have a real clock running.
Step 4: Setup the Cuckoo birdThe cuckoo bird uses the Motor 2 and a large motor, so it can push through the rubber band with more torque. there are also 2 sensors connecting to it, the Infared Sensor and Touch Sensor. The touch sensor is to let us know that the rotation has
To move the Cuckoo bird, we can use the following code, the touch sensor will know when the bird is inside the door, and IR sensor will know when the bird is outside of the door. By combining them we can pretty much make the bird come in and out. We'd also use cuckoo.wav file which utilizes the Sound from ev3 brick board.
from ev3dev2.motor import OUTPUT_B, LargeMotor, SpeedPercent
from ev3dev2.sound import Sound
from ev3dev2.sensor.lego import TouchSensor
from ev3dev2.sensor.lego import InfraredSensor
ir = InfraredSensor()
sound = Sound()
stopper = TouchSensor()
cuckoo = LargeMotor(OUTPUT_B)
while True:
if stopper.is_pressed:
cuckoo.on_for_seconds(SpeedPercent(30), 1)
elif ir.proximity < 30:
print(ir.proximity)
sound.play_file("cuckoo.wav")
cuckoo.on_for_seconds(SpeedPercent(-30), 1)
When that's done, we should have
In step 2 we've went through the environment setup from Alexagadget, we would need to go through mission 1 and mission 2 in order to both setup Amazon device as well as understanding basic features from connecting ev3brick to Alexa Voice Service.
Once that's done we should have Amazon ID and Alexa Gadget Secret as well connecting wake word and tempo to the ev3 brick, we can now move onto next step.
Most of the code will be mirroring mission2, but we will be implementing the step 3 and 4 code in there. We will first need our amazonid and secret from last step for the cuckoo.ini, and this time we will be using Alexa.Gadget.StateListener = 1.0 - timers, wakeword as capabilities. There is a bug on timer that will not give status "clear" for timer, so we will be using wakeword's clear status to stop the cuckoo bird. I have already filed an issue, for now, we can easily use the clear status from the wakeword and still make this work, since the documentation on the timers should have the state cleared. Their Raspberry Pi sample uses Alert, which is another work around for this problem.
[GadgetSettings]
amazonId = [YOUR_AMAZON_ID]
alexaGadgetSecret = [YOUR_AMAZON_GADGET_SECRET]
[GadgetCapabilities]
Alexa.Gadget.StateListener = 1.0 - timers, wakeword
Next in cuckoo.py file, we will be importing following classes,
import os
import sys
import time
import datetime
import logging
import threading
import random
from agt import AlexaGadget
from ev3dev2.led import Leds
from ev3dev2.sound import Sound
from ev3dev2.motor import OUTPUT_A, OUTPUT_B, LargeMotor, MediumMotor, SpeedPercent
from ev3dev2.sensor.lego import TouchSensor
from ev3dev2.sensor.lego import InfraredSensor
Once MindstormsGadget
is connected, we will do the following in our init class, and calibration and set current timer from Step 3. Initializing everything from calibration to starting the auto minute thread so this can actually function like a clock. We will also place cuckoo bird in _auto_cuckoo_thread so it does not bother with main thread.
class MindstormsGadget(AlexaGadget):
"""
A Mindstorms gadget that performs movement in sync with music tempo.
"""
def __init__(self):
"""
Performs Alexa Gadget initialization routines and ev3dev resource allocati on.
"""
super().__init__()
#sound and LED
self.sound = Sound()
self.leds = Leds()
#Initilize sensors and motors
self.ir = InfraredSensor()
self.stopper = TouchSensor()
self.minutehand = MediumMotor(OUTPUT_A)
self.cuckoo = LargeMotor(OUTPUT_B)
#time
self.current = datetime.datetime.now()
hour = self.current.strftime("%I")
minute = self.current.strftime("%M")
rotation = int(hour) + int(minute)/60
print(hour)
print(minute)
print(rotation)
#calibrate
self.minutehand.on_for_rotations(SpeedPercent(50), 12)
#self current time
self.minutehand.on_for_rotations(SpeedPercent(50), rotation)
#timerthread and cuckoobird thread
threading.Thread(target=self._auto_minute_thread, daemon=True).start()
threading.Thread(target=self._auto_cuckoo_thread, daemon=True).start()
The
auto minute thread will also start, moving the minute hand 1/60 every minute
def _auto_minute_thread(self):
while True:
time.sleep(60)
self.minutehand.on_for_rotations(SpeedPercent(50), 1/60)
Inside _auto_cuckoo_thread, we will follow what we did in Step 4 to move to cuckoo bird, and when the cuckoo flag is off, we will check whether the button is pressed, if not, move the cuckoo back into the door.
def _auto_cuckoo_thread(self):
while True:
if self.cuckoobird:
if stopper.is_pressed == True:
print(ir.proximity)
cuckoo.on_for_seconds(SpeedPercent(30), 1)
elif ir.proximity < 30:
sound.play_file("cuckoo.wav")
print(ir.proximity)
cuckoo.on_for_seconds(SpeedPercent(-30), 1)
else:
if stopper.is_pressed == False:
sound.play_file("cuckoo.wav")
print(ir.proximity)
cuckoo.on_for_seconds(SpeedPercent(-30), 1)
Next is core of this application, as mentioned before, there is a bug on timer that will not give status "clear" for timer. In here when the timer is active, we will set the cuckoobird flag to be true, and when the wakeword clears (since timer doesn't send clear status due to a bug as of this writing), we will set the flag to False. When the bug is fixed, simply move the elif statement one tab up, with no code need to be changed.
def on_alexa_gadget_statelistener_stateupdate(self, directive):
"""
Listens for the wakeword state change and react by turning on the LED.
:param directive: contains a payload with the updated state information from Alexa
"""
print(directive)
for state in directive.payload.states:
print(state.name, file=sys.stderr)
#print(directive.payload)
if state.name == 'timers':
if state.value == 'active':
self.cuckoobird = True
elif state.value == 'cleared'
#currently timer does not provide clear status, I think it's a bug so I m gona use wake word's clear here to clear the bird
self.cuckoobird = False
This is the entire application, full code can be downloaded in attachment section. You can use phrases such as:
"Alexa, set timer for 2 minutes"
or
"Alexa, set alarm for 2 hours"
Now move onto the Demo
Step 7: DemoSince I only have an echo dot, I ended up needing to film this at my friend Peter's house because he has an echo show, that can actually show the time so we can do comparison on Alexa's time and actual time. This is not needed, but just easier to see for demo purpose.
Comments