Skip to content

Commit

Permalink
Realtime - Presence
Browse files Browse the repository at this point in the history
  • Loading branch information
jhagas committed Jul 20, 2024
1 parent bac7e23 commit 35db0a6
Show file tree
Hide file tree
Showing 9 changed files with 141 additions and 28 deletions.
44 changes: 27 additions & 17 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,30 +5,27 @@ Simple library to connect ESP32/8266 to Supabase via REST API and WebSockets (fo
## Installation

This library is available at Arduino's Library Manager, as well as PlatformIO Library Manager

- [Arduino Library Manager Guide](http://arduino.cc/en/guide/libraries)

## Examples

See all examples in `examples` folder

## Available Methods

To use Realtime (Postgres Changes), please see the `examples/realtime-postgresChanges` folder. The broadcast and presence features are not implemented yet.
## Supabase PostgREST API (`#include <ESPSupabase.h>`)

### Directly Makes Connection to Database

| Method | Description |
| ------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------ |
| `login_email(String email_a, String password_a)` | **(OPTIONAL, ONLY IF USING RLS)**, Returns http response code `int` |
| `login_phone(String phone_a, String password_a)` | **(OPTIONAL, ONLY IF USING RLS)**, Returns http response code `int` |
| `begin(String url_a, String key_a);` | `url_a` is a Supabase URL and `key_a` is supabase anon key. Returns `void` |
| `login_email(String email_a, String password_a)` | Returns http response code `int` |
| `login_phone(String phone_a, String password_a)` | Returns http response code `int` |
| `insert(String table, String json, bool upsert)` | Returns http response code `int`. If you want to do upsert, set thirt parameter to `true` |
| `upload(String bucket, String filename, String mime_type, Stream *stream, uint32_t size)` | `bucket` is the name of the Supabase Storage bucket without any `/`. `filename` is the name to upload the file with, should have extension but no `/`. Takes a `Stream*` pointer as an argument, this can be Arduino SD `File*` or SPIFFS `File*` types. Returns http response code `int`. `mime_type` is for eg. `image/jpg`. `size` is the total size in bytes of the file to upload. Returns http response code `int`. |
| `upload(String bucket, String filename, String mime_type, uint8_t *buffer, uint32_t size)` | Same function as the previous one but takes a `uint8_t*` buffer instead of a `Stream*`. Can be used for files stored in RAM. |
| `.doSelect()` | Called at the end of select query chain, see [Examples](#examples). Returns http response payload (your data) from Supabase `String` |
| `.doUpdate(String json)` | Called at the end of update query chain, see [Examples](#examples). Returns http response code from Supabase `int` |
| Method | Description |
| ------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `login_email(String email_a, String password_a)` | **(OPTIONAL, ONLY IF USING RLS)**, Returns http response code `int` |
| `login_phone(String phone_a, String password_a)` | **(OPTIONAL, ONLY IF USING RLS)**, Returns http response code `int` |
| `begin(String url_a, String key_a);` | `url_a` is a Supabase URL and `key_a` is supabase anon key. Returns `void` |
| `insert(String table, String json, bool upsert)` | Returns http response code `int`. If you want to do upsert, set thirt parameter to `true` |
| `upload(String bucket, String filename, String mime_type, Stream *stream, uint32_t size)` | `bucket` is the name of the Supabase Storage bucket without any `/`. `filename` is the name to upload the file with, should have extension but no `/`. Takes a `Stream*` pointer as an argument, this can be Arduino SD `File*` or SPIFFS `File*` types. Returns http response code `int`. `mime_type` is for eg. `image/jpg`. `size` is the total size in bytes of the file to upload. Returns http response code `int`. |
| `upload(String bucket, String filename, String mime_type, uint8_t *buffer, uint32_t size)` | Same function as the previous one but takes a `uint8_t*` buffer instead of a `Stream*`. Can be used for files stored in RAM. |
| `.doSelect()` | Called at the end of select query chain, see [Examples](#examples). Returns http response payload (your data) from Supabase `String` |
| `.doUpdate(String json)` | Called at the end of update query chain, see [Examples](#examples). Returns http response code from Supabase `int` |

### Building The Queries

Expand All @@ -46,7 +43,6 @@ String read = db.from("table").select("*").eq("column", "value").order("column",
| `.select(String colls);` | Specify that you want to do select query. It will append `&select=colls` in Request URL |
| `.update(String table);` | Specify that you want to do update query. It will append `&update` in Request URL |


#### Horizontal Filtering (comparison) Operator

| Methods | Description |
Expand Down Expand Up @@ -90,11 +86,25 @@ This method calls in mandatory, must be called after one opetation (let's say `d
db.urlQuery_reset();
```

## Supabase Realtime API (`#include <ESPSupabaseRealtime.h>`)

To use Realtime (Postgres Changes), please see the `examples/realtime-postgresChanges` and `examples/realtime-presence` folder. The broadcast feature are not implemented yet.

| Method | Description |
| ------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------- |
| `login_email(String email_a, String password_a)` | **(OPTIONAL, ONLY IF USING RLS)**, Returns http response code `int` |
| `login_phone(String phone_a, String password_a)` | **(OPTIONAL, ONLY IF USING RLS)**, Returns http response code `int` |
| `begin(String hostname, String key, void (*func)(String))` | Setup the Realtime connection with Supabase URL and Anon key, also put the handle function for the incoming message |
| `sendPresence(String device_name)` | Track the presence (online status) of your ESP device (OPTIONAL) |
| `addChangesListener(String table, String event, String schema, String filter)` | Listen to Postgres Database changes, you can add multiple of this if you want to track changes form multiple tables |
| `listen()` | Start websocket connection |
| `loop()` | Put this in your loop() function, this will handle the websocket connection and send heartbeats to Supabase |

## To-do (sorted by priority)

- [x] Implement Postgres Changes in [Supabase Realtime](https://supabase.com/docs/guides/realtime)
- [x] Implement Presence in [Supabase Realtime](https://supabase.com/docs/guides/realtime)
- [ ] Implement Broadcast in [Supabase Realtime](https://supabase.com/docs/guides/realtime)
- [ ] Implement Presence in [Supabase Realtime](https://supabase.com/docs/guides/realtime)

## Project Using This Library

Expand Down
1 change: 1 addition & 0 deletions examples/insert/insert.ino
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ void setup()

// Uncomment this line below, if you activate RLS in your Supabase Table
// db.login_email("email", "password");

// You can also use
// db.login_phone("phone", "password");

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,10 +46,13 @@ void setup()
}
Serial.println("\nConnected!");

realtime.begin(supabase_url, anon_key, HandleChanges);
realtime.begin(supabase_url, anon_key, HandleChanges);

// Uncomment this line below, if you activate RLS in your Supabase Table
// realtime.login_email("email", "password");

// You can also use
// db.login_phone("phone", "password");

// Parameter 1 : Table name
// Parameter 2 : Event type ("*" | "INSERT" | "UPDATE" | "DELETE")
Expand Down
57 changes: 57 additions & 0 deletions examples/realtime-presence/realtime-presence.ino
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
#include <Arduino.h>
#include <ArduinoJson.h>
#include <ESPSupabaseRealtime.h>

#if defined(ESP8266)
#include <ESP8266WiFi.h>
#else
#include <WiFi.h>
#endif

// Put your supabase URL and Anon key here...
String supabase_url = "https://yourproject.supabase.co";
String anon_key = "anonkey";

// put your WiFi credentials (SSID and Password) here
const char *ssid = "ssid";
const char *psswd = "pass";

SupabaseRealtime realtime;

void HandleChanges(String result)
{
return;
}

void setup()
{
Serial.begin(9600);

WiFi.begin(ssid, psswd);
while (WiFi.status() != WL_CONNECTED)
{
delay(100);
Serial.print(".");
}
Serial.println("\nConnected!");

realtime.begin(supabase_url, anon_key, HandleChanges);

// Uncomment this line below, if you activate Presence Authorization
// https://supabase.com/docs/guides/realtime/authorization#presence

// realtime.login_email("email", "password");

// You can also use

// db.login_phone("phone", "password");

// Parameter 1 : Your ESP Device Name, to track your device in the Supabase Presence
realtime.sendPresence("device name");
realtime.listen();
}

void loop()
{
realtime.loop();
}
1 change: 1 addition & 0 deletions examples/select/select.ino
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ void setup()

// Uncomment this line below, if you activate RLS in your Supabase Table
// db.login_email("email", "password");

// You can also use
// db.login_phone("phone", "password");

Expand Down
1 change: 1 addition & 0 deletions examples/update/update.ino
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ void setup()

// Uncomment this line below, if you activate RLS in your Supabase Table
// db.login_email("email", "password");

// You can also use
// db.login_phone("phone", "password");

Expand Down
2 changes: 2 additions & 0 deletions examples/upload/upload.ino
Original file line number Diff line number Diff line change
Expand Up @@ -80,8 +80,10 @@ void setup()

// Uncomment this line below, if you activate RLS in your Supabase Table
// int loginResponse = db.login_email(email, password);

// You can also use
// int loginResponse = db.login_phone("phone", "password");

// if (loginResponse != 200)
// {
// Serial.printf("Login failed with code: %d.\n\r", loginResponse);
Expand Down
19 changes: 14 additions & 5 deletions src/ESPSupabaseRealtime.h
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ class SupabaseRealtime
String key;
String hostname;

// RLS Stuff
String phone_or_email;
String password;
String data;
Expand All @@ -33,17 +34,23 @@ class SupabaseRealtime
String configAUTH;

// Initial config
const char *config = "{\"event\":\"phx_join\",\"topic\":\"realtime:ESP\",\"payload\":{\"config\":{\"postgres_changes\":[]}},\"ref\":\"sentRef\"}";
const char *config = R"({"event":"phx_join","topic":"realtime:ESP","payload":{"config":{}},"ref":"ESP"})";
// Postgres Changes
bool isPostgresChanges = false;
JsonDocument postgresChanges;
// Presence
const char *jsonPresence = R"({"topic":"realtime:ESP","event":"presence","payload":{"type":"presence","event":"track","payload":{"user":"","online_at":""}},"ref":"ESP"})";
bool isPresence = false;
String presenceConfig;
// bool isBroadcast = false; // Not implemented yet
// JsonDocument broadcastConfig; // Not implemented yet
JsonDocument jsonRealtimeConfig;
String configJSON;

// Heartbeat
unsigned int last_ms = millis();
const char *jsonRealtimeHeartbeat = R"({"event": "heartbeat","topic": "phoenix","payload": {},"ref": "sentRef"})";
const char *tokenConfig = R"({"topic": "realtime:ESP","event": "access_token","payload": {
"access_token": ""
},"ref": "sendRef"})";
const char *jsonRealtimeHeartbeat = R"({"event":"heartbeat","topic":"phoenix","payload":{},"ref":"ESP"})";
const char *tokenConfig = R"({"topic":"realtime:ESP","event":"access_token","payload":{"access_token":""},"ref":"ESP"})";

void processMessage(uint8_t *payload);
void webSocketEvent(WStype_t type, uint8_t *payload, size_t length);
Expand All @@ -53,6 +60,8 @@ class SupabaseRealtime
public:
SupabaseRealtime() {}
void begin(String hostname, String key, void (*func)(String));
void sendPresence(String device_name);
// void broadcast(); // Not implemented yet
void addChangesListener(String table, String event, String schema, String filter);
void listen();
void loop();
Expand Down
39 changes: 34 additions & 5 deletions src/Realtime.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -75,8 +75,9 @@ int SupabaseRealtime::_login_process()

void SupabaseRealtime::addChangesListener(String table, String event, String schema, String filter)
{
isPostgresChanges = true;
JsonDocument tableObj;

tableObj["event"] = event;
tableObj["schema"] = schema;
tableObj["table"] = table;
Expand All @@ -89,10 +90,32 @@ void SupabaseRealtime::addChangesListener(String table, String event, String sch
postgresChanges.add(tableObj);
}

void SupabaseRealtime::sendPresence(String device_name)
{
JsonDocument presence;
isPresence = true;

deserializeJson(presence, jsonPresence);
presence["payload"]["payload"]["user"] = device_name;
serializeJson(presence, presenceConfig);
}

void SupabaseRealtime::listen()
{
deserializeJson(jsonRealtimeConfig, config);
jsonRealtimeConfig["payload"]["config"]["postgres_changes"] = postgresChanges;
if (isPostgresChanges)
{
jsonRealtimeConfig["payload"]["config"]["postgres_changes"] = postgresChanges;
}
if (isPresence)
{
jsonRealtimeConfig["payload"]["config"]["presence"]["key"] = "";
}
// if (isBroadcast)
// {
// // not implemented yet
// // jsonRealtimeConfig["payload"]["config"]["broadcast"] = broadcastConfig;
// }
serializeJson(jsonRealtimeConfig, configJSON);

String slug = "/realtime/v1/websocket?apikey=" + String(key) + "&vsn=1.0.0";
Expand Down Expand Up @@ -133,7 +156,10 @@ void SupabaseRealtime::webSocketEvent(WStype_t type, uint8_t *payload, size_t le
case WStype_CONNECTED:
Serial.println("[WSc] Connected!");
webSocket.sendTXT(configJSON);
webSocket.sendTXT(configAUTH);
if (useAuth)
webSocket.sendTXT(configAUTH);
if (isPresence)
webSocket.sendTXT(presenceConfig);
break;
case WStype_TEXT:
processMessage(payload);
Expand All @@ -156,7 +182,8 @@ void SupabaseRealtime::webSocketEvent(WStype_t type, uint8_t *payload, size_t le

void SupabaseRealtime::loop()
{
if (useAuth && millis() - loginTime > authTimeout / 2)
// Request AUTH token every 50 minutes (on defautlt timeout / 60 min)
if (useAuth && millis() - loginTime > authTimeout / 1.2)
{
webSocket.disconnect();
_login_process();
Expand All @@ -166,11 +193,13 @@ void SupabaseRealtime::loop()
webSocket.loop();
}

// send heartbeat every 30 seconds
if (millis() - last_ms > 30000)
{
last_ms = millis();
webSocket.sendTXT(jsonRealtimeHeartbeat);
webSocket.sendTXT(configAUTH);
if (useAuth)
webSocket.sendTXT(configAUTH);
}
}

Expand Down

0 comments on commit 35db0a6

Please sign in to comment.