Forum: Mikrocontroller und Digitale Elektronik MQTT Publish mit ESP8266 implementieren


von Max M. (maxmicr)


Lesenswert?

Hallo zusammen,

für mein Studium muss ich im Bereich Netzwerke ein Projekt umsetzen, ich 
habe mich für das Übertragen von Temperaturdaten via MQTT von einem 
ESP8266 zu einem Raspi, auf dem Mosquitto läuft, entschieden.

Ich habe versucht, das MQTT PUBLISH Paket selbst zu implementieren, 
hierzu hab ich mich an dieser Dokumentation orientiert: 
http://docs.oasis-open.org/mqtt/mqtt/v3.1.1/os/mqtt-v3.1.1-os.html#_Toc398718037

Die folgende Funktion befüllt diese MQTT-Struktur:
1
typedef struct MQTT {
2
    uint8_t fix_header[FIXED_HEADER_SIZE];
3
    uint8_t *var_header;
4
    uint8_t payload[PAYLOAD_SIZE];
5
6
    uint8_t var_header_size;
7
8
    uint8_t (*createPacket)(struct MQTT*, int32_t);
9
    void (*fillPacket)(struct MQTT*, uint8_t*);
10
} MQTT;
1
#define PACKET_TYPE_PUBLISH             0x30
2
#define PACKET_PUBLISH_NO_DUP           0x00
3
#define PACKET_PUBLISH_QOS_AT_MOST_ONCE 0x00
4
#define PACKET_PUBLISH_RETAIN           0x00
5
6
uint8_t ICACHE_FLASH_ATTR mqttCreatePacket(MQTT *self, int32_t tempData) {
7
    self->fix_header[0] = PACKET_TYPE_PUBLISH | PACKET_PUBLISH_NO_DUP | PACKET_PUBLISH_QOS_AT_MOST_ONCE | PACKET_PUBLISH_RETAIN;
8
9
    char topic[3];
10
11
    topic[0] = 'a';
12
    topic[1] = '/';
13
    topic[2] = 'b';
14
15
    const uint8_t topicStrLen = sizeof(topic) / sizeof(topic[0]);
16
    self->var_header_size = 1 + 1 + topicStrLen;
17
18
    os_free(self->var_header);
19
    self->var_header = os_zalloc(sizeof(uint8_t) * self->var_header_size);
20
21
    self->var_header[0] = 0;
22
    self->var_header[1] = topicStrLen;
23
    for(uint8_t i = 0; i < topicStrLen; i++) {
24
        self->var_header[2 + i] = topic[i];
25
    }
26
27
    self->payload[0] = (tempData >> 24) & 0xFF;
28
    self->payload[1] = (tempData >> 16) & 0xFF;
29
    self->payload[2] = (tempData >> 8) & 0xFF;
30
    self->payload[3] = (tempData & 0xFF);
31
32
    uint8_t completeLen = self->var_header_size + PAYLOAD_SIZE;
33
    uint8_t encodedByte = 0;
34
    do {
35
        encodedByte = completeLen % 128;
36
        completeLen /= 128;
37
        if(completeLen > 0)
38
            encodedByte = encodedByte | 128;
39
        else {
40
            self->fix_header[1] = encodedByte;
41
            break;
42
        }
43
    }
44
    while(completeLen > 0);
45
46
    return  (1 + 1) + self->var_header_size + 4;
47
}

