Alexa Skills via Php

Discuss the Amazon Alexa Assistant
Post Reply
User avatar
ZerOne
Site Admin
Posts: 108
Joined: Sun Dec 13, 2020 8:21 am

https://developer.amazon.com/en-US/alex ... ome-skills

Alexa Smart Home Skills
https://developer.amazon.com/en-US/docs ... l-api.html

Good guide on using a HTTPS Endpoint (Instead of using the AWS Lambda Function
https://www.hackster.io/user00317224/co ... rok-de41d1
And
https://www.instructables.com/Control-R ... o-and-Pyt/
This example uses Ngrok (Which exposes local servers behind NATs and firewalls to the public internet over secure tunnels),

Example Python Script, which listens to port 5000

Code: Select all

from flask import Flask
from flask_ask import Ask, statement, convert_errors
import RPi.GPIO as GPIO
import logging

GPIO.setmode(GPIO.BCM)

app = Flask(__name__)
ask = Ask(app, '/')

logging.getLogger("flask_ask").setLevel(logging.DEBUG)

@ask.intent('GPIOControlIntent', mapping={'status': 'status', 'pin': 'pin'})
def gpio_control(status, pin):

    try:
        pinNum = int(pin)
    except Exception as e:
        return statement('Pin number not valid.')

    GPIO.setup(pinNum, GPIO.OUT)

    if status in ['on', 'high']:    GPIO.output(pinNum, GPIO.HIGH)
    if status in ['off', 'low']:    GPIO.output(pinNum, GPIO.LOW)

    return statement('Turning pin {} {}'.format(pin, status))
Example Code for Amazon Echo / Alexa Intent using PHP
Obtained from https://gist.github.com/solariz/a7b7b09 ... 2b66b9b341

Code: Select all

<?php
/* This is a simple PHP example to host your own Amazon Alexa Skill written in PHP.
In my Case it connects to my smarthome Raspberry pi Cat Feeder with two intents;
1: Dispense Food to the cats.
2: When did the Feeder last time feed the cats? Return a spoken time / date
This Script contains neccessary calls and security to give you a easy to use DIY example.
v2016.12.29    
Details in my Blogpost:  https://solariz.de/de/amazon-echo-alexa-meets-catfeeder.htm
*/
header('Cache-Control: no-cache, must-revalidate');
header('Expires: Mon, 26 Jul 1997 05:00:00 GMT');

// SETUP / CONFIG

$SETUP = array(
	'SkillName' => "CatFeeder",
	'SkillVersion' => '1.0',
	'ApplicationID' => 'amzn1.ask.skill.45c11234-123a-1234-ffaa-1234567890a', // From your ALEXA developer console like: 'amzn1.ask.skill.45c11234-123a-1234-ffaa-1234567890a'
	'CheckSignatureChain' => true, // make sure the request is a true amazonaws api call
	'ReqValidTime' => 60, // Time in Seconds a request is valid
	'AWSaccount' => 'amzn1.ask.account.O3SYEMU2PH2QSTY8OCXLFOZ98T3IJYYJWSAZT48Q', //If this is != empty the specified session->user->userId is required. This is usefull for account bound private only skills
	'validIP' => array(
		"72.21.217.",
		"54.240.197."
	) , // Limit allowed requests to specified IPv4, set to FALSE to disable the check.
	'LC_TIME' => "de_DE"

	// We use german Echo so we want our date output to be german

);
setlocale(LC_TIME, $SETUP['LC_TIME']);

// Getting Input

$rawJSON = file_get_contents('php://input');
$EchoReqObj = json_decode($rawJSON);

if (is_object($EchoReqObj) === false) ThrowRequestError();
$RequestType = $EchoReqObj->request->type;

// Check if Amazon is the Origin

if (is_array($SETUP['validIP']))
	{
	$isAllowedHost = false;
	foreach($SETUP['validIP'] as $ip)
		{
		if (stristr($_SERVER['REMOTE_ADDR'], $ip))
			{
			$isAllowedHost = true;
			break;
			}
		}

	if ($isAllowedHost == false) ThrowRequestError(403, "Forbidden, your Host is not allowed to make this request!");
	unset($isAllowedHost);
	}

// Check if correct requestId

if (strtolower($EchoReqObj->session->application->applicationId) != strtolower($SETUP['ApplicationID']) || empty($EchoReqObj->session->application->applicationId))
	{
	ThrowRequestError(401, "Forbidden, unkown Application ID!");
	}

// Check SSL Signature Chain

if ($SETUP['CheckSignatureChain'] == true)
	{
	if (preg_match("/https:\/\/s3.amazonaws.com(\:443)?\/echo.api\/*/i", $_SERVER['HTTP_SIGNATURECERTCHAINURL']) == false)
		{
		ThrowRequestError(403, "Forbidden, unkown SSL Chain Origin!");
		}

	// PEM Certificate signing Check
	// First we try to cache the pem file locally

	$local_pem_hash_file = sys_get_temp_dir() . '/' . hash("sha256", $_SERVER['HTTP_SIGNATURECERTCHAINURL']) . ".pem";
	if (!file_exists($local_pem_hash_file))
		{
		file_put_contents($local_pem_hash_file, file_get_contents($_SERVER['HTTP_SIGNATURECERTCHAINURL']));
		}

	$local_pem = file_get_contents($local_pem_hash_file);
	if (openssl_verify($rawJSON, base64_decode($_SERVER['HTTP_SIGNATURE']) , $local_pem) !== 1)
		{
		ThrowRequestError(403, "Forbidden, failed to verify SSL Signature!");
		}

	// Parse the Certificate for additional Checks

	$cert = openssl_x509_parse($local_pem);
	if (empty($cert)) ThrowRequestError(424, "Certificate parsing failed!");

	// SANs Check

	if (stristr($cert['extensions']['subjectAltName'], 'echo-api.amazon.com') != true) ThrowRequestError(403, "Forbidden! Certificate SANs Check failed!");

	// Check Certificate Valid Time

	if ($cert['validTo_time_t'] < time())
		{
		ThrowRequestError(403, "Forbidden! Certificate no longer Valid!");

		// Deleting locally cached file to fetch a new at next req

		if (file_exists($local_pem_hash_file)) unlink($local_pem_hash_file);
		}

	// Cleanup

	unset($local_pem_hash_file, $cert, $local_pem);
	}

// Check Valid Time

if (time() - strtotime($EchoReqObj->request->timestamp) > $SETUP['ReqValidTime']) ThrowRequestError(408, "Request Timeout! Request timestamp is to old.");

// Check AWS Account bound, if this is set only a specific aws account can run the skill

if (!empty($SETUP['AWSaccount']))
	{
	if (empty($EchoReqObj->session->user->userId) || $EchoReqObj->session->user->userId != $SETUP['AWSaccount'])
		{
		ThrowRequestError(403, "Forbidden! Access is limited to one configured AWS Account.");
		}
	}

$JsonOut = GetJsonMessageResponse($RequestType, $EchoReqObj);
header('Content-Type: application/json');
header("Content-length: " . strlen($JsonOut));
echo $JsonOut;
exit();

// -----------------------------------------------------------------------------------------//
//					     functions
// -----------------------------------------------------------------------------------------//
// This function returns a json blob for output

function GetJsonMessageResponse($RequestMessageType, $EchoReqObj)
	{
	GLOBAL $SETUP;
	$RequestId = $EchoReqObj->request->requestId;
	$ReturnValue = "";
	if ($RequestMessageType == "LaunchRequest")
		{
		$return_defaults = array(
			'version' => $SETUP['SkillVersion'],
			'sessionAttributes' => array(
				'countActionList' => array(
					'read' => true,
					'category' => true
				)
			) ,
			'response' => array(
				'outputSpeech' => array(
					'type' => "PlainText",
					'text' => "Willkommen beim CatFeeder Beispiel"
				) ,
				'card' => array(
					'type' => "Simple",
					'title' => "CatFeeder",
					'content' => "Test Content"
				) ,
				'reprompt' => array(
					'outputSpeech' => array(
						'type' => "PlainText",
						'text' => "Kann ich dir noch weiter behilflich sein?"
					)
				)
			) ,
			'shouldEndSession' => true
		);
		$ReturnValue = json_encode($return_defaults);
		}
	elseif ($RequestMessageType == "SessionEndedRequest")
		{
		$ReturnValue = json_encode(array(
			'type' => "SessionEndedRequest",
			'requestId' => $RequestId,
			'timestamp' => date("c") ,
			'reason' => "USER_INITIATED"
		));
		}
	elseif ($RequestMessageType == "IntentRequest")
		{
		if ($EchoReqObj->request->intent->name == "CatFeederFeed") // Alexa Intent name
			{

			// do what ever your intent should do here. In my Case I call home to my raspberry pi, see function comment for more info.

			getRequestPayload(array(
				'action' => "feed",
				'size' => 1
			));
			$SpeakPhrase = "OK";
			}
		elseif ($EchoReqObj->request->intent->name == "CatFeederLast") // 2nd Alexa Intent name
			{

			// do what ever your intent should do here. In my Case I call home to my raspberry pi, see function comment for more info.

			$last_feed = getRequestPayload(array(
				'action' => "LastFeed"
			));
			if (!$last_feed || $last_feed <= 1000000000)
				{ // Should return a linux timestamp, check if plausible, else Error.
				$SpeakPhrase = "Ich kann diese Information momentan leider nicht ermitteln.";
				}
			elseif (time() - $last_feed < 900) $SpeakPhrase = "Die Katzen wurden gerade eben gefüttert.";
			elseif (time() - $last_feed < 3600) $SpeakPhrase = "Die Katzen wurden vor weniger als einer Stunde gefüttert.";
			elseif (time() - $last_feed < 7200) $SpeakPhrase = "Die Katzen wurden vor eins bis zwei Stunden gefüttert.";
			  else
				{ // More human readable Date formating:
				if (strftime("%e") == strftime("%e", $last_feed))
					{
					$day = "heute";
					}
				elseif (intval(strftime("%e", $last_feed)) == intval(strftime("%e") - 1))
					{
					$day = "gestern";
					}
				  else
					{
					$day = strftime("%e. %B", $last_feed);
					}

				$SpeakPhrase = "Die letzte Fütterung war " . $day . " um " . strftime("%H:%M", $last_feed);
				}
			}

		$ReturnValue = json_encode(array(
			'version' => $SETUP['SkillVersion'],
			'sessionAttributes' => array(
				'countActionList' => array(
					'read' => true,
					'category' => true
				)
			) ,
			'response' => array(
				'outputSpeech' => array(
					'type' => "PlainText",
					'text' => $SpeakPhrase
				) ,
				'card' => array(
					'type' => "Simple",
					'title' => "CatFeeder",
					'content' => $SpeakPhrase
				)
			) ,
			'shouldEndSession' => true
		));
		}
	  else
		{
		ThrowRequestError();
		}

	return $ReturnValue;
	} // end function GetJsonMessageResponse

function ThrowRequestError($code = 400, $msg = 'Bad Request')
	{
	GLOBAL $SETUP;
	http_response_code($code);
	echo "Error " . $code . "<br />\n" . $msg;
	error_log("alexa/" . $SETUP['SkillName'] . ":\t" . $msg, 0);
	exit();
	}

function getRequestPayload($payload)
	{
	/* this is just a custom function to get my connection to my home device.
	In this example it's a raspberry pi based CatFeeder listening to POST requests.
	It is using a let's encrypt SSL cert and a basic HTTP Auth.
	Use it as a example to DIY here: */
	$username = "basicauthuser";
	$password = "basicauthpasswd";
	$host = "https://myawesome.raspberry.pi.at.home/catfeed_post.php";
	$process = curl_init($host);
	curl_setopt($process, CURLOPT_SSL_VERIFYHOST, 2);
	curl_setopt($process, CURLOPT_SSL_VERIFYPEER, 1);
	curl_setopt($process, CURLOPT_USE_SSL, CURLUSESSL_ALL);
	curl_setopt($process, CURLOPT_SSL_VERIFYSTATUS, 1);
	curl_setopt($process, CURLOPT_HEADER, FALSE);
	curl_setopt($process, CURLOPT_USERPWD, $username . ":" . $password);
	curl_setopt($process, CURLOPT_TIMEOUT, 8);
	curl_setopt($process, CURLOPT_POST, 1);
	curl_setopt($process, CURLOPT_VERBOSE, FALSE);
	curl_setopt($process, CURLOPT_POSTFIELDS, http_build_query($payload));
	curl_setopt($process, CURLOPT_RETURNTRANSFER, TRUE);
	$return = curl_exec($process);
	curl_close($process);
	return $return;
	}
User avatar
ZerOne
Site Admin
Posts: 108
Joined: Sun Dec 13, 2020 8:21 am

PHP Libraries for Amazon Alexa Skill Development
https://github.com/maxbeckers/amazon-alexa-php
And
https://github.com/MiniCodeMonkey/amazon-alexa-php

Another great example using PHP
https://www.welaunch.io/en/2018/05/crea ... -tutorial/

And more Information
https://en.philipp-guttmann.de/Blog/Ale ... point_PHP/

A PHP Hello world example (That is a little dated)
https://blog.gaiterjones.com/amazon-ale ... d-example/

And a nice easy to follow Step Through tutorial
https://www.pierrefay.com/developers-tr ... kills.html
Post Reply

Return to “Amazon Alexa Discussion”