Skip to content

Commit 83780d3

Browse files
committed
initial commit
1 parent f751e9f commit 83780d3

34 files changed

+4005
-0
lines changed

.gitignore

+2
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
config.json
2+
13
# Byte-compiled / optimized / DLL files
24
__pycache__/
35
*.py[cod]

README.md

+8
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,10 @@
11
# echoir
22
Remote control skill for Alexa
3+
4+
The client-side job runner only supports IguanaWorks IR.
5+
# On the machine (I use Raspberry PI) that has the IR transceiver set up, use the record_remote script to record some IR blasts you want to be able to initiate from the Echo.
6+
# Set up your Alexa skill on the Alexa website. This can't be automated at the moment.
7+
# Set up your AWS Lambda function.
8+
# Complete config_sample.json, rename to config.json, zip it up with echo_remote_lambda.py, then upload to Lambda.
9+
# Run echoremote.py on a machine with IguanaWorks IR blaster.
10+
# That's it.

config_sample.json

+15
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
{
2+
"skills": {
3+
"amzn1.echo-sdk-ams.app.sample1": "remote",
4+
"amzn1.echo-sdk-ams.app.sample2": "apple tv",
5+
"amzn1.echo-sdk-ams.app.sample3": "xbox"
6+
},
7+
"queue_url": "https://queue.amazonaws.com/123456789/queueName",
8+
"responses": [
9+
"<speak>Roger that.</speak>",
10+
"<speak>Yes master.</speak>",
11+
"<speak>Will do.</speak>",
12+
"<speak>Done!</speak>",
13+
"<speak>Dude.</speak>"
14+
]
15+
}

echo_remote_lambda.py

+84
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
import json
2+
import logging
3+
from random import randint
4+
import boto3
5+
6+
logger = logging.getLogger()
7+
logger.setLevel(logging.INFO)
8+
9+
with open('config.json') as config_file:
10+
config = json.load(config_file)
11+
12+
sqs = boto3.resource('sqs', region_name='us-east-1')
13+
queue = sqs.Queue(config['queue_url'])
14+
15+
16+
def lambda_handler(event, context):
17+
app_id = event['session']['application']['applicationId']
18+
logger.info('Application ID: {}'.format(app_id))
19+
if app_id not in config['skills']:
20+
raise Exception('Invalid application ID')
21+
22+
if event['request']['type'] == 'LaunchRequest':
23+
handle_launch(config['skills'][app_id])
24+
if event['request']['type'] == 'IntentRequest':
25+
if config['skills'][app_id] == 'remote':
26+
handle_remote(event['request'])
27+
else:
28+
handle_action(config['skills'][app_id], event['request'])
29+
resp = build_ssml_response(get_random_speech())
30+
logger.info('Response: {}'.format(resp))
31+
return resp
32+
33+
34+
def handle_launch(activity):
35+
msg = {
36+
'name': 'Launch',
37+
'slots': {
38+
'Activity': {
39+
'name': 'Activity',
40+
'value': activity
41+
}
42+
}
43+
}
44+
send_message(json.dumps(msg))
45+
46+
47+
def handle_remote(req):
48+
"""
49+
Handles power and volume intents from the generic remote skill
50+
"""
51+
send_message(json.dumps(req['intent']))
52+
53+
54+
def handle_action(device, req):
55+
"""
56+
Passes through intent requests from device-specific skills
57+
after setting the device name
58+
"""
59+
intent = req['intent']
60+
intent['slots']['Device'] = {'name': 'Device', 'value': device}
61+
send_message(json.dumps(intent))
62+
63+
64+
def send_message(body):
65+
queue.send_message(MessageBody=body)
66+
67+
68+
def build_ssml_response(speech):
69+
resp = {
70+
'version': '1.0',
71+
'response': {
72+
'outputSpeech': {
73+
'type': 'SSML',
74+
'ssml': speech
75+
},
76+
'shouldEndSession': True
77+
}
78+
}
79+
return resp
80+
81+
82+
def get_random_speech():
83+
num_responses = len(config['responses'])
84+
return config['responses'][randint(0, num_responses - 1)]

echoremote.py

