Layer: L7 Application · Build flags: PC_ENABLE_GRAPHQL
What this example teaches
GraphQL lets the client say exactly which fields it wants in one request. POST a query to /graphql and the device resolves the selected fields and returns a {"data":{...}} response shaped by the query - so the same endpoint serves a minimal phone client and a rich dashboard without new routes.
A resolver answers one scalar per dotted path. Nested selections become dotted paths (net.rssi); arguments are visible to the resolver for that field:
static bool resolver(const char *path, const pc_gql_args *args, pc_gql_value *out) {
if (!strcmp(path, "heap")) { out->type = pc_gql_type::PC_GQL_INT; out->i = ESP.getFreeHeap(); return true; }
if (!strcmp(path,
"net.rssi")) { out->type = pc_gql_type::PC_GQL_INT; out->i =
pc_net_rssi();
return true; }
if (!strcmp(path, "greet")) {
const char *who = "?";
pc_gql_arg_str(args, "name", &who);
static char b[64]; snprintf(b, sizeof(b), "hi %s", who);
out->type = pc_gql_type::PC_GQL_STR; out->s = b; return true;
}
return false;
}
int8_t pc_net_rssi(void)
Station link RSSI in dBm, or 0 if not associated (and on host builds).
A field with a sub-selection (like net) is an object the engine builds by recursing into the resolver for each child; returning false yields null.
One call executes a query. The engine parses the body, walks the selection set, and writes the response envelope:
pc_gql_result rc = pc_graphql_execute(req->body, req->body_len, resolver, body, sizeof(body));
server.send(id, rc == pc_gql_result::PC_GQL_OK ? 200 : 400, "application/json", body);
It writes {"data":...} on success or {"errors":...} on a parse error; answering 200 with the GraphQL error envelope is the conventional reply.
Build and run
pio ci --board=esp32dev --project-option="framework=arduino" \
--project-option="build_flags=-DPC_ENABLE_GRAPHQL=1" \
--lib="." examples/L7-Application/GraphQL/GraphQL.ino
curl -s --data '{ heap uptime net { rssi } }' http://<ip>/graphql
# {"data":{"heap":210000,"uptime":42,"net":{"rssi":-50}}}
curl -s --data '{ greet(name: "world") }' http://<ip>/graphql
# {"data":{"greet":"hi world"}}
Annotated source
The complete sketch (GraphQL.ino), reproduced verbatim with added explanatory comments:
#define PC_ENABLE_GRAPHQL 1
static const char *SSID = "YOUR_SSID";
static const char *PASSWORD = "YOUR_PASSWORD";
static bool resolver(const char *path, const pc_gql_args *args, pc_gql_value *out)
{
if (!strcmp(path, "heap"))
{
out->type = pc_gql_type::PC_GQL_INT;
out->i = ESP.getFreeHeap();
return true;
}
if (!strcmp(path, "uptime"))
{
out->type = pc_gql_type::PC_GQL_INT;
out->i = millis() / 1000;
return true;
}
if (!strcmp(path, "net.rssi"))
{
out->type = pc_gql_type::PC_GQL_INT;
return true;
}
if (!strcmp(path, "net.ip"))
{
static char ip[20];
snprintf(ip, sizeof(ip), "%u.%u.%u.%u", (unsigned)(v4 & 0xFF), (unsigned)((v4 >> 8) & 0xFF),
(unsigned)((v4 >> 16) & 0xFF), (unsigned)((v4 >> 24) & 0xFF));
out->type = pc_gql_type::PC_GQL_STR;
out->s = ip;
return true;
}
if (!strcmp(path, "greet"))
{
const char *who = "?";
pc_gql_arg_str(args, "name", &who);
static char b[64];
snprintf(b, sizeof(b), "hi %s", who);
out->type = pc_gql_type::PC_GQL_STR;
out->s = b;
return true;
}
return false;
}
void setup()
{
Serial.begin(115200);
delay(250);
Serial.print("IP: ");
Serial.printf("IP: %u.%u.%u.%u\n", (unsigned)(ip & 0xFF), (unsigned)((ip >> 8) & 0xFF),
(unsigned)((ip >> 16) & 0xFF), (unsigned)((ip >> 24) & 0xFF));
char body[512];
pc_gql_result rc = pc_graphql_execute((
const char *)req->
body, req->
body_len, resolver, body,
sizeof(body));
server.
send(
id, rc == pc_gql_result::PC_GQL_OK ? 200 : 400,
"application/json", body);
});
}
void loop()
{
}
Single-port HTTP server with deterministic, zero-allocation execution.
void send(uint8_t slot_id, int code, const char *content_type, const char *payload)
Send an HTTP response with a body and close the connection.
int32_t begin(const WebServerConfig *cfg=nullptr)
Initialize all connection slots and open all registered listeners.
void on(const char *path, HttpMethod method, Handler callback)
Register a route handler.
void handle()
Drive the server - call every Arduino loop() iteration.
Zero-heap GraphQL query subset - parser + executor (PC_ENABLE_GRAPHQL).
bool init_wifi_physical(const char *, const char *)
Connect to a WiFi access point.
uint32_t pc_net_egress_ip(void)
IPv4 (network byte order) of the current egress interface, or 0 if none.
bool wifi_ready()
True if the WiFi station link is up (associated + an IP is assigned).
Layer 1 (Physical) - link bring-up and live egress-interface reporting.
Layer 7 (Application) - public HTTP routing API.
@ HTTP_POST
Non-idempotent create / action.
Fully-parsed HTTP/1.1 request.
uint8_t body[BODY_BUF_SIZE+1]
Stored body bytes, always null-terminated.
size_t body_len
Bytes stored in body[] (≤ BODY_BUF_SIZE).