# 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 ### 1. Bruce Firmware (Recommended) **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**: ```bash # 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**: ```bash # 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`: ```json {"cmd": "COMMAND_NAME", "params": {...}, "id": 12345} ``` - `cmd`: Command name (uppercase) - `params`: Command-specific parameters - `id`: Optional request ID for response matching #### Response Structure ```json {"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**: ```json { "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**: ```json { "status": "ok", "data": { "scan_id": "scan_20250111_1234", "started_at": 1704998400 }, "id": 1 } ``` #### 2. CAPTURE Capture a detected signal. **Request**: ```json { "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**: ```json { "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**: ```json { "cmd": "STOP_SCAN", "params": { "scan_id": "scan_20250111_1234" }, "id": 3 } ``` **Response**: ```json { "status": "ok", "data": { "total_signals": 127, "unique_protocols": 8 }, "id": 3 } ``` #### 4. REPLAY Replay a stored .sub file. **Request**: ```json { "cmd": "REPLAY", "params": { "file": "/sd/captures/garage_door.sub", "repeat": 3, "delay_ms": 100 }, "id": 4 } ``` **Response**: ```json { "status": "ok", "data": { "transmitted": 3, "frequency": 433920000 }, "id": 4 } ``` #### 5. STATUS Get device status. **Request**: ```json { "cmd": "STATUS", "id": 5 } ``` **Response**: ```json { "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**: ```json { "cmd": "CONFIG", "params": { "auto_save": true, "save_path": "/sd/giglez/", "led_brightness": 50, "spectrum_display": false }, "id": 6 } ``` **Response**: ```json { "status": "ok", "data": { "config_updated": true }, "id": 6 } ``` #### 7. LIST_FILES List captured .sub files on SD card. **Request**: ```json { "cmd": "LIST_FILES", "params": { "path": "/sd/giglez/", "pattern": "*.sub" }, "id": 7 } ``` **Response**: ```json { "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**: ```json { "cmd": "GET_FILE", "params": { "path": "/sd/giglez/capture_001.sub" }, "id": 8 } ``` **Response**: ```json { "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 ```json { "event": "SIGNAL_DETECTED", "data": { "frequency": 433920000, "rssi": -72, "timestamp": 1704998400, "protocol": "Unknown", "duration_ms": 45 } } ``` #### SCAN_COMPLETE ```json { "event": "SCAN_COMPLETE", "data": { "scan_id": "scan_20250111_1234", "total_signals": 127, "duration": 60 } } ``` #### ERROR ```json { "event": "ERROR", "data": { "code": "SD_CARD_FULL", "message": "SD card is full, cannot save capture" } } ``` ## Python Communication Library ### Basic Usage ```python 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: ```python 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: ```python 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: ```python 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 ```bash # 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 ```python # 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 ```bash # 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