Files
giglez/docs/tembed_setup.md
T

13 KiB

LilyGo T-Embed Setup and Communication Protocol

Overview

The LilyGo T-Embed is an ESP32-based device with an integrated CC1101 sub-GHz transceiver. This document covers hardware setup, firmware options, and the communication protocol between Android Termux and the T-Embed.

Hardware Specifications

LilyGo T-Embed CC1101

  • MCU: ESP32-S3 (Dual-core 240MHz)
  • Display: 1.9" ST7789 TFT (170x320 pixels)
  • RF Chip: CC1101 Sub-GHz transceiver
  • Frequency Range:
    • 300-348 MHz
    • 387-464 MHz
    • 779-928 MHz
  • Storage: MicroSD card slot
  • Connectivity: USB-C (serial + power)
  • Battery: Built-in LiPo charger

CC1101 Transceiver Capabilities

  • Modulation: ASK/OOK, FSK, GFSK, MSK
  • Data Rate: 0.6 - 500 kbps
  • RX Sensitivity: -112 dBm @ 1.2 kbps
  • TX Power: Configurable up to +12 dBm
  • FIFO Buffer: 64 bytes RX/TX

Firmware Options

Repository: https://github.com/pr3y/Bruce

Bruce is a multi-tool firmware with excellent Sub-GHz support designed for T-Embed.

Features:

  • Read/replay .sub files (Flipper compatible)
  • Spectrum analyzer
  • Frequency scanner
  • Signal recorder
  • SD card support for signature storage

Installation:

# Download from releases
wget https://github.com/pr3y/Bruce/releases/latest/download/Bruce_T-Embed.bin

# Flash using esptool (in Termux)
pip install esptool
esptool.py --chip esp32s3 --port /dev/ttyUSB0 write_flash 0x0 Bruce_T-Embed.bin

2. ESP32-Marauder

Repository: https://github.com/justcallmekoko/ESP32Marauder

Originally for WiFi, has been adapted for Sub-GHz.

3. Custom Firmware (Planned)

GigLez-specific firmware optimized for continuous scanning and GPS coordination.

Communication Protocol

Serial Connection

The T-Embed communicates via USB serial at 115200 baud.

Termux Serial Setup:

# Install required packages
pkg install libusb python

# Python serial library
pip install pyserial

# Find device
ls /dev/ttyUSB*
# Usually /dev/ttyUSB0 or /dev/ttyACM0

Command Protocol

GigLez uses a JSON-based command protocol over serial.

Command Structure

All commands are JSON objects terminated by \n:

{"cmd": "COMMAND_NAME", "params": {...}, "id": 12345}
  • cmd: Command name (uppercase)
  • params: Command-specific parameters
  • id: Optional request ID for response matching

Response Structure

{"status": "ok|error", "data": {...}, "id": 12345, "timestamp": 1704998400}
  • status: "ok" or "error"
  • data: Response data (command-specific)
  • id: Request ID (if provided)
  • timestamp: Unix timestamp from device

Command Reference

1. SCAN

Start scanning for signals on specified frequencies.

Request:

{
  "cmd": "SCAN",
  "params": {
    "frequencies": [433920000, 315000000, 868000000],
    "duration": 60,
    "modulation": "OOK",
    "rssi_threshold": -90
  },
  "id": 1
}

Parameters:

  • frequencies: Array of frequencies in Hz
  • duration: Scan duration in seconds (0 = continuous)
  • modulation: "OOK", "FSK", or "AUTO"
  • rssi_threshold: Minimum signal strength in dBm

Response:

{
  "status": "ok",
  "data": {
    "scan_id": "scan_20250111_1234",
    "started_at": 1704998400
  },
  "id": 1
}

2. CAPTURE

Capture a detected signal.

Request:

{
  "cmd": "CAPTURE",
  "params": {
    "frequency": 433920000,
    "duration": 5,
    "format": "RAW"
  },
  "id": 2
}

Parameters:

  • frequency: Frequency in Hz
  • duration: Capture duration in seconds
  • format: "RAW", "DECODED", or "BOTH"

Response:

{
  "status": "ok",
  "data": {
    "capture_id": "cap_20250111_1235",
    "frequency": 433920000,
    "rssi": -75,
    "protocol": "Princeton",
    "raw_data": [2980, -240, 520, -980, 520, -980, ...],
    "decoded": {
      "bit": 24,
      "key": "00 00 00 00 00 95 D5 D4",
      "te": 400
    }
  },
  "id": 2
}

