Rechercher par mot-clé

Déplacer le PDM de la ZiGate

Objectif principal: Augmenter la mémoire et le nombre d’appareils gérés

Le PDM (Persistent Data Manager) est une base de données contenant des éléments essentiels de la ZiGate comme les appareils enregistrés, certains routages, les groupes, les « binds » etc…

Cette mémoire est généralement contenue dans la mémoire EEprom de la ZiGate. Pour les développeurs, vous n’avez quasiment pas besoin de vous en préoccuper. Vous savez qu’elle est là, mais vous n’avez, généralement pas besoin de vous soucier de dialoguer avec elle. Tout se fait dans la stack.

Si je vous en parle, c’est que cette mémoire, sauvegardée dans une EEprom de 4Ko, a certaines limites (physiques) (principalement le nombre d’appareils gérés).

Pour augmenter la capacité de le PDM, il existe néanmoins une solution.

Déplacer le PDM sur un hôte

Que vous ayez un Raspberry, un ordinateur ou n’importe quel autre support, il est possible (à partir de la version v3.1d de la ZiGate) de déplacer le PDM sur l’hôte.

Cette possibilité nécessite un peu de développement… 🙂 Et oui, ça serait trop facile sinon.

En effet, afin de pouvoir construire, alimenter et lire le PDM sur votre hôte, il faut pouvoir dialoguer avec votre hôte. Il existe déjà un protocole pour le faire… sisi … souvenez vous des commandes classiques émises par votre box ou plugin pour récupérer la version de la ZiGate. Et bien, ce sera le même principe.

Voici la liste des commandes à rajouter dans votre plugin :

E_SL_MSG_SAVE_PDM_RECORD = 0x0200
E_SL_MSG_SAVE_PDM_RECORD_RESPONSE = 0x8200
E_SL_MSG_LOAD_PDM_RECORD_REQUEST = 0x0201
E_SL_MSG_LOAD_PDM_RECORD_RESPONSE = 0x8201
E_SL_MSG_GET_BITMAP_RECORD_REQUEST = 0x0206
E_SL_MSG_GET_BITMAP_RECORD_RESPONSE = 0x8206
E_SL_MSG_INCREMENT_BITMAP_RECORD_REQUEST = 0x0207
E_SL_MSG_INCREMENT_BITMAP_RECORD_RESPONSE = 0x8207
E_SL_MSG_PDM_EXISTENCE_REQUEST = 0x0208
E_SL_MSG_PDM_EXISTENCE_RESPONSE = 0x8208
E_SL_MSG_PDM_HOST_AVAILABLE = 0x0300
E_SL_MSG_PDM_HOST_AVAILABLE_RESPONSE = 0x8300
E_SL_MSG_PDM_LOADED = 0x0302

Structure du PDM

Pour bien comprendre la suite, voici à quoi ressemble le PDM. Dans cette exemple, j’ai pris le parti de prendre une base de données SQLite mais rien ne vous empêche d’utiliser un fichier ou autre technique de gestion de données.

PDM_Structure

En ce qui concerne la structure interne, elle est définie par la stack NXP et nous n’avons pas vraiment la main dessus autrement que par passer par le configurateur NXP (le xxx.zpscfg).

Rien n’empêche cependant de stocker sa propre table avec ses propres données.

Voici à quoi ressemble la base de données PDM et les tables correspondantes :

PDM_ID_APP_VERSION                                                  = 0x10          –> 4 octets
PDM_ID_APP_ZLL_CMSSION                                        = 0x01          –> 32 octets
PDM_ID_INTERNAL_AIB                                                 = 0xF000      –> 20 octets
PDM_ID_INTERNAL_BINDS                                            = 0xF001     –> 40 octets
PDM_ID_INTERNAL_GROUPS                                       = 0xF002      –> 20 octets
PDM_ID_INTERNAL_APS_KEYS                                    = 0xF003      –> 96 octets
PDM_ID_INTERNAL_TC_TABLE                                    = 0xF004      –> 560 octets
PDM_ID_INTERNAL_TC_LOCATIONS                          = 0xF005       –> 280 octets
PDM_ID_INTERNAL_NIB_PERSIST                              = 0xF100       –> 24 octets
PDM_ID_INTERNAL_CHILD_TABLE                             = 0xF101       –> 1800 octets
PDM_ID_INTERNAL_SHORT_ADDRESS_MAP           = 0xF102       –> 280 octets
PDM_ID_INTERNAL_NWK_ADDRESS_MAP               = 0xF103       –> 1120 octets
PDM_ID_INTERNAL_ADDRESS_MAP_TABLE            = 0xF104       –> 280 octets
PDM_ID_INTERNAL_SEC_MATERIAL_KEY                = 0XF105       –> 32 octets

