Android APP

Thiết kế cấu trúc app Android

Cấu trúc ứng dụng điều khiển thiết bị thông minh

1. Tổng quan kiến trúc ứng dụng

Ứng dụng sẽ được thiết kế theo mô hình client-server với giao tiếp qua REST API hoặc WebSocket để đảm bảo tính real-time cho các thiết bị như smartlocker và thiết bị on/off. Dưới đây là các thành phần chính:

Kiến trúc tổng thể

[Ứng dụng Android/iOS] <-> [REST API/WebSocket] <-> [Server] <-> [MQTT Broker] <-> [Thiết bị IoT]
                                    |
                                 [Database]

2. Cấu trúc ứng dụng Android/iOS

2.1. Công nghệ phát triển

2.2. Cấu trúc thư mục (Flutter)

Cấu trúc thư mục được tổ chức theo mô hình feature-based để dễ dàng mở rộng và bảo trì:

lib/
├── features/
│   ├── authentication/
│   │   ├── screens/          # Màn hình đăng nhập, đăng ký
│   │   ├── models/           # Model cho user
│   │   ├── services/         # API calls cho authentication
│   │   └── widgets/          # Các widget tái sử dụng
│   ├── smart_locker/
│   │   ├── screens/          # Màn hình điều khiển smartlocker
│   │   ├── models/           # Model cho locker (trạng thái, hình ảnh...)
│   │   ├── services/         # Giao tiếp với server/MQTT
│   │   └── widgets/          # Widget hiển thị trạng thái, nút điều khiển
│   ├── device_control/
│   │   ├── screens/          # Màn hình điều khiển thiết bị on/off
│   │   ├── models/           # Model cho thiết bị on/off
│   │   ├── services/         # Giao tiếp với server/MQTT
│   │   └── widgets/          # Widget hiển thị trạng thái, nút on/off
│   ├── device_management/
│   │   ├── screens/          # Màn hình thêm thiết bị, quét QR, config Wi-Fi
│   │   ├── models/           # Model cho thiết bị và quyền
│   │   ├── services/         # API calls cho quản lý thiết bị
│   │   └── widgets/          # Widget cho quét QR, chia sẻ thiết bị
├── core/
│   ├── config/               # Cấu hình ứng dụng (API URL, MQTT broker...)
│   ├── models/               # Các model chung (Device, User...)
│   ├── services/             # Dịch vụ chung (HTTP client, MQTT client...)
│   └── utils/                # Các hàm tiện ích (logger, helper functions...)
├── main.dart                 # Điểm vào của ứng dụng

2.3. Giao diện người dùng (UI)

Màn hình chính

Công nghệ UI

3. Phương thức kết nối và giao tiếp với server

3.1. Giao thức kết nối

REST API Endpoints

Dưới đây là các endpoint chính (giả định):

GET    /api/devices                 # Lấy danh sách thiết bị của người dùng
POST   /api/devices/add            # Thêm thiết bị mới (gửi device ID từ QR)
POST   /api/devices/share          # Chia sẻ quyền điều khiển thiết bị
PUT    /api/devices/config-wifi    # Cấu hình Wi-Fi cho thiết bị
POST   /api/auth/register          # Đăng ký tài khoản
POST   /api/auth/login             # Đăng nhập
GET    /api/lockers/:id/status     # Lấy trạng thái smartlocker
POST   /api/lockers/:id/control    # Gửi lệnh đóng/mở locker
GET    /api/devices/:id/status     # Lấy trạng thái thiết bị on/off
POST   /api/devices/:id/control    # Gửi lệnh bật/tắt thiết bị

WebSocket cho hình ảnh

MQTT Topics

3.2. Quy trình xử lý hình ảnh

  1. Thiết bị gửi hình ảnh:

    • Smartlocker chụp ảnh từ camera và gửi lên server qua topic MQTT lockers/{device_id}/image (dữ liệu dạng base64 hoặc binary).

  2. Server trung chuyển:

    • Server nhận dữ liệu hình ảnh, không lưu trữ mà chuyển trực tiếp đến ứng dụng qua WebSocket.

    • Nếu ứng dụng yêu cầu hình ảnh theo nhu cầu (on-demand), server gửi yêu cầu qua MQTT đến thiết bị để chụp ảnh mới.

  3. Ứng dụng lưu trữ cục bộ:

    • Ứng dụng nhận dữ liệu hình ảnh qua WebSocket, lưu vào bộ nhớ cục bộ (local storage) sử dụng path_provider (thư mục tạm hoặc thư mục ứng dụng).

    • Hình ảnh được hiển thị trên UI và xóa khỏi local storage khi không cần thiết (ví dụ: sau khi người dùng đóng màn hình).

Lưu ý tối ưu hóa

3.3. Quy trình giao tiếp

  1. Đăng nhập/Đăng ký:
    • Gửi yêu cầu POST đến /api/auth/login hoặc /api/auth/register.
    • Nhận JWT token để xác thực các yêu cầu tiếp theo.
  2. Thêm thiết bị:
    • Quét mã QR để lấy device ID.
    • Gửi POST /api/devices/add với device ID và thông tin Wi-Fi (SSID, password).
    • Thiết bị nhận cấu hình Wi-Fi qua SmartConfig và kết nối với MQTT broker.
  3. Điều khiển thiết bị:
    • Gửi lệnh qua MQTT topic devices/{device_id}/control.
    • Nhận trạng thái qua topic devices/{device_id}/status.
  4. Stream hình ảnh:
    • Kết nối WebSocket đến server để nhận stream từ camera của smartlocker.
  5. Thông báo:
    • Server gửi thông báo qua FCM khi có sự kiện (locker bị mở trái phép, trạng thái thiết bị thay đổi).

