Erstellen Sie eine WeatherTerminal-App für das Seeed reTerminal (mit Qt 6 &QML)

 C Programming >> C-Programmierung >  >> Tags >> Qt
Erstellen Sie eine WeatherTerminal-App für das Seeed reTerminal (mit Qt 6 &QML)

In dieser Anleitung zeige ich Ihnen, wie Sie mit Qt und QML eine Wetter-App für das Seeed reTerminal erstellen. Stellen Sie sich das reTerminal in Ihrem Eingangsbereich vor und mit einem kurzen Blick auf den Bildschirm wissen Sie, wie das Wetter in den nächsten Stunden wird, ob Sie einen Regenschirm brauchen, ob Sie auf Ihrer Fahrradtour Gegenwind haben oder ob nur klar und sonnig sein. Dieses Tutorial baut auf der reTerminal Yocto boot2qt-Distribution auf, die wir im vorherigen Artikel erstellt haben, und verwendet Qt 6. Qt ist ein C++-Framework, aber diese Wetter-App verwendet fast ausschließlich QML. Ich verwende nur QML, um den Leitfaden zugänglicher zu machen, und auch, weil ich daran gewöhnt bin, alles in C++ zu machen, also macht mir ein Abstecher zu QML auch Spaß.

Dies ist Teil 1 des Leitfadens, in dem wir die Grundlagen einrichten. Dazu gehören die Vernetzung über QML, das Parsen der Open Meteo JSON-Wetter-API in QML und die Anzeige des Wettercodes in QML. Wenn Qt oder C++ neu für Sie sind, machen Sie sich keine Sorgen. QML ist eine deklarative Sprache zum Definieren von GUIs, enthält jedoch JavaScript. Dies bedeutet, dass es einfach ist, Ihre Benutzeroberfläche zu gestalten und JavaScript-Teile einen Teil der Schwerarbeit erledigen zu lassen , was in unserem Fall die Netzwerkaktivität und das JSON-Parsing sein wird. Am Ende dieser Anleitung finden Sie einen grundlegenden Bildschirm, der einen JSON-API-Wettercode in eine Textdarstellung umwandelt und die aktuelle Temperatur anzeigt, die auf dem reTerminal läuft.

Hier ist ein Bild des Endergebnisses von Teil 1, das auf meinem Desktop läuft:

Teil 2 erweitert das WeatherTerminal um die Skalierung der Benutzeroberfläche (zur Ausführung sowohl auf Ihrem PC als auch auf dem reTerminal), dauerhafte Einstellungen, eine Standortauswahl, einen Aktualisierungstimer, mehr Wetterelemente, einschließlich einiger Stunden in die Zukunft, und behandeln fortgeschrittenere QML-Konzepte wie verschiedene Layouts , Verankerungselemente, Bedingungen, Modelle und Eigenschaften. Teil 2 enthält auch die QtVirtual-Tastatur, da das reTerminal keine physische Tastatur hat, wir aber unseren Standort eingeben möchten.

Teil 2 ist noch nicht fertig, sobald das fertig ist, werde ich ihn hier verlinken.

Vollständige Offenlegung:Ich wurde von Seeed kontaktiert, sie schickten mir dieses reTerminal im Austausch für ein paar Artikel. Es ist keine Geldzahlung erforderlich und Seeed hat diesen Artikel vor der Veröffentlichung nicht überprüft. Für offiziellen Support besuchen Sie bitte das Seeed-Wiki.

Der vollständige Quellcode für Teil 1 befindet sich auf meinem Github

Was ist das reTerminal

Das reTerminal wird als zukunftsfähiges Human-Machine Interface (HMI) vermarktet. ThereTerminal wird von einem Raspberry Pi Compute Module 4 (cm4) angetrieben, bei dem es sich um eine Quad-Core ARM Cortex-A72-CPU mit 1,5 GHz und einem kapazitiven 5-Zoll-IPS-Multitouch-Bildschirm mit einer Auflösung von 1280 x 720 handelt. 4 GB RAM und 32 GB eMMC-Speicher sind integriert (nicht erweiterbar). Es verfügt über drahtlose Konnektivität mit 2,4-GHz-/5-GHz-Dualband-WLAN und Bluetooth 5.0 BLE.

Sie können das reTerminal hier kaufen, der aktuelle Preis beträgt USD 195. Dazu gehört ein Rechenmodul 4.

Siehe den anderen Artikel für einen umfassenderen Überblick über die Hardware und Funktionen.

Was Sie vor dem Start tun müssen

Bitte folgen Sie dem vorherigen Artikel zum Einrichten von Yocto boot2qt.

Diese Qt-App läuft nicht auf dem bereitgestellten Raspbian-Betriebssystem auf dem reTerminal, da die von uns verwendete Qt-Version zum Zeitpunkt des Schreibens neuer ist als die in dieser Debian-Version ausgelieferte. Sie könnten fortfahren und Qt 6.2 selbst kompilieren, aber das würde den Rahmen dieses Handbuchs sprengen.

Stellen Sie als Nächstes sicher, dass Sie Qt Creator und Qt Version 6.2 installiert haben. Der Yoctoboot2qt-Artikel enthält Anweisungen für das SDK, das Sie für das reTerminal crosskompilieren müssen.

Konfigurieren Sie in Qt Creator das Kit wie in meinem anderen Handbuch beschrieben und konfigurieren Sie Ihr reTerminal als Gerät für die Bereitstellung. Wenn das alles erledigt ist, komm zurück und fahre fort.

