/** * 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=,total=,pending=,processed=\n * Example: STATS:ip=192.168.1.100,total=12345,pending=100,processed=12245\n */ #include #include #include #include #include #include #include #include #include #include #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; }