bobaflu-颤振编程配件


本文将重点介绍Flutter移动客户端的实现。


哪个移动客户端?


先前的出版物描述了软件附件的系统:
bobaoskit-附件,dnssd和WebSocket


软件附件的类似物是真实的对象。 灯泡,开关,CD /盒带播放器,收音机播放器,恒温器,温度传感器,运动传感器等。...一组配件取决于想象力和程序代码。 您可以至少实现一个棋盘。 对于这样的板,您需要有一个控制字段( controlmove ,它带有一个对象{ from: "e2", to: "e4" } ,以及用于重置图形的服务字段等。...附件脚本将处理控制move字段的请求,接受决定是否可以移动图形,并将在整个字段中返回(或不返回)图形位置的状态。


当前支持的功能最少的附件类型如下:“开关”,“温度传感器”,“恒温器”,“无线电播放器”。


关于国际象棋,将不再进一步讨论。 如果很有趣,在这种情况下,欢迎光临。


因此, bobaoskit.worker正在运行。 附件对象存在于计算机的内存中,您可以阅读有关它们的信息,可以将JSON请求手动发送到WebSocket端口,并查看传入的事件。


为了管理,我制作了最简单的移动应用程序。


为什么扑扑?


在过去的几年中,一个想法一直浮在脑海中,以研究移动设备的编程。 自从我用JavaScript编写以来,我研究了一些解决方案,这些解决方案使您无法学习新的编程语言。


Appcelerator 他和他一起开始学习。 如果内存没有更改,则打开SDK,但是IDE具有各种收费标准。
NativeScript 在这里,我已经创建了一个简单的应用程序,其中显示了带有图片的列表。 并没有进一步。
ReactNative 迄今为止列出的框架中最长的攻击。 最大的挑战是入门。 我看着课程。 最初,结果很明显很有趣。 但是redux和过载后失败了。 然后,他定期尝试开始写作,但顽固地使redux不能压倒一切。


结果,那时(2016年底)我没有任何决定。 也许是因为没有特定的任务,或者是由于其他原因。


接近过去的秋天(2018年),软件配件SDK已在进行中。 自然,您需要一个移动应用程序。 一切始于mdns。 在业余时间,我更新了ReactNative,找到了react-native-zeroconf插件,创建了应用程序。 根据说明,安装完毕,做了一个link ,启动了。 启动了Expo调试应用程序,该应用程序不支持本机模块,因此mdns插件不起作用。 在这一点上,没有足够的空闲时间来创建干净的(没有expo)反应本地应用程序并对其进行测试。 这项工作被推迟了几个月。


同时,关于网络flutter材料越来越多。 我自己安装了。 安装很简单: git clone并添加到PATH 。 其余的已经设置了Android SDK / Xcode(就我而言,Android SDK已经配置了很长时间。由于我不是macOS用户,所以我无法为iOS开发)和Dart SDK(您可以单独安装,但不一定安装,因为它是flutter的一部分)。


工作原理/方案


  • 启动后,该应用程序使用flutter_mdns插件在本地网络上搜索_bobaoskit._tcp服务。 此插件有多种版本,它们均源于已发布的版本,但分别与Dart SDK的新版本不兼容,许多分支和添加的兼容性。 我之所以选择此版本,是因为其他版本无法一次解析多个发现的服务的主机。
    根据检测和确定(onResolve),将主机添加到列表中。
    包含发现的服务列表的页面分别是StatefulWidget ,当它检测到/服务丢失时,将调用setState() {...}
  • 从列表中选择主机时,将创建一个新页面(也称为StatefulWidget ),所选服务的hostport将发送到该页面。
    创建负责通讯的BobaosKit对象。 响应通过回调进行处理,如下 虽然我没有研究太多异步飞镖。 但是从扫描的文档来看, Futures是JS中Promise的类似物。
    记录传入事件的功能(无响应)。 在EventEmitter寻找Dart的EventEmitter 。 我写的很简单。

  void registerListener(String name, Function cb) { this._events.add(new BobaosKitCallback(name, cb)); } void removeAllListeners() { this._events = []; } void emitEvent(String name, dynamic params) { // call all listeners List<BobaosKitCallback> foundCallbacks = this._events.where((t) => t.name == name).toList(); foundCallbacks.forEach((f) => f.cb(params)); } ... ... void listen() { this.ws.listen((text) { var json = jsonDecode(text); if (json.containsKey('response_id')) { .... } else { //   response_id -  this.emitEvent(json['method'], json['payload']); } }); } 

传入事件-如果附件已卸下,添加,更新状态。 或者如果所有附件都已卸下( clear accessories )。


注册功能-用于更新列表,这些事件的小部件。


  • 发送请求以获取有关所有附件的信息。

为每个附件创建一个AccessoryInfo对象:


 import 'package:scoped_model/scoped_model.dart'; // AccessoryInfo extends Model // so, when accessory value is updated it descends down to // all widgets inside ScopedModelDescendant class AccessoryInfo extends Model { dynamic id; dynamic type; String name; String job_channel; List control; List status; bool selected; Map<dynamic, dynamic> currentState; AccessoryInfo(Map<dynamic, dynamic> obj) { this.id = obj['id']; this.type = obj['type']; this.name = obj['name']; this.job_channel = obj['job_channel']; this.control = obj['control']; this.status = obj['status']; this.currentState = {}; } void updateCurrentState(key, value) { currentState[key] = value; notifyListeners(); } void notify() { notifyListeners(); } } 

该对象已经是模型。 最初,我到处都写了StatefulWidgetsetState() {} ,但是setState() {}仅适用于在其中注册了侦听器的小部件。 但是为了进行详细的附件管理,我最初创建了新的Stateful页面,并注意到状态没有更新。 作为解决方案,使用了ScopedModel


收到附件列表后,我们将为每个附件发送状态请求,并将其添加到列表List <AccessoryInfo> 。 调用setState() {} ,从而将支持的附件添加到接口。 受支持的附件类型在ListView.builder./lib/widgets/*.dart中定义。 当前支持的switch/temperature sensor/radio player/thermostat 。 未来的主要工作是添加新的和改进现有的小部件。


  • 现在介绍如何为每个附件创建单独的元素。 例如,考虑一个开关。

  @override Widget build(BuildContext context) { return new ScopedModel<AccessoryInfo>( model: info, child: ScopedModelDescendant<AccessoryInfo>( builder: (context, child, model) { var cardColor = Theme.of(context).cardColor; dynamic switchState = model.currentState['state']; if (switchState is bool) { if (switchState) { cardColor = Colors.deepPurple; } else { cardColor = Theme.of(context).cardColor; } } return Card( color: cardColor, child: ListTile( selected: false, leading: new Icon(Icons.lightbulb_outline), title: new Text("${model.name}"), onTap: () { // to control accessory value // get status value at first bobaos.getStatusValue( model.id, "state", (bool err, Object payload) { if (err) { return print('error ocurred $payload'); } if (payload is Map) { dynamic currentValue = payload['status']['value']; bool newValue; if (currentValue is bool) { // invert newValue = !currentValue; } else { newValue = false; } // then send new value bobaos.controlAccessoryValue( model.id, {"state": newValue}, (bool err, Object payload) { if (err) { return print('error ocurred $payload'); } }); } }); }, onLongPress: () { // TODO: dialog with additional funcs }, )); })); } 

对于switch类型的附件,将在附件的常规列表中创建一个元素,当与之交互时(onTap),将发送一个请求以获取当前值,然后切换该值。 ScopedModel允许ScopedModel重新绘制窗口小部件以进行传入状态更新。


没有为此配件实现长按处理程序。


对于广播播放器,如下所示:


  onLongPress: () { Navigator.of(context).push(MaterialPageRoute( builder: (context) => AccRadioPlayerControl( info: info, bobaos: bobaos, ))); }, 

AccRadioPlayerControl页面,该页面还使用ScopedModel来管理状态。


在此,对程序操作算法的整个描述都用尽了。 没有实现其他功能,例如记住最后一个主机,将配件分类到类别/房间中。 目前,一切都很简单。


问题所在


我将描述现在的主要问题。 我仍然不明白如何检测断开的WebSocket连接。


我使用: WebSocket类


当应用程序/设备长时间处于睡眠模式时,连接断开。 您必须返回到第一页并重新打开发现的服务。


后记


一方面,Flutter学习和发展非常快。 事实证明,ScopedModel比redux更易于理解。
事实证明,Dart与熟悉的JavaScript类似。 键入+动态类型将使每个人都能方便地编写代码。


编写代码的困难:小部件的大量嵌套。 扑朔迷离后,众所周知的回调地狱看起来有所不同。 Vim模式和%将很有用。


现在有关物联网的一些想法。 最近,越来越多的需要在云中注册的智能设备/服务。 需要安装应用程序才能使用的中国网点,请创建一个帐户,然后您才能使用它。


语音助手。 Yandex的Alice要求将识别的文本发送到她的云中。 亚马逊的Alexa的工作方式与此类似。


在我看来,最成功的是Apple HomeKit与Siri一起制造的。 云用于文本识别。 与设备的交互-在局域网中。


我的观点是,云必须出于其目的而存在:远程控制,更新等。。。如果可以在本地网络上控制设备,则需要这样做。


参考文献


  1. 应用程序存储库
  2. Bobaoskit文档 -描述如何安装bobaoskit.worker并启动radio player附件。

Source: https://habr.com/ru/post/zh-CN440088/


All Articles