9 Commits

20 changed files with 395 additions and 89 deletions

View File

@@ -2,6 +2,7 @@ use crate::app_config::AppConfig;
use crate::crypto::pki;
use crate::devices::device::{DeviceId, DeviceInfo};
use crate::energy::energy_actor;
use crate::energy::energy_actor::RelaySyncStatus;
use crate::server::custom_error::HttpResult;
use crate::server::WebEnergyActor;
use actix_web::{web, HttpResponse};
@@ -138,6 +139,11 @@ struct Claims {
info: DeviceInfo,
}
#[derive(serde::Serialize)]
struct SyncResult {
relays: Vec<RelaySyncStatus>,
}
/// Synchronize device
pub async fn sync_device(body: web::Json<SyncRequest>, actor: WebEnergyActor) -> HttpResult {
// First, we need to extract device kid from query
@@ -199,9 +205,9 @@ pub async fn sync_device(body: web::Json<SyncRequest>, actor: WebEnergyActor) ->
}
};
let res = actor
let relays = actor
.send(energy_actor::SynchronizeDevice(device.id, c.claims.info))
.await??;
Ok(HttpResponse::Ok().json(res))
Ok(HttpResponse::Ok().json(SyncResult { relays }))
}

View File

@@ -1,3 +1,3 @@
pub mod files_utils;
pub mod math_utils;
pub mod math_utils;
pub mod time_utils;

View File

@@ -2,7 +2,7 @@
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/sunny.svg" />
<link rel="icon" type="image/svg+xml" href="/assets/sunny.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>SolarEnergy</title>
</head>

View File

