You are here:

So fügst du ein benutzerdefiniertes Attribut zu Ihrer Z-Stack-Anwendung hinzu

art

Sind Sie schon einmal mit ZigBee-Produkten in Berührung gekommen? Sie sind jetzt überall zu finden. Man findet sie als kleine Sensoren in Gebäuden oder als Wandschalter, wenn man keine Kabel in der Wand verlegen möchte. ZigBee ist ein recht vielseitiges drahtloses Protokoll. Es basiert auf IEEE 802.15.4.

Die ZigBee Aliance hat verschiedene Anwendungen beschrieben, in denen ZigBee eingesetzt werden kann. Diese Funktionalitäten sind in Clustern gruppiert. Jedem Cluster sind Befehle und Attribute zugeordnet.

Ein Beispiel: Eine Glühbirne oder ein Schalter würde sich auf den OnOff ZigBeeCluster verlassen. Dieser Cluster definiert die Befehle On, Off und Toggle. Das entsprechende Attribut wäre ein OnOff-Attribut, das den aktuellen Zustand des Schalters oder der Glühbirne darstellt.

Es gibt noch viele weitere vordefinierte Cluster. So gibt es beispielsweise Cluster für Smart-Metering-Anwendungen, Beleuchtung (RGB, Hue usw.), Hausautomatisierung und so weiter.

Nachdem ich mehrere verschiedene Sensoren und Glühbirnen verwendet habe, die alle ZigBee unterstützen, habe ich darüber nachgedacht, mein eigenes ZigBee-Endgerät zu bauen. Dafür habe ich den TI CC2530/31 Chipsatz als SOC gewählt. Dieser Chip ist relativ leicht erhältlich, auch auf einigen chinesischen Marktportalen 😉 und er kommt mit einer gut dokumentierten Firmware-Bibliothek namens Z-Stack.

Der Z-Stack macht es viel einfacher, ein eigenes Gerät mit ZigBee-Funktionalität zu implementieren. Früher dachte ich immer, dass ich all diese Dinge selbst programmieren sollte und muss. Sei es, weil ich nichts von der Existenz solcher Stacks wusste oder weil ich dachte, dass ich die tiefsten inneren Vorgänge eines solchen Netzwerks verstehen muss. Heute kann ich sagen, dass es gar nicht so schlecht war, diese Erfahrungen zu machen. Aber heutzutage sind meine Projekte viel komplexer als früher und ich mag es, wenn ich mit Hilfe von bereits funktionierenden Codebasen loslegen kann.

Heute zeige ich Ihnen, wie Sie Ihre eigenen, benutzerdefinierten Attribute zu einer Z-Stack-basierten Anwendung hinzufügen können und wie Sie aus einer zigbee2mqtt-Umgebung mit Hilfe von mqtt- und zigbee sheperd-Konvertern darauf schreiben können.