Wenn Sie nur die WeatherTerminal-App auf Ihrem Desktop ausführen möchten, müssen Sie yocto boot2qt für das reTerminal nicht einrichten, müssen nicht über Kreuz kompilieren, aber Sie müssen Qt Creator und Qt 6.2 installieren.

Sie können ohne reTerminal folgen, es ist ein guter QML- und Qt-Leitfaden, aber das Ziel dieses Leitfadens ist es, eine App für reTerminal zu erstellen, also denken Sie daran.

Datei -> Neues Projekt

Eines der schönsten Dinge als Entwickler ist der Moment, in dem Sie File -> New Project machen . Unbeschriebenes Blatt, bereit, Ihre Welt zu malen. Keine Cruft, Vermächtnis oder was auch immer. Also genieße diesen Moment. Starten Sie Qt Creator (ich verwende Version 7) und führen Sie den magischen Schritt aus.

Stellen Sie sicher, dass Sie eine Qt Quick (QML)-Anwendung auswählen, wählen Sie qmake als Buildsystem und stellen Sie sicher, dass die Qt-Mindestversion auf 6.2 eingestellt ist. Wählen Sie sowohl das reguläre Qt6-Kit als auch das von Yocto SDK bereitgestellte Kit aus, das Sie im vorherigen Artikel erstellt haben.

Tab-Layout durch Wischen

Wir beginnen mit der Einrichtung eines Layouts mit zwei Registerkarten. Sie können entweder auf die Registerkartenleiste klicken oder nach links/rechts wischen, um zu einer anderen Registerkarte zu navigieren.

Eine Registerkarte ist die Hauptwetterinformationsseite und eine Registerkarte ist für die Einstellungen. Nicht, dass wir viele Einstellungen hätten, aber das grundlegende Layout zu erstellen ist jetzt einfacher, als es später zu ändern.

Navigieren Sie im Datei-Explorer auf der linken Seite zu Resources , qml.qrc , / und öffnen Sie die Datei main.qml

Es sollte einen einfachen ApplicationWindow geben sowie ein oder mehrere import Aussagen. Die Struktur der QML-Datei ist einfach, eine QML-Datei hat ein einzelnes Top-Level-Element, das das Verhalten und die Eigenschaften dieser Komponente definiert.

Wenn Sie eine neue QML-Datei mit dem Namen beispielsweise WeatherButton.qml , könnten Sie diesen Gegenstand in Ihrem ApplicationWindow platzieren indem Sie WeatherButton {} schreiben .

In unserem Fall werden wir einige Komponenten hinzufügen, um das Tablayout aufzubauen. Beginnen Sie, indem Sie oben die folgende Zeile hinzufügen, um die Qt QuickControls zu verwenden:

import QtQuick.Controls

In Qt 5 musste man beim Import eine Versionsnummer angeben, in Qt6 ist das nicht mehr erforderlich.

Ändern Sie den width: und height: Eigenschaftswerte auf 1280 und 720, thereTerminals Bildschirmabmessungen. Setzen Sie etwas Nettes in den Titel und entfernen Sie alle weiteren Inhalte innerhalb des ApplicationWindow Komponente.

Fügen Sie die folgenden Zeilen hinzu:

SwipeView {
    id: swipeView
    anchors.fill: parent
    currentIndex: tabBar.currentIndex    
}

footer: TabBar {
    id: tabBar
    currentIndex: swipeView.currentIndex
    TabButton {
        text: "Weather"
        font.pixelSize: 30
    }
    TabButton {
        text: "Settings"
        font.pixelSize: 30
    }
}

Fahren Sie fort und drücken Sie STRG+R (oder das grüne Dreieck, das wie eine Wiedergabetaste aussieht) und sehen Sie sich das Wunder an, das Sie vollbracht haben:

Versuchen Sie auch, es auf dem reTerminal auszuführen. Wenn Sie das Wayland + Weston-Setup verwenden, um QML-Apps zu rotieren, fügen Sie Folgendes zur Umgebung in QtCreator hinzu:

Wählen Sie das Yocto-Gerätekit und das Remote-Gerät aus und drücken Sie dann Play um es auf dem reTerminal zu kompilieren und auszuführen:

Hier ist ein Bild des reTerminals, auf dem unsere einfache leere Tafel mit Registerkarten ausgeführt wird:

Beachten Sie, dass das Wischen nach links oder rechts noch nicht funktioniert, da der SwipeView hat noch keinen eigentlichen Inhalt.

Ich habe Ihnen gesagt, dass QML einfach ist, kein C++-Code erforderlich ist und Sie bereits eine App mit Registerkarten haben.

Erklären, was wir bisher getan haben, beginnend mit SwipeView :

  • id: swipeView :die Text-ID, mit der dieses spezifische Objekt identifiziert und von anderen Objekten referenziert werden kann. Diese ID muss mit einem Kleinbuchstaben oder einem Unterstrich beginnen und darf keine anderen Zeichen als Buchstaben, Ziffern und Unterstriche enthalten.

  • anchors.fill: parent :macht die Swipeview-Anker zu ihrem übergeordneten Element (dem Fenster), ändert effektiv die Größe, um das gesamte Fenster auszufüllen.

  • currentIndex: tabBar.currentIndex :Eine Eigenschaftsbindung. Immer wenn der Eigenschaftswert currentIndex des tabBar aktualisiert, aktualisiert die QML-Engine automatisch auch den Wert dieser Eigenschaft. Effektive Kopplung von Wischen und Klicken auf einen Tab.

