
Cet article se concentrera sur l'implémentation du client mobile Flutter.
Quel client mobile?
La publication précédente décrivait le système d'accessoires logiciels:
bobaoskit - accessoires, dnssd et WebSocket .
Un analogue d'un accessoire logiciel est un véritable objet. Ampoule, interrupteur, lecteur cd / cassette, radio, thermostat, capteur de température, capteur de mouvement, etc ... Un ensemble d'accessoires est déterminé par l'imagination et le code du programme. Vous pouvez implémenter au moins un échiquier. Pour une telle carte, vous devez avoir un champ de contrôle ( control
) move
qui prend un objet { from: "e2", to: "e4" }
par exemple, et des champs de service pour réinitialiser les chiffres, etc. ... Le script accessoire traitera la demande de contrôle du champ de move
, accepter la décision est de savoir s'il est possible de déplacer la figure, et retournera (ou non) le statut avec la position des figures dans tout le champ.
Les types d'accessoires actuellement pris en charge avec une fonctionnalité minimale sont les suivants: "interrupteur", "capteur de température", "thermostat", "lecteur radio".
Concernant les échecs, il n'y aura plus de discussion. Si c'est intéressant et dans ce cas, bienvenue au chat.
Donc bobaoskit.worker
. Des objets accessoires existent dans la mémoire de l'ordinateur, vous pouvez lire des informations à leur sujet, vous pouvez envoyer manuellement une demande JSON
au port WebSocket
et voir les événements entrants.
Pour la gestion, j'ai créé l'application mobile la plus simple.
Pourquoi papillonner?
Au cours des deux dernières années, une idée a vécu activement dans ma tête pour étudier la programmation pour les appareils mobiles. Depuis que j'écris en JavaScript
, j'ai étudié des solutions qui vous permettent de ne pas apprendre un nouveau langage de programmation.
Appcelerator
Il a commencé l'étude avec lui. Si la mémoire ne change pas, le SDK est ouvert, mais l'IDE avec différents tarifs.
NativeScript
Ici, j'ai déjà créé une application simple affichant une liste avec des photos. Cela n’est pas allé plus loin.
ReactNative
L'assaut le plus long des frameworks répertoriés jusqu'à présent. Le plus grand défi est de commencer. J'ai regardé le cours. Au début, c'est clair, intéressant, il s'avère. Mais le redux
et la surpuissance ont échoué. Ensuite, il essayait régulièrement de commencer à écrire, mais redux
obstinément ne se permettait pas de maîtriser.
En conséquence, je n'étais alors attaché à aucune décision (fin 2016). Peut-être parce qu'il n'y avait pas de tâche spécifique, peut-être pour d'autres raisons.
Plus près de la chute du passé (2018), des travaux étaient déjà en cours sur le sdk pour les accessoires logiciels. Naturellement, vous avez besoin d'une application mobile. Tout a commencé avec mdns. Une fois dans mon temps libre, j'ai mis à jour ReactNative, trouvé le plugin react-native-zeroconf, créé l'application. Selon les instructions, installé, fait un link
, lancé. L'application de débogage d'Expo a démarré, qui ne prend pas en charge les modules natifs et, par conséquent, le plug-in mdns n'a pas fonctionné. À ce stade, il n'y avait pas assez de temps libre pour créer une application native native React (et sans expo) et tester avec. Le travail a été reporté de quelques mois.
Dans le même temps, de plus en plus de documents sont apparus sur le flutter
sur le réseau. Je me suis installé. L'installation est simple: git clone
et ajoutez à PATH
. Le reste est déjà en train de configurer le SDK / Xcode Android (dans mon cas, le SDK Android est configuré depuis longtemps. Je ne peux pas développer pour iOS, parce que je ne suis pas un utilisateur macOS) et le SDK Dart (vous pouvez l'installer séparément, mais pas nécessairement, car il fait partie du flottement).
Principe / schéma de travail
- Une fois lancée, l'application recherche les services
_bobaoskit._tcp
sur le réseau local à l'aide du plugin flutter_mdns . Il existe plusieurs versions de ce plugin, toutes tirent leurs racines de celle publiée , mais il n'est pas compatible avec les nouvelles versions du SDK Dart, respectivement, de nombreuses compatibilité fourchues et ajoutées. J'ai choisi cette version car les autres n'ont pas résolu les hôtes de plusieurs services découverts à la fois.
Lors de la détection et de la détermination (onResolve), l'hôte est ajouté à la liste.
La page avec la liste des services découverts est StatefulWidget
, respectivement, lorsqu'elle détecte / perte de services, setState() {...}
est appelée - Lors du choix d'un hôte dans la liste, une nouvelle page est créée (également
StatefulWidget
), à laquelle l' host
et le port
service sélectionné sont transmis.
L'objet BobaosKit
responsable des communications est créé. Les réponses sont traitées par le biais de rappels, comme alors que je n'ai pas beaucoup étudié la fléchette asynchrone. Mais à en juger par la documentation numérisée, Futures
est un analogue de Promise
in JS.
Les fonctions sont enregistrées pour les événements entrants (pas de réponse). EventEmitter
pour Dart ici. J'ai écrit mon très simple.
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']); } }); }
Événements entrants - si l'accessoire a été supprimé, ajouté, état mis à jour. Ou si tous les accessoires sont retirés ( clear accessories
).
Fonctions enregistrées - pour la mise à jour des listes, des widgets pour ces événements.
- Une demande est envoyée pour des informations sur tous les accessoires.
Un objet AccessoryInfo est créé pour chaque accessoire:
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(); } }
cet objet est déjà un modèle. Initialement, j'ai écrit StatefulWidget
et setState() {}
partout, mais setState() {}
ne fonctionne que pour un widget dans lequel les écouteurs sont enregistrés. Mais pour une gestion détaillée des accessoires, j'ai créé initialement de nouvelles pages avec état, et j'ai remarqué que le statut n'était pas mis à jour. Comme solution - utilisé ScopedModel
.
Après réception de la liste des accessoires, nous envoyons pour chacun d'eux une demande de statut et ajoutons à la liste List <AccessoryInfo>
. Appelez setState() {}
, ajoutant ainsi un accessoire pris en charge à l'interface. Les types d'accessoires pris en charge sont définis dans ListView.builder
et dans ./lib/widgets/*.dart
. switch/temperature sensor/radio player/thermostat
actuellement pris en charge. Le principal travail à venir est d'en ajouter de nouveaux et d'améliorer les widgets existants.
- Maintenant, comment créer des éléments séparés pour chaque accessoire. Par exemple, considérons un interrupteur.
@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 }, )); })); }
Pour un accessoire de type switch
, un élément est créé dans la liste générale des accessoires, lors de l'interaction avec lequel (onTap) une requête est envoyée pour obtenir la valeur courante, puis pour commuter cette valeur. ScopedModel
permet de redessiner le widget pour les mises à jour d'état entrantes.
Un gestionnaire de clic long n'est pas implémenté pour cet accessoire.
Pour un lecteur radio, cela ressemble à ceci:
onLongPress: () { Navigator.of(context).push(MaterialPageRoute( builder: (context) => AccRadioPlayerControl( info: info, bobaos: bobaos, ))); },
La page AccRadioPlayerControl
, qui utilise également ScopedModel
pour gérer l'état.
Sur ce point, la description complète de l'algorithme de fonctionnement du programme est épuisée. Aucune fonctionnalité supplémentaire, telle que la mémorisation du dernier hôte, le tri des accessoires en catégories / chambres n'est implémentée. Pour le moment, tout est simple.
Les problèmes
Je vais décrire le principal problème qui se pose actuellement. Je ne comprends toujours pas comment détecter une connexion WebSocket
rompue.
J'utilise: classe WebSocket .
Lorsque l'application / l'appareil est en mode veille pendant une longue période, la connexion est déconnectée. Vous devez revenir à la toute première page et rouvrir le service découvert.
Postface
D'une part, Flutter est assez rapide pour apprendre et se développer. ScopedModel s'est avéré plus compréhensible pour moi que redux.
Dart s'est avéré être similaire à JavaScript familier. Taper + types dynamiques permettra à tout le monde d'écrire de manière aussi pratique.
Difficultés à écrire du code: imbrication importante des widgets. L'enfer de rappel bien connu après le flutter est différent. Le mode Vim et %
seront utiles.
Maintenant, quelques réflexions sur l'IoT. Récemment, de plus en plus d'appareils / services intelligents qui nécessitent une inscription dans le cloud. Points de vente chinois, pour l'utilisation desquels vous devez installer l'application, créer un compte, et seulement après cela, vous pouvez l'utiliser.
Assistants vocaux. Alice de Yandex a besoin de son cloud dans lequel le texte reconnu est envoyé. Alexa d'Amazon fonctionne de manière similaire.
Le plus réussi, à mon avis, est fait par Apple HomeKit en collaboration avec Siri. Le cloud est utilisé pour la reconnaissance de texte. Interaction avec les appareils - dans le réseau local.
Mon avis est que le cloud doit exister pour sa fonction: télécommande, mise à jour, etc ... Si l'appareil peut être contrôlé sur un réseau local, alors vous devez le faire.
Les références
- Référentiel d'applications
- Documentation de Bobaoskit - décrit comment installer bobaoskit.worker et lancer l'accessoire du
radio player
.