From 7e7c2c0c4a67fa7275ca3833b5bef907286fa57b Mon Sep 17 00:00:00 2001 From: akdeb Date: Fri, 25 Apr 2025 15:48:14 +0100 Subject: [PATCH] added quickstart details --- README.md | 53 +++++++----- firmware-arduino/README.md | 7 ++ .../app/api/generate_auth_token/route.ts | 81 ++++++++++++++----- .../app/components/Settings/AppSettings.tsx | 11 ++- server-deno/.env.example | 2 +- server-deno/main.ts | 9 ++- 6 files changed, 110 insertions(+), 53 deletions(-) diff --git a/README.md b/README.md index a29f369..af284e7 100644 --- a/README.md +++ b/README.md @@ -44,57 +44,66 @@ Control your ESP32 AI device from your phone with the Elato AI webapp. |:---:|:---:|:---:| | *Select from a list of AI characters* | *Talk to your AI with real-time responses* | *Create personalized AI characters* | -## Getting Started +## 🚀 Quick Start -1. Install [Supabase CLI](https://supabase.com/docs/guides/local-development/cli/getting-started) and set up your Local Supabase Backend. From the root directory, run: +1. Ensure `DEV_MODE` is set to `True` in your frontend, and server environment variables and enabled in the `Config.h` file of your firmware. This will allow you to skip the device registration process and speed your way to testing the Realtime API AI chat. + +2. Install [Supabase CLI](https://supabase.com/docs/guides/local-development/cli/getting-started) and set up your Local Supabase Backend. From the root directory, run: ```bash brew install supabase/tap/supabase supabase start # Starts your local Supabase server with the default migrations and seed data. ``` -2. Set up your NextJS Frontend. ([See the Frontend README](frontend-nextjs/README.md)) From the `frontend-nextjs` directory, run the following commands. (**Login creds:** Email: admin@elatoai.com, Password: admin) +3. Set up your NextJS Frontend. ([See the Frontend README](frontend-nextjs/README.md)) From the `frontend-nextjs` directory, run the following commands. (**Login creds:** Email: admin@elatoai.com, Password: admin) ```bash cd frontend-nextjs npm install - -# Set your environment variables cp .env.example .env.local -# NEXT_PUBLIC_SUPABASE_ANON_KEY= -# OPENAI_API_KEY= + +# In .env.local, set your environment variables +# NEXT_PUBLIC_SUPABASE_ANON_KEY= +# OPENAI_API_KEY= # Run the development server npm run dev ``` -3. Add your ESP32-S3 Device MAC Address to the [Settings page](http://localhost:3000/home/settings) in the NextJS Frontend. This links your device to your account. -To find your ESP32-S3 Device's MAC Address, build and upload `test/print_mac_address_test.cpp` using PlatformIO. - 4. Start the Deno server. ([See the Deno server README](server-deno/README.md)) ```bash # Navigate to the server directory cd server-deno - -# Set your environment variables cp .env.example .env -# NEXT_PUBLIC_SUPABASE_ANON_KEY= -# OPENAI_API_KEY= + +# In .env, set your environment variables +# SUPABASE_KEY= +# OPENAI_API_KEY= # Run the server at port 8000 deno run -A --env-file=.env main.ts ``` -5. Add your OpenAI API Key in the `server-deno/.env` and `frontend-nextjs/.env.local` file. -``` -OPENAI_API_KEY= -``` +5. In `Config.cpp` set `ws_server` and `backend_server` to your local IP address. Run `ifconfig` in your console and find `en0` -> `inet` -> `192.168.1.100` (it may be different for your Wifi network). This tells the ESP32 device to connect to your NextJS frontend and Deno server running on your local machine. All services should be on the same Wifi network. -6. Set up your ESP32 Arduino Client. ([See the ESP32 README](firmware-arduino/README.md)) On PlatformIO, first `Build` the project, then `Upload` the project to your ESP32. +6. Build and upload the firmware to your ESP32 device. The ESP32 should open an `ELATO-DEVICE` captive portal to connect to Wifi. Connect to it and go to `http://192.168.4.1` to configure the device wifi. -7. The ESP32 should open an AP `ELATO-DEVICE` to connect to Wifi. Connect to it and go to `http://192.168.4.1` to configure the device wifi. +7. Once your Wifi credentials are configured, turn the device off and on again and it should connect to your Wifi and your server. -8. Once your Wifi is configured, turn the device off and on again and it should connect to your Wifi and the Deno edge server. +8. Now you can talk to your AI Character! -9. Now you can talk to your AI Character! +## 📦 Getting Started with multiple devices + +1. Register your device by adding your ESP32 Device's MAC Address and a unique user code to the `devices` table in Supabase. +> **Pro Tip:** To find your ESP32-S3 Device's MAC Address, build and upload `test/print_mac_address_test.cpp` using PlatformIO. + + +2. Register your user account to this device by adding your unique user code to the [Settings page](http://localhost:3000/home/settings) in the NextJS Frontend. This links your device to your account. + + +3. Set DEV_MODE to `False` in your frontend and server environment variables. +> **Pro Tip:** If you're testing locally, you can enable the `DEV_MODE` macro in the `Config.h` file of your firmware to use your local IP addresses. + + +4. Now you can register multiple devices to your account by repeating the process above. ## Project Architecture diff --git a/firmware-arduino/README.md b/firmware-arduino/README.md index 70b153f..977298c 100644 --- a/firmware-arduino/README.md +++ b/firmware-arduino/README.md @@ -57,6 +57,13 @@ This firmware turns your ESP32 device into a WebSocket audio client for Elato, e - In the firmware project, set `DEV_MODE` in Config.cpp - Update the WebSocket server IP to your local IP address +## NVS Storage + +We store the following data in Non-Volatile Storage (NVS) on the ESP32: +1. **Auth token**: The supabase auth token that is used to authenticate the device with the backend server. +2. **Factory reset**: Whether the device has been factory reset. +3. **Wifi credentials**: The wifi credentials of the device. + ## First-Time Setup 1. Power on your ESP32 diff --git a/frontend-nextjs/app/api/generate_auth_token/route.ts b/frontend-nextjs/app/api/generate_auth_token/route.ts index 71f1fe3..5f51ed0 100644 --- a/frontend-nextjs/app/api/generate_auth_token/route.ts +++ b/frontend-nextjs/app/api/generate_auth_token/route.ts @@ -1,8 +1,9 @@ -import { NextResponse } from 'next/server'; +import { NextResponse } from "next/server"; import jwt from "jsonwebtoken"; -import { createClient } from '@/utils/supabase/server'; +import { createClient } from "@/utils/supabase/server"; const ALGORITHM = "HS256"; +const isDevMode = process.env.DEV_MODE === "True"; interface TokenPayload { [key: string]: any; @@ -12,71 +13,107 @@ const createSupabaseToken = ( jwtSecretKey: string, data: TokenPayload, // Set expiration to null for no expiration, or use a very large number like 10 years - expireDays: number | null = 3650 // Default to 10 years + expireDays: number | null = 3650, // Default to 10 years ): string => { const toEncode = { - aud: 'authenticated', - role: 'authenticated', + aud: "authenticated", + role: "authenticated", sub: data.user_id, email: data.email, // Only include exp if expireDays is not null ...(expireDays && { - exp: Math.floor(Date.now() / 1000) + (expireDays * 86400) + exp: Math.floor(Date.now() / 1000) + (expireDays * 86400), }), user_metadata: { - ...data - } + ...data, + }, }; const encodedJwt = jwt.sign(toEncode, jwtSecretKey, { - algorithm: ALGORITHM + algorithm: ALGORITHM, }); return encodedJwt; }; const getUserByMacAddress = async (macAddress: string) => { const supabase = createClient(); - const { data, error } = await supabase.from('devices').select('*, user:user_id(*)').eq('mac_address', macAddress).single(); + const { data, error } = await supabase.from("devices").select( + "*, user:user_id(*)", + ).eq("mac_address", macAddress).single(); if (error) { throw new Error(error.message); } return data.user; }; +const getDevUser = async () => { + const supabase = createClient(); + const { data, error } = await supabase.from("users").select("*").eq( + "email", + "admin@elatoai.com", + ).single(); + if (error) { + throw new Error(error.message); + } + return data; +}; + export async function GET(req: Request) { try { const { searchParams } = new URL(req.url); - const macAddress = searchParams.get('macAddress'); - + const macAddress = searchParams.get("macAddress"); + if (!macAddress) { return NextResponse.json( - { error: 'MAC address is required' }, - { status: 400 } + { error: "MAC address is required" }, + { status: 400 }, ); } - const user = await getUserByMacAddress(macAddress); + /** + * If `DEV_MODE` is true, we use the dev user. + * Otherwise, we use the user by mac address. + * + * Steps to register your device: + * 1: Register the device `mac_address` and `user_code` in the `devices` tables. + * 2: Make sure the user adds the `user_code` to their account in Settings to link the device to their `user_id`. + * 3: When `DEV_MODE` is false, we then fetch the user by `mac_address`. + */ + let user; + if (isDevMode) { + user = await getDevUser(); + } else { + user = await getUserByMacAddress(macAddress); + } if (!user) { return NextResponse.json( - { error: 'User not found' }, - { status: 400 } + { error: "User not found" }, + { status: 400 }, ); } const payload = { email: user.email, user_id: user.user_id, - created_time: new Date() + created_time: new Date(), }; - const token = createSupabaseToken(process.env.JWT_SECRET_KEY!, payload, null); + const token = createSupabaseToken( + process.env.JWT_SECRET_KEY!, + payload, + null, + ); return NextResponse.json({ token }); } catch (error) { return NextResponse.json( - { error: error instanceof Error ? error.message : 'Internal server error' }, - { status: 500 } + { + error: error instanceof Error + ? error.message + : "Internal server error", + }, + { status: 500 }, ); } -} \ No newline at end of file +} diff --git a/frontend-nextjs/app/components/Settings/AppSettings.tsx b/frontend-nextjs/app/components/Settings/AppSettings.tsx index 4e00e12..90ac28a 100644 --- a/frontend-nextjs/app/components/Settings/AppSettings.tsx +++ b/frontend-nextjs/app/components/Settings/AppSettings.tsx @@ -88,6 +88,8 @@ const AppSettings: React.FC = ({ }); } + const isDevMode = process.env.DEV_MODE === "True"; + return ( <> = ({

Device settings

+ {isDevMode &&
You don't need to register your device because you're in dev mode.
}
- {process.env.DEV_MODE != "True" &&
+