Eigenschaftsbindungen sind eine der Stärken von QML. Ohne eine Eigenschaftsbindung müssten Sie in diesem Fall eine Funktion schreiben, die immer dann, wenn Sie auf eine Tab-Schaltfläche klicken, den SwipeView-Index ändert (um die SwipeView tatsächlich zu aktualisieren) und umgekehrt.

Anker werden im zweiten Teil näher erläutert. Im Moment können Sie sie sich als eine Art Magneten vorstellen. Eine Seite eines Gegenstands ist an einer Seite eines anderen Gegenstands verankert. Aus Leistungsgründen jedoch nur übergeordnete oder gleichgeordnete Elemente.

Als nächstes kommt die footer: TabBar {} . Die footer ist eigentlich eine Eigenschaft von ApplicationWindow Die Eigenschaft nimmt einen Item an als seinen Wert, weshalb Sie einen ganzen TabBar eingeben können im Inneren.

Items sind visuelle Dinge aus dem QtQuick Modul. Quick steht fürQt User Interface Creation Kit .

Die tabBar hat ihren eigenen id: -Eigenschaft und enthält zwei Items in sich selbst zwei TabButtons , die auch ihre eigenen Eigenschaften haben:

TabButton {
    text: "Weather"
    font.pixelSize: 30
}

text: enthält den Text, den Sie auf der Schaltfläche sehen, und font.pixelSize ist, wie zu erwarten, die Größe der Schrift in Pixel.

Wegen TabBar Wenn es sein eigenes Layout (Platzieren von untergeordneten Elementen) auf dem Bildschirm durchführt, ist es nicht erforderlich, x: anzugeben , y: oder anchors: in den Knöpfen. Der TabBar stellt sicher, dass sie nebeneinander liegen.

Wenn Sie auf eine Schaltfläche klicken, wird TabBar , der currentIndex Eigentumsänderungen. Wenn Sie auf Settings klicken es wird zu 1 . Da die Eigenschaft currentIndex ist an currentIndex gebunden Eigenschaft von swipeView , diese SwipeView ist currentIndex wird auch zu 1 . Tatsächlich ergibt dies den SwipeView ändert sein aktuelles Element in das zweite untergeordnete Element darin (denken Sie daran, dass Arrays bei 0 beginnen).

Wenn Sie neu bei Qt sind, finden Sie hier eine Menge Informationen, die auf ein einfaches Beispiel reduziert wurden. Versuchen Sie, herumzuspielen, schauen Sie sich an, was die automatische Vervollständigung für Eigenschaften bietet, und spielen Sie damit herum. Versuchen Sie, die Textfarbe red zu machen zum Beispiel.

Tabs mit Seiten füllen

Jetzt, da wir die Registerkarten haben, können wir sie mit etwas Nützlichem füllen. Klicken Sie mit der rechten Maustaste auf / Ordner in qml.qrc und erstellen Sie eine neue QML-Datei mit dem Namen SettingsPage.qml :

Fügen Sie den folgenden Inhalt ein:

import QtQuick
import QtQuick.Controls

Page {
    id: root
    width: 1240
    height: 640

    header: Label {
        text: "Settings"
        font.pixelSize: 50
    }
}

Dies ist eine leere Platzhalterseite mit nur einer Kopfzeile. Dasselbe wie footer: Eigenschaft von ApplicationWindow , der header: Eigenschaft nimmt einen Item an als Wert, der in diesem Fall ein Label ist . Könnte auch ein Button sein oder worauf du Lust hast. Der Page control übernimmt das Layout und stellt sicher, dass header: Item befindet sich oben auf der Seite.

In main.qml , innerhalb von SwipeView , fügen Sie diese neue Komponente hinzu:

  SwipeView {
    [...]
    SettingsPage {}
}

Drücken Sie Play, um es auszuprobieren, und Sie sollten jetzt einen Header-Text Settings sehen , auf Ihrem Wetter-Tab. Wieso den? Denn die SwipeView hat nur ein untergeordnetes Element, das automatisch index erhält Zahl 0.

Wiederholen Sie die Erstellung der neuen QML-Datei für eine andere Datei, nennen Sie diese WeatherPage.qml

Fügen Sie denselben Inhalt wie in SettingsPage.qml hinzu Datei, aber ändern Sie die Label um Weather zu sagen und fügen Sie es dem SwipeView hinzu in main.qml , rechts über dem SettingsPage :

  SwipeView {
    [...]
    WeatherPage {}
    SettingsPage {}
}

Drücken Sie Play und versuchen Sie es erneut, jetzt sollten Sie Weather sehen als Eröffnungslasche. Seit SwipeView können Sie jetzt auch nach rechts oder links wischen hat jetzt untergeordnete Elemente. Wenn Sie wischen, sollte sich auch die derzeit aktive Registerkarte in der Registerkartenleiste ändern.

Parsing der Open Meteo API

Ich habe mich für die Open-Meteo-API entschieden, weil sie keinen API-Schlüssel oder eine Benutzerregistrierung erfordert und für Open Source oder nicht-kommerzielle Nutzung kostenlos ist. Es bietet eine ordentliche JSON-API, gibt LAT und LON ein und bamm, Sie erhalten die Vorhersage.

