Bootstrap

Flutter封装Coap

前言

我们根据Coap数据通信流程写一个公共组件,用户只要在原本的组件外嵌套这个公共组件就可以使用Coap的功能,这样做更加的方便便捷。

具体步骤

封装一个udp函数

  • 创建一个工厂函数,工厂函数初始化时监听广播数据
  • 发送广播函数:入参有发送的内容,目标的ip地址(默认是255.255.255.255)、ip端口(默认端口为1234)
  • import 'dart:async';
    import 'dart:io';
     
    import 'package:my_app/common/value/serve.dart';
     
    class UDPClient {
      factory UDPClient() => _getInstance();
      static UDPClient get instance => _getInstance();
      static UDPClient? _instance;
      late RawDatagramSocket udpSocket;
      final StreamController<List<int>> _getDataController =
          StreamController.broadcast(); //监听数据流的控制器
      StreamController get getDataController => _getDataController; //获取数据流的控制器
     
      //初始化
      static UDPClient _getInstance() {
        _instance ??= UDPClient._internal();
        return _instance!;
      }
     
      //创建一个UDPClient实例
      UDPClient._internal() {
        (InternetAddress.lookup('pool.ntp.org')).then((value) {
          var serverAddress = value.first;
          // print("获取到的数据:----${serverAddress.type}-----${InternetAddress.anyIPv4}");
          RawDatagramSocket.bind(
                  serverAddress.type == InternetAddressType.IPv6
                      ? InternetAddress.anyIPv6
                      : InternetAddress.anyIPv4,
                  0)
              .then((value) {
            udpSocket = value;
            udpSocket.listen(handleUDPDatagram);
            udpSocket.broadcastEnabled = true;
          });
        });
      }
      //断开连接
      void disconnectFromUDP() {
        udpSocket.close();
        _instance = null;
      }
     
      //监听到的数据
      void handleUDPDatagram(RawSocketEvent event) {
        if (event == RawSocketEvent.read) {
          Datagram? datagram = udpSocket.receive();
          if (datagram != null) {
            List<int> data = datagram.data;
            // print("广播接收内容:$data");
            _getDataController.sink.add(data);
          }
        }
      }
     
      //发送数据
      void sendUDPData(
        List<int> data, {
        String ip = '255.255.255.255',
        int port = UDP_PORT,
      }) {
        print("${InternetAddress(ip)}");
        udpSocket.send(data, InternetAddress(ip), port);
      }
    }

封装一个Coap函数

  • 创建一个Coap的工厂函数,可以传入ip地址和端口号
  • 如果ip、port改变了则会创建一个工厂函数
  • 封装Get、post、postBytes、put函数
import 'dart:async';
 
import 'package:coap/coap.dart';
import 'package:my_app/common/value/serve.dart';
import 'package:typed_data/typed_data.dart';
 
import '../value/coap_config.dart';
 
class CoapClientUtil {
  factory CoapClientUtil({String? host, int? port}) =>
      _getInstance(host: host, port: port);
  static CoapClientUtil get instance => _getInstance(host: _currentHost);
  static CoapClientUtil? _instance;
  static CoapClient? client;
  static String _currentHost = COAP_API_URL;
  static int _currentPort = COAP_PORT;
 
  static CoapClientUtil _getInstance({String? host, int? port}) {
    String localHost = host ?? COAP_API_URL;
    int localPort = port ?? COAP_PORT;
    if (_instance == null ||
        _currentHost != localHost ||
        _currentPort != localPort) {
      _instance = CoapClientUtil._internal(localHost, localPort);
      _currentHost = localHost;
      _currentPort = localPort;
    }
    return _instance!;
  }
 
  CoapClientUtil._internal(String host, int port) {
    CoapConfig conf = CoapConfig();
    var baseUri = Uri(scheme: 'coap', host: host, port: port);
    client = CoapClient(baseUri, config: conf);
  }
 
  // 发送GET请求
  Future<CoapResponse?> get(
    final String path, {
    final CoapMediaType? accept,
    final bool confirmable = true,
    final List<Option<Object?>>? options,
    final bool earlyBlock2Negotiation = false,
    final int maxRetransmit = 0,
    final CoapMulticastResponseHandler? onMulticastResponse,
  }) async {
    try {
      var response = await client!.get(
        path,
        accept: accept,
        confirmable: confirmable,
        options: options,
        earlyBlock2Negotiation: earlyBlock2Negotiation,
        maxRetransmit: maxRetransmit,
        onMulticastResponse: onMulticastResponse,
      );
      return response;
    } catch (e) {
      print("错误的内容:${e}");
      return null;
    }
  }
 