Was Sie brauchen werden:

  • IAR-Embedded Workbench für 8051. Es ist eine 30-Tage-Testversion verfügbar
  • CC-Debugger zum Herunterladen Ihres Codes auf das Target
  • Zielplatine. Es könnte auch ein CC2531 USB-Stick sein
  • zigbee2mqtt zum Laufen bringen (Siehe hier: https://www.zigbee2mqtt.io/getting_started/running_zigbee2mqtt.html)
  • mqtt-Client. MQTT.fx erledigt die Arbeit.
  • Text-Editor
  • Win SCP zum komfortablen Editieren der zigbee2mqtt Konfigurationsdateien
  • Z-Stack 1.2.2 Firmware (http://www.ti.com/tool/Z-STACK-ARCHIVE auch Z-STACK-HOME genannt

Öffnen Sie ein Beispielprojekt aus Ihrem Z-STACK Ordner. Zum Beispiel SampleLight.eww

Unter App/zcl_samplelight.c finden Sie diese Code-Zeile:

  // Register the application's attribute list
  zcl_registerAttrList( SAMPLELIGHT_ENDPOINT, zclSampleLight_NumAttributes, zclSampleLight_Attrs );

Hier registrieren wir alle unsere Attribute bei den zugrunde liegenden Betriebssystemfunktionen und -methoden.

Was passiert also, wenn unser Gerät eine Schreibanforderung für ein Attribut erhält? Dieses Ereignis ruft zclProcessInWriteCmd in der Datei zcl.c in Zeile 4376 aus

376

#ifdef ZCL_WRITE
/*********************************************************************
 * @fn      processInWriteCmd
 *
 * @brief   Process the "Profile" Write and Write No Response Commands
 *
 * @param   pInMsg - incoming message to process
 *
 * @return  TRUE if command processed. FALSE, otherwise.
 */
static uint8 zclProcessInWriteCmd( zclIncoming_t *pInMsg )
{
  zclWriteCmd_t *writeCmd;
  zclWriteRspCmd_t *writeRspCmd;
  uint8 sendRsp = FALSE;
  uint8 j = 0;
  uint8 i;

  writeCmd = (zclWriteCmd_t *)pInMsg->attrCmd;
  if ( pInMsg->hdr.commandID == ZCL_CMD_WRITE )
  {
    // We need to send a response back - allocate space for it
    writeRspCmd = (zclWriteRspCmd_t *)zcl_mem_alloc( sizeof( zclWriteRspCmd_t )
            + sizeof( zclWriteRspStatus_t ) * writeCmd->numAttr );
    if ( writeRspCmd == NULL )
    {
      return FALSE; // EMBEDDED RETURN
    }

    sendRsp = TRUE;
  }

  for ( i = 0; i < writeCmd->numAttr; i++ )
  {
    zclAttrRec_t attrRec;
    zclWriteRec_t *statusRec = &(writeCmd->attrList[i]);

    if ( zclFindAttrRec( pInMsg->msg->endPoint, pInMsg->msg->clusterId,
                         statusRec->attrID, &attrRec ) )
    {
      
      
      if ( statusRec->dataType == attrRec.attr.dataType )
      {
        uint8 status;

        // Write the new attribute value
        if ( attrRec.attr.dataPtr != NULL )

This function will search in our attributes list whether there is a attribute defined which match’s to the actually received write request. The actual request is stored inside of statusRec

But to be able to reach this function and therefore write into any attributes, you must enable ZCL_WRITE in your configuration. You can do this with preprocessor directives.

Da wir wissen, wie das System mit den Attributen umgeht, müssen wir unser eigenes Attribut zu dieser Liste hinzufügen. Die Attribute sind in der Datei zcl_samplelight_data.c definiert – beginnend in Zeile 193

{
    ZCL_CLUSTER_ID_GEN_BASIC,             // Cluster IDs - defined in the foundation (ie. zcl.h)
    {  // Attribute record
      ATTRID_BASIC_HW_VERSION,            // Attribute ID - Found in Cluster Library header (ie. zcl_general.h)
      ZCL_DATATYPE_UINT8,                 // Data Type - found in zcl.h
      ACCESS_CONTROL_READ,                // Variable access control - found in zcl.h
      (void *)&zclSampleLight_HWRevision  // Pointer to attribute variable
    }
  },

Der obige Code zeigt ein Beispielattribut. Jetzt werden wir unser eigenes definieren und am Ende der Liste anhängen.

// My custom attribute
  {
    ZCL_CLUSTER_ID_GEN_BASIC,
    { // Attribute record
      0x4010, //Custom Attribute ID
      ZCL_DATATYPE_UINT16,
      (ACCESS_CONTROL_READ | ACCESS_REPORTABLE | ACCESS_CONTROL_WRITE),
      (void *)&REG_MY_CUSTOM_REGISTER
    }
  }

Der obige Code zeigt unser eigenes Attribut. Ich habe den GEN_BASIC Cluster verwendet und einen UINT16 Datentyp definiert. Ich habe einige Inline-Kommentare gesehen, in denen jemand schrieb, dass es Probleme mit uint8 und Booleschen Werten gibt. Wenn Sie jemals Probleme mit ihnen haben, versuchen Sie stattdessen uint16.

Jetzt müssen wir über Änderungen an unserem Attribut informiert werden. Leider scheint es dafür keine Out-of-the-Box-Lösung zu geben. Es gibt eine für vordefinierte Attribut-IDs, aber wir haben unsere eigenen definiert. Also habe ich einen Workaround gefunden, der die Aufgabe recht gut zu erfüllen scheint.

Immer wenn ein Ereignis eintritt, springt das System in eine Ereignisschleife, die in der Datei zcl_samplelight.c in Zeile 403 deklariert ist. Es gibt mehrere Fälle, aber das Schreiben in ein Attribut erfüllt keinen dieser Fälle, also werden wir den Standardfall verwenden.

  default:
    //Check if a variable has been updated!
    if(REG_MY_CUSTOM_REGISTER_OLD != REG_MY_CUSTOM_REGISTER)
    {
      REG_MY_CUSTOM_REGISTER_OLD = REG_MY_CUSTOM_REGISTER;
    }        
    break;

Hier prüfe ich, ob sich das Register geändert hat. Wenn ja, kann ich meinen gewünschten Codeabschnitt ausführen. Diese Methode der Beobachtung auf Änderungen hat den Vorteil, dass sie nicht nach Änderungen fragt. Wir werden über sie informiert. Ok, nicht explizit über Änderungen, die unser Attribut betreffen, aber der Overhead, der dadurch entsteht, wenn man mehrere Attribute innerhalb des Standardfalls hat, wird trotzdem marginal sein.

Hinzufügen von Konvertern zu zigbee2mqtt

Nun möchten wir in der Lage sein, die Attributwerte von zigbee2mqtt zu setzen. Dazu müssen wir zunächst ein neues Gerät definieren. https://www.zigbee2mqtt.io/how_tos/how_to_support_new_devices.html

 

 

Der obige Link gibt Ihnen einige grundlegende Informationen über diesen Prozess. Wenn Sie Ihr Gerät hinzugefügt haben, dann müssen Sie einen “ToZigbee”-Konverter bereitstellen.

In Ihrer devices.js müssen Sie etwas linke dieses bereitstellen:


toZigbee: [tz.FooBar],

Die Implementierung von tz.FooBar in der Datei toZigbee.js wird wie folgt aussehen:

FooBar: {
        key: ['MyAttributeKey'],
        convert: (key, value, message, type, postfix, options) => {
            if (type === 'set') {
                if (value === 'default') {
                    value = 1;
                }
				const lookup = {
                'Right': '0',
                'Left': '1',
                'IDLE': '2',
				};
				
				value = lookup[value];
				if( ((value >= 0) && value < 3) == false ) value = 2;
				
                return [{
                    cid: 'genBasic',
                    cmd: 'write',
                    cmdType: 'foundation',
                    zclData: [{
                        attrId: 0x4010,
                        dataType: 0x21,
                        attrData: value,
                    }],
                    cfg: cfg.default,
                }];
            }
        },
    },

Lassen Sie mich den obigen Code erklären. Das erste, was wir sehen werden, ist der Parameter MyAttributeKey. Dies ist der Parameter, unter dem Sie Ihr Attribut mit mqtt einstellen können. Ich werde Ihnen das später zeigen. In Zeile 8 haben wir eine Lookup-Tabelle für die Umwandlung von Strings in Zahlen. Dies ist optional, bietet aber einige Vorteile. In Zeile 14 wandeln wir die Eingabe von einer Zeichenkette in eine Zahl um. In der nächsten Zeile prüfen wir, ob ein gültiger Wert vorliegt. Zeile 17 ist für den Aufbau unseres Zigbee-Pakets verantwortlich.

Wir müssen auch die Cluster-ID (cid) definieren, die identisch ist mit dem, was wir bisher in unserer Firmware definiert haben. Attribut-ID und Datentyp müssen ebenfalls übereinstimmen. Für den Datentyp gibt es im Internet eine Liste, in der die verschiedenen Typen aufgeführt sind. http://www.zigbee.org/wp-content/uploads/2014/10/07-5123-06-zigbee-cluster-library-specification.pdf
Beginnend auf Seite 85. Achten Sie darauf, dass Sie die richtige Klasse wählen!

Wenn Sie Ihre Konfigurationsdatei geschrieben haben, ist es an der Zeit, zigbee2mqtt neu zu starten.

Testen Sie Ihre Arbeit!
Setzen Sie hier einen Haltepunkt und beginnen Sie mit dem Debugging.

  default:
    //Check if a variable has been updated!
    if(REG_MY_CUSTOM_REGISTER_OLD != REG_MY_CUSTOM_REGISTER)
    {
      REG_MY_CUSTOM_REGISTER_OLD = REG_MY_CUSTOM_REGISTER;
    }        
    break;

Nachdem das Gerät in zigbee2mqtt angezeigt wird, sollten Sie in der Lage sein, neue Attributwerte mit mqtt an das Gerät zu senden.

Öffnen Sie mqtt.fx, konfigurieren Sie Ihren Server und verbinden Sie sich. Gehen Sie nun auf die Registerkarte “Veröffentlichen” und geben Sie die folgende Zeichenfolge ein:

zigbee2mqtt/YOUR UNIQUE IDENTIFIER/set/MyAttributeKey

Ersetzen Sie IHREN EINZIGARTIGEN IDENTIFIER durch Ihren Identifikator, der im Konsolenprotokoll von zigbee2mqtt angezeigt wird.

Nun können Sie einige Werte von mqtt.fx an Ihren Server senden und zigbee2mqtt sollte diese Nachrichten an Ihr Gerät liefern. Dies wird etwa so aussehen:

zigbee2mqtt:info XX/XX/XXXX, XX:XX:XX PM Zigbee publish to device '0x00124xxxxxxxxxx', genBasic - write - 
[{"attrId":16400,"dataType":33,"attrData":"1"}] - {"manufSpec":0,"disDefaultRsp":0} - 8

Sie sollten jetzt auch Ihren Haltepunkt treffen und den neuen Wert sehen, der Ihrem Attribut zugewiesen wurde!

Gut gemacht! Ich hoffe, dies war hilfreich für jemanden.