@@ -24,16 +24,16 @@
"react-router-dom": "^6.26.2"
},
"devDependencies": {
"@types/react": "^18.3.8",
"@types/react": "^18.3.10",
"@types/react-dom": "^18.3.0",
"@typescript-eslint/eslint-plugin": "^8.6.0",
"@typescript-eslint/parser": "^8.6.0",
"@typescript-eslint/eslint-plugin": "^8.7.0",
"@typescript-eslint/parser": "^8.7.0",
"@vitejs/plugin-react": "^4.3.1",
"eslint": "^8.57.1",
"eslint-plugin-react-hooks": "^4.6.2",
"eslint-plugin-react-refresh": "^0.4.12",
"typescript": "^5.6.2",
"vite": "^5.4.7"
"vite": "^5.4.8"
}
},
"node_modules/@ampproject/remapping": {
@@ -2073,9 +2073,9 @@
"integrity": "sha512-5zvhXYtRNRluoE/jAp4GVsSduVUzNWKkOZrCDBWYtE7biZywwdC2AcEzg+cSMLFRfVgeAFqpfNabiPjxFddV1Q=="
},
"node_modules/@types/react": {
"version": "18.3.8",
"resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.8.tgz",
"integrity": "sha512-syBUrW3/XpnW4WJ41Pft+I+aPoDVbrBVQGEnbD7NijDGlVC+8gV/XKRY+7vMDlfPpbwYt0l1vd/Sj8bJGMbs9Q==",
"version": "18.3.10",
"resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.10.tgz",
"integrity": "sha512-02sAAlBnP39JgXwkAq3PeU9DVaaGpZyF3MGcC0MKgQVkZor5IiiDAipVaxQHtDJAmO4GIy/rVBy/LzVj76Cyqg==",
"license": "MIT",
"dependencies": {
"@types/prop-types": "*",
@@ -2101,17 +2101,17 @@
}
},
"node_modules/@typescript-eslint/eslint-plugin": {
"version": "8.6.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.6.0.tgz",
"integrity": "sha512-UOaz/wFowmoh2G6Mr9gw60B1mm0MzUtm6Ic8G2yM1Le6gyj5Loi/N+O5mocugRGY+8OeeKmkMmbxNqUCq3B4Sg==",
"version": "8.7.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.7.0.tgz",
"integrity": "sha512-RIHOoznhA3CCfSTFiB6kBGLQtB/sox+pJ6jeFu6FxJvqL8qRxq/FfGO/UhsGgQM9oGdXkV4xUgli+dt26biB6A==",
"dev": true,
"license": "MIT",
"dependencies": {
"@eslint-community/regexpp": "^4.10.0",
"@typescript-eslint/scope-manager": "8.6.0",
"@typescript-eslint/type-utils": "8.6.0",
"@typescript-eslint/utils": "8.6.0",
"@typescript-eslint/visitor-keys": "8.6.0",
"@typescript-eslint/scope-manager": "8.7.0",
"@typescript-eslint/type-utils": "8.7.0",
"@typescript-eslint/utils": "8.7.0",
"@typescript-eslint/visitor-keys": "8.7.0",
"graphemer": "^1.4.0",
"ignore": "^5.3.1",
"natural-compare": "^1.4.0",
@@ -2135,16 +2135,16 @@
}
},
"node_modules/@typescript-eslint/parser": {
"version": "8.6.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.6.0.tgz",
"integrity": "sha512-eQcbCuA2Vmw45iGfcyG4y6rS7BhWfz9MQuk409WD47qMM+bKCGQWXxvoOs1DUp+T7UBMTtRTVT+kXr7Sh4O9Ow==",
"version": "8.7.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.7.0.tgz",
"integrity": "sha512-lN0btVpj2unxHlNYLI//BQ7nzbMJYBVQX5+pbNXvGYazdlgYonMn4AhhHifQ+J4fGRYA/m1DjaQjx+fDetqBOQ==",
"dev": true,
"license": "BSD-2-Clause",
"dependencies": {
"@typescript-eslint/scope-manager": "8.6.0",
"@typescript-eslint/types": "8.6.0",
"@typescript-eslint/typescript-estree": "8.6.0",
"@typescript-eslint/visitor-keys": "8.6.0",
"@typescript-eslint/scope-manager": "8.7.0",
"@typescript-eslint/types": "8.7.0",
"@typescript-eslint/typescript-estree": "8.7.0",
"@typescript-eslint/visitor-keys": "8.7.0",
"debug": "^4.3.4"
},
"engines": {
@@ -2164,14 +2164,14 @@
}
},
"node_modules/@typescript-eslint/scope-manager": {
"version": "8.6.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.6.0.tgz",
"integrity": "sha512-ZuoutoS5y9UOxKvpc/GkvF4cuEmpokda4wRg64JEia27wX+PysIE9q+lzDtlHHgblwUWwo5/Qn+/WyTUvDwBHw==",
"version": "8.7.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.7.0.tgz",
"integrity": "sha512-87rC0k3ZlDOuz82zzXRtQ7Akv3GKhHs0ti4YcbAJtaomllXoSO8hi7Ix3ccEvCd824dy9aIX+j3d2UMAfCtVpg==",
"dev": true,
"license": "MIT",
"dependencies": {
"@typescript-eslint/types": "8.6.0",
"@typescript-eslint/visitor-keys": "8.6.0"
"@typescript-eslint/types": "8.7.0",
"@typescript-eslint/visitor-keys": "8.7.0"
},
"engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
@@ -2182,14 +2182,14 @@
}
},
"node_modules/@typescript-eslint/type-utils": {
"version": "8.6.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.6.0.tgz",
"integrity": "sha512-dtePl4gsuenXVwC7dVNlb4mGDcKjDT/Ropsk4za/ouMBPplCLyznIaR+W65mvCvsyS97dymoBRrioEXI7k0XIg==",
"version": "8.7.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.7.0.tgz",
"integrity": "sha512-tl0N0Mj3hMSkEYhLkjREp54OSb/FI6qyCzfiiclvJvOqre6hsZTGSnHtmFLDU8TIM62G7ygEa1bI08lcuRwEnQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"@typescript-eslint/typescript-estree": "8.6.0",
"@typescript-eslint/utils": "8.6.0",
"@typescript-eslint/typescript-estree": "8.7.0",
"@typescript-eslint/utils": "8.7.0",
"debug": "^4.3.4",
"ts-api-utils": "^1.3.0"
},
@@ -2207,9 +2207,9 @@
}
},
"node_modules/@typescript-eslint/types": {
"version": "8.6.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.6.0.tgz",
"integrity": "sha512-rojqFZGd4MQxw33SrOy09qIDS8WEldM8JWtKQLAjf/X5mGSeEFh5ixQlxssMNyPslVIk9yzWqXCsV2eFhYrYUw==",
"version": "8.7.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.7.0.tgz",
"integrity": "sha512-LLt4BLHFwSfASHSF2K29SZ+ZCsbQOM+LuarPjRUuHm+Qd09hSe3GCeaQbcCr+Mik+0QFRmep/FyZBO6fJ64U3w==",
"dev": true,
"license": "MIT",
"engines": {
@@ -2221,14 +2221,14 @@
}
},
"node_modules/@typescript-eslint/typescript-estree": {
"version": "8.6.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.6.0.tgz",
"integrity": "sha512-MOVAzsKJIPIlLK239l5s06YXjNqpKTVhBVDnqUumQJja5+Y94V3+4VUFRA0G60y2jNnTVwRCkhyGQpavfsbq/g==",
"version": "8.7.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.7.0.tgz",
"integrity": "sha512-MC8nmcGHsmfAKxwnluTQpNqceniT8SteVwd2voYlmiSWGOtjvGXdPl17dYu2797GVscK30Z04WRM28CrKS9WOg==",
"dev": true,
"license": "BSD-2-Clause",
"dependencies": {
"@typescript-eslint/types": "8.6.0",
"@typescript-eslint/visitor-keys": "8.6.0",
"@typescript-eslint/types": "8.7.0",
"@typescript-eslint/visitor-keys": "8.7.0",
"debug": "^4.3.4",
"fast-glob": "^3.3.2",
"is-glob": "^4.0.3",
@@ -2250,16 +2250,16 @@
}
},
"node_modules/@typescript-eslint/utils": {
"version": "8.6.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.6.0.tgz",
"integrity": "sha512-eNp9cWnYf36NaOVjkEUznf6fEgVy1TWpE0o52e4wtojjBx7D1UV2WAWGzR+8Y5lVFtpMLPwNbC67T83DWSph4A==",
"version": "8.7.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.7.0.tgz",
"integrity": "sha512-ZbdUdwsl2X/s3CiyAu3gOlfQzpbuG3nTWKPoIvAu1pu5r8viiJvv2NPN2AqArL35NCYtw/lrPPfM4gxrMLNLPw==",
"dev": true,
"license": "MIT",
"dependencies": {
"@eslint-community/eslint-utils": "^4.4.0",
"@typescript-eslint/scope-manager": "8.6.0",
"@typescript-eslint/types": "8.6.0",
"@typescript-eslint/typescript-estree": "8.6.0"
"@typescript-eslint/scope-manager": "8.7.0",
"@typescript-eslint/types": "8.7.0",
"@typescript-eslint/typescript-estree": "8.7.0"
},
"engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
@@ -2273,13 +2273,13 @@
}
},
"node_modules/@typescript-eslint/visitor-keys": {
"version": "8.6.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.6.0.tgz",
"integrity": "sha512-wapVFfZg9H0qOYh4grNVQiMklJGluQrOUiOhYRrQWhx7BY/+I1IYb8BczWNbbUpO+pqy0rDciv3lQH5E1bCLrg==",
"version": "8.7.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.7.0.tgz",
"integrity": "sha512-b1tx0orFCCh/THWPQa2ZwWzvOeyzzp36vkJYOpVg0u8UVOIsfVrnuC9FqAw9gRKn+rG2VmWQ/zDJZzkxUnj/XQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"@typescript-eslint/types": "8.6.0",
"@typescript-eslint/types": "8.7.0",
"eslint-visitor-keys": "^3.4.3"
},
"engines": {
@@ -4400,9 +4400,9 @@
}
},
"node_modules/vite": {
"version": "5.4.7",
"resolved": "https://registry.npmjs.org/vite/-/vite-5.4.7.tgz",
"integrity": "sha512-5l2zxqMEPVENgvzTuBpHer2awaetimj2BGkhBPdnwKbPNOlHsODU+oiazEZzLK7KhAnOrO+XGYJYn4ZlUhDtDQ==",
"version": "5.4.8",
"resolved": "https://registry.npmjs.org/vite/-/vite-5.4.8.tgz",
"integrity": "sha512-FqrItQ4DT1NC4zCUqMB4c4AZORMKIa0m8/URVCZ77OZ/QSNeJ54bU1vrFADbDsuwfIPcgknRkmqakQcgnL4GiQ==",
"dev": true,
"license": "MIT",
"dependencies": {

View File

@@ -26,15 +26,15 @@
"react-router-dom": "^6.26.2"
},
"devDependencies": {
"@types/react": "^18.3.8",
"@types/react": "^18.3.10",
"@types/react-dom": "^18.3.0",
"@typescript-eslint/eslint-plugin": "^8.6.0",
"@typescript-eslint/parser": "^8.6.0",
"@typescript-eslint/eslint-plugin": "^8.7.0",
"@typescript-eslint/parser": "^8.7.0",
"@vitejs/plugin-react": "^4.3.1",
"eslint": "^8.57.1",
"eslint-plugin-react-hooks": "^4.6.2",
"eslint-plugin-react-refresh": "^0.4.12",
"typescript": "^5.6.2",
"vite": "^5.4.7"
"vite": "^5.4.8"
}
}

