Hackster is hosting Hackster Holidays, Ep. 6: Livestream & Giveaway Drawing. Watch previous episodes or stream live on Monday!Stream Hackster Holidays, Ep. 6 on Monday!
Attila Tőkés
Published © CC BY-SA

Alexa / Walabot Controlled Smart 3D Printer

An Alexa and Walabot controlled smart 3D printer.

IntermediateFull instructions provided24 hours3,512

Things used in this project

Hardware components

3D Printer
any printer with Gcode over UART capability will do
×1
Walabot Developer Pack
Walabot Developer Pack
Walabot Maker
×1
Raspberry Pi 3 Model B
Raspberry Pi 3 Model B
×1
Relay (generic)
a generic Relay module
×1
Android device
Android device
tablet or phone
×1

Software apps and online services

Alexa Skills Kit
Amazon Alexa Alexa Skills Kit
AWS IoT
Amazon Web Services AWS IoT
AWS Lambda
Amazon Web Services AWS Lambda

Story

Read more

Custom parts and enclosures

Walabot Holder

Holder that can be used to mount a Walabot Maker to a 20x20mm aluminium profile. Print scaled up to 102% for a better fit.

Schematics

Data Flow

Code

AWS Lambda

Python
The AWS Lamda that handles the Alexa directives and publishes data to the AWS IoT device. It also contains the on-the-fly IoT thing creation for new users.
"""
This sample demonstrates a simple skill built with the Amazon Alexa Skills Kit.
The Intent Schema, Custom Slots, and Sample Utterances for this skill, as well
as testing instructions are located at http://amzn.to/1LzFrj6

For additional samples, visit the Alexa Skills Kit Getting Started guide at
http://amzn.to/1LGWsLG
"""

# from __future__ import print_function

import logging
import time
import json
import uuid
import datetime
import urllib

from email.mime.text import MIMEText
from email.mime.application import MIMEApplication
from email.mime.multipart import MIMEMultipart

import boto3

client = boto3.client('iot-data')
iot_client = boto3.client('iot')

# Setup logger
logger = logging.getLogger()
logger.setLevel(logging.INFO)

thingName = None

def set_iot_value(what, value):
    # Change topic, qos and payload
    response = client.update_thing_shadow(
        thingName = thingName,
        payload = json.dumps({
            'state': {
                'desired': {
                    what: value
                }
            }
        }
        )
    )


# --------------- Helpers that build all of the responses ----------------------

def build_speechlet_response(title, output, reprompt_text, should_end_session):
    return {
        'outputSpeech': {
            'type': 'PlainText',
            'text': output
        },
        'card': {
            'type': 'Simple',
            'title': title,
            'content': output
        },
        'reprompt': {
            'outputSpeech': {
                'type': 'PlainText',
                'text': reprompt_text
            }
        },
        'shouldEndSession': should_end_session
    }


def build_response(session_attributes, speechlet_response):
    return {
        'version': '1.0',
        'sessionAttributes': session_attributes,
        'response': speechlet_response
    }


# --------------- Functions that control the skill's behavior ------------------

def get_welcome_response():
    """ If we wanted to initialize the session to have some attributes we could
    add those here
    """
    card_title = "Welcome"
    speech_output = "Welcome to the 3D Printing Skill. You can try to turn on the printer."

    return build_response({}, build_speechlet_response(
        card_title, speech_output, reprompt_text = None, should_end_session = False))


def get_help_response():
    # The user asked for help
    card_title = "Help"
    speech_output = "You can tell me to:\n" \
        "Turn the printer on or off.\n " \
        "Enable or disable Walabot gesture control.\n " \
        "Auto home all axes.\n " \
        "Preheat for PLA, ABS or PETG printing.\n " \
        "Start printing from SD card.\n\n" \
        "What would you like to do next?"
        
                    
    return build_response({}, build_speechlet_response(
        card_title, speech_output, reprompt_text = None, should_end_session = False))
        

def handle_session_end_request():
    card_title = "Session Ended"
    speech_output = "Thank you for trying the 3D Printing Skill. " \
                    "Have a nice day! "

    return build_response({}, build_speechlet_response(
        card_title, speech_output, reprompt_text = None, should_end_session = True))


def handle_turn_on(intent, session):
    set_iot_value('power', 'on')
    
    return build_response(session,
        build_speechlet_response(
            "Turn ON",
            "Ok. The printer now should be turned on",
            reprompt_text = None,
            should_end_session = True
        )
    )

