Skip to content
This repository has been archived by the owner on Jun 28, 2024. It is now read-only.

Commit

Permalink
commit backend and lambda code - ready for archive
Browse files Browse the repository at this point in the history
  • Loading branch information
eclair4151 committed Jun 28, 2024
1 parent 60f8bfe commit b7ef5bb
Show file tree
Hide file tree
Showing 128 changed files with 2,688 additions and 0 deletions.
Binary file added .DS_Store
Binary file not shown.
272 changes: 272 additions & 0 deletions AWSLambda/control_tv_lambda_call.py
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
26 changes: 26 additions & 0 deletions AlexaSmartTVBackend_RubyOnRails/.gitignore
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
50 changes: 50 additions & 0 deletions AlexaSmartTVBackend_RubyOnRails/Gemfile
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

Loading

0 comments on commit b7ef5bb

Please sign in to comment.