mirror of
https://github.com/meshtastic/python.git
synced 2026-04-20 14:58:54 -04:00
Updated docs & protobufs
This commit is contained in:
@@ -5,7 +5,7 @@
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, minimum-scale=1" />
|
||||
<meta name="generator" content="pdoc 0.10.0" />
|
||||
<title>meshtastic.node API documentation</title>
|
||||
<meta name="description" content="an API for Meshtastic devices …" />
|
||||
<meta name="description" content="Node class" />
|
||||
<link rel="preload stylesheet" as="style" href="https://cdnjs.cloudflare.com/ajax/libs/10up-sanitize.css/11.0.1/sanitize.min.css" integrity="sha256-PK9q560IAAa6WVRRh76LtCaI8pjTJ2z11v0miyNNjrs=" crossorigin>
|
||||
<link rel="preload stylesheet" as="style" href="https://cdnjs.cloudflare.com/ajax/libs/10up-sanitize.css/11.0.1/typography.min.css" integrity="sha256-7l/o7C8jubJiy74VsKTidCy1yBkRtiUGbVkYBylBqUg=" crossorigin>
|
||||
<link rel="stylesheet preload" as="style" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/10.1.1/styles/github.min.css" crossorigin>
|
||||
@@ -22,149 +22,19 @@
|
||||
<h1 class="title">Module <code>meshtastic.node</code></h1>
|
||||
</header>
|
||||
<section id="section-intro">
|
||||
<h1 id="an-api-for-meshtastic-devices">an API for Meshtastic devices</h1>
|
||||
<p>Primary class: SerialInterface
|
||||
Install with pip: "<a href="https://pypi.org/project/meshtastic/">pip3 install meshtastic</a>"
|
||||
Source code on <a href="https://github.com/meshtastic/Meshtastic-python">github</a></p>
|
||||
<p>properties of SerialInterface:</p>
|
||||
<ul>
|
||||
<li>radioConfig - Current radio configuration and device settings, if you write to this the new settings will be applied to
|
||||
the device.</li>
|
||||
<li>nodes - The database of received nodes.
|
||||
Includes always up-to-date location and username information for each
|
||||
node in the mesh.
|
||||
This is a read-only datastructure.</li>
|
||||
<li>nodesByNum - like "nodes" but keyed by nodeNum instead of nodeId</li>
|
||||
<li>myInfo - Contains read-only information about the local radio device (software version, hardware version, etc)</li>
|
||||
</ul>
|
||||
<h1 id="published-pubsub-topics">Published PubSub topics</h1>
|
||||
<p>We use a <a href="https://pypubsub.readthedocs.io/en/v4.0.3/">publish-subscribe</a> model to communicate asynchronous events.
|
||||
Available
|
||||
topics:</p>
|
||||
<ul>
|
||||
<li>meshtastic.connection.established - published once we've successfully connected to the radio and downloaded the node DB</li>
|
||||
<li>meshtastic.connection.lost - published once we've lost our link to the radio</li>
|
||||
<li>meshtastic.receive.text(packet) - delivers a received packet as a dictionary, if you only care about a particular
|
||||
type of packet, you should subscribe to the full topic name.
|
||||
If you want to see all packets, simply subscribe to "meshtastic.receive".</li>
|
||||
<li>meshtastic.receive.position(packet)</li>
|
||||
<li>meshtastic.receive.user(packet)</li>
|
||||
<li>meshtastic.receive.data.portnum(packet) (where portnum is an integer or well known PortNum enum)</li>
|
||||
<li>meshtastic.node.updated(node = NodeInfo) - published when a node in the DB changes (appears, location changed, username changed, etc…)</li>
|
||||
</ul>
|
||||
<p>We receive position, user, or data packets from the mesh.
|
||||
You probably only care about meshtastic.receive.data.
|
||||
The first argument for
|
||||
that publish will be the packet.
|
||||
Text or binary data packets (from sendData or sendText) will both arrive this way.
|
||||
If you print packet
|
||||
you'll see the fields in the dictionary.
|
||||
decoded.data.payload will contain the raw bytes that were sent.
|
||||
If the packet was sent with
|
||||
sendText, decoded.data.text will <strong>also</strong> be populated with the decoded string.
|
||||
For ASCII these two strings will be the same, but for
|
||||
unicode scripts they can be different.</p>
|
||||
<h1 id="example-usage">Example Usage</h1>
|
||||
<pre><code>import meshtastic
|
||||
from pubsub import pub
|
||||
|
||||
def onReceive(packet, interface): # called when a packet arrives
|
||||
print(f"Received: {packet}")
|
||||
|
||||
def onConnection(interface, topic=pub.AUTO_TOPIC): # called when we (re)connect to the radio
|
||||
# defaults to broadcast, specify a destination ID if you wish
|
||||
interface.sendText("hello mesh")
|
||||
|
||||
pub.subscribe(onReceive, "meshtastic.receive")
|
||||
pub.subscribe(onConnection, "meshtastic.connection.established")
|
||||
# By default will try to find a meshtastic device, otherwise provide a device path like /dev/ttyUSB0
|
||||
interface = meshtastic.SerialInterface()
|
||||
|
||||
</code></pre>
|
||||
<p>Node class</p>
|
||||
<details class="source">
|
||||
<summary>
|
||||
<span>Expand source code</span>
|
||||
</summary>
|
||||
<pre><code class="python">"""
|
||||
# an API for Meshtastic devices
|
||||
|
||||
Primary class: SerialInterface
|
||||
Install with pip: "[pip3 install meshtastic](https://pypi.org/project/meshtastic/)"
|
||||
Source code on [github](https://github.com/meshtastic/Meshtastic-python)
|
||||
|
||||
properties of SerialInterface:
|
||||
|
||||
- radioConfig - Current radio configuration and device settings, if you write to this the new settings will be applied to
|
||||
the device.
|
||||
- nodes - The database of received nodes. Includes always up-to-date location and username information for each
|
||||
node in the mesh. This is a read-only datastructure.
|
||||
- nodesByNum - like "nodes" but keyed by nodeNum instead of nodeId
|
||||
- myInfo - Contains read-only information about the local radio device (software version, hardware version, etc)
|
||||
|
||||
# Published PubSub topics
|
||||
|
||||
We use a [publish-subscribe](https://pypubsub.readthedocs.io/en/v4.0.3/) model to communicate asynchronous events. Available
|
||||
topics:
|
||||
|
||||
- meshtastic.connection.established - published once we've successfully connected to the radio and downloaded the node DB
|
||||
- meshtastic.connection.lost - published once we've lost our link to the radio
|
||||
- meshtastic.receive.text(packet) - delivers a received packet as a dictionary, if you only care about a particular
|
||||
type of packet, you should subscribe to the full topic name. If you want to see all packets, simply subscribe to "meshtastic.receive".
|
||||
- meshtastic.receive.position(packet)
|
||||
- meshtastic.receive.user(packet)
|
||||
- meshtastic.receive.data.portnum(packet) (where portnum is an integer or well known PortNum enum)
|
||||
- meshtastic.node.updated(node = NodeInfo) - published when a node in the DB changes (appears, location changed, username changed, etc...)
|
||||
|
||||
We receive position, user, or data packets from the mesh. You probably only care about meshtastic.receive.data. The first argument for
|
||||
that publish will be the packet. Text or binary data packets (from sendData or sendText) will both arrive this way. If you print packet
|
||||
you'll see the fields in the dictionary. decoded.data.payload will contain the raw bytes that were sent. If the packet was sent with
|
||||
sendText, decoded.data.text will **also** be populated with the decoded string. For ASCII these two strings will be the same, but for
|
||||
unicode scripts they can be different.
|
||||
|
||||
# Example Usage
|
||||
```
|
||||
import meshtastic
|
||||
from pubsub import pub
|
||||
|
||||
def onReceive(packet, interface): # called when a packet arrives
|
||||
print(f"Received: {packet}")
|
||||
|
||||
def onConnection(interface, topic=pub.AUTO_TOPIC): # called when we (re)connect to the radio
|
||||
# defaults to broadcast, specify a destination ID if you wish
|
||||
interface.sendText("hello mesh")
|
||||
|
||||
pub.subscribe(onReceive, "meshtastic.receive")
|
||||
pub.subscribe(onConnection, "meshtastic.connection.established")
|
||||
# By default will try to find a meshtastic device, otherwise provide a device path like /dev/ttyUSB0
|
||||
interface = meshtastic.SerialInterface()
|
||||
|
||||
```
|
||||
|
||||
<pre><code class="python">""" Node class
|
||||
"""
|
||||
|
||||
import logging
|
||||
import base64
|
||||
from typing import *
|
||||
from google.protobuf.json_format import MessageToJson
|
||||
from . import portnums_pb2, apponly_pb2, admin_pb2, channel_pb2
|
||||
from .util import stripnl, Timeout
|
||||
|
||||
|
||||
|
||||
def pskToString(psk: bytes):
|
||||
"""Given an array of PSK bytes, decode them into a human readable (but privacy protecting) string"""
|
||||
if len(psk) == 0:
|
||||
return "unencrypted"
|
||||
elif len(psk) == 1:
|
||||
b = psk[0]
|
||||
if b == 0:
|
||||
return "unencrypted"
|
||||
elif b == 1:
|
||||
return "default"
|
||||
else:
|
||||
return f"simple{b - 1}"
|
||||
else:
|
||||
return "secret"
|
||||
from .util import pskToString, stripnl, Timeout, our_exit
|
||||
|
||||
|
||||
class Node:
|
||||
@@ -184,11 +54,11 @@ class Node:
|
||||
def showChannels(self):
|
||||
"""Show human readable description of our channels"""
|
||||
print("Channels:")
|
||||
for c in self.channels:
|
||||
if c.role != channel_pb2.Channel.Role.DISABLED:
|
||||
cStr = stripnl(MessageToJson(c.settings))
|
||||
print(
|
||||
f" {channel_pb2.Channel.Role.Name(c.role)} psk={pskToString(c.settings.psk)} {cStr}")
|
||||
if self.channels:
|
||||
for c in self.channels:
|
||||
if c.role != channel_pb2.Channel.Role.DISABLED:
|
||||
cStr = stripnl(MessageToJson(c.settings))
|
||||
print(f" {channel_pb2.Channel.Role.Name(c.role)} psk={pskToString(c.settings.psk)} {cStr}")
|
||||
publicURL = self.getURL(includeAll=False)
|
||||
adminURL = self.getURL(includeAll=True)
|
||||
print(f"\nPrimary channel URL: {publicURL}")
|
||||
@@ -197,8 +67,10 @@ class Node:
|
||||
|
||||
def showInfo(self):
|
||||
"""Show human readable description of our node"""
|
||||
print(
|
||||
f"Preferences: {stripnl(MessageToJson(self.radioConfig.preferences))}\n")
|
||||
prefs = ""
|
||||
if self.radioConfig and self.radioConfig.preferences:
|
||||
prefs = stripnl(MessageToJson(self.radioConfig.preferences))
|
||||
print(f"Preferences: {prefs}\n")
|
||||
self.showChannels()
|
||||
|
||||
def requestConfig(self):
|
||||
@@ -218,7 +90,7 @@ class Node:
|
||||
def writeConfig(self):
|
||||
"""Write the current (edited) radioConfig to the device"""
|
||||
if self.radioConfig is None:
|
||||
raise Exception("No RadioConfig has been read")
|
||||
our_exit("Error: No RadioConfig has been read")
|
||||
|
||||
p = admin_pb2.AdminMessage()
|
||||
p.set_radio.CopyFrom(self.radioConfig)
|
||||
@@ -239,7 +111,7 @@ class Node:
|
||||
"""Delete the specifed channelIndex and shift other channels up"""
|
||||
ch = self.channels[channelIndex]
|
||||
if ch.role != channel_pb2.Channel.Role.SECONDARY:
|
||||
raise Exception("Only SECONDARY channels can be deleted")
|
||||
our_exit("Warning: Only SECONDARY channels can be deleted")
|
||||
|
||||
# we are careful here because if we move the "admin" channel the channelIndex we need to use
|
||||
# for sending admin channels will also change
|
||||
@@ -318,9 +190,10 @@ class Node:
|
||||
"""
|
||||
# Only keep the primary/secondary channels, assume primary is first
|
||||
channelSet = apponly_pb2.ChannelSet()
|
||||
for c in self.channels:
|
||||
if c.role == channel_pb2.Channel.Role.PRIMARY or (includeAll and c.role == channel_pb2.Channel.Role.SECONDARY):
|
||||
channelSet.settings.append(c.settings)
|
||||
if self.channels:
|
||||
for c in self.channels:
|
||||
if c.role == channel_pb2.Channel.Role.PRIMARY or (includeAll and c.role == channel_pb2.Channel.Role.SECONDARY):
|
||||
channelSet.settings.append(c.settings)
|
||||
bytes = channelSet.SerializeToString()
|
||||
s = base64.urlsafe_b64encode(bytes).decode('ascii')
|
||||
return f"https://www.meshtastic.org/d/#{s}".replace("=", "")
|
||||
@@ -328,7 +201,7 @@ class Node:
|
||||
def setURL(self, url):
|
||||
"""Set mesh network URL"""
|
||||
if self.radioConfig is None:
|
||||
raise Exception("No RadioConfig has been read")
|
||||
our_exit("Warning: No RadioConfig has been read")
|
||||
|
||||
# URLs are of the form https://www.meshtastic.org/d/#{base64_channel_set}
|
||||
# Split on '/#' to find the base64 encoded channel settings
|
||||
@@ -347,7 +220,7 @@ class Node:
|
||||
channelSet.ParseFromString(decodedURL)
|
||||
|
||||
if len(channelSet.settings) == 0:
|
||||
raise Exception("There were no settings.")
|
||||
our_exit("Warning: There were no settings.")
|
||||
|
||||
i = 0
|
||||
for chs in channelSet.settings:
|
||||
@@ -487,34 +360,6 @@ class Node:
|
||||
<section>
|
||||
</section>
|
||||
<section>
|
||||
<h2 class="section-title" id="header-functions">Functions</h2>
|
||||
<dl>
|
||||
<dt id="meshtastic.node.pskToString"><code class="name flex">
|
||||
<span>def <span class="ident">pskToString</span></span>(<span>psk: bytes)</span>
|
||||
</code></dt>
|
||||
<dd>
|
||||
<div class="desc"><p>Given an array of PSK bytes, decode them into a human readable (but privacy protecting) string</p></div>
|
||||
<details class="source">
|
||||
<summary>
|
||||
<span>Expand source code</span>
|
||||
</summary>
|
||||
<pre><code class="python">def pskToString(psk: bytes):
|
||||
"""Given an array of PSK bytes, decode them into a human readable (but privacy protecting) string"""
|
||||
if len(psk) == 0:
|
||||
return "unencrypted"
|
||||
elif len(psk) == 1:
|
||||
b = psk[0]
|
||||
if b == 0:
|
||||
return "unencrypted"
|
||||
elif b == 1:
|
||||
return "default"
|
||||
else:
|
||||
return f"simple{b - 1}"
|
||||
else:
|
||||
return "secret"</code></pre>
|
||||
</details>
|
||||
</dd>
|
||||
</dl>
|
||||
</section>
|
||||
<section>
|
||||
<h2 class="section-title" id="header-classes">Classes</h2>
|
||||
@@ -548,11 +393,11 @@ class Node:
|
||||
def showChannels(self):
|
||||
"""Show human readable description of our channels"""
|
||||
print("Channels:")
|
||||
for c in self.channels:
|
||||
if c.role != channel_pb2.Channel.Role.DISABLED:
|
||||
cStr = stripnl(MessageToJson(c.settings))
|
||||
print(
|
||||
f" {channel_pb2.Channel.Role.Name(c.role)} psk={pskToString(c.settings.psk)} {cStr}")
|
||||
if self.channels:
|
||||
for c in self.channels:
|
||||
if c.role != channel_pb2.Channel.Role.DISABLED:
|
||||
cStr = stripnl(MessageToJson(c.settings))
|
||||
print(f" {channel_pb2.Channel.Role.Name(c.role)} psk={pskToString(c.settings.psk)} {cStr}")
|
||||
publicURL = self.getURL(includeAll=False)
|
||||
adminURL = self.getURL(includeAll=True)
|
||||
print(f"\nPrimary channel URL: {publicURL}")
|
||||
@@ -561,8 +406,10 @@ class Node:
|
||||
|
||||
def showInfo(self):
|
||||
"""Show human readable description of our node"""
|
||||
print(
|
||||
f"Preferences: {stripnl(MessageToJson(self.radioConfig.preferences))}\n")
|
||||
prefs = ""
|
||||
if self.radioConfig and self.radioConfig.preferences:
|
||||
prefs = stripnl(MessageToJson(self.radioConfig.preferences))
|
||||
print(f"Preferences: {prefs}\n")
|
||||
self.showChannels()
|
||||
|
||||
def requestConfig(self):
|
||||
@@ -582,7 +429,7 @@ class Node:
|
||||
def writeConfig(self):
|
||||
"""Write the current (edited) radioConfig to the device"""
|
||||
if self.radioConfig is None:
|
||||
raise Exception("No RadioConfig has been read")
|
||||
our_exit("Error: No RadioConfig has been read")
|
||||
|
||||
p = admin_pb2.AdminMessage()
|
||||
p.set_radio.CopyFrom(self.radioConfig)
|
||||
@@ -603,7 +450,7 @@ class Node:
|
||||
"""Delete the specifed channelIndex and shift other channels up"""
|
||||
ch = self.channels[channelIndex]
|
||||
if ch.role != channel_pb2.Channel.Role.SECONDARY:
|
||||
raise Exception("Only SECONDARY channels can be deleted")
|
||||
our_exit("Warning: Only SECONDARY channels can be deleted")
|
||||
|
||||
# we are careful here because if we move the "admin" channel the channelIndex we need to use
|
||||
# for sending admin channels will also change
|
||||
@@ -682,9 +529,10 @@ class Node:
|
||||
"""
|
||||
# Only keep the primary/secondary channels, assume primary is first
|
||||
channelSet = apponly_pb2.ChannelSet()
|
||||
for c in self.channels:
|
||||
if c.role == channel_pb2.Channel.Role.PRIMARY or (includeAll and c.role == channel_pb2.Channel.Role.SECONDARY):
|
||||
channelSet.settings.append(c.settings)
|
||||
if self.channels:
|
||||
for c in self.channels:
|
||||
if c.role == channel_pb2.Channel.Role.PRIMARY or (includeAll and c.role == channel_pb2.Channel.Role.SECONDARY):
|
||||
channelSet.settings.append(c.settings)
|
||||
bytes = channelSet.SerializeToString()
|
||||
s = base64.urlsafe_b64encode(bytes).decode('ascii')
|
||||
return f"https://www.meshtastic.org/d/#{s}".replace("=", "")
|
||||
@@ -692,7 +540,7 @@ class Node:
|
||||
def setURL(self, url):
|
||||
"""Set mesh network URL"""
|
||||
if self.radioConfig is None:
|
||||
raise Exception("No RadioConfig has been read")
|
||||
our_exit("Warning: No RadioConfig has been read")
|
||||
|
||||
# URLs are of the form https://www.meshtastic.org/d/#{base64_channel_set}
|
||||
# Split on '/#' to find the base64 encoded channel settings
|
||||
@@ -711,7 +559,7 @@ class Node:
|
||||
channelSet.ParseFromString(decodedURL)
|
||||
|
||||
if len(channelSet.settings) == 0:
|
||||
raise Exception("There were no settings.")
|
||||
our_exit("Warning: There were no settings.")
|
||||
|
||||
i = 0
|
||||
for chs in channelSet.settings:
|
||||
@@ -860,7 +708,7 @@ class Node:
|
||||
"""Delete the specifed channelIndex and shift other channels up"""
|
||||
ch = self.channels[channelIndex]
|
||||
if ch.role != channel_pb2.Channel.Role.SECONDARY:
|
||||
raise Exception("Only SECONDARY channels can be deleted")
|
||||
our_exit("Warning: Only SECONDARY channels can be deleted")
|
||||
|
||||
# we are careful here because if we move the "admin" channel the channelIndex we need to use
|
||||
# for sending admin channels will also change
|
||||
@@ -947,9 +795,10 @@ class Node:
|
||||
"""
|
||||
# Only keep the primary/secondary channels, assume primary is first
|
||||
channelSet = apponly_pb2.ChannelSet()
|
||||
for c in self.channels:
|
||||
if c.role == channel_pb2.Channel.Role.PRIMARY or (includeAll and c.role == channel_pb2.Channel.Role.SECONDARY):
|
||||
channelSet.settings.append(c.settings)
|
||||
if self.channels:
|
||||
for c in self.channels:
|
||||
if c.role == channel_pb2.Channel.Role.PRIMARY or (includeAll and c.role == channel_pb2.Channel.Role.SECONDARY):
|
||||
channelSet.settings.append(c.settings)
|
||||
bytes = channelSet.SerializeToString()
|
||||
s = base64.urlsafe_b64encode(bytes).decode('ascii')
|
||||
return f"https://www.meshtastic.org/d/#{s}".replace("=", "")</code></pre>
|
||||
@@ -1050,7 +899,7 @@ class Node:
|
||||
<pre><code class="python">def setURL(self, url):
|
||||
"""Set mesh network URL"""
|
||||
if self.radioConfig is None:
|
||||
raise Exception("No RadioConfig has been read")
|
||||
our_exit("Warning: No RadioConfig has been read")
|
||||
|
||||
# URLs are of the form https://www.meshtastic.org/d/#{base64_channel_set}
|
||||
# Split on '/#' to find the base64 encoded channel settings
|
||||
@@ -1069,7 +918,7 @@ class Node:
|
||||
channelSet.ParseFromString(decodedURL)
|
||||
|
||||
if len(channelSet.settings) == 0:
|
||||
raise Exception("There were no settings.")
|
||||
our_exit("Warning: There were no settings.")
|
||||
|
||||
i = 0
|
||||
for chs in channelSet.settings:
|
||||
@@ -1094,11 +943,11 @@ class Node:
|
||||
<pre><code class="python">def showChannels(self):
|
||||
"""Show human readable description of our channels"""
|
||||
print("Channels:")
|
||||
for c in self.channels:
|
||||
if c.role != channel_pb2.Channel.Role.DISABLED:
|
||||
cStr = stripnl(MessageToJson(c.settings))
|
||||
print(
|
||||
f" {channel_pb2.Channel.Role.Name(c.role)} psk={pskToString(c.settings.psk)} {cStr}")
|
||||
if self.channels:
|
||||
for c in self.channels:
|
||||
if c.role != channel_pb2.Channel.Role.DISABLED:
|
||||
cStr = stripnl(MessageToJson(c.settings))
|
||||
print(f" {channel_pb2.Channel.Role.Name(c.role)} psk={pskToString(c.settings.psk)} {cStr}")
|
||||
publicURL = self.getURL(includeAll=False)
|
||||
adminURL = self.getURL(includeAll=True)
|
||||
print(f"\nPrimary channel URL: {publicURL}")
|
||||
@@ -1117,8 +966,10 @@ class Node:
|
||||
</summary>
|
||||
<pre><code class="python">def showInfo(self):
|
||||
"""Show human readable description of our node"""
|
||||
print(
|
||||
f"Preferences: {stripnl(MessageToJson(self.radioConfig.preferences))}\n")
|
||||
prefs = ""
|
||||
if self.radioConfig and self.radioConfig.preferences:
|
||||
prefs = stripnl(MessageToJson(self.radioConfig.preferences))
|
||||
print(f"Preferences: {prefs}\n")
|
||||
self.showChannels()</code></pre>
|
||||
</details>
|
||||
</dd>
|
||||
@@ -1167,7 +1018,7 @@ class Node:
|
||||
<pre><code class="python">def writeConfig(self):
|
||||
"""Write the current (edited) radioConfig to the device"""
|
||||
if self.radioConfig is None:
|
||||
raise Exception("No RadioConfig has been read")
|
||||
our_exit("Error: No RadioConfig has been read")
|
||||
|
||||
p = admin_pb2.AdminMessage()
|
||||
p.set_radio.CopyFrom(self.radioConfig)
|
||||
@@ -1184,11 +1035,7 @@ class Node:
|
||||
<nav id="sidebar">
|
||||
<h1>Index</h1>
|
||||
<div class="toc">
|
||||
<ul>
|
||||
<li><a href="#an-api-for-meshtastic-devices">an API for Meshtastic devices</a></li>
|
||||
<li><a href="#published-pubsub-topics">Published PubSub topics</a></li>
|
||||
<li><a href="#example-usage">Example Usage</a></li>
|
||||
</ul>
|
||||
<ul></ul>
|
||||
</div>
|
||||
<ul id="index">
|
||||
<li><h3>Super-module</h3>
|
||||
@@ -1196,11 +1043,6 @@ class Node:
|
||||
<li><code><a title="meshtastic" href="index.html">meshtastic</a></code></li>
|
||||
</ul>
|
||||
</li>
|
||||
<li><h3><a href="#header-functions">Functions</a></h3>
|
||||
<ul class="">
|
||||
<li><code><a title="meshtastic.node.pskToString" href="#meshtastic.node.pskToString">pskToString</a></code></li>
|
||||
</ul>
|
||||
</li>
|
||||
<li><h3><a href="#header-classes">Classes</a></h3>
|
||||
<ul>
|
||||
<li>
|
||||
|
||||
Reference in New Issue
Block a user