Merge pull request #247 from SpudGunMan/copilot/enhancement-basic-scheduler

Add scheduler support for news, RSS, marine weather, system info, tide, and solar
refactored some other logic around scheduler and also the update and installer
This commit is contained in:
Kelly
2025-10-28 19:44:54 -07:00
committed by GitHub
7 changed files with 209 additions and 11 deletions
+3 -1
View File
@@ -309,7 +309,9 @@ 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.
# value can also be 'joke' (min/interval) or 'weather' (time/day) or 'link' (hour/interval) for special auto messages
# value can also be 'joke' (min/interval), 'weather' (time/day), 'link' (hour/interval) for special auto messages
# or 'news' (hour/interval), 'readrss' (hour/interval), 'mwx' (time/day), 'sysinfo' (hour/interval),
# 'tide' (time/day), 'solar' (time/day) for automated information broadcasts
# 'custom' for module/scheduler.py custom schedule examples
value =
# interval to use when time is not set (e.g. every 2 days)
+52
View File
@@ -14,8 +14,24 @@ 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.
Available functions you can import and use:
- tell_joke() - Returns a random joke
- welcome_message - A welcome message string
- handle_wxc(message_from_id, deviceID, cmd, days=None) - Weather information
- handleNews(message_from_id, deviceID, message, isDM) - News reader
- get_rss_feed(msg) - RSS feed reader
- handle_mwx(message_from_id, deviceID, cmd) - Marine weather
- sysinfo(message, message_from_id, deviceID, isDM) - System information
- handle_tide(message_from_id, deviceID, channel_number) - Tide information
- handle_sun(message_from_id, deviceID, channel_number) - Sun information
- MOTD - Message of the day string
"""
try:
# Import additional functions for scheduling (optional, depending on your needs)
from mesh_bot import handleNews, sysinfo, handle_mwx, handle_tide, handle_sun
from modules.rss import get_rss_feed
# Example task functions, modify as needed the channel and interface parameters default to schedulerChannel and schedulerInterface
def send_joke(channel, interface):
## uses system.send_message to send the result of tell_joke()
@@ -49,6 +65,30 @@ def setup_custom_schedules(send_message, tell_joke, welcome_message, handle_wxc,
## uses system.send_message to send message of the day string which can be updated in runtime
send_message(MOTD, channel, 0, interface)
def send_news(channel, interface):
## uses system.send_message to send the result of handleNews()
send_message(handleNews(0, interface, 'readnews', False), channel, 0, interface)
def send_rss(channel, interface):
## uses system.send_message to send the result of get_rss_feed()
send_message(get_rss_feed(''), channel, 0, interface)
def send_marine_weather(channel, interface):
## uses system.send_message to send the result of handle_mwx()
send_message(handle_mwx(0, interface, 'mwx'), channel, 0, interface)
def send_sysinfo(channel, interface):
## uses system.send_message to send the result of sysinfo()
send_message(sysinfo('', 0, interface, False), channel, 0, interface)
def send_tide(channel, interface):
## uses system.send_message to send the result of handle_tide()
send_message(handle_tide(0, interface, channel), channel, 0, interface)
def send_sun(channel, interface):
## uses system.send_message to send the result of handle_sun()
send_message(handle_sun(0, interface, channel), channel, 0, interface)
### Send a joke every 2 minutes
#schedule.every(2).minutes.do(lambda: send_joke(schedulerChannel, schedulerInterface))
### Send a good morning message every day at 9 AM
@@ -67,6 +107,18 @@ def setup_custom_schedules(send_message, tell_joke, welcome_message, handle_wxc,
#schedule.every().day.at("13:00").do(lambda: send_motd(schedulerChannel, schedulerInterface))
### Send bbslink message every 2 days at 10 AM
#schedule.every(2).days.at("10:00").do(lambda: send_message("bbslink MeshBot looking for peers", schedulerChannel, 0, schedulerInterface))
### Send news updates every 6 hours
#schedule.every(6).hours.do(lambda: send_news(schedulerChannel, schedulerInterface))
### Send RSS feed every day at 9 AM
#schedule.every().day.at("09:00").do(lambda: send_rss(schedulerChannel, schedulerInterface))
### Send marine weather every day at 6 AM
#schedule.every().day.at("06:00").do(lambda: send_marine_weather(schedulerChannel, schedulerInterface))
### Send system information every day at 12 PM
#schedule.every().day.at("12:00").do(lambda: send_sysinfo(schedulerChannel, schedulerInterface))
### Send tide information every day at 5 AM
#schedule.every().day.at("05:00").do(lambda: send_tide(schedulerChannel, schedulerInterface))
### Send sun information every day at 6 AM
#schedule.every().day.at("06:00").do(lambda: send_sun(schedulerChannel, schedulerInterface))
except Exception as e:
logger.error(f"Error setting up custom schedules: {e}")
+40
View File
@@ -0,0 +1,40 @@
#!/bin/bash
# Set ownership and permissions for Meshing Around application
# Check if run as root
if [ "$EUID" -ne 0 ]; then
echo "Please run as root"
exit 1
fi
# Use first argument as user, or default to meshbot
TARGET_USER="${1:-meshbot}"
# Check if user exists
if ! id "$TARGET_USER" &>/dev/null; then
echo "User '$TARGET_USER' does not exist."
read -p "Would you like to use the current user ($(logname)) instead? [y/N]: " yn
if [[ "$yn" =~ ^[Yy]$ ]]; then
TARGET_USER="$(logname)"
echo "Using current user: $TARGET_USER"
if ! id "$TARGET_USER" &>/dev/null; then
echo "Current user '$TARGET_USER' does not exist or cannot be determined."
exit 1
fi
else
echo "Exiting."
exit 1
fi
fi
echo "Setting ownership to $TARGET_USER:$TARGET_USER"
chown -R "$TARGET_USER:$TARGET_USER" "/opt/meshing-around/-around"
chown -R "$TARGET_USER:$TARGET_USER" "/opt/meshing-around/-around/logs"
chown -R "$TARGET_USER:$TARGET_USER" "/opt/meshing-around/-around/data"
chown "$TARGET_USER:$TARGET_USER" "/opt/meshing-around/-around/config.ini"
chmod 640 "/opt/meshing-around/-around/config.ini"
chmod 750 "/opt/meshing-around/-around/logs"
chmod 750 "/opt/meshing-around/-around/data"
echo "Permissions and ownership have been set."
+5
View File
@@ -287,6 +287,11 @@ echo "Added user $whoami to dialout, tty, and bluetooth groups"
sudo chown -R "$whoami:$whoami" "$program_path/logs"
sudo chown -R "$whoami:$whoami" "$program_path/data"
sudo chown "$whoami:$whoami" "$program_path/config.ini"
sudo chmod 640 "$program_path/config.ini"
echo "Permissions set for meshbot on config.ini"
sudo chmod 750 "$program_path/logs"
sudo chmod 750 "$program_path/data"
echo "Permissions set for meshbot on logs and data directories"
# check and see if some sort of NTP is running
+50 -2
View File
@@ -541,17 +541,23 @@ Configure in `[scheduler]` section of `config.ini`.
See modules/custom_scheduler.py for advanced scheduling using python
**Purpose:**
`scheduler.py` provides automated scheduling for Mesh Bot, allowing you to send messages, jokes, weather updates, and custom actions at specific times or intervals.
`scheduler.py` provides automated scheduling for Mesh Bot, allowing you to send messages, jokes, weather updates, news, RSS feeds, marine weather, system info, tide info, sun info, and custom actions at specific times or intervals.
**How to Use:**
- The scheduler is configured via your bots settings or commands, specifying what to send, when, and on which channel/interface.
- Supports daily, weekly, hourly, and minutely schedules, as well as special jobs like jokes and weather.
- Supports daily, weekly, hourly, and minutely schedules, as well as special jobs like jokes, weather, news, RSS feeds, marine weather, system info, tide info, and sun info.
- For advanced automation, you can define your own schedules in `etc/custom_scheduler.py` (copied to `modules/custom_scheduler.py` at install).
**Features:**
- **Basic Scheduling:** Send messages on a set schedule (e.g., every day at 09:00, every Monday at noon, every hour, etc.).
- **Joke Scheduler:** Automatically send jokes every x min
- **Weather Scheduler:** Send weather updates at time of day, daily.
- **News Scheduler:** Send news updates at specified intervals.
- **RSS Scheduler:** Send RSS feed updates at specified intervals.
- **Marine Weather Scheduler:** Send marine weather forecasts at time of day, daily.
- **System Info Scheduler:** Send system information at specified intervals.
- **Tide Scheduler:** Send tide information at time of day, daily.
- **Sun Scheduler:** Send sun information (sunrise/sunset) at time of day, daily.
- **Custom Scheduler:** run your own scheduled jobs by editing `custom_scheduler.py`.
- **Logging:** All scheduling actions are logged for debugging and monitoring.
@@ -618,6 +624,48 @@ You can schedule messages or actions using the following options in your configu
- Time: `08:00`
- → Sends a weather update daily at 8:00a.
#### **news**
- Schedules the bot to send news updates at the specified interval (in hours).
- **Example:**
- Option: `news`
- Interval: `6`
- → Sends news updates every 6 hours.
#### **readrss**
- Schedules the bot to send RSS feed updates at the specified interval (in hours).
- **Example:**
- Option: `readrss`
- Interval: `4`
- → Sends RSS feed updates every 4 hours.
#### **mwx**
- Schedules the bot to send marine weather updates at the specified time of day, daily.
- **Example:**
- Option: `mwx`
- Time: `06:00`
- → Sends marine weather updates daily at 6:00a.
#### **sysinfo**
- Schedules the bot to send system information at the specified interval (in hours).
- **Example:**
- Option: `sysinfo`
- Interval: `12`
- → Sends system information every 12 hours.
#### **tide**
- Schedules the bot to send tide information at the specified time of day, daily.
- **Example:**
- Option: `tide`
- Time: `05:00`
- → Sends tide information daily at 5:00a.
#### **solar**
- Schedules the bot to send sun information (sunrise/sunset) at the specified time of day, daily.
- **Example:**
- Option: `solar`
- Time: `06:00`
- → Sends sun information daily at 6:00a.
---
### Days of the Week
+41 -7
View File
@@ -1,11 +1,9 @@
# modules/scheduler.py 2025 meshing-around
# Scheduler setup for Mesh Bot
# Scheduler module for mesh_bot
import asyncio
import schedule
from datetime import datetime
from functools import partial
from modules.log import logger
from modules.settings import MOTD
from modules.system import send_message
async def run_scheduler_loop(interval=1):
@@ -80,7 +78,11 @@ def setup_scheduler(
handle_riverFlow,
handle_tide,
handle_satpass,
handleNews,
handle_mwx,
sysinfo,
)
from modules.rss import get_rss_feed
except ImportError as e:
logger.warning(f"Some mesh_bot schedule features are unavailable by option disable in config.ini: {e} comment out the use of these methods in your custom_scheduler.py")
@@ -103,8 +105,10 @@ def setup_scheduler(
if any(option in schedulerValue for option in basicOptions):
if schedulerValue == 'day':
if schedulerTime:
# Specific time each day
schedule.every().day.at(schedulerTime).do(send_sched_msg)
else:
# Every N days
schedule.every(schedulerIntervalInt).days.do(send_sched_msg)
elif 'mon' in schedulerValue and schedulerTime:
schedule.every().monday.at(schedulerTime).do(send_sched_msg)
@@ -127,19 +131,49 @@ def setup_scheduler(
logger.debug(f"System: Starting the basic scheduler to send '{scheduler_message}' on schedule '{schedulerValue}' every {schedulerIntervalInt} interval at time '{schedulerTime}' on Device:{schedulerInterface} Channel:{schedulerChannel}")
elif 'joke' in schedulerValue:
schedule.every(schedulerIntervalInt).minutes.do(
partial(send_message, tell_joke(), schedulerChannel, 0, schedulerInterface)
lambda: send_message(tell_joke(), schedulerChannel, 0, schedulerInterface)
)
logger.debug(f"System: Starting the joke scheduler to send a joke every {schedulerIntervalInt} minutes on Device:{schedulerInterface} Channel:{schedulerChannel}")
elif 'link' in schedulerValue:
schedule.every(schedulerIntervalInt).hours.do(
send_message("bbslink MeshBot looking for peers", schedulerChannel, 0, schedulerInterface)
lambda: send_message("bbslink MeshBot looking for peers", schedulerChannel, 0, schedulerInterface)
)
logger.debug(f"System: Starting the link scheduler to send link messages every {schedulerIntervalInt} hours on Device:{schedulerInterface} Channel:{schedulerChannel}")
elif 'weather' in schedulerValue:
schedule.every().day.at(schedulerTime).do(
partial(send_message, handle_wxc(0, schedulerInterface, 'wx', days=1), schedulerChannel, 0, schedulerInterface)
lambda: send_message(handle_wxc(0, schedulerInterface, 'wx', days=1), schedulerChannel, 0, schedulerInterface)
)
logger.debug(f"System: Starting the weather scheduler to send weather updates every {schedulerIntervalInt} hours on Device:{schedulerInterface} Channel:{schedulerChannel}")
elif 'news' in schedulerValue:
schedule.every(schedulerIntervalInt).hours.do(
lambda: send_message(handleNews(0, schedulerInterface, 'readnews', False), schedulerChannel, 0, schedulerInterface)
)
logger.debug(f"System: Starting the news scheduler to send news updates every {schedulerIntervalInt} hours on Device:{schedulerInterface} Channel:{schedulerChannel}")
elif 'readrss' in schedulerValue:
schedule.every(schedulerIntervalInt).hours.do(
lambda: send_message(get_rss_feed(''), schedulerChannel, 0, schedulerInterface)
)
logger.debug(f"System: Starting the RSS scheduler to send RSS feeds every {schedulerIntervalInt} hours on Device:{schedulerInterface} Channel:{schedulerChannel}")
elif 'mwx' in schedulerValue:
schedule.every().day.at(schedulerTime).do(
lambda: send_message(handle_mwx(0, schedulerInterface, 'mwx'), schedulerChannel, 0, schedulerInterface)
)
logger.debug(f"System: Starting the marine weather scheduler to send marine weather updates at {schedulerTime} on Device:{schedulerInterface} Channel:{schedulerChannel}")
elif 'sysinfo' in schedulerValue:
schedule.every(schedulerIntervalInt).hours.do(
lambda: send_message(sysinfo('', 0, schedulerInterface, False), schedulerChannel, 0, schedulerInterface)
)
logger.debug(f"System: Starting the sysinfo scheduler to send system information every {schedulerIntervalInt} hours on Device:{schedulerInterface} Channel:{schedulerChannel}")
elif 'tide' in schedulerValue:
schedule.every().day.at(schedulerTime).do(
lambda: send_message(handle_tide(0, schedulerInterface, schedulerChannel), schedulerChannel, 0, schedulerInterface)
)
logger.debug(f"System: Starting the tide scheduler to send tide information at {schedulerTime} on Device:{schedulerInterface} Channel:{schedulerChannel}")
elif 'solar' in schedulerValue:
schedule.every().day.at(schedulerTime).do(
lambda: send_message(handle_sun(0, schedulerInterface, schedulerChannel), schedulerChannel, 0, schedulerInterface)
)
logger.debug(f"System: Starting the scheduler to send solar information at {schedulerTime} on Device:{schedulerInterface} Channel:{schedulerChannel}")
elif 'custom' in schedulerValue:
try:
from modules.custom_scheduler import setup_custom_schedules # type: ignore
@@ -151,7 +185,7 @@ def setup_scheduler(
lambda: logger.info("System: Scheduled Broadcast Enabled Reminder")
)
except Exception as e:
logger.warning("Custom scheduler file not found or failed to import. cp etc/custom_scheduler.py modules/custom_scheduler.py")
logger.warning("Custom scheduler file not found or failed to import. cp etc/custom_scheduler.template modules/custom_scheduler.py")
except Exception as e:
logger.error(f"System: Scheduler Error {e}")
return True
+18 -1
View File
@@ -63,6 +63,24 @@ if [[ -f "modules/custom_scheduler.py" ]]; then
echo "Including custom_scheduler.py in backup..."
cp modules/custom_scheduler.py data/
fi
# Check config.ini ownership and permissions
if [[ -f "config.ini" ]]; then
owner=$(stat -f "%Su" config.ini)
perms=$(stat -f "%A" config.ini)
echo "config.ini is owned by: $owner"
echo "config.ini permissions: $perms"
if [[ "$owner" == "root" ]]; then
echo "Warning: config.ini is owned by root check out the etc/set-permissions.sh script"
fi
if [[ $(stat -f "%Lp" config.ini) =~ .*[7,6,2]$ ]]; then
echo "Warning: config.ini is world-writable or world-readable! check out the etc/set-permissions.sh script"
fi
echo "Including config.ini in backup..."
cp config.ini data/config.backup
fi
#create the tar.gz backup
tar -czf "$backup_file" "$path2backup"
if [ $? -ne 0 ]; then
echo "Error: Backup failed."
@@ -70,7 +88,6 @@ else
echo "Backup of ${path2backup} completed: ${backup_file}"
fi
# Build a config_new.ini file merging user config with new defaults
echo "Merging configuration files..."
python3 script/configMerge.py > ini_merge_log.txt 2>&1