ProtoCore v0.0.2
Deterministic, zero-heap network stack for embedded targets
Loading...
Searching...
No Matches
gpio_map.cpp
Go to the documentation of this file.
1// Copyright (C) 2026 Douglas Quigg (dstroy0) <dquigg123@gmail.com>
2// SPDX-License-Identifier: AGPL-3.0-or-later
3
4/**
5 * @file gpio_map.cpp
6 * @brief GPIO pin-mapper direction names, JSON serializer, control parser, and
7 * the ESP32 digital read / write helpers.
8 *
9 * The serializer and the `pin=&level=` parser are pure (host-tested); the digital
10 * I/O uses the Arduino API on ESP32 and is a no-op on host builds. No server
11 * dependency lives here - the route is in gpio_map_routes.cpp.
12 */
13
15
16#if PC_ENABLE_GPIO_MAP
17
18#include <string.h>
19
21
22#if defined(ARDUINO)
23#include <Arduino.h>
24#endif
25const char *pc_gpio_dir_name(pc_gpio_dir dir)
26{
27 switch (dir)
28 {
29 case pc_gpio_dir::PC_GPIO_IN:
30 return "in";
31 case pc_gpio_dir::PC_GPIO_IN_PULLUP:
32 return "in_pullup";
33 case pc_gpio_dir::PC_GPIO_IN_PULLDOWN:
34 return "in_pulldown";
35 case pc_gpio_dir::PC_GPIO_OUT:
36 return "out";
37 default:
38 return "in";
39 }
40}
41
42// The document is three frames: the object that opens the array, one object per pin, and the
43// close. The separating comma is the pin frame's first field so a pin is one append either way.
44static const pc_field GPIO_OPEN[] = {{PC_FK_LIT, 0, 9, "{\"pins\":["}, PC_END};
45static const pc_field GPIO_PIN[] = {
46 PC_STR, // "," from the second pin on
47 {PC_FK_LIT, 0, 7, "{\"pin\":"}, //
48 PC_U32, // pin number
49 {PC_FK_LIT, 0, 9, ",\"label\":"}, //
50 PC_JSON, // label, quoted and escaped
51 {PC_FK_LIT, 0, 7, ",\"dir\":"}, //
52 PC_JSON, // direction name
53 {PC_FK_LIT, 0, 9, ",\"level\":"}, //
54 PC_U32, // 0 or 1
55 {PC_FK_LIT, 0, 1, "}"}, //
56 PC_END,
57};
58static const pc_field GPIO_CLOSE[] = {{PC_FK_LIT, 0, 2, "]}"}, PC_END};
59
60int32_t pc_gpio_json(const pc_gpio_pin *pins, uint8_t count, char *out, uint32_t cap)
61{
62 if (!out || cap == 0)
63 {
64 return 0;
65 }
66 out[0] = '\0';
67 if (!pins)
68 {
69 return 0;
70 }
71 if (pc_frame_append(out, cap, GPIO_OPEN) == 0)
72 {
73 return 0;
74 }
75 for (uint8_t i = 0; i < count; i++)
76 {
77 const pc_gpio_pin *p = &pins[i];
78 if (pc_frame_append(out, cap, GPIO_PIN, i ? "," : "", (uint32_t)p->pin, p->label ? p->label : "",
79 pc_gpio_dir_name(p->dir), p->level ? 1u : 0u) == 0)
80 {
81 return 0;
82 }
83 }
84 return (int32_t)pc_frame_append(out, cap, GPIO_CLOSE);
85}
86
87// Read the decimal integer that follows "name=" in a form-encoded body. Returns
88// false if the field is absent or has no digits.
89static bool form_field_uint(const char *body, size_t len, const char *name, unsigned *out)
90{
91 size_t nlen = strnlen(name, len + 1);
92 for (size_t i = 0; i + nlen + 1 <= len; i++)
93 {
94 bool at_field = (i == 0) || body[i - 1] == '&';
95 if (!at_field || memcmp(body + i, name, nlen) != 0 || body[i + nlen] != '=')
96 {
97 continue;
98 }
99 size_t j = i + nlen + 1;
100 if (j >= len || body[j] < '0' || body[j] > '9')
101 {
102 return false;
103 }
104 unsigned v = 0;
105 for (; j < len && body[j] >= '0' && body[j] <= '9'; j++)
106 {
107 v = v * 10 + (unsigned)(body[j] - '0');
108 }
109 *out = v;
110 return true;
111 }
112 return false;
113}
114
115bool pc_gpio_parse_set(const char *body, size_t len, uint8_t *pin, uint8_t *level)
116{
117 if (!body || !pin || !level)
118 {
119 return false;
120 }
121 unsigned p;
122 unsigned l;
123 if (!form_field_uint(body, len, "pin", &p) || !form_field_uint(body, len, "level", &l))
124 {
125 return false;
126 }
127 *pin = (uint8_t)p;
128 *level = l ? 1 : 0;
129 return true;
130}
131
132bool pc_gpio_is_output(const pc_gpio_pin *pins, uint8_t count, uint8_t pin)
133{
134 if (!pins)
135 {
136 return false;
137 }
138 for (uint8_t i = 0; i < count; i++)
139 {
140 if (pins[i].pin == pin && pins[i].dir == pc_gpio_dir::PC_GPIO_OUT)
141 {
142 return true;
143 }
144 }
145 return false;
146}
147
148#ifdef ARDUINO
149
150void pc_gpio_begin_pins(const pc_gpio_pin *pins, uint8_t count)
151{
152 if (!pins)
153 {
154 return;
155 }
156 for (uint8_t i = 0; i < count; i++)
157 {
158 switch (pins[i].dir)
159 {
160 case pc_gpio_dir::PC_GPIO_OUT:
161 pinMode(pins[i].pin, OUTPUT);
162 break;
163 case pc_gpio_dir::PC_GPIO_IN_PULLUP:
164 pinMode(pins[i].pin, INPUT_PULLUP);
165 break;
166 case pc_gpio_dir::PC_GPIO_IN_PULLDOWN:
167 pinMode(pins[i].pin, INPUT_PULLDOWN);
168 break;
169 default:
170 pinMode(pins[i].pin, INPUT);
171 break;
172 }
173 }
174}
175
176void pc_gpio_read(pc_gpio_pin *pins, uint8_t count)
177{
178 if (!pins)
179 {
180 return;
181 }
182 for (uint8_t i = 0; i < count; i++)
183 {
184 pins[i].level = (uint8_t)(digitalRead(pins[i].pin) ? 1 : 0);
185 }
186}
187
188void pc_gpio_write(uint8_t pin, uint8_t level)
189{
190 digitalWrite(pin, level ? HIGH : LOW);
191}
192
193#else // host build - no GPIO
194
195void pc_gpio_begin_pins(const pc_gpio_pin *, uint8_t)
196{
197}
198
199void pc_gpio_read(pc_gpio_pin *, uint8_t)
200{
201}
202
203void pc_gpio_write(uint8_t, uint8_t)
204{
205}
206
207#endif // ARDUINO
208
209#endif // PC_ENABLE_GPIO_MAP
size_t pc_frame_append(char *out, size_t cap, const pc_field *spec,...)
Append spec to the NUL-terminated contents already in out.
Definition frame.cpp:109
Declarative frame builder: a frame is a static table of typed fields, built by one engine.
#define PC_END
Definition frame.h:110
#define PC_JSON
Definition frame.h:108
#define PC_U32
Definition frame.h:104
@ PC_FK_LIT
literal text from lit; takes no argument
Definition frame.h:56
#define PC_STR
Definition frame.h:103
Browser GPIO pin-mapper / diagnostics (PC_ENABLE_GPIO_MAP).
One field of a frame. Frames are static const pc_field[], so they live in rodata.
Definition frame.h:80