Ich werde die folgende URL in der App verwenden, aber wenn diese aus irgendeinem Grund nicht verfügbar ist, können Sie auch hier den (statischen) Spiegel auf meiner Website verwenden. Letzteres enthält natürlich nicht die aktuelle Prognose, liefert Ihnen aber das korrekte JSON-Format.

Beginnen wir damit, unsere eigenen Eigenschaften in WeatherPage.qml zu definieren , direkt unter dem width und height :

property var parameters: undefined
property double latitude: 52.3738
property double longitude: 4.8910

Die letzten beiden sind selbsterklärend, die erste (parameters ) enthält die dekodierte JSON. Der var Typ ist der anything Geben Sie QML ein. Wenn Sie den Typ kennen, den eine Eigenschaft enthalten wird, ist es schneller, ihn anzugeben (string statt var zum Beispiel). Der var type entspricht einer regulären JavaScript-Variable. Beispielsweise können var-Eigenschaften Zahlen, Zeichenfolgen, Objekte, Arrays und Funktionen speichern. Da unser geparstes JSON vom Typ QJSValue sein wird und es gibt keinen spezifischeren QML-Typ, der dem entspricht, var ist unsere beste Wahl.

Fügen Sie nach dem Hinzufügen der benutzerdefinierten Eigenschaften eine Funktion hinzu. Dies ist eine reguläre JavaScript-Funktion, aber sie kann auf QML-Eigenschaften zugreifen, wie Sie sehen werden:

function getJson(latitude, longitude) {
    var xmlhttp = new XMLHttpRequest()
    var url = "https://api.open-meteo.com/v1/forecast?latitude=" + latitude
            + "&longitude=" + longitude + "&hourly=temperature_2m,relativehumidity_2m,apparent_temperature,weathercode,windspeed_10m,winddirection_10m&daily=weathercode,temperature_2m_max,temperature_2m_min,sunrise,sunset&current_weather=true&timezone=Europe%2FAmsterdam"

    xmlhttp.onreadystatechange = function () {
        if (xmlhttp.readyState === XMLHttpRequest.DONE
                && xmlhttp.status == 200) {
            root.parameters = JSON.parse(xmlhttp.responseText)
        }
    }

    xmlhttp.open("GET", url, true)
    xmlhttp.send()
}

Wenn Sie schon einmal mit JavaScript gearbeitet haben, fällt vielleicht nur Folgendes auf:

root.parameters = JSON.parse(xmlhttp.responseText)

Wenn Sie mit JavaScript nicht vertraut sind, sendet diese Funktion eine GET-Anforderung mit einer Callback-Methode an die API-URL. Die Callback-Methode prüft, ob der GETrequest korrekt beendet wurde, parst die JSON-Antwort und weist das Ergebnis dem QML root.parameters zu Eigentum. root ist der id: unseresPage , hat die QML-Engine komplexe Scoping-Regeln, aber jetzt reicht es zu wissen, dass sie weiß, dass sie die var der Eigenschaft parameters zuweisen muss in dieser Datei, nicht in SettingsPage Datei, obwohl diese Seite auch den id: hat von root . Andere Datei, anderer Kontext.

Beachten Sie, dass diese JavaScript-Methode das Gleichheitszeichen verwendet (= ) und nicht der Doppelpunkt (: ), um der Eigenschaft einen Wert zuzuweisen. Der QML-Doppelpunkt (: ) macht eine Eigenschaftsbindung, das Gleichheitszeichen (= ) nicht. Also, wenn Sie width = height machen würden Innerhalb einer JavaScript-Methode wäre das keine Eigenschaftsbindung, sondern nur eine Zuweisung. Wenn height spätere Änderungen, width wird nicht. Wichtiger Unterschied, aber im Moment nicht so relevant.

Lassen Sie uns eine Schaltfläche hinzufügen, die diese Methode aufruft. Fügen Sie unter den Eigenschaften Folgendes hinzu:

Button {
    id: refreshButton
    anchors.bottom: parent.bottom
    anchors.left: parent.left
    anchors.margins: 5
    text: "Update Weather"
    font.pixelSize: 30
    onClicked: getJson(root.latitude, root.longitude)
}

Die beiden anchors. Lassen Sie die Schaltfläche unten links mit einem kleinen Rand um sie herum erscheinen (auf allen Seiten). Der onClicked Die Eigenschaft ruft unsere JavaScript-Methode mit den beiden Parametern Breitengrad und Längengrad auf, die wir als Eigenschaften von Page definiert haben .

Wenn Sie Play zum Kompilieren und Ausführen drücken, funktioniert die Schaltfläche, aber Sie können das Ergebnis nicht sehen. Die Eigenschaft parameters hat das entschlüsselte JSON, aber wir tun noch nichts damit. Um sicherzustellen, dass wir es richtig gemacht haben, melden wir uns bei der Konsole an. Unter dem Button , fügen Sie Folgendes hinzu:

onParametersChanged: console.log(root.parameters['current_weather']['weathercode'])

Kompilieren und ausführen, die Update-Schaltfläche drücken und das Konsolenprotokoll sollte in etwa so aussehen:

qrc:/WeatherPage.qml:30: TypeError: Cannot read property 'current_weather' of undefined
qml: 3

