David Springer
Created December 13, 2019

firehero

A prototyp of a voice controlled firefighting robot, which would be able to assist firefighters where it is to dangerous for humans.

IntermediateShowcase (no instructions)Over 2 days130

Things used in this project

Story

Read more

Schematics

Gearbox 1

Gearbox 2

Gearbox 3

gripping tool

water canon

Code

skill source code

JavaScript
modified from the LEGO Mindstorms Voice Challenge and translated to german
/*
 * Copyright 2019 Amazon.com, Inc. or its affiliates.  All Rights Reserved.
 *
 * You may not use this file except in compliance with the terms and conditions 
 * set forth in the accompanying LICENSE.TXT file.
 *
 * THESE MATERIALS ARE PROVIDED ON AN "AS IS" BASIS. AMAZON SPECIFICALLY DISCLAIMS, WITH 
 * RESPECT TO THESE MATERIALS, ALL WARRANTIES, EXPRESS, IMPLIED, OR STATUTORY, INCLUDING 
 * THE IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, AND NON-INFRINGEMENT.
*/

// This sample demonstrates sending directives to an Echo connected gadget from an Alexa skill
// using the Alexa Skills Kit SDK (v2). Please visit https://alexa.design/cookbook for additional
// examples on implementing slots, dialog management, session persistence, api calls, and more.

const Alexa = require('ask-sdk-core');
const Util = require('./util');
const Common = require('./common');

// 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) {

        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(`Ich kann leider keinen EV3 brick finden, bitte versuche es erneut oder aktiviere Bluetooth.`)
            .getResponse();
        }

        // Store the gadget endpointId to be used in this skill session
        let endpointId = apiResponse.endpoints[0].endpointId || [];
        Util.putSessionAttribute(handlerInput, 'endpointId', endpointId);

        return handlerInput.responseBuilder
            .speak("Hallo, du kannst mir jetzt Befehle für fire hero 3000 Prototyp sagen!")
            .reprompt("Sage bitte einen Befehl")
            .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);

        return handlerInput.responseBuilder
            .speak(`Geschwindigkeit und Kraft wurde auf ${speed} Prozent gesetzt.`)
            .reprompt("Sage bitte einen Befehl")
            .getResponse();
    }
};

// Construct and send a custom directive to the connected gadget with
// data from the MoveIntent request.
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
        const directive = Util.build(endpointId, NAMESPACE, NAME_CONTROL,
            {
                type: 'move',
                direction: direction,
                duration: duration,
                speed: speed
            });

        const speechOutput = (direction === "bremse")
            ?  "Bremse ausgelöst"
            : `${direction} ${duration} Sekunden mit ${speed} Prozent Geschwindigkeit`;

        return handlerInput.responseBuilder
            .speak(speechOutput)
            .reprompt("Sage bitte einen Befehl")
            .addDirective(directive)
            .getResponse();
    }
};

// Construct and send a custom directive to the connected gadget with data from
// the SetCommandIntent request.
const SetCommandIntentHandler = {
    canHandle(handlerInput) {
        return Alexa.getRequestType(handlerInput.requestEnvelope) === 'IntentRequest'
            && Alexa.getIntentName(handlerInput.requestEnvelope) === 'SetCommandIntent';
    },
    handle: function (handlerInput) {

        let command = Alexa.getSlotValue(handlerInput.requestEnvelope, 'Command');
        if (!command) {
            return handlerInput.responseBuilder
                .speak("Wie bitte? Kannst du das widerholen?")
                .reprompt("Sprich bitte ein bisschen lauter oder rede deutlicher, ich verstehe kein Wort! In sondergelagerten Sonderfällen kann es auch sein, dass ichTaub bin.").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
            });

        return handlerInput.responseBuilder
            .speak(`Jawohl verstanden ${command} durchgeführt`)
            .reprompt("Sage bitte einen Befehl")
            .addDirective(directive)
            .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,
        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();

firehero.py

MicroPython
Main program of my firehero robot.
#!/usr/bin/env python3
# Copyright 2019 David Springer

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, OUTPUT_B, OUTPUT_C, OUTPUT_D, MoveTank, SpeedPercent, MediumMotor

# Setze den Loglevel auf INFO um Meldungen im Logfile zu sehen und DEBUG um noch mehr Infos zu bekommen
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):
   
    VOR = ['vor', 'vorwrts']
    ZURUECK = ['zurck', 'rckwrts']
    LINKS = ['links', 'drehe links', 'links um', 'links drehen']
    RECHTS = ['drehe rechts', 'rechts um', 'rechts drehen', 'rechts']
    STOP = ['bremse', 'notaus', 'bremsen']

#rechts um 90 grad drehen!!!!!

class Command(Enum):
    """
    Eine Liste der Befehle und deren auslsenden Worte.
    Diese Variationen decken sich mit den Slot Varianten.
    """
    KREISEN = ['kreisen', 'kreis']
    PATROL = ['patrolieren']
    SCHWENKEN = ['lschrichtung ndern', 'lscharm schwenken']
    HYDROSCHILD = ['wasservorhang', 'hydroschild', 'schutzmodus']
    GREIFER_AUF = ['greifer auf']
    GREIFER_ZU = ['greifer zu']
    FOLGETONHORN = ['folgetonhorn']


