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.
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.
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.