Am Algorithmus für die Längenangabe im FixedHeader hab ich mich am 
vorgeschriebenen Algorithmus orientiert (Punkt 2.2.3: 
http://docs.oasis-open.org/mqtt/mqtt/v3.1.1/os/mqtt-v3.1.1-os.html#_Toc398718024)

In der *user_send_data*-Funktion kopiere ich die Struktur einfach nur in 
ein Array:
1
void ICACHE_FLASH_ATTR user_send_data(struct espconn *pespconn) {
2
    char *pbuf = (char*)os_zalloc(sizeof(uint8_t) * mqttSize);
3
4
    mqtt.fillPacket(&mqtt, pbuf);
5
    espconn_send(pespconn, (uint8_t*)pbuf, mqttSize);
6
7
    os_free(pbuf);
8
}

Die Variable mqttSize ist hierbei der Rückgabewert der oben gezeigten 
Funktion mqttCreatePacket.

Hier noch die Funktion fillPacket
1
void ICACHE_FLASH_ATTR fillPacket(MQTT *self, uint8_t *packet) {
2
    // Fill Fixed Header
3
    packet[0] = self->fix_header[0];
4
    packet[1] = self->fix_header[1];
5
6
    // Fill Variable Header
7
    packet[2] = self->var_header[0];
8
    packet[3] = self->var_header[1];
9
10
    // Fill variable TopicBytes
11
    for(uint8_t i = 0; i < self->var_header_size - 2; i++) {
12
        packet[4 + i] = self->var_header[i + 2];
13
    }
14
    
15
    // Fill payload
16
    for(uint8_t i = 0; i < PAYLOAD_SIZE; i++) {
17
        packet[2 + self->var_header_size + i] = self->payload[i];
18
    }
19
}

Leider funktioniert das ganze nicht, der Broker auf dem Raspberry 
registriert kein MQTT Packet auf dem Channel *a/b*.
Ich hab mir den Inhalt des Pakets kurz vorm Senden nochmal byteweise 
ausgeben lassen:
1
mqttSize: 11
2
FIXED HEADER BYTE 0: 0x30
3
FIXED HEADER BYTE 1: 0x9
4
VAR HEADER BYTE 0: 0
5
VAR HEADER BYTE 1: 3
6
VAR HEADER BYTE 2: a
7
VAR HEADER BYTE 3: /
8
VAR HEADER BYTE 4: b
9
TEMP FROM PAYLOAD: 2681

Das ist meiner Meinung nach korrekt.

Um zu Überprüfen, ob ein grundsätzlicher Übertragungsfehler vorliegt, 
hab ich schnell ein Arduino-Beispielprogramm mit der *PubSub*-Bibliothek 
zusammengeschrieben - so funktioniert es einwandfrei!

Meines Wissens nach ist die Publish-Nachricht mit QoS = 0 auch komplett 
verbindungslos, d.h. man benötigt keinen vorherigen Verbindungsaufbau?

Ich frage mich nur, wo mein Fehler liegt?

: Bearbeitet durch User
von Hannes J. (Firma: _⌨_) (pnuebergang)


Lesenswert?

Schau mit Wireshark nach was übertragen wird. Schau auch in die 
Server-Logs ob dort Hinweise stehen.

von Zeitverschwendung (Gast)


Lesenswert?

Anstatt deine Zeit zu verschwenden, solltest du eine Lib nehmen die 
funktioniert.

Du sendest ein Byte zu viel bei/für QoS 0. Lass den Packet ID weg und 
gut.

> A PUBLISH Packet MUST NOT contain a Packet Identifier if its QoS value is set to 
0 [MQTT-2.3.1-5].

Das ist aber nicht die Lösung des Problems - die steht im ersten Satz.

MfG

von Max M. (maxmicr)


Lesenswert?

Zeitverschwendung schrieb:
> Lass den Packet ID weg und
> gut.

Blöde Frage von mir vielleicht - aber wo sende ich eine Packet ID?

Genau den Satz, den du zitierst, habe ich nämlich eigentlich beachtet?
1
A PUBLISH Packet MUST NOT contain a Packet Identifier if its QoS value is set to 0

Zeitverschwendung schrieb:
> Anstatt deine Zeit zu verschwenden, solltest du eine Lib nehmen die
> funktioniert.

Da gebe ich dir grundsätzlich recht, nur leider hängt noch etwas mehr 
drann als das Senden des Paketes (z.B. Auslesen des Temperatur-Sensors, 
das müsste ich ja dann ebenfalls auf das Arduino Framework portieren) - 
der Rest klappt ja wunderbar, nur dieses MQTT Paket nicht. Und so viel 
kann man doch eigentlich bei 11 Bytes nicht falsch machen - oder?

: Bearbeitet durch User
von Langeredekurzersinn (Gast)


Lesenswert?

Variable ->length<- header.

Okay? ;-)

von TTundT (Gast)


Lesenswert?

Da liegt der Sinn bei MQTT, möglichst sparsam sein.

Lass das Byte weg.

Du willst aber bestimmt auch mal andere Features nutzen (Mqtt5), 
deswegen nimm die Lib.