def handle_turn_off(intent, session):
    set_iot_value('power', 'off')
    
    return build_response(session,
        build_speechlet_response(
            "Turn OFF",
            "Ok. The printer now should be turned off",
            reprompt_text = None,
            should_end_session = True
        )
    )

def handle_enable_walabot(intent, session):
    set_iot_value('walabot', 'on')
    
    return build_response(session,
        build_speechlet_response(
            "Walabot Gesture Control",
            "Ok. Use your hand to control the X, Y and Z axes",
            reprompt_text = None,
            should_end_session = True
        )
    )

def handle_disable_walabot(intent, session):
    set_iot_value('walabot', 'off')
    
    return build_response(session,
        build_speechlet_response(
            "Walabot Gesture Control",
            "Ok. Walabot Gesture Control is now disabled",
            reprompt_text = None,
            should_end_session = True
        )
    )

def handle_autohome(intent, session):
    set_iot_value('command', 'AutoHome')
    
    return build_response(session,
        build_speechlet_response(
            "Auto Home",
            "Ok.",
            reprompt_text = None,
            should_end_session = True
        )
    )


def handle_start_printing(intent, session):
    set_iot_value('command', 'StartPrinting')
    
    return build_response(session,
        build_speechlet_response(
            "Auto Home",
            "Ok.",
            reprompt_text = None,
            should_end_session = True
        )
    )


def handle_preheat(intent, session):
    if 'Material' in intent['slots']:
        material = intent['slots']['Material']['value'].replace('.', '').upper()
        
        if material not in { 'ABS', 'PLA', 'PETG' }:
            return build_response(session,
                build_speechlet_response(
                    "Preheat",
                    "Sorry, I don't know {}. The available material types are P.L.A, A.B.S and P.E.T.G.. What would you like to do next?".format(material),
                    reprompt_text = None,
                    should_end_session = False
                )
            )
        
        set_iot_value('command', 'Preheat')
        set_iot_value('material', material)
    
        return build_response(session,
            build_speechlet_response(
                "Preheat",
                "Ok.",
                reprompt_text = None,
                should_end_session = True
            )
        )
    
    else:
        return build_response(session,
            build_speechlet_response(
                "Preheat",
                "Sorry, you must specify a material type. The available ones are P.L.A, A.B.S and P.E.T.G.. What would you like to do next?",
                reprompt_text = None,
                should_end_session = False
            )
        )
        

# --------------- Events ------------------

def on_session_started(session_started_request, session):
    """ Called when the session starts """

    print("on_session_started requestId=" + session_started_request['requestId']
          + ", sessionId=" + session['sessionId'])


def on_launch(launch_request, session):
    """ Called when the user launches the skill without specifying what they
    want
    """

    print("on_launch requestId=" + launch_request['requestId'] +
          ", sessionId=" + session['sessionId'])
    # Dispatch to your skill's launch
    return get_welcome_response()


def on_intent(intent_request, session):
    """ Called when the user specifies an intent for this skill """

    print("on_intent requestId=" + intent_request['requestId'] +
          ", sessionId=" + session['sessionId'])

    intent = intent_request['intent']
    intent_name = intent_request['intent']['name']

    # Dispatch to your skill's intent handlers
    if intent_name == "TurnOnIntent":
        return handle_turn_on(intent, session)
        
    elif intent_name == "TurnOffIntent":
        return handle_turn_off(intent, session)
    
    if intent_name == "EnableWalabotIntent":
        return handle_enable_walabot(intent, session)
        
    elif intent_name == "DisableWalabotIntent":
        return handle_disable_walabot(intent, session)
    
    elif intent_name == "PreheatIntent":
        return handle_preheat(intent, session)
        
    elif intent_name == "AutoHomeIntent":
        return handle_autohome(intent, session)
        
    elif intent_name == "StartPrintingIntent":
        return handle_start_printing(intent, session)
        
    elif intent_name == "AMAZON.HelpIntent":
        return get_help_response()
        
    elif intent_name == "AMAZON.CancelIntent" or intent_name == "AMAZON.StopIntent":
        return handle_session_end_request()
        
    else:
        raise ValueError("Invalid intent")


def on_session_ended(session_ended_request, session):
    """ Called when the user ends the session.

    Is not called when the skill returns should_end_session=true
    """
    print("on_session_ended requestId=" + session_ended_request['requestId'] +
          ", sessionId=" + session['sessionId'])
    # add cleanup logic here


