BLE_PROTOCOL.md: - ESP32 BLE provisioning protocol spec - Characteristics UUIDs and data formats - WiFi credential exchange flow - Security considerations - Error handling SENSORS_IMPLEMENTATION_PLAN.md: - Complete implementation roadmap - Phase 1: BLE scanning and connection - Phase 2: WiFi provisioning - Phase 3: Device management - Phase 4: Status monitoring - API endpoints and data models - Testing checklist Technical reference for: - Backend developers - Mobile developers - QA team
694 lines
20 KiB
Markdown
694 lines
20 KiB
Markdown
# WellNuo Sensors - Implementation Plan
|
||
|
||
## 🎯 Цель
|
||
Заменить мок-данные в экране `equipment.tsx` на реальное управление WP сенсорами через Bluetooth Low Energy (BLE).
|
||
|
||
---
|
||
|
||
## ⚠️ КРИТИЧНО: Bluetooth в iOS Simulator
|
||
|
||
**iOS Simulator НЕ ПОДДЕРЖИВАЕТ Bluetooth!**
|
||
|
||
### Решение:
|
||
- **На реальном устройстве:** полный BLE функционал
|
||
- **В Simulator:** показываем mock-данные + предупреждение
|
||
|
||
### Проверка доступности BLE:
|
||
```typescript
|
||
import * as Device from 'expo-device';
|
||
import { Platform } from 'react-native';
|
||
|
||
const isBLEAvailable = Platform.OS !== 'web' && Device.isDevice;
|
||
// Device.isDevice = false в Simulator, true на реальном устройстве
|
||
```
|
||
|
||
---
|
||
|
||
## 📚 Библиотека: react-native-ble-plx
|
||
|
||
**Рекомендуемая библиотека:** `react-native-ble-plx`
|
||
- ✅ Поддержка Expo (через config plugin)
|
||
- ✅ Кроссплатформенность (iOS + Android)
|
||
- ✅ Активная поддержка
|
||
- ✅ TypeScript типы
|
||
|
||
### Установка:
|
||
```bash
|
||
npx expo install react-native-ble-plx
|
||
```
|
||
|
||
### Конфигурация `app.json`:
|
||
```json
|
||
{
|
||
"expo": {
|
||
"plugins": [
|
||
[
|
||
"react-native-ble-plx",
|
||
{
|
||
"isBackgroundEnabled": true,
|
||
"modes": ["peripheral", "central"],
|
||
"bluetoothAlwaysPermission": "Allow $(PRODUCT_NAME) to connect to WellNuo sensors"
|
||
}
|
||
]
|
||
]
|
||
}
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## 🏗️ Архитектура
|
||
|
||
### 1. BLE Service (`services/ble/`)
|
||
|
||
```
|
||
services/ble/
|
||
├── BLEManager.ts # Центральный менеджер BLE
|
||
├── WellPlugDevice.ts # Класс для работы с WP устройствами
|
||
├── BLECommands.ts # Константы команд (pin|7856, w, W, a, s, D)
|
||
├── MockBLEManager.ts # Mock для Simulator
|
||
└── types.ts # TypeScript типы
|
||
```
|
||
|
||
### 2. Context (`contexts/BLEContext.tsx`)
|
||
|
||
Глобальный контекст для управления BLE состоянием:
|
||
- Список найденных устройств
|
||
- Состояние сканирования
|
||
- Подключенные устройства
|
||
- Ошибки
|
||
|
||
### 3. Хуки (`hooks/`)
|
||
|
||
```typescript
|
||
// hooks/useBLE.ts
|
||
export function useBLE() {
|
||
// Доступ к BLEContext
|
||
// Scan, connect, disconnect
|
||
}
|
||
|
||
// hooks/useWellPlugDevice.ts
|
||
export function useWellPlugDevice(deviceId: string) {
|
||
// Управление конкретным WP устройством
|
||
// getWiFiList(), setWiFi(), checkCurrentWiFi()
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## 🎨 Экраны и User Flow
|
||
|
||
### Экран 1: Equipment List (текущий `equipment.tsx`)
|
||
|
||
**Функционал:**
|
||
- ✅ Показать список привязанных WP сенсоров к beneficiary
|
||
- ✅ Статус онлайн/офлайн через API `request_devices` (fresh=true)
|
||
- ✅ Последний раз виден (Last seen)
|
||
- ✅ Текущая WiFi сеть (получаем через BLE команду `a`)
|
||
- ✅ Кнопка "Настроить" → переход на Device Settings
|
||
- ✅ Кнопка "Добавить сенсор" → Scan Screen
|
||
|
||
**Данные:**
|
||
```typescript
|
||
interface WPSensor {
|
||
deviceId: string; // device_id из PostgreSQL
|
||
wellId: number; // well_id (497, 523)
|
||
mac: string; // MAC адрес (142B2F81A14C)
|
||
name: string; // "WP_497_81a14c"
|
||
|
||
// Статус (из API)
|
||
status: 'online' | 'offline';
|
||
lastSeen: Date; // timestamp из device_list API
|
||
|
||
// WiFi (из BLE)
|
||
currentWiFi?: {
|
||
ssid: string; // "FrontierTower"
|
||
rssi: number; // -67 dBm
|
||
};
|
||
|
||
// Привязка
|
||
beneficiaryId: string;
|
||
deploymentId: number;
|
||
}
|
||
```
|
||
|
||
**API для получения списка:**
|
||
```typescript
|
||
// services/api.ts
|
||
async getDevicesForBeneficiary(beneficiaryId: string): Promise<WPSensor[]> {
|
||
// 1. Получить deployment_id для beneficiary из PostgreSQL
|
||
// 2. Вызвать Legacy API: device_list_by_deployment
|
||
// 3. Вызвать request_devices(fresh=true) для онлайн статуса
|
||
// 4. Объединить данные
|
||
}
|
||
```
|
||
|
||
**BLE для текущего WiFi:**
|
||
- При открытии экрана → подключаемся к каждому устройству по BLE
|
||
- Отправляем команду `a`
|
||
- Парсим ответ: `mac,XXXXXX|a|SSID,RSSI`
|
||
- Обновляем UI
|
||
|
||
---
|
||
|
||
### Экран 2: Add Sensor (новый `add-sensor.tsx`)
|
||
|
||
**Путь:** `app/(tabs)/beneficiaries/[id]/add-sensor.tsx`
|
||
|
||
**Функционал:**
|
||
- ✅ Сканирование BLE устройств с именем `WP_*`
|
||
- ✅ Показать список найденных (с RSSI - сила сигнала)
|
||
- ✅ Сортировка по близости (RSSI)
|
||
- ✅ Выбор устройства → переход на Setup WiFi Screen
|
||
|
||
**UI:**
|
||
```
|
||
┌─────────────────────────┐
|
||
│ ← Scan for Sensors │
|
||
├─────────────────────────┤
|
||
│ │
|
||
│ 📡 Scanning... │
|
||
│ │
|
||
│ WP_497_81a14c │
|
||
│ Signal: ████░░ -55dBm │
|
||
│ [Connect] │
|
||
│ │
|
||
│ WP_523_81aad4 │
|
||
│ Signal: ███░░░ -67dBm │
|
||
│ [Connect] │
|
||
│ │
|
||
│ [Rescan] │
|
||
│ │
|
||
└─────────────────────────┘
|
||
```
|
||
|
||
**Алгоритм сканирования:**
|
||
```typescript
|
||
async function scanForWPDevices() {
|
||
const manager = new BleManager();
|
||
|
||
// Сканируем 10 секунд
|
||
manager.startDeviceScan(
|
||
null, // serviceUUIDs (можем фильтровать по service UUID)
|
||
null, // options
|
||
(error, device) => {
|
||
if (error) {
|
||
// Handle error
|
||
return;
|
||
}
|
||
|
||
// Фильтруем только WP_ устройства
|
||
if (device.name?.startsWith('WP_')) {
|
||
// Добавляем в список
|
||
// Сортируем по RSSI (ближе = выше значение)
|
||
}
|
||
}
|
||
);
|
||
|
||
// Через 10 секунд останавливаем
|
||
setTimeout(() => manager.stopDeviceScan(), 10000);
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
### Экран 3: Setup WiFi (новый `setup-wifi.tsx`)
|
||
|
||
**Путь:** `app/(tabs)/beneficiaries/[id]/setup-wifi.tsx`
|
||
|
||
**Параметры:** `?deviceName=WP_497_81a14c&beneficiaryId=123`
|
||
|
||
**Функционал:**
|
||
|
||
**Шаг 1: Подключение к устройству**
|
||
```
|
||
┌─────────────────────────┐
|
||
│ ← Setup WP_497 │
|
||
├─────────────────────────┤
|
||
│ │
|
||
│ 📱 Connecting to │
|
||
│ WP_497_81a14c... │
|
||
│ │
|
||
│ ⏳ Please wait │
|
||
│ │
|
||
└─────────────────────────┘
|
||
```
|
||
|
||
**Шаг 2: Unlock (автоматически)**
|
||
- Отправляем `pin|7856`
|
||
- Ждём ответ `pin|ok`
|
||
|
||
**Шаг 3: Получение списка WiFi**
|
||
```
|
||
┌─────────────────────────┐
|
||
│ ← Select WiFi Network │
|
||
├─────────────────────────┤
|
||
│ │
|
||
│ 📶 FrontierTower │
|
||
│ Signal: ████░ -55 │
|
||
│ [Select] │
|
||
│ │
|
||
│ 📶 HomeNetwork │
|
||
│ Signal: ███░░ -67 │
|
||
│ [Select] │
|
||
│ │
|
||
│ 📶 TP-Link_5G │
|
||
│ Signal: ██░░░ -75 │
|
||
│ [Select] │
|
||
│ │
|
||
│ [Rescan] │
|
||
│ │
|
||
└─────────────────────────┘
|
||
```
|
||
|
||
**Алгоритм:**
|
||
- Отправляем команду `w`
|
||
- Ждём ответ: `mac,XXXXXX|w|3|SSID1,RSSI1|SSID2,RSSI2|SSID3,RSSI3`
|
||
- Парсим список
|
||
- Сортируем по RSSI (сильный сигнал первым)
|
||
|
||
**Шаг 4: Ввод пароля**
|
||
```
|
||
┌─────────────────────────┐
|
||
│ ← WiFi Password │
|
||
├─────────────────────────┤
|
||
│ │
|
||
│ Network: FrontierTower │
|
||
│ │
|
||
│ Password: │
|
||
│ [**************] │
|
||
│ [ ] Show password │
|
||
│ │
|
||
│ [Connect] │
|
||
│ │
|
||
└─────────────────────────┘
|
||
```
|
||
|
||
**Шаг 5: Настройка WiFi**
|
||
- Отправляем `W|FrontierTower,password123`
|
||
- Ждём ответ `mac,XXXXXX|W|ok` или `mac,XXXXXX|W|fail`
|
||
- Если `ok` → ждём 5 секунд → проверяем командой `a`
|
||
|
||
**Шаг 6: Проверка подключения**
|
||
```
|
||
┌─────────────────────────┐
|
||
│ ✅ Connected! │
|
||
├─────────────────────────┤
|
||
│ │
|
||
│ Network: FrontierTower │
|
||
│ Signal: ████░ -67 dBm │
|
||
│ │
|
||
│ Device is now online │
|
||
│ and sending data │
|
||
│ │
|
||
│ [Continue] │
|
||
│ │
|
||
└─────────────────────────┘
|
||
```
|
||
|
||
---
|
||
|
||
### Экран 4: Device Settings (новый `device-settings.tsx`)
|
||
|
||
**Путь:** `app/(tabs)/beneficiaries/[id]/device-settings/[deviceId].tsx`
|
||
|
||
**Функционал:**
|
||
- ✅ Показать текущую WiFi (команда `a`)
|
||
- ✅ Изменить WiFi → Setup WiFi Screen
|
||
- ✅ Перезагрузить устройство (команда `s`)
|
||
- ✅ Отвязать от beneficiary (Detach)
|
||
- ✅ Проверить статус онлайн/офлайн
|
||
|
||
**UI:**
|
||
```
|
||
┌─────────────────────────┐
|
||
│ ← Device Settings │
|
||
│ WP_497_81a14c │
|
||
├─────────────────────────┤
|
||
│ │
|
||
│ Status │
|
||
│ 🟢 Online │
|
||
│ Last seen: 2 min ago │
|
||
│ │
|
||
│ WiFi Network │
|
||
│ 📶 FrontierTower │
|
||
│ Signal: ████░ -67 dBm │
|
||
│ [Change WiFi] │
|
||
│ │
|
||
│ Device Info │
|
||
│ MAC: 142B2F81A14C │
|
||
│ Well ID: 497 │
|
||
│ │
|
||
│ Actions │
|
||
│ [🔄 Reboot Device] │
|
||
│ [🔗 Detach Device] │
|
||
│ │
|
||
└─────────────────────────┘
|
||
```
|
||
|
||
---
|
||
|
||
## 🔌 API Integration
|
||
|
||
### Legacy API Endpoints
|
||
|
||
**1. Получить список устройств deployment:**
|
||
```typescript
|
||
POST https://eluxnetworks.net/function/well-api/api
|
||
{
|
||
function: 'device_list_by_deployment',
|
||
user_name: 'USER',
|
||
token: 'TOKEN',
|
||
deployment_id: 70,
|
||
first: 0,
|
||
last: 100
|
||
}
|
||
```
|
||
|
||
**2. Проверить онлайн статус (batch):**
|
||
```typescript
|
||
POST https://eluxnetworks.net/function/well-api/api
|
||
{
|
||
function: 'request_devices',
|
||
user_name: 'USER',
|
||
token: 'TOKEN',
|
||
deployment_id: 70,
|
||
group_id: 'All',
|
||
location: 'All',
|
||
fresh: true // ← Только онлайн устройства
|
||
}
|
||
```
|
||
|
||
**3. Привязать устройство к deployment:**
|
||
```typescript
|
||
POST https://eluxnetworks.net/function/well-api/api
|
||
{
|
||
function: 'set_deployment',
|
||
// ... все параметры из BLE_PROTOCOL.md
|
||
devices: [497, 523], // well_ids
|
||
wifis: ["FrontierTower|password123"]
|
||
}
|
||
```
|
||
|
||
### PostgreSQL
|
||
|
||
**Получить deployment_id для beneficiary:**
|
||
```sql
|
||
SELECT d.deployment_id, pd.access_to_deployments
|
||
FROM person_details pd
|
||
JOIN deployments d ON d.deployment_id = ANY(string_to_array(pd.access_to_deployments, ',')::int[])
|
||
WHERE pd.user_id = $1;
|
||
```
|
||
|
||
---
|
||
|
||
## 🧩 Компоненты
|
||
|
||
### 1. DeviceCard Component
|
||
|
||
Переиспользуемая карточка устройства:
|
||
```tsx
|
||
<DeviceCard
|
||
device={sensor}
|
||
onPress={() => router.push(`/device-settings/${sensor.deviceId}`)}
|
||
onDetach={() => handleDetach(sensor)}
|
||
showWiFi={true}
|
||
/>
|
||
```
|
||
|
||
### 2. WiFiNetworkItem Component
|
||
|
||
Элемент списка WiFi сетей:
|
||
```tsx
|
||
<WiFiNetworkItem
|
||
ssid="FrontierTower"
|
||
rssi={-67}
|
||
onSelect={() => handleSelectNetwork('FrontierTower')}
|
||
/>
|
||
```
|
||
|
||
### 3. BLEScanner Component
|
||
|
||
Компонент сканирования:
|
||
```tsx
|
||
<BLEScanner
|
||
isScanning={scanning}
|
||
devices={foundDevices}
|
||
onDeviceSelect={(device) => handleConnect(device)}
|
||
onRescan={() => startScan()}
|
||
/>
|
||
```
|
||
|
||
### 4. SimulatorWarning Component
|
||
|
||
Предупреждение для Simulator:
|
||
```tsx
|
||
{!Device.isDevice && (
|
||
<SimulatorWarning
|
||
message="Bluetooth is not available in iOS Simulator. Please test on a real device."
|
||
/>
|
||
)}
|
||
```
|
||
|
||
---
|
||
|
||
## 🔐 Permissions
|
||
|
||
### iOS (Info.plist через app.json)
|
||
|
||
```json
|
||
{
|
||
"expo": {
|
||
"ios": {
|
||
"infoPlist": {
|
||
"NSBluetoothAlwaysUsageDescription": "WellNuo needs Bluetooth to connect to your wellness sensors",
|
||
"NSBluetoothPeripheralUsageDescription": "WellNuo needs Bluetooth to manage your sensors"
|
||
}
|
||
}
|
||
}
|
||
}
|
||
```
|
||
|
||
### Android (AndroidManifest.xml через app.json)
|
||
|
||
```json
|
||
{
|
||
"expo": {
|
||
"android": {
|
||
"permissions": [
|
||
"android.permission.BLUETOOTH",
|
||
"android.permission.BLUETOOTH_ADMIN",
|
||
"android.permission.BLUETOOTH_CONNECT",
|
||
"android.permission.BLUETOOTH_SCAN",
|
||
"android.permission.ACCESS_FINE_LOCATION"
|
||
]
|
||
}
|
||
}
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## 📱 Mock Data для Simulator
|
||
|
||
```typescript
|
||
// services/ble/MockBLEManager.ts
|
||
|
||
export class MockBLEManager implements IBLEManager {
|
||
async scanDevices(): Promise<WPDevice[]> {
|
||
// Возвращаем фейковые WP устройства
|
||
return [
|
||
{
|
||
id: 'mock-1',
|
||
name: 'WP_497_81a14c',
|
||
rssi: -55,
|
||
mac: '142B2F81A14C'
|
||
},
|
||
{
|
||
id: 'mock-2',
|
||
name: 'WP_523_81aad4',
|
||
rssi: -67,
|
||
mac: '142B2F81AAD4'
|
||
}
|
||
];
|
||
}
|
||
|
||
async connect(deviceId: string): Promise<boolean> {
|
||
// Симулируем задержку
|
||
await delay(1000);
|
||
return true;
|
||
}
|
||
|
||
async getWiFiList(): Promise<WiFiNetwork[]> {
|
||
return [
|
||
{ ssid: 'FrontierTower', rssi: -55 },
|
||
{ ssid: 'HomeNetwork', rssi: -67 },
|
||
{ ssid: 'TP-Link_5G', rssi: -75 }
|
||
];
|
||
}
|
||
|
||
async setWiFi(ssid: string, password: string): Promise<boolean> {
|
||
await delay(2000);
|
||
return true;
|
||
}
|
||
|
||
async getCurrentWiFi(): Promise<{ ssid: string; rssi: number }> {
|
||
return { ssid: 'FrontierTower', rssi: -67 };
|
||
}
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## 🧪 Тестирование
|
||
|
||
### 1. На iOS Simulator
|
||
- ❌ BLE не работает
|
||
- ✅ Показываем mock данные
|
||
- ✅ Проверяем UI/UX флоу
|
||
|
||
### 2. На реальном iPhone
|
||
- ✅ Полный BLE функционал
|
||
- ✅ Подключение к WP устройствам
|
||
- ✅ Настройка WiFi
|
||
|
||
### 3. Android Emulator
|
||
- ⚠️ Может работать с пробросом Bluetooth
|
||
- Не рекомендуется для основной разработки
|
||
|
||
---
|
||
|
||
## 📋 План Реализации (По Шагам)
|
||
|
||
### Фаза 1: Инфраструктура (2-3 часа)
|
||
1. ✅ Установить `react-native-ble-plx`
|
||
2. ✅ Настроить permissions (iOS + Android)
|
||
3. ✅ Создать `services/ble/BLEManager.ts`
|
||
4. ✅ Создать `services/ble/MockBLEManager.ts`
|
||
5. ✅ Создать `contexts/BLEContext.tsx`
|
||
6. ✅ Добавить проверку `Device.isDevice`
|
||
|
||
### Фаза 2: API Integration (1-2 часа)
|
||
1. ✅ Добавить методы в `services/api.ts`:
|
||
- `getDevicesForBeneficiary()`
|
||
- `getDeviceStatus()`
|
||
- `attachDeviceToDeployment()`
|
||
2. ✅ Интегрировать Legacy API endpoints
|
||
3. ✅ Добавить PostgreSQL queries
|
||
|
||
### Фаза 3: Equipment Screen (1-2 часа)
|
||
1. ✅ Заменить mock данные на реальные API calls
|
||
2. ✅ Добавить BLE функционал для getCurrentWiFi
|
||
3. ✅ Обновить UI для WP сенсоров
|
||
4. ✅ Добавить SimulatorWarning
|
||
|
||
### Фаза 4: Add Sensor Screen (2-3 часа)
|
||
1. ✅ Создать новый экран `add-sensor.tsx`
|
||
2. ✅ Реализовать BLE сканирование
|
||
3. ✅ Показать список устройств с RSSI
|
||
4. ✅ Добавить кнопку подключения
|
||
|
||
### Фаза 5: Setup WiFi Screen (3-4 часа)
|
||
1. ✅ Создать экран `setup-wifi.tsx`
|
||
2. ✅ Реализовать мульти-шаговый флоу:
|
||
- Connect → Unlock → Get WiFi List → Enter Password → Set WiFi → Verify
|
||
3. ✅ Обработка ошибок
|
||
4. ✅ Привязка к deployment через API
|
||
|
||
### Фаза 6: Device Settings Screen (1-2 часа)
|
||
1. ✅ Создать экран `device-settings.tsx`
|
||
2. ✅ Показать текущую WiFi
|
||
3. ✅ Кнопки: Change WiFi, Reboot, Detach
|
||
4. ✅ Статус онлайн/офлайн
|
||
|
||
### Фаза 7: Тестирование (2-3 часа)
|
||
1. ✅ Тест на Simulator (mock данные)
|
||
2. ✅ Тест на реальном iPhone с WP устройствами
|
||
3. ✅ Проверить все флоу
|
||
4. ✅ Обработка edge cases
|
||
|
||
---
|
||
|
||
## 🚨 Edge Cases
|
||
|
||
### 1. Bluetooth выключен
|
||
```tsx
|
||
if (!isBluetoothEnabled) {
|
||
Alert.alert(
|
||
'Bluetooth Disabled',
|
||
'Please enable Bluetooth to connect to sensors',
|
||
[
|
||
{ text: 'Open Settings', onPress: () => Linking.openSettings() },
|
||
{ text: 'Cancel' }
|
||
]
|
||
);
|
||
}
|
||
```
|
||
|
||
### 2. Нет permission
|
||
```tsx
|
||
const status = await manager.requestPermissions();
|
||
if (status !== 'granted') {
|
||
// Показать инструкцию
|
||
}
|
||
```
|
||
|
||
### 3. Устройство не найдено
|
||
- Показать "No devices found"
|
||
- Кнопка Rescan
|
||
- Проверить что устройство включено
|
||
|
||
### 4. Не подключается к WiFi
|
||
- Показать ошибку от устройства
|
||
- Предложить повторить
|
||
- Проверить правильность пароля
|
||
|
||
### 5. Устройство оффлайн
|
||
- Показать серым
|
||
- Не пытаться подключаться по BLE
|
||
- Показать Last seen
|
||
|
||
---
|
||
|
||
## 🎯 Итоговый User Flow
|
||
|
||
```
|
||
1. Юзер открывает Equipment Screen
|
||
→ Видит список WP сенсоров (если есть)
|
||
→ Статус: онлайн/оффлайн (из API)
|
||
→ Текущая WiFi (из BLE)
|
||
|
||
2. Юзер нажимает "Add Sensor"
|
||
→ Открывается Add Sensor Screen
|
||
→ Сканирование BLE (10 сек)
|
||
→ Список найденных WP_* устройств
|
||
→ Сортировка по близости (RSSI)
|
||
|
||
3. Юзер выбирает устройство
|
||
→ Подключение по BLE
|
||
→ Автоматический unlock (pin|7856)
|
||
→ Получение списка WiFi сетей
|
||
→ Показ списка с сигналом
|
||
|
||
4. Юзер выбирает WiFi сеть
|
||
→ Ввод пароля
|
||
→ Отправка credentials (W|SSID,PASS)
|
||
→ Ожидание подключения (5 сек)
|
||
→ Проверка (команда a)
|
||
|
||
5. Success!
|
||
→ Устройство подключено к WiFi
|
||
→ Привязка к beneficiary через API
|
||
→ Возврат на Equipment Screen
|
||
→ Устройство появляется в списке как "online"
|
||
```
|
||
|
||
---
|
||
|
||
## 📝 Changelog
|
||
|
||
- **2026-01-14** - Создан план реализации функционала сенсоров
|
||
- Определена архитектура BLE управления
|
||
- Спроектированы все экраны
|
||
- Решена проблема с Simulator (mock данные)
|