View File

Before

Width:  |  Height:  |  Size: 407 KiB

After

Width:  |  Height:  |  Size: 407 KiB

View File

Before

Width:  |  Height:  |  Size: 557 B

After

Width:  |  Height:  |  Size: 557 B

View File

@@ -62,7 +62,7 @@ export function LoginRoute() {
<Grid
size={{ sm: 4, md: 7, xs: false }}
sx={{
backgroundImage: 'url("/sun.jpg")',
backgroundImage: 'url("/assets/sun.jpg")',
backgroundColor: (t) =>
t.palette.mode === "light"
? t.palette.grey[50]

View File

@@ -17,7 +17,6 @@ import { AsyncWidget } from "../widgets/AsyncWidget";
import { BoolText } from "../widgets/BoolText";
import { SolarEnergyRouteContainer } from "../widgets/SolarEnergyRouteContainer";
import { TimeWidget } from "../widgets/TimeWidget";
import { EditDeviceRelaysDialog } from "../dialogs/EditDeviceRelaysDialog";
import { useNavigate } from "react-router-dom";
export function RelaysListRoute(p: {

View File

@@ -49,6 +49,10 @@
"regex": "c",
"stdlib.h": "c",
"secure_api.h": "c",
"jwt.h": "c"
"jwt.h": "c",
"sync_response.h": "c",
"gpio.h": "c",
"esp_system.h": "c",
"relays.h": "c"
}
}

Binary file not shown.

View File

@@ -1,4 +1,4 @@
idf_component_register(SRCS "jwt.c" "secure_api.c" "http_client.c" "ethernet.c" "unsecure_api.c" "system.c" "crypto.c" "random.c" "storage.c" "main.c"
idf_component_register(SRCS "relays.c" "sync_response.c" "jwt.c" "secure_api.c" "http_client.c" "ethernet.c" "unsecure_api.c" "system.c" "crypto.c" "random.c" "storage.c" "main.c"
"dev_name.c"
INCLUDE_DIRS ".")

View File

@@ -10,11 +10,6 @@
*/
#define DEV_VERSION "0.0.1"
/**
* Device max number of relays
*/
#define DEV_MAX_RELAYS 1
/**
* Backend unsecure API URL
*/
@@ -44,3 +39,8 @@
* Secure origin len
*/
#define SEC_ORIG_LEN 255
/**
* Interval of time (in seconds) between two synchronisations
*/
#define SYNC_TIME_INTERVAL 5

View File

@@ -10,6 +10,7 @@
#include "secure_api.h"
#include "ethernet.h"
#include "constants.h"
#include "relays.h"
static const char *TAG = "main";
@@ -21,6 +22,10 @@ void app_main(void)
ESP_LOGI(TAG, "SolarEnergy WT32-ETH01 device");
// Turn off all relays
relays_turn_off_all();
relays_setup();
// Initialize storage
if (storage_init() == false)
{
@@ -167,22 +172,46 @@ void app_main(void)
// Main loop
ESP_LOGI(TAG, "Starting main loop");
// TODO : implement more properly
size_t fails = 0;
while (true)
{
if (!secure_api_sync_device())
sync_response *res = secure_api_sync_device();
if (!res)
{
ESP_LOGE(TAG, "Failed to synchronise device!");
}
else
{
ESP_LOGI(TAG, "Successfully synchronised device!");
fails += 1;
ESP_LOGE(TAG, "Failed to synchronise device! (number=%d)", fails);
// Safely turn off all relays after a given number of failures
if (fails > 5)
{
ESP_LOGE(TAG, "Many failures, will stop all relays...");
relays_turn_off_all();
}
// Restart the card after too much failures
if (fails > 10)
{
ESP_LOGE(TAG, "Too many failures, will try to reboot in 3 secs...");
system_sleep(3);
reboot();
}
system_sleep(SYNC_TIME_INTERVAL);
continue;
}
system_sleep(10);
fails = 0;
sync_response_print(res);
for (size_t i = 0; i < relays_count(); i++)
{
relays_set(i, sync_response_is_relay_on(res, i));
}
sync_response_free(res);
system_sleep(SYNC_TIME_INTERVAL);
}
system_sleep(120);
reboot();
}

View File

@@ -0,0 +1,63 @@
#include <driver/gpio.h>
#include "esp_log.h"
#include "relays.h"
#include "constants.h"
static const char *TAG = "relays";
/**
* Device relays GPIO ids
*/
static int DEVICE_GPIO_IDS[3] = {4, 14, 15};
int relays_count()
{
return sizeof(DEVICE_GPIO_IDS) / sizeof(int);
}
void relays_setup()
{
int pin_bit_mask = 0;
for (size_t i = 0; i < relays_count(); i++)
{
pin_bit_mask |= 1ULL << DEVICE_GPIO_IDS[i];
}
// zero-initialize the config structure.
gpio_config_t io_conf = {};
// disable interrupt
io_conf.intr_type = GPIO_INTR_DISABLE;
// set as output mode
io_conf.mode = GPIO_MODE_OUTPUT;
// bit mask of the pins that you want to set,e.g.GPIO18/19
io_conf.pin_bit_mask = pin_bit_mask;
// disable pull-down mode
io_conf.pull_down_en = 0;
// disable pull-up mode
io_conf.pull_up_en = 0;
// configure GPIO with the given settings
gpio_config(&io_conf);
}
void relays_turn_off_all()
{
ESP_LOGI(TAG, "Turning off all relays...");
for (size_t i = 0; i < relays_count(); i++)
{
relays_set(i, false);
}
}
void relays_set(int number, bool is_on)
{
size_t gpio_id = DEVICE_GPIO_IDS[number];
ESP_LOGI(TAG, "Set relay %d (gpio %d) to %s", number, gpio_id, is_on ? "on" : "off");
int res = gpio_set_level(gpio_id, is_on ? 0 : 1);
if (res != ESP_OK)
{
ESP_LOGE(TAG, "Failed to toggle GPIO %d : %d", gpio_id, res);
}
}

View File

@@ -0,0 +1,36 @@
/**
* Relays management
*/
#pragma once
#include <stddef.h>
#ifdef __cplusplus
extern "C"
{
#endif
/**
* Get the max number of relays
*/
int relays_count();
/**
* Configure the relays
*/
void relays_setup();
/**
* Turn off all relays
*/
void relays_turn_off_all();
/**
* Turn on / off a relay
*/
void relays_set(int number, bool is_on);
#ifdef __cplusplus
}
#endif

View File

@@ -11,6 +11,7 @@
#include "storage.h"
#include "http_client.h"
#include "jwt.h"
#include "relays.h"
#include "esp_log.h"
@@ -122,7 +123,7 @@ static cJSON *genDevInfo()
return NULL;
cJSON_AddStringToObject(json, "reference", DEV_REFERENCE);
cJSON_AddStringToObject(json, "version", DEV_VERSION);
cJSON_AddNumberToObject(json, "max_relays", DEV_MAX_RELAYS);
cJSON_AddNumberToObject(json, "max_relays", relays_count());
return json;
}
@@ -148,16 +149,16 @@ int secure_api_enroll_device()
free(csr);
char *body = cJSON_PrintUnformatted(obj);
cJSON_Delete(obj);
if (!body)
{
ESP_LOGE(TAG, "Failed to generate JSON body!");
cJSON_Delete(obj);
return 1;
}
char *res = process_secure_request("/devices_api/mgmt/enroll", body);
cJSON_Delete(obj);
free(body);
if (res == NULL)
@@ -195,7 +196,7 @@ char *secure_api_get_dev_certificate()
return res;
}
void *secure_api_sync_device()
sync_response *secure_api_sync_device()
{
cJSON *obj = cJSON_CreateObject();
if (!obj)
@@ -215,10 +216,54 @@ void *secure_api_sync_device()
return NULL;
}
printf("JWT: %s\n", encoded_req);
// Prepare request body
cJSON *json_body = cJSON_CreateObject();
if (!json_body)
{
ESP_LOGE(TAG, "Failed to allocated memory to store sync request body!");
free(encoded_req);
return NULL;
}
cJSON_AddStringToObject(json_body, "payload", encoded_req);
free(encoded_req);
// TODO : replace
printf("here implement sync device logic\n");
return NULL;
char *body = cJSON_PrintUnformatted(json_body);
cJSON_Delete(json_body);
if (!body)
{
ESP_LOGE(TAG, "Failed to allocated memory to store encoded sync request body!");
return NULL;
}
// Send request
char *res = process_secure_request("/devices_api/mgmt/sync", body);
free(body);
if (res == NULL)
{
ESP_LOGE(TAG, "Sync request failed!");
return NULL;
}
// Parse response
cJSON *states = cJSON_Parse(res);
free(res);
if (!states)
{
ESP_LOGE(TAG, "Failed to decode sync response from server!");
return NULL;
}
sync_response *sync_res = sync_response_parse(states);
cJSON_Delete(states);
if (!sync_res)
{
ESP_LOGE(TAG, "Failed to parse sync response from server!");
return NULL;
}
return sync_res;
}

View File

@@ -2,6 +2,11 @@
* Secure API functions
*/
#include <stddef.h>
#include <stdbool.h>
#include "sync_response.h"
#pragma once
#ifdef __cplusplus
@@ -45,7 +50,7 @@ extern "C"
*
* Returns NULL in case of failure
*/
void *secure_api_sync_device();
sync_response *secure_api_sync_device();
#ifdef __cplusplus
}

