#!/system/bin/sh # FlipperDroid Bridge Daemon # Manages bidirectional communication between Android and Flipper Zero # Protocol: binary framed messages over USB CDC (ttyACM) or BT rfcomm CONFIG_DIR="/data/adb/flipperdroid" CONFIG_FILE="$CONFIG_DIR/config.sh" LOG_FILE="$CONFIG_DIR/logs/flipperdroid.log" FIFO_TX="$CONFIG_DIR/bridge_tx" FIFO_RX="$CONFIG_DIR/bridge_rx" CMD_FIFO="$CONFIG_DIR/cmd_fifo" RESP_DIR="$CONFIG_DIR/responses" MAGIC_HI=$(printf '\xFD') MAGIC_LO=$(printf '\x01') log() { local level="$1"; shift local min_level=$(cat "$CONFIG_DIR/log_level" 2>/dev/null || echo 2) [ "$level" -le "$min_level" ] && echo "[$(date '+%Y-%m-%d %H:%M:%S')] [bridge] $*" >> "$LOG_FILE" } source "$CONFIG_FILE" 2>/dev/null mkdir -p "$RESP_DIR" # CRC8 lookup (Dallas/Maxim polynomial 0x31) crc8() { local data="$1" local crc=0 local i j byte for i in $(echo "$data" | sed 's/../& /g'); do byte=$((16#$i)) crc=$((crc ^ byte)) for j in $(seq 1 8); do if [ $((crc & 0x80)) -ne 0 ]; then crc=$(( ((crc << 1) ^ 0x31) & 0xFF )) else crc=$(( (crc << 1) & 0xFF )) fi done done printf '%02x' $crc } # Build a protocol frame: magic(2) + len(2) + cmd(1) + payload(N) + crc8(1) build_frame() { local cmd_hex="$1" local payload_hex="$2" local payload_len=$((${#payload_hex} / 2)) local total_len=$((payload_len + 1)) # cmd + payload local len_hi=$(printf '%02x' $((total_len >> 8))) local len_lo=$(printf '%02x' $((total_len & 0xFF))) local frame_data="${len_hi}${len_lo}${cmd_hex}${payload_hex}" local crc=$(crc8 "$frame_data") echo "FD01${frame_data}${crc}" } # Send raw hex frame to Flipper send_raw() { local hex="$1" local dev=$(cat "$CONFIG_DIR/flipper_dev" 2>/dev/null) [ -z "$dev" ] || [ ! -c "$dev" ] && return 1 echo "$hex" | xxd -r -p > "$dev" } # Send command and wait for response send_cmd() { local cmd="$1" local payload="${2:-}" local timeout="${3:-5}" local frame=$(build_frame "$cmd" "$payload") local dev=$(cat "$CONFIG_DIR/flipper_dev" 2>/dev/null) [ -z "$dev" ] || [ ! -c "$dev" ] && echo "ERR:no_device" && return 1 log 3 "TX: cmd=0x${cmd} payload=${payload:-}" # Send frame echo "$frame" | xxd -r -p > "$dev" 2>/dev/null if [ $? -ne 0 ]; then log 1 "TX failed — device gone?" echo "disconnected" > "$CONFIG_DIR/status" echo "ERR:tx_failed" return 1 fi # Read response with timeout local response="" response=$(timeout "$timeout" dd if="$dev" bs=1 count=256 2>/dev/null | xxd -p | tr -d '\n') if [ -z "$response" ]; then log 1 "RX timeout for cmd 0x${cmd}" echo "ERR:timeout" return 1 fi log 3 "RX: $response" # Parse response — extract after magic bytes local resp_cmd=$(echo "$response" | cut -c9-10) local resp_payload=$(echo "$response" | cut -c11-) if [ "$resp_cmd" = "fe" ]; then echo "OK:${resp_payload}" elif [ "$resp_cmd" = "ff" ]; then echo "ERR:${resp_payload}" else echo "RAW:${response}" fi } ############################# # Handshake with Flipper ############################# handshake() { log 2 "Initiating handshake with Flipper Zero..." # Send PING local resp=$(send_cmd "01" "" 3) if ! echo "$resp" | grep -q "^OK"; then log 1 "Handshake PING failed: $resp" return 1 fi log 2 "PING OK" # Get version resp=$(send_cmd "02" "" 3) if echo "$resp" | grep -q "^OK:"; then local version_hex=$(echo "$resp" | sed 's/OK://') echo "$version_hex" | xxd -r -p > "$CONFIG_DIR/flipper_version" 2>/dev/null log 2 "Flipper version: $(cat "$CONFIG_DIR/flipper_version")" fi # Get capabilities resp=$(send_cmd "03" "" 3) if echo "$resp" | grep -q "^OK:"; then echo "$resp" | sed 's/OK://' > "$CONFIG_DIR/flipper_caps" log 2 "Capabilities: $(cat "$CONFIG_DIR/flipper_caps")" fi echo "connected" > "$CONFIG_DIR/status" log 2 "Handshake complete — bridge active" return 0 } ############################# # Command FIFO handler # WebUI and other tools write commands here ############################# process_api_command() { local cmd_line="$1" local req_id=$(echo "$cmd_line" | cut -d'|' -f1) local subsystem=$(echo "$cmd_line" | cut -d'|' -f2) local action=$(echo "$cmd_line" | cut -d'|' -f3) local params=$(echo "$cmd_line" | cut -d'|' -f4-) log 3 "API cmd: $req_id $subsystem $action $params" local result="" case "$subsystem" in system) case "$action" in ping) result=$(send_cmd "01") ;; version) result=$(send_cmd "02") ;; caps) result=$(send_cmd "03") ;; status) result=$(send_cmd "04") ;; esac ;; gpio) case "$action" in init) local pin=$(echo "$params" | cut -d',' -f1) local mode=$(echo "$params" | cut -d',' -f2) local pin_hex=$(printf '%02x' "$pin") local mode_hex=$(printf '%02x' "$mode") result=$(send_cmd "10" "${pin_hex}${mode_hex}") ;; write) local pin=$(echo "$params" | cut -d',' -f1) local val=$(echo "$params" | cut -d',' -f2) result=$(send_cmd "11" "$(printf '%02x%02x' "$pin" "$val")") ;; read) local pin=$(echo "$params" | cut -d',' -f1) result=$(send_cmd "12" "$(printf '%02x' "$pin")") ;; pwm) local pin=$(echo "$params" | cut -d',' -f1) local freq=$(echo "$params" | cut -d',' -f2) local duty=$(echo "$params" | cut -d',' -f3) local pin_hex=$(printf '%02x' "$pin") local freq_hex=$(printf '%08x' "$freq") local duty_hex=$(printf '%02x' "$duty") result=$(send_cmd "13" "${pin_hex}${freq_hex}${duty_hex}") ;; adc) local pin=$(echo "$params" | cut -d',' -f1) result=$(send_cmd "14" "$(printf '%02x' "$pin")") ;; esac ;; subghz) case "$action" in set_freq) local freq=$(echo "$params" | cut -d',' -f1) result=$(send_cmd "20" "$(printf '%08x' "$freq")") ;; tx) local data_hex="$params" result=$(send_cmd "21" "$data_hex" 10) ;; rx_start) result=$(send_cmd "22") ;; rx_stop) result=$(send_cmd "23") ;; get_rssi) result=$(send_cmd "24") ;; set_mod) local mod=$(echo "$params" | cut -d',' -f1) local bw=$(echo "$params" | cut -d',' -f2) result=$(send_cmd "25" "$(printf '%02x%02x' "$mod" "$bw")") ;; replay) local slot=$(echo "$params" | cut -d',' -f1) result=$(send_cmd "26" "$(printf '%02x' "$slot")") ;; esac ;; rfid) case "$action" in read) result=$(send_cmd "30" "" 10) ;; emulate) result=$(send_cmd "31" "$params") ;; write) result=$(send_cmd "32" "$params") ;; esac ;; nfc) case "$action" in poll) result=$(send_cmd "40" "" 10) ;; read_full) result=$(send_cmd "41" "" 15) ;; emulate) result=$(send_cmd "42" "$params") ;; relay_start) result=$(send_cmd "43") ;; relay_stop) result=$(send_cmd "44") ;; raw_exchange) result=$(send_cmd "45" "$params" 10) ;; esac ;; ir) case "$action" in tx) local proto=$(echo "$params" | cut -d',' -f1) local addr=$(echo "$params" | cut -d',' -f2) local cmd=$(echo "$params" | cut -d',' -f3) result=$(send_cmd "50" "$(printf '%02x%08x%08x' "$proto" "$addr" "$cmd")") ;; tx_raw) result=$(send_cmd "51" "$params" 10) ;; rx_start) result=$(send_cmd "52") ;; rx_stop) result=$(send_cmd "53") ;; replay) result=$(send_cmd "54" "$(printf '%02x' "$params")") ;; esac ;; ibutton) case "$action" in read) result=$(send_cmd "60" "" 10) ;; emulate) result=$(send_cmd "61" "$params") ;; write) result=$(send_cmd "62" "$params") ;; esac ;; badusb) case "$action" in start) result=$(send_cmd "70") ;; exec) result=$(send_cmd "71" "$(echo -n "$params" | xxd -p)") ;; stop) result=$(send_cmd "72") ;; esac ;; file) case "$action" in list) result=$(send_cmd "90" "$(echo -n "$params" | xxd -p)" 10) ;; read) result=$(send_cmd "91" "$(echo -n "$params" | xxd -p)" 10) ;; write) result=$(send_cmd "92" "$params" 10) ;; delete) result=$(send_cmd "93" "$(echo -n "$params" | xxd -p)") ;; esac ;; *) result="ERR:unknown_subsystem" ;; esac # Write result to response file echo "$result" > "$RESP_DIR/${req_id}" log 3 "Result for $req_id: $result" } ############################# # Async event listener # Reads unsolicited events from Flipper ############################# event_listener() { local dev=$(cat "$CONFIG_DIR/flipper_dev" 2>/dev/null) [ -z "$dev" ] || [ ! -c "$dev" ] && return log 2 "Event listener started on $dev" while true; do # Read from device, look for event frames (cmd >= 0xA0) local raw=$(dd if="$dev" bs=1 count=2 2>/dev/null | xxd -p) # Check for magic bytes if [ "$raw" = "fd01" ]; then # Read length local len_hex=$(dd if="$dev" bs=1 count=2 2>/dev/null | xxd -p) local len=$((16#$len_hex)) # Read cmd + payload + crc local data=$(dd if="$dev" bs=1 count=$((len + 1)) 2>/dev/null | xxd -p) local event_cmd=$(echo "$data" | cut -c1-2) local event_payload=$(echo "$data" | cut -c3-$((${#data} - 2))) case "$event_cmd" in a0) # GPIO interrupt local pin=$((16#$(echo "$event_payload" | cut -c1-2))) local val=$((16#$(echo "$event_payload" | cut -c3-4))) log 3 "GPIO IRQ: pin=$pin val=$val" echo "gpio_irq|$pin|$val|$(date +%s)" >> "$CONFIG_DIR/events" ;; a1) # SubGHz RX data log 3 "SubGHz RX: $event_payload" echo "subghz_rx|$event_payload|$(date +%s)" >> "$CONFIG_DIR/events" ;; a2) # IR RX log 3 "IR RX: $event_payload" echo "ir_rx|$event_payload|$(date +%s)" >> "$CONFIG_DIR/events" ;; a3) # NFC field detected log 3 "NFC field event: $event_payload" echo "nfc_field|$event_payload|$(date +%s)" >> "$CONFIG_DIR/events" ;; a4) # Button press on Flipper local btn=$((16#$(echo "$event_payload" | cut -c1-2))) local state=$((16#$(echo "$event_payload" | cut -c3-4))) log 3 "Button: btn=$btn state=$state" echo "button|$btn|$state|$(date +%s)" >> "$CONFIG_DIR/events" ;; a5) # CPU offload request from Flipper local task_id=$(echo "$event_payload" | cut -c1-8) local workload=$(echo "$event_payload" | cut -c9-) log 2 "CPU offload request: task=$task_id" handle_cpu_offload "$task_id" "$workload" & ;; esac fi done } ############################# # CPU sharing — run Flipper's workload on phone ############################# handle_cpu_offload() { local task_id="$1" local workload_hex="$2" log 2 "Processing CPU offload task $task_id" # Decode workload — it's a simple bytecode or shell command local workload=$(echo "$workload_hex" | xxd -r -p) local result="" # Execute in sandboxed context result=$(echo "$workload" | timeout 30 sh 2>&1) local exit_code=$? # Send result back to Flipper local status_hex=$(printf '%02x' $exit_code) local result_hex=$(echo -n "$result" | xxd -p | tr -d '\n') send_cmd "81" "${task_id}${status_hex}${result_hex}" 5 log 2 "CPU task $task_id complete (exit=$exit_code)" } ############################# # Connection monitor ############################# connection_monitor() { while true; do sleep 10 local dev=$(cat "$CONFIG_DIR/flipper_dev" 2>/dev/null) local status=$(cat "$CONFIG_DIR/status" 2>/dev/null) if [ "$status" = "connected" ]; then # Check if device still exists if [ ! -c "$dev" ]; then log 1 "Flipper device $dev disappeared — disconnected" echo "disconnected" > "$CONFIG_DIR/status" # Try to rediscover sleep 5 local new_dev="" for tty in /dev/ttyACM*; do [ -c "$tty" ] && new_dev="$tty" && break done if [ -n "$new_dev" ]; then log 2 "Flipper reconnected on $new_dev" echo "$new_dev" > "$CONFIG_DIR/flipper_dev" stty -F "$new_dev" ${BAUD_RATE:-115200} raw -echo -echoe -echok 2>/dev/null handshake && echo "connected" > "$CONFIG_DIR/status" fi else # Heartbeat ping local resp=$(send_cmd "01" "" 2) if ! echo "$resp" | grep -q "^OK"; then log 1 "Heartbeat failed — marking disconnected" echo "disconnected" > "$CONFIG_DIR/status" fi fi elif [ "$status" = "disconnected" ]; then # Try to find Flipper for tty in /dev/ttyACM*; do if [ -c "$tty" ]; then log 2 "Found new device $tty, attempting handshake" echo "$tty" > "$CONFIG_DIR/flipper_dev" stty -F "$tty" ${BAUD_RATE:-115200} raw -echo -echoe -echok 2>/dev/null if handshake; then echo "connected" > "$CONFIG_DIR/status" fi break fi done fi done } ############################# # Main loop ############################# log 2 "FlipperDroid bridge daemon starting" # Create command FIFO rm -f "$CMD_FIFO" mkfifo "$CMD_FIFO" 2>/dev/null chmod 660 "$CMD_FIFO" # Initial handshake if [ "$(cat "$CONFIG_DIR/status" 2>/dev/null)" = "connected" ]; then handshake fi # Start connection monitor in background connection_monitor & MONITOR_PID=$! # Start event listener in background event_listener & LISTENER_PID=$! # Store PIDs for cleanup echo "$MONITOR_PID" > "$CONFIG_DIR/monitor.pid" echo "$LISTENER_PID" > "$CONFIG_DIR/listener.pid" # Main command processing loop — reads from FIFO log 2 "Bridge daemon ready, listening on $CMD_FIFO" while true; do if read -r cmd_line < "$CMD_FIFO" 2>/dev/null; then [ -n "$cmd_line" ] && process_api_command "$cmd_line" & else sleep 1 fi done