Der erste Fehler ist in Ordnung, den können wir vorerst ignorieren. Als die Eigenschaft deklariert wurde, wurde sie leer initialisiert und löste ein geändertes Signal aus, aber die von uns geschriebene onChanged-Funktion überprüft nicht, ob die Parameter leer sind.

Die zweite Zeile (qml: 3 ) ist der eigentliche weathercode aus der JSON-API.

Nehmen Sie sich einen Moment Zeit, um sich zu amüsieren. Ohne C++-Code zu schreiben, haben Sie eine plattformübergreifende App mit Registerkartenleisten und einer Schaltfläche erstellt, die eine JSON-API von einem Netzwerk-Webdienst erhält. Auch hier verwende ich nur QML für diesen Leitfaden, weil es supereinfach ist.

Hinter den Kulissen, der onParametersChanged: line ist ein Slot (Signalhandler), der aufgerufen wird, wenn changed Signal wird von unserem parameters abgefeuert Variable. Qt hat ein weiteres sehr leistungsfähiges Konzept namens Signals and Slots, das ein bisschen wie ein Observer-Design-Pattern oder Pub-Sub ist, aber auf Steroiden und C++ typsicher ist. Ich werde es nicht weiter erklären, ich könnte ein Buch nur über Signale und Slots schreiben, wenn Sie interessiert sind, sehen Sie sich die Qt-Dokumentation darüber an.

Jede Eigenschaft, auch unsere benutzerdefinierten, hat einen changed Signal, das erstellt die QML-Engine für uns. Dieses Signal wird automatisch ausgegeben, wenn sich der Wert einer QML-Eigenschaft ändert. Dieser Signaltyp ist ein property change signal und Signalhandler für diese Signale werden in Form von onPropertyChanged geschrieben , wobei Property ist der Name der Eigenschaft, wobei der erste Buchstabe groß geschrieben wird.

Die console.log() Funktion, die wir dem onParametersChanged zugewiesen haben slot (Signalhandler) gibt den Inhalt des JSON-Objekts ['current_weather']['weathercode'] aus .

Wettercode parsen

Jetzt, da wir mit einem Klick auf eine Schaltfläche mit der JSON-API kommunizieren können, ist es an der Zeit, diese API zu parsen. Wir beginnen mit dem aktuellen WeatherCode, der ein numerisches Standardformat für Wetterbedingungen ist, wie Clear Sky oder Thunderstorm .

Die Codes sind auf der Open-Meteo-API-Seite ausgeschrieben, und eine umfassendere Beschreibung befindet sich auf der noaa.gov-Site.

Neben einer reinen Textausgabe fügen wir ein nettes Symbol hinzu, das sich ändert, wenn sich der Wettercode ändert.

Erstellen Sie wie zuvor eine neue QML-Datei mit dem Namen WeatherCode.qml und fügen Sie Folgendes ein:

import QtQuick

Item {
    id: root
    property var parameters: undefined
}

Im WeatherPage.qml , fügen Sie diese neue Komponente oberhalb von Button hinzu wir haben früher hinzugefügt:

WeatherCode {
    id: weatherCode
    anchors.top: parent.top
    anchors.left: parent.left
    anchors.right: parent.right
    parameters: root.parameters
}

Die anchors Platzieren Sie dieses Steuerelement oben links auf der Seite und strecken Sie es nach rechts. Die Höhe definieren wir später im Steuerelement selbst. Wenn ein Steuerelement keine Breite/Höhe oder Anker hat, ist es nicht sichtbar. Wir geben die parameters weiter des WeatherPage bis zu WeatherCode . Dies ist eine Eigenschaftsbindung, wenn Sie also auf Update klicken Schaltfläche, die WeatherCode control erhält auch den neuen aktualisierten parameters .

Erstellen Sie in Ihrem Qt-Projektordner einen neuen Ordner mit dem Namen icons und laden Sie den folgenden svg herunter Dateien von FontAwesome.com :

  • circle-question-solid.svg
  • clock-solid.svg
  • cloud-rain.svg
  • cloud-showers-heavy-solid.svg
  • cloud-showers-water-solid.svg
  • cloud-sun-solid.svg
  • poo-storm-solid.svg
  • rainbow-solid.svg
  • smog-solid.svg
  • snowflake-solid.svg
  • sun-solid.svg
  • temperatur-half-solid.svg
  • temperatur-high-solid.svg
  • temperatur-low-solid.svg
  • wind-solid.svg

Diese sind alle Teil von Font Awesome Free und CC-BY-4.0 lizenziert.

Klicken Sie in Qt Creator mit der rechten Maustaste auf qml.qrc Datei in der Seitenleiste und klicken Sie auf Add existing files . Wählen Sie alle Symbole aus, die Sie im icons heruntergeladen haben Ordner.

Fügen Sie einen neuen Image hinzu Steuerung an WeatherCode.qml Datei, unterhalb der Eigenschaften:

Image {
    id: weatherCodeIcon
    source: root.parameters ? weathercodeToIcon(
                                      root.parameters['current_weather']['weathercode']) : "qrc:icons/circle-question-solid.svg"
    asynchronous: true
    anchors.top: parent.top
    anchors.left: parent.left
    anchors.margins: 5
    width: 90
    height: width
}

