Update flipper and can_sniffer
This commit is contained in:
@@ -1,8 +1,15 @@
|
|||||||
"""
|
"""
|
||||||
Flipper Zero UART Handler.
|
Flipper Zero UART Handler with Handshake Protocol.
|
||||||
|
|
||||||
Sends CAN sniffer statistics to Flipper Zero via UART.
|
Waits for INIT command from Flipper Zero before sending statistics.
|
||||||
Provides real-time monitoring on Flipper Zero display.
|
Supports handshake protocol for secure connection establishment.
|
||||||
|
|
||||||
|
Protocol:
|
||||||
|
1. RPI5 waits in passive mode, listening for commands
|
||||||
|
2. Flipper sends: INIT:flipper\n
|
||||||
|
3. RPI5 responds: ACK:rpi5,ip=x.x.x.x\n
|
||||||
|
4. RPI5 starts sending: STATS:ip=...,total=...,pending=...,processed=...\n
|
||||||
|
5. Flipper sends: STOP:flipper\n to disconnect
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import socket
|
import socket
|
||||||
@@ -26,10 +33,8 @@ def get_ip_address() -> str:
|
|||||||
IP address string or "0.0.0.0" if unable to determine
|
IP address string or "0.0.0.0" if unable to determine
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
# Create a socket to determine the outgoing IP
|
|
||||||
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
|
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
|
||||||
s.settimeout(0.1)
|
s.settimeout(0.1)
|
||||||
# Connect to a public address (doesn't actually send data)
|
|
||||||
s.connect(("8.8.8.8", 80))
|
s.connect(("8.8.8.8", 80))
|
||||||
ip = s.getsockname()[0]
|
ip = s.getsockname()[0]
|
||||||
s.close()
|
s.close()
|
||||||
@@ -37,7 +42,6 @@ def get_ip_address() -> str:
|
|||||||
except Exception:
|
except Exception:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
# Fallback: try to get any non-localhost IP
|
|
||||||
try:
|
try:
|
||||||
hostname = socket.gethostname()
|
hostname = socket.gethostname()
|
||||||
ip = socket.gethostbyname(hostname)
|
ip = socket.gethostbyname(hostname)
|
||||||
@@ -51,15 +55,18 @@ def get_ip_address() -> str:
|
|||||||
|
|
||||||
class FlipperHandler(BaseHandler):
|
class FlipperHandler(BaseHandler):
|
||||||
"""
|
"""
|
||||||
Handler that sends statistics to Flipper Zero via UART.
|
Handler that communicates with Flipper Zero via UART.
|
||||||
|
|
||||||
|
Implements handshake protocol:
|
||||||
|
- Waits for INIT:flipper command
|
||||||
|
- Responds with ACK:rpi5,ip=x.x.x.x
|
||||||
|
- Sends STATS periodically while connected
|
||||||
|
- Stops on STOP:flipper command
|
||||||
|
|
||||||
UART Configuration:
|
UART Configuration:
|
||||||
- Device: /dev/ttyAMA0 (or configured device)
|
- Device: /dev/ttyAMA0 (or configured device)
|
||||||
- Baud: 115200
|
- Baud: 115200
|
||||||
- Format: 8N1
|
- Format: 8N1
|
||||||
|
|
||||||
Protocol:
|
|
||||||
Sends text line: STATS:ip=<ip>,total=<n>,pending=<n>,processed=<n>\n
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, enabled: Optional[bool] = None):
|
def __init__(self, enabled: Optional[bool] = None):
|
||||||
@@ -69,7 +76,6 @@ class FlipperHandler(BaseHandler):
|
|||||||
Args:
|
Args:
|
||||||
enabled: Whether handler is enabled. If None, reads from config.
|
enabled: Whether handler is enabled. If None, reads from config.
|
||||||
"""
|
"""
|
||||||
# Check config for enabled status
|
|
||||||
if enabled is None:
|
if enabled is None:
|
||||||
enabled = getattr(config, "flipper", None) is not None
|
enabled = getattr(config, "flipper", None) is not None
|
||||||
if enabled:
|
if enabled:
|
||||||
@@ -80,15 +86,18 @@ class FlipperHandler(BaseHandler):
|
|||||||
self.serial_port: Optional[Any] = None
|
self.serial_port: Optional[Any] = None
|
||||||
self.device = "/dev/ttyAMA0"
|
self.device = "/dev/ttyAMA0"
|
||||||
self.baudrate = 115200
|
self.baudrate = 115200
|
||||||
self.send_interval = 1.0 # Send stats every 1 second
|
self.send_interval = 1.0
|
||||||
|
|
||||||
# Load config if available
|
|
||||||
if hasattr(config, "flipper"):
|
if hasattr(config, "flipper"):
|
||||||
flipper_cfg = config.flipper
|
flipper_cfg = config.flipper
|
||||||
self.device = getattr(flipper_cfg, "device", self.device)
|
self.device = getattr(flipper_cfg, "device", self.device)
|
||||||
self.baudrate = getattr(flipper_cfg, "baudrate", self.baudrate)
|
self.baudrate = getattr(flipper_cfg, "baudrate", self.baudrate)
|
||||||
self.send_interval = getattr(flipper_cfg, "send_interval", self.send_interval)
|
self.send_interval = getattr(flipper_cfg, "send_interval", self.send_interval)
|
||||||
|
|
||||||
|
# Connection state
|
||||||
|
self._connected = False
|
||||||
|
self._running = False
|
||||||
|
|
||||||
# Statistics
|
# Statistics
|
||||||
self._stats_lock = threading.Lock()
|
self._stats_lock = threading.Lock()
|
||||||
self._total_frames = 0
|
self._total_frames = 0
|
||||||
@@ -97,17 +106,16 @@ class FlipperHandler(BaseHandler):
|
|||||||
self._sent_count = 0
|
self._sent_count = 0
|
||||||
self._error_count = 0
|
self._error_count = 0
|
||||||
|
|
||||||
# Background sender thread
|
# Threads
|
||||||
self._sender_thread: Optional[threading.Thread] = None
|
self._rx_thread: Optional[threading.Thread] = None
|
||||||
self._running = False
|
self._tx_thread: Optional[threading.Thread] = None
|
||||||
|
|
||||||
# IP address cache
|
# IP address
|
||||||
self._ip_address = "0.0.0.0"
|
self._ip_address = "0.0.0.0"
|
||||||
self._last_ip_check = 0
|
|
||||||
|
|
||||||
def initialize(self) -> bool:
|
def initialize(self) -> bool:
|
||||||
"""
|
"""
|
||||||
Initialize UART connection to Flipper Zero.
|
Initialize UART connection.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
True if initialization successful
|
True if initialization successful
|
||||||
@@ -124,13 +132,12 @@ class FlipperHandler(BaseHandler):
|
|||||||
timeout=0.1,
|
timeout=0.1,
|
||||||
)
|
)
|
||||||
|
|
||||||
# Get initial IP address
|
|
||||||
self._ip_address = get_ip_address()
|
self._ip_address = get_ip_address()
|
||||||
self._last_ip_check = time.time()
|
|
||||||
|
|
||||||
self._initialized = True
|
self._initialized = True
|
||||||
|
|
||||||
self.logger.info(
|
self.logger.info(
|
||||||
f"Flipper handler initialized on {self.device} @ {self.baudrate} baud"
|
f"Flipper handler initialized on {self.device} @ {self.baudrate} baud, "
|
||||||
|
f"IP: {self._ip_address}"
|
||||||
)
|
)
|
||||||
return True
|
return True
|
||||||
|
|
||||||
@@ -142,71 +149,145 @@ class FlipperHandler(BaseHandler):
|
|||||||
return False
|
return False
|
||||||
|
|
||||||
def start(self) -> None:
|
def start(self) -> None:
|
||||||
"""Start the background sender thread."""
|
"""Start the RX listener and TX sender threads."""
|
||||||
if self._running:
|
if self._running:
|
||||||
return
|
return
|
||||||
|
|
||||||
self._running = True
|
self._running = True
|
||||||
self._sender_thread = threading.Thread(
|
self._connected = False
|
||||||
target=self._sender_loop, name="FlipperSender", daemon=True
|
|
||||||
)
|
# Start RX thread (listens for commands)
|
||||||
self._sender_thread.start()
|
self._rx_thread = threading.Thread(
|
||||||
self.logger.info("Flipper sender thread started")
|
target=self._rx_loop, name="FlipperRX", daemon=True
|
||||||
|
)
|
||||||
|
self._rx_thread.start()
|
||||||
|
|
||||||
|
# Start TX thread (sends stats when connected)
|
||||||
|
self._tx_thread = threading.Thread(
|
||||||
|
target=self._tx_loop, name="FlipperTX", daemon=True
|
||||||
|
)
|
||||||
|
self._tx_thread.start()
|
||||||
|
|
||||||
|
self.logger.info("Flipper handler started, waiting for connection...")
|
||||||
|
|
||||||
|
def _rx_loop(self) -> None:
|
||||||
|
"""Receive loop - listens for commands from Flipper."""
|
||||||
|
buffer = ""
|
||||||
|
|
||||||
def _sender_loop(self) -> None:
|
|
||||||
"""Background loop that sends stats periodically."""
|
|
||||||
while self._running:
|
while self._running:
|
||||||
try:
|
try:
|
||||||
self._send_stats()
|
if not self.serial_port or not self.serial_port.is_open:
|
||||||
|
time.sleep(0.1)
|
||||||
|
continue
|
||||||
|
|
||||||
|
# Read available data
|
||||||
|
if self.serial_port.in_waiting > 0:
|
||||||
|
data = self.serial_port.read(self.serial_port.in_waiting)
|
||||||
|
buffer += data.decode("utf-8", errors="ignore")
|
||||||
|
|
||||||
|
# Process complete lines
|
||||||
|
while "\n" in buffer:
|
||||||
|
line, buffer = buffer.split("\n", 1)
|
||||||
|
line = line.strip()
|
||||||
|
if line:
|
||||||
|
self._process_command(line)
|
||||||
|
else:
|
||||||
|
time.sleep(0.05)
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
self.logger.debug(f"Error sending stats to Flipper: {e}")
|
self.logger.debug(f"RX error: {e}")
|
||||||
with self._stats_lock:
|
time.sleep(0.1)
|
||||||
self._error_count += 1
|
|
||||||
|
def _process_command(self, command: str) -> None:
|
||||||
|
"""
|
||||||
|
Process received command from Flipper.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
command: Received command string
|
||||||
|
"""
|
||||||
|
self.logger.info(f"Received command: {command}")
|
||||||
|
|
||||||
|
if command.startswith("INIT:"):
|
||||||
|
# Handshake initiation
|
||||||
|
client_id = command[5:].strip()
|
||||||
|
self.logger.info(f"Handshake request from: {client_id}")
|
||||||
|
|
||||||
|
# Send ACK with IP address
|
||||||
|
self._ip_address = get_ip_address()
|
||||||
|
ack_msg = f"ACK:rpi5,ip={self._ip_address}\n"
|
||||||
|
self._send_raw(ack_msg)
|
||||||
|
|
||||||
|
self._connected = True
|
||||||
|
self.logger.info(f"Connected to Flipper, IP: {self._ip_address}")
|
||||||
|
|
||||||
|
elif command.startswith("STOP:"):
|
||||||
|
# Disconnect request
|
||||||
|
client_id = command[5:].strip()
|
||||||
|
self.logger.info(f"Disconnect request from: {client_id}")
|
||||||
|
self._connected = False
|
||||||
|
self.logger.info("Disconnected from Flipper")
|
||||||
|
|
||||||
|
def _tx_loop(self) -> None:
|
||||||
|
"""Transmit loop - sends stats when connected."""
|
||||||
|
while self._running:
|
||||||
|
try:
|
||||||
|
if self._connected:
|
||||||
|
self._send_stats()
|
||||||
|
|
||||||
time.sleep(self.send_interval)
|
time.sleep(self.send_interval)
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
self.logger.debug(f"TX error: {e}")
|
||||||
|
with self._stats_lock:
|
||||||
|
self._error_count += 1
|
||||||
|
|
||||||
|
def _send_raw(self, message: str) -> bool:
|
||||||
|
"""
|
||||||
|
Send raw message via UART.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
message: Message to send
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
True if sent successfully
|
||||||
|
"""
|
||||||
|
if not self.serial_port or not self.serial_port.is_open:
|
||||||
|
return False
|
||||||
|
|
||||||
|
try:
|
||||||
|
self.serial_port.write(message.encode("utf-8"))
|
||||||
|
self.serial_port.flush()
|
||||||
|
self.logger.debug(f"TX: {message.strip()}")
|
||||||
|
return True
|
||||||
|
except Exception as e:
|
||||||
|
self.logger.debug(f"Send error: {e}")
|
||||||
|
return False
|
||||||
|
|
||||||
def _send_stats(self) -> None:
|
def _send_stats(self) -> None:
|
||||||
"""Send current statistics to Flipper Zero."""
|
"""Send current statistics to Flipper Zero."""
|
||||||
if not self.serial_port or not self.serial_port.is_open:
|
if not self._connected:
|
||||||
return
|
return
|
||||||
|
|
||||||
# Refresh IP address every 60 seconds
|
|
||||||
current_time = time.time()
|
|
||||||
if current_time - self._last_ip_check > 60:
|
|
||||||
self._ip_address = get_ip_address()
|
|
||||||
self._last_ip_check = current_time
|
|
||||||
|
|
||||||
with self._stats_lock:
|
with self._stats_lock:
|
||||||
total = self._total_frames
|
total = self._total_frames
|
||||||
pending = self._pending_frames
|
pending = self._pending_frames
|
||||||
processed = self._processed_frames
|
processed = self._processed_frames
|
||||||
|
|
||||||
# Build stats message
|
|
||||||
message = f"STATS:ip={self._ip_address},total={total},pending={pending},processed={processed}\n"
|
message = f"STATS:ip={self._ip_address},total={total},pending={pending},processed={processed}\n"
|
||||||
|
|
||||||
try:
|
if self._send_raw(message):
|
||||||
self.serial_port.write(message.encode("utf-8"))
|
|
||||||
self.serial_port.flush()
|
|
||||||
|
|
||||||
with self._stats_lock:
|
with self._stats_lock:
|
||||||
self._sent_count += 1
|
self._sent_count += 1
|
||||||
|
|
||||||
except Exception as e:
|
|
||||||
self.logger.debug(f"UART write error: {e}")
|
|
||||||
with self._stats_lock:
|
|
||||||
self._error_count += 1
|
|
||||||
|
|
||||||
def handle(self, frame: CANFrame) -> bool:
|
def handle(self, frame: CANFrame) -> bool:
|
||||||
"""
|
"""
|
||||||
Handle a single CAN frame.
|
Handle a single CAN frame.
|
||||||
|
|
||||||
Updates frame counters for statistics.
|
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
frame: CANFrame to handle
|
frame: CANFrame to handle
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
True (always succeeds, just updates counters)
|
True (always succeeds)
|
||||||
"""
|
"""
|
||||||
with self._stats_lock:
|
with self._stats_lock:
|
||||||
self._total_frames += 1
|
self._total_frames += 1
|
||||||
@@ -221,14 +302,12 @@ class FlipperHandler(BaseHandler):
|
|||||||
frames: List of CANFrame objects
|
frames: List of CANFrame objects
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
Number of frames processed (all of them)
|
Number of frames processed
|
||||||
"""
|
"""
|
||||||
count = len(frames)
|
count = len(frames)
|
||||||
with self._stats_lock:
|
with self._stats_lock:
|
||||||
self._total_frames += count
|
self._total_frames += count
|
||||||
# After batch processing, frames are processed
|
|
||||||
self._processed_frames += count
|
self._processed_frames += count
|
||||||
# Reduce pending by batch count
|
|
||||||
self._pending_frames = max(0, self._pending_frames - count)
|
self._pending_frames = max(0, self._pending_frames - count)
|
||||||
return count
|
return count
|
||||||
|
|
||||||
@@ -236,8 +315,6 @@ class FlipperHandler(BaseHandler):
|
|||||||
"""
|
"""
|
||||||
Update pending frame count.
|
Update pending frame count.
|
||||||
|
|
||||||
Called externally to sync with actual queue size.
|
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
pending_count: Current number of pending frames
|
pending_count: Current number of pending frames
|
||||||
"""
|
"""
|
||||||
@@ -245,26 +322,30 @@ class FlipperHandler(BaseHandler):
|
|||||||
self._pending_frames = pending_count
|
self._pending_frames = pending_count
|
||||||
|
|
||||||
def flush(self) -> None:
|
def flush(self) -> None:
|
||||||
"""Flush - send immediate stats update."""
|
"""Flush - send immediate stats if connected."""
|
||||||
|
if self._connected:
|
||||||
try:
|
try:
|
||||||
self._send_stats()
|
self._send_stats()
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
self.logger.debug(f"Error in flush: {e}")
|
self.logger.debug(f"Flush error: {e}")
|
||||||
|
|
||||||
def shutdown(self) -> None:
|
def shutdown(self) -> None:
|
||||||
"""Shutdown the handler."""
|
"""Shutdown the handler."""
|
||||||
self.logger.info("Shutting down Flipper handler...")
|
self.logger.info("Shutting down Flipper handler...")
|
||||||
|
|
||||||
self._running = False
|
self._running = False
|
||||||
|
self._connected = False
|
||||||
|
|
||||||
if self._sender_thread and self._sender_thread.is_alive():
|
# Wait for threads
|
||||||
self._sender_thread.join(timeout=2.0)
|
if self._rx_thread and self._rx_thread.is_alive():
|
||||||
|
self._rx_thread.join(timeout=2.0)
|
||||||
|
|
||||||
|
if self._tx_thread and self._tx_thread.is_alive():
|
||||||
|
self._tx_thread.join(timeout=2.0)
|
||||||
|
|
||||||
|
# Close serial port
|
||||||
if self.serial_port and self.serial_port.is_open:
|
if self.serial_port and self.serial_port.is_open:
|
||||||
try:
|
try:
|
||||||
# Send final "disconnected" message
|
|
||||||
self.serial_port.write(b"STATS:ip=---,total=0,pending=0,processed=0\n")
|
|
||||||
self.serial_port.flush()
|
|
||||||
self.serial_port.close()
|
self.serial_port.close()
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
self.logger.debug(f"Error closing serial port: {e}")
|
self.logger.debug(f"Error closing serial port: {e}")
|
||||||
@@ -288,6 +369,10 @@ class FlipperHandler(BaseHandler):
|
|||||||
"error_count": self._error_count,
|
"error_count": self._error_count,
|
||||||
"device": self.device,
|
"device": self.device,
|
||||||
"baudrate": self.baudrate,
|
"baudrate": self.baudrate,
|
||||||
"connected": self.serial_port.is_open if self.serial_port else False,
|
"connected": self._connected,
|
||||||
"ip_address": self._ip_address,
|
"ip_address": self._ip_address,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
def is_connected(self) -> bool:
|
||||||
|
"""Check if Flipper is connected."""
|
||||||
|
return self._connected
|
||||||
|
|||||||
@@ -1,206 +1,325 @@
|
|||||||
/**
|
/**
|
||||||
* CAN Monitor for Flipper Zero
|
* CAN Monitor for Flipper Zero
|
||||||
*
|
*
|
||||||
* Receives CAN sniffer statistics from RPI5 via UART.
|
* Multi-page application with handshake protocol.
|
||||||
* Displays: IP address, total frames, pending frames, processed frames.
|
|
||||||
*
|
*
|
||||||
* UART Configuration:
|
* Handshake:
|
||||||
* - TX: GPIO 13 (pin 13)
|
* 1. User presses OK on welcome screen
|
||||||
* - RX: GPIO 14 (pin 14)
|
* 2. Flipper sends: INIT:flipper\n
|
||||||
* - Baud: 115200
|
* 3. RPI5 responds: ACK:rpi5,ip=x.x.x.x\n
|
||||||
* - 8N1
|
* 4. Flipper allows navigation to stats page
|
||||||
|
* 5. RPI5 starts sending: STATS:ip=...,total=...,pending=...,processed=...\n
|
||||||
*
|
*
|
||||||
* Protocol: Text-based
|
* Wiring:
|
||||||
* Format: STATS:ip=<ip>,total=<n>,pending=<n>,processed=<n>\n
|
* RPI5 TX (GPIO14, Pin 8) -> Flipper RX (Pin 14)
|
||||||
* Example: STATS:ip=192.168.1.100,total=12345,pending=100,processed=12245\n
|
* RPI5 RX (GPIO15, Pin 10) <- Flipper TX (Pin 13)
|
||||||
|
* RPI5 GND (Pin 6) -> Flipper GND (Pin 8/11/18)
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include <furi.h>
|
#include <furi.h>
|
||||||
#include <furi_hal.h>
|
#include <furi_hal.h>
|
||||||
#include <gui/gui.h>
|
#include <gui/gui.h>
|
||||||
#include <gui/view_port.h>
|
#include <gui/view_port.h>
|
||||||
#include <notification/notification.h>
|
|
||||||
#include <notification/notification_messages.h>
|
|
||||||
|
|
||||||
#include <expansion/expansion.h>
|
#include <expansion/expansion.h>
|
||||||
#include <furi_hal_serial.h>
|
|
||||||
|
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
|
|
||||||
#define TAG "CANMonitor"
|
#define TAG "CANMonitor"
|
||||||
|
|
||||||
// UART configuration
|
|
||||||
#define UART_CH FuriHalSerialIdUsart
|
|
||||||
#define UART_BAUD 115200
|
#define UART_BAUD 115200
|
||||||
|
|
||||||
// Buffer sizes
|
|
||||||
#define RX_BUFFER_SIZE 256
|
#define RX_BUFFER_SIZE 256
|
||||||
#define IP_BUFFER_SIZE 32
|
#define IP_BUFFER_SIZE 32
|
||||||
|
|
||||||
// Statistics structure
|
// Connection state
|
||||||
|
typedef enum {
|
||||||
|
StateDisconnected,
|
||||||
|
StateConnecting,
|
||||||
|
StateConnected,
|
||||||
|
} ConnectionState;
|
||||||
|
|
||||||
|
// Pages
|
||||||
|
typedef enum {
|
||||||
|
PageWelcome,
|
||||||
|
PageStats,
|
||||||
|
} AppPage;
|
||||||
|
|
||||||
|
// Statistics
|
||||||
typedef struct {
|
typedef struct {
|
||||||
char ip_address[IP_BUFFER_SIZE];
|
char ip_address[IP_BUFFER_SIZE];
|
||||||
uint32_t total_frames;
|
uint32_t total_frames;
|
||||||
uint32_t pending_frames;
|
uint32_t pending_frames;
|
||||||
uint32_t processed_frames;
|
uint32_t processed_frames;
|
||||||
bool connected;
|
bool data_received;
|
||||||
uint32_t last_update_tick;
|
uint32_t last_update_tick;
|
||||||
} CanStats;
|
} CanStats;
|
||||||
|
|
||||||
// Application context
|
// App context
|
||||||
typedef struct {
|
typedef struct {
|
||||||
Gui* gui;
|
Gui* gui;
|
||||||
ViewPort* view_port;
|
ViewPort* view_port;
|
||||||
FuriMessageQueue* event_queue;
|
|
||||||
FuriMutex* mutex;
|
FuriMutex* mutex;
|
||||||
|
|
||||||
FuriHalSerialHandle* serial_handle;
|
FuriHalSerialHandle* serial;
|
||||||
FuriStreamBuffer* rx_stream;
|
FuriStreamBuffer* rx_stream;
|
||||||
FuriThread* rx_thread;
|
FuriThread* worker_thread;
|
||||||
|
|
||||||
CanStats stats;
|
CanStats stats;
|
||||||
bool running;
|
AppPage current_page;
|
||||||
|
ConnectionState conn_state;
|
||||||
|
volatile bool running;
|
||||||
|
volatile bool send_init;
|
||||||
} CanMonitorApp;
|
} CanMonitorApp;
|
||||||
|
|
||||||
// Event types
|
// Send data via UART
|
||||||
typedef enum {
|
static void uart_send(CanMonitorApp* app, const char* data) {
|
||||||
EventTypeKey,
|
if(app->serial) {
|
||||||
EventTypeStats,
|
furi_hal_serial_tx(app->serial, (uint8_t*)data, strlen(data));
|
||||||
} EventType;
|
FURI_LOG_I(TAG, "TX: %s", data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
typedef struct {
|
// Parse ACK response: ACK:rpi5,ip=x.x.x.x
|
||||||
EventType type;
|
static bool parse_ack(const char* line, CanStats* stats) {
|
||||||
InputEvent input;
|
if(strncmp(line, "ACK:", 4) != 0) {
|
||||||
} CanMonitorEvent;
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
// Parse statistics from received line
|
const char* p = line + 4;
|
||||||
static bool parse_stats_line(const char* line, CanStats* stats) {
|
|
||||||
// Format: STATS:ip=192.168.1.100,total=12345,pending=100,processed=12245
|
// Check for rpi5 identifier
|
||||||
|
if(strncmp(p, "rpi5", 4) != 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse ip=
|
||||||
|
const char* ip = strstr(p, "ip=");
|
||||||
|
if(ip) {
|
||||||
|
ip += 3;
|
||||||
|
// Find end (comma, newline, or end of string)
|
||||||
|
size_t len = 0;
|
||||||
|
while(ip[len] && ip[len] != ',' && ip[len] != '\n' && ip[len] != '\r') {
|
||||||
|
len++;
|
||||||
|
}
|
||||||
|
if(len > 0 && len < IP_BUFFER_SIZE) {
|
||||||
|
memcpy(stats->ip_address, ip, len);
|
||||||
|
stats->ip_address[len] = '\0';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse STATS: STATS:ip=x.x.x.x,total=N,pending=N,processed=N
|
||||||
|
static bool parse_stats(const char* line, CanStats* stats) {
|
||||||
if(strncmp(line, "STATS:", 6) != 0) {
|
if(strncmp(line, "STATS:", 6) != 0) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
const char* data = line + 6;
|
const char* p = line + 6;
|
||||||
|
|
||||||
// Parse ip=
|
// Parse ip=
|
||||||
const char* ip_start = strstr(data, "ip=");
|
const char* ip = strstr(p, "ip=");
|
||||||
if(ip_start) {
|
if(ip) {
|
||||||
ip_start += 3;
|
ip += 3;
|
||||||
const char* ip_end = strchr(ip_start, ',');
|
const char* end = strchr(ip, ',');
|
||||||
if(ip_end) {
|
if(end && (size_t)(end - ip) < IP_BUFFER_SIZE) {
|
||||||
size_t len = ip_end - ip_start;
|
size_t len = end - ip;
|
||||||
if(len < IP_BUFFER_SIZE) {
|
memcpy(stats->ip_address, ip, len);
|
||||||
strncpy(stats->ip_address, ip_start, len);
|
|
||||||
stats->ip_address[len] = '\0';
|
stats->ip_address[len] = '\0';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// Parse total=
|
// Parse total=
|
||||||
const char* total_start = strstr(data, "total=");
|
const char* total = strstr(p, "total=");
|
||||||
if(total_start) {
|
if(total) {
|
||||||
stats->total_frames = strtoul(total_start + 6, NULL, 10);
|
stats->total_frames = strtoul(total + 6, NULL, 10);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Parse pending=
|
// Parse pending=
|
||||||
const char* pending_start = strstr(data, "pending=");
|
const char* pending = strstr(p, "pending=");
|
||||||
if(pending_start) {
|
if(pending) {
|
||||||
stats->pending_frames = strtoul(pending_start + 8, NULL, 10);
|
stats->pending_frames = strtoul(pending + 8, NULL, 10);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Parse processed=
|
// Parse processed=
|
||||||
const char* processed_start = strstr(data, "processed=");
|
const char* processed = strstr(p, "processed=");
|
||||||
if(processed_start) {
|
if(processed) {
|
||||||
stats->processed_frames = strtoul(processed_start + 10, NULL, 10);
|
stats->processed_frames = strtoul(processed + 10, NULL, 10);
|
||||||
}
|
}
|
||||||
|
|
||||||
stats->connected = true;
|
stats->data_received = true;
|
||||||
stats->last_update_tick = furi_get_tick();
|
stats->last_update_tick = furi_get_tick();
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// UART receive callback
|
// Process received line
|
||||||
static void uart_rx_callback(
|
static void process_line(CanMonitorApp* app, const char* line) {
|
||||||
FuriHalSerialHandle* handle,
|
FURI_LOG_I(TAG, "RX: %s", line);
|
||||||
FuriHalSerialRxEvent event,
|
|
||||||
void* context) {
|
furi_mutex_acquire(app->mutex, FuriWaitForever);
|
||||||
CanMonitorApp* app = context;
|
|
||||||
UNUSED(handle);
|
if(app->conn_state == StateConnecting) {
|
||||||
|
// Waiting for ACK
|
||||||
|
if(parse_ack(line, &app->stats)) {
|
||||||
|
FURI_LOG_I(TAG, "ACK received, IP: %s", app->stats.ip_address);
|
||||||
|
app->conn_state = StateConnected;
|
||||||
|
}
|
||||||
|
} else if(app->conn_state == StateConnected) {
|
||||||
|
// Parse stats
|
||||||
|
parse_stats(line, &app->stats);
|
||||||
|
}
|
||||||
|
|
||||||
|
furi_mutex_release(app->mutex);
|
||||||
|
view_port_update(app->view_port);
|
||||||
|
}
|
||||||
|
|
||||||
|
// UART RX callback
|
||||||
|
static void uart_callback(FuriHalSerialHandle* handle, FuriHalSerialRxEvent event, void* ctx) {
|
||||||
|
CanMonitorApp* app = ctx;
|
||||||
|
|
||||||
if(event == FuriHalSerialRxEventData) {
|
if(event == FuriHalSerialRxEventData) {
|
||||||
uint8_t data = furi_hal_serial_async_rx(handle);
|
uint8_t byte = furi_hal_serial_async_rx(handle);
|
||||||
furi_stream_buffer_send(app->rx_stream, &data, 1, 0);
|
furi_stream_buffer_send(app->rx_stream, &byte, 1, 0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// UART receive thread
|
// Worker thread
|
||||||
static int32_t uart_rx_thread(void* context) {
|
static int32_t worker_thread(void* ctx) {
|
||||||
CanMonitorApp* app = context;
|
CanMonitorApp* app = ctx;
|
||||||
char rx_buffer[RX_BUFFER_SIZE];
|
char buffer[RX_BUFFER_SIZE];
|
||||||
size_t rx_index = 0;
|
size_t idx = 0;
|
||||||
|
uint32_t last_rx_tick = 0;
|
||||||
|
|
||||||
|
FURI_LOG_I(TAG, "Worker started");
|
||||||
|
|
||||||
while(app->running) {
|
while(app->running) {
|
||||||
uint8_t data;
|
// Check if we need to send INIT
|
||||||
size_t len = furi_stream_buffer_receive(app->rx_stream, &data, 1, 100);
|
if(app->send_init) {
|
||||||
|
app->send_init = false;
|
||||||
|
uart_send(app, "INIT:flipper\n");
|
||||||
|
|
||||||
|
furi_mutex_acquire(app->mutex, FuriWaitForever);
|
||||||
|
app->conn_state = StateConnecting;
|
||||||
|
furi_mutex_release(app->mutex);
|
||||||
|
view_port_update(app->view_port);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Receive data
|
||||||
|
uint8_t byte;
|
||||||
|
size_t len = furi_stream_buffer_receive(app->rx_stream, &byte, 1, 50);
|
||||||
|
|
||||||
if(len > 0) {
|
if(len > 0) {
|
||||||
if(data == '\n' || data == '\r') {
|
last_rx_tick = furi_get_tick();
|
||||||
if(rx_index > 0) {
|
|
||||||
rx_buffer[rx_index] = '\0';
|
|
||||||
|
|
||||||
// Parse the received line
|
if(byte == '\n' || byte == '\r') {
|
||||||
|
if(idx > 0) {
|
||||||
|
buffer[idx] = '\0';
|
||||||
|
process_line(app, buffer);
|
||||||
|
idx = 0;
|
||||||
|
}
|
||||||
|
} else if(idx < RX_BUFFER_SIZE - 1) {
|
||||||
|
buffer[idx++] = byte;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Timeout: parse partial data after 500ms
|
||||||
|
if(idx > 0 && (furi_get_tick() - last_rx_tick) > 500) {
|
||||||
|
buffer[idx] = '\0';
|
||||||
|
process_line(app, buffer);
|
||||||
|
idx = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check data timeout (5 sec) - only when connected
|
||||||
furi_mutex_acquire(app->mutex, FuriWaitForever);
|
furi_mutex_acquire(app->mutex, FuriWaitForever);
|
||||||
if(parse_stats_line(rx_buffer, &app->stats)) {
|
if(app->conn_state == StateConnected && app->stats.data_received) {
|
||||||
// Notify view to redraw
|
if((furi_get_tick() - app->stats.last_update_tick) > 5000) {
|
||||||
|
app->stats.data_received = false;
|
||||||
view_port_update(app->view_port);
|
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);
|
furi_mutex_release(app->mutex);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
FURI_LOG_I(TAG, "Worker stopped");
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Draw callback
|
// Draw welcome page
|
||||||
static void draw_callback(Canvas* canvas, void* context) {
|
static void draw_welcome(Canvas* canvas, CanMonitorApp* app) {
|
||||||
CanMonitorApp* app = context;
|
|
||||||
|
|
||||||
furi_mutex_acquire(app->mutex, FuriWaitForever);
|
|
||||||
|
|
||||||
canvas_clear(canvas);
|
canvas_clear(canvas);
|
||||||
canvas_set_font(canvas, FontPrimary);
|
|
||||||
|
|
||||||
// Title
|
// Title
|
||||||
canvas_draw_str_aligned(canvas, 64, 2, AlignCenter, AlignTop, "CAN Monitor");
|
canvas_set_font(canvas, FontPrimary);
|
||||||
|
canvas_draw_str_aligned(canvas, 64, 8, AlignCenter, AlignCenter, "CAN Monitor");
|
||||||
|
|
||||||
canvas_set_font(canvas, FontSecondary);
|
canvas_set_font(canvas, FontSecondary);
|
||||||
|
|
||||||
// Connection status
|
// Subtitle
|
||||||
if(app->stats.connected) {
|
canvas_draw_str_aligned(canvas, 64, 22, AlignCenter, AlignCenter, "RPI5 CAN Sniffer");
|
||||||
canvas_draw_str(canvas, 0, 14, "Status: Connected");
|
|
||||||
} else {
|
// Status based on connection state
|
||||||
canvas_draw_str(canvas, 0, 14, "Status: Waiting...");
|
furi_mutex_acquire(app->mutex, FuriWaitForever);
|
||||||
|
ConnectionState state = app->conn_state;
|
||||||
|
char ip_buf[IP_BUFFER_SIZE];
|
||||||
|
strncpy(ip_buf, app->stats.ip_address, IP_BUFFER_SIZE);
|
||||||
|
furi_mutex_release(app->mutex);
|
||||||
|
|
||||||
|
switch(state) {
|
||||||
|
case StateDisconnected:
|
||||||
|
canvas_draw_str_aligned(canvas, 64, 38, AlignCenter, AlignCenter, "Press OK to connect");
|
||||||
|
canvas_draw_str_aligned(canvas, 64, 50, AlignCenter, AlignCenter, "[OK]");
|
||||||
|
break;
|
||||||
|
|
||||||
|
case StateConnecting:
|
||||||
|
canvas_draw_str_aligned(canvas, 64, 38, AlignCenter, AlignCenter, "Connecting...");
|
||||||
|
canvas_draw_str_aligned(canvas, 64, 50, AlignCenter, AlignCenter, "Waiting for RPI5");
|
||||||
|
break;
|
||||||
|
|
||||||
|
case StateConnected:
|
||||||
|
canvas_draw_str_aligned(canvas, 64, 34, AlignCenter, AlignCenter, "Connected!");
|
||||||
|
if(strlen(ip_buf) > 0) {
|
||||||
|
char buf[48];
|
||||||
|
snprintf(buf, sizeof(buf), "RPI5: %s", ip_buf);
|
||||||
|
canvas_draw_str_aligned(canvas, 64, 46, AlignCenter, AlignCenter, buf);
|
||||||
|
}
|
||||||
|
canvas_draw_str_aligned(canvas, 64, 58, AlignCenter, AlignCenter, "Press RIGHT >");
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// IP Address
|
// Draw stats page
|
||||||
|
static void draw_stats(Canvas* canvas, CanMonitorApp* app) {
|
||||||
char buf[64];
|
char buf[64];
|
||||||
|
|
||||||
|
canvas_clear(canvas);
|
||||||
|
|
||||||
|
// Header
|
||||||
|
canvas_set_font(canvas, FontPrimary);
|
||||||
|
canvas_draw_str(canvas, 10, 10, "CAN Monitor");
|
||||||
|
|
||||||
|
// Navigation hint
|
||||||
|
canvas_draw_str(canvas, 0, 10, "<");
|
||||||
|
|
||||||
|
// Connection indicator
|
||||||
|
furi_mutex_acquire(app->mutex, FuriWaitForever);
|
||||||
|
bool data_ok = app->stats.data_received;
|
||||||
|
furi_mutex_release(app->mutex);
|
||||||
|
|
||||||
|
if(data_ok) {
|
||||||
|
canvas_draw_disc(canvas, 120, 6, 4);
|
||||||
|
} else {
|
||||||
|
canvas_draw_circle(canvas, 120, 6, 4);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Separator
|
||||||
|
canvas_draw_line(canvas, 0, 14, 128, 14);
|
||||||
|
|
||||||
|
canvas_set_font(canvas, FontSecondary);
|
||||||
|
|
||||||
|
furi_mutex_acquire(app->mutex, FuriWaitForever);
|
||||||
|
|
||||||
|
// IP Address
|
||||||
if(strlen(app->stats.ip_address) > 0) {
|
if(strlen(app->stats.ip_address) > 0) {
|
||||||
snprintf(buf, sizeof(buf), "IP: %s", app->stats.ip_address);
|
snprintf(buf, sizeof(buf), "IP: %s", app->stats.ip_address);
|
||||||
} else {
|
} else {
|
||||||
@@ -209,7 +328,7 @@ static void draw_callback(Canvas* canvas, void* context) {
|
|||||||
canvas_draw_str(canvas, 0, 26, buf);
|
canvas_draw_str(canvas, 0, 26, buf);
|
||||||
|
|
||||||
// Total frames
|
// Total frames
|
||||||
snprintf(buf, sizeof(buf), "Total: %lu", (unsigned long)app->stats.total_frames);
|
snprintf(buf, sizeof(buf), "Total frames: %lu", (unsigned long)app->stats.total_frames);
|
||||||
canvas_draw_str(canvas, 0, 38, buf);
|
canvas_draw_str(canvas, 0, 38, buf);
|
||||||
|
|
||||||
// Pending frames
|
// Pending frames
|
||||||
@@ -218,110 +337,158 @@ static void draw_callback(Canvas* canvas, void* context) {
|
|||||||
|
|
||||||
// Processed frames
|
// Processed frames
|
||||||
snprintf(buf, sizeof(buf), "Processed: %lu", (unsigned long)app->stats.processed_frames);
|
snprintf(buf, sizeof(buf), "Processed: %lu", (unsigned long)app->stats.processed_frames);
|
||||||
canvas_draw_str(canvas, 0, 62, buf);
|
canvas_draw_str(canvas, 4, 62, buf);
|
||||||
|
|
||||||
furi_mutex_release(app->mutex);
|
furi_mutex_release(app->mutex);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Input callback
|
// Draw callback
|
||||||
static void input_callback(InputEvent* input_event, void* context) {
|
static void draw_callback(Canvas* canvas, void* ctx) {
|
||||||
CanMonitorApp* app = context;
|
CanMonitorApp* app = ctx;
|
||||||
CanMonitorEvent event = {.type = EventTypeKey, .input = *input_event};
|
|
||||||
furi_message_queue_put(app->event_queue, &event, FuriWaitForever);
|
switch(app->current_page) {
|
||||||
|
case PageWelcome:
|
||||||
|
draw_welcome(canvas, app);
|
||||||
|
break;
|
||||||
|
case PageStats:
|
||||||
|
draw_stats(canvas, app);
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Initialize application
|
// Input callback
|
||||||
static CanMonitorApp* can_monitor_app_alloc(void) {
|
static void input_callback(InputEvent* event, void* ctx) {
|
||||||
|
CanMonitorApp* app = ctx;
|
||||||
|
|
||||||
|
if(event->type == InputTypeShort) {
|
||||||
|
furi_mutex_acquire(app->mutex, FuriWaitForever);
|
||||||
|
|
||||||
|
switch(event->key) {
|
||||||
|
case InputKeyOk:
|
||||||
|
if(app->current_page == PageWelcome && app->conn_state == StateDisconnected) {
|
||||||
|
// Start handshake
|
||||||
|
app->send_init = true;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case InputKeyRight:
|
||||||
|
// Only allow if connected
|
||||||
|
if(app->current_page == PageWelcome && app->conn_state == StateConnected) {
|
||||||
|
app->current_page = PageStats;
|
||||||
|
view_port_update(app->view_port);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case InputKeyLeft:
|
||||||
|
if(app->current_page == PageStats) {
|
||||||
|
app->current_page = PageWelcome;
|
||||||
|
view_port_update(app->view_port);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case InputKeyBack:
|
||||||
|
if(app->current_page == PageWelcome) {
|
||||||
|
app->running = false;
|
||||||
|
} else {
|
||||||
|
app->current_page = PageWelcome;
|
||||||
|
view_port_update(app->view_port);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
furi_mutex_release(app->mutex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Main entry point
|
||||||
|
int32_t can_monitor_app(void* p) {
|
||||||
|
UNUSED(p);
|
||||||
|
|
||||||
|
FURI_LOG_I(TAG, "Starting CAN Monitor");
|
||||||
|
|
||||||
|
// Allocate app
|
||||||
CanMonitorApp* app = malloc(sizeof(CanMonitorApp));
|
CanMonitorApp* app = malloc(sizeof(CanMonitorApp));
|
||||||
memset(app, 0, sizeof(CanMonitorApp));
|
memset(app, 0, sizeof(CanMonitorApp));
|
||||||
|
|
||||||
app->running = true;
|
app->running = true;
|
||||||
|
app->current_page = PageWelcome;
|
||||||
|
app->conn_state = StateDisconnected;
|
||||||
|
app->send_init = false;
|
||||||
|
|
||||||
|
// Mutex
|
||||||
app->mutex = furi_mutex_alloc(FuriMutexTypeNormal);
|
app->mutex = furi_mutex_alloc(FuriMutexTypeNormal);
|
||||||
app->event_queue = furi_message_queue_alloc(8, sizeof(CanMonitorEvent));
|
|
||||||
|
|
||||||
// Initialize stats
|
// Disable expansion protocol
|
||||||
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* expansion = furi_record_open(RECORD_EXPANSION);
|
||||||
expansion_disable(expansion);
|
expansion_disable(expansion);
|
||||||
furi_record_close(RECORD_EXPANSION);
|
furi_record_close(RECORD_EXPANSION);
|
||||||
|
|
||||||
// Initialize UART
|
FURI_LOG_I(TAG, "Expansion disabled");
|
||||||
|
|
||||||
|
// Init UART
|
||||||
app->rx_stream = furi_stream_buffer_alloc(RX_BUFFER_SIZE, 1);
|
app->rx_stream = furi_stream_buffer_alloc(RX_BUFFER_SIZE, 1);
|
||||||
app->serial_handle = furi_hal_serial_control_acquire(UART_CH);
|
app->serial = furi_hal_serial_control_acquire(FuriHalSerialIdUsart);
|
||||||
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
|
if(app->serial) {
|
||||||
app->rx_thread = furi_thread_alloc_ex("CANMonitorRx", 1024, uart_rx_thread, app);
|
furi_hal_serial_init(app->serial, UART_BAUD);
|
||||||
furi_thread_start(app->rx_thread);
|
furi_hal_serial_async_rx_start(app->serial, uart_callback, app, false);
|
||||||
|
FURI_LOG_I(TAG, "UART initialized at %d baud", UART_BAUD);
|
||||||
|
} else {
|
||||||
|
FURI_LOG_E(TAG, "Failed to acquire UART");
|
||||||
|
}
|
||||||
|
|
||||||
// Initialize GUI
|
// Start worker
|
||||||
|
app->worker_thread = furi_thread_alloc_ex("CanMonitorWorker", 1024, worker_thread, app);
|
||||||
|
furi_thread_start(app->worker_thread);
|
||||||
|
|
||||||
|
// Init GUI
|
||||||
app->gui = furi_record_open(RECORD_GUI);
|
app->gui = furi_record_open(RECORD_GUI);
|
||||||
app->view_port = view_port_alloc();
|
app->view_port = view_port_alloc();
|
||||||
view_port_draw_callback_set(app->view_port, draw_callback, app);
|
view_port_draw_callback_set(app->view_port, draw_callback, app);
|
||||||
view_port_input_callback_set(app->view_port, input_callback, app);
|
view_port_input_callback_set(app->view_port, input_callback, app);
|
||||||
gui_add_view_port(app->gui, app->view_port, GuiLayerFullscreen);
|
gui_add_view_port(app->gui, app->view_port, GuiLayerFullscreen);
|
||||||
|
|
||||||
return app;
|
FURI_LOG_I(TAG, "GUI initialized, waiting for user input");
|
||||||
|
|
||||||
|
// Main loop
|
||||||
|
while(app->running) {
|
||||||
|
furi_delay_ms(100);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Free application
|
FURI_LOG_I(TAG, "Shutting down");
|
||||||
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
|
// Send disconnect signal
|
||||||
furi_hal_serial_async_rx_stop(app->serial_handle);
|
if(app->conn_state == StateConnected) {
|
||||||
furi_hal_serial_deinit(app->serial_handle);
|
uart_send(app, "STOP:flipper\n");
|
||||||
furi_hal_serial_control_release(app->serial_handle);
|
}
|
||||||
|
|
||||||
|
// Cleanup
|
||||||
|
furi_thread_join(app->worker_thread);
|
||||||
|
furi_thread_free(app->worker_thread);
|
||||||
|
|
||||||
|
if(app->serial) {
|
||||||
|
furi_hal_serial_async_rx_stop(app->serial);
|
||||||
|
furi_hal_serial_deinit(app->serial);
|
||||||
|
furi_hal_serial_control_release(app->serial);
|
||||||
|
}
|
||||||
furi_stream_buffer_free(app->rx_stream);
|
furi_stream_buffer_free(app->rx_stream);
|
||||||
|
|
||||||
// Re-enable expansion protocol
|
// Re-enable expansion
|
||||||
Expansion* expansion = furi_record_open(RECORD_EXPANSION);
|
expansion = furi_record_open(RECORD_EXPANSION);
|
||||||
expansion_enable(expansion);
|
expansion_enable(expansion);
|
||||||
furi_record_close(RECORD_EXPANSION);
|
furi_record_close(RECORD_EXPANSION);
|
||||||
|
|
||||||
// Free GUI
|
|
||||||
view_port_enabled_set(app->view_port, false);
|
view_port_enabled_set(app->view_port, false);
|
||||||
gui_remove_view_port(app->gui, app->view_port);
|
gui_remove_view_port(app->gui, app->view_port);
|
||||||
view_port_free(app->view_port);
|
view_port_free(app->view_port);
|
||||||
furi_record_close(RECORD_GUI);
|
furi_record_close(RECORD_GUI);
|
||||||
|
|
||||||
furi_message_queue_free(app->event_queue);
|
|
||||||
furi_mutex_free(app->mutex);
|
furi_mutex_free(app->mutex);
|
||||||
|
|
||||||
free(app);
|
free(app);
|
||||||
}
|
|
||||||
|
|
||||||
// Main application entry point
|
FURI_LOG_I(TAG, "Bye!");
|
||||||
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;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user