View File

@@ -0,0 +1,75 @@
#include "sync_response.h"
#include <stdlib.h>
#include <esp_log.h>
const static char *TAG = "sync_response";
sync_response *sync_response_parse(cJSON *res)
{
cJSON *relays_json = cJSON_GetObjectItem(res, "relays");
if (relays_json == NULL)
{
ESP_LOGE(TAG, "Missing relays status in sync response!");
return NULL;
}
int relays_size = cJSON_GetArraySize(relays_json);
sync_response *sync_res = calloc(1, sizeof(sync_response) + relays_size * sizeof(bool));
if (!sync_res)
{
ESP_LOGE(TAG, "Failed to allocate memory to store synchronization response!");
return NULL;
}
sync_res->len = relays_size;
for (int i = 0; i < sync_res->len; i++)
{
sync_res->relays[i] = false;
cJSON *item = cJSON_GetArrayItem(relays_json, i);
assert(item != NULL);
cJSON *enabled = cJSON_GetObjectItem(item, "enabled");
if (enabled == NULL)
{
ESP_LOGE(TAG, "At least a relay is missing the enabled field. Assuming false.");
continue;
}
if (cJSON_IsTrue(enabled))
{
sync_res->relays[i] = true;
}
}
return sync_res;
}
void sync_response_print(sync_response *res)
{
ESP_LOGI(TAG, " === sync response begin === ");
ESP_LOGI(TAG, "# of relays: %d", res->len);
for (size_t i = 0; i < res->len; i++)
ESP_LOGI(TAG, "Relay[%d]=%s", i, res->relays[i] ? "ON" : "off");
ESP_LOGI(TAG, " === sync response end === ");
}
void sync_response_free(sync_response *res)
{
if (res != NULL)
free(res);
}
bool sync_response_is_relay_on(sync_response *res, int relay_number)
{
if (res->len <= relay_number)
{
ESP_LOGW(TAG, "Requested state of an unconfigured relay (%d). Defaulting to off.", relay_number);
return false;
}
return res->relays[relay_number];
}

View File

@@ -0,0 +1,44 @@
/**
* Synchronisation response
*/
#pragma once
#include <stddef.h>
#include <stdbool.h>
#include <cJSON.h>
#ifdef __cplusplus
extern "C"
{
#endif
typedef struct sync_response
{
size_t len;
bool relays[];
} sync_response;
/**
* Decode synchronize response from server
*/
sync_response *sync_response_parse(cJSON *res);
/**
* Print synchronize reponse content
*/
void sync_response_print(sync_response *res);
/**
* Free memory allocated for synchronize response
*/
void sync_response_free(sync_response *res);
/**
* Check the desired status of a relay
*/
bool sync_response_is_relay_on(sync_response *res, int relay_number);
#ifdef __cplusplus
}
#endif