Smart Camera ESP32
An AI-driven, real-time Sentry Turret platform leveraging asynchronous I/O and computer vision to deliver high-precision autonomous motion tracking on resource-constrained embedded hardware.
Loading...
Searching...
No Matches
websockets.h
1#pragma once
2
3// TODO: Change name to the header file and split to cpp file as well
4#include "base_detection_module.h"
5#include "camera.h"
6#include "motion_data.h"
7#include <cstdio>
8#include <cstring>
9#include <esp_http_server.h>
10#include <esp_log.h>
11
12static const char* TAG_WEBSOCKETS = "WEBSOCKETS";
13
15{
16public:
17 WebsocketHandler(httpd_handle_t server_handle, const char* uri)
18 : _server_handle(server_handle), _uri(uri), _is_registered(false)
19 {
20 }
21
22 virtual ~WebsocketHandler() { this->unregister_endpoint(); }
23
24 void register_endpoint()
25 {
26 ESP_LOGI(TAG_WEBSOCKETS, "WebSocket: Register endpoint %s", this->_uri);
27 httpd_uri_t uri_obj = {0};
28 uri_obj.uri = this->_uri;
29 uri_obj.method = HTTP_GET;
30 uri_obj.user_ctx = this;
31 uri_obj.handler = WebsocketHandler::websocket_handler;
32 uri_obj.is_websocket = true;
33
34 httpd_register_uri_handler(this->_server_handle, &uri_obj);
35 }
36
37 void unregister_endpoint()
38 {
39 ESP_LOGI(TAG_WEBSOCKETS, "WebSocket: Unregister endpoint %s", this->_uri);
40 httpd_unregister_uri_handler(this->_server_handle, this->_uri, HTTP_GET);
41 }
42
43 static esp_err_t websocket_handler(httpd_req_t* req)
44 {
45 WebsocketHandler* _this = reinterpret_cast<WebsocketHandler*>(req->user_ctx);
46
47 if (req->method == HTTP_GET)
48 {
49 // Initial WebSocket upgrade handshake
50 ESP_LOGI(TAG_WEBSOCKETS, "WebSocket: New connection established to %s", _this->_uri);
51 return ESP_OK;
52 }
53
54 // WebSocket frame handler - keep connection alive
55 httpd_ws_frame_t ws_pkt;
56 memset(&ws_pkt, 0, sizeof(httpd_ws_frame_t));
57
58 // get packet length
59 esp_err_t ret = httpd_ws_recv_frame(req, &ws_pkt, 0);
60
61 if (ret != ESP_OK)
62 {
63 ESP_LOGW(TAG_WEBSOCKETS, "WebSocket: Frame receive error: %s", esp_err_to_name(ret));
64 return ret;
65 }
66
67 if (ws_pkt.len <= 0)
68 {
69 return ESP_OK;
70 }
71
72 uint8_t* buf = (uint8_t*)malloc(ws_pkt.len);
73 if (!buf)
74 {
75 return ESP_OK;
76 }
77
78 ws_pkt.payload = buf;
79 ret = httpd_ws_recv_frame(req, &ws_pkt, ws_pkt.len);
80 if (ret != ESP_OK)
81 {
82 free(buf);
83 return ret;
84 }
85
86 httpd_ws_frame_t response_pkt = {0};
87 response_pkt.type = _this->get_frame_type();
88
89 void* resource = _this->handler(ws_pkt.payload, ws_pkt.len, response_pkt.payload, response_pkt.len);
90 if (resource == NULL)
91 {
92 ESP_LOGE(TAG_WEBSOCKETS, "Handler returned NULL for %s", _this->_uri);
93 return ESP_FAIL;
94 }
95
96 ESP_LOGD(TAG_WEBSOCKETS, "Sending response on %s, type=%d, len=%d", _this->_uri, response_pkt.type,
97 response_pkt.len);
98
99 ret = httpd_ws_send_frame(req, &response_pkt);
100 if (ret != ESP_OK)
101 {
102 free(buf);
103 return ret;
104 }
105
106 _this->release_resource(resource);
107
108 free(buf);
109 return ESP_OK;
110 }
111
112 // return resource which needs to be released
113 virtual void* handler(const uint8_t* recv_buf, size_t recv_len, uint8_t*& out_buf, size_t& out_len) = 0;
114
115 virtual void release_resource(void* resource) {} // release resources if needed
116
121 virtual httpd_ws_type_t get_frame_type() const { return HTTPD_WS_TYPE_TEXT; }
122
123private:
124 httpd_handle_t _server_handle;
125 const char* _uri;
126 bool _is_registered;
127};
128
130{
131public:
132 StreamWebsocketHandler(httpd_handle_t server_handle, const char* uri, Camera& camera)
133 : WebsocketHandler(server_handle, uri), _camera(camera)
134 {
135 }
136
137 virtual void* handler(const uint8_t* recv_buf, size_t recv_len, uint8_t*& out_buf, size_t& out_len)
138 {
139 const camera_buffer_t& fb = this->_camera.get_frame_buffer();
140
141 if (fb.length == 0)
142 {
143 ESP_LOGE(TAG_WEBSOCKETS, "Frame buffer is invalid");
144 return NULL;
145 }
146
147 out_buf = (uint8_t*)fb.buffer;
148 out_len = fb.length;
149
150 return (void*)-1;
151 }
152
153 virtual httpd_ws_type_t get_frame_type() const override { return HTTPD_WS_TYPE_BINARY; }
154
155 virtual void release_resource(void* resource)
156 {
157 // NOTE: this assums that the buffer is the first field of camera_fb_t
158 // this->_camera.release((camera_fb_t*)resource);
159 }
160
161private:
162 Camera& _camera;
163};
164
166{
167public:
168 CommandsWebSocketHandler(httpd_handle_t server_handle, const char* uri, BaseDetectionModule* detection = nullptr)
169 : WebsocketHandler(server_handle, uri), _detection_instance(detection)
170 {
171 }
172
173 virtual void* handler(const uint8_t* recv_buf, size_t recv_len, uint8_t*& out_buf, size_t& out_len)
174 {
175 // Allocate buffer for JSON response (256 bytes should be sufficient)
176 uint8_t* json_buf = (uint8_t*)malloc(256);
177 if (!json_buf)
178 {
179 ESP_LOGE(TAG_WEBSOCKETS, "Failed to allocate JSON buffer");
180 out_buf = (uint8_t*)"{}";
181 out_len = 2;
182 return NULL; // Return NULL to indicate error
183 }
184
185 // Serialize metrics to JSON format
186 if (_detection_instance != nullptr)
187 {
188 MotionData motion = _detection_instance->get_motion_data();
189
190 // Format: {"detected": bool, "x": int, "y": int, "width": int, "height": int, "pixels": int}
191 int len = snprintf((char*)json_buf, 256,
192 "{\"detected\":%d,\"x\":%d,\"y\":%d,\"width\":%d,\"height\":%d,\"pixels\":%d}",
193 motion.is_detected() ? 1 : 0, motion.get_centroid_x(), motion.get_centroid_y(),
194 motion.get_frame_width(), motion.get_frame_height(), motion.get_pixel_count());
195
196 out_buf = json_buf;
197 out_len = len;
198 ESP_LOGD(TAG_WEBSOCKETS, "Metrics: detected=%d, x=%d, y=%d, pixels=%d", motion.is_detected(),
199 motion.get_centroid_x(), motion.get_centroid_y(), motion.get_pixel_count());
200 } else
201 {
202 // Fallback if no detection module
203 int len = snprintf((char*)json_buf, 256,
204 "{\"detected\":0,\"x\":0,\"y\":0,\"width\":320,\"height\":240,\"pixels\":0}");
205 out_buf = json_buf;
206 out_len = len;
207 ESP_LOGW(TAG_WEBSOCKETS, "No detection module connected, sending fallback metrics");
208 }
209
210 return (void*)json_buf; // Return the allocated buffer for cleanup
211 }
212
213 virtual void release_resource(void* resource)
214 {
215 if (resource != nullptr)
216 {
217 free(resource);
218 }
219 }
220
221private:
222 BaseDetectionModule* _detection_instance;
223};
An abstract base class (Interface) that defines the contract for detection algorithms....
Definition base_detection_module.h:22
virtual MotionData get_motion_data() const
Get the latest motion data from the detection module.
Definition base_detection_module.h:44
Singleton-style manager for camera lifecycle and frame acquisition.
Definition camera.h:63
Definition websockets.h:166
Clean abstraction for motion detection results.
Definition motion_data.h:20
int get_centroid_x() const
Get motion centroid X coordinate.
Definition motion_data.h:73
int get_frame_height() const
Get the height of the frame processed.
Definition motion_data.h:91
int get_pixel_count() const
Get the volume of detected motion.
Definition motion_data.h:97
int get_centroid_y() const
Get motion centroid Y coordinate.
Definition motion_data.h:79
int get_frame_width() const
Get the width of the frame processed.
Definition motion_data.h:85
bool is_detected() const
Check if valid motion was detected in this snapshot.
Definition motion_data.h:67
Definition websockets.h:130
virtual httpd_ws_type_t get_frame_type() const override
Get the WebSocket frame type for this handler.
Definition websockets.h:153
Definition websockets.h:15
virtual httpd_ws_type_t get_frame_type() const
Get the WebSocket frame type for this handler.
Definition websockets.h:121
Definition camera.h:49