1"use strict";(self.webpackChunkmeshtastic=self.webpackChunkmeshtastic||[]).push([["23121"],{75391(e,t,n){n.r(t),n.d(t,{metadata:()=>a,default:()=>f,frontMatter:()=>r,contentTitle:()=>d,toc:()=>m,assets:()=>l});var a=JSON.parse('{"id":"software/integrations/mqtt/adafruit-io","title":"Adafruit IO","description":"Adafruit IO for Meshtastic","source":"@site/versioned_docs/version-2.7/software/integrations/mqtt/adafruit-io.mdx","sourceDirName":"software/integrations/mqtt","slug":"/software/integrations/mqtt/adafruit-io","permalink":"/docs/2.7/software/integrations/mqtt/adafruit-io","draft":false,"unlisted":false,"tags":[],"version":"2.7","lastUpdatedBy":"rcarteraz","sidebarPosition":5,"frontMatter":{"id":"adafruit-io","title":"Adafruit IO","sidebar_label":"Adafruit IO","sidebar_position":5},"sidebar":"Sidebar","previous":{"title":"Home Assistant","permalink":"/docs/2.7/software/integrations/mqtt/home-assistant"},"next":{"title":"Meshtasticator (Simulator)","permalink":"/docs/2.7/software/meshtasticator/"}}'),i=n(91987),o=n(67008),s=n(87622);let r={id:"adafruit-io",title:"Adafruit IO",sidebar_label:"Adafruit IO",sidebar_position:5},d,l={},m=[{value:"Adafruit IO for Meshtastic",id:"adafruit-io-for-meshtastic",level:2}];function c(e){let t={a:"a",admonition:"admonition",code:"code",h2:"h2",p:"p",pre:"pre",...(0,o.R)(),...e.components};return(0,i.jsxs)(i.Fragment,{children:[(0,i.jsx)(t.h2,{id:"adafruit-io-for-meshtastic",children:"Adafruit IO for Meshtastic"}),"\n",(0,i.jsxs)(t.p,{children:["Adafruit IO can be used to graph telemetry and messages from a Meshtastic network via json/",(0,i.jsx)(s.A,{term:"MQTT",definition:"A lightweight messaging protocol designed for small sensors and mobile devices, enabling efficient data transmission in the Meshtastic network for Internet connectivity and integration with IoT platforms. See https://en.wikipedia.org/wiki/MQTT. In Meshtastic, MQTT is used to connect a node to the internet, and can be used to connect multiple meshes to each other.",routePath:"/docs/terms/",children:"mqtt"}),". The following example script will listen for node packets and publish voltage, ",(0,i.jsx)(s.A,{term:"RSSI",definition:"The raw power level of a received signal, where a value closer to zero is stronger. Clients rate signal quality from SNR rather than RSSI.",routePath:"/docs/terms/",children:"rssi"}),", ",(0,i.jsx)(s.A,{term:"SNR",definition:"A measure used in communications to quantify the level of a desired signal to the level of background noise. In Meshtastic and other wireless systems, a higher SNR indicates a clearer signal that can enhance the reliability and quality of data transmission.",routePath:"/docs/terms/",children:"snr"})," and messages to individual feeds on adafruit IO. If a feed doesn't exist it will be created. Be aware, however, that the free Adafruit account is limited to 10 feeds. Once your feeds are being populated with data you can create dashboards to display graphs and gauges with the data."]}),"\n",(0,i.jsx)(t.admonition,{type:"info",children:(0,i.jsxs)(t.p,{children:["To utilize this script you need to have an Adafruit IO account, a working ",(0,i.jsx)(s.A,{term:"MQTT",definition:"A lightweight messaging protocol designed for small sensors and mobile devices, enabling efficient data transmission in the Meshtastic network for Internet connectivity and integration with IoT platforms. See https://en.wikipedia.org/wiki/MQTT. In Meshtastic, MQTT is used to connect a node to the internet, and can be used to connect multiple meshes to each other.",routePath:"/docs/terms/",children:"mqtt"})," broker setup and a mesh node that is publishing to the ",(0,i.jsx)(s.A,{term:"MQTT",definition:"A lightweight messaging protocol designed for small sensors and mobile devices, enabling efficient data transmission in the Meshtastic network for Internet connectivity and integration with IoT platforms. See https://en.wikipedia.org/wiki/MQTT. In Meshtastic, MQTT is used to connect a node to the internet, and can be used to connect multiple meshes to each other.",routePath:"/docs/terms/",children:"mqtt"})," broker."]})}),"\n",(0,i.jsxs)(t.p,{children:["You will need to modify the ini sample file with your Adafruit IO and ",(0,i.jsx)(s.A,{term:"MQTT",definition:"A lightweight messaging protocol designed for small sensors and mobile devices, enabling efficient data transmission in the Meshtastic network for Internet connectivity and integration with IoT platforms. See https://en.wikipedia.org/wiki/MQTT. In Meshtastic, MQTT is used to connect a node to the internet, and can be used to connect multiple meshes to each other.",routePath:"/docs/terms/",children:"mqtt"})," credentials\nCode repository is here [",(0,i.jsx)(t.a,{href:"https://bitbucket.org/tavdog/mesh_aio_logger/src/main/",children:"https://bitbucket.org/tavdog/mesh_aio_logger/src/main/"}),"]"]}),"\n",(0,i.jsx)(t.pre,{children:(0,i.jsx)(t.code,{className:"language-ini",children:"[GENERAL]\nPRINT_CONFIG = false\n; https://pynative.com/list-all-timezones-in-python/\nTIMEZONE = US/Hawaii\nCHANNEL_LIST = LongFast,Private\n\n[MQTT]\nSERVER = mqtt.server.net\nPORT = 1883\nUSERNAME = a\nPASSWORD = a\n\n[AIO]\nUSER = a\nKEY = a\n;leave FEED_GROUP as Default is you don't want a separate group.\nFEED_GROUP = Default\n[LOG]\nVOLTAGE = true\nMESSAGE = true\nPOSITION = false\nSNR = false\nRSSI = false\n"})}),"\n",(0,i.jsx)(t.pre,{children:(0,i.jsx)(t.code,{className:"language-python",children:"# Persistent mqtt client, watch for voltage, telemetry, message packets and publish them to io\n\n# Import Adafruit IO REST client.\nfrom Adafruit_IO import Client, Feed, Data, RequestError\nfrom datetime import datetime\nimport paho.mqtt.client as mqtt\nimport os\nimport sys\nimport json\nimport time\nimport pytz\nimport configparser\n\nconfig = configparser.ConfigParser()\n# Read the configuration file\nconfig.read('config.ini')\n\nif config.getboolean('GENERAL','PRINT_CONFIG'):\n # Iterate through sections and options to print all config values\n for section in config.sections():\n print(f\"[{section}]\")\n for key, value in config.items(section):\n print(f\"{key} = {value}\")\n print() # Add an empty line between sections for better readability\n\n# set your timezone here\nTIMEZONE = config['GENERAL']['TIMEZONE']\nCHANNEL_LIST = config['GENERAL']['CHANNEL_LIST']\nMQTT_SERVER = config['MQTT']['SERVER']\nMQTT_PORT = int(config['MQTT']['PORT'])\nMQTT_USERNAME = config['MQTT']['USERNAME']\nMQTT_PASSWORD = config['MQTT']['PASSWORD']\nAIO_USER = config['AIO']['USER']\nAIO_KEY = config['AIO']['KEY']\nAIO_FEED_GROUP = config['AIO']['FEED_GROUP'] # leave as Default is you don't want a separate group.\nLOG_SNR = config.getboolean('LOG','SNR')\nLOG_VOLTAGE = config.getboolean('LOG','VOLTAGE')\nLOG_RSSI = config.getboolean('LOG','RSSI')\nLOG_MESSAGE = config.getboolean('LOG','MESSAGE')\nLOG_PO
1SITION = config.getboolean('LOG','POSITION')\n\n###### END SETTINGS ######\n\nmy_timezone = pytz.timezone(TIMEZONE)\nchannel_array = CHANNEL_LIST.split(',')\nprint(\"\\n\")\nmqttClient = mqtt.Client(\"mesh_client_rssi\")\nmqttClient.username_pw_set(MQTT_USERNAME, MQTT_PASSWORD)\n\naio = Client(AIO_USER,AIO_KEY)\n\n# bootstrap your node_db here if you want\n#node_db = dict()\nnode_db = { 947782216: 'G1 Kula',\n 1839130823: 'HR-Haleakala Repeater',\n 1969840052: 'MR8-Magnet Rak 8dbi',\n}\n\n# get feed object or create it if it doesn't exist yet.\ndef get_feed(full_name,kind):\n name = full_name.split('-')[0]\n #print(\"getting feed: \" + name)\n if \"\\\\x00\" in name or \"\\\\u0000\" in name or \"\\x00\" in name: # json don't like emojis\n name = full_name.split('-')[1].replace(' ','-').replace('.','-')\n try:\n feed = aio.feeds(f\"{AIO_FEED_GROUP}.{name.lower()}-{kind}\")\n except:\n print(\"creating feed:\" + f\"{AIO_FEED_GROUP}.{name.lower()}-{kind}\")\n # Create Feed\n new_feed = Feed(name=f\"{name}_{kind}\")\n feed = aio.create_feed(feed=new_feed,group_key=AIO_FEED_GROUP)\n\n return feed\n\ndef publish_rssi(data,metadata):\n name = node_db[data['from']]\n feed = get_feed(name,\"rssi\") # use a function here because we will create the feed if it doesn't exist already.\n #print(feed.key + \" \\t\\t: \" + str(data['rssi']))\n aio.send_data(feed.key, data['rssi'],metadata)\n\ndef publish_snr(data,metadata):\n name = node_db[data['from']]\n feed = get_feed(name,\"snr\") # use a function here because we will create the feed if it doesn't exist already.\n #print(feed.key + \" \\t\\t: \" + str(data['snr']))\n aio.send_data(feed.key, data['snr'],metadata)\n\ndef publish_voltage(data,metadata):\n name = node_db[data['from']]\n feed = get_feed(name,\"voltage\") # use a function here because we will create the feed if it doesn't exist already. print(feed.key + \" \\t: \" + str(data['payload'].get('voltage',\"none\")))\n #print(feed.key + \" \\t: \" + str(data['payload']['voltage']))\n aio.send_data(feed.key, data['payload'].get('voltage',0),metadata)\n\ndef publish_packet(data):\n\n feed = aio.feeds(AIO_FEED_GROUP+\".messages\")\n if (data['from'] in node_db):\n data['fname'] = node_db[data['from']]\n if (data['to'] in node_db):\n data['tname'] = node_db[data['to']]\n # trim down the data. we really only want to see the message content\n if 'stamp' in data:\n stamp = datetime.fromtimestamp(data['timestamp'],my_timezone).strftime('%Y-%m-%d %H:%M:%S')\n else:\n current_time = datetime.now() # Assuming UTC time\n # Convert to the desired timezone (e.g., 'America/Los_Angeles' or your preferred timezone)\n current_time = current_time.astimezone(my_timezone)\n stamp = current_time.strftime('%Y-%m-%d %H:%M:%S')\n\n trimmed = {\n 'from' : data.get('fname',None) or data.get('from'),\n 'to' : data.get('tname',None) or data.get('to'),\n 'channel' : data['channel'],\n 'message' : data['payload'],\n 'stamp' : stamp,\n }\n aio.send_data(feed.key, json.dumps(trimmed, indent=4))\n print(trimmed)\n\ndef on_message(client, userdata, message):\n try:\n data = json.loads(str(message.payload.decode(\"utf-8\")))\n except Exception as e:\n print(e)\n return\n\n # check the topic of the message\n if data['type'] == \"text\" and LOG_MESSAGE:\n # publish all message packets to the message log\n print(data)\n try:\n publish_packet(data)\n except Exception as e:\n print(\"error in publish:\",e)\n\n # update node_db if needed\n if data['type'] == 'nodeinfo':\n # add to the node_db if we haven't seen it before\n if data['from'] not in node_db:\n node_db[data['from']] = data['payload']['shortname'] + '-' + data['payload']['longname']\n print(str(node_db))\n return # don't publish data from nodedb packets.\n\n # \"payload\":{\"altitude\":113,\"latitude_i\":2087
159687,\"longitude_i\":-1565037665\n metadata = None\n if data['type'] == 'position' and LOG_POSITION:\n metadata = {\n 'lat': data['payload']['latitude_i'] / 10000000, #40.726190,\n 'lon': data['payload']['longitude_i'] / 10000000, #-74.005334,\n 'ele': data['payload'].get('altitude',None),\n 'created_at': str(datetime.now(my_timezone)),\n }\n # add to the node_db if we know who they are already\n\n# if metadata:\n# print(metadata)\n\n if data['from'] in node_db:\n try:\n if LOG_RSSI and 'rssi' in data and data['rssi'] != 0:\n publish_rssi( data, metadata )\n if LOG_SNR and 'snr' in data and data['snr'] != 0:\n publish_snr( data, metadata )\n if LOG_VOLTAGE and 'payload' in data and 'voltage' in data['payload'] and data['payload'].get('voltage',0) != 0:\n publish_voltage( data, metadata )\n pass\n except Exception as e:\n print(\"Error sending to IO:\", str(e))\n\n\nmqttClient.on_message = on_message\n\n#def on_log(client, userdata, level, buf):\n #print(\"log: \",buf)\n\n#mqttClient.on_log=on_log\n\nwhile(True):\n if (not mqttClient.is_connected()) :\n print(\"Connecting to mqtt server\")\n mqttClient.connect(MQTT_SERVER, MQTT_PORT)\n mqttClient.loop_start()\n for channel in channel_array:\n print(\"Subscribing to %s\" % channel)\n mqttClient.subscribe(\"mesh/2/json/%s/#\" % (channel))\n time.sleep(1)\n\n time.sleep(.01)\n\n"})})]})}function f(e={}){let{wrapper:t}={...(0,o.R)(),...e.components};return t?(0,i.jsx)(t,{...e,children:(0,i.jsx)(c,{...e})}):c(e)}},67008(e,t,n){n.d(t,{R:()=>s,x:()=>r});var a=n(71763);let i={},o=a.createContext(i);function s(e){let t=a.useContext(o);return a.useMemo(function(){return"function"==typeof e?e(t):{...t,...e}},[t,e])}function r(e){let t;return t=e.disableParentContext?"function"==typeof e.components?e.components(i):e.components||i:s(e.components),a.createElement(o.Provider,{value:t},e.children)}},87622(e,t,n){n.d(t,{A:()=>s});var a=n(71763),i=n(37773),o=n(91987);function s({term:e,definition:t,abbreviation:n,routePath:r="/glossary",children:d}){let[l,m]=(0,a.useState)(!1),[c,f]=(0,a.useState)(null),[p,u]=(0,a.useState)("top"),h=(0,a.useRef)(null),g=(0,a.useRef)(null),_=(0,a.useCallback)(()=>{let e;if(!h.current||!g.current)return;let t=h.current.getBoundingClientRect(),n=g.current.getBoundingClientRect(),a=window.innerWidth,i=window.innerHeight,o=document.querySelector(".navbar"),s=o?o.getBoundingClientRect().bottom:0,r=t.top-s>=n.height+8,d=i-t.bottom>=n.height+8,l=r||!d?"top":"bottom";e="top"===l?t.top-n.height-8:t.bottom+8;let m=t.left+t.width/2-n.width/2;m=Math.max(8,Math.min(m,a-n.width-8)),u(l),f({top:Math.max(s+4,e),left:m})},[]);(0,a.useEffect)(()=>{let e;if(!l)return;let t=requestAnimationFrame(()=>{e=requestAnimationFrame(()=>{_()})}),n=()=>_(),a=()=>_();return window.addEventListener("scroll",n,!0),window.addEventListener("resize",a),()=>{cancelAnimationFrame(t),e&&cancelAnimationFrame(e),window.removeEventListener("scroll",n,!0),window.removeEventListener("resize",a)}},[l,_]);let b=(0,i.P_)("docusaurus-plugin-glossary"),y=(0,a.useMemo)(()=>{if(t&&"string"==typeof t&&t.length>0)return t;let n=(b&&b.terms||[]).find(t=>"string"==typeof t.term&&t.term.toLowerCase()===String(e).toLowerCase());return n&&n.definition?n.definition:void 0},[t,b,e]),w=(0,a.useMemo)(()=>{let t=n;if(!t){let n=(b&&b.terms||[]).find(t=>"string"==typeof t.term&&t.term.toLowerCase()===String(e).toLowerCase());t=n&&n.abbreviation}if("string"!=typeof t)return;let a=t.trim();if(a&&a.toLowerCase()!==String(e).toLowerCase())return a},[n,b,e]),T=(0,a.useMemo)(()=>r&&"string"==typeof r&&r.length>0?r:b&&b.routePath||"/glossary",[b,r]),E=d||e,O=e.toLowerCase().replace(/\s+/g,"-");return(0,o.jsxs)("span",{ref:h,className:"glossaryTermWrapper_ud7W",children:[(0,o.jsx)("a",{href:`${T}#${O}`,className:"glossaryTerm_x2nJ",onMouseEnter:()=>m(!0),onMouseLeave:()=>m(!1),onFocus:()=>m(!0),onBlur:()=>m(!1),"aria-describedby":`tooltip-${O}`,children:E}),y&&(0,o.jsxs)("span",{ref:g,id:`tooltip-${O}`,className:`tooltip_WjgA ${l?"tooltipVisible_AjfD":""} ${"top"===p?"tooltipTop_uMN4":"tooltipBottom_U9P5"} tooltipFloating_nLk7`,role:"tooltip",style:l&&c?{top:`${c.top}px`,left:`${c.left}px`}:void 0,children:[(0,o.jsx)("strong",{children:e}),w?` (${w}). `:"",y]})]})}}}]);
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.