Taille totale : 4588 octets

Diagramme d’action

Voici le principe de fonctionnement de la ZiGate avec le PDM. Bien entendu, le principe est le même pour la gestion du PDM en interne.

PDM_diagramme_action

Ce schéma est bien entendu simplifié et peut-être incomplet, mais permet de comprendre comment les échanges se font entre la base de données PDM et la ZiGate.

Code pour chaque évènement

Pour le moment, j’ai recensé uniquement les commandes essentielles. Au démarrage de la ZiGate, seules les commande suivantes sont demandées:

0x0300, 0x0200, 0x0201, 0x0208, 0x0206, 0x0207

Les deux dernières ne sont pas utilisées pour le moment, mais il convient d’au moins répondre correctement.

Commande 0x0300

def vPDMHostAvailableRequest(self,sData):
    """ Internal function"""
    RecordId = (''.join(x.encode('hex') for x in sData))
    status='00'
    self.oSL.SendMessageNoAck(E_SL_MSG_PDM_HOST_AVAILABLE_RESPONSE, (status))

Commande 0x0206

def vPDMGetBitmapRequest(self,sData):
    """ Internal function"""
    RecordId = (''.join(x.encode('hex') for x in sData))
    status='00'
    self.oSL.SendMessageNoAck(E_SL_MSG_GET_BITMAP_RECORD_RESPONSE, (status+RecordId+"00000000000000000000000000000000"))

Commande 0x0207

def vPDMIncBitmapRequest(self,sData):
    """ Internal function"""
    RecordId = (''.join(x.encode('hex') for x in sData))
    status='00'
    self.oSL.SendMessageNoAck(E_SL_MSG_INCREMENT_BITMAP_RECORD_RESPONSE, (status+RecordId+"00000000000000000000000000000000"))

Commande 0x0208

def vPDMExistanceRequest(self,sData):
    """ Internal function"""
    conn = sqlite3.connect('pdm.db')
    c = conn.cursor()
    conn.text_factory = str
    RecordId = (''.join(x.encode('hex') for x in sData))
    c.execute("SELECT COUNT(PdmRecId) FROM PdmData WHERE PdmRecId = ?", (RecordId,))
    data=c.fetchone() 
    status='00'
    if (data[0] == 0):
        self.oSL.SendMessageNoAck(E_SL_MSG_PDM_EXISTENCE_RESPONSE, (RecordId+"000000"))
    else:
        c.execute("SELECT PdmRecSize FROM PdmData WHERE PdmRecId = ?", (RecordId,))
        data=c.fetchone() 
        size = int(data[1],16)
        self.oSL.SendMessageNoAck(E_SL_MSG_PDM_EXISTENCE_RESPONSE, (RecordId+'01'+str(size)))
    conn.commit() 
    conn.close()

Commande 0x0200

def vPDMSaveRequest(self,sData):
    """ Internal function
    """
    conn = sqlite3.connect('/home/pi/host/pdm.db')
    c = conn.cursor()
    conn.text_factory = str
    RecordId = (''.join(x.encode('hex') for x in sData[:2]))
    CurrentCount = (''.join(x.encode('hex') for x in sData[4:6]))
    u32NumberOfWrites = (''.join(x.encode('hex') for x in sData[6:8]))
    u32Size = (''.join(x.encode('hex') for x in sData[2:4]))
    dataReceived = int((''.join(x.encode('hex') for x in sData[8:10])),16)
    sWriteData=(''.join(x.encode('hex') for x in sData[10:(dataReceived)]))
    c.execute("SELECT * FROM PdmData WHERE PdmRecId = ?", (RecordId,))
    data=c.fetchone() 
    if data is None:
        sql="INSERT INTO PdmData (PdmRecId,PdmRecSize,PersistedData) VALUES ('"+RecordId+"','"+u32Size+"','"+sWriteData+"')"
        c.execute(sql)
    else:
        if(int(u32NumberOfWrites)>0 ):
            sWriteData = data[2]+sWriteData 
            c.execute("DELETE from PdmData WHERE PdmRecId = ? ",(RecordId,))
            c.execute("INSERT INTO PdmData (PdmRecId,PdmRecSize,PersistedData) VALUES (?,?,?)",(RecordId,u32Size,sWriteData))
        else:
            c.execute("DELETE from PdmData WHERE PdmRecId = ? ",(RecordId,))
            c.execute("INSERT INTO PdmData (PdmRecId,PdmRecSize,PersistedData) VALUES (?,?,?)",(RecordId,u32Size,sWriteData))
    oCB.oSL._WriteMessage(E_SL_MSG_SAVE_PDM_RECORD_RESPONSE,"00"+RecordId+u32NumberOfWrites)
    conn.commit()
    conn.close()