Das mit Arduino würde ich schnell vergessen, das hält nur auf. Besorge 
dir PlatformIO und nutze das esp8266 SDK.

Und schau doch mal bspw. beim esp8266 Wifi repeater rein, da ist für 
dich alles dabei.. ;-)

MfG

von Max M. (maxmicr)


Lesenswert?

Oh Gott ich blicks nicht:
1
Lass das Byte weg.

: Bearbeitet durch User
von Augenauf (Gast)


Lesenswert?

mqttSize: 10
FIXED HEADER BYTE 0: 0x30
FIXED HEADER BYTE 1: 0x9
VAR HEADER BYTE 0: 3
VAR HEADER BYTE 1: a
VAR HEADER BYTE 2: /
VAR HEADER BYTE 3: b
TEMP FROM PAYLOAD: 2681

von Max M. (maxmicr)


Angehängte Dateien:

Lesenswert?

Ich hab mich daran orientiert. Das 1. Byte (0) sind die MSB der 
Längenangabe, das 2. Byte (3) sind die LSB. Erst nach dem Topic Name 
kommen die Packet Identifier - die hab ich doch weg gelassen?

von Nope (Gast)


Lesenswert?

Lies bitte Punkt 2.2.3 (noch) einmal.

von Max M. (maxmicr)


Lesenswert?

Tut mir Leid, dass ich es nicht verstehe. Ich werde die Änderung 
probieren und mich dann nochmal melden.

Vielen Dank schonmal und Grüße

von LibLibLibLibLib (Gast)


Lesenswert?

Bis zur Länge 127 ist das nur ein Byte. Du sendest also definitiv ein 
Byte zu viel.

Wenn du mal mehr Daten (>127) hast dann wendest du das Verfahren nach 
Punkt 2.2.3 an.

Nimm eine Lib. (:

von Max M. (maxmicr)


Lesenswert?

LibLibLibLibLib schrieb:
> Bis zur Länge 127 ist das nur ein Byte.

Das macht Sinn - aber warum ist dann im Beispiel auf der Webseite 
trotzdem dass 0-Byte mit angegeben?

von Max M. (maxmicr)


Lesenswert?

Hab jetzt sowohl das
1
mqttSize: 10
2
FIXED HEADER BYTE 0: 0x30
3
FIXED HEADER BYTE 1: 0x9
4
VAR HEADER BYTE 0: 3
5
VAR HEADER BYTE 1: a
6
VAR HEADER BYTE 2: /
7
VAR HEADER BYTE 3: b
8
TEMP FROM PAYLOAD: 2983

als auch das
1
mqttSize: 10
2
FIXED HEADER BYTE 0: 0x30
3
FIXED HEADER BYTE 1: 0x8
4
VAR HEADER BYTE 0: 3
5
VAR HEADER BYTE 1: a
6
VAR HEADER BYTE 2: /
7
VAR HEADER BYTE 3: b
8
TEMP FROM PAYLOAD: 2983

ausprobiert. Der Broker sagt nur
1
1560806826: Socket error on client <unknown>, disconnecting.

:(

Edit: Das Arduino-Programm macht noch ein Connect:
1
void reconnect() {
2
    while (!client.connected()) {
3
        Serial.print("Reconnecting...");
4
        if (!client.connect("ESP8266Client")) {
5
            Serial.print("failed, rc=");
6
            Serial.print(client.state());
7
            Serial.println(" retrying in 5 seconds");
8
            delay(5000);
9
        }
10
    }
11
}

Braucht man das wenn man nur ein publish machen will?

: Bearbeitet durch User
von Max M. (maxmicr)


Lesenswert?

Die Lösung ist, dass mein MQTT-Publish aus der 1. Nachricht korrekt war, 
ich hab mir mit Wireshark die Packete angesehen, die die Bibliothek 
versendet und habs nachgebaut.

Das was noch gefehlt hat, war ein MQTT-CONNECT. Nun funktionierts :)

Bitte melde dich an um einen Beitrag zu schreiben. Anmeldung ist kostenlos und dauert nur eine Minute.
Bestehender Account
Schon ein Account bei Google/GoogleMail? Keine Anmeldung erforderlich!
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.