<===

ProNotes

2025-10-18 18:16:00
Широковещание аналогового значения по BLE — это **классический случай для IoT-датчиков**. ESP32-S3-Zero имеет встроенные АЦП на пинах GPIO1-10. Сделаем **BLE Beacon**, который каждые 500мс рекламирует текущее значение с аналогового пина **без подключения**!

## Простой пример: **BLE Beacon аналогового датчика**

**Подключение**: Подайте сигнал на **GPIO1** (A0 на Xiao ESP32S3). Например:
- Потенциометр (средний пин → GPIO1, края → 3V3/GND)
- Фоторезистор + резистор 10kΩ
- Любые 0-3.3V

```python
import time
import board
import _bleio
import analogio

print("BLE Analog Beacon starting!")

# Аналоговый вход (GPIO1 = A0 на Xiao ESP32S3)
analog_in = analogio.AnalogIn(board.GP1)  # Измените на ваш пин!

# LED для индикации
led = _bleio.DigitalInOut(board.LED)
led.direction = _bleio.DigitalInOut.Direction.OUTPUT

# BLE адаптер
adapter = _bleio.Adapter()

# Создаем сервис (для beacon не обязательно, но добавим)
service = _bleio.Service(_bleio.UUID("12345678-1234-5678-1234-56789abcdef0"))
char = _bleio.Characteristic(
    service,
    _bleio.UUID("87654321-4321-8765-4321-fedcba987654"),
    properties=_bleio.Characteristic.READ,
    initial_value=b"0"
)
service.add_characteristic(char)
adapter.register_services(service)

# Рекламный пакет — ЗДЕСЬ МАГИЯ!
advertisement = _bleio.Advertisement()
advertisement.complete_local_name = "AnalogSensor"

def value_to_bytes(value):
    """Конвертируем 0-65535 в 2 байта"""
    return value.to_bytes(2, 'little')

def update_advertisement(analog_value):
    """Обновляем рекламу новым значением"""
    # Manufacturer Data: первые 2 байта = значение АЦП
    data = b'\xFF\x01' + value_to_bytes(analog_value)  # \xFF\x01 = наш код
    advertisement.manufacturer_data = (_bleio.UUID(0xFFFF), data)
    advertisement.service_uuid = service.uuid

print("Broadcasting analog value every 500ms...")

while True:
    # Читаем аналоговый вход (0-65535)
    raw_value = analog_in.value
    voltage = analog_in.voltage  # 0-3.3V
    
    print(f"Raw: {raw_value}, Voltage: {voltage:.2f}V")
    
    # Обновляем характеристику (для подключенных клиентов)
    char.value = f"{raw_value:05d}".encode()
    
    # Обновляем рекламу БЕЗ ПЕРЕРЫВА!
    update_advertisement(raw_value)
    
    # Начинаем/обновляем рекламу
    if not adapter.advertising:
        adapter.start_advertising(advertisement)
    
    led.value = True
    time.sleep(0.5)  # Каждые 500мс
    led.value = False
    time.sleep(0.1)
```

## 🎯 **Как это работает:**

| Компонент | Что делает |
|-----------|------------|
| `analog_in.value` | Читает 0-65535 (16 бит) |
| `manufacturer_data` | **2 байта в рекламе** = текущее значение |
| **Реклама каждые 500мс** | **БЕЗ подключения!** Смартфон видит данные |
| `char.value` | Для подключенных клиентов (бонус) |

## 📱 **Чтение на смартфоне (БЕЗ ПОДКЛЮЧЕНИЯ!)**

### **1. nRF Connect (Android/iOS) — ПРОСТО:**
1. Сканируйте → найдите **"AnalogSensor"**
2. **НЕ подключайтесь!** Нажмите на устройство
3. **Manufacturer Data**: увидите `FF 01 XX YY`
   - `XX YY` = значение АЦП (пример: `FF 01 34 12` = 0x1234 = 4660)

### **2. Bluefruit Connect — UART Mode:**
- Подключитесь → UART → увидите "04660" (из характеристики)

### **3. Собственное приложение:**
```javascript
// Пример для BLE-сканера
function onAdvertisement(device, advertisement) {
    let data = advertisement.manufacturerData;
    if (data && data[0] === 0xFF && data[1] === 0x01) {
        let value = (data[3] << 8) | data[2]; // 0-65535
        let voltage = (value / 65535) * 3.3;
        console.log(`Voltage: ${voltage.toFixed(2)}V`);
    }
}
```

## 🚀 **УЛУЧШЕННАЯ ВЕРСИЯ с фильтрацией:**

```python
# Добавьте в начало основного кода:
smoothed_value = 0
alpha = 0.1  # Коэффициент сглаживания

# В цикле замените чтение на:
raw_value = analog_in.value
smoothed_value = alpha * raw_value + (1 - alpha) * smoothed_value
```

## 📋 **Таблица пинов АЦП для Xiao ESP32S3:**
| Пин | GPIO | Название |
|-----|------|----------|
| **A0** | **GP1** | Рекомендую! |
| A1 | GP2 | - |
| A2 | GP3 | - |
| A3 | GP4 | - |
| A4 | GP5 | - |
| A5 | GP6 | - |
| A6 | GP7 | - |
| A7 | GP8 | - |

## 🎯 **Тестирование за 30 секунд:**
1. **Код на плату** → LED мигает
2. **Потенциометр на GP1**
3. **nRF Connect** → Manufacturer Data меняется при вращении!
4. **Готово!** Данные в эфире 24/7

## 💡 **Идеи применения:**
- **IoT датчик** (температура, влажность)
- **Умный дом** (освещенность)
- **Беспроводной вольтметр**
- **Мониторинг батареи**


=========== Клиент (исполнительное устройство) ============