+109
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
#!/home/pi/.virtualenvs/python34/bin/python
2+
3+
import boto3
4+
import boto3.session
5+
import json
6+
from subprocess import call
7+
from time import sleep
8+
import os.path
9+
from botocore.exceptions import ClientError
10+
from botocore.exceptions import BotoCoreError
11+
import logging
12+
LOG_FILENAME = '/home/pi/log/echoremote.log'
13+
logging.basicConfig(filename=LOG_FILENAME, level=logging.INFO)
14+
15+
16+
def log(msg):
17+
logging.info(msg)
18+
19+
sequences = {'all_off': ['tv_off.txt', 'receiver_off.txt',
20+
'xbox_off.txt', 'apple_tv_home.txt'],
21+
'apple_tv': ['tv_on.txt',
22+
'receiver_apple_tv.txt',
23+
'apple_tv_home.txt'],
24+
'xbox': ['tv_on.txt', 'receiver_xbox.txt', 'xbox_on.txt'],
25+
'raspberry_pie': ['tv_on.txt', 'receiver_raspberry_pie.txt']}
26+
keys = ('ActionA', 'ActionB', 'ActionC', 'ActionD', 'ActionE', 'ActionF')
27+
signals_path = '/home/pi/scribbles/python/echoir/signals/'
28+
29+
30+
def handle_seq(sequence):
31+
for signal in sequence:
32+
send_signal(signal)
33+
sleep(0.05)
34+
35+
36+
def send_signal(signal):
37+
signal_full = os.path.join(signals_path, signal)
38+
if os.path.isfile(signal_full):
39+
call(['/usr/bin/igclient', '--send', signal_full])
40+
log(" Sent: {}".format(signal))
41+
else:
42+
log(" NOT SENT: {}".format(signal))
43+
44+
45+
def process_msg(msg):
46+
log(msg.body)
47+
intent = json.loads(msg.body)
48+
action = intent['name']
49+
if action == 'Power':
50+
handle_power(intent['slots'])
51+
elif action == 'Volume':
52+
handle_volume(intent['slots'])
53+
elif action == 'Launch':
54+
handle_launch(intent['slots'])
55+
elif action == 'Action':
56+
handle_action(intent['slots'])
57+
msg.delete()
58+
59+
60+
def handle_power(slots):
61+
device = slots['Device']['value'].replace('the ', '').replace(' ', '_')
62+
onoff = slots['OnOff']['value']
63+
if device == 'everything':
64+
handle_seq(sequences['all_off'])
65+
else:
66+
signal = '{}_{}.txt'.format(device, onoff)
67+
send_signal(signal)
68+
69+
70+
def handle_volume(slots):
71+
signal = 'receiver_volume_{}.txt'.format(slots['UpDown']['value'])
72+
reps = int(slots['Repeat']['value'])
73+
sequence = [signal for i in range(reps)]
74+
handle_seq(sequence)
75+
76+
77+
def handle_launch(slots):
78+
action = slots['Activity']['value'].replace(' ', '_')
79+
if action in sequences:
80+
handle_seq(sequences[action])
81+
82+
83+
def handle_action(slots):
84+
device = slots['Device']['value'].replace('the ', '').replace(' ', '_')
85+
device = device.lower()
86+
sequence = []
87+
for key in keys:
88+
if 'value' in slots[key] and slots[key]['value'] is not None:
89+
sequence.append(slots[key]['value'].replace(' ', '_').lower())
90+
signals = ['{}_{}.txt'.format(device, a) for a in sequence]
91+
handle_seq(signals)
92+
93+
94+
url = 'https://queue.amazonaws.com/720549148055/echoRemote'
95+
s = boto3.session.Session(region_name='us-east-1')
96+
queue = s.resource('sqs').Queue(url)
97+
98+
99+
while True:
100+
try:
101+
log("Polling for messages...")
102+
messages = queue.receive_messages(WaitTimeSeconds=20)
103+
for message in messages:
104+
log(" Processing a message.")
105+
process_msg(message)
106+
except (ClientError, BotoCoreError) as e:
107+
log("Request failed. Sleeping for a minute.")
108+
log(str(e))
109+
sleep(60)

record.py

+36
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
import sys
2+
import re
3+
4+
received = r"^received"
5+
receiver = r"^receiver"
6+
pulse = r"^pulse:"
7+
space = r"^space:"
8+
9+
lastword = ''
10+
signals = []
11+
12+
for line in sys.stdin:
13+
words = line.strip().split(' ')
14+
15+
if re.search(received, words[0]) or re.search(receiver, words[0]):
16+
continue
17+
words[1] = int(words[1])
18+
if re.search(space, words[0]) and words[1] == 152917:
19+
continue
20+
21+
word = re.sub(pulse, 'pulse', words[0])
22+
word = re.sub(space, 'space', word)
23+
24+
if lastword == word:
25+
item = signals.pop()
26+
item[1] = item[1] + words[1]
27+
signals.append(item)
28+
elif lastword == '' and word == 'space':
29+
continue
30+
else:
31+
signals.append([word, words[1]])
32+
lastword = word
33+
34+
for i in range(len(signals)):
35+
if i < len(signals) - 1 or signals[i][0] != 'space':
36+
print("{} {}".format(*signals[i]))

record_remote

+2
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
workon python34
2+
igclient --receiver-on --sleep 5 | python /home/pi/scribbles/python/echoir/record.py

0 commit comments

Comments
 (0)