  // 发送POST请求
  Future<CoapResponse?> post(
    final String path, {
    required final String payload,
    final CoapMediaType? format,
    final CoapMediaType? accept,
    final bool confirmable = true,
    final List<Option<Object?>>? options,
    final bool earlyBlock2Negotiation = false,
    final int maxRetransmit = 0,
    final CoapMulticastResponseHandler? onMulticastResponse,
  }) async {
    try {
      var response = await client!.post(
        path,
        payload: payload,
        format: format,
        accept: accept,
        confirmable: confirmable,
        options: options,
        earlyBlock2Negotiation: earlyBlock2Negotiation,
        maxRetransmit: maxRetransmit,
        onMulticastResponse: onMulticastResponse,
      );
      return response;
    } catch (e) {
      print("错误的内容:${e}");
      return null;
    }
  }
 
  /// 发送post请求,且携带的参数为二进制数组
  /// 需要注意的是如果返回的数据也是二进制数组则打印的response中的Payload为<<<< Payload incomplete >>>>>
   这是因为展示的payload走的是res.payloadString,看下发源码可知,转换成utf8抛出异常了,我们只要拿数据的时候使用res.payload即可
  /// String get payloadString {
  ///   final payload = this.payload;
  ///   if (payload.isNotEmpty) {
  ///     try {
  ///       final ret = utf8.decode(payload);
  ///       return ret;
  ///     } on FormatException catch (_) {
  ///       // The payload may be incomplete, if so and the conversion
  ///       // fails indicate this.
  ///       return '<<<< Payload incomplete >>>>>';
  ///     }
  ///   }
  ///   return '';
  /// }
 
  Future<CoapResponse?> postBytes(
    final String path, {
    required final Uint8Buffer payload,
    final CoapMediaType? format,
    final CoapMediaType? accept,
    final bool confirmable = true,
    final List<Option<Object?>>? options,
    final bool earlyBlock2Negotiation = false,
    final int maxRetransmit = 0,
    final CoapMulticastResponseHandler? onMulticastResponse,
  }) async {
    try {
      var response = await client!.postBytes(
        path,
        payload: payload,
        format: format,
        accept: accept,
        confirmable: confirmable,
        options: options,
        earlyBlock2Negotiation: earlyBlock2Negotiation,
        maxRetransmit: maxRetransmit,
        onMulticastResponse: onMulticastResponse,
      );
      return response;
    } catch (e) {
      print("错误的内容:${e}");
      return null;
    }
  }
 
  // 发送PUT请求
  Future<CoapResponse?> put(
    final String path, {
    required final String payload,
    final CoapMediaType? format,
    final CoapMediaType? accept,
    final bool confirmable = true,
    // final List<Uint8Buffer>? etags,
    final MatchEtags matchEtags = MatchEtags.onMatch,
    final List<Option<Object?>>? options,
    final bool earlyBlock2Negotiation = false,
    final int maxRetransmit = 0,
    final CoapMulticastResponseHandler? onMulticastResponse,
  }) async {
    try {
      var response = await client!.put(
        path,
        payload: payload,
        format: format,
        accept: accept,
        confirmable: confirmable,
        // etags: etags,
        matchEtags: matchEtags,
        options: options,
        earlyBlock2Negotiation: earlyBlock2Negotiation,
        maxRetransmit: maxRetransmit,
        onMulticastResponse: onMulticastResponse,
      );
      return response;
    } catch (e) {
      print("错误的内容:${e}");
      return null;
    }
  }
 
  close() {
    client?.close();
  }
 
  disply() {
    client?.close();
    client = null;
    _instance = null;
  }
}

封装一个通用组件

  • 传参内容:mac地址、Widget、key
    • mac地址:因为通信协议中地址需要使用mac地址
    • Widget:要展示的界面
    • key:根据key值调用封装组件中的函数
  • 初始化逻辑:Coap数据通信流程
  • 如果要进行界面跳转
    • 调用setDispose,关闭coap、udp等信息
    • 同步界面跳转
    • 创建一个定时器,定时器中包含初始化udp、coap
import 'dart:async';
import 'dart:convert';
 
import 'package:coap/coap.dart';
import 'package:connectivity_plus/connectivity_plus.dart';
import 'package:flutter/material.dart';
import 'package:my_app/common/utils/coap.dart';
import 'package:my_app/common/utils/fun.dart';
import 'package:my_app/common/utils/udp.dart';
import 'package:my_app/pages/device_settings/time_switch_addtime/bottom_picker.dart';
import 'package:typed_data/typed_data.dart';
 
///macStr 是测试的,后期要改
class CoapClientPackage extends StatefulWidget {
  const CoapClientPackage({
    super.key,
    this.mac = '11:22:33:44:55:66',
    required this.widget,
  });
  final String mac;
  final Widget widget;
 
  @override
  State<CoapClientPackage> createState() => CoapClientPackageState();
}
 
