Green Thumb - An EV3+Alexa Plant Caretaker
Living in the northern hemisphere, the days are short in the winter. Indoor plants struggle. This project allows hands free plant care. Use Alexa to turn on and off a Grow Light.
This project can be expanded to include, "water my plant" and "add fertilizer" with the addition of a second motor.
How it Works
Use Alexa to activate and use Plant Caretaker. Watch this video to see how it works.
Hardware
For this project, you will need one EV3 brick, one Large Motor and one Amazon Alexa Device (I used an Echo Show). Connect the Large Motor to Port D on the EV3 Brick. What you build will depend upon the type of Grow Light that you purchase. There are many different varieties and the on/off switch varies greatly. The goal is to build a lever that is connected to a large motor that presses the button on/off. Instructions are provided only for the one I used. Therefore, you will have to customize the Grow Light Activator shown below to suit your model.
Mount the Grow Light Activator above the plant(s). You do not have to have a LEGO structure to mount it with. You can use rope and PVC or any other materials to support your Grow Light Activator above the plant.
If you choose to expand the project to water plants, etc, you can add additional motors.
Software
The software consists of two parts - the part that runs as an Alexa skill and the part that runs on the EV3. I describe each part below.
Alexa Skill. The Alexa skill for this application is activated using the utterance "Plant Caretaker". At this time, the skill has a single unique intent called LightIntent. LightIntent is invoked by the utterance "light" and takes a single input slot to indicate on or off (to turn the light on or off). In the future, I plan to add intents to water my plants as well. This intent configuration is in the model.json file that is on the GitHub repository linked to this project.
The javascript code for the skill has two interesting sections. The first, shown below, connects the Alexa device to the EV3. It informs the user if the EV3 is not connected.
let request = handlerInput.requestEnvelope;
let { apiEndpoint, apiAccessToken } = request.context.System;
let 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();
}
The second interesting section handles the LightIntent described in the model.json file.
const LightIntentHandler = {
canHandle(handlerInput) {
return Alexa.getRequestType(handlerInput.requestEnvelope) === 'IntentRequest'
&& Alexa.getIntentName(handlerInput.requestEnvelope) === 'LightIntent';
},
handle: function (handlerInput) {
const request = handlerInput.requestEnvelope;
const action = Alexa.getSlotValue(request, 'LightStatus');
let speechOutput = 'Turning light ' + action
// Get data from session attribute
const attributesManager = handlerInput.attributesManager;
const endpointId = attributesManager.getSessionAttributes().endpointId || [];
// Construct the directive with the payload containing the move parameters
const directive = Util.build(endpointId, NAMESPACE, NAME_CONTROL,
{
type: 'light',
action: action,
});
return handlerInput.responseBuilder
.speak(speechOutput)
.reprompt("awaiting command")
.addDirective(directive)
.getResponse();
}
};
The above code first extracts the action that the user is requesting (on or off) from the user input. It then constructs a message for the EV3 using Util.build. This message indicates that it is a 'light' command and the action that is requested. Finally, the handler has the Alexa repeat the command back to the user and send the message as part of its return statement.
EV3 Program. The first part of the EV3 program handles incoming messages from the Alexa.
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 == "light":
# Expected params: [action]
self._activate(payload["action"])
except KeyError:
print("Missing expected parameters: {}".format(directive), file=sys.stderr)
The above code checks to see if it is "light" message from the Alexa and, if it is, calls the method _activate.
def _activate(self, command):
"""
Handles preset commands.
:param command: on or off
"""
print("Light command: ({})".format(command), file=sys.stderr)
if command in Light.ON.value:
self.switch.on_for_degrees(SpeedPercent(20), 50)
if command in Light.OFF.value:
self.switch.on_for_degrees(SpeedPercent(20), -50)
The _activate code checks to see if the command that was sent is either ON or OFF (or a set of similar keywords). If it is ON, the program moves the Large Motor to turn on the light. Similarly, the OFF command moves the Large Motor in the opposite direction.
Comments