2017 habe ich begonnen, ein Projekt über nodejs zu schreiben - eine Implementierung des Weinzierl ObjectServer-Protokolls für den Zugriff auf KNX-Werte. Während des Schreibprozesses haben wir Folgendes studiert: Arbeiten mit Binärprotokollen, Präsentieren von Daten, Arbeiten mit Sockets (insbesondere Unix-Sockets), Arbeiten mit Redis-Datenbanken und Pub / Sub-Kanälen.
Das Projekt hat eine stabile Version erreicht. Zu diesem Zeitpunkt wähle ich langsam andere Sprachen aus, insbesondere Dart und Flutter als seine Anwendung. Auf dem Regal ohne Aktion bestäubt zum Zeitpunkt des Studentenhandbuchs G. Schildt.
Ein hartnäckiger Gedanke, das Projekt in C umzuschreiben, kam mir in den Sinn. Ich betrachte Optionen Go, Rust und stoße andere syntaktische Konstruktionen ab. Es gibt keine Möglichkeit zu beginnen, die Idee wird für eine Weile verschoben.
Im Mai dieses Jahres habe ich mich entschlossen, die Sprache D zu betrachten, aus irgendeinem Grund davon überzeugt, dass der Buchstabe D dynamisch bedeutet. Ich habe mich lange gefragt, wo und warum dieser Gedanke in meinem Kopf war, also habe ich keine Antwort gefunden. ABER das ist nicht mehr wichtig, da ich den ganzen Sommer über vom Umschreiben mitgerissen wurde.
Die Essenz des Projekts
KNX BAOS 830/832/838 Module werden über UART mit einem Computer verbunden, das ObjectServer-Protokoll ist in FT1.2 eingeschlossen. Die Anwendung stellt eine Verbindung mit /dev/ttyXXX
, verarbeitet eingehende Daten, sendet die in den PUB / SUB-Kanal konvertierten Bytes der Benutzeranforderung aus JSON-Nachrichten an dieselbe Warteschlange oder basierend auf Redis-Listen an die Jobwarteschlange (für Knotenjs werden Warteschlangen mithilfe des Bee-Queue-Pakets implementiert )
queue.on("job", data => {
Dynamik
JSON in js ist eine native Sache, ich hatte keine Ahnung, wie die Verarbeitung in statisch typisierten Sprachen erfolgt. Wie sich herausstellte, ein kleiner Unterschied. Nehmen Sie zum Beispiel die Methode get value
. Als Argumente wird entweder eine Zahl verwendet - die Datumspunktnummer oder ein Array von Zahlen.
In js werden Überprüfungen durchgeführt:
if (Array.isArray(payload)) {
Im Wesentlichen das gleiche auf D:
if (payload.type() == JSONType.integer) {
Aus irgendeinem Grund hat mich zum Zeitpunkt von Rust-a-Überlegungen das mangelnde Verständnis für die Arbeit mit JSON verlangsamt. Ein weiterer Punkt im Zusammenhang mit der Dynamik: Arrays. In js gewöhnt man sich daran, dass es ausreicht, die push
Methode aufzurufen, um ein Element hinzuzufügen. In C wird Dynamik durch manuelle Zuweisung von Speicher implementiert, aber ich wollte dort nicht wirklich klettern. Wie sich herausstellte, unterstützt Dlang dynamische Arrays.
ubyte[] res;
Eingehende UART-Daten in js wurden in Object
konvertiert. Für diese Zwecke sind Strukturen, Aufzählungen mit Werten und Verknüpfungen in D großartig.
enum OS_Services { unknown, GetServerItemReq = 0x01, GetServerItemRes = 0x81, SetServerItemReq = 0x02, SetServerItemRes = 0x82,
Mit einer eingehenden Nachricht:
ubyte mainService = data.read!ubyte(); ubyte subService = data.read!ubyte(); try { if (mainService == OS_MainService) { switch(subService) { case OS_Services.GetServerItemRes: result.direction = OS_MessageDirection.response; result.service= OS_Services.GetServerItemRes; result.success = true; result.server_items = _processServerItemRes(data); break; case OS_Services.SetServerItemRes: result.direction = OS_MessageDirection.response;
In js habe ich die Bytewerte in einem Array gespeichert, mit eingehenden Daten eine Suche durchgeführt und eine Zeichenfolge mit dem Namen des Dienstes zurückgegeben. Strukturen, Aufzählungen und Assoziationen sehen strenger aus.
Arbeiten Sie mit Byte-Daten-Arrays
Node.js Ich mag die Abstraktion von Buffer
. Beispiel: Es ist praktisch, die Konvertierung von zwei Bytes in eine vorzeichenlose Ganzzahl mithilfe der Methode readUInt16BE(offset)
, um - writeUInt16BE(value, offset)
, aktiv verwendete Puffer für die Arbeit mit dem Binärprotokoll zu schreiben. Für dlang habe ich zunächst wollige Repository-Pakete mit etwas Ähnlichem gestartet. Die Antwort wurde in der Standardbibliothek std.bitmanip
. Für vorzeichenlose Ganzzahlen mit einer Länge von 2 Bytes: ushort start = data.read!ushort()
, zum Schreiben: result.write!ushort(start, 2);
wobei das 2. Argument der Offset ist.
EE, Versprechen, asynchron / warten.
Das Schlimmste war das Programmieren ohne EventEmitter
. In node.js werden Listener-Funktionen einfach registriert und bei einem Ereignis aufgerufen. Man muss also nicht lange nachdenken. Die dyl-Pakete tinylis und serialport
(Abhängigkeiten meiner Anwendung) verfügen über nicht blockierende Methoden zur Verarbeitung von Nachrichten. Die Lösung ist einfach: Im Moment ist es richtig, Nachrichten über serielle Schnittstelle und Pub / Sub-Kanal nacheinander zu empfangen. Im Falle einer eingehenden Benutzeranforderung an den Pub / Sub-Kanal sollte das Programm eine Nachricht an die serielle Schnittstelle senden, das Ergebnis abrufen und den Benutzer an Pub / Sub zurücksenden. Es wurde beschlossen, die Methoden für die Blockierung serieller Anforderungen festzulegen.
while(!(_responseReceived || _resetInd || _interrupted)) { try { processIncomingData(); processIncomingInterrupts(); if (_resetInd || _interrupted) { _response.success = false; _response.service = OS_Services.unknown; _response.error = Errors.interrupted; _responseReceived = true; _ackReceived = true; }
In einer while-Schleife werden Daten von der nicht blockierenden Methode processIncomingData()
abgefragt. Die Wahrscheinlichkeit, dass das KNX-Modul neu gestartet wird (getrennt und wieder mit dem KNX-Bus oder der KNX-Software verbunden), wird ebenfalls angegeben. Außerdem überprüft der Handler processIncomingInterrupts()
den Service-Pub / Sub-Kanal auf eine reset
. Keine Versprechen oder asynchrone Funktionen, im Gegensatz zu früheren js-Implementierungen. Ich musste über die Struktur des Programms nachdenken (nämlich über die Reihenfolge der Funktionsaufrufe), aber aufgrund des Fehlens unnötiger Abstraktionen wurde das Programmieren einfacher. Wenn im js-Code das await someAsyncMethod
aufgerufen wird, wird die asynchrone Funktion als Blockierung aufgerufen und durchläuft die Ereignisschleife. Die Möglichkeit der Sprache ist gut, aber Sie können darauf verzichten.
Unterschiede
Jobwarteschlange. Die Implementierung von node.js verwendet zu diesem Zweck das bee-queue
Paket. In einer Implementierung auf D werden Anforderungen nur über pub / sub gesendet.
Ansonsten ist fast alles identisch.
Die kompilierte Version verbraucht zehnmal weniger RAM, was für Einplatinencomputer wichtig sein kann.
Zusammenstellung
Die Kompilierung wurde mit ldc auf der aarch64-Plattform durchgeführt.
So installieren Sie ldc:
curl -fsS https://dlang.org/install.sh | bash -s ldc
Es wurde ein Motherboard zusammengebaut, das aus drei Hauptkomponenten bestand: NanoPi Neo Core2 als Computer, KNX BAOS-Modul 830 für die Kommunikation mit dem KNX-Bus und Silvertel Ag9205 für die PoE-Stromversorgung, auf dem die Programmierung durchgeführt wurde.
Fazit
Ich werde nicht beurteilen, welche Sprache besser oder schlechter ist. Für jeden sein eigenes: js eignet sich hervorragend zum Lernen. Die Ebene der Abstraktionen (Versprechen, Emitter) ermöglicht es Ihnen, die Struktur der Anwendung einfach und schnell aufzubauen. Ich näherte mich der Implementierung auf dlang mit einem klaren, auswendig gelernten Plan für anderthalb Jahre, was zu tun ist. Wenn Sie wissen, welche Daten wie verarbeitet werden müssen, ist die statische Eingabe nicht unheimlich. Mit nicht blockierenden Methoden können Sie einen Arbeitszyklus organisieren. Dies war meine erste Arbeit an D, eine faszinierende und informative Arbeit.
Was das Verlassen der Komfortzone betrifft (wie im Titel angegeben): In meinem Fall hatte die Angst große Augen, was mich lange Zeit daran hinderte, etwas anderes als Nodejs auszuprobieren.
Quellcodes sind offen und finden Sie unter github.com/dobaos/dobaos