class MindstormsGadget(AlexaGadget):
    """
    Ein Mindstorm Gadget, welches Bewegungen durchfhrt die mittels Stimme befohlen werden
    Es gibt zwei Befehlsarten, direkte Bewegungen und vordefinierte Bewegungen.
    """

    def __init__(self):
        """
        Fhrt Alexa Gadget Initialisierung der Routinen aus und weist ev3dev Ressourcen zu.
        """
        super().__init__()

        # Gadget state
        self.patrol_mode = False

        # Ev3dev initialization
        self.leds = Leds()
        self.sound = Sound()
        self.drive = MoveTank(OUTPUT_B, OUTPUT_C)
        self.weapon = MediumMotor(OUTPUT_A)
        self.greifer = MediumMotor(OUTPUT_D)

        # Start threads
        threading.Thread(target=self._patrol_thread, daemon=True).start()

    def on_connected(self, device_addr):
        """
        Gadget verbindet sich mit dem Echo Gert
        :param device_addr: die Adresse des verbundenen Gerts
        """
        #wenn fehler orange geht nicht!
        self.leds.set_color("LEFT", "GREEN")
        self.leds.set_color("RIGHT", "GREEN")
        logger.info("{} verbunden mit ECHO Gert".format(self.friendly_name))

    def on_disconnected(self, device_addr):
        """
        Gadget unterbricht Verbindung zum Echo Gert
        :param device_addr: die Adresse des vorher verbundenen Gerts
        """
        self.leds.set_color("LEFT", "BLACK")
        self.leds.set_color("RIGHT", "BLACK")
        logger.info("{} verbindung fehlgeschlagen/abbgebrochen".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 == "move":

                # Expected params: [direction, duration, speed]
                self._move(payload["direction"], int(payload["duration"]), int(payload["speed"]))

            if control_type == "command":
                # Expected params: [command]
                self._activate(payload["command"])

        except KeyError:
            print("Folgender Parameter fehlt oder ist nicht vorhanden: {}".format(directive), file=sys.stderr)

    def _move(self, direction, duration: int, speed: int, is_blocking=False):
        """
        Behandelt move Kommandos.
        Rechts und links Bewegungen knnen je nach Oberflche unter- oder bersteuern.
        
        :param direction: Bewegungsrichtung
        :param duration: Dauer der Bewegung in Sekunden
        :param speed: Geschwindigkeit in Prozent als Integerwert
        :param is_blocking: wenm gesetzt luft Motor den duration Wert bis neues Kommando gegeben werden kann
        """
        print("Move Befehl: ({}, {}, {}, {})".format(direction, speed, duration, is_blocking), file=sys.stderr)
        if direction in Direction.VOR.value:
            self.drive.on_for_seconds(SpeedPercent(-speed), SpeedPercent(-speed), duration, block=is_blocking)

        if direction in Direction.ZURUECK.value:
            self.drive.on_for_seconds(SpeedPercent(speed), SpeedPercent(speed), duration, block=is_blocking)

        if direction in (Direction.RECHTS.value + Direction.LINKS.value):
            self._turn(direction, speed)
            self.drive.on_for_seconds(SpeedPercent(-speed), SpeedPercent(-speed), duration, block=is_blocking)

        if direction in Direction.STOP.value:
            self.drive.off()
            self.patrol_mode = False

    def _activate(self, command, speed=-50):
        """
        Behandelt die Vorgabewerte
        :param command: Vorgabewert fr das Kommando
        :param speed: Vorgabewert fr Geschwindigkeit falls anwendbar
        """
        print("Aktiviere Kommando: ({}, {})".format(command, speed), file=sys.stderr)
        if command in Command.KREISEN.value:
            self.drive.on_for_seconds(SpeedPercent(int(speed)), SpeedPercent(5), 12)

        if command in Command.PATROL.value:
            # Set patrol mode to resume patrol thread processing
            self.patrol_mode = True
            
        if command in Command.SCHWENKEN.value:
            self.weapon.on_for_rotations(SpeedPercent(30), 3)

        if command in Command.HYDROSCHILD.value:
            self.weapon.on_for_rotations(SpeedPercent(100), 30)

        if command in Command.GREIFER_AUF.value:
            self.greifer.on_for_rotations(SpeedPercent(-5), 1.5)

        if command in Command.GREIFER_ZU.value:
            self.greifer.on_for_rotations(SpeedPercent(5), 1.5)

        if command in Command.FOLGETONHORN.value:
            self.sound.play_file('martinshorn.wav', volume=100)

    def _turn(self, direction, speed):
        """
        Dreht basierend auf Geschwindigkeit und Richtung.
        Kalibriert fr harte Oberflchen.
        :param direction: Drehrichtung
        :param speed: Drehgeschwindigkeit
        """
        if direction in Direction.LINKS.value:
            self.drive.on_for_seconds(SpeedPercent(speed), SpeedPercent(0), 2)

        if direction in Direction.RECHTS.value:
            self.drive.on_for_seconds(SpeedPercent(0), SpeedPercent(speed), 2)

    def _patrol_thread(self):
        """
        Fhrt im Patrolliermodus zufllige Bewegungen aus.
        """
        while True:
            while self.patrol_mode:
                print("Patrol mode activated randomly picks a path", file=sys.stderr)
                direction = random.choice(list(Direction))
                duration = random.randint(1, 5)
                speed = random.randint(1, 4) * 25

                while direction == Direction.STOP:
                    direction = random.choice(list(Direction))

                # direction: all except stop, duration: 1-5s, speed: 25, 50, 75, 100
                self._move(direction.value[0], duration, speed)
                time.sleep(duration)
            time.sleep(1)


if __name__ == '__main__':

    gadget = MindstormsGadget()

    # Setzt die Schriftart am LCD Monitor und schaltet LEDs ab
    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", "ORANGE")
    gadget.leds.set_color("RIGHT", "ORANGE")
 
    # 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")

Credits

David Springer

David Springer

1 project • 1 follower
Thanks to PNG tree.

Comments