Files
can_sniffer/flip_monitor/can_monitor.c

328 lines
9.0 KiB
C

/**
* CAN Monitor for Flipper Zero
*
* Receives CAN sniffer statistics from RPI5 via UART.
* Displays: IP address, total frames, pending frames, processed frames.
*
* UART Configuration:
* - TX: GPIO 13 (pin 13)
* - RX: GPIO 14 (pin 14)
* - Baud: 115200
* - 8N1
*
* Protocol: Text-based
* Format: STATS:ip=<ip>,total=<n>,pending=<n>,processed=<n>\n
* Example: STATS:ip=192.168.1.100,total=12345,pending=100,processed=12245\n
*/
#include <furi.h>
#include <furi_hal.h>
#include <gui/gui.h>
#include <gui/view_port.h>
#include <notification/notification.h>
#include <notification/notification_messages.h>
#include <expansion/expansion.h>
#include <furi_hal_serial.h>
#include <stdlib.h>
#include <string.h>
#define TAG "CANMonitor"
// UART configuration
#define UART_CH FuriHalSerialIdUsart
#define UART_BAUD 115200
// Buffer sizes
#define RX_BUFFER_SIZE 256
#define IP_BUFFER_SIZE 32
// Statistics structure
typedef struct {
char ip_address[IP_BUFFER_SIZE];
uint32_t total_frames;
uint32_t pending_frames;
uint32_t processed_frames;
bool connected;
uint32_t last_update_tick;
} CanStats;
// Application context
typedef struct {
Gui* gui;
ViewPort* view_port;
FuriMessageQueue* event_queue;
FuriMutex* mutex;
FuriHalSerialHandle* serial_handle;
FuriStreamBuffer* rx_stream;
FuriThread* rx_thread;
CanStats stats;
bool running;
} CanMonitorApp;
// Event types
typedef enum {
EventTypeKey,
EventTypeStats,
} EventType;
typedef struct {
EventType type;
InputEvent input;
} CanMonitorEvent;
// Parse statistics from received line
static bool parse_stats_line(const char* line, CanStats* stats) {
// Format: STATS:ip=192.168.1.100,total=12345,pending=100,processed=12245
if(strncmp(line, "STATS:", 6) != 0) {
return false;
}
const char* data = line + 6;
// Parse ip=
const char* ip_start = strstr(data, "ip=");
if(ip_start) {
ip_start += 3;
const char* ip_end = strchr(ip_start, ',');
if(ip_end) {
size_t len = ip_end - ip_start;
if(len < IP_BUFFER_SIZE) {
strncpy(stats->ip_address, ip_start, len);
stats->ip_address[len] = '\0';
}
}
}
// Parse total=
const char* total_start = strstr(data, "total=");
if(total_start) {
stats->total_frames = strtoul(total_start + 6, NULL, 10);
}
// Parse pending=
const char* pending_start = strstr(data, "pending=");
if(pending_start) {
stats->pending_frames = strtoul(pending_start + 8, NULL, 10);
}
// Parse processed=
const char* processed_start = strstr(data, "processed=");
if(processed_start) {
stats->processed_frames = strtoul(processed_start + 10, NULL, 10);
}
stats->connected = true;
stats->last_update_tick = furi_get_tick();
return true;
}
// UART receive callback
static void uart_rx_callback(
FuriHalSerialHandle* handle,
FuriHalSerialRxEvent event,
void* context) {
CanMonitorApp* app = context;
UNUSED(handle);
if(event == FuriHalSerialRxEventData) {
uint8_t data = furi_hal_serial_async_rx(handle);
furi_stream_buffer_send(app->rx_stream, &data, 1, 0);
}
}
// UART receive thread
static int32_t uart_rx_thread(void* context) {
CanMonitorApp* app = context;
char rx_buffer[RX_BUFFER_SIZE];
size_t rx_index = 0;
while(app->running) {
uint8_t data;
size_t len = furi_stream_buffer_receive(app->rx_stream, &data, 1, 100);
if(len > 0) {
if(data == '\n' || data == '\r') {
if(rx_index > 0) {
rx_buffer[rx_index] = '\0';
// Parse the received line
furi_mutex_acquire(app->mutex, FuriWaitForever);
if(parse_stats_line(rx_buffer, &app->stats)) {
// Notify view to redraw
view_port_update(app->view_port);
}
furi_mutex_release(app->mutex);
rx_index = 0;
}
} else if(rx_index < RX_BUFFER_SIZE - 1) {
rx_buffer[rx_index++] = data;
}
}
// Check connection timeout (5 seconds)
furi_mutex_acquire(app->mutex, FuriWaitForever);
if(app->stats.connected &&
(furi_get_tick() - app->stats.last_update_tick) > 5000) {
app->stats.connected = false;
view_port_update(app->view_port);
}
furi_mutex_release(app->mutex);
}
return 0;
}
// Draw callback
static void draw_callback(Canvas* canvas, void* context) {
CanMonitorApp* app = context;
furi_mutex_acquire(app->mutex, FuriWaitForever);
canvas_clear(canvas);
canvas_set_font(canvas, FontPrimary);
// Title
canvas_draw_str_aligned(canvas, 64, 2, AlignCenter, AlignTop, "CAN Monitor");
canvas_set_font(canvas, FontSecondary);
// Connection status
if(app->stats.connected) {
canvas_draw_str(canvas, 0, 14, "Status: Connected");
} else {
canvas_draw_str(canvas, 0, 14, "Status: Waiting...");
}
// IP Address
char buf[64];
if(strlen(app->stats.ip_address) > 0) {
snprintf(buf, sizeof(buf), "IP: %s", app->stats.ip_address);
} else {
snprintf(buf, sizeof(buf), "IP: ---");
}
canvas_draw_str(canvas, 0, 26, buf);
// Total frames
snprintf(buf, sizeof(buf), "Total: %lu", (unsigned long)app->stats.total_frames);
canvas_draw_str(canvas, 0, 38, buf);
// Pending frames
snprintf(buf, sizeof(buf), "Pending: %lu", (unsigned long)app->stats.pending_frames);
canvas_draw_str(canvas, 0, 50, buf);
// Processed frames
snprintf(buf, sizeof(buf), "Processed: %lu", (unsigned long)app->stats.processed_frames);
canvas_draw_str(canvas, 0, 62, buf);
furi_mutex_release(app->mutex);
}
// Input callback
static void input_callback(InputEvent* input_event, void* context) {
CanMonitorApp* app = context;
CanMonitorEvent event = {.type = EventTypeKey, .input = *input_event};
furi_message_queue_put(app->event_queue, &event, FuriWaitForever);
}
// Initialize application
static CanMonitorApp* can_monitor_app_alloc(void) {
CanMonitorApp* app = malloc(sizeof(CanMonitorApp));
memset(app, 0, sizeof(CanMonitorApp));
app->running = true;
app->mutex = furi_mutex_alloc(FuriMutexTypeNormal);
app->event_queue = furi_message_queue_alloc(8, sizeof(CanMonitorEvent));
// Initialize stats
memset(&app->stats, 0, sizeof(CanStats));
strcpy(app->stats.ip_address, "");
// Disable expansion protocol to use UART
Expansion* expansion = furi_record_open(RECORD_EXPANSION);
expansion_disable(expansion);
furi_record_close(RECORD_EXPANSION);
// Initialize UART
app->rx_stream = furi_stream_buffer_alloc(RX_BUFFER_SIZE, 1);
app->serial_handle = furi_hal_serial_control_acquire(UART_CH);
furi_hal_serial_init(app->serial_handle, UART_BAUD);
furi_hal_serial_async_rx_start(app->serial_handle, uart_rx_callback, app, false);
// Start RX thread
app->rx_thread = furi_thread_alloc_ex("CANMonitorRx", 1024, uart_rx_thread, app);
furi_thread_start(app->rx_thread);
// Initialize GUI
app->gui = furi_record_open(RECORD_GUI);
app->view_port = view_port_alloc();
view_port_draw_callback_set(app->view_port, draw_callback, app);
view_port_input_callback_set(app->view_port, input_callback, app);
gui_add_view_port(app->gui, app->view_port, GuiLayerFullscreen);
return app;
}
// Free application
static void can_monitor_app_free(CanMonitorApp* app) {
// Stop RX thread
app->running = false;
furi_thread_join(app->rx_thread);
furi_thread_free(app->rx_thread);
// Deinitialize UART
furi_hal_serial_async_rx_stop(app->serial_handle);
furi_hal_serial_deinit(app->serial_handle);
furi_hal_serial_control_release(app->serial_handle);
furi_stream_buffer_free(app->rx_stream);
// Re-enable expansion protocol
Expansion* expansion = furi_record_open(RECORD_EXPANSION);
expansion_enable(expansion);
furi_record_close(RECORD_EXPANSION);
// Free GUI
view_port_enabled_set(app->view_port, false);
gui_remove_view_port(app->gui, app->view_port);
view_port_free(app->view_port);
furi_record_close(RECORD_GUI);
furi_message_queue_free(app->event_queue);
furi_mutex_free(app->mutex);
free(app);
}
// Main application entry point
int32_t can_monitor_app(void* p) {
UNUSED(p);
CanMonitorApp* app = can_monitor_app_alloc();
CanMonitorEvent event;
bool running = true;
// Main event loop
while(running) {
if(furi_message_queue_get(app->event_queue, &event, 100) == FuriStatusOk) {
if(event.type == EventTypeKey) {
if(event.input.type == InputTypeShort ||
event.input.type == InputTypeLong) {
if(event.input.key == InputKeyBack) {
running = false;
}
}
}
}
}
can_monitor_app_free(app);
return 0;
}