
本文将重点介绍Flutter移动客户端的实现。
哪个移动客户端?
先前的出版物描述了软件附件的系统:
bobaoskit-附件,dnssd和WebSocket 。
软件附件的类似物是真实的对象。 灯泡,开关,CD /盒带播放器,收音机播放器,恒温器,温度传感器,运动传感器等。...一组配件取决于想象力和程序代码。 您可以至少实现一个棋盘。 对于这样的板,您需要有一个控制字段( control
) move
,它带有一个对象{ 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
),所选服务的host
和port
将发送到该页面。
创建负责通讯的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(); } }
该对象已经是模型。 最初,我到处都写了StatefulWidget
和setState() {}
,但是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一起制造的。 云用于文本识别。 与设备的交互-在局域网中。
我的观点是,云必须出于其目的而存在:远程控制,更新等。。。如果可以在本地网络上控制设备,则需要这样做。
参考文献
- 应用程序存储库
- Bobaoskit文档 -描述如何安装bobaoskit.worker并启动
radio player
附件。