diff --git a/config.template b/config.template index ff5c325..d34faba 100644 --- a/config.template +++ b/config.template @@ -323,6 +323,7 @@ value = # interval to use when time is not set (e.g. every 2 days) interval = # time of day in 24:00 hour format when value is 'day' and interval is not set +# Process run :00,:20,:40 try and vary the 20 minute offsets to avoid collision time = [radioMon] diff --git a/etc/custom_scheduler.template b/etc/custom_scheduler.template index f3f6876..1d1c469 100644 --- a/etc/custom_scheduler.template +++ b/etc/custom_scheduler.template @@ -15,6 +15,7 @@ def setup_custom_schedules(send_message, tell_joke, welcome_message, handle_wxc, 5. Make sure to uncomment (delete the single #) the example schedules down at the end of the file to enable them Python is sensitive to indentation so be careful when editing this file. https://thonny.org is included on pi's image and is a simple IDE to use for editing python files. + 6. System Tasks run every 20min try and avoid overlapping schedules to reduce API rapid fire issues. use like 8:05 Available functions you can import and use, be sure they are enabled modules in config.ini: - tell_joke() - Returns a random joke diff --git a/install.sh b/install.sh index 0a5149a..c4f4f78 100755 --- a/install.sh +++ b/install.sh @@ -457,8 +457,8 @@ if [[ $(echo "${embedded}" | grep -i "^n") ]]; then printf "sudo systemctl disable %s.service\n" "$service" >> install_notes.txt printf "sudo systemctl disable %s.service\n" "$service" >> install_notes.txt printf "\n older chron statment to run the report generator hourly:\n" >> install_notes.txt - printf "0 * * * * /usr/bin/python3 $program_path/etc/report_generator5.py" >> install_notes.txt - printf " to edit crontab run 'crontab -e'\n" >> install_notes.txt + #printf "0 * * * * /usr/bin/python3 $program_path/etc/report_generator5.py" >> install_notes.txt + #printf " to edit crontab run 'crontab -e'\n" >> install_notes.txt printf "\nmesh_bot_reporting.timer installed to run daily at 4:20 am\n" >> install_notes.txt printf "Check timer status: systemctl status mesh_bot_reporting.timer\n" >> install_notes.txt printf "List all timers: systemctl list-timers\n" >> install_notes.txt @@ -483,21 +483,6 @@ else # add service dependency for meshtasticd into service file #replace="s|After=network.target|After=network.target meshtasticd.service|g" - # Set up the meshing around service - sudo cp /opt/meshing-around/etc/$service.service /etc/systemd/system/$service.service - sudo systemctl daemon-reload - sudo systemctl enable $service.service - sudo systemctl start $service.service - - sudo systemctl daemon-reload - # # check if the cron job already exists - # if ! crontab -l | grep -q "$chronjob"; then - # # add the cron job to run the report_generator5.py script - # (crontab -l 2>/dev/null; echo "$chronjob") | crontab - - # printf "\nAdded cron job to run report_generator5.py\n" - # else - # printf "\nCron job already exists, skipping\n" - # fi # document the service install printf "Reference following commands:\n\n" > install_notes.txt printf "sudo systemctl status %s.service\n" "$service" >> install_notes.txt @@ -508,8 +493,8 @@ else printf "sudo systemctl stop %s.service\n" "$service" >> install_notes.txt printf "sudo systemctl disable %s.service\n" "$service" >> install_notes.txt printf "older crontab to run the report generator hourly:" >> install_notes.txt - printf "0 * * * * /usr/bin/python3 $program_path/etc/report_generator5.py" >> install_notes.txt - printf " to edit crontab run 'crontab -e'" >> install_notes.txt + #printf "0 * * * * /usr/bin/python3 $program_path/etc/report_generator5.py" >> install_notes.txt + #printf " to edit crontab run 'crontab -e'" >> install_notes.txt printf "\nmesh_bot_reporting.timer installed to run daily at 4:20 am\n" >> install_notes.txt printf "Check timer status: systemctl status mesh_bot_reporting.timer\n" >> install_notes.txt printf "List all timers: systemctl list-timers\n" >> install_notes.txt diff --git a/mesh_bot.py b/mesh_bot.py index e9eac0b..0e28652 100755 --- a/mesh_bot.py +++ b/mesh_bot.py @@ -1909,25 +1909,18 @@ def onReceive(packet, interface): # 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') - # 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: - # Find the channel name whose hash matches channel_number - for chan_name, info in device['channels'].items(): - if info['hash'] == channel_number: - print(f"Matched channel hash {info['hash']} to channel name {chan_name}") - channel_name = chan_name - break + try: + channel_name, _ = resolve_channel_name(channel_number, rxNode, interface) + except Exception as e: + channel_name = "unknown" + logger.debug(f"System: channel resolution error: {e}") + #debug channel info + # if "unknown" in str(channel_name): + # logger.debug(f"System: Received Packet on Channel:{channel_number} on Interface:{rxNode}") + # else: + # logger.debug(f"System: Received Packet on Channel:{channel_number} Name:{channel_name} on Interface:{rxNode}") + # check if the packet has a simulator flag simulator_flag = packet.get('decoded', {}).get('simulator', False) if isinstance(simulator_flag, dict): diff --git a/modules/system.py b/modules/system.py index 8bb0d2b..7e7d7ed 100644 --- a/modules/system.py +++ b/modules/system.py @@ -422,29 +422,58 @@ def build_channel_cache(force_refresh: bool = False): # lightweight call to fetch local node and its channels once node = globals()[f'interface{i}'].getNode('^local') channels = getattr(node, "channels", []) or [] + # try to use the node-provided channel/hash table if available + try: + ch_hash_table = node.get_channels_with_hash() + except Exception: + logger.warning(f"System: update meshtastic API 2.7.4 +") + ch_hash_table = {} + channel_dict = {} for channel in channels: if getattr(channel, "role", False): channel_name = getattr(channel.settings, "name", "").strip() channel_number = getattr(channel, "index", 0) - if channel_name: - channel_dict[channel_name] = {"number": channel_number} + if not channel_name: + continue + + ch_hash = None + # ch_hash_table may map by name or by index; try both defensively + if isinstance(ch_hash_table, dict): + # by name + if channel_name in ch_hash_table: + entry = ch_hash_table[channel_name] + if isinstance(entry, dict): + ch_hash = entry.get("hash") or entry.get("pskHash") or entry.get("hashValue") + elif isinstance(entry, (list, tuple)) and len(entry) >= 2: + ch_hash = entry[1] + else: + ch_hash = entry + # by index + elif channel_number in ch_hash_table: + entry = ch_hash_table[channel_number] + if isinstance(entry, dict): + ch_hash = entry.get("hash") or entry.get("pskHash") or entry.get("hashValue") + elif isinstance(entry, (list, tuple)) and len(entry) >= 2: + ch_hash = entry[1] + else: + ch_hash = entry + + # fallback to generate_hash with default PSK if no table/hash available + if ch_hash is None: + try: + ch_hash = generate_hash(channel_name, "AQ==") + except Exception: + ch_hash = 0 + + channel_dict[channel_name] = {"number": channel_number, "hash": ch_hash} if channel_dict: cache.append({"interface_id": i, "channels": channel_dict}) logger.debug(f"System: Fetched Channel List from Device{i} (cached)") except Exception as e: logger.debug(f"System: Error fetching channel list from Device{i}: {e}") - # compute and attach channel hash (PSK default) once - for device in cache: - for channel_name, info in list(device["channels"].items()): - psk_base64 = "AQ==" - try: - channel_hash = generate_hash(channel_name, psk_base64) - except Exception: - channel_hash = 0 - device["channels"][channel_name] = {"number": info.get("number", 0), "hash": channel_hash} - + # hashes are attached above using node.get_channels_with_hash() when available _channel_cache = cache return _channel_cache @@ -455,12 +484,9 @@ def refresh_channel_cache(): channel_list = build_channel_cache() #### FUN-ctions #### -def resolve_channel_name(channel_number, rxNode=1, interface_obj=None, allow_node_lookup: bool = False): +def resolve_channel_name(channel_number, rxNode=1, interface_obj=None): """ - Resolve a channel number/hash to a human name. - Prefers the cached channel_list (build_channel_cache) and only does node API lookups - if allow_node_lookup is True. - Returns (channel_name, matched_index_or_hash) + Resolve a channel number/hash to its name using cached channel list. """ try: # ensure cache exists (cheap) @@ -480,63 +506,9 @@ def resolve_channel_name(channel_number, rxNode=1, interface_obj=None, allow_nod return (chan_name, info) except Exception: continue - break - - # If caller allows, try heavier node-level lookups as a fallback - if not allow_node_lookup: - return ("unknown", channel_number) - - if interface_obj is None: - interface_obj = globals().get(f'interface{rxNode}') - # Try node-level API - node = None - if interface_obj: - if hasattr(interface_obj, "get_node") and callable(interface_obj.get_node): - node = interface_obj.get_node() - elif hasattr(interface_obj, "node"): - node = getattr(interface_obj, "node") - - channels = None - if node is not None and hasattr(node, "get_channels_with_hash"): - try: - channels = node.get_channels_with_hash() - except Exception: - channels = None - - # Fallback: generate channel list from raw payload - if not channels: - try: - from meshtastic.util import generate_channel_hash - except Exception: - generate_channel_hash = None - - channels_raw = {} - if node is not None: - if isinstance(node, dict): - channels_raw = node.get("channels", {}) or {} - else: - channels_raw = getattr(node, "channels", None) or getattr(node, "channels_payload", None) or {} - if channels_raw is None: - channels_raw = {} - - if generate_channel_hash and channels_raw: - try: - channels = generate_channel_hash(channels_raw) - except Exception: - channels = None - - # If we have a channels sequence, try to match by hash or index - if channels and isinstance(channels, (list, tuple)): - for ch in channels: - try: - if str(ch.get('hash')) == str(channel_number) or str(ch.get('index')) == str(channel_number): - return (ch.get('name', 'unknown'), ch.get('index') or ch.get('hash')) - except Exception: - continue - - except Exception: - pass - return ("unknown", channel_number) + break # stop searching other devices + except Exception as e: + logger.debug(f"System: Error resolving channel name from cache: {e}") def cleanup_memory(): diff --git a/pong_bot.py b/pong_bot.py index 1b1b0a7..0b2d918 100755 --- a/pong_bot.py +++ b/pong_bot.py @@ -279,24 +279,17 @@ def onReceive(packet, interface): # 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') - # 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 + try: + channel_name, _ = resolve_channel_name(channel_number, rxNode, interface) + except Exception as e: + channel_name = "unknown" + logger.debug(f"System: channel resolution error: {e}") - # get channel hashes for the interface - device = next((d for d in channel_list if d["interface_id"] == rxNode), None) - if device: - # Find the channel name whose hash matches channel_number - for chan_name, info in device['channels'].items(): - if info['hash'] == channel_number: - print(f"Matched channel hash {info['hash']} to channel name {chan_name}") - channel_name = chan_name - break + #debug channel info + # if "unknown" in str(channel_name): + # logger.debug(f"System: Received Packet on Channel:{channel_number} on Interface:{rxNode}") + # else: + # logger.debug(f"System: Received Packet on Channel:{channel_number} Name:{channel_name} on Interface:{rxNode}") # check if the packet has a simulator flag simulator_flag = packet.get('decoded', {}).get('simulator', False)