This repository has been archived by the owner on Jun 28, 2024. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 24
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
commit backend and lambda code - ready for archive
- Loading branch information
1 parent
60f8bfe
commit b7ef5bb
Showing
128 changed files
with
2,688 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,272 @@ | ||
|
||
from __future__ import print_function | ||
|
||
import json | ||
from uuid import uuid4 | ||
import urllib.request | ||
import urllib.parse | ||
import jwt | ||
from AWSIoTPythonSDK.MQTTLib import AWSIoTMQTTClient | ||
import random, string | ||
import requests | ||
|
||
|
||
ALEXA_REQUEST_DISCOVER = "Alexa.Discovery" | ||
ALEXA_REQUEST_POWER = "Alexa.PowerController" | ||
ALEXA_REQUEST_CHANNEL = "Alexa.ChannelController" | ||
ALEXA_REQUEST_STEP_SPEAKER = "Alexa.StepSpeaker" | ||
ALEXA_REQUEST_PLAYBACK = "Alexa.PlaybackController" | ||
JWT_PASSWORD = "ENTER_YOUR_JWT_PASSWORD" | ||
|
||
CONTROL_TURN_ON = "TurnOn" | ||
CONTROL_TURN_OFF = "TurnOff" | ||
CONTROL_CHANGE_CHANNEL = "ChangeChannel" | ||
CONTROL_SKIP_CHANNEL = "SkipChannels" | ||
CONTROL_PLAY = "Play" | ||
CONTROL_PAUSE = "Pause" | ||
CONTROL_STOP = "Stop" | ||
CONTROL_ADJUST_VOLUME = "AdjustVolume" | ||
CONTROL_MUTE = "SetMute" | ||
|
||
|
||
def randomId(): | ||
letters = string.ascii_lowercase | ||
return ''.join(random.choice(letters) for i in range(15)) | ||
|
||
def sendMessage(topic, event, json_data): | ||
jwt_token = event['directive']['endpoint']['scope']['token'] | ||
endpointid = event['directive']['endpoint']['endpointId'] | ||
data = jwt.decode(jwt_token, JWT_PASSWORD, algorithms=['HS256']) | ||
json_data["operation"] = event['directive']['header']['name'] | ||
json_data["endpointid"] = endpointid | ||
print(data) | ||
|
||
myMQTTClient = AWSIoTMQTTClient(randomId(), useWebsocket=True) | ||
myMQTTClient.configureEndpoint("afkx1f9takwol.iot.us-east-1.amazonaws.com", 443) | ||
myMQTTClient.configureOfflinePublishQueueing(-1) # Infinite offline Publish queueing | ||
myMQTTClient.configureDrainingFrequency(2) # Draining: 2 Hz | ||
myMQTTClient.configureConnectDisconnectTimeout(4) # 10 sec | ||
myMQTTClient.configureMQTTOperationTimeout(4) | ||
myMQTTClient.configureCredentials("root.pem") | ||
myMQTTClient.configureIAMCredentials("YOUR_IAM_ACCESS_KEY", "YOUR_IAM_SECRET_KEY") | ||
myMQTTClient.connect() | ||
response = myMQTTClient.publish(topic + '/' + data['device_uuid'] , json.dumps(json_data), 0) | ||
myMQTTClient.disconnect() | ||
print(topic) | ||
|
||
def lambda_handler(event, context): | ||
|
||
if event['directive']['header']['namespace'] == ALEXA_REQUEST_DISCOVER: | ||
return discover_device(event) | ||
|
||
if event['directive']['header']['namespace'] == ALEXA_REQUEST_POWER: | ||
return power_device(event) | ||
|
||
if event['directive']['header']['namespace'] == ALEXA_REQUEST_CHANNEL: | ||
return change_channel_device(event) | ||
|
||
if event['directive']['header']['namespace'] == ALEXA_REQUEST_STEP_SPEAKER: | ||
return step_speaker_device(event) | ||
|
||
if event['directive']['header']['namespace'] == ALEXA_REQUEST_PLAYBACK: | ||
return playback_device(event) | ||
print('un supported control request') | ||
return None | ||
|
||
|
||
def discover_device(event): | ||
print('discover_device') | ||
discovered_appliances = { | ||
"endpoints": get_appliances(event) | ||
} | ||
return build_discover_response(event['directive']['header'], discovered_appliances) | ||
|
||
|
||
def get_appliances(event): | ||
tvs = [] | ||
|
||
jwt_token = event['directive']['payload']['scope']['token'] | ||
data = jwt.decode(jwt_token, JWT_PASSWORD, algorithms=['HS256']) | ||
headers = {'content-type': 'application/json', 'jwt':jwt_token} | ||
payload ={"uuid": data['device_uuid']} | ||
response = requests.post('https://alexasmarttv.dev/api/v1/get_devices', data=json.dumps(payload), headers=headers) | ||
json_data = json.loads(response.text) | ||
|
||
for tv in json_data['tvs']: | ||
tvs.append( | ||
{ | ||
"endpointId": tv['mac_address'], | ||
"manufacturerName": "Samsung", | ||
"displayCategories":[ "TV"], | ||
"friendlyName": tv['name'], | ||
"description":"Samsung Smart TV", | ||
"capabilities": [ | ||
{ | ||
"type":"AlexaInterface", | ||
"interface":ALEXA_REQUEST_STEP_SPEAKER, | ||
"version":"1.0", | ||
"properties":{ | ||
"supported":[ | ||
{ | ||
"name":CONTROL_ADJUST_VOLUME, | ||
}, | ||
{ | ||
"name":CONTROL_MUTE | ||
} | ||
] | ||
} | ||
}, | ||
{ | ||
"type":"AlexaInterface", | ||
"interface":ALEXA_REQUEST_CHANNEL, | ||
"version":"1.0", | ||
"properties":{ | ||
"supported":[ | ||
{ | ||
"name":CONTROL_CHANGE_CHANNEL | ||
}, | ||
{ | ||
"name":CONTROL_SKIP_CHANNEL | ||
} | ||
] | ||
} | ||
}, | ||
{ | ||
"type":"AlexaInterface", | ||
"interface":ALEXA_REQUEST_POWER, | ||
"version":"1.0", | ||
"properties":{ | ||
"supported":[ | ||
{ | ||
"name":CONTROL_TURN_OFF | ||
}, | ||
{ | ||
"name": CONTROL_TURN_ON | ||
} | ||
] | ||
} | ||
}, | ||
{ | ||
"type":"AlexaInterface", | ||
"interface":ALEXA_REQUEST_PLAYBACK, | ||
"version":"1.0", | ||
"properties":{ | ||
"supported":[ | ||
{ | ||
"name":CONTROL_PLAY | ||
}, | ||
{ | ||
"name": CONTROL_PAUSE | ||
}, | ||
{ | ||
"name": CONTROL_STOP | ||
} | ||
] | ||
} | ||
} | ||
], | ||
"additionalApplianceDetails": {} | ||
} | ||
) | ||
return tvs | ||
|
||
|
||
def build_discover_response(event_header, discovered_appliances): | ||
header = { | ||
"payloadVersion": event_header['payloadVersion'], | ||
"namespace": event_header['namespace'], | ||
"name": "Discover.Response", | ||
"messageId": str(uuid4()) | ||
} | ||
response = { | ||
"event": { | ||
"header": header, | ||
"payload": discovered_appliances | ||
} | ||
} | ||
|
||
return response | ||
|
||
|
||
def power_device(event): | ||
print('power_device') | ||
value = "" | ||
|
||
if event['directive']['header']['name'] == CONTROL_TURN_ON: | ||
sendMessage('power',event,{}) | ||
value = "ON" | ||
|
||
if event['directive']['header']['name'] == CONTROL_TURN_OFF: | ||
sendMessage('power',event,{}) | ||
value = "OFF" | ||
|
||
properties = [{ | ||
"namespace": ALEXA_REQUEST_POWER, | ||
"name": "powerState", | ||
"value": value, | ||
"uncertaintyInMilliseconds": 500 | ||
}] | ||
|
||
return build_control_response(event, properties) | ||
|
||
def change_channel_device(event): | ||
print('channel') | ||
value = "" | ||
|
||
if event['directive']['header']['name'] == CONTROL_CHANGE_CHANNEL: | ||
sendMessage('channel',event,{"channel_data": event['directive']['payload']}) | ||
value = event['directive']['payload']['channel'] | ||
|
||
if event['directive']['header']['name'] == CONTROL_SKIP_CHANNEL: | ||
sendMessage('channel',event,{"channelCount": event['directive']['payload']['channelCount']}) | ||
value = {"channel":{"number":"1","callSign":"unknown","affiliateCallSign":"unknown"}} #who knows what channel we are on | ||
|
||
properties = [{ | ||
"namespace": ALEXA_REQUEST_CHANNEL, | ||
"name": "channel", | ||
"value": value, | ||
"uncertaintyInMilliseconds": 500 | ||
}] | ||
|
||
return build_control_response(event, properties) | ||
|
||
def step_speaker_device(event): | ||
print('speaker') | ||
|
||
if event['directive']['header']['name'] == CONTROL_MUTE: | ||
sendMessage('speaker',event,{}) | ||
|
||
if event['directive']['header']['name'] == CONTROL_ADJUST_VOLUME: | ||
sendMessage('speaker',event,{"volumeSteps": event['directive']['payload']['volumeSteps']}) | ||
|
||
properties = [] | ||
|
||
return build_control_response(event, properties) | ||
|
||
def playback_device(event): | ||
print('playback') | ||
|
||
if event['directive']['header']['name'] == CONTROL_PLAY or event['directive']['header']['name'] == CONTROL_PAUSE or event['directive']['header']['name'] == CONTROL_STOP: | ||
sendMessage('playback',event,{}) | ||
|
||
properties = [] | ||
|
||
return build_control_response(event, properties) | ||
|
||
def build_control_response(event, properties): | ||
response = {"event" :{ | ||
"header": { | ||
"namespace":"Alexa", | ||
"messageId": str(uuid4()), | ||
"name": "Response", | ||
"payloadVersion": event['directive']['header']['payloadVersion'], | ||
"correlationToken": event['directive']['header']['correlationToken'] | ||
}, | ||
"endpoint":event["directive"]["endpoint"], | ||
"payload": {} | ||
}, | ||
"context":{"properties": properties} | ||
} | ||
|
||
|
||
return response |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
log/*.log | ||
**/*.spec.log | ||
db/*.sqlite3 | ||
db/schema.rb | ||
tmp | ||
shared/log/* | ||
tmp/**/* | ||
public/assets/* | ||
public/cache/* | ||
public/images/themes/* | ||
public/javascripts/cache/* | ||
public/stylesheets/cache/* | ||
public/themes | ||
public/files/* | ||
*.swp | ||
vendor/engines/adva_rbac/spec/db/*.sqlite3 | ||
vendor/engines/adva_rbac/spec/log/* | ||
.DS_Store | ||
*.so | ||
*.dylib | ||
*.o | ||
*.bundle | ||
Makefile | ||
*.out | ||
mkmf.log | ||
coverage.data |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,50 @@ | ||
source 'https://rubygems.org' | ||
|
||
gem 'letsencrypt_plugin' | ||
# Bundle edge Rails instead: gem 'rails', github: 'rails/rails' | ||
gem 'rails', '5.1.4' | ||
# Use sqlite3 as the database for Active Record | ||
gem 'sqlite3' | ||
# Use SCSS for stylesheets | ||
gem 'sass-rails', '~> 5.0' | ||
# Use Uglifier as compressor for JavaScript assets | ||
gem 'uglifier', '>= 1.3.0' | ||
# Use CoffeeScript for .coffee assets and views | ||
gem 'coffee-rails', '~> 4.2' | ||
# See https://github.com/rails/execjs#readme for more supported runtimes | ||
# gem 'therubyracer', platforms: :ruby | ||
gem 'puma', '~> 3.0' | ||
# Turbolinks makes following links in your web application faster. Read more: https://github.com/rails/turbolinks | ||
gem 'turbolinks' | ||
# Build JSON APIs with ease. Read more: https://github.com/rails/jbuilder | ||
gem 'jbuilder', '~> 2.5' | ||
# bundle exec rake doc:rails generates the API under doc/api. | ||
gem 'sdoc', '~> 0.4.0', group: :doc | ||
gem 'bcrypt', '~> 3.1.7' | ||
gem 'jwt' | ||
gem 'aws-sdk-iot', '~> 1' | ||
gem 'json-schema' | ||
gem 'pg' | ||
gem 'rest-client' | ||
# Use ActiveModel has_secure_password | ||
# gem 'bcrypt', '~> 3.1.7' | ||
|
||
# Use Unicorn as the app server | ||
# gem 'unicorn' | ||
|
||
# Use Capistrano for deployment | ||
# gem 'capistrano-rails', group: :development | ||
|
||
group :development, :test do | ||
# Call 'byebug' anywhere in the code to stop execution and get a debugger console | ||
gem 'byebug' | ||
end | ||
|
||
group :development do | ||
# Access an IRB console on exception pages or by using <%= console %> in views | ||
gem 'web-console', '~> 2.0' | ||
|
||
# Spring speeds up development by keeping your application running in the background. Read more: https://github.com/rails/spring | ||
gem 'spring' | ||
end | ||
|
Oops, something went wrong.