def create_iot_thing(userId, userEmail, userName):
    print("creating IoT Thing for new user " + userId)
    
    # Steps:
    thing_name = '3D_Printer-' + userId.replace(".", "-")

    # 1. create keys and certificate
    key_and_cert = iot_client.create_keys_and_certificate(setAsActive = True)
    
    print("Key & Certificate: {}".format(key_and_cert))

    # 2. create the thing
    thing = iot_client.create_thing(
        thingName = thing_name,
        thingTypeName = '3d-printer',
        attributePayload = {
            'attributes': {
                'UserId': userId
            },
        }
    )
    
    print("Thing: {}".format(thing))
    
    # 3. attach policy
    iot_client.attach_policy(
        policyName = "OneUserOneThing",
        target = key_and_cert['certificateArn']
    )
    
    print("attached policy")

    # 4. attach the certificate to the thing
    iot_client.attach_thing_principal(
        thingName = thing['thingName'],
        principal = key_and_cert['certificateArn']
    )
    
    print("attached certificate")
    
    # Send email
    
    msg = MIMEMultipart()
    
    msg['Subject'] = 'Alexa - 3D Printing Skill'
    msg['From'] = 'your.email@gmail.com'
    msg['To'] = userEmail
    
    msg.preamble = 'Multipart!\n'
    
    part = MIMEText(
        "Hi " + userName + "!\n" \
        "\n" \
        "Welcome to the 3D Printing Skill!\n" \
        "\n" \
        "A 3D Printer IoT thing was created for you:\n" + thing['thingName'] + "\n" \
        "\n" \
        "The thing's certificates are attached to the email.\n" \
        "\n" \
        "Have a goog day!")
    msg.attach(part)
    
    part = MIMEApplication(key_and_cert['certificatePem'])
    part.add_header('Content-Disposition', 'attachment', filename='{}-certificate.pem.crt.txt'.format(thing_name))
    msg.attach(part)
    
    part = MIMEApplication(key_and_cert['keyPair']['PrivateKey'])
    part.add_header('Content-Disposition', 'attachment', filename='{}-private.pem.key.txt'.format(thing_name))
    msg.attach(part)
    
    part = MIMEApplication(key_and_cert['keyPair']['PublicKey'])
    part.add_header('Content-Disposition', 'attachment', filename='{}-public.pem.key.txt'.format(thing_name))
    msg.attach(part)
    
    part = MIMEApplication(
        "-----BEGIN CERTIFICATE-----\n" \
        "MIIE0zCCA7ugAwIBAgIQGNrRniZ96LtKIVjNzGs7SjANBgkqhkiG9w0BAQUFADCB\n" \
        "yjELMAkGA1UEBhMCVVMxFzAVBgNVBAoTDlZlcmlTaWduLCBJbmMuMR8wHQYDVQQL\n" \
        "ExZWZXJpU2lnbiBUcnVzdCBOZXR3b3JrMTowOAYDVQQLEzEoYykgMjAwNiBWZXJp\n" \
        "U2lnbiwgSW5jLiAtIEZvciBhdXRob3JpemVkIHVzZSBvbmx5MUUwQwYDVQQDEzxW\n" \
        "ZXJpU2lnbiBDbGFzcyAzIFB1YmxpYyBQcmltYXJ5IENlcnRpZmljYXRpb24gQXV0\n" \
        "aG9yaXR5IC0gRzUwHhcNMDYxMTA4MDAwMDAwWhcNMzYwNzE2MjM1OTU5WjCByjEL\n" \
        "MAkGA1UEBhMCVVMxFzAVBgNVBAoTDlZlcmlTaWduLCBJbmMuMR8wHQYDVQQLExZW\n" \
        "ZXJpU2lnbiBUcnVzdCBOZXR3b3JrMTowOAYDVQQLEzEoYykgMjAwNiBWZXJpU2ln\n" \
        "biwgSW5jLiAtIEZvciBhdXRob3JpemVkIHVzZSBvbmx5MUUwQwYDVQQDEzxWZXJp\n" \
        "U2lnbiBDbGFzcyAzIFB1YmxpYyBQcmltYXJ5IENlcnRpZmljYXRpb24gQXV0aG9y\n" \
        "aXR5IC0gRzUwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCvJAgIKXo1\n" \
        "nmAMqudLO07cfLw8RRy7K+D+KQL5VwijZIUVJ/XxrcgxiV0i6CqqpkKzj/i5Vbex\n" \
        "t0uz/o9+B1fs70PbZmIVYc9gDaTY3vjgw2IIPVQT60nKWVSFJuUrjxuf6/WhkcIz\n" \
        "SdhDY2pSS9KP6HBRTdGJaXvHcPaz3BJ023tdS1bTlr8Vd6Gw9KIl8q8ckmcY5fQG\n" \
        "BO+QueQA5N06tRn/Arr0PO7gi+s3i+z016zy9vA9r911kTMZHRxAy3QkGSGT2RT+\n" \
        "rCpSx4/VBEnkjWNHiDxpg8v+R70rfk/Fla4OndTRQ8Bnc+MUCH7lP59zuDMKz10/\n" \
        "NIeWiu5T6CUVAgMBAAGjgbIwga8wDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8E\n" \
        "BAMCAQYwbQYIKwYBBQUHAQwEYTBfoV2gWzBZMFcwVRYJaW1hZ2UvZ2lmMCEwHzAH\n" \
        "BgUrDgMCGgQUj+XTGoasjY5rw8+AatRIGCx7GS4wJRYjaHR0cDovL2xvZ28udmVy\n" \
        "aXNpZ24uY29tL3ZzbG9nby5naWYwHQYDVR0OBBYEFH/TZafC3ey78DAJ80M5+gKv\n" \
        "MzEzMA0GCSqGSIb3DQEBBQUAA4IBAQCTJEowX2LP2BqYLz3q3JktvXf2pXkiOOzE\n" \
        "p6B4Eq1iDkVwZMXnl2YtmAl+X6/WzChl8gGqCBpH3vn5fJJaCGkgDdk+bW48DW7Y\n" \
        "5gaRQBi5+MHt39tBquCWIMnNZBU4gcmU7qKEKQsTb47bDN0lAtukixlE0kF6BWlK\n" \
        "WE9gyn6CagsCqiUXObXbf+eEZSqVir2G3l6BFoMtEMze/aiCKm0oHw0LxOXnGiYZ\n" \
        "4fQRbxC1lfznQgUy286dUV4otp6F01vvpX1FQHKOtw5rDgb7MzVIcbidJ4vEZV8N\n" \
        "hnacRHr2lVz2XTIIM6RUthg/aFzyQkqFOFSDX9HoLPKsEdao7WNq\n" \
        "-----END CERTIFICATE-----"
    )
    part.add_header('Content-Disposition', 'attachment', filename='VeriSign-Class-3-Public-Primary-Certification-Authority-G5.pem.txt')
    msg.attach(part)
    
    ses_client = boto3.client('ses')
    mailResult = ses_client.send_raw_email(
        RawMessage = { 'Data' : msg.as_string().encode() },
        Source = 'attitokes@gmail.com',
        Destinations = [userEmail]
    )
    
    print("Email sending: {}".format(mailResult))
    return thing_name

    
