ra8-firmware 0.1.0
Bare-metal firmware for the Renesas RA8 family (RA8D2 / RA8P1)
Loading...
Searching...
No Matches
c6_cam_net.c
Go to the documentation of this file.
1
17
18#include <stdint.h>
19#include <string.h>
20
21#include "c6_camera_server.h"
22#include "nx_api.h"
23#include "nx_ether_driver_c6.h"
24#include "nxd_dhcp_client.h"
25#include "ra8_attributes.h"
26#include "ra8_c6link.h"
27#include "ra8_err.h"
28#include "tx_api.h"
29
30static NX_PACKET_POOL s_pool;
31alignas(4) static uint8_t s_pool_mem[k_c6_cam_net_pool_bytes];
32static NX_IP s_ip;
33alignas(8) static uint8_t s_ip_stack[k_c6_cam_net_ip_stack];
34alignas(4) static uint8_t s_arp_cache[k_c6_cam_net_arp_bytes];
35static NX_DHCP s_dhcp;
36static NX_TCP_SOCKET s_http_socket;
37static CHAR s_pool_name[] = "c6_cam_pool";
38static CHAR s_ip_name[] = "c6_cam_ip";
39static CHAR s_dhcp_name[] = "c6_cam_dhcp";
40static CHAR s_socket_name[] = "c6_cam_http";
41
42static const char s_page[] =
43 "<!doctype html><html><head><meta charset=utf-8>"
44 "<meta name=viewport content='width=device-width,initial-scale=1'>"
45 "<title>EK-RA8D2 Camera</title><style>body{margin:0;background:#111;color:#eee;"
46 "font:16px system-ui;display:grid;place-items:center;min-height:100vh}main{text-align:center}"
47 "img{width:min(96vw,960px);height:auto;image-rendering:auto;border:1px solid #555}"
48 "small{display:block;margin-top:.5rem;color:#aaa}audio{margin-top:1rem}</style></head><body><main>"
49 "<h1>EK-RA8D2 / OV5640</h1><img id=stream src=/stream.mjpg alt='live camera'>"
50 "<small>bounded multipart MJPEG stream with automatic reconnect</small>"
51 "<audio controls src=/audio.wav></audio>"
52 "<small>latest rolling one-second onboard MIC1 recording</small>"
53 "<script>const s=document.getElementById('stream');s.onerror=()=>{"
54 "setTimeout(()=>{s.src='/stream.mjpg?t='+Date.now()},1000)}</script>"
55 "</main></body></html>";
56
71{
72 UINT status = nx_packet_pool_create(&s_pool,
75 (VOID*)s_pool_mem,
76 (ULONG)sizeof(s_pool_mem));
77 if (status != NX_SUCCESS) {
78 return status;
79 }
80 status = nx_ip_create(&s_ip,
82 IP_ADDRESS(0, 0, 0, 0),
83 IP_ADDRESS(0, 0, 0, 0),
84 &s_pool,
86 (VOID*)s_ip_stack,
87 (ULONG)sizeof(s_ip_stack),
89 if (status != NX_SUCCESS) {
90 return status;
91 }
92 status = nx_arp_enable(&s_ip, (VOID*)s_arp_cache, (ULONG)sizeof(s_arp_cache));
93 if (status != NX_SUCCESS) {
94 return status;
95 }
96 status = nx_udp_enable(&s_ip);
97 if (status != NX_SUCCESS) {
98 return status;
99 }
100 status = nx_tcp_enable(&s_ip);
101 if (status != NX_SUCCESS) {
102 return status;
103 }
104 return nx_icmp_enable(&s_ip);
105}
106
108{
109 if ((link == nullptr) || (mac == nullptr) || (out == nullptr)) {
110 return k_ra8_err_null_ptr;
111 }
112 *out = (c6_cam_lease_t){};
115 nx_system_initialize();
116 if (internal_c6_cam_net_create() != NX_SUCCESS) {
118 }
119 UINT status = nx_dhcp_create(&s_dhcp, &s_ip, s_dhcp_name);
120 if (status == NX_SUCCESS) {
121 status = nx_dhcp_start(&s_dhcp);
122 }
123 ULONG actual = 0U;
124 if (status == NX_SUCCESS) {
125 status = nx_ip_status_check(&s_ip,
126 (ULONG)NX_IP_ADDRESS_RESOLVED,
127 &actual,
129 }
130 if (status != NX_SUCCESS) {
131 return k_ra8_err_timeout;
132 }
133 ULONG ip = 0U;
134 ULONG mask = 0U;
135 ULONG gateway = 0U;
136 ULONG server = 0U;
137 (void)nx_ip_address_get(&s_ip, &ip, &mask);
138 (void)nx_ip_gateway_address_get(&s_ip, &gateway);
139 (void)nx_dhcp_server_address_get(&s_dhcp, &server);
140 out->ip = (uint32_t)ip;
141 out->mask = (uint32_t)mask;
142 out->gateway = (uint32_t)gateway;
143 out->dhcp_server = (uint32_t)server;
144 out->bound = (ip != 0U);
145 return out->bound ? k_ra8_ok : k_ra8_err_timeout;
146}
147
163RA8_INTERNAL static UINT internal_c6_cam_http_send(const void* data, uint32_t bytes)
164{
165 const uint8_t* cursor = (const uint8_t*)data;
166 uint32_t remaining = bytes;
167 while (remaining != 0U) {
168 const uint32_t chunk =
169 (remaining > (uint32_t)k_c6_cam_http_chunk) ? (uint32_t)k_c6_cam_http_chunk : remaining;
170 NX_PACKET* packet = NX_NULL;
171 UINT status = nx_packet_allocate(&s_pool, &packet, NX_TCP_PACKET, NX_WAIT_FOREVER);
172 if (status != NX_SUCCESS) {
173 return status;
174 }
175 status = nx_packet_data_append(packet,
176 (VOID*)(uintptr_t)cursor,
177 (ULONG)chunk,
178 &s_pool,
179 NX_WAIT_FOREVER);
180 if (status != NX_SUCCESS) {
181 (void)nx_packet_release(packet);
182 return status;
183 }
184 status = nx_tcp_socket_send(&s_http_socket, packet, NX_WAIT_FOREVER);
185 if (status != NX_SUCCESS) {
186 (void)nx_packet_release(packet);
187 return status;
188 }
189 cursor += chunk;
190 remaining -= chunk;
191 }
192 return NX_SUCCESS;
193}
194
211RA8_INTERNAL static uint32_t
212internal_c6_cam_append(char* out, uint32_t at, uint32_t cap, const char* text)
213{
214 while ((at < cap) && (*text != '\0')) {
215 out[at++] = *text++;
216 }
217 return at;
218}
219
225
242RA8_INTERNAL static uint32_t
243internal_c6_cam_append_u32(char* out, uint32_t at, uint32_t cap, uint32_t value)
244{
245 char reverse[k_c6_cam_net_decimal_digits] = {};
246 uint32_t count = 0U;
247 do {
248 reverse[count++] = (char)('0' + (value % (uint32_t)k_c6_cam_net_decimal_digits));
249 value /= (uint32_t)k_c6_cam_net_decimal_digits;
250 } while ((value != 0U) && (count < (uint32_t)k_c6_cam_net_decimal_digits));
251 while ((count != 0U) && (at < cap)) {
252 out[at++] = reverse[--count];
253 }
254 return at;
255}
256
276 const void* body,
277 uint32_t body_bytes,
278 const c6_cam_frame_timing_t* timing,
279 uint32_t timestamp_ms)
280{
281 char header[320] = {};
282 uint32_t length =
283 internal_c6_cam_append(header, 0U, sizeof(header), "HTTP/1.1 200 OK\r\nContent-Type: ");
284 length = internal_c6_cam_append(header, length, sizeof(header), type);
285 if (timing != nullptr) {
286 length =
287 internal_c6_cam_append(header, length, sizeof(header), "\r\nServer-Timing: capture;dur=");
288 length = internal_c6_cam_append_u32(header, length, sizeof(header), timing->capture_ms);
289 length = internal_c6_cam_append(header, length, sizeof(header), ", convert;dur=");
290 length = internal_c6_cam_append_u32(header, length, sizeof(header), timing->convert_ms);
291 length = internal_c6_cam_append(header, length, sizeof(header), ", encode;dur=");
292 length = internal_c6_cam_append_u32(header, length, sizeof(header), timing->encode_ms);
293 }
294 if (timestamp_ms != 0U) {
295 length = internal_c6_cam_append(header, length, sizeof(header), "\r\nX-RA8-Timestamp-Ms: ");
296 length = internal_c6_cam_append_u32(header, length, sizeof(header), timestamp_ms);
297 }
298 length = internal_c6_cam_append(header, length, sizeof(header), "\r\nContent-Length: ");
299 length = internal_c6_cam_append_u32(header, length, sizeof(header), body_bytes);
300 length =
302 length,
303 sizeof(header),
304 "\r\nCache-Control: no-store, no-cache\r\nConnection: close\r\n\r\n");
305 UINT status = internal_c6_cam_http_send(header, length);
306 if (status == NX_SUCCESS) {
307 status = internal_c6_cam_http_send(body, body_bytes);
308 }
309 return status;
310}
311
326{
327 static const char stream_header[] = "HTTP/1.1 200 OK\r\n"
328 "Content-Type: multipart/x-mixed-replace; boundary=frame\r\n"
329 "Cache-Control: no-store, no-cache\r\n"
330 "Connection: close\r\n\r\n";
331 UINT status = internal_c6_cam_http_send(stream_header, (uint32_t)(sizeof(stream_header) - 1U));
332 for (uint32_t frame_index = 0U;
333 (frame_index < (uint32_t)k_c6_cam_stream_frames) && (status == NX_SUCCESS);
334 frame_index++) {
335 const uint8_t* jpeg = nullptr;
336 uint32_t jpeg_bytes = 0U;
337 c6_cam_frame_timing_t timing = {};
338 const ra8_err_t err = c6_cam_camera_capture_jpeg(&jpeg, &jpeg_bytes, &timing);
339 if (err != k_ra8_ok) {
341 return NX_NOT_SUCCESSFUL;
342 }
343 char part_header[128] = {};
344 uint32_t length =
345 internal_c6_cam_append(part_header,
346 0U,
347 sizeof(part_header),
348 "--frame\r\nContent-Type: image/jpeg\r\nContent-Length: ");
349 length = internal_c6_cam_append_u32(part_header, length, sizeof(part_header), jpeg_bytes);
350 length = internal_c6_cam_append(part_header, length, sizeof(part_header), "\r\n\r\n");
351 status = internal_c6_cam_http_send(part_header, length);
352 if (status == NX_SUCCESS) {
353 status = internal_c6_cam_http_send(jpeg, jpeg_bytes);
354 }
355 if (status == NX_SUCCESS) {
356 static const char part_tail[] = "\r\n";
357 status = internal_c6_cam_http_send(part_tail, (uint32_t)(sizeof(part_tail) - 1U));
358 }
359 if (status == NX_SUCCESS) {
360 c6_cam_puts("c6_cam: stream jpeg_bytes=");
361 c6_cam_put_u32(jpeg_bytes);
362 c6_cam_puts(" capture_ms=");
364 c6_cam_puts(" convert_ms=");
366 c6_cam_puts(" encode_ms=");
368 c6_cam_puts("\r\n");
369 }
370 }
371 return status;
372}
373
390RA8_INTERNAL static bool
391internal_c6_cam_request_is(const char* request, uint32_t bytes, const char* path)
392{
393 if ((bytes < (uint32_t)k_c6_cam_http_get_min) || (memcmp(request, "GET ", 4U) != 0)) {
394 return false;
395 }
396 uint32_t index = 4U;
397 uint32_t path_index = 0U;
398 while ((index < bytes) && (path[path_index] != '\0') && (request[index] == path[path_index])) {
399 index++;
400 path_index++;
401 }
402 if (path[path_index] != '\0') {
403 return false;
404 }
405 return (index < bytes) && ((request[index] == ' ') || (request[index] == '?'));
406}
407
420{
421 const uint8_t* jpeg = nullptr;
422 uint32_t jpeg_bytes = 0U;
423 c6_cam_frame_timing_t timing = {};
424 const ra8_err_t err = c6_cam_camera_capture_jpeg(&jpeg, &jpeg_bytes, &timing);
425 if (err == k_ra8_ok) {
426 (void)
427 internal_c6_cam_http_response("image/jpeg", jpeg, jpeg_bytes, &timing, timing.timestamp_ms);
428 c6_cam_puts("c6_cam: frame jpeg_bytes=");
429 c6_cam_put_u32(jpeg_bytes);
430 c6_cam_puts(" capture_ms=");
432 c6_cam_puts(" convert_ms=");
434 c6_cam_puts(" encode_ms=");
436 c6_cam_puts("\r\n");
437 return;
438 }
439 static const char failed[] = "camera capture failed\n";
440 (void)internal_c6_cam_http_response("text/plain",
441 failed,
442 (uint32_t)(sizeof(failed) - 1U),
443 nullptr,
444 0U);
445 c6_cam_puts("c6_cam: frame FAIL err=");
448 c6_cam_puts("\r\n");
449}
450
463{
464 const uint8_t* wav = nullptr;
465 uint32_t wav_bytes = 0U;
466 uint32_t timestamp_ms = 0U;
467 const ra8_err_t err = c6_cam_audio_snapshot_wav(&wav, &wav_bytes, &timestamp_ms);
468 if (err == k_ra8_ok) {
469 (void)internal_c6_cam_http_response("audio/wav", wav, wav_bytes, nullptr, timestamp_ms);
470 c6_cam_puts("c6_cam: audio wav_bytes=");
471 c6_cam_put_u32(wav_bytes);
472 c6_cam_puts(" timestamp_ms=");
473 c6_cam_put_u32(timestamp_ms);
474 c6_cam_puts("\r\n");
475 return;
476 }
477 static const char unavailable[] = "audio capture not ready\n";
478 (void)internal_c6_cam_http_response("text/plain",
479 unavailable,
480 (uint32_t)(sizeof(unavailable) - 1U),
481 nullptr,
482 0U);
483}
484
498RA8_INTERNAL static void internal_c6_cam_http_dispatch(const char* request, uint32_t bytes)
499{
500 if (internal_c6_cam_request_is(request, bytes, "/stream.mjpg")) {
502 } else if (internal_c6_cam_request_is(request, bytes, "/frame.jpg")) {
504 } else if (internal_c6_cam_request_is(request, bytes, "/audio.wav")) {
506 } else if (internal_c6_cam_request_is(request, bytes, "/health")) {
507 static const char healthy[] = "PASS c6 camera livestream audio=PASS camera=PASS\n";
508 (void)internal_c6_cam_http_response("text/plain",
509 healthy,
510 (uint32_t)(sizeof(healthy) - 1U),
511 nullptr,
512 0U);
513 } else {
514 (void)internal_c6_cam_http_response("text/html; charset=utf-8",
515 s_page,
516 (uint32_t)(sizeof(s_page) - 1U),
517 nullptr,
518 0U);
519 }
520}
521
533{
534 NX_PACKET* request_packet = NX_NULL;
535 if (nx_tcp_socket_receive(&s_http_socket, &request_packet, NX_WAIT_FOREVER) != NX_SUCCESS) {
536 return;
537 }
538 ULONG packet_bytes = 0U;
539 (void)nx_packet_length_get(request_packet, &packet_bytes);
540 uint32_t request_bytes = (packet_bytes > (ULONG)k_c6_cam_request_max)
541 ? (uint32_t)k_c6_cam_request_max
542 : (uint32_t)packet_bytes;
543 char request[k_c6_cam_request_max] = {};
544 ULONG copied = 0U;
545 (void)nx_packet_data_extract_offset(request_packet, 0U, request, (ULONG)request_bytes, &copied);
546 (void)nx_packet_release(request_packet);
547 internal_c6_cam_http_dispatch(request, (uint32_t)copied);
548}
549
551{
552 UINT status = nx_tcp_socket_create(&s_ip,
555 NX_IP_NORMAL,
556 NX_FRAGMENT_OKAY,
557 NX_IP_TIME_TO_LIVE,
559 NX_NULL,
560 NX_NULL);
561 if (status == NX_SUCCESS) {
562 status = nx_tcp_server_socket_listen(&s_ip,
566 NX_NULL);
567 }
568 if (status != NX_SUCCESS) {
569 c6_cam_puts("c6_cam: FAIL HTTP socket setup\r\n");
570 while (true) {
571 tx_thread_sleep(NX_IP_PERIODIC_RATE);
572 }
573 }
574 c6_cam_puts("c6_cam: PASS HTTP camera server listening\r\n");
575 while (true) {
576 if (nx_tcp_server_socket_accept(&s_http_socket, NX_WAIT_FOREVER) == NX_SUCCESS) {
578 (void)nx_tcp_socket_disconnect(&s_http_socket, NX_WAIT_FOREVER);
579 (void)nx_tcp_server_socket_unaccept(&s_http_socket);
580 }
581 (void)nx_tcp_server_socket_relisten(&s_ip, (UINT)k_c6_cam_http_port, &s_http_socket);
582 }
583}
static CHAR s_dhcp_name[]
Definition c6_cam_net.c:39
static UINT internal_c6_cam_http_send(const void *data, uint32_t bytes)
Send an exact byte span over the accepted HTTP socket.
Definition c6_cam_net.c:163
c6_cam_net_format_t
Decimal formatting and minimum HTTP request sizes.
Definition c6_cam_net.c:221
@ k_c6_cam_http_get_min
Minimum bytes in an HTTP GET prefix.
Definition c6_cam_net.c:223
@ k_c6_cam_net_decimal_digits
Decimal digits in a 32-bit integer.
Definition c6_cam_net.c:222
void c6_cam_http_serve(void)
Serve the browser UI and fresh JPEG frames forever.
Definition c6_cam_net.c:550
static void internal_c6_cam_http_dispatch(const char *request, uint32_t bytes)
Dispatch a bounded HTTP request to one route handler.
Definition c6_cam_net.c:498
static uint8_t s_pool_mem[k_c6_cam_net_pool_bytes]
Definition c6_cam_net.c:31
static CHAR s_pool_name[]
Definition c6_cam_net.c:37
static void internal_c6_cam_http_audio(void)
Snapshot and serve the current audio window.
Definition c6_cam_net.c:462
static const char s_page[]
Definition c6_cam_net.c:42
static void internal_c6_cam_http_handle(void)
Receive and dispatch one HTTP request.
Definition c6_cam_net.c:532
static void internal_c6_cam_http_frame(void)
Capture and serve one JPEG still response.
Definition c6_cam_net.c:419
static NX_TCP_SOCKET s_http_socket
Definition c6_cam_net.c:36
static bool internal_c6_cam_request_is(const char *request, uint32_t bytes, const char *path)
Match a bounded HTTP GET request path.
Definition c6_cam_net.c:391
static UINT internal_c6_cam_http_response(const char *type, const void *body, uint32_t body_bytes, const c6_cam_frame_timing_t *timing, uint32_t timestamp_ms)
Send one complete close-delimited HTTP response.
Definition c6_cam_net.c:275
static UINT internal_c6_cam_http_stream(void)
Serve a bounded multipart MJPEG sequence.
Definition c6_cam_net.c:325
static NX_DHCP s_dhcp
Definition c6_cam_net.c:35
ra8_err_t c6_cam_net_up(ra8_c6link_t *link, const ra8_c6link_mac_t *mac, c6_cam_lease_t *out)
Bind NetX to an associated raw C6 link and obtain a DHCP lease.
Definition c6_cam_net.c:107
static CHAR s_socket_name[]
Definition c6_cam_net.c:40
static CHAR s_ip_name[]
Definition c6_cam_net.c:38
static UINT internal_c6_cam_net_create(void)
Create the static NetX packet pool and IP instance.
Definition c6_cam_net.c:70
static uint32_t internal_c6_cam_append_u32(char *out, uint32_t at, uint32_t cap, uint32_t value)
Append an unsigned decimal value to a fixed buffer.
Definition c6_cam_net.c:243
static uint32_t internal_c6_cam_append(char *out, uint32_t at, uint32_t cap, const char *text)
Append bounded text to a fixed HTTP header buffer.
Definition c6_cam_net.c:212
Shared contracts for ESP32-C6 browser camera servers.
void c6_cam_put_u32(uint32_t value)
Write one unsigned decimal value to SCI8.
ra8_err_t c6_cam_audio_snapshot_wav(const uint8_t **out_wav, uint32_t *out_bytes, uint32_t *out_timestamp_ms)
Snapshot the rolling microphone ring as mono PCM-S16LE WAV.
@ k_c6_cam_tcp_window
HTTP socket receive window.
@ k_c6_cam_net_ip_stack
NetX IP thread stack bytes.
@ k_c6_cam_request_max
Maximum parsed request bytes.
@ k_c6_cam_net_pool_bytes
NetX packet pool bytes.
@ k_c6_cam_dhcp_wait_ms
DHCP lease timeout.
@ k_c6_cam_net_arp_bytes
NetX ARP cache bytes.
@ k_c6_cam_net_pkt_payload
NetX packet payload bytes.
@ k_c6_cam_net_ip_prio
NetX IP thread priority.
@ k_c6_cam_listen_backlog
Single-client listen backlog.
@ k_c6_cam_http_chunk
Maximum HTTP send chunk.
@ k_c6_cam_stream_frames
Frames served before client reconnect.
@ k_c6_cam_http_port
Browser server TCP port.
ra8_err_t c6_cam_camera_capture_jpeg(const uint8_t **out_jpeg, uint32_t *out_bytes, c6_cam_frame_timing_t *out_timing)
void c6_cam_puts(const char *text)
Write a bounded NUL-terminated string to SCI8.
void c6_cam_camera_report_last_error(void)
Emit backend-specific diagnostics for the last capture failure.
static ULONG s_ip_stack[k_demo_ip_stack/sizeof(ULONG)]
Definition main.c:232
static NX_IP s_ip
Definition main.c:229
static ULONG s_arp_cache[k_demo_arp_cache/sizeof(ULONG)]
Definition main.c:233
NetX Duo network driver shim that bridges onto the ESP32-C6 link.
void nx_ether_driver_c6_bind(ra8_c6link_t *link)
Bind an open C6 link handle to the driver and arm its mutex.
void nx_ether_driver_c6(NX_IP_DRIVER *driver_req)
NetX Duo link driver entry point for the ESP32-C6 Wi-Fi station.
void nx_ether_driver_c6_set_mac(const uint8_t mac[6])
Tell the driver the station MAC address to stamp on outgoing frames.
Annotation-attribute framework macros for ra8-firmware.
#define RA8_INTERNAL
Marker that a function is intended to be static (file-local).
Error Code Definitions for ra8-firmware.
@ k_ra8_err_not_initialized
Module not initialized – _init() not yet called successfully.
Definition ra8_err.h:235
@ k_ra8_ok
Success – operation completed with all postconditions satisfied.
Definition ra8_err.h:119
@ k_ra8_err_null_ptr
Pointer was NULL where a valid pointer was required.
Definition ra8_err.h:478
@ k_ra8_err_timeout
Operation exceeded its time budget.
Definition ra8_err.h:188
ra8_err_codes_t ra8_err_t
Canonical error-return type used by every ra8-firmware API.
Definition ra8_err.h:546
const char * ra8_err_to_str(ra8_err_t err)
Human-readable string for an error code.
Definition ra8_log.c:737
static ra8_esp_hosted_pool_state_t s_pool
Singleton state of the memory and queue half.
int memcmp(const void *a, const void *b, size_t n)
Compare bytes in two memory areas.
unsigned int UINT
ThreadX-compatible unsigned int (host stub).
#define tx_thread_sleep
char CHAR
ThreadX-compatible CHAR (host stub).
unsigned long ULONG
ThreadX-compatible unsigned long (host stub).
Per-frame pipeline latency exposed in HTTP Server-Timing headers.
uint32_t convert_ms
Pixel conversion latency.
uint32_t timestamp_ms
Capture-start monotonic timestamp.
uint32_t capture_ms
Camera capture latency.
uint32_t encode_ms
Codec latency.
DHCP lease returned by the raw, bench-validated C6/NetX path.
bool bound
True after a lease is bound.
uint32_t dhcp_server
DHCP server IPv4 address.
uint32_t ip
Assigned IPv4 address.
uint32_t gateway
Assigned IPv4 default gateway.
uint32_t mask
Assigned IPv4 network mask.