Commande 0x0201

def vPDMLoadRequest(self,sData):
    """ Internal function"""
    conn = sqlite3.connect('pdm.db')
    c = conn.cursor()
    conn.text_factory = str
    RecordId = (''.join(x.encode('hex') for x in sData))
    c.execute("SELECT * FROM PdmData WHERE PdmRecId = ?", (RecordId,))
    data=c.fetchone() 
    status='00'
    if data is None:
        TotalBlocks = 0
        BlockId = 0
        size =0
        self.oSL.SendMessageNoAck(E_SL_MSG_LOAD_PDM_RECORD_RESPONSE, (status+RecordId+str(size).zfill(8)+str(TotalBlocks).zfill(8)+str(BlockId).zfill(8))+str(size).zfill(8))
    else:
        status='02'
        persistedData = data[2]
        size = data[1]
        TotalBlocks = (long(size,16)/128)
        if((long(size,16)%128)>0):
             NumberOfWrites = TotalBlocks + 1
        else:
             NumberOfWrites = TotalBlocks
        bMoreData=True
        count =0
        lowerbound = 0
        upperbound = 0
        while(bMoreData):
            u32Size = long(size,16) - (count*128)
            if(u32Size>128):
                u32Size = 256
            else:
                bMoreData = False
                u32Size = u32Size*2

            upperbound =upperbound + u32Size
            DataStrip = persistedData[lowerbound:upperbound]
            count = count+1
            self.oSL.SendMessageNoAck(E_SL_MSG_LOAD_PDM_RECORD_RESPONSE,(status+RecordId+size+(hex(NumberOfWrites).strip('0x')).strip('L').zfill(8)+(hex(count).strip('0x')).strip('L').zfill(8)+(hex(u32Size/2).strip('0x')).strip('L').zfill(8)+DataStrip)) 
            lowerbound = lowerbound+u32Size 

    conn.commit()
    conn.close()

Conclusion

Avec cette méthode, vous pourrez augmenter le nombre d’appareils ZigBee gérés par une ZiGate. Cette méthode a été testée avec une ZiGate-USB, une PiZiGate et une ZiGate-DIN. Malgré tout, il existe une limitation, la mémoire RAM. En effet, il faut savoir que pour des raisons de performances les données sont transmises dans la mémoire RAM et cette mémoire regroupe certaines fonctions et variables du programme. On ne peut donc pas rajouter autant d’appareils que l’on souhaite. D’autre part, il est aussi possible que le rajout de fonctionnalités dans la ZiGate viennent limiter encore plus cette mémoire. C’est pour cette raison que malgré tout, il existe une limite.

Avec cette méthode, il est possible d’atteindre un nombre de :

  • 140 appareils gérés (avec les routeurs) — (70 avec le PDM intégré)
  • 80 appareils gérés en direct                     — (40 avec le PDM intégré)

La sauvegarde et la restauration des données est aussi facilité car il suffit simplement de copier / coller votre BDD ou fichier.

Je tiens à remercier @schrodingersket qui a contribué sur cette partie et m’a permis d’avancer plus vite que prévu.

Pour ceux qui le souhaitent, je fourni un pack développeur contenant :

  • Le dernier firmware comprenant la gestion du PDM sur un hôte
  • ZWGUI mis à jour (pour voir le fonctionnement complet)
  • Une BDD vide (PDM.db)

N’hésitez pas à revenir vers moi pour avoir d’autres informations.

Translate »