# --------------- Main handler ------------------

def lambda_handler(event, context):
    global thingName
    """ Route the incoming request based on type (LaunchRequest, IntentRequest,
    etc.) The JSON body of the request is provided in the event parameter.
    """
    print("event.session.application.applicationId=" +
          event['session']['application']['applicationId'])
          
    print(event)
    
    if not 'accessToken' in event['session']['user']:
        response = build_response({}, {
            'outputSpeech' : {
                'type': 'PlainText',
                'text': 'Please use the companion app to authenticate on Amazon to start using this skill'
            },

            'card': {
                'type': 'LinkAccount'
            },

            'shouldEndSession': True
        })
        print(response)
        return response
    
    else:
        userInfoResponse = urllib.request.urlopen("https://api.amazon.com/user/profile?access_token=" + event['session']['user']['accessToken']).read()
        userInfo = json.loads(userInfoResponse.decode('utf-8'))
        print("User info: {}".format(userInfo))
        
        # iot thing lookup
        thingsResponse = iot_client.list_things(
            maxResults = 1,
            attributeName = "UserId",
            attributeValue = userInfo['user_id']
            #thingTypeName = '3d-printer'
        )
        
        print("Thing lookup response: {}".format(thingsResponse))
        
        if len(thingsResponse['things']) == 1:
            # thing found
            thingName = thingsResponse['things'][0]['thingName']
            print("Thing found: " + thingName)
            
        else:
            # thing not found, we will create one on-the-fly
            thingName = create_iot_thing(userInfo['user_id'], userInfo['email'], userInfo['name'])
            print("Created new thing: " + thingName)
        

    """
    Uncomment this if statement and populate with your skill's application ID to
    prevent someone else from configuring a skill that sends requests to this
    function.
    """
    # if (event['session']['application']['applicationId'] !=
    #         "amzn1.echo-sdk-ams.app.[unique-value-here]"):
    #     raise ValueError("Invalid Application ID")

    if event['session']['new']:
        on_session_started({'requestId': event['request']['requestId']},
                           event['session'])

    if event['request']['type'] == "LaunchRequest":
        return on_launch(event['request'], event['session'])
    elif event['request']['type'] == "IntentRequest":
        return on_intent(event['request'], event['session'])
    elif event['request']['type'] == "SessionEndedRequest":
        return on_session_ended(event['request'], event['session'])

