{"id":2461,"date":"2020-08-06T12:00:57","date_gmt":"2020-08-06T17:00:57","guid":{"rendered":"http:\/\/blog.espol.edu.ec\/girni\/?p=2461"},"modified":"2026-07-23T12:58:43","modified_gmt":"2026-07-23T17:58:43","slug":"lorawan-mqtt-archivo-con-python","status":"publish","type":"post","link":"https:\/\/blog.espol.edu.ec\/girni\/lorawan-mqtt-archivo-con-python\/","title":{"rendered":"8. Chirpstack - mensajes MQTT y Reporte con Python"},"content":{"rendered":"\n<h2 class=\"wp-block-heading\">1. LoRaWAN - mensajes MQTT<\/h2>\n\n\n\n<p>Los datos son enviados como un tren de bits escritos en hexadecimal desde el dispositivo.<\/p>\n\n\n\n<p>Por ejemplo, un mensaje a transmitir conformado por 4 datos num\u00e9ricos en hexadecimal desde el dispositivo son:<\/p>\n\n\n\n<pre class=\"wp-block-code alignwide\"><code>static void prepareTxFrame( uint8_t port ){\n    appDataSize = 4;\/\/AppDataSize max value is 64\n    appData&#091;0] = 0x00;\n    appData&#091;1] = 0x01;\n    appData&#091;2] = 0x02;\n    appData&#091;3] = 0x03;\n}<\/code><\/pre>\n\n\n\n<p>Se leen en un mensaje mqtt como:<\/p>\n\n\n\n<pre class=\"wp-block-code alignwide\"><code>application\/1\/device\/3532363324003700\/event\/up {\n    \"applicationID\":\"1\",\n    \"applicationName\":\"GirniAppPrueba01\",\n    \"deviceName\":\"GirniDispositivo01\",\n    \"devEUI\":\"3532363324003700\",\n    \"txInfo\":{\"frequency\":902700000,\"dr\":0},\n    \"adr\":true,\n    \"fCnt\":0,\n    \"fPort\":2,\n    <strong>\"data\":\"AAECAw==\"<\/strong>\n    }<\/code><\/pre>\n\n\n\n<p>por lo que el mensaje recibido por el gateway es:<\/p>\n\n\n\n<pre class=\"wp-block-code alignwide\"><code>\"data\":\"AAECAw==\"<\/code><\/pre>\n\n\n\n<p>que para leerlo nuevamente en hexadecimal requiere ser traducido desde \"base64\" por ejemplo en la consola de Rasberry OS como:<\/p>\n\n\n\n<pre class=\"wp-block-code alignwide\"><code>pi@GWHTM01:~ $ echo -n AAECAw== | base64 --decode | hexdump -C\n00000000  <strong>00 01 02 03<\/strong>                                       |....|\n00000004<\/code><\/pre>\n\n\n\n<p>que es el mensaje original enviado desde el dispositivo<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">2. Mensajes MQTT con Python<\/h2>\n\n\n\n<p>De disponer de acceso al servidor MQTT se pueden capturar los mensajes y procesarlos por ejemplo para realizar estad\u00edsticas de RSSI y SNR de cada dispositivo respecto al Gateway.<\/p>\n\n\n\n<p>Se requiere una conexi\u00f3n autenticada al servidor MQTT, para caso experimental se recurre a Python con la librer\u00eda PAHO para acceder como cliente.<\/p>\n\n\n\n<pre class=\"wp-block-code alignwide\"><code># grabando mensajes MQTT\n# configurar los par\u00e1metros para la creacion de archivo de texto\nimport paho.mqtt.client as mqtt\n\n# INGRESO\n# Parametros para la conexi\u00f3n\nservidormqtt = \"192.168.xx.xx\"\ntopicolee = \"#\"\n\n# Para el archivo.txt\ncuentamensaje = 0\nnombrearchivo = 'unreporte.txt'\narchivo = open(nombrearchivo,'w')\narchivo.close()  # Cierra el archivo\n\ntramabuscada = &#091;'application\/1\/device\/c9c2a03913ac2ec5\/event\/up',\n                'application\/1\/device\/b35915f22af620d1\/event\/up',\n                'application\/1\/device\/b5a2349b828d97ea\/event\/up',\n                'application\/1\/device\/f2f724028c0e996c\/event\/up']\ncuentaDisp = &#091;0,0,0,0]\ncuentagtw = 0\n\n# BLOQUE B\u00c1SICO DE PROGRAMA\n# Al recibir CONNACK desde el servidor.\ndef on_connect(client, userdata, flags, rc):\n    print(\"Conexi\u00f3n\/c\u00f3digo de resultado: \"+str(rc))\n\n    # Inicio o renovaci\u00f3n de subscripci\u00f3n\n    client.subscribe(topicolee)\n\n    return()\n\n# el t\u00f3pico tiene una publicaci\u00f3n\ndef on_message(client, userdata, msg):\n    global cuentamensaje\n    print(msg.topic) #+\" \"+str(msg.payload))\n    unmensaje = msg.topic+\" \"+str(msg.payload)\n\n    # Archivo en modo a\u00f1adir 'append'\n    archivo = open(nombrearchivo,'a')\n    unalinea = unmensaje + '\\n'\n    archivo.write(unalinea)\n    \n    if (msg.topic in tramabuscada):\n        cual = tramabuscada.index(msg.topic)\n        cuentaDisp&#091;cual] = cuentaDisp&#091;cual] + 1\n        \n    cuentamensaje = cuentamensaje + 1\n    print('\\n mensajes recibidos: ', cuentamensaje)\n    print(' cuenta por dispositivos: ', cuentaDisp)\n    print()\n\n    return()\n\nclient = mqtt.Client()\nclient.on_connect = on_connect\nclient.on_message = on_message\nclient.connect(servidormqtt, 1883, 60)\nclient.loop_forever()<\/code><\/pre>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\" \/>\n\n\n\n<h2 class=\"wp-block-heading\">3. Mensajes MQTT y reporte txt con Python<\/h2>\n\n\n\n<p>Para procesar el archivo del reporte.txt se puede leer mensaje por mensaje y extraer el registro de RSSI y SNR de cada dispositivo usando Python.<\/p>\n\n\n\n<pre class=\"wp-block-code alignwide\"><code># procesar un archivo de mensajes mqtt\nimport numpy as np\nimport json as json\n\n# INGRESO\n# Parametros para la conexi\u00f3n\nservidormqtt = \"192.168.xx.xx\"\ntopicolee = \"#\"\n\n# Para el archivo.txt\nnombrearchivo = 'unreporte.txt'\nconteo = {\"lineas\":{'cuenta':0},\n          \"gateway\":{'cuenta':0},\n          \"application\":{'cuenta':0}\n          }\ntipotrama = &#091;]\n\n# para tabular\ntabla = &#091;]\nregistros = {}\nfrecuencias = &#091;]\ntramabuscada = &#091;'application\/1\/device\/c9c2a03913ac2ec5\/event\/up',\n                'application\/1\/device\/b35915f22af620d1\/event\/up',\n                'application\/1\/device\/b5a2349b828d97ea\/event\/up',\n                'application\/1\/device\/f2f724028c0e996c\/event\/up']\n\n\n# PROCEDIMIENTO\narchivo = open(nombrearchivo,'r')\nunalinea = archivo.readline()\nwhile not(unalinea==''):\n    conteo&#091;\"lineas\"]&#091;'cuenta'] = conteo&#091;\"lineas\"]&#091;'cuenta'] + 1\n    unalinea = archivo.readline()\n    unalinea = unalinea.strip('\\n')\n    desde = unalinea.find(\" b'{\")\n    untipotrama = unalinea&#091;0:desde]\n    if not(untipotrama in tipotrama) and (untipotrama != ''):\n        tipotrama.append(untipotrama)\n        conteo&#091;untipotrama] = {'cuenta':0}\n    for entrada in conteo:\n        if unalinea.startswith(entrada):\n            conteo&#091;entrada]&#091;'cuenta'] = conteo&#091;entrada]&#091;'cuenta']+1\n\n    for unapptrama in tramabuscada:\n        tamanot = len(unapptrama)\n        if unalinea.startswith(unapptrama):\n            datostxt = unalinea&#091;tamanot+3:].strip(\"'\")\n            datosjson = json.loads(datostxt)\n\n            undeviceName = datosjson&#091;\"deviceName\"]\n            unrssi = datosjson&#091;\"rxInfo\"]&#091;0]&#091;\"rssi\"]\n            unloRaSNR = datosjson&#091;\"rxInfo\"]&#091;0]&#091;\"loRaSNR\"]\n            unfrequency = datosjson&#091;\"txInfo\"]&#091;\"frequency\"]\n            undr = datosjson&#091;\"txInfo\"]&#091;\"dr\"]\n            if not(unfrequency in frecuencias):\n                frecuencias.append(unfrequency)\n                \n            if undeviceName in registros:\n                registros&#091;undeviceName]&#091;'conteo'] = registros&#091;undeviceName]&#091;'conteo']+1\n                registros&#091;undeviceName]&#091;'rssi'].append(&#091;unfrequency,unrssi,\n                                                         unloRaSNR,undr])\n            else:\n                registros&#091;undeviceName]={'rssi':&#091;],\n                                         'conteo':0,\n                                         'resumen':&#091;0,0,0,0],\n                                         }      \n \narchivo.close()  # Cierra el archivo\n\n# Calcula resumen por dispositivo\nfrecuencias.sort()\nfrecuencia = &#091;902300000, 902500000, 902700000, 902900000,\n              903100000, 903300000, 903500000, 903700000]\ncanalobserva = len(frecuencia)                                               \nfor dispositivo in registros:\n    vector = np.array(registros&#091;dispositivo]&#091;'rssi'])\n    cuenta = np.zeros(canalobserva, dtype=int)   \n    suma = np.zeros(canalobserva,dtype=int)\n    channelid = np.arange(1, canalobserva+1)\n    cadaRSSI = &#091;&#091;],&#091;],&#091;],&#091;],&#091;],&#091;],&#091;],&#091;],&#091;]]\n    cadaSNR  = &#091;&#091;],&#091;],&#091;],&#091;],&#091;],&#091;],&#091;],&#091;],&#091;]]\n    cadadr   = &#091;&#091;],&#091;],&#091;],&#091;],&#091;],&#091;],&#091;],&#091;],&#091;]]\n    resumen = &#091;]\n    # Clasificando\n    k = len(vector)\n    for j in range (0,k,1):\n        cual = frecuencia.index(vector&#091;j]&#091;0])\n        valorRssi = vector&#091;j]&#091;1]\n        valorSNR = vector&#091;j]&#091;2]\n        valordr = vector&#091;j]&#091;3]\n        cadaRSSI&#091;cual].append(valorRssi)\n        cadaSNR&#091;cual].append(valorSNR)\n        cadadr&#091;cual].append(valordr)\n    \n    for j in range(0,canalobserva,1):\n        vectorRSSI = np.array(cadaRSSI&#091;j])\n        vectorSNR = np.array(cadaSNR&#091;j])\n        vectordr = np.array(cadadr&#091;j])\n        if len(vectorRSSI)&gt;0:\n            promedioRSSI = np.average(vectorRSSI)\n            maximoRSSI = np.max(vectorRSSI)\n            cantidadRSSI = len(vectorRSSI)\n            minimoRSSI = np.min(vectorRSSI)\n            \n            promedioSNR = np.average(vectorSNR)\n            maximoSNR = np.max(vectorSNR)\n            cantidadSNR = len(vectorSNR)\n            minimoSNR = np.min(vectorSNR)\n\n            promediodr = np.average(vectordr)\n            maximodr = np.max(vectordr)\n            cantidaddr = len(vectordr)\n            minimodr = np.min(vectordr)\n            \n            resumen.append(np.array(&#091; j+1,\n                                      cantidadRSSI,\n                                      promedioRSSI,\n                                      minimoRSSI,\n                                      maximoRSSI,\n                                      promedioSNR,\n                                      minimoSNR,\n                                      maximoSNR,\n                                      promediodr,\n                                      ]))\n    resumen = np.array(resumen)\n    registros&#091;dispositivo]&#091;'resumen'] = resumen\n\n# SALIDA\nnp.set_printoptions(precision = 2)\nprint('conteo de tramas')\nfor entrada in conteo:\n    print(entrada,\": \", conteo&#091;entrada]&#091;'cuenta'])\nprint('frecuencias: ',frecuencia)\nprint('\\nTabulando ---------')\nfor dispositivo in registros:\n    print()\n    print('dispositivo_id: '+str(dispositivo))\n    print('total muestras: '+str(registros&#091;dispositivo]&#091;'conteo']))\n    print('Promedios RSSI y SNR: ')\n    print('&#091;canal muestras\\t RSSI \\t min \\t max \\t SNR  min   max   DR  ]')\n    print(registros&#091;dispositivo]&#091;'resumen'])\n    registros&#091;dispositivo]&#091;'conteo']<\/code><\/pre>\n\n\n\n<p>&nbsp;<\/p>\n","protected":false},"excerpt":{"rendered":"<p>1. LoRaWAN - mensajes MQTT Los datos son enviados como un tren de bits escritos en hexadecimal desde el dispositivo. Por ejemplo, un mensaje a transmitir conformado por 4 datos num\u00e9ricos en hexadecimal desde el dispositivo son: Se leen en un mensaje mqtt como: por lo que el mensaje recibido por el gateway es: que [&hellip;]<\/p>\n","protected":false},"author":8043,"featured_media":0,"comment_status":"closed","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[1447338],"tags":[602214,1457736],"class_list":["post-2461","post","type-post","status-publish","format-standard","hentry","category-chirpstack-broker","tag-iot","tag-lorawan"],"_links":{"self":[{"href":"https:\/\/blog.espol.edu.ec\/girni\/wp-json\/wp\/v2\/posts\/2461","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/blog.espol.edu.ec\/girni\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/blog.espol.edu.ec\/girni\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/blog.espol.edu.ec\/girni\/wp-json\/wp\/v2\/users\/8043"}],"replies":[{"embeddable":true,"href":"https:\/\/blog.espol.edu.ec\/girni\/wp-json\/wp\/v2\/comments?post=2461"}],"version-history":[{"count":14,"href":"https:\/\/blog.espol.edu.ec\/girni\/wp-json\/wp\/v2\/posts\/2461\/revisions"}],"predecessor-version":[{"id":4408,"href":"https:\/\/blog.espol.edu.ec\/girni\/wp-json\/wp\/v2\/posts\/2461\/revisions\/4408"}],"wp:attachment":[{"href":"https:\/\/blog.espol.edu.ec\/girni\/wp-json\/wp\/v2\/media?parent=2461"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/blog.espol.edu.ec\/girni\/wp-json\/wp\/v2\/categories?post=2461"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/blog.espol.edu.ec\/girni\/wp-json\/wp\/v2\/tags?post=2461"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}