Task Scheduler
task_scheduler.py runs as a background asyncio coroutine (run_scheduler_periodically()) within the main MeshDash process. It has two responsibilities: executing scheduled tasks and sending community heartbeats.
Execution Loop
The scheduler loop runs every 60 seconds. On each cycle it:
- Reads all tasks from
tasks.db - For each enabled task, uses croniter to determine whether the task was due since the last check
- Executes any due tasks via HTTP POST to the local MeshDash API
- Sends a community heartbeat if enabled and the interval has elapsed
Cron Evaluation
The scheduler uses the croniter library with the system's local timezone (detected via tzlocal). This means cron expressions like 0 8 * * * fire at 8 AM in the system's local time, not UTC.
The evaluation window is: did a trigger fall between the last check time and now? This is robust to scheduler restarts — if the service was down when a task was due, it will run on the next cycle after restart (within the evaluation window).
Task Types
Standard Message (taskType: message)
Payload is a plain text string. The scheduler POSTs:
POST /api/messages
{"message": "Good morning!", "destination": "!aabbccdd"}
If the actionPayload is valid JSON with a message key, it is sent as-is (allowing channel overrides). If it is plain text, it is wrapped into the above structure.
Web Sensor / Website Monitor (taskType: website_monitor)
Payload is a JSON string. When the scheduler detects url, block_id, and prefix keys in the payload, it calls the web monitor endpoint instead:
POST /monitor/website
{
"url": "https://example.com/data",
"block_id": 3,
"prefix": "Tank Level:",
"node_id": "!aabbccdd",
"channel": 0
}
The server fetches the URL, extracts the specified block, prepends the prefix, and sends the result as a Meshtastic message. Accepted taskType values: message, send, sendmessage, sendmsg, website_monitor, websitemonitor — all are treated identically.
Retry Logic
Each task action retries up to SCHEDULER_MAX_RETRIES times (default 3) on failure. Between attempts, the scheduler waits SCHEDULER_RETRY_DELAY_SECONDS (default 10 s). Retries apply to:
- HTTP timeout errors (both connect and read/write)
- HTTP 5xx server errors from the local API
- Network request errors
Non-retriable failures (HTTP 4xx) fail immediately without retrying.
Community Heartbeat
If COMMUNITY_API=true, the scheduler sends periodic heartbeats to meshdash.co.uk containing:
- Node status and statistics
- Local node GPS position (if
SEND_LOCAL_NODE_LOCATION=true) - Other visible node positions (if
SEND_OTHER_NODES_LOCATION=true) - Location offset applied if
LOCATION_OFFSET_ENABLED=true
Heartbeat frequency is controlled by HEARTBEAT_INTERVAL_MINUTES (default: 1 minute).
remote_c2_worker_enhanced()) and the community heartbeat in the task scheduler both communicate with meshdash.co.uk, but they are separate workers with different purposes. The C2 bridge handles proxy requests; the scheduler heartbeat updates the community map.Timezone Handling
The scheduler requires tzlocal to be installed for accurate local time handling. If missing, it falls back to the system's UTC offset. Install with:
pip install tzlocal
Check your system timezone with timedatectl (Linux) to verify cron expressions will fire at the expected local time.
Scheduler Configuration Tuning
The scheduler reads several config keys that affect its behaviour at runtime:
SCHEDULER_MAX_RETRIESSCHEDULER_RETRY_DELAY_SECONDSSCHEDULER_CONNECT_TIMEOUTSCHEDULER_RW_TIMEOUTHEARTBEAT_INTERVAL_MINUTESTask Status Tracking
The scheduler does not update task status back to the database — it simply fires and forgets with retry logic. If a task needs to confirm delivery, use the MeshShark view to observe ACK packets, or check the GET /api/messages/history endpoint for the DELIVERED status after the task fires.
Task Enabled Flag
Tasks with enabled: false are fetched from the database but skipped immediately during evaluation. This allows you to pause individual tasks without deleting them. The Tasks view in the dashboard shows an enabled/disabled toggle on each task row.