mirror of
https://github.com/SpudGunMan/meshing-around.git
synced 2026-03-28 17:32:36 +01:00
Compare commits
46 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
54837884a7 | ||
|
|
91501d42db | ||
|
|
e0bcc31204 | ||
|
|
1cb56aa1b7 | ||
|
|
8cf2db3b49 | ||
|
|
755fc4fac3 | ||
|
|
7c341ed0e7 | ||
|
|
c87db75f2f | ||
|
|
13875b7cf8 | ||
|
|
fd4925ee92 | ||
|
|
eccc48ff3f | ||
|
|
3a6d464398 | ||
|
|
6c7e8558b0 | ||
|
|
74a744c77e | ||
|
|
5225998c92 | ||
|
|
97a0ff3112 | ||
|
|
1250479219 | ||
|
|
f8bcc4f495 | ||
|
|
5d1608f366 | ||
|
|
a19dc93350 | ||
|
|
30d4e487c9 | ||
|
|
5bf1ade2b0 | ||
|
|
13cefc2002 | ||
|
|
640bead32c | ||
|
|
49d9b58627 | ||
|
|
ad1a8aa1ce | ||
|
|
55567815ef | ||
|
|
346fb38bbd | ||
|
|
104e70c01c | ||
|
|
2111bb46ae | ||
|
|
459dad4c32 | ||
|
|
d9febeef0f | ||
|
|
f8a94fca71 | ||
|
|
a30b3dc2d2 | ||
|
|
b45254795d | ||
|
|
4a209e0c17 | ||
|
|
1aecb42186 | ||
|
|
6513e9f177 | ||
|
|
dd14034f3c | ||
|
|
57093d09ef | ||
|
|
29a26d5d14 | ||
|
|
43e6349351 | ||
|
|
628f66e4b7 | ||
|
|
29f97c62d0 | ||
|
|
b805e6d428 | ||
|
|
6d5ded7df6 |
19
.github/dependabot.yml
vendored
19
.github/dependabot.yml
vendored
@@ -5,7 +5,24 @@ updates:
|
||||
directory: "/"
|
||||
schedule:
|
||||
interval: "weekly"
|
||||
assignees:
|
||||
- "SpudGunMan"
|
||||
labels:
|
||||
- "dependencies"
|
||||
- package-ecosystem: "pip"
|
||||
directory: "/"
|
||||
schedule:
|
||||
interval: "weekly"
|
||||
interval: "weekly"
|
||||
assignees:
|
||||
- "SpudGunMan"
|
||||
labels:
|
||||
- "dependencies"
|
||||
- package-ecosystem: "docker"
|
||||
directory: "/"
|
||||
schedule:
|
||||
interval: "weekly"
|
||||
assignees:
|
||||
- "SpudGunMan"
|
||||
labels:
|
||||
- "dependencies"
|
||||
|
||||
|
||||
61
.github/workflows/docker-image.yml
vendored
Normal file
61
.github/workflows/docker-image.yml
vendored
Normal file
@@ -0,0 +1,61 @@
|
||||
#
|
||||
name: Create and publish a Docker image on new release
|
||||
|
||||
# Configures this workflow to run every time a change is pushed to the branch called `release`.
|
||||
on:
|
||||
release:
|
||||
types: [released]
|
||||
workflow_dispatch:
|
||||
|
||||
# Defines two custom environment variables for the workflow. These are used for the Container registry domain, and a name for the Docker image that this workflow builds.
|
||||
env:
|
||||
REGISTRY: ghcr.io
|
||||
IMAGE_NAME: ${{ github.repository }}
|
||||
|
||||
# There is a single job in this workflow. It's configured to run on the latest available version of Ubuntu.
|
||||
jobs:
|
||||
build-and-push-image:
|
||||
runs-on: ubuntu-latest
|
||||
# Sets the permissions granted to the `GITHUB_TOKEN` for the actions in this job.
|
||||
permissions:
|
||||
contents: read
|
||||
packages: write
|
||||
attestations: write
|
||||
id-token: write
|
||||
#
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
# 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@65b78e6e13532edd9afa3aa52ac7964289d1a9c1
|
||||
with:
|
||||
registry: ${{ env.REGISTRY }}
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
# 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@9ec57ed1fcdbf14dcef7dfbe97b2010124a938b7
|
||||
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.
|
||||
# It uses the `context` parameter to define the build's context as the set of files located in the specified path. For more information, see [Usage](https://github.com/docker/build-push-action#usage) in the README of the `docker/build-push-action` repository.
|
||||
# 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@f2a1d5e99d037542a71f64918e516c093c6f3fc4
|
||||
with:
|
||||
context: .
|
||||
push: true
|
||||
tags: ${{ steps.meta.outputs.tags }}
|
||||
labels: ${{ steps.meta.outputs.labels }}
|
||||
|
||||
# This step generates an artifact attestation for the image, which is an unforgeable statement about where and how it was built. It increases supply chain security for people who consume the image. For more information, see [Using artifact attestations to establish provenance for builds](/actions/security-guides/using-artifact-attestations-to-establish-provenance-for-builds).
|
||||
- name: Generate artifact attestation
|
||||
uses: actions/attest-build-provenance@v2
|
||||
with:
|
||||
subject-name: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME}}
|
||||
subject-digest: ${{ steps.push.outputs.digest }}
|
||||
push-to-registry: true
|
||||
|
||||
10
.github/workflows/greetings.yml
vendored
10
.github/workflows/greetings.yml
vendored
@@ -2,17 +2,21 @@ name: Greetings
|
||||
|
||||
on:
|
||||
issues:
|
||||
types:
|
||||
- opened
|
||||
types: [opened]
|
||||
pull_request:
|
||||
types: [opened]
|
||||
|
||||
permissions:
|
||||
issues: write
|
||||
pull-requests: write
|
||||
|
||||
jobs:
|
||||
greeting:
|
||||
name: Greet first-time contributors
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/first-interaction@v3
|
||||
with:
|
||||
repo-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
issue_message: "Dependabot's first issue"
|
||||
issue_message: "Dependabot's first issue"
|
||||
pr_message: "Thank you for your pull request!"
|
||||
31
Dockerfile
31
Dockerfile
@@ -1,21 +1,32 @@
|
||||
FROM python:3.13-slim
|
||||
FROM python:3.14-slim
|
||||
ENV PYTHONUNBUFFERED=1
|
||||
|
||||
RUN apt-get update && apt-get install -y gettext tzdata locales nano && rm -rf /var/lib/apt/lists/*
|
||||
ENV PYTHONUNBUFFERED=1 \
|
||||
LANG=en_US.UTF-8 \
|
||||
LANGUAGE=en_US:en \
|
||||
LC_ALL=en_US.UTF-8 \
|
||||
TZ=America/Los_Angeles
|
||||
|
||||
# Set the locale default to en_US.UTF-8
|
||||
RUN sed -i -e 's/# en_US.UTF-8 UTF-8/en_US.UTF-8 UTF-8/' /etc/locale.gen && \
|
||||
dpkg-reconfigure --frontend=noninteractive locales && \
|
||||
update-locale LANG=en_US.UTF-8
|
||||
ENV LANG="en_US.UTF-8"
|
||||
ENV TZ="America/Los_Angeles"
|
||||
RUN apt-get update && \
|
||||
apt-get install -y gettext tzdata locales nano && \
|
||||
sed -i 's/^# *\(en_US.UTF-8 UTF-8\)/\1/' /etc/locale.gen && \
|
||||
locale-gen en_US.UTF-8 && \
|
||||
rm -rf /var/lib/apt/lists/*
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
# Install dependencies first for better caching
|
||||
COPY requirements.txt /app/
|
||||
RUN pip install --no-cache-dir -r requirements.txt
|
||||
|
||||
# Copy the rest of the application
|
||||
COPY . /app
|
||||
COPY config.template /app/config.ini
|
||||
|
||||
RUN pip install -r requirements.txt
|
||||
|
||||
RUN chmod +x /app/script/docker/entrypoint.sh
|
||||
|
||||
# Add a non-root user and switch to it
|
||||
RUN useradd -m appuser
|
||||
USER appuser
|
||||
|
||||
ENTRYPOINT ["/bin/bash", "/app/script/docker/entrypoint.sh"]
|
||||
|
||||
@@ -3,13 +3,13 @@
|
||||
Mesh Bot is a feature-rich Python bot designed to enhance your [Meshtastic](https://meshtastic.org/docs/introduction/) network experience. It provides powerful tools for network testing, messaging, games, and more—all via text-based message delivery. Whether you want to test your mesh, send messages, or play games, [mesh_bot.py](mesh_bot.py) has you covered.
|
||||
|
||||
* [Getting Started](#getting-started)
|
||||
* [Configuration](#configuration-guide)
|
||||
|
||||

|
||||
|
||||
#### TLDR
|
||||
* [install.sh](INSTALL.md)
|
||||
* [modules/README.md](modules/README.md)
|
||||
* [modules/games/README.md](modules/games/README.md)
|
||||
* [Configuration Guide](modules/README.md)
|
||||
* [Games Help](modules/games/README.md)
|
||||
|
||||
## Key Features
|
||||

|
||||
@@ -25,6 +25,8 @@ Mesh Bot is a feature-rich Python bot designed to enhance your [Meshtastic](http
|
||||
- **Hardware Testing**: The `test` command sends incrementally sized data to test radio buffer limits.
|
||||
- **Network Monitoring**: Alerts for noisy nodes, tracks node locations, and suggests optimal relay placement.
|
||||
|
||||
- **Site Survey & Location Logging**: Use the `map` command to log your current GPS location with a custom description—ideal for site surveys, asset tracking, or mapping nodes locations. Entries are saved to a CSV file for later analysis or visualization.
|
||||
|
||||
### Multi-Radio/Node Support
|
||||
- **Simultaneous Monitoring**: Observe up to nine networks at once.
|
||||
- **Flexible Messaging**: Send mail and messages between networks.
|
||||
|
||||
43
compose.yaml
Normal file
43
compose.yaml
Normal file
@@ -0,0 +1,43 @@
|
||||
# Docker Compose configuration for Meshing Around.
|
||||
# This setup includes the main Meshing Around service, with optional Ollama and Prometheus Node Exporter services.
|
||||
# Adjust device mappings, ports, and configurations as needed for your environment.
|
||||
services:
|
||||
meshing-around:
|
||||
stdin_open: true
|
||||
tty: true
|
||||
ports:
|
||||
- 8420:8420
|
||||
devices: # Optional if using meshtasticd. Pass through radio device.
|
||||
- /dev/ttyUSB0 # Replace this with your actual device!
|
||||
#- /dev/ttyAMA0 # Example
|
||||
volumes:
|
||||
- /data/meshing-around/config.ini:/app/config.ini:rw
|
||||
image: ghcr.io/SpudGunMan/meshing-around:test-all-changes
|
||||
container_name: meshing-around
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
- OLLAMA_API_URL=http://ollama:11434
|
||||
extra_hosts:
|
||||
#- "host.docker.internal:host-gateway" # Enables access to host services from within the container.
|
||||
user: "1000:1000" # run as non-root user for better security
|
||||
|
||||
meshtasticd: # Runs a virtual node. Optional, but can be used to link meshing-around directly to mqtt.
|
||||
ports:
|
||||
- 4403:4403
|
||||
restart: unless-stopped
|
||||
container_name: meshtasticd
|
||||
image: meshtastic/meshtasticd:beta
|
||||
|
||||
ollama: # Used for enabling LLM interactions.
|
||||
ports:
|
||||
- 11434:11434 # Ollama API port
|
||||
volumes:
|
||||
- /data/ollama:/root/.ollama
|
||||
container_name: ollama
|
||||
image: ollama/ollama:latest
|
||||
restart: unless-stopped
|
||||
healthcheck:
|
||||
test: ["CMD", "curl", "-f", "http://localhost:11434/api/tags"]
|
||||
interval: 30s
|
||||
timeout: 10s
|
||||
retries: 5
|
||||
@@ -287,7 +287,7 @@ 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 (everyXmin) or weather (hour) for special scheduled messages
|
||||
# value can also be joke (everyXmin) or weather (hour) or link (hour) for special auto messages
|
||||
# custom for module/scheduler.py custom schedule examples
|
||||
value =
|
||||
# interval to use when time is not set (e.g. every 2 days)
|
||||
|
||||
@@ -4,26 +4,28 @@ from modules.system import send_message
|
||||
|
||||
def setup_custom_schedules(send_message, tell_joke, welcome_message, handle_wxc, MOTD, schedulerChannel, schedulerInterface):
|
||||
# custom scheduler job to run the schedule see examples below
|
||||
logger.debug(f"System: Starting the custom scheduler default to send reminder every Monday at noon on Device:{schedulerInterface} Channel:{schedulerChannel}")
|
||||
logger.debug(f"System: Starting the custom_scheduler.py 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"))
|
||||
|
||||
|
||||
# Enhanced Examples of using the scheduler, Times here are in 24hr format
|
||||
# https://schedule.readthedocs.io/en/stable/
|
||||
|
||||
# Send a joke every 2 minutes
|
||||
#logger.debug(f"System: Custom Scheduler: Send a joke every 2 minutes on Device:{schedulerInterface} Channel:{schedulerChannel}")
|
||||
#schedule.every(2).minutes.do(lambda: send_message(tell_joke(), schedulerChannel, 0, schedulerInterface))
|
||||
|
||||
# Good Morning Every day at 09:00 using send_message function to channel 2 on device 1
|
||||
#schedule.every().day.at("09:00").do(lambda: send_message("Good Morning", 2, 0, 1))
|
||||
|
||||
# Send WX every Morning at 08:00 using handle_wxc function to channel 2 on device 1
|
||||
#logger.debug("System: Custom Scheduler: Send WX every Morning at 08:00")
|
||||
#schedule.every().day.at("08:00").do(lambda: send_message(handle_wxc(0, 1, 'wx'), 2, 0, 1))
|
||||
|
||||
# Send Weather Channel Notice Wed. Noon on channel 2, device 1
|
||||
#schedule.every().wednesday.at("12:00").do(lambda: send_message("Weather alerts available on 'Alerts' channel with default 'AQ==' key.", 2, 0, 1))
|
||||
|
||||
# Send config URL for Medium Fast Network Use every other day at 10:00 to default channel 2 on device 1
|
||||
#logger.debug("System: Custom Scheduler: Config URL for Medium Fast Network Use every other day at 10:00")
|
||||
#schedule.every(2).days.at("10:00").do(lambda: send_message("Join us on Medium Fast https://meshtastic.org/e/#CgcSAQE6AggNEg4IARAEOAFAA0gBUB5oAQ", 2, 0, 1))
|
||||
|
||||
# Send a Net Starting Now Message Every Wednesday at 19:00 using send_message function to channel 2 on device 1
|
||||
@@ -33,6 +35,7 @@ def setup_custom_schedules(send_message, tell_joke, welcome_message, handle_wxc,
|
||||
#schedule.every().day.at("12:00").do(lambda: send_message("Welcome to the group", 2, 0, 1)).day(15, 25)
|
||||
|
||||
# Send a Welcome Notice for group on the 15th and 25th of the month at 12:00
|
||||
#logger.debug(f"System: Custom Scheduler: Welcome Notice for group on the 15th and 25th of the month at 12:00 on Device:{schedulerInterface} Channel:{schedulerChannel}")
|
||||
#schedule.every().day.at("12:00").do(lambda: send_message("Welcome to the group", schedulerChannel, 0, schedulerInterface)).day(15, 25)
|
||||
|
||||
# Send a joke every 6 hours
|
||||
@@ -45,4 +48,5 @@ def setup_custom_schedules(send_message, tell_joke, welcome_message, handle_wxc,
|
||||
#schedule.every().day.at("13:00").do(lambda: send_message(MOTD, schedulerChannel, 0, schedulerInterface))
|
||||
|
||||
# Send bbslink looking for peers every other day at 10:00
|
||||
#logger.debug("System: Custom Scheduler: bbslink MeshBot looking for peers every other day at 10:00")
|
||||
#schedule.every(2).days.at("10:00").do(lambda: send_message("bbslink MeshBot looking for peers", schedulerChannel, 0, schedulerInterface))
|
||||
38
install.sh
38
install.sh
@@ -78,9 +78,9 @@ fi
|
||||
|
||||
# add user to groups for serial access
|
||||
printf "\nAdding user to dialout, bluetooth, and tty groups for serial access\n"
|
||||
sudo usermod -a -G dialout $USER
|
||||
sudo usermod -a -G tty $USER
|
||||
sudo usermod -a -G bluetooth $USER
|
||||
sudo usermod -a -G dialout "$USER"
|
||||
sudo usermod -a -G tty "$USER"
|
||||
sudo usermod -a -G bluetooth "$USER"
|
||||
|
||||
# copy service files
|
||||
cp etc/pong_bot.tmp etc/pong_bot.service
|
||||
@@ -186,10 +186,10 @@ fi
|
||||
|
||||
# set the correct path in the service file
|
||||
replace="s|/dir/|$program_path/|g"
|
||||
sed -i $replace etc/pong_bot.service
|
||||
sed -i $replace etc/mesh_bot.service
|
||||
sed -i $replace etc/mesh_bot_reporting.service
|
||||
sed -i $replace etc/mesh_bot_w3.service
|
||||
sed -i "$replace" etc/pong_bot.service
|
||||
sed -i "$replace" etc/mesh_bot.service
|
||||
sed -i "$replace" etc/mesh_bot_reporting.service
|
||||
sed -i "$replace" etc/mesh_bot_w3.service
|
||||
# set the correct user in the service file?
|
||||
|
||||
#ask if we should add a user for the bot
|
||||
@@ -209,9 +209,9 @@ else
|
||||
whoami=$(whoami)
|
||||
fi
|
||||
# set basic permissions for the bot user
|
||||
sudo usermod -a -G dialout $whoami
|
||||
sudo usermod -a -G tty $whoami
|
||||
sudo usermod -a -G bluetooth $whoami
|
||||
sudo usermod -a -G dialout "$whoami"
|
||||
sudo usermod -a -G tty "$whoami"
|
||||
sudo usermod -a -G bluetooth "$whoami"
|
||||
echo "Added user $whoami to dialout, tty, and bluetooth groups"
|
||||
|
||||
sudo chown -R "$whoami:$whoami" "$program_path/logs"
|
||||
@@ -227,15 +227,15 @@ fi
|
||||
|
||||
# set the correct user in the service file
|
||||
replace="s|User=pi|User=$whoami|g"
|
||||
sed -i $replace etc/pong_bot.service
|
||||
sed -i $replace etc/mesh_bot.service
|
||||
sed -i $replace etc/mesh_bot_reporting.service
|
||||
sed -i $replace etc/mesh_bot_w3.service
|
||||
sed -i "$replace" etc/pong_bot.service
|
||||
sed -i "$replace" etc/mesh_bot.service
|
||||
sed -i "$replace" etc/mesh_bot_reporting.service
|
||||
sed -i "$replace" etc/mesh_bot_w3.service
|
||||
replace="s|Group=pi|Group=$whoami|g"
|
||||
sed -i $replace etc/pong_bot.service
|
||||
sed -i $replace etc/mesh_bot.service
|
||||
sed -i $replace etc/mesh_bot_reporting.service
|
||||
sed -i $replace etc/mesh_bot_w3.service
|
||||
sed -i "$replace" etc/pong_bot.service
|
||||
sed -i "$replace" etc/mesh_bot.service
|
||||
sed -i "$replace" etc/mesh_bot_reporting.service
|
||||
sed -i "$replace" etc/mesh_bot_w3.service
|
||||
printf "\n service files updated\n"
|
||||
|
||||
if [[ $(echo "${bot}" | grep -i "^p") ]]; then
|
||||
@@ -355,7 +355,7 @@ else
|
||||
else
|
||||
printf "\nCron job already exists, skipping\n"
|
||||
fi
|
||||
printf "Reference following commands:\n\n" "$service" > install_notes.txt
|
||||
printf "Reference following commands:\n\n" > install_notes.txt
|
||||
printf "sudo systemctl status %s.service\n" "$service" >> install_notes.txt
|
||||
printf "sudo systemctl start %s.service\n" "$service" >> install_notes.txt
|
||||
printf "sudo systemctl restart %s.service\n\n" "$service" >> install_notes.txt
|
||||
|
||||
@@ -5,12 +5,12 @@
|
||||
cd "$(dirname "$0")"
|
||||
|
||||
|
||||
if [ ! -f "config.ini" ]; then
|
||||
if [[ ! -f "config.ini" ]]; then
|
||||
cp config.template config.ini
|
||||
fi
|
||||
|
||||
# activate the virtual environment if it exists
|
||||
if [ -d "venv" ]; then
|
||||
if [[ -d "venv" ]]; then
|
||||
source venv/bin/activate
|
||||
else
|
||||
echo "Virtual environment not found, this tool just launches the .py in venv"
|
||||
@@ -22,9 +22,9 @@ if [[ "$1" == pong* ]]; then
|
||||
python3 pong_bot.py
|
||||
elif [[ "$1" == mesh* ]]; then
|
||||
python3 mesh_bot.py
|
||||
elif [ "$1" == "html" ]; then
|
||||
elif [[ "$1" == "html" ]]; then
|
||||
python3 etc/report_generator.py
|
||||
elif [ "$1" == "html5" ]; then
|
||||
elif [[ "$1" == "html5" ]]; then
|
||||
python3 etc/report_generator5.py
|
||||
elif [[ "$1" == add* ]]; then
|
||||
python3 script/addFav.py
|
||||
|
||||
41
mesh_bot.py
41
mesh_bot.py
@@ -68,6 +68,7 @@ def auto_response(message, snr, rssi, hop, pkiStatus, message_from_id, channel_n
|
||||
"leaderboard": lambda: get_mesh_leaderboard(message, message_from_id, deviceID),
|
||||
"lemonstand": lambda: handleLemonade(message, message_from_id, deviceID),
|
||||
"lheard": lambda: handle_lheard(message, message_from_id, deviceID, isDM),
|
||||
"map": lambda: mapHandler(message_from_id, deviceID, channel_number, message, snr, rssi, hop),
|
||||
"mastermind": lambda: handleMmind(message, message_from_id, deviceID),
|
||||
"messages": lambda: handle_messages(message, deviceID, channel_number, msg_history, publicChannel, isDM),
|
||||
"moon": lambda: handle_moon(message_from_id, deviceID, channel_number),
|
||||
@@ -253,12 +254,12 @@ def handle_ping(message_from_id, deviceID, message, hop, snr, rssi, isDM, chann
|
||||
|
||||
# append SNR/RSSI or hop info
|
||||
if hop.startswith("Gateway") or hop.startswith("MQTT"):
|
||||
msg += f" [GW]"
|
||||
msg += " [GW]"
|
||||
elif hop.startswith("Direct"):
|
||||
msg += f" [RF]"
|
||||
msg += " [RF]"
|
||||
else:
|
||||
#flood
|
||||
msg += f" [F]"
|
||||
msg += " [F]"
|
||||
|
||||
if (float(snr) != 0 or float(rssi) != 0) and "Hops" not in hop:
|
||||
msg += f"\nSNR:{snr} RSSI:{rssi}"
|
||||
@@ -527,7 +528,8 @@ def handle_satpass(message_from_id, deviceID, message='', vox=False):
|
||||
userList = message.split("satpass ")[1].split(" ")[0]
|
||||
#split userList and make into satList overrided the config.ini satList
|
||||
satList = userList.split(",")
|
||||
except:
|
||||
except Exception as e:
|
||||
logger.error(f"Exception occurred: {e}")
|
||||
return "example use:🛰️satpass 25544,33591"
|
||||
|
||||
# Detailed satellite pass
|
||||
@@ -904,8 +906,8 @@ def handleGolf(message, nodeID, deviceID):
|
||||
if last_cmd == "new" and nodeID != 0:
|
||||
# create new player
|
||||
|
||||
msg = f"Welcome to 🏌️GolfSim⛳️\n"
|
||||
msg += f"Clubs: (D)river, (L)ow Iron, (M)id Iron, (H)igh Iron, (G)ap Wedge, Lob (W)edge (C)addie\n"
|
||||
msg = "Welcome to 🏌️GolfSim⛳️\n"
|
||||
msg += "Clubs: (D)river, (L)ow Iron, (M)id Iron, (H)igh Iron, (G)ap Wedge, Lob (W)edge (C)addie\n"
|
||||
|
||||
msg += playGolf(nodeID=nodeID, message=message, last_cmd=last_cmd)
|
||||
return msg
|
||||
@@ -1316,7 +1318,7 @@ def handle_lheard(message, nodeid, deviceID, isDM):
|
||||
else:
|
||||
# trim the last \n
|
||||
bot_response = bot_response[:-1]
|
||||
|
||||
|
||||
# get count of nodes heard
|
||||
bot_response += f"\n👀In Mesh: {len(seenNodes)}"
|
||||
|
||||
@@ -1327,7 +1329,7 @@ def handle_history(message, nodeid, deviceID, isDM, lheard=False):
|
||||
global cmdHistory, lheardCmdIgnoreNode, bbs_admin_list
|
||||
msg = ""
|
||||
buffer = []
|
||||
|
||||
|
||||
if "?" in message and isDM:
|
||||
return message.split("?")[0].title() + " command returns a list of commands received."
|
||||
|
||||
@@ -1367,7 +1369,7 @@ def handle_history(message, nodeid, deviceID, isDM, lheard=False):
|
||||
for j in range(len(buffer)):
|
||||
if buffer[j][0] == nodeName:
|
||||
buffer[j] = (nodeName, prettyTime)
|
||||
|
||||
|
||||
# create the message from the buffer list
|
||||
buffer.reverse() # reverse the list to show the latest first
|
||||
for i in range(0, len(buffer)):
|
||||
@@ -1411,11 +1413,11 @@ def handle_whoami(message_from_id, deviceID, hop, snr, rssi, pkiStatus):
|
||||
msg += f"I see the signal strength is {rssi} and the SNR is {snr} with hop count of {hop}"
|
||||
if pkiStatus[1] != 'ABC':
|
||||
msg += f"\nYour PKI bit is {pkiStatus[0]} pubKey: {pkiStatus[1]}"
|
||||
|
||||
|
||||
loc = get_node_location(message_from_id, deviceID)
|
||||
if loc != [latitudeValue, longitudeValue]:
|
||||
msg += f"\nYou are at: lat:{loc[0]} lon:{loc[1]}"
|
||||
|
||||
|
||||
# check the positionMetadata for nodeID and get metadata
|
||||
if positionMetadata and message_from_id in positionMetadata:
|
||||
metadata = positionMetadata[message_from_id]
|
||||
@@ -1498,20 +1500,20 @@ def onReceive(packet, interface):
|
||||
rxNodeHostName = interface.__dict__.get('ip', None)
|
||||
rxNode = next(
|
||||
(i for i in range(1, 10)
|
||||
if multiple_interface and rxHost and
|
||||
globals().get(f'hostname{i}', '').split(':', 1)[0] in rxHost and
|
||||
globals().get(f'interface{i}_type', '') == 'tcp'),None)
|
||||
if multiple_interface and rxHost and
|
||||
globals().get(f'hostname{i}', '').split(':', 1)[0] in rxHost and
|
||||
globals().get(f'interface{i}_type', '') == 'tcp'),None)
|
||||
|
||||
if rxType == 'SerialInterface':
|
||||
rxInterface = interface.__dict__.get('devPath', 'unknown')
|
||||
rxNode = next(
|
||||
(i for i in range(1, 10)
|
||||
if globals().get(f'port{i}', '') in rxInterface),None)
|
||||
|
||||
if globals().get(f'port{i}', '') in rxInterface),None)
|
||||
|
||||
if rxType == 'BLEInterface':
|
||||
rxNode = next(
|
||||
(i for i in range(1, 10)
|
||||
if globals().get(f'interface{i}_type', '') == 'ble'),0)
|
||||
if globals().get(f'interface{i}_type', '') == 'ble'),0)
|
||||
|
||||
if rxNode is None:
|
||||
# default to interface 1 ## FIXME needs better like a default interface setting or hash lookup
|
||||
@@ -1556,7 +1558,6 @@ def onReceive(packet, interface):
|
||||
|
||||
# BBS DM MAIL CHECKER
|
||||
if bbs_enabled and 'decoded' in packet:
|
||||
|
||||
msg = bbs_check_dm(message_from_id)
|
||||
if msg:
|
||||
logger.info(f"System: BBS DM Delivery: {msg[1]} For: {get_name_from_number(message_from_id, 'long', rxNode)}")
|
||||
@@ -1699,7 +1700,7 @@ def onReceive(packet, interface):
|
||||
else:
|
||||
# respond with help message on DM
|
||||
send_message(help_message, channel_number, message_from_id, rxNode)
|
||||
|
||||
|
||||
# log the message to the message log
|
||||
if log_messages_to_file:
|
||||
msgLogger.info(f"Device:{rxNode} Channel:{channel_number} | {get_name_from_number(message_from_id, 'long', rxNode)} | DM | " + message_string.replace('\n', '-nl-'))
|
||||
@@ -1756,7 +1757,7 @@ def onReceive(packet, interface):
|
||||
if log_messages_to_file:
|
||||
msgLogger.info(f"Device:{rxNode} Channel:{channel_number} | {get_name_from_number(message_from_id, 'long', rxNode)} | " + message_string.replace('\n', '-nl-'))
|
||||
|
||||
# repeat the message on the other device
|
||||
# repeat the message on the other device
|
||||
if repeater_enabled and multiple_interface:
|
||||
# wait a responseDelay to avoid message collision from lora-ack.
|
||||
time.sleep(responseDelay)
|
||||
|
||||
@@ -19,7 +19,7 @@ Updated Oct-2025 "ver 1.9.8.4"
|
||||
- [Voice Commands (VOX)](#voice-commands-vox)
|
||||
- [Ollama LLM/AI](#ollama-llmai)
|
||||
- [Wikipedia Search](#wikipedia-search)
|
||||
- [Scheduler](#scheduler)
|
||||
- [Scheduler](#-mesh-bot-scheduler-user-guide)
|
||||
- [Other Utilities](#other-utilities)
|
||||
- [Configuration](#configuration)
|
||||
- [Troubleshooting](#troubleshooting)
|
||||
@@ -37,6 +37,37 @@ See [modules/adding_more.md](adding_more.md) for developer notes.
|
||||
|
||||
## Networking
|
||||
|
||||
### ping / pinging / test / testing / ack
|
||||
|
||||
- **Usage:** `ping`, `pinging`, `test`, `testing`, `ack`, `ping @user`, `ping #tag`
|
||||
- **Description:** Sends a ping to the bot. The bot responds with signal information such as SNR (Signal-to-Noise Ratio), RSSI (Received Signal Strength Indicator), and hop count. Used for making field report etc.
|
||||
- **Targeted Ping:**
|
||||
You can direct a ping to a specific user or group by mentioning their short name or tag:
|
||||
- `ping @NODE` — Pings a Joke to specific node by its short name.
|
||||
- **Example:**
|
||||
```
|
||||
ping
|
||||
```
|
||||
Response:
|
||||
```
|
||||
SNR: 12.5, RSSI: -80, Hops: 2
|
||||
```
|
||||
```
|
||||
ping @Top of the hill
|
||||
```
|
||||
Response:
|
||||
```
|
||||
PING @Top of the hill SNR: 10.2, RSSI: -85, Hops: 1
|
||||
```
|
||||
- **Help:**
|
||||
Send `ping?` in a Direct Message (DM) for usage instructions.
|
||||
|
||||
---
|
||||
|
||||
### Notes
|
||||
- You can mention users or tags in your ping/test messages (e.g., `ping @user` or `ping #group`) to target specific nodes or groups.
|
||||
- Some commands may only be available in Direct Messages, depending on configuration.
|
||||
|
||||
| Command | Description | ✅ Works Off-Grid |
|
||||
|--------------|-------------|------------------|
|
||||
| `ping`, `ack` | Return data for signal. Example: `ping 15 #DrivingI5` (activates auto-ping every 20 seconds for count 15 via DM only) you can also ping @NODE short name and if BBS DM enabled it will send them a joke | ✅ |
|
||||
@@ -92,6 +123,8 @@ Enable/disable games in `[games]` section of `config.ini`.
|
||||
|
||||
Enable in `[bbs]` section of `config.ini`.
|
||||
|
||||
more at [meshBBS: How-To & API Documentation](bbstools.md)
|
||||
|
||||
---
|
||||
|
||||
## Checklist
|
||||
@@ -127,6 +160,40 @@ Enable in `[checklist]` section of `config.ini`.
|
||||
|
||||
Configure in `[location]` section of `config.ini`.
|
||||
|
||||
Certainly! Here’s a README help section for your `mapHandler` command, suitable for users of your meshbot:
|
||||
|
||||
---
|
||||
|
||||
## 📍 Map Command
|
||||
|
||||
The `map` command allows you to log your current GPS location with a custom description. This is useful for mapping mesh nodes, events, or points of interest.
|
||||
|
||||
### Usage
|
||||
|
||||
- **Show Help**
|
||||
```
|
||||
map help
|
||||
```
|
||||
Displays usage instructions for the map command.
|
||||
|
||||
- **Log a Location**
|
||||
```
|
||||
map <description>
|
||||
```
|
||||
Example:
|
||||
```
|
||||
map Found a new mesh node near the park
|
||||
```
|
||||
This will log your current location with the description "Found a new mesh node near the park".
|
||||
|
||||
### How It Works
|
||||
|
||||
- The bot records your user ID, latitude, longitude, and your description in a CSV file (`data/map_data.csv`).
|
||||
- If your location data is missing or invalid, you’ll receive an error message.
|
||||
- You can view or process the CSV file later for mapping or analysis.
|
||||
|
||||
**Tip:** Use `map help` at any time to see these instructions in the bot.
|
||||
|
||||
---
|
||||
|
||||
## EAS & Emergency Alerts
|
||||
@@ -201,7 +268,7 @@ Configure in `[wikipedia]` section of `config.ini`.
|
||||
|
||||
---
|
||||
|
||||
## Scheduler
|
||||
## 📅 Mesh Bot Scheduler User Guide
|
||||
|
||||
Automate messages and tasks using the scheduler module.
|
||||
|
||||
@@ -239,6 +306,76 @@ To send a daily message at 09:00:
|
||||
- All scheduled jobs run asynchronously as long as the bot is running.
|
||||
- For troubleshooting, check the logs for scheduler activity and errors.
|
||||
|
||||
|
||||
### Basic Scheduler Options
|
||||
|
||||
You can schedule messages or actions using the following options in your configuration:
|
||||
|
||||
- **day**: Run every day at a specific time or every N days.
|
||||
- **mon, tue, wed, thu, fri, sat, sun**: Run on a specific day of the week at a specific time.
|
||||
- **hour**: Run every N hours.
|
||||
- **min**: Run every N minutes.
|
||||
|
||||
#### **Examples:**
|
||||
|
||||
| Option | Time/Interval | What it does |
|
||||
|-------------|--------------|---------------------------------------------------|
|
||||
| `day` | `08:00` | Runs every day at 8:00 AM |
|
||||
| `day` | `2` | Runs every 2 days |
|
||||
| `mon` | `09:30` | Runs every Monday at 9:30 AM |
|
||||
| `hour` | `1` | Runs every hour |
|
||||
| `min` | `30` | Runs every 30 minutes |
|
||||
|
||||
- If you specify a day (e.g., `mon`) and a time (e.g., `09:30`), the message will be sent at that time on that day.
|
||||
- If you specify `hour` or `min`, set the interval (e.g., every 2 hours or every 15 minutes).
|
||||
|
||||
---
|
||||
|
||||
### Special Scheduler Options
|
||||
|
||||
#### **joke**
|
||||
- Schedules the bot to send a random joke at the specified interval.
|
||||
- **Example:**
|
||||
- Option: `joke`
|
||||
- Interval: `60`
|
||||
- → Sends a joke every 60 minutes.
|
||||
|
||||
#### **link**
|
||||
- Schedules the bot to send a satellite link message at the specified interval (in hours).
|
||||
- **Example:**
|
||||
- Option: `link`
|
||||
- Interval: `2`
|
||||
- → Sends a bbslink message every 2 hours.
|
||||
|
||||
#### **weather**
|
||||
- Schedules the bot to send a weather update at the specified interval (in hours).
|
||||
- **Example:**
|
||||
- Option: `weather`
|
||||
- Interval: `3`
|
||||
- → Sends a weather update every 3 hours.
|
||||
|
||||
---
|
||||
|
||||
### Days of the Week
|
||||
|
||||
You can use any of these options to schedule messages on specific days:
|
||||
- `mon`, `tue`, `wed`, `thu`, `fri`, `sat`, `sun`
|
||||
|
||||
**Example:**
|
||||
- Option: `fri`
|
||||
- Time: `17:00`
|
||||
- → Sends the message every Friday at 5:00 PM.
|
||||
|
||||
---
|
||||
|
||||
### Configuration Fields
|
||||
|
||||
- **schedulerValue**: The schedule type (e.g., `day`, `joke`, `weather`, `mon`, etc.)
|
||||
- **schedulerTime**: The time to run (e.g., `08:00`). Leave blank for interval-based schedules.
|
||||
- **schedulerInterval**: The interval (e.g., `2` for every 2 hours/days/minutes).
|
||||
- **schedulerChannel**: The channel number to send to.
|
||||
- **schedulerInterface**: The device/interface number.
|
||||
|
||||
---
|
||||
|
||||
## Other Utilities
|
||||
|
||||
189
modules/bbstools.md
Normal file
189
modules/bbstools.md
Normal file
@@ -0,0 +1,189 @@
|
||||
|
||||
---
|
||||
|
||||
# 📡 meshBBS: How-To & API Documentation
|
||||
|
||||
This document covers the Bulliten Board System or BBS componment of the meshing-around project.
|
||||
|
||||
## Table of Contents
|
||||
|
||||
1. [BBS Core Functions](#1-bbs-core-functions)
|
||||
- [Direct Messages (DMs)](#11-direct-messages-dms)
|
||||
2. [BBS Database Sync: File-Based (Out-of-Band)](#1-bbs-database-sync-file-based-out-of-band)
|
||||
3. [BBS Over-the-Air (OTA) Sync: Linking](#2-bbs-over-the-air-ota-sync-linking)
|
||||
4. [Scheduling BBS Sync](#3-scheduling-bbs-sync)
|
||||
5. [Best Practices](#4-best-practices)
|
||||
6. [Example: Full Sync Workflow](#5-example-full-sync-workflow)
|
||||
7. [Troubleshooting](#6-troubleshooting)
|
||||
8. [API Reference: BBS Sync](#7-api-reference-bbs-sync)
|
||||
|
||||
## 1. **BBS Core Functions**
|
||||
|
||||
## 1.1 **Direct Messages (DMs)**
|
||||
|
||||
### **How DMs Work**
|
||||
- Direct Messages (DMs) are private messages sent from one node to another.
|
||||
- DMs are stored separately from public posts in `data/bbsdm.pkl`.
|
||||
- Each DM entry in the pickle, typically includes: `[id, toNode, message, fromNode, timestamp, threadID, replytoID]`.
|
||||
|
||||
### **DM Delivery**
|
||||
- When a DM is posted using `bbs_post_dm(toNode, message, fromNode)`, it is added to the recipient's DM database.
|
||||
- DMs can be delivered in two ways:
|
||||
1. **File-Based Sync:**
|
||||
- The `bbsdm.pkl` file is copied between nodes using SCP, rsync, or other file transfer methods.
|
||||
- After syncing, the recipient node can check for new DMs using `bbs_check_dm(toNode)`.
|
||||
2. **Over-the-Air (OTA) Sync:**
|
||||
- DMs can be exchanged between nodes using the same OTA sync mechanism as other posts.
|
||||
- The bot will receive (onRX) or detect any packet and deliver the DM/mail to the recipient.
|
||||
- DMs are only visible to the intended recipient node and are not listed in the public message list.
|
||||
|
||||
### **DM Commands**
|
||||
| Command | Description |
|
||||
|-----------------|---------------------------------------------|
|
||||
| `bbs_post_dm` | Send a direct message to another node |
|
||||
| `bbs_check_dm` | Check for new DMs for your node |
|
||||
| `bbs_delete_dm` | Delete a DM after reading |
|
||||
|
||||
---
|
||||
|
||||
|
||||
### **Message Storage**
|
||||
The .. database is
|
||||
- Messages are stored in `data/bbsdb.pkl` (public posts) and `data/bbsdm.pkl` (direct messages).
|
||||
- Format: Each message is a list, e.g. `[id, subject, body, fromNode, timestamp, threadID, replytoID]`.
|
||||
|
||||
|
||||
| Command | Description |
|
||||
|--------------|-----------------------------------------------|
|
||||
| `bbshelp` | Show BBS help |
|
||||
| `bbslist` | List messages |
|
||||
| `bbsread` | Read a message by ID |
|
||||
| `bbspost` | Post a message or DM |
|
||||
| `bbsdelete` | Delete a message |
|
||||
| `bbsinfo` | BBS stats (sysop) |
|
||||
| `bbslink` | Link messages between BBS systems |
|
||||
|
||||
---
|
||||
Enable in `[bbs]` section of `config.ini`.
|
||||
|
||||
## 1. **BBS Database Sync: File-Based (Out-of-Band)**
|
||||
|
||||
### **Manual/Automated File Sync (e.g., SSH/SCP)**
|
||||
- **Purpose:** Sync BBS data between nodes by copying `bbsdb.pkl` and `bbsdm.pkl` files.
|
||||
- **How-To:**
|
||||
1. **Locate Files:**
|
||||
- `data/bbsdb.pkl` (public posts)
|
||||
- `data/bbsdm.pkl` (direct messages)
|
||||
2. **Copy Files:**
|
||||
Use `scp` or `rsync` to copy files between nodes:
|
||||
```sh
|
||||
scp user@remote:/path/to/meshing-around/data/bbsdb.pkl ./data/bbsdb.pkl
|
||||
scp user@remote:/path/to/meshing-around/data/bbsdm.pkl ./data/bbsdm.pkl
|
||||
```
|
||||
3. **Reload Database:**
|
||||
After copying, when the "API" is enabled the watchdog will look for changes and injest.
|
||||
|
||||
- **Automating with Cron/Scheduler:**
|
||||
- Set up a cron job or use the bot’s scheduler to periodically pull/push files.
|
||||
|
||||
---
|
||||
|
||||
## 2. **BBS Over-the-Air (OTA) Sync: Linking**
|
||||
### **How OTA Sync Works**
|
||||
- Nodes can exchange BBS messages using special commands over the mesh network.
|
||||
- Uses `bbslink` and `bbsack` commands for message exchange.
|
||||
- Future supports compression for bandwidth efficiency.
|
||||
|
||||
### **Enabling BBS Linking**
|
||||
- Set `bbs_link_enabled = True` in your config.
|
||||
- Optionally, set `bbs_link_whitelist` to restrict which nodes can sync.
|
||||
|
||||
### **Manual Sync Command**
|
||||
- To troubleshoot request sync from another node, send:
|
||||
```
|
||||
bbslink <messageID> $<subject> #<body>
|
||||
```
|
||||
- The receiving node will respond with `bbsack <messageID>`.
|
||||
|
||||
### **Out-of-Band Channel**
|
||||
- For high-reliability sync, configure a dedicated channel (not used for chat).
|
||||
---
|
||||
|
||||
## 3. **Scheduling BBS Sync**
|
||||
|
||||
### **Using the Bot’s Scheduler**
|
||||
|
||||
- You can schedule periodic sync requests to a peer node.
|
||||
- Example: Every hour, send a `bbslink` request to a peer.
|
||||
see more at [Module Readme](README.md#scheduler)
|
||||
---
|
||||
|
||||
## 4. **Best Practices**
|
||||
|
||||
- **Backup:** Regularly back up `bbsdb.pkl` and `bbsdm.pkl`.
|
||||
- **Security:** Use SSH keys for file transfer; restrict OTA sync to trusted nodes.
|
||||
- **Reliability:** Use a dedicated channel for BBS sync to avoid chat congestion.
|
||||
- **Automation:** Use the scheduler for regular syncs, both file-based and OTA.
|
||||
|
||||
---
|
||||
|
||||
## 5. **Example: Full Sync Workflow**
|
||||
|
||||
1. **Set up a dedicated sync channel** (e.g., channel bot-admin).
|
||||
2. **Configure both nodes** with `bbs_link_enabled = True` and add each other to `bbs_link_whitelist`.
|
||||
3. **Schedule sync** every hour:
|
||||
- Node A sends `bbslink 0` to Node B on channel 99.
|
||||
- Node B responds with messages and `bbsack`.
|
||||
4. **Optionally, use SSH/scp** to copy `bbsdb.pkl` for full out-of-band backup.
|
||||
|
||||
---
|
||||
|
||||
## 6. **Troubleshooting**
|
||||
|
||||
- **Messages not syncing?**
|
||||
- Check `bbs_link_enabled` and whitelist settings.
|
||||
- Ensure both nodes are on the same sync channel.
|
||||
- Check logs for errors.
|
||||
|
||||
- **File sync issues?**
|
||||
- Verify file permissions and paths.
|
||||
- Ensure the bot reloads the database after file copy.
|
||||
|
||||
## 7. **API Reference: BBS Sync**
|
||||
|
||||
### **Key Functions in Python**
|
||||
| Function | Purpose | Usage Example |
|
||||
|-------------------------|-------------------------------------------|----------------------------------------------------|
|
||||
| `bbs_post_message()` | Post a new public message | `bbs_post_message(subject, body, fromNode)` |
|
||||
| `bbs_read_message()` | Read a message by ID | `bbs_read_message(messageID)` |
|
||||
| `bbs_delete_message()` | Delete a message (admin/owner only) | `bbs_delete_message(messageID, fromNode)` |
|
||||
| `bbs_list_messages()` | List all message subjects | `bbs_list_messages()` |
|
||||
| `bbs_post_dm()` | Post a direct message | `bbs_post_dm(toNode, message, fromNode)` |
|
||||
| `bbs_check_dm()` | Check for DMs for a node | `bbs_check_dm(toNode)` |
|
||||
| `bbs_delete_dm()` | Delete a DM after reading | `bbs_delete_dm(toNode, message)` |
|
||||
| `get_bbs_stats()` | Get stats on BBS and DMs | `get_bbs_stats()` |
|
||||
|
||||
|
||||
| Function | Purpose |
|
||||
|---------------------------|-------------------------------------------|
|
||||
| `bbs_sync_posts()` | Handles incoming/outgoing sync requests |
|
||||
| `bbs_receive_compressed()`| Handles compressed sync data |
|
||||
| `compress_data()` | Compresses data for OTA transfer |
|
||||
| `decompress_data()` | Decompresses received data |
|
||||
|
||||
|
||||
### **Handle Incoming Sync**
|
||||
- The bot automatically processes incoming `bbslink` and `bbsack` commands via `bbs_sync_posts()`.
|
||||
|
||||
### **Compressed Sync**
|
||||
Future Use
|
||||
- If `useSynchCompression` is enabled, use:
|
||||
```python
|
||||
compressed = compress_data(msg)
|
||||
send_raw_bytes(peerNode, compressed)
|
||||
```
|
||||
- Receiving node uses `bbs_receive_compressed()`.
|
||||
|
||||
---
|
||||
|
||||
---
|
||||
@@ -100,8 +100,8 @@ def tableOfContents():
|
||||
'plant': '🌱', 'tree': '🌳', 'flower': '🌸', 'leaf': '🍃', 'cactus': '🌵', 'mushroom': '🍄', 'herb': '🌿', 'bamboo': '🎍', 'rose': '🌹', 'tulip': '🌷', 'sunflower': '🌻',
|
||||
'hibiscus': '🌺', 'cherry blossom': '🌸', 'bouquet': '💐', 'seedling': '🌱', 'palm tree': '🌴', 'evergreen tree': '🌲', 'deciduous tree': '🌳', 'fallen leaf': '🍂', 'maple leaf': '🍁',
|
||||
'ear of rice': '🌾', 'shamrock': '☘️', 'four leaf clover': '🍀', 'grapes': '🍇', 'melon': '🍈', 'watermelon': '🍉', 'tangerine': '🍊', 'lemon': '🍋', 'banana': '🍌', 'pineapple': '🍍',
|
||||
'mango': '🥭', 'apple': '🍎', 'green apple': '🍏', 'pear': '🍐', 'peach': '🍑', 'cherries': '🍒', 'strawberry': '🍓', 'kiwi': '🥝', 'tomato': '🍅', 'coconut': '🥥', 'avocado': '🥑',
|
||||
'eggplant': '🍆', 'potato': '🥔', 'carrot': '🥕', 'corn': '🌽', 'hot pepper': '🌶️', 'cucumber': '🥒', 'leafy green': '🥬', 'broccoli': '🥦', 'garlic': '🧄', 'onion': '🧅',
|
||||
'green apple': '🍏', 'pear': '🍐', 'peach': '🍑', 'cherries': '🍒', 'strawberry': '🍓', 'kiwi': '🥝', 'tomato': '🍅', 'coconut': '🥥', 'avocado': '🥑',
|
||||
'hot pepper': '🌶️', 'cucumber': '🥒', 'leafy green': '🥬', 'broccoli': '🥦', 'garlic': '🧄', 'onion': '🧅',
|
||||
'peanuts': '🥜', 'chestnut': '🌰', 'bread': '🍞', 'croissant': '🥐', 'baguette': '🥖', 'flatbread': '🥙', 'pretzel': '🥨', 'bagel': '🥯', 'pancakes': '🥞', 'waffle': '🧇', 'cheese': '🧀',
|
||||
'meat': '🍖', 'poultry': '🍗', 'bacon': '🥓', 'hamburger': '🍔', 'fries': '🍟', 'pizza': '🍕', 'hot dog': '🌭', 'sandwich': '🥪', 'taco': '🌮', 'burrito': '🌯', 'tamale': '🫔',
|
||||
'stuffed flatbread': '🥙', 'falafel': '🧆', 'egg': '🥚', 'fried egg': '🍳', 'shallow pan of food': '🥘', 'pot of food': '🍲', 'fondue': '🫕', 'bowl with spoon': '🥣', 'green salad': '🥗',
|
||||
@@ -115,19 +115,19 @@ def tableOfContents():
|
||||
'globe with meridians': '🌐', 'world map': '🗺️', 'mountain': '⛰️', 'volcano': '🌋', 'mount fuji': '🗻', 'camping': '🏕️', 'beach with umbrella': '🏖️', 'desert': '🏜️', 'desert island': '🏝️',
|
||||
'national park': '🏞️', 'stadium': '🏟️', 'classical building': '🏛️', 'building construction': '🏗️', 'brick': '🧱', 'rock': '🪨', 'wood': '🪵', 'hut': '🛖', 'houses': '🏘️', 'derelict house': '🏚️',
|
||||
'house with garden': '🏡', 'office building': '🏢', 'japanese post office': '🏣', 'post office': '🏤', 'hospital': '🏥', 'bank': '🏦', 'hotel': '🏨', 'love hotel': '🏩', 'convenience store': '🏪',
|
||||
'school': '🏫', 'department store': '🏬', 'factory': '🏭', 'japanese castle': '🏯', 'castle': '🏰', 'wedding': '💒', 'tokyo tower': '🗼', 'statue of liberty': '🗽', 'church': '⛪', 'mosque': '🕌',
|
||||
'hindu temple': '🛕', 'synagogue': '🕍', 'shinto shrine': '⛩️', 'kaaba': '🕋', 'fountain': '⛲', 'tent': '⛺', 'foggy': '🌁', 'night with stars': '🌃', 'sunrise over mountains': '🌄', 'sunrise': '🌅',
|
||||
'cityscape at dusk': '🌆', 'sunset': '🌇', 'cityscape': '🏙️', 'bridge at night': '🌉', 'hot springs': '♨️', 'carousel horse': '🎠', 'ferris wheel': '🎡', 'roller coaster': '🎢', 'barber pole': '💈',
|
||||
'castle': '🏰', 'wedding': '💒', 'tokyo tower': '🗼', 'statue of liberty': '🗽', 'church': '⛪', 'mosque': '🕌',
|
||||
'fountain': '⛲', 'tent': '⛺', 'foggy': '🌁', 'night with stars': '🌃', 'sunrise over mountains': '🌄', 'sunrise': '🌅',
|
||||
'cityscape at dusk': '🌆', 'sunset': '🌇', 'cityscape': '🏙️', 'bridge at night': '🌉', 'hot springs': '♨️', 'carousel horse': '🎠', 'barber pole': '💈',
|
||||
'robot': '🤖', 'alien': '👽', 'ghost': '👻', 'skull': '💀', 'pumpkin': '🎃', 'clown': '🤡', 'wizard': '🧙', 'elf': '🧝', 'fairy': '🧚', 'mermaid': '🧜', 'vampire': '🧛',
|
||||
'zombie': '🧟', 'genie': '🧞', 'superhero': '🦸', 'supervillain': '🦹', 'mage': '🧙', 'knight': '🛡️', 'ninja': '🥷', 'pirate': '🏴☠️', 'angel': '👼', 'devil': '😈', 'dragon': '🐉',
|
||||
'unicorn': '🦄', 'phoenix': '🦅', 'griffin': '🦅', 'centaur': '🐎', 'minotaur': '🐂', 'cyclops': '👁️', 'medusa': '🐍', 'sphinx': '🦁', 'kraken': '🦑', 'yeti': '❄️', 'sasquatch': '🦧',
|
||||
'loch ness monster': '🦕', 'chupacabra': '🐐', 'banshee': '👻', 'golem': '🗿', 'djinn': '🧞', 'basilisk': '🐍', 'hydra': '🐉', 'cerberus': '🐶', 'chimera': '🐐', 'manticore': '🦁', 'wyvern': '🐉',
|
||||
'pegasus': '🦄', 'hippogriff': '🦅', 'kelpie': '🐎', 'selkie': '🦭', 'kitsune': '🦊', 'tanuki': '🦝', 'tengu': '🦅', 'oni': '👹', 'yokai': '👻', 'kappa': '🐢', 'yurei': '👻',
|
||||
'kami': '👼', 'shinigami': '💀', 'bakemono': '👹', 'tsukumogami': '🧸', 'noppera-bo': '👤', 'rokurokubi': '🧛', 'yuki-onna': '❄️', 'jorogumo': '🕷️', 'nue': '🐍', 'ubume': '👼',
|
||||
'atom': '⚛️', 'dna': '🧬', 'microscope': '🔬', 'telescope': '🔭', 'rocket': '🚀', 'satellite': '🛰️', 'spaceship': '🛸', 'planet': '🪐', 'black hole': '🕳️', 'galaxy': '🌌',
|
||||
'comet': '☄️', 'constellation': '🌠', 'lightning': '⚡', 'magnet': '🧲', 'battery': '🔋', 'computer': '💻', 'keyboard': '⌨️', 'mouse': '🖱️', 'printer': '🖨️', 'floppy disk': '💾',
|
||||
'atom': '⚛️', 'dna': '🧬', 'microscope': '🔬', 'telescope': '🔭', 'satellite': '🛰️', 'spaceship': '🛸', 'planet': '🪐', 'black hole': '🕳️', 'galaxy': '🌌',
|
||||
'constellation': '🌠', 'lightning': '⚡', 'magnet': '🧲', 'computer': '💻', 'keyboard': '⌨️', 'mouse': '🖱️', 'printer': '🖨️', 'floppy disk': '💾',
|
||||
'cd': '💿', 'dvd': '📀', 'smartphone': '📱', 'tablet': '📲', 'watch': '⌚', 'camera': '📷', 'video camera': '📹', 'projector': '📽️', 'radio': '📻', 'television': '📺',
|
||||
'satellite dish': '📡', 'game controller': '🎮', 'joystick': '🕹️', 'vr headset': '🕶️', 'headphones': '🎧', 'speaker': '🔊', 'flashlight': '🔦', 'circuit': '🔌', 'chip': '💻',
|
||||
'satellite dish': '📡', 'game controller': '🎮', 'joystick': '🕹️', 'vr headset': '🕶️', 'headphones': '🎧', 'speaker': '🔊', 'circuit': '🔌', 'chip': '💻',
|
||||
'server': '🖥️', 'database': '💾', 'cloud': '☁️', 'network': '🌐', 'code': '💻', 'bug': '🐛', 'virus': '🦠', 'bacteria': '🦠', 'lab coat': '🥼', 'safety goggles': '🥽',
|
||||
'test tube': '🧪', 'petri dish': '🧫', 'beaker': '🧪', 'bunsen burner': '🔥', 'graduated cylinder': '🧪', 'pipette': '🧪', 'scalpel': '🔪', 'syringe': '💉', 'pill': '💊',
|
||||
'stethoscope': '🩺', 'thermometer': '🌡️', 'x-ray': '🩻', 'brain': '🧠', 'heart': '❤️', 'lung': '🫁', 'bone': '🦴', 'muscle': '💪', 'robot arm': '🦾', 'robot leg': '🦿',
|
||||
@@ -136,21 +136,18 @@ def tableOfContents():
|
||||
'spider': '🕷️', 'scorpion': '🦂', 'turkey': '🦃', 'peacock': '🦚', 'parrot': '🦜', 'swan': '🦢', 'flamingo': '🦩', 'dodo': '🦤', 'sloth': '🦥', 'otter': '🦦',
|
||||
'skunk': '🦨', 'kangaroo': '🦘', 'badger': '🦡', 'beaver': '🦫', 'bison': '🦬', 'mammoth': '🦣', 'raccoon': '🦝', 'hedgehog': '🦔', 'squirrel': '🐿️', 'chipmunk': '🐿️',
|
||||
'porcupine': '🦔', 'llama': '🦙', 'giraffe': '🦒', 'zebra': '🦓', 'hippopotamus': '🦛', 'rhinoceros': '🦏', 'gorilla': '🦍', 'orangutan': '🦧', 'elephant': '🐘', 'camel': '🐫',
|
||||
'llama': '🦙', 'alpaca': '🦙', 'buffalo': '🐃', 'ox': '🐂', 'deer': '🦌', 'moose': '🦌', 'reindeer': '🦌', 'goat': '🐐', 'sheep': '🐑', 'ram': '🐏', 'lamb': '🐑', 'horse': '🐴',
|
||||
'unicorn': '🦄', 'zebra': '🦓', 'cow': '🐄', 'pig': '🐖', 'boar': '🐗', 'mouse': '🐁', 'rat': '🐀', 'hamster': '🐹', 'rabbit': '🐇', 'chipmunk': '🐿️', 'beaver': '🦫', 'hedgehog': '🦔',
|
||||
'bat': '🦇', 'bear': '🐻', 'koala': '🐨', 'panda': '🐼', 'sloth': '🦥', 'otter': '🦦', 'skunk': '🦨', 'kangaroo': '🦘', 'badger': '🦡', 'turkey': '🦃', 'chicken': '🐔', 'rooster': '🐓',
|
||||
'peacock': '🦚', 'parrot': '🦜', 'swan': '🦢', 'flamingo': '🦩', 'dodo': '🦤', 'crocodile': '🐊', 'turtle': '🐢', 'lizard': '🦎', 'snake': '🐍', 'dragon': '🐉', 'sauropod': '🦕', 't-rex': '🦖',
|
||||
'whale': '🐋', 'dolphin': '🐬', 'fish': '🐟', 'blowfish': '🐡', 'shark': '🦈', 'octopus': '🐙', 'shell': '🐚', 'crab': '🦀', 'lobster': '🦞', 'shrimp': '🦐', 'squid': '🦑', 'snail': '🐌', 'butterfly': '🦋',
|
||||
'bee': '🐝', 'beetle': '🐞', 'ant': '🐜', 'cricket': '🦗', 'spider': '🕷️', 'scorpion': '🦂', 'mosquito': '🦟', 'microbe': '🦠', 'locomotive': '🚂', 'arm': '💪', 'leg': '🦵', 'sponge': '🧽',
|
||||
'toothbrush': '🪥', 'broom': '🧹', 'basket': '🧺', 'roll of paper': '🧻', 'bucket': '🪣', 'soap': '🧼', 'toilet paper': '🧻', 'shower': '🚿', 'bathtub': '🛁', 'razor': '🪒', 'lotion': '🧴',
|
||||
'letter': '✉️', 'envelope': '✉️', 'mail': '📬', 'post': '📮', 'golf': '⛳️', 'golfing': '⛳️', 'office': '🏢', 'meeting': '📅', 'presentation': '📊', 'report': '📄', 'document': '📄',
|
||||
'alpaca': '🦙', 'buffalo': '🐃', 'ox': '🐂', 'deer': '🦌', 'moose': '🦌', 'reindeer': '🦌', 'goat': '🐐', 'sheep': '🐑', 'ram': '🐏', 'lamb': '🐑', 'horse': '🐴',
|
||||
'rat': '🐀', 'hedgehog': '🦔', 'chicken': '🐔', 'rooster': '🐓', 'crocodile': '🐊', 'turtle': '🐢', 'lizard': '🦎', 'dragon': '🐉', 'sauropod': '🦕', 't-rex': '🦖', 'butterfly': '🦋',
|
||||
'mosquito': '🦟', 'microbe': '🦠', 'locomotive': '🚂', 'arm': '💪', 'leg': '🦵', 'sponge': '🧽',
|
||||
'toothbrush': '🪥', 'roll of paper': '🧻', 'soap': '🧼', 'toilet paper': '🧻', 'shower': '🚿', 'bathtub': '🛁', 'razor': '🪒', 'lotion': '🧴',
|
||||
'letter': '✉️', 'envelope': '✉️', 'mail': '📬', 'post': '📮', 'golf': '⛳️', 'golfing': '⛳️', 'meeting': '📅', 'presentation': '📊', 'report': '📄', 'document': '📄',
|
||||
'file': '📁', 'folder': '📂', 'sports': '🏅', 'athlete': '🏃', 'competition': '🏆', 'race': '🏁', 'tournament': '🏆', 'champion': '🏆', 'medal': '🏅', 'victory': '🏆', 'win': '🏆', 'lose': '😞',
|
||||
'draw': '🤝', 'team': '👥', 'player': '👤', 'coach': '👨🏫', 'referee': '🧑⚖️', 'stadium': '🏟️', 'arena': '🏟️', 'field': '🏟️', 'court': '🏟️', 'track': '🏟️', 'gym': '🏋️', 'fitness': '🏋️', 'exercise': '🏋️',
|
||||
'workout': '🏋️', 'training': '🏋️', 'practice': '🏋️', 'game': '🎮', 'match': '🎮', 'score': '🏅', 'goal': '🥅', 'point': '🏅', 'basket': '🏀', 'home run': '⚾️', 'strike': '🎳', 'spare': '🎳', 'frame': '🎳',
|
||||
'inning': '⚾️', 'quarter': '🏈', 'half': '🏈', 'overtime': '🏈', 'penalty': '⚽️', 'foul': '⚽️', 'timeout': '⏱️', 'substitute': '🔄', 'bench': '🪑', 'sideline': '🏟️', 'dugout': '⚾️', 'locker room': '🚪', 'shower': '🚿',
|
||||
'uniform': '👕', 'jersey': '👕', 'cleats': '👟', 'helmet': '⛑️', 'pads': '🛡️', 'gloves': '🧤', 'bat': '⚾️', 'ball': '⚽️', 'puck': '🏒', 'stick': '🏒', 'net': '🥅', 'hoop': '🏀', 'goalpost': '🥅', 'whistle': '🔔',
|
||||
'scoreboard': '📊', 'fans': '👥', 'crowd': '👥', 'cheer': '📣', 'boo': '😠', 'applause': '👏', 'celebration': '🎉', 'parade': '🎉', 'trophy': '🏆', 'medal': '🏅', 'ribbon': '🎀', 'cup': '🏆', 'championship': '🏆',
|
||||
'league': '🏆', 'season': '🏆', 'playoffs': '🏆', 'finals': '🏆', 'champion': '🏆', 'runner-up': '🥈', 'third place': '🥉', 'snowman': '☃️', 'snowmen': '⛄️'
|
||||
'league': '🏆', 'season': '🏆', 'playoffs': '🏆', 'finals': '🏆', 'runner-up': '🥈', 'third place': '🥉', 'snowman': '☃️', 'snowmen': '⛄️'
|
||||
}
|
||||
|
||||
return wordToEmojiMap
|
||||
|
||||
@@ -16,7 +16,7 @@ def get_govUK_alerts(lat, lon):
|
||||
try:
|
||||
# get UK.gov alerts
|
||||
url = 'https://www.gov.uk/alerts'
|
||||
response = requests.get(url)
|
||||
response = requests.get(url, timeout=urlTimeoutSeconds)
|
||||
soup = bs.BeautifulSoup(response.text, 'html.parser')
|
||||
# the alerts are in <h2 class="govuk-heading-m" id="alert-status">
|
||||
alert = soup.find('h2', class_='govuk-heading-m', id='alert-status')
|
||||
@@ -35,7 +35,7 @@ def get_nina_alerts():
|
||||
alerts = []
|
||||
for regionalKey in myRegionalKeysDE:
|
||||
url = ("https://nina.api.proxy.bund.dev/api31/dashboard/" + regionalKey + ".json")
|
||||
response = requests.get(url)
|
||||
response = requests.get(url, timeout=urlTimeoutSeconds)
|
||||
data = response.json()
|
||||
|
||||
for item in data:
|
||||
@@ -53,7 +53,7 @@ def get_wxUKgov():
|
||||
try:
|
||||
# get UK weather warnings
|
||||
url = 'https://www.metoffice.gov.uk/weather/guides/rss'
|
||||
response = requests.get(url)
|
||||
response = requests.get(url, timeout=urlTimeoutSeconds)
|
||||
soup = bs.BeautifulSoup(response.content, 'xml')
|
||||
|
||||
items = soup.find_all('item')
|
||||
|
||||
@@ -10,9 +10,11 @@ import xml.dom.minidom
|
||||
from datetime import datetime
|
||||
from modules.log import *
|
||||
import math
|
||||
import csv
|
||||
import os
|
||||
|
||||
|
||||
trap_list_location = ("whereami", "wx", "wxa", "wxalert", "rlist", "ea", "ealert", "riverflow", "valert", "earthquake", "howfar")
|
||||
trap_list_location = ("whereami", "wx", "wxa", "wxalert", "rlist", "ea", "ealert", "riverflow", "valert", "earthquake", "howfar", "map",)
|
||||
|
||||
def where_am_i(lat=0, lon=0, short=False, zip=False):
|
||||
whereIam = ""
|
||||
@@ -133,7 +135,7 @@ def getArtSciRepeaters(lat=0, lon=0):
|
||||
if zipCode.isnumeric():
|
||||
try:
|
||||
artsci_url = f"http://www.artscipub.com/mobile/showstate.asp?zip={zipCode}"
|
||||
response = requests.get(artsci_url)
|
||||
response = requests.get(artsci_url, timeout=urlTimeoutSeconds)
|
||||
if response.status_code!=200:
|
||||
logger.error(f"Location:Error fetching data from {artsci_url} with status code {response.status_code}")
|
||||
soup = bs.BeautifulSoup(response.text, 'html.parser')
|
||||
@@ -1054,3 +1056,89 @@ def get_openskynetwork(lat=0, lon=0):
|
||||
aircraft_report = aircraft_report[:-1]
|
||||
aircraft_report = abbreviate_noaa(aircraft_report)
|
||||
return aircraft_report if aircraft_report else NO_ALERTS
|
||||
|
||||
def log_locationData_toMap(userID, location, message):
|
||||
"""
|
||||
Logs location data to a CSV file for meshing purposes.
|
||||
Returns True if successful, False otherwise.
|
||||
"""
|
||||
lat, lon = location
|
||||
if lat is None or lon is None or lat == 0.0 or lon == 0.0:
|
||||
return False
|
||||
|
||||
# Set default directory to ../data/
|
||||
default_dir = os.path.join(os.path.dirname(__file__), "..", "data")
|
||||
os.makedirs(default_dir, exist_ok=True)
|
||||
map_filepath = os.path.join(default_dir, "map_data.csv")
|
||||
|
||||
# Check if the file exists to determine if we need to write headers
|
||||
write_header = not os.path.isfile(map_filepath) or os.path.getsize(map_filepath) == 0
|
||||
|
||||
try:
|
||||
with open(map_filepath, mode='a', newline='') as csvfile:
|
||||
fieldnames = ['userID', 'Latitude', 'Longitude', 'Description']
|
||||
writer = csv.DictWriter(csvfile, fieldnames=fieldnames)
|
||||
|
||||
# Write headers only if the file did not exist before or is empty
|
||||
if write_header:
|
||||
writer.writeheader()
|
||||
|
||||
writer.writerow({
|
||||
'userID': userID,
|
||||
'Latitude': lat,
|
||||
'Longitude': lon,
|
||||
'Description': message if message else ""
|
||||
})
|
||||
|
||||
logger.debug(f"Logged location for {userID} to {map_filepath}")
|
||||
return True
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to log location for {userID}: {e}")
|
||||
return False
|
||||
|
||||
def mapHandler(userID, deviceID, channel_number, message, snr, rssi, hop):
|
||||
from modules.system import get_node_location
|
||||
command = message[len("map"):].strip()
|
||||
location = get_node_location(userID, deviceID)
|
||||
lat = location[0]
|
||||
lon = location[1]
|
||||
"""
|
||||
Handles 'map' commands from meshbot.
|
||||
Usage:
|
||||
map <description text> - Log current location with description
|
||||
"""
|
||||
command = str(command) # Ensure command is always a string
|
||||
|
||||
if command.strip().lower() == "?":
|
||||
return (
|
||||
"Usage:\n"
|
||||
" 🗺️map <description text> - Log your current location with a description\n"
|
||||
"Example:\n"
|
||||
" 🗺️map Found a new mesh node near the park"
|
||||
)
|
||||
|
||||
description = command.strip()
|
||||
# if no description provided, set to default
|
||||
if not description:
|
||||
description = "Logged:"
|
||||
# Sanitize description for CSV injection
|
||||
if description and description[0] in ('=', '+', '-', '@'):
|
||||
description = "'" + description
|
||||
|
||||
# if there is SNR and RSSI info, append to description
|
||||
if snr is not None and rssi is not None:
|
||||
description += f" SNR:{snr}dB RSSI:{rssi}dBm"
|
||||
|
||||
# if there is hop info, append to description
|
||||
if hop is not None:
|
||||
description += f" Meta:{hop}"
|
||||
|
||||
# location should be a tuple: (lat, lon)
|
||||
if not location or len(location) != 2:
|
||||
return "🚫Location data is missing or invalid."
|
||||
|
||||
success = log_locationData_toMap(userID, location, description)
|
||||
if success:
|
||||
return f"📍Location logged "
|
||||
else:
|
||||
return "🚫Failed to log location. Please try again."
|
||||
|
||||
@@ -4,6 +4,7 @@ import urllib.request
|
||||
import xml.etree.ElementTree as ET
|
||||
import html
|
||||
from html.parser import HTMLParser
|
||||
import bs4 as bs
|
||||
|
||||
class MLStripper(HTMLParser):
|
||||
def __init__(self):
|
||||
@@ -16,9 +17,12 @@ class MLStripper(HTMLParser):
|
||||
return ''.join(self.fed)
|
||||
|
||||
def strip_tags(html_text):
|
||||
s = MLStripper()
|
||||
s.feed(html_text)
|
||||
return s.get_data()
|
||||
# use BeautifulSoup to strip HTML tags
|
||||
if not html_text:
|
||||
return ""
|
||||
soup = bs.BeautifulSoup(html_text, "html.parser")
|
||||
text = soup.get_text(separator=" ", strip=True)
|
||||
return ' '.join(text.split())
|
||||
|
||||
RSS_FEED_URLS = rssFeedURL
|
||||
RSS_FEED_NAMES = rssFeedNames
|
||||
|
||||
@@ -55,6 +55,10 @@ async def setup_scheduler(
|
||||
# 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 'link' in schedulerValue.lower():
|
||||
# Schedule to send a link message every specified interval
|
||||
schedule.every(int(schedulerInterval)).hours.do(lambda: send_message(handle_satpass(schedulerInterface, 'link'), schedulerChannel, 0, schedulerInterface))
|
||||
logger.debug(f"System: Starting the link scheduler to send link messages every {schedulerInterval} hours 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))
|
||||
|
||||
@@ -441,6 +441,6 @@ try:
|
||||
noisyTelemetryLimit = config['messagingSettings'].getint('noisyTelemetryLimit', 5) # default 5 packets
|
||||
except Exception as e:
|
||||
print(f"System: Error reading config file: {e}")
|
||||
print(f"System: Check the config.ini against config.template file for missing sections or values.")
|
||||
print(f"System: Exiting...")
|
||||
print("System: Check the config.ini against config.template file for missing sections or values.")
|
||||
print("System: Exiting...")
|
||||
exit(1)
|
||||
|
||||
@@ -98,7 +98,13 @@ class SurveyModule:
|
||||
return
|
||||
filename = os.path.join(self.response_dir, f'{survey_name}_responses.csv')
|
||||
try:
|
||||
# Check if file exists and if it has a header
|
||||
write_header = not os.path.isfile(filename) or os.path.getsize(filename) == 0
|
||||
with open(filename, 'a', encoding='utf-8') as f:
|
||||
# Write header if needed
|
||||
if write_header:
|
||||
header = ['timestamp', 'user_id', 'location'] + [f'Q{i+1}' for i in range(len(self.responses[user_id]['answers']))]
|
||||
f.write(','.join(header) + '\n')
|
||||
# Always write: timestamp, userID, position, answers...
|
||||
timestamp = datetime.now().strftime('%d%m%Y%H%M%S')
|
||||
user_id_str = str(user_id)
|
||||
|
||||
@@ -6,9 +6,8 @@ import wikipedia # pip install wikipedia
|
||||
# Kiwix support for local wiki
|
||||
if use_kiwix_server:
|
||||
import requests
|
||||
from bs4 import BeautifulSoup
|
||||
import bs4 as bs
|
||||
from urllib.parse import quote
|
||||
from bs4.element import Comment
|
||||
|
||||
# Kiwix helper functions (only loaded if use_kiwix_server is True)
|
||||
if wikipedia_enabled and use_kiwix_server:
|
||||
@@ -16,13 +15,13 @@ if wikipedia_enabled and use_kiwix_server:
|
||||
"""Filter visible text from HTML elements for Kiwix"""
|
||||
if element.parent.name in ['style', 'script', 'head', 'title', 'meta', '[document]']:
|
||||
return False
|
||||
if isinstance(element, Comment):
|
||||
if isinstance(element, bs.element.Comment):
|
||||
return False
|
||||
return True
|
||||
|
||||
def text_from_html(body):
|
||||
"""Extract visible text from HTML content"""
|
||||
soup = BeautifulSoup(body, 'html.parser')
|
||||
soup = bs.BeautifulSoup(body, 'html.parser')
|
||||
texts = soup.find_all(string=True)
|
||||
visible_texts = filter(tag_visible, texts)
|
||||
return " ".join(t.strip() for t in visible_texts if t.strip())
|
||||
|
||||
21
pong_bot.py
21
pong_bot.py
@@ -51,9 +51,6 @@ def auto_response(message, snr, rssi, hop, pkiStatus, message_from_id, channel_n
|
||||
logger.debug(f"System: Bot detected Commands:{cmds}")
|
||||
# run the first command after sorting
|
||||
bot_response = command_handler[cmds[0]['cmd']]()
|
||||
|
||||
# wait a responseDelay to avoid message collision from lora-ack
|
||||
time.sleep(responseDelay)
|
||||
|
||||
return bot_response
|
||||
|
||||
@@ -86,21 +83,21 @@ def handle_ping(message_from_id, deviceID, message, hop, snr, rssi, isDM, chann
|
||||
type = "✋ACK"
|
||||
elif "cqcq" in message.lower() or "cq" in message.lower() or "cqcqcq" in message.lower():
|
||||
if deviceID == 1:
|
||||
myname = get_name_from_number(myNodeNum1, 'short', 1)
|
||||
myname = get_name_from_number(deviceID, 'short', 1)
|
||||
elif deviceID == 2:
|
||||
myname = get_name_from_number(myNodeNum2, 'short', 2)
|
||||
myname = get_name_from_number(deviceID, 'short', 2)
|
||||
msg = f"QSP QSL OM DE {myname} K\n"
|
||||
else:
|
||||
msg = "🔊 Can you hear me now?"
|
||||
|
||||
# append SNR/RSSI or hop info
|
||||
if hop.startswith("Gateway") or hop.startswith("MQTT"):
|
||||
msg += f" [GW]"
|
||||
msg += " [GW]"
|
||||
elif hop.startswith("Direct"):
|
||||
msg += f" [RF]"
|
||||
msg += " [RF]"
|
||||
else:
|
||||
#flood
|
||||
msg += f" [F]"
|
||||
msg += " [F]"
|
||||
|
||||
if (float(snr) != 0 or float(rssi) != 0) and "Hops" not in hop:
|
||||
msg += f"\nSNR:{snr} RSSI:{rssi}"
|
||||
@@ -317,8 +314,8 @@ def onReceive(packet, interface):
|
||||
transport_mechanism = packet['decoded'].get('transport_mechanism', 'unknown')
|
||||
|
||||
# check if the packet is from us
|
||||
if message_from_id == myNodeNum1 or message_from_id == myNodeNum2:
|
||||
logger.warning(f"System: Packet from self {message_from_id} loop or traffic replay deteted")
|
||||
if message_from_id in [myNodeNum1, myNodeNum2, myNodeNum3, myNodeNum4, myNodeNum5, myNodeNum6, myNodeNum7, myNodeNum8, myNodeNum9]:
|
||||
logger.warning(f"System: Packet from self {message_from_id} loop or traffic replay detected")
|
||||
|
||||
# get the signal strength and snr if available
|
||||
if packet.get('rxSnr') or packet.get('rxRssi'):
|
||||
@@ -389,7 +386,7 @@ def onReceive(packet, interface):
|
||||
return
|
||||
|
||||
# If the packet is a DM (Direct Message) respond to it, otherwise validate its a message for us on the channel
|
||||
if packet['to'] == myNodeNum1 or packet['to'] == myNodeNum2:
|
||||
if packet['to'] in [myNodeNum1, myNodeNum2, myNodeNum3, myNodeNum4, myNodeNum5, myNodeNum6, myNodeNum7, myNodeNum8, myNodeNum9]:
|
||||
# message is DM to us
|
||||
isDM = True
|
||||
# check if the message contains a trap word, DMs are always responded to
|
||||
@@ -470,7 +467,7 @@ def onReceive(packet, interface):
|
||||
logger.debug(f"System: Error Packet = {packet}")
|
||||
|
||||
async def start_rx():
|
||||
print (CustomFormatter.bold_white + f"\nMeshtastic Autoresponder Bot CTL+C to exit\n" + CustomFormatter.reset)
|
||||
print (CustomFormatter.bold_white + "\nMeshtastic Autoresponder Bot CTL+C to exit\n" + CustomFormatter.reset)
|
||||
# Start the receive subscriber using pubsub via meshtastic library
|
||||
pub.subscribe(onReceive, 'meshtastic.receive')
|
||||
pub.subscribe(onDisconnect, 'meshtastic.connection.lost')
|
||||
|
||||
@@ -1,52 +1,51 @@
|
||||
# OLD Docker Compose configuration for Meshing Around application with optional services.
|
||||
# This setup includes the main Meshing Around service, with optional Ollama and Prometheus Node Exporter services.
|
||||
# Adjust device mappings, ports, and configurations as needed for your environment.
|
||||
|
||||
configs:
|
||||
me_config:
|
||||
file: ./config.ini # Path to the configuration file for Meshing Around.
|
||||
# Windows users may need to adjust the path format, e.g., C:/path/to/config.ini
|
||||
|
||||
services:
|
||||
meshing-around:
|
||||
build:
|
||||
context: ../..
|
||||
depends_on:
|
||||
ollama:
|
||||
condition: service_healthy
|
||||
devices:
|
||||
- /dev/ttyAMA10 # Replace this with your actual device!
|
||||
configs:
|
||||
- source: me_config
|
||||
target: /app/config.ini
|
||||
devices: # Optional if using meshtasticd. Pass through radio device.
|
||||
- /dev/ttyAMA10 # Replace this with your actual device!
|
||||
#- /dev/ttyUSB0 # Example for USB device
|
||||
user: 1000:1000 # run as non-root user for better security.
|
||||
extra_hosts:
|
||||
- "host.docker.internal:host-gateway" # Used to access a local linux meshtasticd device via tcp
|
||||
ollama:
|
||||
image: ollama/ollama:0.5.1
|
||||
volumes:
|
||||
- ./ollama:/root/.ollama
|
||||
- ./ollama-entrypoint.sh:./entrypoint.sh
|
||||
container_name: ollama
|
||||
pull_policy: always
|
||||
tty: true
|
||||
restart: always
|
||||
entrypoint:
|
||||
- /usr/bin/bash
|
||||
- /script/docker/entrypoint.sh
|
||||
expose:
|
||||
- 11434
|
||||
healthcheck:
|
||||
test: "apt update && apt install curl -y && curl -f http://localhost:11434/api/tags | grep -q llama3.2:3b"
|
||||
interval: 30s
|
||||
timeout: 10s
|
||||
retries: 20
|
||||
node-exporter:
|
||||
image: quay.io/prometheus/node-exporter:latest
|
||||
volumes:
|
||||
- /proc:/host/proc:ro
|
||||
- /sys:/host/sys:ro
|
||||
- /:/rootfs:ro
|
||||
command:
|
||||
- --path.procfs=/host/proc
|
||||
- --path.rootfs=/rootfs
|
||||
- --path.sysfs=/host/sys
|
||||
- --collector.filesystem.mount-points-exclude=^/(sys|proc|dev|host|etc)($$|/)
|
||||
# - "host.docker.internal:host-gateway" # Enables access to host services from within the container.
|
||||
container_name: meshing-around
|
||||
restart: unless-stopped
|
||||
expose:
|
||||
- 9100
|
||||
network_mode: host
|
||||
pid: host
|
||||
configs:
|
||||
me_config:
|
||||
file: ./config.ini
|
||||
#tty: true # Enable only if interactive terminal is needed.
|
||||
ports:
|
||||
#- "8420:8420" # web report interface
|
||||
#- "443:443" # HTTPS interface meshtasticD
|
||||
environment:
|
||||
- OLLAMA_API_URL=http://ollama:11434
|
||||
|
||||
# Uncomment the following service if you want to enable Ollama for local LLM API access.
|
||||
# ollama:
|
||||
# image: ollama/ollama:0.5.1
|
||||
# volumes:
|
||||
# - ./ollama:/root/.ollama
|
||||
# - ./ollama-entrypoint.sh:./entrypoint.sh
|
||||
# container_name: ollama
|
||||
# pull_policy: always
|
||||
# tty: true
|
||||
# restart: always
|
||||
# entrypoint:
|
||||
# - /usr/bin/bash
|
||||
# - /script/docker/entrypoint.sh
|
||||
# ports:
|
||||
# - "11434:11434"
|
||||
# healthcheck:
|
||||
# test: "curl -f http://localhost:11434/api/tags | grep -q llama3.2:3b"
|
||||
# interval: 30s
|
||||
# timeout: 10s
|
||||
# retries: 20
|
||||
@@ -8,8 +8,8 @@ pid=$!
|
||||
# Pause for Ollama to start.
|
||||
sleep 5
|
||||
|
||||
echo "🔴 Retrieve llama3.2:3b model..."
|
||||
ollama pull llama3.2:3b
|
||||
echo "🔴 Retrieve gemma3:270m model..."
|
||||
ollama pull gemma3:270m
|
||||
echo "🟢 Done!"
|
||||
|
||||
# Wait for Ollama process to finish.
|
||||
|
||||
@@ -46,7 +46,7 @@ fi
|
||||
|
||||
# copy modules/custom_scheduler.py template if it does not exist
|
||||
if [[ ! -f modules/custom_scheduler.py ]]; then
|
||||
cp etc/custom_scheduler.py modules/custom_scheduler.py
|
||||
cp -n etc/custom_scheduler.py modules/
|
||||
printf "\nCustom scheduler template copied to modules/custom_scheduler.py\n"
|
||||
fi
|
||||
|
||||
@@ -56,7 +56,7 @@ echo "Backing up data/ directory..."
|
||||
backup_file="data_backup.tar.gz"
|
||||
path2backup="data/"
|
||||
#copy custom_scheduler.py if it exists
|
||||
if [ -f "modules/custom_scheduler.py" ]; then
|
||||
if [[ -f "modules/custom_scheduler.py" ]]; then
|
||||
echo "Including custom_scheduler.py in backup..."
|
||||
cp modules/custom_scheduler.py data/
|
||||
fi
|
||||
@@ -71,8 +71,7 @@ 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
|
||||
|
||||
if [ -f ini_merge_log.txt ]; then
|
||||
if [[ -f ini_merge_log.txt ]]; then
|
||||
if grep -q "Error during configuration merge" ini_merge_log.txt; then
|
||||
echo "Configuration merge encountered errors. Please check ini_merge_log.txt for details."
|
||||
else
|
||||
@@ -83,7 +82,7 @@ else
|
||||
fi
|
||||
|
||||
# if service was stopped earlier, restart it
|
||||
if [ "$service_stopped" = true ]; then
|
||||
if [[ "$service_stopped" = true ]]; then
|
||||
echo "Restarting services..."
|
||||
systemctl start mesh_bot.service
|
||||
systemctl start pong_bot.service
|
||||
|
||||
Reference in New Issue
Block a user