mirror of
https://github.com/SpudGunMan/meshing-around.git
synced 2026-03-28 17:32:36 +01:00
Compare commits
9 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1324f83f17 | ||
|
|
08ae8c31a0 | ||
|
|
957e803951 | ||
|
|
2de3441d67 | ||
|
|
2b420022f9 | ||
|
|
d01f143adf | ||
|
|
5f5aeeadac | ||
|
|
d57826613c | ||
|
|
af09dc0cf9 |
@@ -522,7 +522,9 @@ enabled = False # enable or disable the scheduler module
|
||||
interface = 1 # channel to send the message to
|
||||
channel = 2
|
||||
message = "MeshBot says Hello! DM for more info."
|
||||
value = # value can be min,hour,day,mon,tue,wed,thu,fri,sat,sun
|
||||
value = # value can be min,hour,day,mon,tue,wed,thu,fri,sat,sun.
|
||||
# value can also be joke (everyXmin) or weather (hour) for special scheduled messages
|
||||
# custom for module/scheduler.py custom schedule examples
|
||||
interval = # interval to use when time is not set (e.g. every 2 days)
|
||||
time = # time of day in 24:00 hour format when value is 'day' and interval is not set
|
||||
```
|
||||
|
||||
@@ -277,7 +277,9 @@ channel = 2
|
||||
message = "MeshBot says Hello! DM for more info."
|
||||
# enable overides the above and uses the motd as the message
|
||||
schedulerMotd = False
|
||||
# value can be min,hour,day,mon,tue,wed,thu,fri,sat,sun. or custom for module/scheduler.py
|
||||
# value can be min,hour,day,mon,tue,wed,thu,fri,sat,sun.
|
||||
# value can also be joke (everyXmin) or weather (hour) for special scheduled messages
|
||||
# custom for module/scheduler.py custom schedule examples
|
||||
value =
|
||||
# interval to use when time is not set (e.g. every 2 days)
|
||||
interval =
|
||||
|
||||
224
etc/meshview.ino
224
etc/meshview.ino
@@ -1,224 +0,0 @@
|
||||
// Example to receive and decode Meshtastic UDP packets
|
||||
// Make sure to install the meashtastic library and generate the .pb.h and .pb.c files from the Meshtastic .proto definitions
|
||||
// https://github.com/meshtastic/protobufs/tree/master/meshtastic
|
||||
|
||||
// Example to receive and decode Meshtastic UDP packets
|
||||
|
||||
#include <WiFi.h>
|
||||
#include <WiFiUdp.h>
|
||||
// #include <AESLib.h> // or another AES library
|
||||
|
||||
#include "pb_decode.h"
|
||||
#include "meshtastic/mesh.pb.h" // MeshPacket, Position, etc.
|
||||
#include "meshtastic/portnums.pb.h" // Port numbers enum
|
||||
#include "meshtastic/telemetry.pb.h" // Telemetry message
|
||||
|
||||
const char* ssid = "YOUR_WIFI_SSID";
|
||||
const char* password = "YOUR_WIFI_PASSWORD";
|
||||
|
||||
const char* default_key = "1PG7OiApB1nwvP+rz05pAQ=="; // Your network key here
|
||||
uint8_t aes_key[16]; // Buffer for decoded key
|
||||
|
||||
const char* MCAST_GRP = "224.0.0.69";
|
||||
const uint16_t MCAST_PORT = 4403;
|
||||
|
||||
unsigned long udpPacketCount = 0;
|
||||
|
||||
WiFiUDP udp;
|
||||
IPAddress multicastIP;
|
||||
|
||||
void setup() {
|
||||
Serial.begin(115200);
|
||||
delay(1000);
|
||||
|
||||
Serial.println("Scanning for WiFi networks...");
|
||||
int n = WiFi.scanNetworks();
|
||||
if (n == 0) {
|
||||
Serial.println("No networks found.");
|
||||
} else {
|
||||
Serial.print(n);
|
||||
Serial.println(" networks found:");
|
||||
for (int i = 0; i < n; ++i) {
|
||||
Serial.print(i + 1);
|
||||
Serial.print(": ");
|
||||
Serial.print(WiFi.SSID(i));
|
||||
Serial.print(" (RSSI ");
|
||||
Serial.print(WiFi.RSSI(i));
|
||||
Serial.print(")");
|
||||
Serial.println((WiFi.encryptionType(i) == WIFI_AUTH_OPEN) ? " [OPEN]" : " [SECURED]");
|
||||
delay(10);
|
||||
}
|
||||
}
|
||||
|
||||
Serial.println("Connecting to WiFi...");
|
||||
WiFi.mode(WIFI_STA);
|
||||
WiFi.begin(ssid, password);
|
||||
|
||||
unsigned long startAttemptTime = millis();
|
||||
const unsigned long wifiTimeout = 20000;
|
||||
|
||||
while (WiFi.status() != WL_CONNECTED && millis() - startAttemptTime < wifiTimeout) {
|
||||
delay(500);
|
||||
Serial.print(".");
|
||||
}
|
||||
|
||||
if (WiFi.status() == WL_CONNECTED) {
|
||||
Serial.println("\nWiFi connected.");
|
||||
Serial.print("IP address: ");
|
||||
Serial.println(WiFi.localIP());
|
||||
|
||||
multicastIP.fromString(MCAST_GRP);
|
||||
if (udp.beginMulticast(multicastIP, MCAST_PORT)) {
|
||||
Serial.println("UDP multicast listener started.");
|
||||
} else {
|
||||
Serial.println("Failed to start UDP multicast listener.");
|
||||
}
|
||||
} else {
|
||||
Serial.print("\nFailed to connect to WiFi. SSID: ");
|
||||
Serial.println(ssid);
|
||||
Serial.println("Check SSID, range, and password.");
|
||||
}
|
||||
}
|
||||
|
||||
void printHex(const uint8_t* buf, size_t len) {
|
||||
for (size_t i = 0; i < len; i++) {
|
||||
Serial.printf("%02X ", buf[i]);
|
||||
}
|
||||
Serial.println();
|
||||
}
|
||||
|
||||
void printAscii(const uint8_t* buf, size_t len) {
|
||||
for (size_t i = 0; i < len; i++) {
|
||||
char c = static_cast<char>(buf[i]);
|
||||
Serial.print(isprint(c) ? c : '.');
|
||||
}
|
||||
Serial.println();
|
||||
}
|
||||
|
||||
void decodeKey() {
|
||||
// Convert base64 key to raw bytes
|
||||
// You may need to add a base64 decoding function/library
|
||||
// Example: decode_base64(default_key, aes_key, sizeof(aes_key));
|
||||
}
|
||||
|
||||
void decryptPayload(const uint8_t* encrypted, size_t len, uint8_t* decrypted) {
|
||||
// Use AESLib or similar to decrypt
|
||||
// Example: aes128_dec_single(decrypted, encrypted, aes_key);
|
||||
}
|
||||
|
||||
void loop() {
|
||||
int packetSize = udp.parsePacket();
|
||||
if (!packetSize) {
|
||||
delay(50);
|
||||
return;
|
||||
}
|
||||
|
||||
udpPacketCount++;
|
||||
Serial.print("UDP packets seen: ");
|
||||
Serial.println(udpPacketCount);
|
||||
|
||||
uint8_t buffer[512];
|
||||
int len = udp.read(buffer, sizeof(buffer));
|
||||
if (len <= 0) {
|
||||
Serial.println("Failed to read UDP packet.");
|
||||
delay(50);
|
||||
return;
|
||||
}
|
||||
|
||||
// Always show raw payload
|
||||
Serial.print("Raw UDP payload (hex): ");
|
||||
printHex(buffer, len);
|
||||
Serial.print("Raw UDP payload (ASCII): ");
|
||||
printAscii(buffer, len);
|
||||
|
||||
// Decode outer MeshPacket
|
||||
meshtastic_MeshPacket pkt = meshtastic_MeshPacket_init_zero;
|
||||
pb_istream_t stream = pb_istream_from_buffer(buffer, len);
|
||||
|
||||
if (!pb_decode(&stream, meshtastic_MeshPacket_fields, &pkt)) {
|
||||
Serial.println("Failed to decode meshtastic_MeshPacket.");
|
||||
delay(50);
|
||||
return;
|
||||
}
|
||||
|
||||
// Basic MeshPacket fields
|
||||
Serial.print("id: "); Serial.println(pkt.id);
|
||||
Serial.print("rx_time: "); Serial.println(pkt.rx_time);
|
||||
Serial.print("rx_snr: "); Serial.println(pkt.rx_snr, 2);
|
||||
Serial.print("rx_rssi: "); Serial.println(pkt.rx_rssi);
|
||||
Serial.print("hop_limit: "); Serial.println(pkt.hop_limit);
|
||||
Serial.print("priority: "); Serial.println(pkt.priority);
|
||||
Serial.print("from: "); Serial.println(pkt.from);
|
||||
Serial.print("to: "); Serial.println(pkt.to);
|
||||
Serial.print("channel: "); Serial.println(pkt.channel);
|
||||
|
||||
// Only proceed if we have a decoded Data variant
|
||||
if (pkt.which_payload_variant != meshtastic_MeshPacket_decoded_tag) {
|
||||
Serial.println("Packet does not contain decoded Data (maybe encrypted or other variant).");
|
||||
delay(50);
|
||||
return;
|
||||
}
|
||||
|
||||
const meshtastic_Data& data = pkt.decoded;
|
||||
Serial.print("Portnum: "); Serial.println(data.portnum);
|
||||
Serial.print("Payload size: "); Serial.println(data.payload.size);
|
||||
|
||||
if (data.payload.size == 0) {
|
||||
Serial.println("No inner payload bytes.");
|
||||
delay(50);
|
||||
return;
|
||||
}
|
||||
|
||||
// Decode by portnum
|
||||
switch (data.portnum) {
|
||||
|
||||
case meshtastic_PortNum_TEXT_MESSAGE_APP: {
|
||||
// Current schemas do not use a separate user.pb.h. Text payload is plain bytes.
|
||||
Serial.print("Decoded text message: ");
|
||||
printAscii(data.payload.bytes, data.payload.size);
|
||||
break;
|
||||
}
|
||||
|
||||
case meshtastic_PortNum_POSITION_APP: {
|
||||
meshtastic_Position pos = meshtastic_Position_init_zero;
|
||||
pb_istream_t ps = pb_istream_from_buffer(data.payload.bytes, data.payload.size);
|
||||
if (pb_decode(&ps, meshtastic_Position_fields, &pos)) {
|
||||
Serial.print("Position lat="); Serial.print(pos.latitude_i / 1e7, 7);
|
||||
Serial.print(" lon="); Serial.print(pos.longitude_i / 1e7, 7);
|
||||
Serial.print(" alt="); Serial.println(pos.altitude);
|
||||
} else {
|
||||
Serial.println("Failed to decode Position payload.");
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case meshtastic_PortNum_TELEMETRY_APP: {
|
||||
meshtastic_Telemetry tel = meshtastic_Telemetry_init_zero;
|
||||
pb_istream_t ts = pb_istream_from_buffer(data.payload.bytes, data.payload.size);
|
||||
if (pb_decode(&ts, meshtastic_Telemetry_fields, &tel)) {
|
||||
// Print a few common fields if present
|
||||
if (tel.which_variant == meshtastic_Telemetry_device_metrics_tag) {
|
||||
const meshtastic_DeviceMetrics& m = tel.variant.device_metrics;
|
||||
Serial.print("Telemetry battery_level="); Serial.print(m.battery_level);
|
||||
Serial.print(" voltage="); Serial.print(m.voltage);
|
||||
Serial.print(" air_util_tx="); Serial.println(m.air_util_tx);
|
||||
} else {
|
||||
Serial.println("Telemetry decoded, different variant. Raw bytes:");
|
||||
printHex(data.payload.bytes, data.payload.size);
|
||||
}
|
||||
} else {
|
||||
Serial.println("Failed to decode Telemetry payload.");
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
default: {
|
||||
Serial.print("Unhandled portnum "); Serial.print((int)data.portnum);
|
||||
Serial.println(", showing payload as hex:");
|
||||
printHex(data.payload.bytes, data.payload.size);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
delay(50);
|
||||
}
|
||||
20
mesh_bot.py
20
mesh_bot.py
@@ -1444,17 +1444,13 @@ def onReceive(packet, interface):
|
||||
# extract interface details from inbound packet
|
||||
rxType = type(interface).__name__
|
||||
|
||||
# Valies assinged to the packet
|
||||
rxNode, message_from_id, snr, rssi, hop, hop_away, channel_number = 0, 0, 0, 0, 0, 0, 0
|
||||
# Values assinged to the packet
|
||||
rxNode = message_from_id = snr = rssi = hop = hop_away = channel_number = hop_start = hop_count = hop_limit = 0
|
||||
pkiStatus = (False, 'ABC')
|
||||
replyIDset = False
|
||||
emojiSeen = False
|
||||
simulator_flag = False
|
||||
isDM = False
|
||||
channel_number = 0
|
||||
hop_away = 0
|
||||
hop_start = 0
|
||||
hop_count = 0
|
||||
channel_name = "unknown"
|
||||
playingGame = False
|
||||
|
||||
@@ -1500,10 +1496,18 @@ def onReceive(packet, interface):
|
||||
elif multiple_interface and interface8_type == 'ble': rxNode = 8
|
||||
elif multiple_interface and interface9_type == 'ble': rxNode = 9
|
||||
|
||||
# check if the packet has a channel flag use it
|
||||
# check if the packet has a channel flag use it ## FIXME needs to be channel hash lookup
|
||||
if packet.get('channel'):
|
||||
channel_number = packet.get('channel')
|
||||
channel_name = "unknown"
|
||||
# get channel name from channel number from connected devices
|
||||
for device in channel_list:
|
||||
if device["interface_id"] == rxNode:
|
||||
device_channels = device['channels']
|
||||
for chan_name, info in device_channels.items():
|
||||
if info['number'] == channel_number:
|
||||
channel_name = chan_name
|
||||
break
|
||||
|
||||
# get channel hashes for the interface
|
||||
device = next((d for d in channel_list if d["interface_id"] == rxNode), None)
|
||||
if device:
|
||||
|
||||
@@ -97,7 +97,7 @@ def bbs_delete_message(messageID = 0, fromNode = 0):
|
||||
|
||||
def bbs_post_message(subject, message, fromNode, threadID=0, replytoID=0):
|
||||
# post a message to the bbsdb
|
||||
now = today.strftime('%Y-%m-%d %H:%M:%S')
|
||||
now = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
|
||||
thread = threadID
|
||||
replyto = replytoID
|
||||
# post a message to the bbsdb and assign a messageID
|
||||
|
||||
@@ -756,7 +756,7 @@ def get_nws_marine(zone, days=3):
|
||||
|
||||
marine_pz_data = marine_pz_data.text
|
||||
#validate data
|
||||
todayDate = today.strftime("%Y%m%d")
|
||||
todayDate = datetime.now().strftime("%Y%m%d")
|
||||
if marine_pz_data.startswith("Expires:"):
|
||||
expires = marine_pz_data.split(";;")[0].split(":")[1]
|
||||
expires_date = expires[:8]
|
||||
|
||||
@@ -1,15 +1,16 @@
|
||||
# modules/scheduler.py 2025 meshing-around
|
||||
# Scheduler setup for Mesh Bot
|
||||
import asyncio
|
||||
import schedule
|
||||
from modules.log import logger
|
||||
from modules.system import send_message, BroadcastScheduler
|
||||
from modules.system import send_message
|
||||
# methods available for custom scheduler messages
|
||||
from mesh_bot import tell_joke, welcome_message, MOTD, handle_wxc, handle_moon, handle_sun, handle_riverFlow, handle_tide, handle_satpass
|
||||
|
||||
async def setup_scheduler(
|
||||
schedulerMotd, MOTD, schedulerMessage, schedulerChannel, schedulerInterface,
|
||||
schedulerValue, schedulerTime, schedulerInterval, logger, BroadcastScheduler
|
||||
):
|
||||
schedulerValue, schedulerTime, schedulerInterval, logger, BroadcastScheduler):
|
||||
|
||||
# methods available for custom scheduler messages
|
||||
from mesh_bot import tell_joke, welcome_message, handle_wxc, handle_moon, handle_sun, handle_riverFlow, handle_tide, handle_satpass
|
||||
schedulerValue = schedulerValue.lower().strip()
|
||||
schedulerTime = schedulerTime.strip()
|
||||
schedulerInterval = schedulerInterval.strip()
|
||||
@@ -23,7 +24,8 @@ async def setup_scheduler(
|
||||
scheduler_message = schedulerMessage
|
||||
|
||||
# Basic Scheduler Options
|
||||
if 'custom' not in schedulerValue:
|
||||
basicOptions = ['day', 'mon', 'tue', 'wed', 'thu', 'fri', 'sat', 'sun', 'hour', 'min']
|
||||
if any(option.lower() in schedulerValue.lower() for option in basicOptions):
|
||||
# Basic scheduler job to run the schedule see examples below for custom schedules
|
||||
if schedulerValue.lower() == 'day':
|
||||
if schedulerTime != '':
|
||||
@@ -49,16 +51,28 @@ async def setup_scheduler(
|
||||
elif 'min' in schedulerValue.lower():
|
||||
schedule.every(int(schedulerInterval)).minutes.do(lambda: send_message(scheduler_message, schedulerChannel, 0, schedulerInterface))
|
||||
logger.debug(f"System: Starting the basic scheduler to send '{scheduler_message}' on schedule '{schedulerValue}' every {schedulerInterval} interval at time '{schedulerTime}' on Device:{schedulerInterface} Channel:{schedulerChannel}")
|
||||
else:
|
||||
# Default schedule if no valid configuration is provided
|
||||
elif 'joke' in schedulerValue.lower():
|
||||
# Schedule to send a joke every specified interval
|
||||
schedule.every(int(schedulerInterval)).minutes.do(lambda: send_message(tell_joke(), schedulerChannel, 0, schedulerInterface))
|
||||
logger.debug(f"System: Starting the joke scheduler to send a joke every {schedulerInterval} minutes on Device:{schedulerInterface} Channel:{schedulerChannel}")
|
||||
elif 'weather' in schedulerValue.lower():
|
||||
# Schedule to send weather updates every specified interval
|
||||
schedule.every(int(schedulerInterval)).hours.do(lambda: send_message(handle_wxc(0, schedulerInterface, 'wx'), schedulerChannel, 0, schedulerInterface))
|
||||
logger.debug(f"System: Starting the weather scheduler to send weather updates every {schedulerInterval} hours on Device:{schedulerInterface} Channel:{schedulerChannel}")
|
||||
elif 'custom' in schedulerValue.lower():
|
||||
# Custom scheduler job to run the schedule see examples below
|
||||
|
||||
# custom scheduler job to run the schedule see examples below
|
||||
logger.debug(f"System: Starting the scheduler to send reminder every Monday at noon on Device:{schedulerInterface} Channel:{schedulerChannel}")
|
||||
logger.debug(f"System: Starting the custom scheduler default to send reminder every Monday at noon on Device:{schedulerInterface} Channel:{schedulerChannel}")
|
||||
schedule.every().monday.at("12:00").do(lambda: logger.info("System: Scheduled Broadcast Enabled Reminder"))
|
||||
|
||||
# send a joke every 15 minutes
|
||||
#schedule.every(15).minutes.do(lambda: send_message(tell_joke(), schedulerChannel, 0, schedulerInterface))
|
||||
|
||||
# Place your custom schedule code below this line, helps with merges
|
||||
|
||||
# Place your custom schedule code above this line
|
||||
|
||||
# Start the Broadcast Scheduler
|
||||
await BroadcastScheduler()
|
||||
except Exception as e:
|
||||
|
||||
@@ -61,8 +61,9 @@ class SurveyModule:
|
||||
'answers': [],
|
||||
'location': location if surveyRecordLocation and location is not None else 'N/A'
|
||||
}
|
||||
msg = f"'{survey_name}'📝survey\nSend answer' or 'end'\n"
|
||||
msg = f"'{survey_name}'📝survey\n"
|
||||
msg += self.show_question(user_id)
|
||||
msg += f"\nSend answer' or 'end'"
|
||||
return msg
|
||||
except Exception as e:
|
||||
logger.error(f"Error starting survey for user {user_id}: {e}")
|
||||
@@ -96,12 +97,12 @@ class SurveyModule:
|
||||
filename = os.path.join(self.response_dir, f'{survey_name}_responses.csv')
|
||||
try:
|
||||
with open(filename, 'a', encoding='utf-8') as f:
|
||||
row = list(map(str, self.responses[user_id]['answers']))
|
||||
if surveyRecordID:
|
||||
row.insert(0, str(user_id))
|
||||
if surveyRecordLocation:
|
||||
location = self.responses[user_id].get('location')
|
||||
row.insert(1 if surveyRecordID else 0, str(location) if location is not None else "N/A")
|
||||
# Always write: timestamp, userID, position, answers...
|
||||
timestamp = datetime.datetime.now().strftime('%d%m%Y%H%M%S')
|
||||
user_id_str = str(user_id)
|
||||
location = self.responses[user_id].get('location', "N/A")
|
||||
answers = list(map(str, self.responses[user_id]['answers']))
|
||||
row = [timestamp, user_id_str, str(location)] + answers
|
||||
f.write(','.join(row) + '\n')
|
||||
logger.info(f"Survey: Responses for user {user_id} saved for survey '{survey_name}' to {filename}.")
|
||||
except Exception as e:
|
||||
@@ -125,7 +126,8 @@ class SurveyModule:
|
||||
return "Please answer with a letter (A, B, C, ...)."
|
||||
option_index = ord(answer_char) - 65
|
||||
if 0 <= option_index < len(question['options']):
|
||||
self.responses[user_id]['answers'].append(str(option_index))
|
||||
# Valid answer record letter, not index
|
||||
self.responses[user_id]['answers'].append(answer_char)
|
||||
self.responses[user_id]['current_question'] += 1
|
||||
return f"Recorded..\n" + self.show_question(user_id)
|
||||
else:
|
||||
|
||||
@@ -410,8 +410,9 @@ for i in range(1, 10):
|
||||
# add channel hash to channel_list
|
||||
for device in channel_list:
|
||||
interface_id = device["interface_id"]
|
||||
interface = globals().get(f'interface{interface_id}')
|
||||
for channel_name, channel_number in device["channels"].items():
|
||||
psk_base64 = base64.b64encode(channel.settings.psk).decode('utf-8')
|
||||
psk_base64 = "AQ==" # default PSK
|
||||
channel_hash = generate_hash(channel_name, psk_base64)
|
||||
# add hash to the channel entry in channel_list under key 'hash'
|
||||
for entry in channel_list:
|
||||
@@ -510,7 +511,6 @@ def get_name_from_number(number, type='long', nodeInt=1):
|
||||
name = str(decimal_to_hex(number)) # If name not found, use the ID as string
|
||||
return name
|
||||
|
||||
|
||||
def get_num_from_short_name(short_name, nodeInt=1):
|
||||
interface = globals()[f'interface{nodeInt}']
|
||||
# Get the node number from the short name, converting all to lowercase for comparison (good practice?)
|
||||
|
||||
17
pong_bot.py
17
pong_bot.py
@@ -216,11 +216,14 @@ def onReceive(packet, interface):
|
||||
rxType = type(interface).__name__
|
||||
|
||||
# Valies assinged to the packet
|
||||
rxNode, message_from_id, snr, rssi, hop, hop_away, channel_number = 0, 0, 0, 0, 0, 0, 0
|
||||
rxNode = message_from_id = snr = rssi = hop = hop_away = channel_number = hop_start = hop_count = hop_limit = 0
|
||||
pkiStatus = (False, 'ABC')
|
||||
replyIDset = False
|
||||
emojiSeen = False
|
||||
simulator_flag = False
|
||||
isDM = False
|
||||
channel_name = "unknown"
|
||||
playingGame = False
|
||||
|
||||
if DEBUGpacket:
|
||||
# Debug print the interface object
|
||||
@@ -265,10 +268,18 @@ def onReceive(packet, interface):
|
||||
elif multiple_interface and interface8_type == 'ble': rxNode = 8
|
||||
elif multiple_interface and interface9_type == 'ble': rxNode = 9
|
||||
|
||||
# check if the packet has a channel flag use it
|
||||
# check if the packet has a channel flag use it ## FIXME needs to be channel hash lookup
|
||||
if packet.get('channel'):
|
||||
channel_number = packet.get('channel')
|
||||
channel_name = "unknown"
|
||||
# get channel name from channel number from connected devices
|
||||
for device in channel_list:
|
||||
if device["interface_id"] == rxNode:
|
||||
device_channels = device['channels']
|
||||
for chan_name, info in device_channels.items():
|
||||
if info['number'] == channel_number:
|
||||
channel_name = chan_name
|
||||
break
|
||||
|
||||
# get channel hashes for the interface
|
||||
device = next((d for d in channel_list if d["interface_id"] == rxNode), None)
|
||||
if device:
|
||||
|
||||
Reference in New Issue
Block a user