Raspberry Pi Client

Python
The Python code running on the Raspberry Pi. It contains the AWS IoT shadow client, the 3D printer control interface and the Walabot gesture control functionality.
No preview (download only).

3D Printing Skill JSON

JSON
{
    "interactionModel": {
        "languageModel": {
            "invocationName": "three d printer",
            "intents": [
                {
                    "name": "AMAZON.CancelIntent",
                    "samples": []
                },
                {
                    "name": "AMAZON.HelpIntent",
                    "samples": []
                },
                {
                    "name": "AMAZON.StopIntent",
                    "samples": []
                },
                {
                    "name": "TurnOnIntent",
                    "slots": [],
                    "samples": [
                        "turn on",
                        "Turn on the printer"
                    ]
                },
                {
                    "name": "AutoHomeIntent",
                    "slots": [],
                    "samples": [
                        "Auto home all axes",
                        "Home all axes",
                        "Auto home"
                    ]
                },
                {
                    "name": "PreheatIntent",
                    "slots": [
                        {
                            "name": "Material",
                            "type": "MATERIAL_TYPE",
                            "samples": [
                                "PETG",
                                "ABS",
                                "PLA"
                            ]
                        }
                    ],
                    "samples": [
                        "Prepare for {Material} printing",
                        "Preheat for {Material} printing"
                    ]
                },
                {
                    "name": "TurnOffIntent",
                    "slots": [],
                    "samples": [
                        "turn off",
                        "Turn off the printer"
                    ]
                },
                {
                    "name": "EnableWalabotIntent",
                    "slots": [],
                    "samples": [
                        "enable walabot control",
                        "enable Walabot gesture control"
                    ]
                },
                {
                    "name": "DisableWalabotIntent",
                    "slots": [],
                    "samples": [
                        "disable walabot control",
                        "disable Walabot gesture control"
                    ]
                },
                {
                    "name": "StartPrintingIntent",
                    "slots": [],
                    "samples": [
                        "Start printing from SD card",
                        "Start printing"
                    ]
                }
            ],
            "types": [
                {
                    "name": "MATERIAL_TYPE",
                    "values": [
                        {
                            "id": "PETG",
                            "name": {
                                "value": "PETG"
                            }
                        },
                        {
                            "id": "ABS",
                            "name": {
                                "value": "ABS"
                            }
                        },
                        {
                            "id": "PLA",
                            "name": {
                                "value": "PLA"
                            }
                        }
                    ]
                }
            ]
        },
        "dialog": {
            "intents": [
                {
                    "name": "PreheatIntent",
                    "confirmationRequired": false,
                    "prompts": {},
                    "slots": [
                        {
                            "name": "Material",
                            "type": "MATERIAL_TYPE",
                            "confirmationRequired": false,
                            "elicitationRequired": true,
                            "prompts": {
                                "elicitation": "Elicit.Slot.841370314794.905683533812"
                            }
                        }
                    ]
                }
            ]
        },
        "prompts": [
            {
                "id": "Elicit.Slot.841370314794.905683533812",
                "variations": [
                    {
                        "type": "PlainText",
                        "value": "What material?"
                    }
                ]
            }
        ]
    }
}

3D Printer with Walabot support Android Studio Project

Java
the Android Studio project with the "3D Printer with Walabot support" Android App
No preview (download only).

3D printer with Walabot app APK (debug)

Java
No preview (download only).

updated Raspberry Pi Client with support for the Android App

Python
No preview (download only).

Credits

Attila Tőkés

Attila Tőkés

36 projects • 221 followers
Software Engineer experimenting with hardware projects involving IoT, Computer Vision, ML & AI, FPGA, Crypto and other related technologies.

Comments