### Простой пример BLE-клиента на ESP32-S3 с CircuitPython (только `_bleio`)

Этот код сделает ESP32-S3-Zero **BLE-клиентом (central)**: он сканирует эфир, находит beacon-сервер из предыдущего примера ("AnalogSensor"), подключается (если нужно, но для beacon можно читать из рекламы), читает аналоговое значение, вычисляет напряжение и управляет **реле** на пине **GP2** (например, реле-модуль на 3.3V логике: высокий уровень — вкл, низкий — выкл).

#### Подключение:
- **Аналоговый датчик**: На сервере (предыдущий код).
- **Реле**: Подключите к **GP2** (сигнальный пин реле → GP2, GND → GND, VCC → 3V3). Используйте реле-модуль с опторазвязкой для безопасности.
- **LED**: Встроенный для индикации (реле включено — LED горит).
- **Тестирование**: Запустите сервер на одной плате, клиент на другой. В консоли клиента увидите значения.

#### Код примера (code.py)
Сохраните в `code.py` на CIRCUITPY второй платы.

```python
import time
import board
import _bleio
import digitalio

print("BLE Client starting! Scanning for AnalogSensor...")

# Реле на GP2 (высокий уровень — ВКЛ)
relay = digitalio.DigitalInOut(board.GP2)  # Измените пин, если нужно
relay.direction = digitalio.Direction.OUTPUT
relay.value = False  # Изначально выкл

# LED для индикации
led = digitalio.DigitalInOut(board.LED)
led.direction = digitalio.Direction.OUTPUT
led.value = False

# BLE адаптер
adapter = _bleio.Adapter()

# UUID сервиса и характеристики из сервера
SERVICE_UUID = _bleio.UUID("12345678-1234-5678-1234-56789abcdef0")
CHAR_UUID = _bleio.UUID("87654321-4321-8765-4321-fedcba987654")

# Порог напряжения
VOLT_THRESHOLD = 1.5
MAX_RAW = 65535  # 16-бит АЦП
MAX_VOLT = 3.3   # Диапазон ESP32-S3

while True:
    # Сканируем устройства (каждые 5 сек, чтобы не тратить батарею)
    print("Scanning...")
    scan_results = adapter.scan(5.0)  # Сканировать 5 сек
    
    target_device = None
    for result in scan_results:
        if result.name == "AnalogSensor":
            target_device = result
            print("Found AnalogSensor!")
            break
    
    if target_device:
        # Пытаемся подключиться (для чтения характеристики)
        try:
            connection = adapter.connect(target_device.address, timeout=10.0)
            print("Connected!")
            
            # Находим сервис и характеристику
            service = connection[SERVICE_UUID]
            char = service[CHAR_UUID]
            
            # Читаем значение (строка, как "04660")
            raw_bytes = char.value
            if raw_bytes:
                raw_value = int(raw_bytes.decode().strip())
                voltage = (raw_value / MAX_RAW) * MAX_VOLT
                print(f"Received: Raw={raw_value}, Voltage={voltage:.2f}V")
                
                # Управление реле
                if voltage < VOLT_THRESHOLD:
                    relay.value = True
                    led.value = True
                    print("Relay ON (voltage low)!")
                else:
                    relay.value = False
                    led.value = False
                    print("Relay OFF")
            
            # Отключаемся, чтобы не держать соединение
            connection.disconnect()
        except Exception as e:
            print("Connection error:", e)
    
    else:
        print("No AnalogSensor found. Retrying...")
        relay.value = False
        led.value = False
    
    time.sleep(5)  # Пауза перед следующим сканом
```

#### Как это работает:
1. **Сканирование**: Каждые 5 сек ищет устройство с именем "AnalogSensor".
2. **Подключение**: Подключается, находит сервис/характеристику по UUID.
3. **Чтение**: Получает сырое значение (0-65535), вычисляет напряжение (0-3.3V).
4. **Логика реле**:
   - Если < 1.5V → Реле ВКЛ (relay.value = True), LED горит.
   - Иначе → Реле ВЫКЛ.
5. **Отключение**: Чтобы экономить энергию и позволить другим подключаться.
6. **Цикл**: Повторяет, если сервер пропал — реле выключается.

#### Альтернатива: Чтение из рекламы (без подключения!)
Если сервер транслирует в `manufacturer_data` (как в моем предыдущем коде), клиент может читать **без подключения** — быстрее и проще!

Добавьте/замените в коде после сканирования:

```python
if target_device:
    # Читаем manufacturer_data БЕЗ подключения
    if target_device.manufacturer_data:
        data = target_device.manufacturer_data.get(_bleio.UUID(0xFFFF))
        if data and data[:2] == b'\xFF\x01':
            raw_value = int.from_bytes(data[2:4], 'little')
            voltage = (raw_value / MAX_RAW) * MAX_VOLT
            print(f"From adv: Raw={raw_value}, Voltage={voltage:.2f}V")
            
            if voltage < VOLT_THRESHOLD:
                relay.value = True
                led.value = True
                print("Relay ON!")
            else:
                relay.value = False
                led.value = False
                print("Relay OFF")
```

- **Преимущество**: Нет подключения — работает с beacon в реальном времени.
- **Используйте это**, если сервер настроен на manufacturer_data (как в примере).

#### Возможные проблемы:
- **Нет соединения?** Убедитесь, что UUID совпадают с сервером.
- **Реле не работает?** Проверьте пин (GP2 доступен на Xiao ESP32S3). Если реле на 5V — используйте level-shifter.
- **Точность АЦП**: ESP32-S3 АЦП нелинейный, для точности калибруйте (добавьте offset в код).
- **Батарея**: Сканирование жрет энергию — увеличьте паузу.
← Previous
Back to list