Compare commits

...

37 Commits

Author SHA1 Message Date
Kelly
0fb351ef4d fix end
thanks for issue https://github.com/SpudGunMan/meshing-around/issues/291
2026-02-26 20:22:53 -08:00
Kelly
2f6abade80 ARRLFixz
https://www.arrl.org/withdrawn-questions
2026-02-26 20:21:31 -08:00
Kelly
5247f8d9d3 Update locationdata.py 2026-02-26 15:20:18 -08:00
Kelly
b36059183c Update locationdata.py 2026-02-26 15:18:49 -08:00
Kelly
f737e401a5 Update locationdata.py 2026-02-26 15:15:10 -08:00
Kelly
98b5f4fb7f Update scheduler.py 2026-02-26 14:43:55 -08:00
Kelly
17fa03ff9d Merge branch 'main' of https://github.com/SpudGunMan/meshing-around 2026-02-26 14:41:32 -08:00
Kelly
40aaa7202c Update scheduler.py 2026-02-26 14:41:02 -08:00
Kelly
5088397856 Merge pull request #292 from SpudGunMan/dependabot/github_actions/docker/build-push-action-10e90e3645eae34f1e60eeb005ba3a3d33f178e8
Bump docker/build-push-action from 8c1e8f8e5bf845ba3773a14f3967965548a2341e to 10e90e3645eae34f1e60eeb005ba3a3d33f178e8
2026-02-26 14:20:07 -08:00
Kelly
db1c31579c Update install.sh 2026-02-26 14:18:38 -08:00
dependabot[bot]
dcf1b8f3cc Bump docker/build-push-action
Bumps [docker/build-push-action](https://github.com/docker/build-push-action) from 8c1e8f8e5bf845ba3773a14f3967965548a2341e to 10e90e3645eae34f1e60eeb005ba3a3d33f178e8.
- [Release notes](https://github.com/docker/build-push-action/releases)
- [Commits](8c1e8f8e5b...10e90e3645)

---
updated-dependencies:
- dependency-name: docker/build-push-action
  dependency-version: 10e90e3645eae34f1e60eeb005ba3a3d33f178e8
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-02-16 10:09:14 +00:00
Kelly
2a7000a2e6 Update hamtest.py 2026-02-14 16:49:59 -08:00
Kelly
aa0aaed0b5 Merge pull request #288 from SpudGunMan/dependabot/github_actions/docker/build-push-action-8c1e8f8e5bf845ba3773a14f3967965548a2341e 2026-02-04 08:27:55 -08:00
Kelly
9db4dc8ab9 Merge pull request #289 from SpudGunMan/dependabot/github_actions/docker/login-action-3227f5311cb93ffd14d13e65d8cc400d30f4dd8a 2026-02-04 08:27:37 -08:00
dependabot[bot]
85e8f41dca Bump docker/login-action
Bumps [docker/login-action](https://github.com/docker/login-action) from 0567fa5ae8c9a197cb207537dc5cbb43ca3d803f to 3227f5311cb93ffd14d13e65d8cc400d30f4dd8a.
- [Release notes](https://github.com/docker/login-action/releases)
- [Commits](0567fa5ae8...3227f5311c)

---
updated-dependencies:
- dependency-name: docker/login-action
  dependency-version: 3227f5311cb93ffd14d13e65d8cc400d30f4dd8a
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-02-02 10:27:43 +00:00
dependabot[bot]
ddb123b759 Bump docker/build-push-action
Bumps [docker/build-push-action](https://github.com/docker/build-push-action) from 64c9b141502b80dbbd71e008a0130ad330f480f8 to 8c1e8f8e5bf845ba3773a14f3967965548a2341e.
- [Release notes](https://github.com/docker/build-push-action/releases)
- [Commits](64c9b14150...8c1e8f8e5b)

---
updated-dependencies:
- dependency-name: docker/build-push-action
  dependency-version: 8c1e8f8e5bf845ba3773a14f3967965548a2341e
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-02-02 10:27:30 +00:00
dependabot[bot]
10afde663e Bump docker/login-action (#286) 2026-01-19 09:09:51 -08:00
dependabot[bot]
c931d13e6e Bump docker/login-action
Bumps [docker/login-action](https://github.com/docker/login-action) from 6862ffc5ab2cdb4405cf318a62a6f4c066e2298b to 916386b00027d425839f8da46d302dab33f5875b.
- [Release notes](https://github.com/docker/login-action/releases)
- [Commits](6862ffc5ab...916386b000)

---
updated-dependencies:
- dependency-name: docker/login-action
  dependency-version: 916386b00027d425839f8da46d302dab33f5875b
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-01-16 16:46:29 -08:00
dependabot[bot]
ba6075b616 Bump docker/build-push-action
Bumps [docker/build-push-action](https://github.com/docker/build-push-action) from 9e436ba9f2d7bcd1d038c8e55d039d37896ddc5d to 64c9b141502b80dbbd71e008a0130ad330f480f8.
- [Release notes](https://github.com/docker/build-push-action/releases)
- [Commits](9e436ba9f2...64c9b14150)

---
updated-dependencies:
- dependency-name: docker/build-push-action
  dependency-version: 64c9b141502b80dbbd71e008a0130ad330f480f8
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-01-16 16:46:09 -08:00
Jacob Morris
68c065825b Adds data persistence loop and shared method to handle all persistence operations 2026-01-16 16:45:46 -08:00
Kelly
213f121807 Update battleship.py 2026-01-13 17:37:26 -08:00
SpudGunMan
530d78482a Update locationdata.py 2026-01-02 16:00:17 -08:00
SpudGunMan
09515b9bc0 Update tictactoe.py 2025-12-27 19:40:05 -08:00
SpudGunMan
9b8c9d80c8 Update hamtest.py 2025-12-27 19:40:02 -08:00
SpudGunMan
8ee838f5c6 Update hangman.py 2025-12-27 19:39:34 -08:00
SpudGunMan
757d6d30b8 fix 2025-12-27 19:38:06 -08:00
SpudGunMan
1ee785d388 fix 2025-12-27 19:32:58 -08:00
SpudGunMan
c3284f0a0f fix Turn Counter 2025-12-27 16:21:17 -08:00
SpudGunMan
bdcc479360 enhance
fix turn count at 0
2025-12-27 15:39:27 -08:00
SpudGunMan
b1444b24e4 Update mmind.py 2025-12-27 15:26:21 -08:00
SpudGunMan
aef67da492 alertDuration
customize the check for API alerts
2025-12-27 15:06:56 -08:00
SpudGunMan
b8b8145447 enhance
@SnyderMesh Thanks for Idea
2025-12-26 15:33:11 -08:00
dependabot[bot]
42a4842a5b Bump docker/login-action
Bumps [docker/login-action](https://github.com/docker/login-action) from 28fdb31ff34708d19615a74d67103ddc2ea9725c to 6862ffc5ab2cdb4405cf318a62a6f4c066e2298b.
- [Release notes](https://github.com/docker/login-action/releases)
- [Commits](28fdb31ff3...6862ffc5ab)

---
updated-dependencies:
- dependency-name: docker/login-action
  dependency-version: 6862ffc5ab2cdb4405cf318a62a6f4c066e2298b
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-12-22 11:35:11 -08:00
SpudGunMan
201591d469 fix dopewar replay bug
reference https://github.com/SpudGunMan/meshing-around/issues/274 thanks MJTheis

Co-Authored-By: MJTheis <232630404+mjtheis@users.noreply.github.com>
2025-12-21 18:27:26 -08:00
dependabot[bot]
4ecdc7b108 Bump docker/metadata-action (#273) 2025-12-09 09:16:24 -08:00
Kelly
3f78bf7a67 Merge pull request #272 from SpudGunMan/dependabot/github_actions/actions/checkout-6 2025-12-09 09:15:48 -08:00
dependabot[bot]
8af21b760c Bump actions/checkout from 5 to 6
Bumps [actions/checkout](https://github.com/actions/checkout) from 5 to 6.
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](https://github.com/actions/checkout/compare/v5...v6)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-version: '6'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-11-24 11:21:13 +00:00
19 changed files with 164 additions and 117 deletions

View File

@@ -25,10 +25,10 @@ jobs:
#
steps:
- name: Checkout repository
uses: actions/checkout@v5
uses: actions/checkout@v6
# Uses the `docker/login-action` action to log in to the Container registry registry using the account and password that will publish the packages. Once published, the packages are scoped to the account defined here.
- name: Log in to the Container registry
uses: docker/login-action@28fdb31ff34708d19615a74d67103ddc2ea9725c
uses: docker/login-action@3227f5311cb93ffd14d13e65d8cc400d30f4dd8a
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
@@ -36,7 +36,7 @@ jobs:
# This step uses [docker/metadata-action](https://github.com/docker/metadata-action#about) to extract tags and labels that will be applied to the specified image. The `id` "meta" allows the output of this step to be referenced in a subsequent step. The `images` value provides the base name for the tags and labels.
- name: Extract metadata (tags, labels) for Docker
id: meta
uses: docker/metadata-action@8d8c7c12f7b958582a5cb82ba16d5903cb27976a
uses: docker/metadata-action@c299e40c65443455700f0fdfc63efafe5b349051
with:
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
# This step uses the `docker/build-push-action` action to build the image, based on your repository's `Dockerfile`. If the build succeeds, it pushes the image to GitHub Packages.
@@ -44,7 +44,7 @@ jobs:
# It uses the `tags` and `labels` parameters to tag and label the image with the output from the "meta" step.
- name: Build and push Docker image
id: push
uses: docker/build-push-action@9e436ba9f2d7bcd1d038c8e55d039d37896ddc5d
uses: docker/build-push-action@10e90e3645eae34f1e60eeb005ba3a3d33f178e8
with:
context: .
push: true

View File

@@ -490,4 +490,10 @@ autoBanThreshold = 5
# Throttle value for API requests no ban_hammer
apiThrottleValue = 20
# Timeframe for offenses (in seconds)
autoBanTimeframe = 3600
autoBanTimeframe = 3600
[dataPersistence]
# Enable or disable the data persistence loop service
enabled = True
# Interval in seconds for the persistence loop (how often to save data)
interval = 300

View File

@@ -959,18 +959,6 @@
"To relay messages between satellites"
]
},
{
"id": "E2A13",
"correct": 1,
"refs": "",
"question": "Which of the following techniques is used by digital satellites to relay messages?",
"answers": [
"Digipeating",
"Store-and-forward",
"Multisatellite relaying",
"Node hopping"
]
},
{
"id": "E2B01",
"correct": 0,
@@ -2495,18 +2483,6 @@
"Utilizing a Class D final amplifier"
]
},
{
"id": "E4D05",
"correct": 0,
"refs": "",
"question": "What transmitter frequencies would create an intermodulation-product signal in a receiver tuned to 146.70 MHz when a nearby station transmits on 146.52 MHz?",
"answers": [
"146.34 MHz and 146.61 MHz",
"146.88 MHz and 146.34 MHz",
"146.10 MHz and 147.30 MHz",
"146.30 MHz and 146.90 MHz"
]
},
{
"id": "E4D06",
"correct": 2,
@@ -3851,18 +3827,6 @@
"Permeability"
]
},
{
"id": "E6D07",
"correct": 3,
"refs": "",
"question": "What is the current that flows in the primary winding of a transformer when there is no load on the secondary winding?",
"answers": [
"Stabilizing current",
"Direct current",
"Excitation current",
"Magnetizing current"
]
},
{
"id": "E6D08",
"correct": 1,

View File

@@ -35,18 +35,6 @@
"12 meters"
]
},
{
"id": "G1A04",
"correct": 3,
"refs": "[97.303(h)]",
"question": "Which of the following amateur bands is restricted to communication only on specific channels, rather than frequency ranges?",
"answers": [
"11 meters",
"12 meters",
"30 meters",
"60 meters"
]
},
{
"id": "G1A05",
"correct": 0,
@@ -347,18 +335,6 @@
"Submit a rule-making proposal to the FCC describing the codes and methods of the technique"
]
},
{
"id": "G1C09",
"correct": 2,
"refs": "[97.313(i)]",
"question": "What is the maximum power limit on the 60-meter band?",
"answers": [
"1500 watts PEP",
"10 watts RMS",
"ERP of 100 watts PEP with respect to a dipole",
"ERP of 100 watts PEP with respect to an isotropic antenna"
]
},
{
"id": "G1C11",
"correct": 3,
@@ -611,18 +587,6 @@
"1500 watts"
]
},
{
"id": "G1E09",
"correct": 0,
"refs": "[97.115]",
"question": "Under what circumstances are messages that are sent via digital modes exempt from Part 97 third-party rules that apply to other modes of communication?",
"answers": [
"Under no circumstances",
"When messages are encrypted",
"When messages are not encrypted",
"When under automatic control"
]
},
{
"id": "G1E10",
"correct": 0,
@@ -4079,18 +4043,6 @@
"All these choices are correct"
]
},
{
"id": "G8C01",
"correct": 2,
"refs": "",
"question": "On what band do amateurs share channels with the unlicensed Wi-Fi service?",
"answers": [
"432 MHz",
"902 MHz",
"2.4 GHz",
"10.7 GHz"
]
},
{
"id": "G8C02",
"correct": 0,

View File

@@ -107,6 +107,18 @@ if [[ ! -w ${program_path} ]]; then
exit 1
fi
# check if we have git and curl installed
if ! command -v git &> /dev/null
then
printf "git not found, trying 'apt-get install git'\n"
sudo apt-get install git
fi
if ! command -v curl &> /dev/null
then
printf "curl not found, trying 'apt-get install curl'\n"
sudo apt-get install curl
fi
# check if we are in /opt/meshing-around
if [[ "$program_path" != "/opt/meshing-around" ]]; then
echo "----------------------------------------------"

View File

@@ -738,11 +738,6 @@ def handleDopeWars(message, nodeID, rxNode):
if p.get('userID') == nodeID:
p['last_played'] = time.time()
msg = playDopeWars(nodeID, message)
# if message starts wth 'e'xit remove player from tracker
if message.lower().startswith('e'):
dwPlayerTracker[:] = [p for p in dwPlayerTracker if p.get('userID') != nodeID]
msg = 'You have exited Dope Wars.'
return msg
def handle_gTnW(chess = False):
@@ -2277,8 +2272,11 @@ async def main():
# Create core tasks
tasks.append(asyncio.create_task(start_rx(), name="mesh_rx"))
tasks.append(asyncio.create_task(watchdog(), name="watchdog"))
# Add optional tasks
if my_settings.dataPersistence_enabled:
tasks.append(asyncio.create_task(dataPersistenceLoop(), name="data_persistence"))
if my_settings.file_monitor_enabled:
tasks.append(asyncio.create_task(handleFileWatcher(), name="file_monitor"))

View File

@@ -6,6 +6,7 @@ import random
import copy
import uuid
import time
from modules.settings import battleshipTracker
OCEAN = "~"
FIRE = "x"

View File

@@ -5,6 +5,7 @@ import random
import time
import pickle
from modules.log import logger
from modules.settings import dwPlayerTracker
# Global variables
total_days = 7 # number of days or rotations the player has to play
@@ -391,6 +392,13 @@ def endGameDw(nodeID):
return msg
if cash < starting_cash:
msg = "You lost money, better go get a real job.💸"
# remove player from all trackers and databases
dwPlayerTracker[:] = [p for p in dwPlayerTracker if p.get('userID') != nodeID]
dwCashDb[:] = [p for p in dwCashDb if p.get('userID') != nodeID]
dwInventoryDb[:] = [p for p in dwInventoryDb if p.get('userID') != nodeID]
dwLocationDb[:] = [p for p in dwLocationDb if p.get('userID') != nodeID]
dwGameDayDb[:] = [p for p in dwGameDayDb if p.get('userID') != nodeID]
return msg

View File

@@ -5,6 +5,7 @@ import random
import time
import pickle
from modules.log import logger
from modules.settings import golfTracker
# Clubs setup
driver_distances = list(range(230, 280, 5))

View File

@@ -10,6 +10,7 @@ import json
import random
import os
from modules.log import logger
from modules.settings import hamtestTracker
class HamTest:
def __init__(self):
@@ -135,8 +136,16 @@ class HamTest:
# remove the game[id] from the list
del self.game[id]
# hamtestTracker stores dicts like {"nodeID": nodeID, ...}
for i in range(len(hamtestTracker)):
try:
if hamtestTracker[i].get('nodeID') == id:
hamtestTracker.pop(i)
break
except Exception:
continue
return msg
hamtestTracker = []
hamtest = HamTest()

View File

@@ -3,6 +3,7 @@ from modules.log import logger, getPrettyTime
import os
import json
import random
from modules.settings import hangmanTracker
class Hangman:
WORDS = [

View File

@@ -211,7 +211,7 @@ def compareCodeMMind(secret_code, user_guess, nodeID):
def playGameMMind(diff, secret_code, turn_count, nodeID, message):
msg = ''
won = False
if turn_count <= 10:
if turn_count < 11:
user_guess = getGuessMMind(diff, message, nodeID)
if user_guess == "XXXX":
msg += f"Invalid guess. Please enter 4 valid colors letters.\n🔴🟢🔵🔴 is RGBR"
@@ -240,7 +240,7 @@ def playGameMMind(diff, secret_code, turn_count, nodeID, message):
# reset turn count in tracker
for i in range(len(mindTracker)):
if mindTracker[i]['nodeID'] == nodeID:
mindTracker[i]['turns'] = 0
mindTracker[i]['turns'] = 1
mindTracker[i]['secret_code'] = ''
mindTracker[i]['cmd'] = 'new'
@@ -277,6 +277,7 @@ def start_mMind(nodeID, message):
if mindTracker[i]['nodeID'] == nodeID:
mindTracker[i]['cmd'] = 'makeCode'
mindTracker[i]['diff'] = diff
mindTracker[i]['turns'] = 1
# Return color message to player
msg += chooseDifficultyMMind(message.lower()[0])
return msg

View File

@@ -4,6 +4,7 @@
import random
import time
import modules.settings as my_settings
from modules.settings import tictactoeTracker
useSynchCompression = True
if useSynchCompression:
@@ -16,9 +17,14 @@ class TicTacToe:
if getattr(my_settings, "disable_emojis_in_games", False):
self.X = "X"
self.O = "O"
self.digit_emojis = None
else:
self.X = ""
self.O = "⭕️"
# Unicode emoji digits 1⃣-9
self.digit_emojis = [
"1", "2", "3", "4", "5", "6", "7", "8", "9"
]
self.display_module = display_module
self.game = {}
self.win_lines_3d = self.generate_3d_win_lines()
@@ -73,7 +79,13 @@ class TicTacToe:
row = []
for j in range(3):
cell = b[i*3+j]
row.append(cell if cell != " " else str(i*3+j+1))
if cell != " ":
row.append(cell)
else:
if self.digit_emojis:
row.append(self.digit_emojis[i*3+j])
else:
row.append(str(i*3+j+1))
s += " | ".join(row) + "\n"
return s
return ""
@@ -147,10 +159,24 @@ class TicTacToe:
msg = self.new_game(nodeID, new_mode, g["channel"], g["deviceID"])
return msg
try:
pos = int(input_msg)
except Exception:
return f"Enter a number between 1 and {max_pos}."
# Accept emoji digits as input
pos = None
# Try to match emoji digits if enabled
if self.digit_emojis:
try:
# Remove variation selectors for matching
normalized_input = input_msg.replace("\ufe0f", "")
for idx, emoji in enumerate(self.digit_emojis[:max_pos]):
if normalized_input == emoji.replace("\ufe0f", ""):
pos = idx + 1
break
except Exception:
pass
if pos is None:
try:
pos = int(input_msg)
except Exception:
return f"Enter a number or emoji between 1 and {max_pos}."
if not self.make_move(nodeID, pos):
return f"Invalid move! Pick 1-{max_pos}:"

View File

@@ -4,6 +4,7 @@ import random
import time
import pickle
from modules.log import logger, getPrettyTime
from modules.settings import vpTracker
vpStartingCash = 20
# Define the Card class
@@ -260,6 +261,7 @@ class PlayerVP:
def getLastCmdVp(nodeID):
global vpTracker
last_cmd = ""
for i in range(len(vpTracker)):
if vpTracker[i]['nodeID'] == nodeID:
@@ -267,6 +269,7 @@ def getLastCmdVp(nodeID):
return last_cmd
def setLastCmdVp(nodeID, cmd):
global vpTracker
for i in range(len(vpTracker)):
if vpTracker[i]['nodeID'] == nodeID:
vpTracker[i]['cmd'] = cmd

View File

@@ -83,8 +83,10 @@ def getRepeaterBook(lat=0, lon=0):
elsewhereapi = "https://www.repeaterbook.com/row_repeaters/prox2_result.php?"
if grid[:2] in ['CN', 'DN', 'EN', 'FN', 'CM', 'DM', 'EM', 'FM', 'DL', 'EL', 'FL']:
repeater_url = usapi
logger.debug("Location: Fetching repeater data from RepeaterBook US API for grid " + grid)
else:
repeater_url = elsewhereapi
logger.debug("Location: Fetching repeater data from RepeaterBook International API for grid " + grid)
repeater_url += f"city={grid}&lat=&long=&distance=50&Dunit=m&band%5B%5D=4&band%5B%5D=16&freq=&call=&mode%5B%5D=1&mode%5B%5D=2&mode%5B%5D=4&mode%5B%5D=64&status_id=1&use=%25&use=OPEN&order=distance_calc%2C+state_id+ASC"
@@ -92,8 +94,28 @@ def getRepeaterBook(lat=0, lon=0):
msg = ''
user_agent = {'User-agent': 'Mozilla/5.0'}
response = requests.get(repeater_url, headers=user_agent, timeout=my_settings.urlTimeoutSeconds)
if response.status_code!=200:
# Fail early on bad HTTP status
if response.status_code != 200:
logger.error(f"Location:Error fetching repeater data from {repeater_url} with status code {response.status_code}")
return my_settings.ERROR_FETCHING_DATA
# Detect Cloudflare / bot-check pages or other anti-bot responses by looking
# for known phrases that indicate a challenge page instead of the expected HTML table.
try:
lowered = response.text.lower()
except Exception:
lowered = ''
cloudflare_signs = (
"checking your browser",
"please enable javascript",
"just a moment",
"cf-chl-bypass",
"attention required",
)
if any(sig in lowered for sig in cloudflare_signs):
logger.warning("Location: Cloudflare/bot-check detected when fetching repeater data")
return my_settings.ERROR_FETCHING_DATA
soup = bs.BeautifulSoup(response.text, 'html.parser')
table = soup.find('table', attrs={'class': 'table table-striped table-hover align-middle sortable'})
if table is not None:
@@ -115,6 +137,8 @@ def getRepeaterBook(lat=0, lon=0):
}
data.append(repeater)
else:
# No table found — could be legitimately no data or markup change.
logger.debug("Location: No repeater table found on RepeaterBook page, scraping failed or no data for region.")
msg = "No Data for your Region"
except Exception as e:
msg = "No repeaters found 😔"
@@ -419,7 +443,11 @@ def getWeatherAlertsNOAA(lat=0, lon=0, useDefaultLatLon=False):
alertxml = xml.dom.minidom.parseString(alert_data.text)
for i in alertxml.getElementsByTagName("entry"):
title = i.getElementsByTagName("title")[0].childNodes[0].nodeValue
area_desc = i.getElementsByTagName("cap:areaDesc")[0].childNodes[0].nodeValue
area_desc_nodes = i.getElementsByTagName("cap:areaDesc")
if area_desc_nodes and area_desc_nodes[0].childNodes:
area_desc = area_desc_nodes[0].childNodes[0].nodeValue
else:
area_desc = ""
# Extract NWSheadline from cap:parameter if present
nws_headline = ""

View File

@@ -5,6 +5,7 @@ import schedule
from datetime import datetime
from modules.log import logger
from modules.system import send_message
from modules.settings import MOTD, schedulerMotd, schedulerMessage, schedulerChannel, schedulerInterface, schedulerValue, schedulerTime, schedulerInterval
async def run_scheduler_loop(interval=1):
logger.debug(f"System: Scheduler loop started Tasks: {len(schedule.jobs)}, Details:{extract_schedule_fields(schedule.get_jobs())}")
@@ -24,11 +25,12 @@ async def run_scheduler_loop(interval=1):
except asyncio.CancelledError:
logger.debug("System: Scheduler loop cancelled, shutting down.")
def safe_int(val, default=0, type=""):
def safe_int(val, default=0, type=''):
try:
return int(val)
except (ValueError, TypeError):
logger.debug(f"System: Scheduler config {type} error '{val}' to int, using default {default}")
if val != '':
logger.debug(f"System: Scheduler config {type} error '{val}' to int, using default {default}")
return default
def extract_schedule_fields(jobs):

View File

@@ -323,6 +323,9 @@ try:
coastalForecastDays = config['location'].getint('coastalForecastDays', 3) # default 3 days
# location alerts
alert_duration = config['location'].getint('alertDuration', 20) # default 20 minutes
if alert_duration < 10: # the API calls need throttle time
alert_duration = 10
eAlertBroadcastEnabled = config['location'].getboolean('eAlertBroadcastEnabled', False) # old deprecated name
ipawsAlertEnabled = config['location'].getboolean('ipawsAlertEnabled', False) # default False new ^
# Keep both in sync for backward compatibility
@@ -504,6 +507,10 @@ try:
autoBanThreshold = config['messagingSettings'].getint('autoBanThreshold', 5) # default 5 offenses
autoBanTimeframe = config['messagingSettings'].getint('autoBanTimeframe', 3600) # default 1 hour in seconds
apiThrottleValue = config['messagingSettings'].getint('apiThrottleValue', 20) # default 20 requests
# data persistence settings
dataPersistence_enabled = config.getboolean('dataPersistence', 'enabled', fallback=True) # default True
dataPersistence_interval = config.getint('dataPersistence', 'interval', fallback=300) # default 300 seconds (5 minutes)
except Exception as e:
print(f"System: Error reading config file: {e}")
print("System: Check the config.ini against config.template file for missing sections or values.")

View File

@@ -1304,8 +1304,8 @@ def handleAlertBroadcast(deviceID=1):
if should_send_alert("overdue", overdueAlerts, min_interval=300): # 5 minutes interval for overdue alerts
send_message(overdueAlerts, emergency_responder_alert_channel, 0, emergency_responder_alert_interface)
# Only allow API call every 20 minutes
if not (clock.minute % 20 == 0 and clock.second <= 17):
# Only allow API call every alert_duration minutes at xx:00, xx:20, xx:40
if not (clock.minute % alert_duration == 0 and clock.second <= 17):
return False
# Collect alerts
@@ -2425,8 +2425,36 @@ async def watchdog():
load_bbsdm()
load_bbsdb()
def saveAllData():
try:
# Save BBS data if enabled
if bbs_enabled:
save_bbsdb()
save_bbsdm()
logger.debug("Persistence: BBS data saved")
# Save leaderboard data if enabled
if logMetaStats:
saveLeaderboard()
logger.debug("Persistence: Leaderboard data saved")
# Save ban list
save_bbsBanList()
logger.debug("Persistence: Ban list saved")
logger.info("Persistence: Save completed")
except Exception as e:
logger.error(f"Persistence: Save error: {e}")
async def dataPersistenceLoop():
"""Data persistence service loop for periodic data saving"""
logger.debug("Persistence: Loop started")
while True:
await asyncio.sleep(dataPersistence_interval)
saveAllData()
def exit_handler():
# Close the interface and save the BBS messages
# Close the interface and save all data
logger.debug(f"System: Closing Autoresponder")
try:
logger.debug(f"System: Closing Interface1")
@@ -2438,12 +2466,9 @@ def exit_handler():
globals()[f'interface{i}'].close()
except Exception as e:
logger.error(f"System: closing: {e}")
if bbs_enabled:
save_bbsdb()
save_bbsdm()
logger.debug(f"System: BBS Messages Saved")
if logMetaStats:
saveLeaderboard()
saveAllData()
logger.debug(f"System: Exiting")
asyncLoop.stop()
asyncLoop.close()

View File

@@ -671,8 +671,11 @@ async def main():
# Create core tasks
tasks.append(asyncio.create_task(start_rx(), name="mesh_rx"))
tasks.append(asyncio.create_task(watchdog(), name="watchdog"))
# Add optional tasks
if my_settings.dataPersistence_enabled:
tasks.append(asyncio.create_task(dataPersistenceLoop(), name="data_persistence"))
if my_settings.file_monitor_enabled:
tasks.append(asyncio.create_task(handleFileWatcher(), name="file_monitor"))