Files
giglez/docs/tembed_setup.md

663 lines
13 KiB
Markdown

# 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