File size: 6,411 Bytes
3aa60e0
 
 
 
 
4d0931b
e3273bb
 
 
3aa60e0
4d0931b
ff4e8e9
e9b0471
3aa60e0
 
 
5894fd7
3aa60e0
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
5894fd7
 
3aa60e0
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
4d0931b
 
 
 
 
9d3b2ec
4d0931b
 
3aa60e0
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
5894fd7
 
e3273bb
5894fd7
e3273bb
5894fd7
3aa60e0
 
 
 
 
 
 
 
 
 
 
 
5894fd7
 
 
 
 
43e56c9
3aa60e0
43e56c9
3aa60e0
43e56c9
 
3aa60e0
43e56c9
e9b0471
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
from fastapi import FastAPI, Request, HTTPException
import requests
import json
import uuid
from datetime import datetime, timezone
import os  # Import the 'os' module

app = FastAPI()

# --- CONFIGURATION ---
# Use Hugging Face Secrets for sensitive data
CLIENT_ID = "amzn1.application-oa2-client.bc4475f5b85148fa8c1329e4bb6da5e9"  # Your *NEW* ALEXA Client ID (from Custom Skill)
CLIENT_SECRET = os.environ.get("CLIENT_SECRET")  # Get *ALEXA* Client Secret from environment variable
ALEXA_EVENT_GATEWAY = "https://api.amazonalexa.com/v3/events"

# --- DEVICE MAPPING ---
TRIGGERS = {
    "Trigger1": {"endpointId": "device-001", "description": "Webhook Trigger 1", "state": "NOT_DETECTED"},
    "Trigger2": {"endpointId": "device-002", "description": "Webhook Trigger 2", "state": "NOT_DETECTED"},
    "Trigger3": {"endpointId": "device-003", "description": "Webhook Trigger 3", "state": "NOT_DETECTED"},
    "Trigger4": {"endpointId": "device-004", "description": "Webhook Trigger 4", "state": "NOT_DETECTED"},
    "Trigger5": {"endpointId": "device-005", "description": "Webhook Trigger 5", "state": "NOT_DETECTED"},
    "Trigger6": {"endpointId": "device-006", "description": "Webhook Trigger 6", "state": "NOT_DETECTED"},
    "Trigger7": {"endpointId": "device-007", "description": "Webhook Trigger 7", "state": "NOT_DETECTED"},
    "Trigger8": {"endpointId": "device-008", "description": "Webhook Trigger 8", "state": "NOT_DETECTED"},
    "Trigger9": {"endpointId": "device-009", "description": "Webhook Trigger 9", "state": "NOT_DETECTED"},
    "Trigger10": {"endpointId": "device-010", "description": "Webhook Trigger 10", "state": "NOT_DETECTED"},
}

# --- TOKEN MANAGEMENT (Simple - In-Memory for Now) ---
TOKEN_DATA = {
    "access_token": None,
    "expires_at": None,  # Timestamp when the token expires
}

# --- HELPER FUNCTION: Get LWA Access Token ---
async def get_lwa_access_token():
    """Fetches or refreshes the LWA access token."""
    global TOKEN_DATA

    # Check if we have a token and if it's still valid
    if TOKEN_DATA["access_token"] and TOKEN_DATA["expires_at"] and datetime.now(timezone.utc).timestamp() < TOKEN_DATA["expires_at"]:
        return TOKEN_DATA["access_token"]

    # Get a new token
    token_url = "https://api.amazon.com/auth/o2/token"
    data = {
        "grant_type": "client_credentials",
        "client_id": CLIENT_ID,
        "client_secret": CLIENT_SECRET,
        "scope": "alexa::proactive_events",
    }

    # --- DEBUGGING LOGS ---
    print("----- LWA Token Request Data -----")
    print(data)
    print("---------------------------------")
    print(f"----- CLIENT_SECRET Value: {CLIENT_SECRET} -----")
    # --- END OF DEBUGGING LOGS ---

    try:
        response = requests.post(token_url, data=data)
        response.raise_for_status()  # Raise HTTPError for bad responses (4xx or 5xx)
        token_response = response.json()
        TOKEN_DATA["access_token"] = token_response["access_token"]
        # Calculate expiry time (subtract a small buffer to be safe)
        TOKEN_DATA["expires_at"] = datetime.now(timezone.utc).timestamp() + token_response["expires_in"] - 60
        return TOKEN_DATA["access_token"]
    except requests.exceptions.RequestException as e:
        print(f"Error getting LWA token: {e}")
        raise HTTPException(status_code=500, detail="Failed to get LWA access token")


# --- HELPER FUNCTION: Send ChangeReport to Alexa ---
async def send_change_report(endpoint_id, property_name, property_value):
    """Sends a ChangeReport event to Alexa."""
    try:
        access_token = await get_lwa_access_token()
    except HTTPException: # Re-raise exception from get_lwa_access_token
        raise

    headers = {
        "Authorization": f"Bearer {access_token}",
        "Content-Type": "application/json",
    }

    change_report = {
        "event": {
            "header": {
                "namespace": "Alexa",
                "name": "ChangeReport",
                "messageId": str(uuid.uuid4()),
                "payloadVersion": "3",
            },
            "endpoint": {
                "endpointId": endpoint_id,
            },
            "payload": {
                "change": {
                    "cause": {"type": "PHYSICAL_INTERACTION"},
                    "properties": [
                        {
                            "namespace": "Alexa.ContactSensor",
                            "name": property_name,
                            "value": property_value,
                            "timeOfSample": datetime.now(timezone.utc).isoformat(),
                            "uncertaintyInMilliseconds": 0,
                        }
                    ],
                }
            },
        }
    }

    try:
        response = requests.post(
            ALEXA_EVENT_GATEWAY, headers=headers, data=json.dumps(change_report)
        )
        response.raise_for_status()
        print(f"ChangeReport sent for {endpoint_id}. Response: {response.status_code} - {response.text}") # Log full response
    except requests.exceptions.RequestException as e:
        print(f"Error sending ChangeReport: {e}")
        raise HTTPException(status_code=500, detail=f"Failed to send ChangeReport: {e}")


# --- WEBHOOK ENDPOINT ---
@app.post("/{trigger_name}")
async def alexa_webhook(trigger_name: str, request: Request):
    data = await request.json()
    print(f"Received request for {trigger_name}: {data}")

    if trigger_name in TRIGGERS:
        endpoint_id = TRIGGERS[trigger_name]["endpointId"]

        # Toggle the state
        current_state = TRIGGERS[trigger_name]["state"]
        new_state = "NOT_DETECTED" if current_state == "DETECTED" else "DETECTED"
        TRIGGERS[trigger_name]["state"] = new_state

        try:
            await send_change_report(endpoint_id, "detectionState", new_state)
        except HTTPException as e:
            return e  # Return the HTTPException directly

        return {
            "event": {
                "header": {
                    "namespace": "Alexa",
                    "name": "Response",
                    "payloadVersion": "3",
                    "messageId": str(uuid.uuid4())
                },
                "payload": {"message": f"{trigger_name} activated! New state: {new_state}"}
            }
        }

    else:
        raise HTTPException(status_code=404, detail=f"Unknown trigger: {trigger_name}")