Sie sollten sich inzwischen mit der QML-Syntax vertraut gemacht haben. Die Höhe ist eine an die Breite gebundene Eigenschaft, der anchors platzieren Sie dies oben links mit einem kleinen Rand darum herum. Der asynchronous -Eigenschaft weist die QML-Engine an, beim Laden dieses Bildes nicht zu blockieren. Mit einem Bild ist es kein Engpass, aber mit mehr Bildern wird schnell klar, warum alle Bilder asynchron geladen werden sollen (weil die Benutzeroberfläche blockiert, unbrauchbar ist, einfriert).

Der source: -Eigenschaft ist komplexer und führt Sie in ein weit verbreitetes QML-Konzept ein, das ternary if Aussage. Wenn root.parameters gefüllt ist (not undefined ), dann tun Sie, was nach dem Fragezeichen steht (? ). Wenn nicht, tun Sie, was nach dem Doppelpunkt steht (: ). Dies könnte auch (in Pseudocode) geschrieben werden als:

if(root.parameters !== undefined); then
    source = weathercodeToIcon(root.parameters['current_weather']['weathercode'])
else
    source = "qrc:icons/circle-question-solid.svg"

Wir haben parameters definiert als undefined , solange wir nicht auf Update geklickt haben klicken, wird ein Fragezeichen-Symbol angezeigt. Wenn wir die update anrufen Funktion, ein parametersChanged Das Signal wird ausgelöst und diese Eigenschaftsbindung wird neu bewertet.

Der weathercodeToIcon() Funktion enthält den folgenden Code. Fügen Sie es direkt unter den Eigenschaften in dieser Datei ein:

function weathercodeToIcon(weathercode) {
    switch (weathercode) {
    case 0:
        return "qrc:icons/sun-solid.svg"
    case 1:
    case 2:
    case 3:
        return "qrc:icons/cloud-sun-solid.svg"
    case 45:
    case 48:
        return "qrc:icons/smog-solid.svg"
    case 51:
    case 53:
    case 55:
    case 56:
    case 57:
    case 61:
    case 80:
        return "qrc:icons/cloud-rain.svg"
    case 63:
    case 66:
        return "qrc:icons/cloud-showers-solid.svg"
    case 65:
    case 67:
        return "qrc:icons/cloud-showers-water-solid.svg"
    case 71:
    case 73:
    case 75:
    case 77:
    case 85:
    case 86:
        return "qrc:icons/snowflake-solid.svg"
    case 81:
    case 82:
        return "qrc:icons/cloud-showers-heavy-solid.svg"
    case 95:
    case 96:
    case 99:
        return "qrc:icons/poo-storm-solid.svg"
    default:
        return "qrc:icons/rainbow-solid.svg"
    }
}

Wie Sie sehen können, nichts Besonderes, nur eine große Switch-Anweisung. Geben Sie für jede Reihe von Wettercodewerten ein anderes Symbol zurück.

Neben dem Bild und über dem geparsten Wettercodetext möchte ich eine kleine Kopfzeile. Lassen Sie uns das hinzufügen, fügen Sie dies über dem Image ein :

Text {
    id: weatherHeaderText
    text: "Current Weather"
    anchors.top: parent.top
    anchors.left: weatherCodeIcon.right
    anchors.leftMargin: 20
    horizontalAlignment: Text.AlignLeft
    verticalAlignment: Text.AlignTop
    font.pixelSize: 18
}

Hier ist etwas Neues, anchors.left: weatherCodeIcon.right . Das bedeutet, dass die linke Seite des Textobjekts mit der rechten Seite des Symbols verankert werden sollte. Fügen Sie ein bisschen leftMargin hinzu um es schön zu machen und du bist fertig. Nun, wo auch immer Sie das Symbol platzieren, direkt daneben steht immer dieser Text. Wenn Sie das Symbol verschieben, müssen Sie den x: nicht manuell aktualisieren oder y: des Text , es wird alles automatisch für Sie erledigt.

Oben in der Datei, direkt unter dem id: , fügen Sie eine neue Eigenschaft für height hinzu dieser Kontrolle:

