adafruit-io
Adafruit IO для Meshtastic
Adafruit IO можно использовать для построения графиков телеметрии и сообщений из сети Meshtastic через json/mqtt. Следующий пример скрипта будет прослушивать пакеты узлов и публиковать напряжение, rssi, snr и сообщения в отдельные фиды на Adafruit IO. Если фид не существует, он будет создан. Однако имейте в виду, что бесплатный аккаунт Adafruit ограничен 10 фидами. После того как ваши фиды начнут заполняться данными, вы можете создать панели для отображения графиков и индикаторов с данными.
:::info
Чтобы использовать этот скрипт, вам нужен аккаунт Adafruit IO, настроенный брокер MQTT и узел mesh, который публикует данные в брокер MQTT.
:::
Вам потребуется изменить образец ini-файла с вашими учетными данными Adafruit IO и MQTT
Репозиторий кода находится здесь [https://bitbucket.org/tavdog/mesh_aio_logger/src/main/]
[GENERAL]
PRINT_CONFIG = false
; https://pynative.com/list-all-timezones-in-python/
TIMEZONE = US/Hawaii
CHANNEL_LIST = LongFast,Private
[MQTT]
SERVER = mqtt.server.net
PORT = 1883
USERNAME = a
PASSWORD = a
[AIO]
USER = a
KEY = a
;leave FEED_GROUP as Default is you don't want a separate group.
FEED_GROUP = Default
[LOG]
VOLTAGE = true
MESSAGE = true
POSITION = false
SNR = false
RSSI = false
# Persistent mqtt client, watch for voltage, telemetry, message packets and publish them to io
# Import Adafruit IO REST client.
from Adafruit_IO import Client, Feed, Data, RequestError
from datetime import datetime
config = configparser.ConfigParser()
# Read the configuration file
config.read('config.ini')
if config.getboolean('GENERAL','PRINT_CONFIG'):
# Iterate through sections and options to print all config values
for section in config.sections():
print(f"[{section}]")
for key, value in config.items(section):
print(f"{key} = {value}")
print() # Add an empty line between sections for better readability
# set your timezone here
TIMEZONE = config['GENERAL']['TIMEZONE']
CHANNEL_LIST = config['GENERAL']['CHANNEL_LIST']
MQTT_SERVER = config['MQTT']['SERVER']
MQTT_PORT = int(config['MQTT']['PORT'])
MQTT_USERNAME = config['MQTT']['USERNAME']
MQTT_PASSWORD = config['MQTT']['PASSWORD']
AIO_USER = config['AIO']['USER']
AIO_KEY = config['AIO']['KEY']
AIO_FEED_GROUP = config['AIO']['FEED_GROUP'] # leave as Default is you don't want a separate group.
LOG_SNR = config.getboolean('LOG','SNR')
LOG_VOLTAGE = config.getboolean('LOG','VOLTAGE')
LOG_RSSI = config.getboolean('LOG','RSSI')
LOG_MESSAGE = config.getboolean('LOG','MESSAGE')
LOG_POSITION = config.getboolean('LOG','POSITION')
###### END SETTINGS ######
my_timezone = pytz.timezone(TIMEZONE)
channel_array = CHANNEL_LIST.split(',')
print("\n")
mqttClient = mqtt.Client("mesh_client_rssi")
mqttClient.username_pw_set(MQTT_USERNAME, MQTT_PASSWORD)
aio = Client(AIO_USER,AIO_KEY)
# bootstrap your node_db here if you want
#node_db = dict()
node_db = { 947782216: 'G1 Kula',
1839130823: 'HR-Haleakala Repeater',
1969840052: 'MR8-Magnet Rak 8dbi',
}
# get feed object or create it if it doesn't exist yet.
def get_feed(full_name,kind):
name = full_name.split('-')[0]
#print("getting feed: " + name)
if "\\x00" in name or "\\u0000" in name or "\x00" in name: # json don't like emojis
name = full_name.split('-')[1].replace(' ','-').replace('.','-')
try:
feed = aio.feeds(f"{AIO_FEED_GROUP}.{name.lower()}-{kind}")
except:
print("creating feed:" + f"{AIO_FEED_GROUP}.{name.lower()}-{kind}")
# Create Feed
new_feed = Feed(name=f"{name}_{kind}")
feed = aio.create_feed(feed=new_feed,group_key=AIO_FEED_GROUP)
return feed
def publish_rssi(data,metadata):
name = node_db[data['from']]
feed = get_feed(name,"rssi") # use a function here because we will create the feed if it doesn't exist already.
#print(feed.key + " \t\t: " + str(data['rssi']))
aio.send_data(feed.key, data['rssi'],metadata)
def publish_snr(data,metadata):
name = node_db[data['from']]
feed = get_feed(name,"snr") # use a function here because we will create the feed if it doesn't exist already.
#print(feed.key + " \t\t: " + str(data['snr']))
aio.send_data(feed.key, data['snr'],metadata)
def publish_voltage(data,metadata):
name = node_db[data['from']]
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")))
#print(feed.key + " \t: " + str(data['payload']['voltage']))
aio.send_data(feed.key, data['payload'].get('voltage',0),metadata)
def publish_packet(data):
feed = aio.feeds(AIO_FEED_GROUP+".messages")
if (data['from'] in node_db):
data['fname'] = node_db[data['from']]
if (data['to'] in node_db):
data['tname'] = node_db[data['to']]
# trim down the data. we really only want to see the message content
if 'stamp' in data:
stamp = datetime.fromtimestamp(data['timestamp'],my_timezone).strftime('%Y-%m-%d %H:%M:%S')
else:
current_time = datetime.now() # Assuming UTC time
# Convert to the desired timezone (e.g., 'America/Los_Angeles' or your preferred timezone)
current_time = current_time.astimezone(my_timezone)
stamp = current_time.strftime('%Y-%m-%d %H:%M:%S')
trimmed = {
'from' : data.get('fname',None) or data.get('from'),
'to' : data.get('tname',None) or data.get('to'),
'channel' : data['channel'],
'message' : data['payload'],
'stamp' : stamp,
}
aio.send_data(feed.key, json.dumps(trimmed, indent=4))
print(trimmed)
def on_message(client, userdata, message):
try:
data = json.loads(str(message.payload.decode("utf-8")))
except Exception as e:
print(e)
return
# check the topic of the message
if data['type'] == "text" and LOG_MESSAGE:
# publish all message packets to the message log
print(data)
try:
publish_packet(data)
except Exception as e:
print("error in publish:",e)
# update node_db if needed
if data['type'] == 'nodeinfo':
# add to the node_db if we haven't seen it before
if data['from'] not in node_db:
node_db[data['from']] = data['payload']['shortname'] + '-' + data['payload']['longname']
print(str(node_db))
return # don't publish data from nodedb packets.
# "payload":{"altitude":113,"latitude_i":208759687,"longitude_i":-1565037665
metadata = None
if data['type'] == 'position' and LOG_POSITION:
metadata = {
'lat': data['payload']['latitude_i'] / 10000000, #40.726190,
'lon': data['payload']['longitude_i'] / 10000000, #-74.005334,
'ele': data['payload'].get('altitude',None),
'created_at': str(datetime.now(my_timezone)),
}
# add to the node_db if we know who they are already
# if metadata:
# print(metadata)
if data['from'] in node_db:
try:
if LOG_RSSI and 'rssi' in data and data['rssi'] != 0:
publish_rssi( data, metadata )
if LOG_SNR and 'snr' in data and data['snr'] != 0:
publish_snr( data, metadata )
if LOG_VOLTAGE and 'payload' in data and 'voltage' in data['payload'] and data['payload'].get('voltage',0) != 0:
publish_voltage( data, metadata )
pass
except Exception as e:
print("Error sending to IO:", str(e))
mqttClient.on_message = on_message
#def on_log(client, userdata, level, buf):
#print("log: ",buf)
#mqttClient.on_log=on_log
while(True):
if (not mqttClient.is_connected()) :
print("Connecting to mqtt server")
mqttClient.connect(MQTT_SERVER, MQTT_PORT)
mqttClient.loop_start()
for channel in channel_array:
print("Subscribing to %s" % channel)
mqttClient.subscribe("mesh/2/json/%s/#" % (channel))
time.sleep(1)
time.sleep(.01)