class CoapClientPackageState extends State<CoapClientPackage> {
  StreamSubscription? _networkStatusSubscription; //监听设备的网络类型
  CoapClientUtil? coapClient;
 
  //udp获取到的数据
  UDPClient? udpClient;
  StreamSubscription? _sacnSubscription;
  bool isCoap = false;
  Timer? timer;
  @override
  void initState() {
    super.initState();
    // initUDP();
 
    // //监听移动终端联网方式
    // _listenNetworkStatus();
    getinitState();
  }
 
  getinitState() {
    print("初始化");
    initUDP();
    //监听移动终端联网方式
    _listenNetworkStatus();
  }
 
  setDispose() {
    print("移除");
    _networkStatusSubscription?.cancel(); //取消监听
    coapClient?.disply(); //关闭coap连接
    udpClient?.disconnectFromUDP(); //关闭udp连接
    _sacnSubscription?.cancel(); //取消监听
    timer?.cancel(); //取消定时器
  }
 
  @override
  void dispose() {
    setDispose();
    super.dispose();
  }
 
  initUDP() {
    udpClient = UDPClient.instance;
    _sacnSubscription?.cancel();
    _sacnSubscription = udpClient?.getDataController.stream.listen((data) {
      if (data is List<int>) {
        print("这是哪个数据:$data");
        setState(() {
          isCoap = false;
        });
        switch (data[2]) {
          case 129: //
            if (data[0] == 170 && data.length == 15 && data[14] == 85) {
              //将从data[7]开始截取数组
              // List macArr = data.sublist(3, 9); //截取mac地址
              // //将十进制数组转换成十六进制mac并添加:
              // String macStr =
              //     macArr.map((e) => e.toRadixString(16).padLeft(2, '0')).join(':');
              // print("macArr:$macArr -----${macStr}");
              timer?.cancel();
              String serverIp =
                  "${data[9]}.${data[10]}.${data[11]}.${data[12]}";
 
              print("获取到了ip地址:$serverIp");
              //创建一个coap服务
              coapClient = CoapClientUtil(
                host: serverIp,
                port: 5683,
              );
              setState(() {
                isCoap = true;
              });
            }
 
            break;
          default:
        }
      }
    });
  }
 
  //监听移动终端联网方式
  void _listenNetworkStatus() async {
    _networkStatusSubscription?.cancel(); //取消之前的监听
    bool isWif = await isWifi();
    if (isWif) {
      isWifiAfter();
    } else {
      _networkStatusSubscription = Connectivity()
          .onConnectivityChanged
          .listen((ConnectivityResult result) {
        if (result == ConnectivityResult.wifi) {
          //当前的类型是WiFi
          isWifiAfter();
        } else {
          setState(() {
            isCoap = false;
          });
        }
      });
    }
  }
 
  isWifiAfter() {
    print('当前的类型是WiFi');
 
    //这里需要在发送mac地址,这里使用模拟的数据
    String macStr = widget.mac;
    // String macStr = '11:22:33:44:55:66';
 
    //将macStr装换成List<int>
    List<int> macArr =
        macStr.split(':').map((e) => int.parse(e, radix: 16)).toList();
    List<int> sendData = sendCoapData('01', macArr);
    print("-----=====------macArr:${sendData},macStr");
    Timer(Duration(seconds: 1), () {
      udpClient?.sendUDPData(sendData, port: 1234);
    });
    timer?.cancel();
    timer = Timer.periodic(Duration(seconds: 3), (timer) {
      if (udpClient != null) {
        try {
          udpClient?.sendUDPData(sendData, port: 1234);
        } catch (e) {
          print("发送出现了问题:${e}");
        }
      } else {
        print("为空");
      }
    });
  }
 
  //发送coap请求
  Future<CoapResponse?> sendCoap(String payload) async {
    String macStr = widget.mac;
    // String macStr = '11:22:33:44:55:66';
    var res = await coapClient?.post('/api/v1/$macStr/rpc',
        accept: CoapMediaType.applicationJson, payload: payload);
    return res;
  }
 
  //发送透传数据
  Future<CoapResponse?> sendTranCoap(String payload) async {
    String macStr = widget.mac;
    // String macStr = '11:22:33:44:55:66';
    var res = await coapClient?.post('/api/v1/$macStr/trans',
        accept: CoapMediaType.applicationJson, payload: payload);
    return res;
  }
 
  //发送透传数据
  Future<CoapResponse?> sendTranCoap1(Uint8Buffer payload) async {
    String macStr = widget.mac;
    // String macStr = '11:22:33:44:55:66';
    var res = await coapClient?.postBytes('/api/v1/$macStr/trans',
        accept: CoapMediaType.applicationJson, payload: payload);
    return res;
  }
 
  @override
  Widget build(BuildContext context) {
    return widget.widget;
  }
}

;