3. STOP_SCAN

Stop an active scan.

Request:

{
  "cmd": "STOP_SCAN",
  "params": {
    "scan_id": "scan_20250111_1234"
  },
  "id": 3
}

Response:

{
  "status": "ok",
  "data": {
    "total_signals": 127,
    "unique_protocols": 8
  },
  "id": 3
}

4. REPLAY

Replay a stored .sub file.

Request:

{
  "cmd": "REPLAY",
  "params": {
    "file": "/sd/captures/garage_door.sub",
    "repeat": 3,
    "delay_ms": 100
  },
  "id": 4
}

Response:

{
  "status": "ok",
  "data": {
    "transmitted": 3,
    "frequency": 433920000
  },
  "id": 4
}

5. STATUS

Get device status.

Request:

{
  "cmd": "STATUS",
  "id": 5
}

Response:

{
  "status": "ok",
  "data": {
    "firmware": "GigLez-T-Embed v0.1.0",
    "uptime": 3600,
    "battery_voltage": 3.85,
    "battery_percent": 78,
    "sd_card": {
      "present": true,
      "free_mb": 15234,
      "total_mb": 31456
    },
    "current_scan": "scan_20250111_1234",
    "cc1101": {
      "status": "idle",
      "frequency": 433920000,
      "rssi": -95
    }
  },
  "id": 5
}

6. CONFIG

Configure device settings.

Request:

{
  "cmd": "CONFIG",
  "params": {
    "auto_save": true,
    "save_path": "/sd/giglez/",
    "led_brightness": 50,
    "spectrum_display": false
  },
  "id": 6
}

Response:

{
  "status": "ok",
  "data": {
    "config_updated": true
  },
  "id": 6
}

7. LIST_FILES

List captured .sub files on SD card.

Request:

{
  "cmd": "LIST_FILES",
  "params": {
    "path": "/sd/giglez/",
    "pattern": "*.sub"
  },
  "id": 7
}

Response:

{
  "status": "ok",
  "data": {
    "files": [
      {
        "name": "capture_001.sub",
        "size": 245,
        "created": 1704998400
      },
      {
        "name": "capture_002.sub",
        "size": 312,
        "created": 1704998500
      }
    ],
    "total_count": 2,
    "total_bytes": 557
  },
  "id": 7
}

8. GET_FILE

Retrieve a file from SD card.

Request:

{
  "cmd": "GET_FILE",
  "params": {
    "path": "/sd/giglez/capture_001.sub"
  },
  "id": 8
}

Response:

{
  "status": "ok",
  "data": {
    "filename": "capture_001.sub",
    "size": 245,
    "content": "Filetype: Flipper SubGhz Key File\nVersion: 1\n..."
  },
  "id": 8
}

Event Notifications

The T-Embed can send unsolicited event notifications:

SIGNAL_DETECTED

{
  "event": "SIGNAL_DETECTED",
  "data": {
    "frequency": 433920000,
    "rssi": -72,
    "timestamp": 1704998400,
    "protocol": "Unknown",
    "duration_ms": 45
  }
}

SCAN_COMPLETE

{
  "event": "SCAN_COMPLETE",
  "data": {
    "scan_id": "scan_20250111_1234",
    "total_signals": 127,
    "duration": 60
  }
}

ERROR

{
  "event": "ERROR",
  "data": {
    "code": "SD_CARD_FULL",
    "message": "SD card is full, cannot save capture"
  }
}

Python Communication Library

Basic Usage

import serial
import json
import time

class TEmbedController:
    def __init__(self, port='/dev/ttyUSB0', baudrate=115200):
        self.serial = serial.Serial(port, baudrate, timeout=1)
        self.request_id = 0
        time.sleep(2)  # Wait for device reset

    def send_command(self, cmd, params=None):
        """Send command and return response"""
        self.request_id += 1
        message = {
            'cmd': cmd,
            'params': params or {},
            'id': self.request_id
        }

        # Send
        self.serial.write(json.dumps(message).encode() + b'\n')

        # Wait for response
        line = self.serial.readline().decode().strip()
        if line:
            return json.loads(line)
        return None

    def start_scan(self, frequencies, duration=60):
        """Start scanning on specified frequencies"""
        return self.send_command('SCAN', {
            'frequencies': frequencies,
            'duration': duration,
            'modulation': 'AUTO',
            'rssi_threshold': -90
        })

    def capture_signal(self, frequency, duration=5):
        """Capture signal at frequency"""
        return self.send_command('CAPTURE', {
            'frequency': frequency,
            'duration': duration,
            'format': 'BOTH'
        })

    def get_status(self):
        """Get device status"""
        return self.send_command('STATUS')

    def listen_events(self, callback):
        """Listen for event notifications"""
        while True:
            line = self.serial.readline().decode().strip()
            if line:
                try:
                    data = json.loads(line)
                    if 'event' in data:
                        callback(data)
                except json.JSONDecodeError:
                    pass

