Hardware components | ||||||
| × | 1 | ||||
| × | 1 | ||||
Software apps and online services | ||||||
| ||||||
| ||||||
|
It is fun and useful to be able to control garage door with Alexa. I built a Lego Mindstorms Ev3 garage door that responds to user's door open/close command , asks and checks the secret code to open the door. It also detects guests in front of the door and interacts with them. Alexa connects to Mindstorms EV3 through bluetooth.
Step 1: Project Planning- To build a garage with vertical up/down lift door with Mindstorm Ev3. The up/down lift motion is controlled by EV3 medium motor. An EV3 infrared sensor detects guests in front of the doors to interacts with them.
- Setting up the Ev3dev microSD card and the visual studio code environment as detailed here.
- To write and test the code.
- Lego Mindstorm EV3
- Amazon Echo Dot
- Micro SD card - 16GB ( 8 -16 GB)
- USB Wifi Adapter
- Computer for coding, A way to flash the SD card.
Actuator: Medium Motor.
Sensor: EV3 Infrared sensor
In this project, the EV3 brick is programmed in Python 3 using visual studio code. Node.js is used for coding Alexa skill.
Programming the brick: In the brick, I had two code files. doorkeeper.py and the doorkeeper.ini. Amazon ID and the secret code need to be written in the ini file for Alexa to be able to authenticate the gadget.
doorkeeper.py is the main file where the python code is written to interact with Alexa, control the motor and get input from sensor.
First step is to import all the EV3 capabilities used in this program e.g. Leds, Sound, MediumMotor etc.
Alexa skill sends custom directives for the EV3 gadget to respond. For example, one custom directive might be the command to open the garage door. In this project, the custom directive is with Custom.Mindstorms.Gadget namespace and Control name. To respond to these directives, a class MindstormsGadget, which extends AlexaGadget class is created and the callback method on_custom_mindstorms_gadget_control is defined within the class.
The code required to decode and respond to different directives are written within this callback method.
The method _door_intent() contains the code to open or close the door by activating the medium motor for 2 secs.
The code to decode the command and verify the secret code is written _activate() method.
The method _proximity_thread() is the thread which keeps reading the IR sensor value and sends a message to the Alexa skill when reading is less than 20 meaning that a guest is detected in front of the door.
The thread is registered inside the __init__() of MindstormsGadget class.
If an user says a wrong secret code 3 times, it is programmed to ask the user to wait for 1 minute.
The skill interaction model:
The skill interaction model is in the form of a .json file. It contains the something called Intent and Slots. An intent represents an action that fulfills a user's spoken request. Intents can optionally have arguments called slots. For example, in this project, we have DoorIntent and SetCommandIntent .
Skill logic implementation:
The main file in this context is index.js file. There are different handlers defined within this file.
LaunchRequestHandler: This handler is launched when the alexa skill is launched by saying "Alexa, open mindstorms". It first tries to connect to the EV3 brick. If no EV3 brick is found, it prompts so. If EV3 brick is connected, it stores the endpointID as a session attribute.
DoorIntentHandler: This handler is called when Open/Close door commands are said to Alexa. It builds the appropriate directive and sends it to the EV3 gadget to respond.
SetCommandIntentHandler: This handler is launched when the secret code is said too Alexa.
EventsReceivedRequestHandler and ExpiredRequestHandler: The first one is launched when this Alexa skill receives a custom event from MindstormsGadget. This contains code how it should respond to such and event. The first part of EventsReceivedRequestHandler is canHandle(), which basically validates the token and the endPoint ID.
The second part of EventsReceivedRequestHandler is handle(), which defines what action should be taken when the event is received. For example, when Proximity event is received from EV3 gadget, which tells that somebody is close to the door, Alexa should prompt "Hello! What do you want?" and wait for answer.
An event handler can only be active for a maximum duration of 90 seconds. When the duration expired, ExpiredRequestHandler is called. In this project, the event handler is extended inside ExpiredRequestHandler.
All the handlers are included inside the Alexa request handler inside index.js.
const Alexa = require('ask-sdk-core');
const Util = require('./util');
const Common = require('./common');
// The audio tag to include background music
const BG_MUSIC = '<audio src="soundbank://soundlibrary/ui/gameshow/amzn_ui_sfx_gameshow_waiting_loop_30s_01"></audio>';
// The namespace of the custom directive to be sent by this skill
const NAMESPACE = 'Custom.Mindstorms.Gadget';
// The name of the custom directive to be sent this skill
const NAME_CONTROL = 'control';
const LaunchRequestHandler = {
canHandle(handlerInput) {
return Alexa.getRequestType(handlerInput.requestEnvelope) === 'LaunchRequest';
},
handle: async function(handlerInput) {
const request = handlerInput.requestEnvelope;
const { apiEndpoint, apiAccessToken } = request.context.System;
const apiResponse = await Util.getConnectedEndpoints(apiEndpoint, apiAccessToken);
if ((apiResponse.endpoints || []).length === 0) {
return handlerInput.responseBuilder
.speak(`I couldn't find an EV3 Brick connected to this Echo device. Please check to make sure your EV3 Brick is connected, and try again.`)
.getResponse();
}
// Store the gadget endpointId to be used in this skill session
const endpointId = apiResponse.endpoints[0].endpointId || [];
Util.putSessionAttribute(handlerInput, 'endpointId', endpointId);
// Set skill duration to 5 minutes (ten 30-seconds interval)
Util.putSessionAttribute(handlerInput, 'duration', 4);
// Set the token to track the event handler
const token = handlerInput.requestEnvelope.request.requestId;
Util.putSessionAttribute(handlerInput, 'token', token);
let speechOutput = "Welcome, voice interface activated";
return handlerInput.responseBuilder
.speak(speechOutput + BG_MUSIC)
.addDirective(Util.buildStartEventHandler(token,60000, {}))
.getResponse();
}
};
// Construct and send a custom directive to the connected gadget with
// data from the DoorIntent.
const DoorIntentHandler = {
canHandle(handlerInput) {
return Alexa.getRequestType(handlerInput.requestEnvelope) === 'IntentRequest'
&& Alexa.getIntentName(handlerInput.requestEnvelope) === 'DoorIntent';
},
handle: function (handlerInput) {
const request = handlerInput.requestEnvelope;
const direction = Alexa.getSlotValue(request, 'Direction');
// Get data from session attribute
const attributesManager = handlerInput.attributesManager;
const endpointId = attributesManager.getSessionAttributes().endpointId || [];
// Construct the directive with the payload containing the move parameters
let directive = Util.build(endpointId, NAMESPACE, NAME_CONTROL,
{
type: 'doorIntent',
direction: direction
});
return handlerInput.responseBuilder
.addDirective(directive)
.getResponse();
}
};
// Construct and send a custom directive to the connected gadget with data from
// the SetCommandIntent.
const SetCommandIntentHandler = {
canHandle(handlerInput) {
return Alexa.getRequestType(handlerInput.requestEnvelope) === 'IntentRequest'
&& Alexa.getIntentName(handlerInput.requestEnvelope) === 'SetCommandIntent';
},
handle: function (handlerInput) {
const request = handlerInput.requestEnvelope;
// Code is optional.
let code = Alexa.getSlotValue(request, 'Code') || "0";
let command = Alexa.getSlotValue(request, 'Command') || "CheckCode";
const attributesManager = handlerInput.attributesManager;
let endpointId = attributesManager.getSessionAttributes().endpointId || [];
// Construct the directive with the payload containing the move parameters
let directive = Util.build(endpointId, NAMESPACE, NAME_CONTROL,
{
type: 'command',
command: command,
code: code
});
let speechOutput = `command ${command} activated`;
if (command === 'guard' || command === 'guard mode' || command === "CheckCode") {
speechOutput = '';
}
return handlerInput.responseBuilder
.speak(speechOutput + BG_MUSIC)
.addDirective(directive)
.getResponse();
}
};
const EventsReceivedRequestHandler = {
// Checks for a valid token and endpoint.
canHandle(handlerInput) {
let { request } = handlerInput.requestEnvelope;
console.log('Request type: ' + Alexa.getRequestType(handlerInput.requestEnvelope));
if (request.type !== 'CustomInterfaceController.EventsReceived') return false;
const attributesManager = handlerInput.attributesManager;
let sessionAttributes = attributesManager.getSessionAttributes();
let customEvent = request.events[0];
// Validate event token
if (sessionAttributes.token !== request.token) {
console.log("Event token doesn't match. Ignoring this event");
return false;
}
// Validate endpoint
let requestEndpoint = customEvent.endpoint.endpointId;
if (requestEndpoint !== sessionAttributes.endpointId) {
console.log("Event endpoint id doesn't match. Ignoring this event");
return false;
}
return true;
},
handle(handlerInput) {
console.log("== Received Custom Event ==");
let customEvent = handlerInput.requestEnvelope.request.events[0];
let payload = customEvent.payload;
let name = customEvent.header.name;
let speechOutput;
if (name === 'Proximity') {
let distance = parseInt(payload.distance);
if (distance < 20) {
let speechOutput = "Hello! What do you want?";
return handlerInput.responseBuilder
.speak(speechOutput, "REPLACE_ALL")
.withShouldEndSession(false)
.getResponse();
}
} else if (name === 'Speech') {
speechOutput = payload.speechOut;
} else if (name === 'SpeechWait') {
speechOutput = payload.speechOut;
return handlerInput.responseBuilder
.speak(speechOutput, "REPLACE_ALL")
.withShouldEndSession(false)
.getResponse();
}
else {
speechOutput = "Event not recognized. Awaiting new command.";
}
return handlerInput.responseBuilder
.speak(speechOutput + BG_MUSIC, "REPLACE_ALL")
.getResponse();
}
};
const ExpiredRequestHandler = {
canHandle(handlerInput) {
return Alexa.getRequestType(handlerInput.requestEnvelope) === 'CustomInterfaceController.Expired'
},
handle(handlerInput) {
console.log("== Custom Event Expiration Input ==");
// Set the token to track the event handler
const token = handlerInput.requestEnvelope.request.requestId;
Util.putSessionAttribute(handlerInput, 'token', token);
const attributesManager = handlerInput.attributesManager;
let duration = attributesManager.getSessionAttributes().duration || 0;
if (duration > 0) {
Util.putSessionAttribute(handlerInput, 'duration', --duration);
// Extends skill session
const speechOutput = `${duration} minutes remaining.`;
return handlerInput.responseBuilder
.addDirective(Util.buildStartEventHandler(token, 60000, {}))
.speak(speechOutput + BG_MUSIC)
.getResponse();
}
else {
// End skill session
return handlerInput.responseBuilder
.speak("Skill duration expired. Goodbye.")
.withShouldEndSession(true)
.getResponse();
}
}
};
// The SkillBuilder acts as the entry point for your skill, routing all request and response
// payloads to the handlers above. Make sure any new handlers or interceptors you've
// defined are included below. The order matters - they're processed top to bottom.
exports.handler = Alexa.SkillBuilders.custom()
.addRequestHandlers(
LaunchRequestHandler,
SetCommandIntentHandler,
DoorIntentHandler,
EventsReceivedRequestHandler,
ExpiredRequestHandler,
Common.HelpIntentHandler,
Common.CancelAndStopIntentHandler,
Common.SessionEndedRequestHandler,
Common.IntentReflectorHandler, // make sure IntentReflectorHandler is last so it doesn't override your custom intent handlers
)
.addRequestInterceptors(Common.RequestInterceptor)
.addErrorHandlers(
Common.ErrorHandler,
)
.lambda();
const Alexa = require('ask-sdk-core');
const Util = require('./util');
const Common = require('./common');
// The audio tag to include background music
const BG_MUSIC = '<audio src="soundbank://soundlibrary/ui/gameshow/amzn_ui_sfx_gameshow_waiting_loop_30s_01"></audio>';
// The namespace of the custom directive to be sent by this skill
const NAMESPACE = 'Custom.Mindstorms.Gadget';
// The name of the custom directive to be sent this skill
const NAME_CONTROL = 'control';
const LaunchRequestHandler = {
canHandle(handlerInput) {
return Alexa.getRequestType(handlerInput.requestEnvelope) === 'LaunchRequest';
},
handle: async function(handlerInput) {
const request = handlerInput.requestEnvelope;
const { apiEndpoint, apiAccessToken } = request.context.System;
const apiResponse = await Util.getConnectedEndpoints(apiEndpoint, apiAccessToken);
if ((apiResponse.endpoints || []).length === 0) {
return handlerInput.responseBuilder
.speak(`I couldn't find an EV3 Brick connected to this Echo device. Please check to make sure your EV3 Brick is connected, and try again.`)
.getResponse();
}
// Store the gadget endpointId to be used in this skill session
const endpointId = apiResponse.endpoints[0].endpointId || [];
Util.putSessionAttribute(handlerInput, 'endpointId', endpointId);
// Set skill duration to 5 minutes (ten 30-seconds interval)
Util.putSessionAttribute(handlerInput, 'duration', 2);
// Set the token to track the event handler
const token = handlerInput.requestEnvelope.request.requestId;
Util.putSessionAttribute(handlerInput, 'token', token);
let speechOutput = "Welcome, voice interface activated";
return handlerInput.responseBuilder
.speak(speechOutput + BG_MUSIC)
.addDirective(Util.buildStartEventHandler(token,60000, {}))
.getResponse();
}
};
// Add the speed value to the session attribute.
// This allows other intent handler to use the specified speed value
// without asking the user for input.
const SetSpeedIntentHandler = {
canHandle(handlerInput) {
return Alexa.getRequestType(handlerInput.requestEnvelope) === 'IntentRequest'
&& Alexa.getIntentName(handlerInput.requestEnvelope) === 'SetSpeedIntent';
},
handle: function (handlerInput) {
// Bound speed to (1-100)
let speed = Alexa.getSlotValue(handlerInput.requestEnvelope, 'Speed');
speed = Math.max(1, Math.min(100, parseInt(speed)));
Util.putSessionAttribute(handlerInput, 'speed', speed);
let speechOutput = `speed set to ${speed} percent.`;
return handlerInput.responseBuilder
.speak(speechOutput + BG_MUSIC)
.getResponse();
}
};
// Construct and send a custom directive to the connected gadget with
// data from the MoveIntent.
const MoveIntentHandler = {
canHandle(handlerInput) {
return Alexa.getRequestType(handlerInput.requestEnvelope) === 'IntentRequest'
&& Alexa.getIntentName(handlerInput.requestEnvelope) === 'MoveIntent';
},
handle: function (handlerInput) {
const request = handlerInput.requestEnvelope;
const direction = Alexa.getSlotValue(request, 'Direction');
// Duration is optional, use default if not available
const duration = Alexa.getSlotValue(request, 'Duration') || "2";
// Get data from session attribute
const attributesManager = handlerInput.attributesManager;
const speed = attributesManager.getSessionAttributes().speed || "50";
const endpointId = attributesManager.getSessionAttributes().endpointId || [];
// Construct the directive with the payload containing the move parameters
let directive = Util.build(endpointId, NAMESPACE, NAME_CONTROL,
{
type: 'move',
direction: direction,
duration: duration,
speed: speed
});
//const speechOutput = (direction === "brake")
// ? "Applying brake"
// : `${direction} ${duration} seconds at ${speed} percent speed`;
//return handlerInput.responseBuilder
// .speak(speechOutput + BG_MUSIC)
// .addDirective(directive)
// .getResponse();
return handlerInput.responseBuilder
.addDirective(directive)
.getResponse();
}
};
// Construct and send a custom directive to the connected gadget with data from
// the SetCommandIntent.
const SetCommandIntentHandler = {
canHandle(handlerInput) {
return Alexa.getRequestType(handlerInput.requestEnvelope) === 'IntentRequest'
&& Alexa.getIntentName(handlerInput.requestEnvelope) === 'SetCommandIntent';
},
handle: function (handlerInput) {
const request = handlerInput.requestEnvelope;
// Code is optional.
let code = Alexa.getSlotValue(request, 'Code') || "0";
let command = Alexa.getSlotValue(request, 'Command') || "CheckCode";
//if (!command) {
// return handlerInput.responseBuilder
// .speak("Can you repeat that?")
// .withShouldEndSession(false)
// .getResponse();
//}
const attributesManager = handlerInput.attributesManager;
let endpointId = attributesManager.getSessionAttributes().endpointId || [];
let speed = attributesManager.getSessionAttributes().speed || "50";
// Construct the directive with the payload containing the move parameters
let directive = Util.build(endpointId, NAMESPACE, NAME_CONTROL,
{
type: 'command',
command: command,
speed: speed,
code: code
});
let speechOutput = `command ${command} activated`;
if (command === 'sentry' || command === 'sentry mode' || command === "CheckCode") {
speechOutput = '';
}
return handlerInput.responseBuilder
.speak(speechOutput + BG_MUSIC)
.addDirective(directive)
.getResponse();
}
};
const EventsReceivedRequestHandler = {
// Checks for a valid token and endpoint.
canHandle(handlerInput) {
let { request } = handlerInput.requestEnvelope;
console.log('Request type: ' + Alexa.getRequestType(handlerInput.requestEnvelope));
if (request.type !== 'CustomInterfaceController.EventsReceived') return false;
const attributesManager = handlerInput.attributesManager;
let sessionAttributes = attributesManager.getSessionAttributes();
let customEvent = request.events[0];
// Validate event token
if (sessionAttributes.token !== request.token) {
console.log("Event token doesn't match. Ignoring this event");
return false;
}
// Validate endpoint
let requestEndpoint = customEvent.endpoint.endpointId;
if (requestEndpoint !== sessionAttributes.endpointId) {
console.log("Event endpoint id doesn't match. Ignoring this event");
return false;
}
return true;
},
handle(handlerInput) {
console.log("== Received Custom Event ==");
let customEvent = handlerInput.requestEnvelope.request.events[0];
let payload = customEvent.payload;
let name = customEvent.header.name;
let speechOutput;
if (name === 'Proximity') {
let distance = parseInt(payload.distance);
if (distance < 20) {
let speechOutput = "Hello! What do you want?";
return handlerInput.responseBuilder
.speak(speechOutput, "REPLACE_ALL")
.withShouldEndSession(false)
.getResponse();
}
} else if (name === 'Sentry') {
if ('fire' in payload) {
speechOutput = "Threat eliminated";
}
} else if (name === 'Speech') {
speechOutput = payload.speechOut;
} else if (name === 'SpeechWait') {
speechOutput = payload.speechOut;
return handlerInput.responseBuilder
.speak(speechOutput, "REPLACE_ALL")
.withShouldEndSession(false)
.getResponse();
}
else {
speechOutput = "Event not recognized. Awaiting new command.";
}
return handlerInput.responseBuilder
.speak(speechOutput + BG_MUSIC, "REPLACE_ALL")
.getResponse();
}
};
const ExpiredRequestHandler = {
canHandle(handlerInput) {
return Alexa.getRequestType(handlerInput.requestEnvelope) === 'CustomInterfaceController.Expired'
},
handle(handlerInput) {
console.log("== Custom Event Expiration Input ==");
// Set the token to track the event handler
const token = handlerInput.requestEnvelope.request.requestId;
Util.putSessionAttribute(handlerInput, 'token', token);
const attributesManager = handlerInput.attributesManager;
let duration = attributesManager.getSessionAttributes().duration || 0;
if (duration > 0) {
Util.putSessionAttribute(handlerInput, 'duration', --duration);
// Extends skill session
const speechOutput = `${duration} minutes remaining.`;
return handlerInput.responseBuilder
.addDirective(Util.buildStartEventHandler(token, 60000, {}))
.speak(speechOutput + BG_MUSIC)
.getResponse();
}
else {
// End skill session
return handlerInput.responseBuilder
.speak("Skill duration expired. Goodbye.")
.withShouldEndSession(true)
.getResponse();
}
}
};
// The SkillBuilder acts as the entry point for your skill, routing all request and response
// payloads to the handlers above. Make sure any new handlers or interceptors you've
// defined are included below. The order matters - they're processed top to bottom.
exports.handler = Alexa.SkillBuilders.custom()
.addRequestHandlers(
LaunchRequestHandler,
SetSpeedIntentHandler,
SetCommandIntentHandler,
MoveIntentHandler,
EventsReceivedRequestHandler,
ExpiredRequestHandler,
Common.HelpIntentHandler,
Common.CancelAndStopIntentHandler,
Common.SessionEndedRequestHandler,
Common.IntentReflectorHandler, // make sure IntentReflectorHandler is last so it doesn't override your custom intent handlers
)
.addRequestInterceptors(Common.RequestInterceptor)
.addErrorHandlers(
Common.ErrorHandler,
)
.lambda();
#!/usr/bin/env python3
import os
import sys
import time
import logging
import json
import random
import threading
from enum import Enum
from agt import AlexaGadget
from ev3dev2.led import Leds
from ev3dev2.sound import Sound
from ev3dev2.motor import OUTPUT_A, MediumMotor, SpeedPercent
from ev3dev2.sensor.lego import InfraredSensor
# Set the logging level to INFO to see messages from AlexaGadget
logging.basicConfig(level=logging.INFO, stream=sys.stdout, format='%(message)s')
logging.getLogger().addHandler(logging.StreamHandler(sys.stderr))
logger = logging.getLogger(__name__)
class Direction(Enum):
"""
The list of open/close commands and their variations.
These variations correspond to the skill slot values.
"""
OPEN = ['open', 'open door', 'door open']
CLOSE = ['close', 'close door', 'door close']
class Command(Enum):
"""
The list of preset commands and their invocation variation.
These variations correspond to the skill slot values.
"""
GUARD = ['guard', 'guard mode','doorkeeper']
CODECHECK = ['CheckCode']
class EventName(Enum):
"""
The list of custom event name sent from this gadget
"""
PROXIMITY = "Proximity"
SPEECH = "Speech"
SPEECHWAIT = "SpeechWait"
class Code(Enum):
CODE_GARBAGE = ['?']
class CodeValue:
CODE = 4567
class MindstormsGadget(AlexaGadget):
"""
A Mindstorms gadget that can perform bi-directional interaction with an Alexa skill.
"""
def __init__(self):
"""
Performs Alexa Gadget initialization routines and ev3dev resource allocation.
"""
super().__init__()
# Robot state
self.guard_mode = False
self.patrol_mode = False
self.is_door_open = False
# Connect two large motors on output ports B and C
self.sound = Sound()
self.leds = Leds()
self.ir = InfraredSensor()
self.door = MediumMotor(OUTPUT_A)
# Start threads
threading.Thread(target=self._proximity_thread, daemon=True).start()
# Code check parameters
self.codecheckCounter = 0
self.codeCheckLimitReached = False
self.start_time = 0
self.elapsed_time = 0
def on_connected(self, device_addr):
"""
Gadget connected to the paired Echo device.
:param device_addr: the address of the device we connected to
"""
self.leds.set_color("LEFT", "GREEN")
self.leds.set_color("RIGHT", "GREEN")
logger.info("{} connected to Echo device".format(self.friendly_name))
def on_disconnected(self, device_addr):
"""
Gadget disconnected from the paired Echo device.
:param device_addr: the address of the device we disconnected from
"""
self.leds.set_color("LEFT", "BLACK")
self.leds.set_color("RIGHT", "BLACK")
logger.info("{} disconnected from Echo device".format(self.friendly_name))
def on_custom_mindstorms_gadget_control(self, directive):
"""
Handles the Custom.Mindstorms.Gadget control directive.
:param directive: the custom directive with the matching namespace and name
"""
try:
payload = json.loads(directive.payload.decode("utf-8"))
print("Control payload: {}".format(payload), file=sys.stderr)
control_type = payload["type"]
if control_type == "doorIntent":
# Expected params: [direction, duration, speed]
self._door_intent(payload["direction"])
if control_type == "command":
# Expected params: [command]
if payload["code"] not in Code.CODE_GARBAGE.value:
self._activate(payload["command"],int(payload["code"]))
else:
self._send_event(EventName.SPEECHWAIT, {'speechOut': "Can you repeat that?"})
except KeyError:
print("Missing expected parameters: {}".format(directive), file=sys.stderr)
def _door_intent(self, direction, is_blocking=False):
"""
Handles door intent commands from the directive.
"""
print("Door Intent command: ({}, {})".format(direction, is_blocking), file=sys.stderr)
if direction in Direction.OPEN.value:
self._check_elspsed_time()
if (not self.codeCheckLimitReached):
if (not self.is_door_open):
self._send_event(EventName.SPEECHWAIT, {'speechOut': "Please say the secret code"})
else:
print("Door already open", file=sys.stderr)
self._send_event(EventName.SPEECH, {'speechOut': "Door already open!"})
if direction in Direction.CLOSE.value:
if (self.is_door_open):
print("Closing Door", file=sys.stderr)
self._send_event(EventName.SPEECH, {'speechOut': "Door Closed!"})
self.door.on_for_seconds(SpeedPercent(-20), 2)
self.is_door_open = False
self.guard_mode = True
else:
print("Door already closed", file=sys.stderr)
self._send_event(EventName.SPEECH, {'speechOut': "Door already closed"})
def _activate(self, command, code: int):
"""
Handles preset commands.
:param command: the commands e.g. activate guard mode
:param code: the secret code
"""
print("Activate command: ({}, {})".format(command, code), file=sys.stderr)
if command in Command.GUARD.value:
self.guard_mode = True
self._send_event(EventName.SPEECH, {'speechOut': "Guard mode activated"})
# Perform Shuffle posture
self.leds.set_color("LEFT", "YELLOW", 1)
self.leds.set_color("RIGHT", "YELLOW", 1)
if command in Command.CODECHECK.value:
self._check_elspsed_time()
if (not self.codeCheckLimitReached):
#self.guard_mode = False
if code == CodeValue.CODE:
print("Code check successful;", file=sys.stderr)
self.codeCheckLimitReached = False
self.codecheckCounter = 0
if (not self.is_door_open):
print("Opening Door", file=sys.stderr)
self._send_event(EventName.SPEECH, {'speechOut': "Door Opened!"})
self.door.on_for_seconds(SpeedPercent(20), 2)
self.is_door_open = True
#self.guard_mode = False
else:
print("Door already open", file=sys.stderr)
self._send_event(EventName.SPEECH, {'speechOut': "Door already open!"})
else:
#self.guard_mode = False
print("Code not right", file=sys.stderr)
self.codecheckCounter = self.codecheckCounter + 1
#self.guard_mode = True
if self.codecheckCounter > 2:
self.codeCheckLimitReached = True
self.start_time = time.time()
self._send_event(EventName.SPEECH, {'speechOut': "Max try reached. Try again after 1 minute"})
time.sleep(5)
self.guard_mode = True
else:
self._send_event(EventName.SPEECHWAIT, {'speechOut': "Code Wrong! Please try again"})
def _send_event(self, name: EventName, payload):
"""
Sends a custom event to trigger an action.
:param name: the name of the custom event
:param payload: the JSON payload
"""
self.send_custom_event('Custom.Mindstorms.Gadget', name.value, payload)
def _proximity_thread(self):
"""
Monitors anything in front of the door when guard mode is activated.
If the minimum distance is breached, send a custom event to trigger action on
the Alexa skill.
"""
count = 0
while True:
while self.guard_mode:
distance = self.ir.proximity
print("Proximity: {}".format(distance), file=sys.stderr)
count = count + 1 if distance < 20 else 0
if count > 3:
if (not self.codeCheckLimitReached):
print("Somebody too close. Sending event to skill", file=sys.stderr)
self.leds.set_color("LEFT", "RED", 1)
self.leds.set_color("RIGHT", "RED", 1)
self._send_event(EventName.PROXIMITY, {'distance': distance})
count = 0
self.guard_mode = False
time.sleep(0.2)
time.sleep(1)
def _check_elspsed_time(self):
if self.codeCheckLimitReached:
if time.time() - self.start_time > 60:
self.codeCheckLimitReached = False
self.codecheckCounter = 0
if __name__ == '__main__':
gadget = MindstormsGadget()
# Set LCD font and turn off blinking LEDs
os.system('setfont Lat7-Terminus12x6')
gadget.leds.set_color("LEFT", "BLACK")
gadget.leds.set_color("RIGHT", "BLACK")
# Startup sequence
gadget.sound.play_song((('C4', 'e'), ('D4', 'e'), ('E5', 'q')))
gadget.leds.set_color("LEFT", "GREEN")
gadget.leds.set_color("RIGHT", "GREEN")
# Gadget main entry point
gadget.main()
# Shutdown sequence
gadget.sound.play_song((('E5', 'e'), ('C4', 'e')))
gadget.leds.set_color("LEFT", "BLACK")
gadget.leds.set_color("RIGHT", "BLACK")
{
"interactionModel": {
"languageModel": {
"invocationName": "mindstorms",
"intents": [
{
"name": "AMAZON.CancelIntent",
"samples": []
},
{
"name": "AMAZON.HelpIntent",
"samples": []
},
{
"name": "AMAZON.StopIntent",
"samples": []
},
{
"name": "AMAZON.NavigateHomeIntent",
"samples": []
},
{
"name": "DoorIntent",
"slots": [
{
"name": "Direction",
"type": "DirectionType"
}
],
"samples": [
"{Direction} now",
"Please {Direction} the Door"
]
},
{
"name": "SetCommandIntent",
"slots": [
{
"name": "Command",
"type": "CommandType"
},
{
"name": "Code",
"type": "AMAZON.NUMBER"
}
],
"samples": [
"activate {Command} mode",
"activate {Command}",
"{Code}",
"Code is {Code}",
"Passcode is {Code}"
]
}
],
"types": [
{
"name": "DirectionType",
"values": [
{
"name": {
"value": "open"
}
},
{
"name": {
"value": "close"
}
}
]
},
{
"name": "CommandType",
"values": [
{
"name": {
"value": "guard"
}
}
]
}
]
}
}
}
'use strict'
const Alexa = require('ask-sdk-core');
const HelpIntentHandler = {
canHandle(handlerInput) {
return Alexa.getRequestType(handlerInput.requestEnvelope) === 'IntentRequest'
&& Alexa.getIntentName(handlerInput.requestEnvelope) === 'AMAZON.HelpIntent';
},
handle(handlerInput) {
const speakOutput = 'You can say hello to me! How can I help?';
return handlerInput.responseBuilder
.speak(speakOutput)
.reprompt(speakOutput)
.getResponse();
}
};
const CancelAndStopIntentHandler = {
canHandle(handlerInput) {
return Alexa.getRequestType(handlerInput.requestEnvelope) === 'IntentRequest'
&& (Alexa.getIntentName(handlerInput.requestEnvelope) === 'AMAZON.CancelIntent'
|| Alexa.getIntentName(handlerInput.requestEnvelope) === 'AMAZON.StopIntent');
},
handle(handlerInput) {
const speakOutput = 'Goodbye!';
return handlerInput.responseBuilder
.speak(speakOutput)
.getResponse();
}
};
const SessionEndedRequestHandler = {
canHandle(handlerInput) {
return Alexa.getRequestType(handlerInput.requestEnvelope) === 'SessionEndedRequest';
},
handle(handlerInput) {
// Any cleanup logic goes here.
return handlerInput.responseBuilder.getResponse();
}
};
// The intent reflector is used for interaction model testing and debugging.
// It will simply repeat the intent the user said. You can create custom handlers
// for your intents by defining them above, then also adding them to the request
// handler chain below.
const IntentReflectorHandler = {
canHandle(handlerInput) {
return Alexa.getRequestType(handlerInput.requestEnvelope) === 'IntentRequest';
},
handle(handlerInput) {
const intentName = Alexa.getIntentName(handlerInput.requestEnvelope);
const speakOutput = `You just triggered ${intentName}`;
return handlerInput.responseBuilder
.speak(speakOutput)
.reprompt("I don't understand this command, try again")
.getResponse();
}
};
// Generic error handling to capture any syntax or routing errors. If you receive an error
// stating the request handler chain is not found, you have not implemented a handler for
// the intent being invoked or included it in the skill builder below.
const ErrorHandler = {
canHandle() {
return true;
},
handle(handlerInput, error) {
console.log(`~~~~ Error handled: ${error.stack}`);
const speakOutput = `Sorry, I had trouble doing what you asked. Please try again.`;
return handlerInput.responseBuilder
.speak(speakOutput)
.reprompt(speakOutput)
.getResponse();
}
};
// The request interceptor is used for request handling testing and debugging.
// It will simply log the request in raw json format before any processing is performed.
const RequestInterceptor = {
process(handlerInput) {
let { attributesManager, requestEnvelope } = handlerInput;
let sessionAttributes = attributesManager.getSessionAttributes();
// Log the request for debug purposes.
console.log(`=====Request==${JSON.stringify(requestEnvelope)}`);
console.log(`=========SessionAttributes==${JSON.stringify(sessionAttributes, null, 2)}`);
}
};
module.exports = {
HelpIntentHandler,
CancelAndStopIntentHandler,
SessionEndedRequestHandler,
IntentReflectorHandler,
ErrorHandler,
RequestInterceptor
};
{
"name": "agt-mindstorms",
"version": "1.1.0",
"description": "A sample skill demonstrating how to use AGT with Lego Mindstorms",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "Amazon Alexa",
"license": "ISC",
"dependencies": {
"ask-sdk-core": "^2.6.0",
"ask-sdk-model": "^1.18.0",
"aws-sdk": "^2.326.0",
"request": "^2.81.0",
"lodash": "^4.17.11"
}
}
{
"name": "agt-mindstorms",
"version": "1.1.0",
"description": "A skill to control EV3 garage door with Lego Mindstorms",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "Amazon Alexa",
"license": "ISC",
"dependencies": {
"ask-sdk-core": "^2.6.0",
"ask-sdk-model": "^1.18.0",
"aws-sdk": "^2.326.0",
"request": "^2.81.0",
"lodash": "^4.17.11"
}
}
Comments