WebSocket API
Real-time bidirectional communication for instant sensor updates and relay control
Why WebSocket?
WebSocket provides persistent connection for real-time data streaming, eliminating the need for polling. Perfect for live sensor monitoring, instant relay control, and notifications.
Connection
WebSocket Endpoint
Productiontext
wss://api.gridova.com/wsDevelopmenttext
ws://localhost:8080/wsAuthentication
Send authentication token immediately after connection:
websocket_client.jsjavascript
const ws = new WebSocket('wss://api.gridova.com/ws');
ws.onopen = () => {
console.log('Connected to WebSocket');
// Send authentication
ws.send(JSON.stringify({
type: 'auth',
token: 'your_jwt_token_here'
}));
};
ws.onmessage = (event) => {
const data = JSON.parse(event.data);
console.log('Received:', data);
if (data.type === 'auth_success') {
console.log('Authenticated successfully');
}
};
ws.onerror = (error) => {
console.error('WebSocket error:', error);
};
ws.onclose = () => {
console.log('Disconnected from WebSocket');
};Message Types
ESP32 WebSocket Client
esp32_websocket_client.inocpp
#include <WiFi.h>
#include <WebSocketsClient.h>
#include <ArduinoJson.h>
#include <PZEM004Tv30.h>
const char* ssid = "YOUR_WIFI_SSID";
const char* password = "YOUR_WIFI_PASSWORD";
const char* wsHost = "api.gridova.com";
const uint16_t wsPort = 443; // or 80 for ws://
const char* wsPath = "/ws";
const char* authToken = "YOUR_JWT_TOKEN";
WebSocketsClient webSocket;
PZEM004Tv30 pzem(Serial2, 16, 17);
#define RELAY1_PIN 25
void setup() {
Serial.begin(115200);
pinMode(RELAY1_PIN, OUTPUT);
digitalWrite(RELAY1_PIN, HIGH); // OFF
// Connect to WiFi
WiFi.begin(ssid, password);
while (WiFi.status() != WL_CONNECTED) {
delay(500);
Serial.print(".");
}
Serial.println("\nWiFi connected");
// Setup WebSocket
webSocket.beginSSL(wsHost, wsPort, wsPath); // Use .begin() for ws://
webSocket.onEvent(webSocketEvent);
webSocket.setReconnectInterval(5000);
Serial.println("WebSocket initialized");
}
void loop() {
webSocket.loop();
// Send sensor data every 5 seconds
static unsigned long lastSend = 0;
if (millis() - lastSend > 5000) {
sendSensorData();
lastSend = millis();
}
}
void webSocketEvent(WStype_t type, uint8_t * payload, size_t length) {
switch(type) {
case WStype_DISCONNECTED:
Serial.println("WebSocket Disconnected");
break;
case WStype_CONNECTED:
Serial.println("WebSocket Connected");
authenticate();
break;
case WStype_TEXT:
handleMessage((char*)payload);
break;
case WStype_ERROR:
Serial.println("WebSocket Error");
break;
}
}
void authenticate() {
StaticJsonDocument<256> doc;
doc["type"] = "auth";
doc["token"] = authToken;
String json;
serializeJson(doc, json);
webSocket.sendTXT(json);
Serial.println("Sent authentication");
}
void handleMessage(char* payload) {
StaticJsonDocument<512> doc;
DeserializationError error = deserializeJson(doc, payload);
if (error) {
Serial.println("Failed to parse JSON");
return;
}
const char* type = doc["type"];
if (strcmp(type, "auth_success") == 0) {
Serial.println("✓ Authenticated");
// Subscribe to device updates
StaticJsonDocument<128> subDoc;
subDoc["type"] = "subscribe";
subDoc["device_id"] = "ESP32_001";
String json;
serializeJson(subDoc, json);
webSocket.sendTXT(json);
}
else if (strcmp(type, "relay_control") == 0) {
int relay = doc["relay"];
bool state = doc["state"];
if (relay == 1) {
digitalWrite(RELAY1_PIN, state ? LOW : HIGH);
Serial.printf("Relay 1: %s\n", state ? "ON" : "OFF");
}
}
else if (strcmp(type, "ping") == 0) {
// Respond to ping
webSocket.sendTXT("{\"type\":\"pong\"}");
}
}
void sendSensorData() {
float voltage = pzem.voltage();
float current = pzem.current();
float power = pzem.power();
float energy = pzem.energy();
if (isnan(voltage)) return;
StaticJsonDocument<512> doc;
doc["type"] = "sensor_data";
doc["device_id"] = "ESP32_001";
doc["timestamp"] = millis();
JsonObject data = doc.createNestedObject("data");
data["voltage"] = voltage;
data["current"] = current;
data["power"] = power;
data["energy"] = energy;
data["frequency"] = pzem.frequency();
data["power_factor"] = pzem.pf();
String json;
serializeJson(doc, json);
webSocket.sendTXT(json);
Serial.println("Sent sensor data");
}Library Required
Install WebSockets by Markus Sattler from Arduino Library Manager
Flutter Implementation
gridova_websocket_service.dartdart
import 'package:web_socket_channel/web_socket_channel.dart';
import 'dart:convert';
import 'dart:async';
class GridovaWebSocketService {
WebSocketChannel? _channel;
StreamController<Map<String, dynamic>> _controller =
StreamController.broadcast();
String? _token;
bool _isConnected = false;
Timer? _reconnectTimer;
Stream<Map<String, dynamic>> get stream => _controller.stream;
bool get isConnected => _isConnected;
void connect(String token) {
_token = token;
_reconnectTimer?.cancel();
try {
_channel = WebSocketChannel.connect(
Uri.parse('wss://api.gridova.com/ws'),
);
_isConnected = true;
print('✓ WebSocket connected');
// Authenticate
send({
'type': 'auth',
'token': token,
});
// Listen to messages
_channel!.stream.listen(
_onMessage,
onError: _onError,
onDone: _onDone,
);
} catch (e) {
print('Connection error: $e');
_scheduleReconnect();
}
}
void _onMessage(dynamic message) {
try {
final data = json.decode(message);
print('Received: ${data['type']}');
if (data['type'] == 'auth_success') {
print('✓ Authenticated');
}
_controller.add(data);
} catch (e) {
print('Parse error: $e');
}
}
void _onError(error) {
print('WebSocket error: $error');
_isConnected = false;
_scheduleReconnect();
}
void _onDone() {
print('WebSocket closed');
_isConnected = false;
_scheduleReconnect();
}
void _scheduleReconnect() {
_reconnectTimer = Timer(Duration(seconds: 5), () {
if (_token != null) {
print('Reconnecting...');
connect(_token!);
}
});
}
void send(Map<String, dynamic> data) {
if (_channel != null && _isConnected) {
_channel!.sink.add(json.encode(data));
}
}
void controlRelay(String deviceId, int relay, bool state) {
send({
'type': 'relay_control',
'device_id': deviceId,
'relay': relay,
'state': state,
});
}
void subscribe(String deviceId) {
send({
'type': 'subscribe',
'device_id': deviceId,
});
}
void dispose() {
_reconnectTimer?.cancel();
_channel?.sink.close();
_controller.close();
_isConnected = false;
}
}
// Usage example
void main() {
final ws = GridovaWebSocketService();
ws.stream.listen((data) {
switch (data['type']) {
case 'sensor_data':
print('Voltage: ${data['data']['voltage']} V');
break;
case 'relay_updated':
print('Relay ${data['relay']}: ${data['state']}');
break;
case 'alert':
print('Alert: ${data['message']}');
break;
}
});
ws.connect('your_jwt_token');
// Subscribe to device
Future.delayed(Duration(seconds: 2), () {
ws.subscribe('ESP32_001');
});
// Control relay
Future.delayed(Duration(seconds: 5), () {
ws.controlRelay('ESP32_001', 1, true);
});
}