# Example usage
device = TEmbedController('/dev/ttyUSB0')

# Check status
status = device.get_status()
print(f"Battery: {status['data']['battery_percent']}%")

# Start scan
scan = device.start_scan([433920000, 315000000, 868000000], duration=60)
print(f"Scan started: {scan['data']['scan_id']}")

# Listen for signals
def on_event(event):
    if event['event'] == 'SIGNAL_DETECTED':
        print(f"Signal detected: {event['data']['frequency']} Hz, RSSI: {event['data']['rssi']} dBm")

device.listen_events(on_event)

Async Implementation

For better performance in production:

import asyncio
import aioserial

class AsyncTEmbedController:
    def __init__(self, port='/dev/ttyUSB0', baudrate=115200):
        self.port = port
        self.baudrate = baudrate
        self.request_id = 0
        self.pending_requests = {}

    async def connect(self):
        self.serial = aioserial.AioSerial(port=self.port, baudrate=self.baudrate)
        await asyncio.sleep(2)

    async def send_command(self, cmd, params=None):
        self.request_id += 1
        message = {
            'cmd': cmd,
            'params': params or {},
            'id': self.request_id
        }

        # Send
        await self.serial.write_async(json.dumps(message).encode() + b'\n')

        # Create future for response
        future = asyncio.Future()
        self.pending_requests[self.request_id] = future

        return await future

    async def listen(self, event_callback):
        """Background task to listen for responses and events"""
        while True:
            line = await self.serial.readline_async()
            if line:
                try:
                    data = json.loads(line.decode().strip())

                    # Handle response
                    if 'id' in data and data['id'] in self.pending_requests:
                        self.pending_requests[data['id']].set_result(data)
                        del self.pending_requests[data['id']]

                    # Handle event
                    elif 'event' in data:
                        await event_callback(data)

                except json.JSONDecodeError:
                    pass

GPS Coordination

Timestamp Synchronization

The T-Embed doesn't have GPS, so timestamps come from Android:

import time

# Send timestamp to device
device.send_command('CONFIG', {
    'system_time': int(time.time())
})

# Device will use this to calibrate its internal clock

Capture with GPS

When a signal is captured, immediately tag it with GPS:

import android
from tembed import TEmbedController

# Initialize Android SL4A for GPS
droid = android.Android()
droid.startLocating()

# T-Embed controller
device = TEmbedController()

# Event handler
async def on_signal(event):
    if event['event'] == 'SIGNAL_DETECTED':
        # Get current GPS location
        location = droid.getLastKnownLocation().result
        gps = location.get('gps') or location.get('network')

        if gps:
            # Store in database with GPS
            store_capture({
                'frequency': event['data']['frequency'],
                'rssi': event['data']['rssi'],
                'latitude': gps['latitude'],
                'longitude': gps['longitude'],
                'accuracy': gps.get('accuracy', 0),
                'timestamp': event['data']['timestamp']
            })

# Start listening
await device.listen(on_signal)

Troubleshooting

Device Not Detected

# Check USB devices
lsusb

# Check serial ports
ls -l /dev/ttyUSB* /dev/ttyACM*

# Grant permissions (Termux)
termux-usb -l  # List devices
termux-usb -r /dev/bus/usb/XXX/YYY  # Request permission

Serial Communication Errors

# Add error handling
try:
    response = device.send_command('STATUS')
except serial.SerialException as e:
    print(f"Serial error: {e}")
    # Attempt reconnection
    device.serial.close()
    device.serial.open()

Firmware Update

# Backup SD card first
adb pull /sdcard/giglez /path/to/backup

# Flash new firmware
esptool.py --chip esp32s3 --port /dev/ttyUSB0 erase_flash
esptool.py --chip esp32s3 --port /dev/ttyUSB0 write_flash 0x0 firmware.bin

Next Steps

  1. Flash Bruce firmware to T-Embed
  2. Test serial communication from Termux
  3. Implement capture coordination with GPS
  4. Build async event handling system
  5. Develop custom firmware optimized for GigLez