4. Các biến số điều khiển

4.1. Biến trạng thái thiết bị

4.2. Model dữ liệu (Dart) 

class SmartLocker {
  final String deviceId;
  final String status; // "locked" or "unlocked"
  final bool hasItem;
  final bool alert;
  final String? imagePath; // Đường dẫn cục bộ đến hình ảnh

  SmartLocker({
    required this.deviceId,
    required this.status,
    required this.hasItem,
    required this.alert,
    this.imagePath,
  });

  factory SmartLocker.fromJson(Map<String, dynamic> json) {
    return SmartLocker(
      deviceId: json['device_id'],
      status: json['status'],
      hasItem: json['has_item'],
      alert: json['alert'],
      imagePath: json['image_path'],
    );
  }
}

class OnOffDevice {
  final String deviceId;
  final String status; // "on" or "off"

  OnOffDevice({
    required this.deviceId,
    required this.status,
  });

  factory OnOffDevice.fromJson(Map<String, dynamic> json) {
    return OnOffDevice(
      deviceId: json['device_id'],
      status: json['status'],
    );
  }
}

5. Quy trình phát triển chi tiết

5.1. Thiết lập môi trường

5.2. Triển khai giao tiếp WebSocket cho hình ảnh

Dưới đây là ví dụ code kết nối WebSocket trong Flutter để nhận hình ảnh:

import 'package:web_socket_channel/io.dart';
import 'package:path_provider/path_provider.dart';
import 'dart:io';

class ImageService {
  IOWebSocketChannel? channel;

  void connect(String deviceId) {
    channel = IOWebSocketChannel.connect('ws://server-url/lockers/$deviceId/image-stream');
    channel!.stream.listen((data) async {
      // Giả định data là base64 string
      final bytes = base64Decode(data);
      final directory = await getTemporaryDirectory();
      final file = File('${directory.path}/locker_$deviceId_${DateTime.now().millisecondsSinceEpoch}.jpg');
      await file.writeAsBytes(bytes);
      print('Image saved at: ${file.path}');
      // Cập nhật UI với đường dẫn file
    });
  }

  void disconnect() {
    channel?.sink.close();
  }
}

5.3. Lưu trữ và quản lý hình ảnh cục bộ

Ví dụ code lưu và xóa hình ảnh:

import 'package:path_provider/path_provider.dart';
import 'dart:io';

class LocalStorageService {
  // Lưu hình ảnh vào bộ nhớ tạm
  Future<String> saveImage(Uint8List imageBytes, String deviceId) async {
    final directory = await getTemporaryDirectory();
    final file = File('${directory.path}/locker_$deviceId_${DateTime.now().millisecondsSinceEpoch}.jpg');
    await file.writeAsBytes(imageBytes);
    return file.path;
  }

  // Xóa hình ảnh cũ để tiết kiệm bộ nhớ
  Future<void> cleanOldImages(String deviceId) async {
    final directory = await getTemporaryDirectory();
    final files = directory.listSync().where((file) => file.path.contains('locker_$deviceId')).toList();
    if (files.length > 5) { // Giới hạn 5 hình ảnh
      files.sort((a, b) => a.path.compareTo(b.path));
      for (var i = 0; i < files.length - 5; i++) {
        await File(files[i].path).delete();
      }
    }
  }
}

5.4. Tích hợp SmartConfig

Sử dụng esp_smartconfig để cấu hình Wi-Fi cho thiết bị:

import 'package:esp_smartconfig/esp_smartconfig.dart';

Future<void> configureWifi(String ssid, String password) async {
  final provisioner = Provisioner.espTouch();
  await provisioner.start(ProvisioningRequest(
    ssid: ssid,
    password: password,
  ));
  provisioner.listen((response) {
    print('Device connected: ${response.ipAddress}');
  });
}

5.5. Quét mã QR

Sử dụng qr_code_scanner để quét mã QR:

import 'package:qr_code_scanner/qr_code_scanner.dart';

class QRScannerScreen extends StatefulWidget {
  @override
  _QRScannerScreenState createState() => _QRScannerScreenState();
}

class _QRScannerScreenState extends State<QRScannerScreen> {
  final GlobalKey qrKey = GlobalKey(debugLabel: 'QR');
  QRViewController? controller;

  @override
  Widget build(BuildContext context) {
    return QRView(
      key: qrKey,
      onQRViewCreated: (QRViewController controller) {
        this.controller = controller;
        controller.scannedDataStream.listen((scanData) {
          print('Scanned QR: ${scanData.code}');
          // Gửi device ID tới server để thêm thiết bị
        });
      },
    );
  }
}

6. Bảo mật

7. Các bước triển khai tiếp theo

  1. Thiết kế database schema cho người dùng, thiết bị, và quyền truy cập.
  2. Triển khai backend server sử dụng Node.js, Python (FastAPI), hoặc Spring Boot.
  3. Tích hợp FCM để gửi thông báo đẩy.
  4. Kiểm thử giao tiếp MQTT và SmartConfig trên thiết bị thực tế.
  5. Triển khai tính năng stream hình ảnh qua WebSocket.