Item {
    id: root
    height: weatherCodeIcon.height
    [...]

Eine weitere Eigenschaftsbindung, die dieses gesamte Steuerelement so hoch macht wie das Bildsymbol. Wir haben WeatherCode verankert in WeatherPage bei top ,left und right , aber nicht bottom . Wenn wir keine Höhe festlegen würden, wäre das Element unsichtbar.

Gehen Sie auf Play und führen Sie den Code aus. Klicken Sie auf Update Schaltfläche und das Symbol sollte sich von einem Fragezeichen in den aktuellen Wettercode ändern, den wir in weathercodeToIcon abgebildet haben switch Aussage:

Um die Wettercode-Steuerung abzuschließen, fügen wir auch den aktuellen Wettertext hinzu. Fast identisch mit weathercodeToIcon Funktion machen wir jetzt eine weathercodeToText Funktion, mit einem weiteren großen switch . Fügen Sie es unter der anderen Funktion hinzu:

function weathercodeToText(weathercode) {
    switch (weathercode) {
    case 0:
        return "Clear sky"
    case 1:
        return "Mainly clear"
    case 2:
        return "Partly cloudy"
    case 3:
        return "Overcast"
    case 45:
        return "Fog"
    case 48:
        return "Fog (Depositing rime)"
    case 51:
        return "Light Drizzle"
    case 53:
        return "Moderate Drizzle"
    case 55:
        return "Dense Drizzle"
    case 56:
        return "Light Freezing Drizzle"
    case 57:
        return "Dense Freezing Drizzle"
    case 61:
        return "Slight Rain"
    case 63:
        return "Moderate Rain"
    case 65:
        return "Heavy Rain"
    case 66:
        return "Light Freezing Rain"
    case 67:
        return "Heavy Freezing Rain"
    case 71:
        return "Slight Snowfall"
    case 73:
        return "Moderate Snowfall"
    case 75:
        return "Heavy Snowfall"
    case 77:
        return "Snow grains"
    case 80:
        return "Slight Rainshower"
    case 81:
        return "Moderate Rainshower"
    case 82:
        return "Violent Rainshower"
    case 85:
        return "Slight Snowshowers"
    case 86:
        return "Heavy Snowshowers"
    case 95:
        return "Thunderstorm"
    case 96:
        return "Thunderstorm with slight hail"
    case 99:
        return "Thunderstorm with heavy hail"
    default:
        return "Rainbows!"
    }
}

Unter Ihrem Image , fügen Sie einen neuen Text hinzu Steuerung:

Text {
    id: weatherCodeText
    text: root.parameters ? weathercodeToText(
                                root.parameters['current_weather']['weathercode']) : "Loading weather, please press update"

    anchors.bottom: weatherCodeIcon.bottom
    anchors.left: weatherCodeIcon.right
    anchors.leftMargin: 20
    horizontalAlignment: Text.AlignHCenter
    verticalAlignment: Text.AlignBottom
    font.pixelSize: 50
    wrapMode: Text.WordWrap
}

Was diese Steuerung tut, sollte keine Überraschung mehr sein. Wir anchor es rechts neben dem Symbolbild und, wenn der parameters definiert sind, übergeben Sie sie an unserenweathercodeToText Funktion, die das aktuelle Wetter zurückgibt. Wenn noch keine Parameter vorhanden sind, wird Loading Weather, please press update angezeigt .

Denken Sie daran, dass der vollständige Code auf meinem GitHub zu finden ist, sodass Sie überprüfen können, ob Sie richtig gefolgt sind, indem Sie Ihre QML-Datei mit meiner vergleichen.

Nachdem wir den Wettercode geparst haben, können wir mit der Temperatur fortfahren. Sieht diesem Teil sehr ähnlich, ohne die großen JavaScript-Parsingmethoden, da es nur eine Zahl ist.

Temperatur

Erstellen Sie wie zuvor eine neue QML-Datei mit dem Namen Temperature.qml . Fügen Sie den leeren Item ein Schablone. Ich schließe den height ein und dieparameters , weil wir das bereits im vorherigen Teil behandelt haben:

import QtQuick

Item {
    id: root
    height: temperatureIcon.height
    property var parameters: undefined

}

Da ich möchte, dass dieses Steuerelement wie der WeatherCode aussieht, hat dieses das gleiche Layout, ein Symbol und einen kleinen Kopfzeilentext. Diesmal gibt es keinen Unterschied im Symbol, also kein JSON-Parsing. Fügen Sie es unter parameters ein :

Image {
    id: temperatureIcon
    source: "qrc:icons/temperature-half-solid.svg"
    asynchronous: true
    anchors.top: parent.top
    anchors.left: parent.left
    anchors.margins: 5
    width: 90
    height: width
}

Text {
    id: apparentTemperatureText
    text: "Apparent Temperature"
    anchors.top: parent.top
    anchors.left: temperatureIcon.right
    anchors.leftMargin: 20
    horizontalAlignment: Text.AlignLeft
    verticalAlignment: Text.AlignTop
    font.pixelSize: 18
}

Der obige QML-Code sollte Ihnen vertraut sein, da es in diesem Handbuch nichts gibt, was wir noch nicht getan haben.

Wenn Sie möchten, können Sie die aktuelle scheinbare Temperatur analysieren und, wenn sie höher oder niedriger als ein festgelegter Wert ist, ein anderes Temperatursymbol anzeigen. Zeigen Sie für alles unter 10 Grad Celsius das Symbol temperature-low-solid.svg, für alles über 20 das temperature-high-solid.svg und alles dazwischen das temperature-half-solid.svg. Wie das geht, bleibt dem Leser als Übung überlassen, aber mit den Beispielen im vorherigen Wettercode-Abschnitt sollte das nicht schwierig sein.

Ich habe die scheinbare Temperatur im Gegensatz zur regulären Temperatur gewählt, hauptsächlich weil die JSON-API diese Variable nicht in current_weather verfügbar macht JSON-Struktur, also müssen wir hourly parsen Teil des JSON. Andernfalls wäre dieses Beispiel sehr ähnlich wie der Wettercode, was langweilig wäre. Und natürlich ist die scheinbare Temperatur nützlicher, wenn Sie das reTerminal in Ihrem Flur aufhängen, um zu wissen, welchen Mantel Sie anziehen müssen. Es könnte 10 Grad, aber sonnig und windstill sein, was sich wärmer anfühlt, oder 15 Grad mit eisigen Winden, was sich viel kälter anfühlt. Für den Zweck des erneuten Anschlusses dort ist also die scheinbare Temperatur zutreffender.

Die API-Dokumentation sagt Folgendes bezüglich des Formats und der stündlichen Daten:

Wenn wir die aktuelle Stunde des Tages abrufen können, können wir dieses Feld aus dem JSON-Objekt auswählen und die Temperatur für die aktuelle Stunde abrufen. Hier ist eine komprimierte JSON-Ausgabe:

{
    [...]
    "hourly_units": {
        "apparent_temperature": "degC",
    },
    "hourly": {
        "apparent_temperature": [-1.9, -2.4, -3.2, -3.3, -3.3, [...] ],
    }
}

Das Feld [hourly][apparant_temperature] ist eine Liste. Stunde 0 des aktuellen Tages hat scheinbare Temperatur -1.9 Grad Celsius. Stunde 1 hat -2.4 und so weiter.

In unserer QML-Datei, wenn der parameters JSON enthalten, lautet die Syntax für den Zugriff auf Stunde1 wie folgt:

 root.parameters['hourly']['apparent_temperature'][1]

Eine schnelle JavaScript-Funktion, um die aktuelle Stunde zu erhalten, ist unten:

function currentHour() {
    const date = new Date()
    return date.getHours()
}

Durch die Kombination der beiden ergibt der folgende Code einen property das die aktuelle Stundentemperatur hat:

property double currentTemperature:  root.parameters['hourly']['apparent_temperature'][currentHour()]

In diesem Fall suche ich nicht nach parameters undefiniert sein, weil ich das später in Text überprüfen werde Kontrolle. Sonst hättest du dort eine magische Zahl, wie 999 oder was auch immer. Nicht die ausdrucksstärkste Art.

Die API legt auch die Einheiten offen, in denen sich die Daten befinden, wie das obige Beispiel ebenfalls zeigt. Sie können darauf zugreifen, wie Sie auf die anderen Elemente zugreifen können:

property string currentTemperatureUnit: root.parameters ? root.parameters['hourly_units']['apparent_temperature'] : ""

Kombinieren der obigen Eigenschaften zu einem Text Steuerung:

Text {
    id: currentTemperatureText
    text: root.parameters ? currentTemperature + "<small> "
                            + currentTemperatureUnit + "</small>" : "..."

    anchors.bottom: temperatureIcon.bottom
    anchors.left: temperatureIcon.right
    anchors.right: parent.right
    anchors.leftMargin: 20

    horizontalAlignment: Text.AlignLeft
    verticalAlignment: Text.AlignBottom
    font.pixelSize: 54
    minimumPixelSize: 45
    textFormat: Text.RichText
}

Eine neue Eigenschaft ist textFormat . Wenn Sie dies auf Text.RichText setzen Sie können HTML verwenden. Sie können auch Text.StyledText verwenden für etwas grundlegendes HTML, aber das enthält nicht den <small> Schild. Mir gefällt, wie es aussieht, wenn die Einheit kleiner als die Zahl ist.

So sieht das fertige Steuerelement aus, wenn Sie noch nicht auf Aktualisieren geklickt haben:

So sieht es aus, wenn Sie JSON aktualisiert haben:

Fügen Sie das Steuerelement zu WeatherPage.qml hinzu Datei, direkt unter WeatherCode {} :

Temperature {
    id: temperature
    anchors.top: weatherCode.bottom
    anchors.topMargin: 30
    anchors.left: parent.left
    anchors.right: parent.right
    parameters: root.parameters
}

Dasselbe wie zuvor, aber jetzt ist dieses Steuerelement in weatherCode verankert unten mit etwas Rand.

Teil 1 fertigstellen

Die Grundlagen sind alle vorhanden, Sie parsen JSON und zeigen die Daten in Ihren eigenen benutzerdefinierten Steuerelementen an. Gut erledigt! Um Teil 1 abzuschließen, fügen wir zwei weitere Schaltflächen hinzu. Eine zum Beenden der App und eine zum Laden von Beispiel-JSON. Die Schaltfläche Beenden lässt die App über systemd neu starten auf dem reTerminal, kann praktisch sein.

Die Beispielschaltfläche finde ich nützlich. Ich habe den gesamten JSON-Datenstring in eine String-Eigenschaft mit dem Namen exampleJson eingefügt :

property string exampleJson: '{"generationtime_ms":2.30...

Die Schaltfläche hat diese Methode als onClicked Eigenschaft:

root.parameters = JSON.parse(exampleJson)    

Das erspart Ihnen einen Netzanruf beim Testen und liefert Ihnen immer die gleichen Daten. Außerdem erspart es das Überladen der API.

Hier sind die beiden Schaltflächen:

Button {
    id: exampleButton
    anchors.bottom: parent.bottom
    anchors.left: refreshButton.right
    anchors.margins: 5
    text: "Example JSON"
    font.pixelSize: 30
    onClicked: root.parameters = JSON.parse(exampleJson)
}

Button {
    id: quitButtom
    anchors.bottom: parent.bottom
    anchors.left: exampleButton.right
    anchors.margins: 5
    text: "Quit"
    font.pixelSize: 30
    onClicked: Qt.callLater(Qt.quit)
}

Das fertige Ergebnis sieht so aus:

Loben Sie sich selbst, denn Sie haben großartige Arbeit geleistet. Im nächsten Teil fügen wir die Windgeschwindigkeit und -richtung (nützlich auf dem Fahrrad), persistente Einstellungen, das Wetter für die nächsten Stunden und die Qt VirtualKeyboard hinzu:

Die Tabelle beinhaltet eine fortgeschrittenere Verankerung und einen Layout enthält das Qt VirtualKeyboard auch die Yocto-Konfiguration, um sicherzustellen, dass das reTerminal das Modul erstellt.