mirror of
https://github.com/SpudGunMan/meshing-around.git
synced 2026-03-28 17:32:36 +01:00
Compare commits
101 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
47e21dbaab | ||
|
|
267f50c591 | ||
|
|
0013a7bb74 | ||
|
|
73fe8be432 | ||
|
|
3d45195ae9 | ||
|
|
ff390cf470 | ||
|
|
17d8cd1067 | ||
|
|
b9348c906d | ||
|
|
6ba3508cc5 | ||
|
|
1c78f154da | ||
|
|
e0a3d0f94e | ||
|
|
066211e9f2 | ||
|
|
5701cd108b | ||
|
|
b877a294ac | ||
|
|
2aedcfc46e | ||
|
|
12147db5d0 | ||
|
|
cef37b574b | ||
|
|
6f121b7aac | ||
|
|
9e31b7f47e | ||
|
|
f3103984ef | ||
|
|
9c8b3f0a54 | ||
|
|
f88cbf210e | ||
|
|
9909113beb | ||
|
|
c1b783b1cd | ||
|
|
9b3b6a5d3d | ||
|
|
cffdb3c089 | ||
|
|
7bb9c9ac55 | ||
|
|
830ec95080 | ||
|
|
0ea575ac70 | ||
|
|
d836255716 | ||
|
|
4f115c9c21 | ||
|
|
63bd5b836d | ||
|
|
5ad9b9a261 | ||
|
|
7a024b681f | ||
|
|
75df5a695b | ||
|
|
0ef8cffd56 | ||
|
|
73e8e063d2 | ||
|
|
82880677f4 | ||
|
|
fe8ba8aaf4 | ||
|
|
cea9147745 | ||
|
|
c1c68d4c10 | ||
|
|
5fcd21680e | ||
|
|
9e1356172f | ||
|
|
de7fdfad11 | ||
|
|
a87055874a | ||
|
|
5c7433091d | ||
|
|
f0ca818461 | ||
|
|
76006dcda7 | ||
|
|
33abe646ae | ||
|
|
c47004c47c | ||
|
|
e66d945be7 | ||
|
|
10afc128f4 | ||
|
|
e6fc794951 | ||
|
|
4839e9ba03 | ||
|
|
bde15e311a | ||
|
|
21c83222e9 | ||
|
|
bbcdd6656a | ||
|
|
7f61b86252 | ||
|
|
25ae27a162 | ||
|
|
a04133e82f | ||
|
|
2a9dfc90ee | ||
|
|
f1bf84f6f0 | ||
|
|
4b91ef10b4 | ||
|
|
cd4497b129 | ||
|
|
01374a8307 | ||
|
|
46c115b783 | ||
|
|
eec7230a84 | ||
|
|
9394fd6ca9 | ||
|
|
c6653da1f3 | ||
|
|
9f47958a03 | ||
|
|
78e51b7be1 | ||
|
|
26fcf6fc02 | ||
|
|
c2336850fe | ||
|
|
54e0d17e70 | ||
|
|
7a6d1f7b29 | ||
|
|
7e26d3f0e5 | ||
|
|
89be8e13a2 | ||
|
|
aa8482ab52 | ||
|
|
69605e0984 | ||
|
|
8e15a3fc99 | ||
|
|
d671b19bce | ||
|
|
943dd4d5a3 | ||
|
|
05d8671b3f | ||
|
|
4bccd33827 | ||
|
|
71ebe7087f | ||
|
|
8dbffe2e63 | ||
|
|
cbea9b5294 | ||
|
|
acdc94cd06 | ||
|
|
e9deb62047 | ||
|
|
f1ad470f88 | ||
|
|
b19f7be0b0 | ||
|
|
053acd1ac6 | ||
|
|
3d5b671d81 | ||
|
|
f090230c96 | ||
|
|
d9040a4ec7 | ||
|
|
e35c954e5d | ||
|
|
93ed84fdee | ||
|
|
9f074e5250 | ||
|
|
12d94fb0dc | ||
|
|
afa2bc4024 | ||
|
|
5079c67f62 |
@@ -1 +1,2 @@
|
||||
currently operating under "Agile software development" aka rolling code; no major structure. meshing about .. get it..
|
||||
currently operating under "Agile software development" aka rolling code; no major structure. meshing about .. get it..
|
||||
there is some ideas for adding code in modules/README.md
|
||||
56
README.md
56
README.md
@@ -11,13 +11,14 @@ Welcome to the Mesh Bot project! This feature-rich bot is designed to enhance yo
|
||||
- **Automated Responses**: The bot detects keywords like "ping" and responds with "pong" in direct messages (DMs) or group channels.
|
||||
- **Customizable Triggers**: Monitor group channels for specific keywords and set custom responses.
|
||||
- **Emergency Response**: Monitor channels for keywords indicating emergencies and alert a wider audience.
|
||||
- **New Node Hello**: Greet new nodes on the mesh with a hello message
|
||||
|
||||
### Network Tools
|
||||
- **Build, Test Local Mesh**: Ping allow for message delivery testing with more realistic packets vs. telemetry
|
||||
- **Test Node Hardware**: `test` will send incremental sized data into the radio buffer for overall length of message testing
|
||||
|
||||
### Dual Radio/Node Support
|
||||
- **Simultaneous Monitoring**: Monitor two networks at the same time.
|
||||
### Multi Radio/Node Support
|
||||
- **Simultaneous Monitoring**: Monitor up to nine networks at the same time.
|
||||
- **Flexible Messaging**: send mail and messages, between networks.
|
||||
|
||||
### Advanced Messaging Capabilities
|
||||
@@ -26,7 +27,8 @@ Welcome to the Mesh Bot project! This feature-rich bot is designed to enhance yo
|
||||
- **Store and Forward**: Replay messages with the `messages` command, and log messages locally to disk.
|
||||
- **Send Mail**: Send mail to nodes using `bbspost @nodeNumber #message` or `bbspost @nodeShortName #message`.
|
||||
- **BBS Linking**: Combine multiple bots to expand BBS reach.
|
||||
- **E-Mail/SMS**: Send mesh-messages to E-Mail or SMS expanding visability.
|
||||
- **E-Mail/SMS**: Send mesh-messages to E-Mail or SMS(Email) expanding visability.
|
||||
- **New Node Hello**: Send a hello to any new node seen in text message.
|
||||
|
||||
### Interactive AI and Data Lookup
|
||||
- **NOAA location Data**: Get localized weather(alerts), River Flow, and Tide information. Open-Meteo is used for wx only outside NOAA coverage.
|
||||
@@ -37,6 +39,9 @@ Welcome to the Mesh Bot project! This feature-rich bot is designed to enhance yo
|
||||
### Proximity Alerts
|
||||
- **Location-Based Alerts**: Get notified when members arrive back at a configured lat/long, perfect for remote locations like campsites.
|
||||
|
||||
### CheckList / Check In Out
|
||||
- **Asset Tracking**: Maintain a list of node/asset checkin and checkout. Usefull for accountability of people, assets. Radio-Net, FEMA, Trailhead.
|
||||
|
||||
### Fun and Games
|
||||
- **Built-in Games**: Enjoy games like DopeWars, Lemonade Stand, BlackJack, and VideoPoker.
|
||||
- **Command-Based Gameplay**: Issue `games` to display help and start playing.
|
||||
@@ -49,7 +54,7 @@ Welcome to the Mesh Bot project! This feature-rich bot is designed to enhance yo
|
||||
- **FEMA iPAWS/EAS Alerts via API**: Use an internet-connected node to message Emergency Alerts from FEMA
|
||||
- **NOAA EAS Alerts via API**: Use an internet-connected node to message Emergency Alerts from NOAA.
|
||||
- **EAS Alerts over the air**: Utilizing external tools to report EAS alerts offline over mesh.
|
||||
- **UK.GOV Alerts**: Pulling data form the UK.GOV alert page
|
||||
- **NINA alerts for Germany**: Emergency Alerts from xrepository.de feed
|
||||
|
||||
### File Monitor Alerts
|
||||
- **File Monitor**: Monitor a flat/text file for changes, broadcast the contents of the message to the mesh channel.
|
||||
@@ -62,7 +67,7 @@ Welcome to the Mesh Bot project! This feature-rich bot is designed to enhance yo
|
||||
- **Message Chunking**: Automatically chunk messages over 160 characters to ensure higher delivery success across hops.
|
||||
|
||||
## Getting Started
|
||||
This project is developed on Linux (specifically a Raspberry Pi) but should work on any platform where the [Meshtastic protobuf API](https://meshtastic.org/docs/software/python/cli/) modules are supported, and with any compatible [Meshtastic](https://meshtastic.org/docs/getting-started/) hardware. For pico or low-powered devices, see projects for embedding, [buildroot](https://github.com/buildroot-meshtastic/buildroot-meshtastic), there is also [femtofox](https://github.com/noon92/femtofox). 🥔 Please use responsibly and follow local rulings for such equipment. This project captures packets, logs them, and handles over the air communications which can include PII such as GPS locations.
|
||||
This project is developed on Linux (specifically a Raspberry Pi) but should work on any platform where the [Meshtastic protobuf API](https://meshtastic.org/docs/software/python/cli/) modules are supported, and with any compatible [Meshtastic](https://meshtastic.org/docs/getting-started/) hardware. For pico or low-powered devices, see projects for embedding, [buildroot](https://github.com/buildroot-meshtastic/buildroot-meshtastic), also see [femtofox](https://github.com/noon92/femtofox). 🥔 Please use responsibly and follow local rulings for such equipment. This project captures packets, logs them, and handles over the air communications which can include PII such as GPS locations.
|
||||
|
||||
### Installation
|
||||
|
||||
@@ -73,14 +78,14 @@ git clone https://github.com/spudgunman/meshing-around
|
||||
```
|
||||
The code is under active development, so make sure to pull the latest changes regularly!
|
||||
|
||||
#### Automation of setup
|
||||
#### Quick setup
|
||||
- **Automated Installation**: `install.sh` will automate optional venv and requirements installation.
|
||||
- **Launch Script**: `launch.sh` will activate and launch the app in the venv
|
||||
- **Launch Script**: `launch.sh` only used in a venv install, to launch the bot and the report generator.
|
||||
|
||||
#### Docker Installation
|
||||
If you prefer to use [Docker](script/docker/README.md)
|
||||
See further info on the [docker.md](script/docker/README.md)
|
||||
|
||||
#### Custom Install
|
||||
#### Manual Install
|
||||
Install the required dependencies using pip:
|
||||
```sh
|
||||
pip install -r requirements.txt
|
||||
@@ -91,8 +96,10 @@ Copy the configuration template to `config.ini` and edit it to suit your needs:
|
||||
cp config.template config.ini
|
||||
```
|
||||
|
||||
### Configuration
|
||||
Copy the [config.template](config.template) to `config.ini` and set the appropriate interface for your method (serial/ble/tcp). While BLE and TCP will work, they are not as reliable as serial connections. There is a watchdog to reconnect TCP if possible. To get the BLE MAC address, use:
|
||||
### Configuration Guide
|
||||
The following is documentation for the config.ini file
|
||||
|
||||
If you have not done so, or want to 'factory reset', copy the [config.template](config.template) to `config.ini` and set the appropriate interface for your method (serial/ble/tcp). While BLE and TCP will work, they are not as reliable as serial connections. There is a watchdog to reconnect TCP if possible. To get the BLE MAC address, use:
|
||||
```sh
|
||||
meshtastic --ble-scan
|
||||
```
|
||||
@@ -200,8 +207,8 @@ alert_interface = 1
|
||||
### EAS Alerting
|
||||
To Alert on Mesh with the EAS API you can set the channels and enable, checks every 20min.
|
||||
|
||||
#### FEMA iPAWS/EAS and UK.gov
|
||||
This uses USA: SAME, FIPS, ZIP code to locate the alerts in the feed. By default ignoring Test messages. UK.gov for England
|
||||
#### FEMA iPAWS/EAS and NINA
|
||||
This uses USA: SAME, FIPS, ZIP code to locate the alerts in the feed. By default ignoring Test messages.
|
||||
|
||||
```ini
|
||||
eAlertBroadcastEnabled = False # Goverment IPAWS/CAP Alert Broadcast
|
||||
@@ -210,7 +217,10 @@ ignoreFEMAtest = True # Ignore any headline that includes the word Test
|
||||
# comma separated list of codes (e.g., SAME,FIPS,ZIP) trigger local alert.
|
||||
# find your SAME https://www.weather.gov/nwr/counties
|
||||
mySAME = 053029,053073
|
||||
enableGBalerts = False # use UK.gov for alert source
|
||||
|
||||
# To use other country services enable only a single optional serivce
|
||||
|
||||
enableDEalerts = False # Use DE Alert Broadcast Data see template for filters
|
||||
```
|
||||
|
||||
#### NOAA EAS
|
||||
@@ -305,7 +315,14 @@ rtl_fm -f 162425000 -s 22050 | multimon-ng -t raw -a EAS /dev/stdin | python eas
|
||||
#### Newspaper on mesh
|
||||
a newspaper could be built by external scripts. could use Ollama to compile text via news web pages and write news.txt
|
||||
|
||||
you can also enable the line by line (hint just search for the commented lines with a 🐝) to return a string from the [bee movie](https://courses.cs.washington.edu/courses/cse163/20wi/files/lectures/L04/bee-movie.txt) for example adding it alongside news.txt as bee.txt
|
||||
### Greet new nodes QRZ module
|
||||
This isnt QRZ.com this is Q code for who is calling me, this will track new nodes and say hello
|
||||
```ini
|
||||
[qrz]
|
||||
enabled = True # QRZ Hello to new nodes
|
||||
qrz_hello_string = "send CMD or DM me for more info." # will be sent to all heard nodes once
|
||||
training = True # Training mode will not send the hello message to new nodes, use this to build up database
|
||||
```
|
||||
|
||||
### Scheduler
|
||||
In the config.ini enable the module
|
||||
@@ -359,7 +376,7 @@ There is no direct support for MQTT in the code, however, reports from Discord a
|
||||
### Radio Propagation & Weather Forcasting
|
||||
| Command | Description | |
|
||||
|---------|-------------|-------------------
|
||||
| `ea` and `ealert` | Return FEMA iPAWS/EAS alerts in USA or UK. Headline or expanded details for USA | |
|
||||
| `ea` and `ealert` | Return FEMA iPAWS/EAS alerts in USA or DE Headline or expanded details for USA | |
|
||||
| `hfcond` | Returns a table of HF solar conditions | |
|
||||
| `rlist` | Returns a table of nearby repeaters from RepeaterBook | |
|
||||
| `riverflow` | Return information from NOAA for river flow info. Example: `riverflow modules/settings.py`| |
|
||||
@@ -394,6 +411,13 @@ There is no direct support for MQTT in the code, however, reports from Discord a
|
||||
| `satpass` | returns the pass info from API for defined NORAD ID in config or Example: `satpass 25544,33591`| |
|
||||
| `wiki:` | Searches Wikipedia and returns the first few sentences of the first result if a match. Example: `wiki: lora radio` |
|
||||
|
||||
### CheckList
|
||||
| Command | Description | |
|
||||
|---------|-------------|-
|
||||
| `checkin` | Check in the node to the checklist database, you can add a note like `checkin ICO` or `checkin radio4` | ✅ |
|
||||
| `checkout` | Checkout the node in the checklist database, checkout all from node | ✅ |
|
||||
| `checklist` | Display the checklist database, with note | ✅ |
|
||||
|
||||
### Games (via DM)
|
||||
| Command | Description | |
|
||||
|---------|-------------|-
|
||||
|
||||
@@ -123,8 +123,8 @@ useMetric = False
|
||||
# repeaterList lookup location (rbook / artsci)
|
||||
repeaterLookup = rbook
|
||||
|
||||
# NOAA weather forecast days, the first two rows are today and tonight
|
||||
NOAAforecastDuration = 4
|
||||
# NOAA weather forecast days
|
||||
NOAAforecastDuration = 3
|
||||
# number of weather alerts to display
|
||||
NOAAalertCount = 2
|
||||
|
||||
@@ -134,23 +134,30 @@ UseMeteoWxAPI = False
|
||||
# NOAA Hydrology unique identifiers, LID or USGS ID
|
||||
riverListDefault =
|
||||
|
||||
# EAS Alert Broadcast
|
||||
# NOAA EAS Alert Broadcast
|
||||
wxAlertBroadcastEnabled = False
|
||||
# EAS Alert Broadcast Channels
|
||||
wxAlertBroadcastCh = 2
|
||||
# Add extra location to the weather alert
|
||||
enableExtraLocationWx = False
|
||||
|
||||
# Goverment IPAWS/CAP Alert Broadcast
|
||||
# Goverment Alert Broadcast defaults to FEMA IPAWS
|
||||
eAlertBroadcastEnabled = False
|
||||
# Goverment Emergency IPAWS/CAP Alert Broadcast Channels
|
||||
# Goverment Alert Broadcast Channels
|
||||
eAlertBroadcastCh = 2
|
||||
|
||||
# FEMA Alert Broadcast Settings
|
||||
# Ignore any headline that includes the word Test
|
||||
ignoreFEMAtest = True
|
||||
# comma separated list of codes (e.g., SAME,FIPS,ZIP) trigger local alert.
|
||||
# find your SAME https://www.weather.gov/nwr/counties
|
||||
mySAME = 053029,053073
|
||||
enableGBalerts = False
|
||||
|
||||
# Use DE Alert Broadcast Data
|
||||
enableDEalerts = False
|
||||
# comma separated list of regional codes trigger local alert.
|
||||
# find your regional codet at https://www.xrepository.de/api/xrepository/urn:de:bund:destatis:bevoelkerungsstatistik:schluessel:rs_2021-07-31/download/Regionalschl_ssel_2021-07-31.json
|
||||
myRegionalKeysDE = 110000000000,120510000000
|
||||
|
||||
# Satalite Pass Prediction
|
||||
# Register for free API https://www.n2yo.com/login/
|
||||
@@ -158,6 +165,20 @@ n2yoAPIKey =
|
||||
# NORAD list https://www.n2yo.com/satellites/
|
||||
satList = 25544,7530
|
||||
|
||||
# CheckList Checkin/Checkout
|
||||
[checklist]
|
||||
enabled = False
|
||||
checklist_db = data/checklist.db
|
||||
reverse_in_out = False
|
||||
|
||||
[qrz]
|
||||
# QRZ Hello to new nodes with message
|
||||
enabled = False
|
||||
qrz_db = data/qrz.db
|
||||
qrz_hello_string = "send CMD or DM me for more info."
|
||||
# Training mode will not send the hello message to new nodes
|
||||
training = True
|
||||
|
||||
# repeater module
|
||||
[repeater]
|
||||
enabled = False
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
# /etc/systemd/system/mesh_bot.service
|
||||
# sudo systemctl daemon-reload
|
||||
# sudo systemctl enable mesh_bot.service
|
||||
# sudo systemctl start mesh_bot.service
|
||||
|
||||
[Unit]
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
# /etc/systemd/system/mesh_bot.service
|
||||
# /etc/systemd/system/mesh_bot_reporting.service
|
||||
# sudo systemctl daemon-reload
|
||||
# sudo systemctl start mesh_bot.service
|
||||
# sudo systemctl enable mesh_bot_reporting.service
|
||||
# sudo systemctl start mesh_bot_reporting.service
|
||||
|
||||
[Unit]
|
||||
Description=MeshingAround-Reporting
|
||||
|
||||
23
etc/mesh_bot_w3.tmp
Normal file
23
etc/mesh_bot_w3.tmp
Normal file
@@ -0,0 +1,23 @@
|
||||
# /etc/systemd/system/mesh_bot_w3.service
|
||||
# sudo systemctl daemon-reload
|
||||
# sudo systemctl enable mesh_bot_w3.service
|
||||
# sudo systemctl start mesh_bot_w3.service
|
||||
|
||||
[Unit]
|
||||
Description=MeshingAround-W3Server
|
||||
After=network.target
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
User=pi
|
||||
Group=pi
|
||||
WorkingDirectory=/dir/
|
||||
ExecStart=python3 modules/web.py
|
||||
ExecStop=pkill -f mesh_bot_w3.py
|
||||
|
||||
# Disable Python's buffering of STDOUT and STDERR, so that output from the
|
||||
# service shows up immediately in systemd's logs
|
||||
Environment=PYTHONUNBUFFERED=1
|
||||
|
||||
Restart=on-failure
|
||||
Type=notify #try simple if any problems
|
||||
@@ -1,5 +1,6 @@
|
||||
# /etc/systemd/system/pong_bot.service
|
||||
# sudo systemctl daemon-reload
|
||||
# sudo systemctl enable pong_bot.service
|
||||
# sudo systemctl start pong_bot.service
|
||||
|
||||
[Unit]
|
||||
|
||||
42
install.sh
42
install.sh
@@ -80,6 +80,7 @@ sudo usermod -a -G bluetooth $USER
|
||||
cp etc/pong_bot.tmp etc/pong_bot.service
|
||||
cp etc/mesh_bot.tmp etc/mesh_bot.service
|
||||
cp etc/mesh_bot_reporting.tmp etc/mesh_bot_reporting.service
|
||||
cp etc/mesh_bot_w3.tmp etc/mesh_bot_w3.service
|
||||
|
||||
# generate config file, check if it exists
|
||||
if [[ -f config.ini ]]; then
|
||||
@@ -158,10 +159,10 @@ else
|
||||
fi
|
||||
|
||||
# if $1 is passed
|
||||
if [[ $1 == "mesh" ]]; then
|
||||
bot="mesh"
|
||||
elif [[ $1 == "pong" ]]; then
|
||||
if [[ $1 == "pong" ]]; then
|
||||
bot="pong"
|
||||
elif [[ $1 == "mesh" ]] || [[ $(echo "${embedded}" | grep -i "^y") ]]; then
|
||||
bot="mesh"
|
||||
else
|
||||
printf "\n\n"
|
||||
echo "Which bot do you want to install as a service? Pong Mesh or None? (pong/mesh/n)"
|
||||
@@ -176,6 +177,7 @@ 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
|
||||
# set the correct user in the service file?
|
||||
|
||||
#ask if we should add a user for the bot
|
||||
@@ -207,10 +209,12 @@ 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
|
||||
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
|
||||
printf "\n service files updated\n"
|
||||
|
||||
if [[ $(echo "${bot}" | grep -i "^p") ]]; then
|
||||
@@ -260,19 +264,18 @@ if [[ $(echo "${embedded}" | grep -i "^n") ]]; then
|
||||
fi
|
||||
fi
|
||||
|
||||
if [[ $(echo "${embedded}" | grep -i "^n") ]]; then
|
||||
# document the service install
|
||||
printf "To install the %s service and keep notes, reference following commands:\n\n" "$service" > install_notes.txt
|
||||
printf "sudo cp %s/etc/%s.service /etc/systemd/system/etc/%s.service\n" "$program_path" "$service" "$service" >> install_notes.txt
|
||||
printf "sudo systemctl daemon-reload\n" >> install_notes.txt
|
||||
printf "sudo systemctl enable %s.service\n" "$service" >> install_notes.txt
|
||||
printf "sudo systemctl start %s.service\n" "$service" >> install_notes.txt
|
||||
printf "sudo systemctl status %s.service\n\n" "$service" >> install_notes.txt
|
||||
printf "To see logs and stop the service:\n" >> install_notes.txt
|
||||
printf "sudo journalctl -u %s.service\n" "$service" >> install_notes.txt
|
||||
printf "sudo systemctl stop %s.service\n" "$service" >> install_notes.txt
|
||||
printf "sudo systemctl disable %s.service\n" "$service" >> install_notes.txt
|
||||
fi
|
||||
# document the service install
|
||||
printf "To install the %s service and keep notes, reference following commands:\n\n" "$service" > install_notes.txt
|
||||
printf "sudo cp %s/etc/%s.service /etc/systemd/system/etc/%s.service\n" "$program_path" "$service" "$service" >> install_notes.txt
|
||||
printf "sudo systemctl daemon-reload\n" >> install_notes.txt
|
||||
printf "sudo systemctl enable %s.service\n" "$service" >> install_notes.txt
|
||||
printf "sudo systemctl start %s.service\n" "$service" >> install_notes.txt
|
||||
printf "sudo systemctl status %s.service\n" "$service" >> install_notes.txt
|
||||
printf "sudo systemctl restart %s.service\n\n" "$service" >> install_notes.txt
|
||||
printf "To see logs and stop the service:\n" >> install_notes.txt
|
||||
printf "sudo journalctl -u %s.service\n" "$service" >> install_notes.txt
|
||||
printf "sudo systemctl stop %s.service\n" "$service" >> install_notes.txt
|
||||
printf "sudo systemctl disable %s.service\n" "$service" >> install_notes.txt
|
||||
|
||||
if [[ $(echo "${venv}" | grep -i "^y") ]]; then
|
||||
printf "\nFor running on venv, virtual launch bot with './launch.sh mesh' in path $program_path\n" >> install_notes.txt
|
||||
@@ -294,6 +297,8 @@ else
|
||||
replace="s|# hostname = meshtastic.local|hostname = localhost|g"
|
||||
sed -i "$replace" config.ini
|
||||
printf "\nConfig file updated for embedded\n"
|
||||
# add service dependency for meshtasticd into service file
|
||||
#replace="s|After=network.target|After=network.target meshtasticd.service|g"
|
||||
|
||||
# Set up the meshing around service
|
||||
sudo cp /opt/meshing-around/etc/$service.service /etc/systemd/system/$service.service
|
||||
@@ -301,7 +306,9 @@ else
|
||||
sudo systemctl enable $service.service
|
||||
sudo systemctl start $service.service
|
||||
printf "Reference following commands:\n\n" "$service" > install_notes.txt
|
||||
printf "sudo systemctl status %s.service\n\n" "$service" >> 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
|
||||
printf "To see logs and stop the service:\n" >> install_notes.txt
|
||||
printf "sudo journalctl -u %s.service\n" "$service" >> install_notes.txt
|
||||
printf "sudo systemctl stop %s.service\n" "$service" >> install_notes.txt
|
||||
@@ -322,6 +329,7 @@ exit 0
|
||||
# sudo systemctl disable mesh_bot_reporting
|
||||
# sudo rm /etc/systemd/system/mesh_bot.service
|
||||
# sudo rm /etc/systemd/system/mesh_bot_reporting.service
|
||||
# sudo rm /etc/systemd/system/mesh_bot_w3.service
|
||||
# sudo rm /etc/systemd/system/pong_bot.service
|
||||
# sudo systemctl daemon-reload
|
||||
# sudo systemctl reset-failed
|
||||
|
||||
@@ -1,14 +1,16 @@
|
||||
# Logs and Reports
|
||||
Logs will collect here. Give a day of logs or a bunch of messages to have good reports.
|
||||
|
||||
Reporting is via [../etc/report_generator5.py](../etc/report_generator5.py). The report_generator5 has newer feel and HTML5 coding. The index.html output is published in [../etc/www](../etc/www) there is a .cfg file created on first run for configuring values as needed.
|
||||
- `multi_log_reader = True` on by default will read all logs (or set to false to return daily logs)
|
||||
## Reporting Note
|
||||
Reporting is via [../etc/report_generator5.py](../etc/report_generator5.py). The report_generator5 has newer feel and HTML5 coding. The index.html output is published in [../etc/www](../etc/www) there is a .cfg file created on first run for configuring values as needed (like moving web root)
|
||||
- Make sure to have `SyslogToFile = True` and default of DEBUG log level to fully enable reporting! ‼️
|
||||
- provided serviceTimer templates in etc/
|
||||
- If you are in a venv and using launch.sh you can `launch.sh html5`
|
||||
|
||||

|
||||
|
||||
## Settings
|
||||
Logging messages to disk or 'Syslog' to disk uses the python native logging function.
|
||||
```
|
||||
```conf
|
||||
[general]
|
||||
# logging to file of the non Bot messages only
|
||||
LogMessagesToFile = False
|
||||
@@ -19,13 +21,7 @@ sysloglevel = DEBUG
|
||||
# Number of log files to keep in days, 0 to keep all
|
||||
log_backup_count = 32
|
||||
```
|
||||
## Web Reporting WebServer
|
||||
There is a web-server module. You can run `python3 modules/web.py` from the project root directory and it will serve up the web content.
|
||||
|
||||
To change the stdout (what you see on the console) logging level (default is DEBUG) see the following example, line is in [../modules/log.py](../modules/log.py)
|
||||
|
||||
```
|
||||
# Set level for stdout handler
|
||||
stdout_handler.setLevel(logging.INFO)
|
||||
```
|
||||
|
||||
There is a web-server module you can run `python modules/web.py` from the project root directory and it will serve up the web content.
|
||||
by default. http://localhost:8420
|
||||
find it at. http://localhost:8420
|
||||
101
mesh_bot.py
101
mesh_bot.py
@@ -42,8 +42,11 @@ def auto_response(message, snr, rssi, hop, pkiStatus, message_from_id, channel_n
|
||||
"bbspost": lambda: handle_bbspost(message, message_from_id, deviceID),
|
||||
"bbsread": lambda: handle_bbsread(message),
|
||||
"blackjack": lambda: handleBlackJack(message, message_from_id, deviceID),
|
||||
"checkin": lambda: handle_checklist(message, message_from_id, deviceID),
|
||||
"checklist": lambda: handle_checklist(message, message_from_id, deviceID),
|
||||
"checkout": lambda: handle_checklist(message, message_from_id, deviceID),
|
||||
"clearsms": lambda: handle_sms(message_from_id, message),
|
||||
"cmd": lambda: help_message,
|
||||
"cmd": lambda: handle_cmd(message, message_from_id, deviceID),
|
||||
"cq": lambda: handle_ping(message_from_id, deviceID, message, hop, snr, rssi, isDM, channel_number),
|
||||
"cqcq": lambda: handle_ping(message_from_id, deviceID, message, hop, snr, rssi, isDM, channel_number),
|
||||
"cqcqcq": lambda: handle_ping(message_from_id, deviceID, message, hop, snr, rssi, isDM, channel_number),
|
||||
@@ -137,6 +140,13 @@ def auto_response(message, snr, rssi, hop, pkiStatus, message_from_id, channel_n
|
||||
time.sleep(responseDelay)
|
||||
return bot_response
|
||||
|
||||
def handle_cmd(message, message_from_id, deviceID):
|
||||
# why CMD? its just a command list. a terminal would normally use "Help"
|
||||
# I didnt want to invoke the word "help" in Meshtastic due to its possible emergency use
|
||||
if " " in message and message.split(" ")[1] in trap_list:
|
||||
return "🤖 just use the commands directly in chat"
|
||||
return help_message
|
||||
|
||||
def handle_ping(message_from_id, deviceID, message, hop, snr, rssi, isDM, channel_number):
|
||||
global multiPing
|
||||
myNodeNum = globals().get(f'myNodeNum{deviceID}', 777)
|
||||
@@ -338,8 +348,9 @@ def handle_satpass(message_from_id, deviceID, channel_number, message):
|
||||
return passes
|
||||
|
||||
def handle_llm(message_from_id, channel_number, deviceID, message, publicChannel):
|
||||
global llmRunCounter, llmLocationTable, llmTotalRuntime, cmdHistory
|
||||
global llmRunCounter, llmLocationTable, llmTotalRuntime, cmdHistory, seenNodes
|
||||
location_name = 'no location provided'
|
||||
msg = ''
|
||||
|
||||
if location_enabled:
|
||||
# if message_from_id is is the llmLocationTable use the location from the list to save on API calls
|
||||
@@ -365,17 +376,20 @@ def handle_llm(message_from_id, channel_number, deviceID, message, publicChannel
|
||||
# consider this a command use for the cmdHistory list
|
||||
cmdHistory.append({'nodeID': message_from_id, 'cmd': 'llm-use', 'time': time.time()})
|
||||
|
||||
# if the message_from_id is not in the llmLocationTable send the welcome message
|
||||
for i in range(0, len(llmLocationTable)):
|
||||
if not any(d['nodeID'] == message_from_id for d in llmLocationTable):
|
||||
if (channel_number == publicChannel and antiSpam) or useDMForResponse:
|
||||
# send via DM
|
||||
send_message(welcome_message, channel_number, message_from_id, deviceID)
|
||||
time.sleep(responseDelay)
|
||||
else:
|
||||
# send via channel
|
||||
send_message(welcome_message, channel_number, 0, deviceID)
|
||||
time.sleep(responseDelay)
|
||||
# check for a welcome message (is this redundant?)
|
||||
if not any(node['nodeID'] == message_from_id and node['welcome'] == True for node in seenNodes):
|
||||
if (channel_number == publicChannel and antiSpam) or useDMForResponse:
|
||||
# send via DM
|
||||
send_message(welcome_message, channel_number, message_from_id, deviceID)
|
||||
time.sleep(responseDelay)
|
||||
else:
|
||||
# send via channel
|
||||
send_message(welcome_message, channel_number, 0, deviceID)
|
||||
time.sleep(responseDelay)
|
||||
# mark the node as welcomed
|
||||
for node in seenNodes:
|
||||
if node['nodeID'] == message_from_id:
|
||||
node['welcome'] = True
|
||||
|
||||
# update the llmLocationTable for future use
|
||||
for i in range(0, len(llmLocationTable)):
|
||||
@@ -394,26 +408,18 @@ def handle_llm(message_from_id, channel_number, deviceID, message, publicChannel
|
||||
# information for the user on how long the query will take on average
|
||||
if llmRunCounter > 0:
|
||||
averageRuntime = sum(llmTotalRuntime) / len(llmTotalRuntime)
|
||||
if averageRuntime > 25:
|
||||
msg = f"Please wait, average query time is: {int(averageRuntime)} seconds"
|
||||
if (channel_number == publicChannel and antiSpam) or useDMForResponse:
|
||||
# send via DM
|
||||
send_message(msg, channel_number, message_from_id, deviceID)
|
||||
time.sleep(responseDelay)
|
||||
else:
|
||||
# send via channel
|
||||
send_message(msg, channel_number, 0, deviceID)
|
||||
time.sleep(responseDelay)
|
||||
msg = f"Average query time is: {int(averageRuntime)} seconds" if averageRuntime > 25 else ''
|
||||
else:
|
||||
msg = "Please wait, response could take 30+ seconds. Fund the SysOp's GPU budget!"
|
||||
|
||||
if msg != '':
|
||||
if (channel_number == publicChannel and antiSpam) or useDMForResponse:
|
||||
# send via DM
|
||||
send_message(msg, channel_number, message_from_id, deviceID)
|
||||
time.sleep(responseDelay)
|
||||
else:
|
||||
# send via channel
|
||||
send_message(msg, channel_number, 0, deviceID)
|
||||
time.sleep(responseDelay)
|
||||
time.sleep(responseDelay)
|
||||
|
||||
start = time.time()
|
||||
|
||||
@@ -694,15 +700,20 @@ def handle_wxc(message_from_id, deviceID, cmd):
|
||||
|
||||
def handle_emergency_alerts(message, message_from_id, deviceID):
|
||||
location = get_node_location(message_from_id, deviceID)
|
||||
if enableGBalerts:
|
||||
# UK Alerts
|
||||
return get_govUK_alerts(str(location[0]), str(location[1]))
|
||||
if enableDEalerts:
|
||||
# nina Alerts
|
||||
return get_nina_alerts()
|
||||
if message.lower().startswith("ealert"):
|
||||
# Detailed alert FEMA
|
||||
return getIpawsAlert(str(location[0]), str(location[1]))
|
||||
else:
|
||||
# Headlines only FEMA
|
||||
return getIpawsAlert(str(location[0]), str(location[1]), shortAlerts=True)
|
||||
|
||||
def handle_checklist(message, message_from_id, deviceID):
|
||||
name = get_name_from_number(message_from_id, 'short', deviceID)
|
||||
location = get_node_location(message_from_id, deviceID)
|
||||
return process_checklist_command(message_from_id, message, name, location)
|
||||
|
||||
def handle_bbspost(message, message_from_id, deviceID):
|
||||
if "$" in message and not "example:" in message:
|
||||
@@ -965,13 +976,14 @@ def checkPlayingGame(message_from_id, message_string, rxNode, channel_number):
|
||||
game = "None"
|
||||
|
||||
trackers = [
|
||||
(dwPlayerTracker, "DopeWars", handleDopeWars),
|
||||
(lemonadeTracker, "LemonadeStand", handleLemonade),
|
||||
(vpTracker, "VideoPoker", handleVideoPoker),
|
||||
(jackTracker, "BlackJack", handleBlackJack),
|
||||
(mindTracker, "MasterMind", handleMmind),
|
||||
(golfTracker, "GolfSim", handleGolf),
|
||||
(dwPlayerTracker, "DopeWars", handleDopeWars) if 'dwPlayerTracker' in globals() else None,
|
||||
(lemonadeTracker, "LemonadeStand", handleLemonade) if 'lemonadeTracker' in globals() else None,
|
||||
(vpTracker, "VideoPoker", handleVideoPoker) if 'vpTracker' in globals() else None,
|
||||
(jackTracker, "BlackJack", handleBlackJack) if 'jackTracker' in globals() else None,
|
||||
(mindTracker, "MasterMind", handleMmind) if 'mindTracker' in globals() else None,
|
||||
(golfTracker, "GolfSim", handleGolf) if 'golfTracker' in globals() else None,
|
||||
]
|
||||
trackers = [tracker for tracker in trackers if tracker is not None]
|
||||
|
||||
for tracker, game_name, handle_game_func in trackers:
|
||||
playingGame, game = check_and_play_game(tracker, message_from_id, message_string, rxNode, channel_number, game_name, handle_game_func)
|
||||
@@ -1186,6 +1198,7 @@ def onReceive(packet, interface):
|
||||
else:
|
||||
# message is on a channel
|
||||
if messageTrap(message_string):
|
||||
# message is for us to respond to
|
||||
if ignoreDefaultChannel and channel_number == publicChannel:
|
||||
logger.debug(f"System: ignoreDefaultChannel CMD:{message_string} From: {get_name_from_number(message_from_id, 'short', rxNode)}")
|
||||
else:
|
||||
@@ -1208,7 +1221,7 @@ def onReceive(packet, interface):
|
||||
send_message(auto_response(message_string, snr, rssi, hop, pkiStatus, message_from_id, channel_number, rxNode, isDM), channel_number, 0, rxNode)
|
||||
|
||||
else:
|
||||
# message is not for bot to respond to
|
||||
# message is not for us to respond to
|
||||
# ignore the message but add it to the message history list
|
||||
if zuluTime:
|
||||
timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
|
||||
@@ -1239,6 +1252,18 @@ def onReceive(packet, interface):
|
||||
logger.debug(f"Repeating message on Device{i} Channel:{channel_number}")
|
||||
send_message(rMsg, channel_number, 0, i)
|
||||
time.sleep(responseDelay)
|
||||
|
||||
# if QRZ enabled check if we have said hello
|
||||
if qrz_hello_enabled:
|
||||
if never_seen_before(message_from_id):
|
||||
name = {get_name_from_number(message_from_id, 'short', rxNode)}
|
||||
# add to qrz_hello list
|
||||
hello(message_from_id, name)
|
||||
# send a hello message as a DM
|
||||
if not train_qrz:
|
||||
time.sleep(responseDelay)
|
||||
send_message(f"Hello {name} {qrz_hello_string}", channel_number, message_from_id, rxNode)
|
||||
time.sleep(responseDelay)
|
||||
else:
|
||||
# Evaluate non TEXT_MESSAGE_APP packets
|
||||
consumeMetadata(packet, rxNode)
|
||||
@@ -1314,6 +1339,12 @@ async def start_rx():
|
||||
logger.debug(f"System: Emergency Alert Broadcast Enabled on channels {emergencyAlertBroadcastCh}")
|
||||
if emergency_responder_enabled:
|
||||
logger.debug(f"System: Emergency Responder Enabled on channels {emergency_responder_alert_channel} for interface {emergency_responder_alert_interface}")
|
||||
if qrz_hello_enabled and train_qrz:
|
||||
logger.debug(f"System: QRZ Welcome/Hello Enabled with training mode")
|
||||
if qrz_hello_enabled and not train_qrz:
|
||||
logger.debug(f"System: QRZ Welcome/Hello Enabled")
|
||||
if checklist_enabled:
|
||||
logger.debug(f"System: CheckList Module Enabled")
|
||||
if enableSMTP:
|
||||
if enableImap:
|
||||
logger.debug(f"System: SMTP Email Alerting Enabled using IMAP")
|
||||
|
||||
42
modules/README.md
Normal file
42
modules/README.md
Normal file
@@ -0,0 +1,42 @@
|
||||
# Modules and Adding stuff
|
||||
|
||||
To help with code testing see `etc/simulator.py` to simulate a bot. I also enjoy meshtasticd(linux-native) in noradio with MQTT server and client to just emulate a mesh.
|
||||
|
||||
## By following these steps, you can add a new bbs option to the bot.
|
||||
|
||||
1. **Define the Command Handler**:
|
||||
Add a new function in mesh_bot.py to handle the new command. For example, if you want to add a command `newcommand`:
|
||||
```python
|
||||
def handle_newcommand(message, message_from_id, deviceID):
|
||||
return "This is a response from the new command."
|
||||
```
|
||||
Additionally you can add a whole new module.py, I recommend doing this if you need to import more stuff, try and wedge it into similar spots if you can. You will need to import the file as well, look further at `modules/system.py` for more.
|
||||
2. **Add the Command to the Auto Response**:
|
||||
Update the auto_response function in mesh_bot.py to include the new command:
|
||||
```python
|
||||
def auto_response(message, snr, rssi, hop, pkiStatus, message_from_id, channel_number, deviceID, isDM):
|
||||
#...
|
||||
"newcommand": lambda: handle_newcommand(message, message_from_id, deviceID),
|
||||
#...
|
||||
```
|
||||
3. **Update the Trap List and Help**:
|
||||
A quick way to do this is to edit the line 16/17 in `modules/system.py` to include the new command:
|
||||
```python
|
||||
#...
|
||||
trap_list = ("cmd", "cmd?", "newcommand") # default trap list, with the new command added
|
||||
help_message = "Bot CMD?:newcommand, "
|
||||
#...
|
||||
```
|
||||
|
||||
**If looking to merge** the prefered way would be to update `modules/system.py` Adding this block below `ping` which ends around line 28:
|
||||
```python
|
||||
# newcommand Configuration
|
||||
newcommand_enabled = True # settings.py handles the config.ini values; this is a placeholder
|
||||
if newcommand_enabled:
|
||||
trap_list_newcommand = ("newcommand",)
|
||||
trap_list = trap_list + trap_list_newcommand
|
||||
help_message = help_message + ", newcommand"
|
||||
```
|
||||
|
||||
5. **Test the New Command**:
|
||||
Run MeshBot and test the new command by sending a message with the command `newcommand` to ensure it responds correctly.
|
||||
160
modules/checklist.py
Normal file
160
modules/checklist.py
Normal file
@@ -0,0 +1,160 @@
|
||||
# Checkin Checkout database module for the bot
|
||||
# K7MHI Kelly Keeton 2024
|
||||
|
||||
import sqlite3
|
||||
from modules.log import *
|
||||
import time
|
||||
|
||||
trap_list_checklist = ("checkin", "checkout", "checklist", "purgein", "purgeout")
|
||||
|
||||
def initialize_checklist_database():
|
||||
# create the database
|
||||
conn = sqlite3.connect(checklist_db)
|
||||
c = conn.cursor()
|
||||
# Check if the checkin table exists, and create it if it doesn't
|
||||
c.execute('''CREATE TABLE IF NOT EXISTS checkin
|
||||
(checkin_id INTEGER PRIMARY KEY, checkin_name TEXT, checkin_date TEXT, checkin_time TEXT, location TEXT, checkin_notes TEXT)''')
|
||||
# Check if the checkout table exists, and create it if it doesn't
|
||||
c.execute('''CREATE TABLE IF NOT EXISTS checkout
|
||||
(checkout_id INTEGER PRIMARY KEY, checkout_name TEXT, checkout_date TEXT, checkout_time TEXT, location TEXT, checkout_notes TEXT)''')
|
||||
conn.commit()
|
||||
conn.close()
|
||||
logger.debug("System: Ensured data/checklist.db exists with required tables")
|
||||
|
||||
def checkin(name, date, time, location, notes):
|
||||
location = ", ".join(map(str, location))
|
||||
# checkin a user
|
||||
conn = sqlite3.connect(checklist_db)
|
||||
c = conn.cursor()
|
||||
try:
|
||||
c.execute("INSERT INTO checkin (checkin_name, checkin_date, checkin_time, location, checkin_notes) VALUES (?, ?, ?, ?, ?)", (name, date, time, location, notes))
|
||||
# # remove any checkouts that are older than the checkin
|
||||
# c.execute("DELETE FROM checkout WHERE checkout_date < ? OR (checkout_date = ? AND checkout_time < ?)", (date, date, time))
|
||||
except sqlite3.OperationalError as e:
|
||||
if "no such table" in str(e):
|
||||
initialize_checklist_database()
|
||||
c.execute("INSERT INTO checkin (checkin_name, checkin_date, checkin_time, location, checkin_notes) VALUES (?, ?, ?, ?, ?)", (name, date, time, location, notes))
|
||||
else:
|
||||
raise
|
||||
conn.commit()
|
||||
conn.close()
|
||||
if reverse_in_out:
|
||||
return "Checked✅Out: " + str(name)
|
||||
else:
|
||||
return "Checked✅In: " + str(name)
|
||||
|
||||
def delete_checkin(checkin_id):
|
||||
# delete a checkin
|
||||
conn = sqlite3.connect(checklist_db)
|
||||
c = conn.cursor()
|
||||
c.execute("DELETE FROM checkin WHERE checkin_id = ?", (checkin_id,))
|
||||
conn.commit()
|
||||
conn.close()
|
||||
return "Checkin deleted." + str(checkin_id)
|
||||
|
||||
def checkout(name, date, time_str, location, notes):
|
||||
location = ", ".join(map(str, location))
|
||||
# checkout a user
|
||||
conn = sqlite3.connect(checklist_db)
|
||||
c = conn.cursor()
|
||||
try:
|
||||
# Check if the user has a checkin before checking out
|
||||
c.execute("""
|
||||
SELECT checkin_id FROM checkin
|
||||
WHERE checkin_name = ?
|
||||
AND NOT EXISTS (
|
||||
SELECT 1 FROM checkout
|
||||
WHERE checkout_name = checkin_name
|
||||
AND (checkout_date > checkin_date OR (checkout_date = checkin_date AND checkout_time > checkin_time))
|
||||
)
|
||||
ORDER BY checkin_date DESC, checkin_time DESC
|
||||
LIMIT 1
|
||||
""", (name,))
|
||||
checkin_record = c.fetchone()
|
||||
if checkin_record:
|
||||
c.execute("INSERT INTO checkout (checkout_name, checkout_date, checkout_time, location, checkout_notes) VALUES (?, ?, ?, ?, ?)", (name, date, time_str, location, notes))
|
||||
# calculate length of time checked in
|
||||
c.execute("SELECT checkin_time FROM checkin WHERE checkin_id = ?", (checkin_record[0],))
|
||||
checkin_time = c.fetchone()[0]
|
||||
checkin_datetime = time.strptime(date + " " + checkin_time, "%Y-%m-%d %H:%M:%S")
|
||||
time_checked_in_seconds = time.time() - time.mktime(checkin_datetime)
|
||||
timeCheckedIn = time.strftime("%H:%M:%S", time.gmtime(time_checked_in_seconds))
|
||||
# # remove the checkin record older than the checkout
|
||||
# c.execute("DELETE FROM checkin WHERE checkin_date < ? OR (checkin_date = ? AND checkin_time < ?)", (date, date, time_str))
|
||||
except sqlite3.OperationalError as e:
|
||||
if "no such table" in str(e):
|
||||
initialize_checklist_database()
|
||||
c.execute("INSERT INTO checkout (checkout_name, checkout_date, checkout_time, location, checkout_notes) VALUES (?, ?, ?, ?, ?)", (name, date, time_str, location, notes))
|
||||
else:
|
||||
raise
|
||||
conn.commit()
|
||||
conn.close()
|
||||
if checkin_record:
|
||||
if reverse_in_out:
|
||||
return "Checked⌛️In: " + str(name) + " duration " + timeCheckedIn
|
||||
else:
|
||||
return "Checked⌛️Out: " + str(name) + " duration " + timeCheckedIn
|
||||
else:
|
||||
return "None found for " + str(name)
|
||||
|
||||
def delete_checkout(checkout_id):
|
||||
# delete a checkout
|
||||
conn = sqlite3.connect(checklist_db)
|
||||
c = conn.cursor()
|
||||
c.execute("DELETE FROM checkout WHERE checkout_id = ?", (checkout_id,))
|
||||
conn.commit()
|
||||
conn.close()
|
||||
return "Checkout deleted." + str(checkout_id)
|
||||
|
||||
def list_checkin():
|
||||
# list checkins
|
||||
conn = sqlite3.connect(checklist_db)
|
||||
c = conn.cursor()
|
||||
c.execute("""
|
||||
SELECT * FROM checkin
|
||||
WHERE checkin_id NOT IN (
|
||||
SELECT checkin_id FROM checkout
|
||||
WHERE checkout_date > checkin_date OR (checkout_date = checkin_date AND checkout_time > checkin_time)
|
||||
)
|
||||
""")
|
||||
rows = c.fetchall()
|
||||
conn.close()
|
||||
timeCheckedIn = ""
|
||||
checkin_list = ""
|
||||
for row in rows:
|
||||
#calculate length of time checked in
|
||||
timeCheckedIn = time.strftime("%H:%M:%S", time.gmtime(time.time() - time.mktime(time.strptime(row[2] + " " + row[3], "%Y-%m-%d %H:%M:%S"))))
|
||||
checkin_list += "ID: " + row[1] + " checked-In for " + timeCheckedIn
|
||||
if row[5] != "":
|
||||
checkin_list += "📝" + row[5]
|
||||
if row != rows[-1]:
|
||||
checkin_list += "\n"
|
||||
# if empty list
|
||||
if checkin_list == "":
|
||||
return "No data to display."
|
||||
return checkin_list
|
||||
|
||||
def process_checklist_command(nodeID, message, name="none", location="none"):
|
||||
current_date = time.strftime("%Y-%m-%d")
|
||||
current_time = time.strftime("%H:%M:%S")
|
||||
# if user on bbs_ban_list reject command
|
||||
if str(nodeID) in bbs_ban_list:
|
||||
logger.warning("System: Checklist attempt from the ban list")
|
||||
return "unable to process command"
|
||||
try:
|
||||
comment = message.split(" ", 1)[1]
|
||||
except IndexError:
|
||||
comment = ""
|
||||
# handle checklist commands
|
||||
if ("checkin" in message.lower() and not reverse_in_out) or ("checkout" in message.lower() and reverse_in_out):
|
||||
return checkin(name, current_date, current_time, location, comment)
|
||||
elif ("checkout" in message.lower() and not reverse_in_out) or ("checkin" in message.lower() and reverse_in_out):
|
||||
return checkout(name, current_date, current_time, location, comment)
|
||||
elif "purgein" in message.lower():
|
||||
return delete_checkin(nodeID)
|
||||
elif "purgeout" in message.lower():
|
||||
return delete_checkout(nodeID)
|
||||
elif "checklist" in message.lower():
|
||||
return list_checkin()
|
||||
else:
|
||||
return "Invalid command."
|
||||
@@ -10,8 +10,9 @@ import xml.dom.minidom
|
||||
from modules.log import *
|
||||
|
||||
trap_list_location_eu = ("ukalert", "ukwx", "ukflood")
|
||||
trap_list_location_de = ("dealert", "dewx", "deflood")
|
||||
|
||||
def get_govUK_alerts(shortAlerts=False):
|
||||
def get_govUK_alerts(lat, lon):
|
||||
try:
|
||||
# get UK.gov alerts
|
||||
url = 'https://www.gov.uk/alerts'
|
||||
@@ -27,7 +28,23 @@ def get_govUK_alerts(shortAlerts=False):
|
||||
return "🚨" + alert.get_text(strip=True)
|
||||
else:
|
||||
return NO_ALERTS
|
||||
|
||||
|
||||
def get_nina_alerts():
|
||||
try:
|
||||
# get api.bund.dev alerts
|
||||
alerts = []
|
||||
for regionalKey in myRegionalKeysDE:
|
||||
url = ("https://nina.api.proxy.bund.dev/api31/dashboard/" + regionalKey + ".json")
|
||||
response = requests.get(url)
|
||||
data = response.json()
|
||||
|
||||
for item in data:
|
||||
title = item["i18nTitle"]["de"]
|
||||
alerts.append(f"🚨 {title}")
|
||||
return "\n".join(alerts) if alerts else NO_ALERTS
|
||||
except Exception as e:
|
||||
logger.warning("Error getting NINA DE alerts: " + str(e))
|
||||
return NO_ALERTS
|
||||
|
||||
def get_wxUKgov():
|
||||
# get UK weather warnings
|
||||
@@ -57,4 +74,4 @@ def get_floodUKgov():
|
||||
# get UK flood warnings
|
||||
url = 'https://environment.data.gov.uk/flood-widgets/rss/feed-England.xml'
|
||||
|
||||
return NO_ALERTS
|
||||
return NO_ALERTS
|
||||
|
||||
@@ -211,6 +211,10 @@ def llm_query(input, nodeID=0, location_name=None):
|
||||
if result.status_code == 200:
|
||||
result_json = result.json()
|
||||
result = result_json.get("response", "")
|
||||
|
||||
# deepseek-r1 has added <think> </think> tags to the response
|
||||
if "<think>" in result:
|
||||
result = result.split("</think>")[1]
|
||||
else:
|
||||
raise Exception(f"HTTP Error: {result.status_code}")
|
||||
|
||||
|
||||
@@ -71,7 +71,16 @@ def where_am_i(lat=0, lon=0, short=False, zip=False):
|
||||
def getRepeaterBook(lat=0, lon=0):
|
||||
grid = mh.to_maiden(float(lat), float(lon))
|
||||
data = []
|
||||
repeater_url = f"https://www.repeaterbook.com/repeaters/prox_result.php?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"
|
||||
# check if in the US or not
|
||||
usapi ="https://www.repeaterbook.com/repeaters/prox_result.php?"
|
||||
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
|
||||
else:
|
||||
repeater_url = elsewhereapi
|
||||
|
||||
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"
|
||||
|
||||
try:
|
||||
msg = ''
|
||||
response = requests.get(repeater_url)
|
||||
@@ -95,10 +104,8 @@ def getRepeaterBook(lat=0, lon=0):
|
||||
'direction': cells[i + 9].text.strip() if i + 9 < len(cells) else 'N/A'
|
||||
}
|
||||
data.append(repeater)
|
||||
else:
|
||||
msg = "bug?Not enough columns"
|
||||
else:
|
||||
msg = "bug?Table not found"
|
||||
msg = "No Data for your Region"
|
||||
except Exception as e:
|
||||
msg = "No repeaters found 😔"
|
||||
# Limit the output to the first 4 repeaters
|
||||
@@ -229,36 +236,36 @@ def get_NOAAweather(lat=0, lon=0, unit=0):
|
||||
unit = 1
|
||||
|
||||
weather_url = "https://forecast.weather.gov/MapClick.php?FcstType=text&lat=" + str(lat) + "&lon=" + str(lon)
|
||||
if unit == 1:
|
||||
weather_url += "&unit=1"
|
||||
|
||||
weather_api = "https://api.weather.gov/points/" + str(lat) + "," + str(lon)
|
||||
# extract the "forecast": property from the JSON response
|
||||
try:
|
||||
weather_data = requests.get(weather_url, timeout=urlTimeoutSeconds)
|
||||
weather_data = requests.get(weather_api, timeout=urlTimeoutSeconds)
|
||||
if not weather_data.ok:
|
||||
logger.error("Location:Error fetching weather data from NOAA")
|
||||
logger.warning("Location:Error fetching weather data from NOAA for location")
|
||||
return ERROR_FETCHING_DATA
|
||||
except (requests.exceptions.RequestException):
|
||||
logger.error("Location:Error fetching weather data from NOAA")
|
||||
logger.warning("Location:Error fetching weather data from NOAA for location")
|
||||
return ERROR_FETCHING_DATA
|
||||
# get the forecast URL from the JSON response
|
||||
weather_json = weather_data.json()
|
||||
forecast_url = weather_json['properties']['forecast']
|
||||
try:
|
||||
forecast_data = requests.get(forecast_url, timeout=urlTimeoutSeconds)
|
||||
if not forecast_data.ok:
|
||||
logger.warning("Location:Error fetching weather forecast from NOAA")
|
||||
return ERROR_FETCHING_DATA
|
||||
except (requests.exceptions.RequestException):
|
||||
logger.warning("Location:Error fetching weather forecast from NOAA")
|
||||
return ERROR_FETCHING_DATA
|
||||
|
||||
soup = bs.BeautifulSoup(weather_data.text, 'html.parser')
|
||||
table = soup.find('div', id="detailed-forecast-body")
|
||||
# from periods, get the detailedForecast from number of days in NOAAforecastDuration
|
||||
forecast_json = forecast_data.json()
|
||||
forecast = forecast_json['properties']['periods']
|
||||
for day in forecast[:forecastDuration]:
|
||||
# abreviate the forecast
|
||||
|
||||
if table is None:
|
||||
logger.error("Location:Bad weather data from NOAA")
|
||||
return ERROR_FETCHING_DATA
|
||||
else:
|
||||
# get rows
|
||||
rows = table.find_all('div', class_="row")
|
||||
|
||||
# extract data from rows
|
||||
for row in rows:
|
||||
# shrink the text
|
||||
line = abbreviate_noaa(row.text)
|
||||
# only grab a few days of weather
|
||||
if len(weather.split("\n")) < forecastDuration:
|
||||
weather += line + "\n"
|
||||
# trim off last newline
|
||||
weather += day['name'] + ": " + abbreviate_noaa(day['detailedForecast']) + "\n"
|
||||
# remove last newline
|
||||
weather = weather[:-1]
|
||||
|
||||
# get any alerts and return the count
|
||||
@@ -323,6 +330,9 @@ def abbreviate_noaa(row):
|
||||
"degrees": "°",
|
||||
"percent": "%",
|
||||
"department": "Dept.",
|
||||
"amounts less than a tenth of an inch possible.": "< 0.1in",
|
||||
"temperatures": "temps.",
|
||||
"temperature": "temp.",
|
||||
}
|
||||
|
||||
line = row
|
||||
|
||||
57
modules/qrz.py
Normal file
57
modules/qrz.py
Normal file
@@ -0,0 +1,57 @@
|
||||
# Module to respomnd to new nodes we havent seen before with a hello message
|
||||
# K7MHI Kelly Keeton 2024
|
||||
|
||||
import sqlite3
|
||||
from modules.log import *
|
||||
|
||||
def initalize_qrz_database():
|
||||
# create the database
|
||||
conn = sqlite3.connect(qrz_db)
|
||||
c = conn.cursor()
|
||||
# Check if the qrz table exists, and create it if it doesn't
|
||||
c.execute('''CREATE TABLE IF NOT EXISTS qrz
|
||||
(qrz_id INTEGER PRIMARY KEY, qrz_call TEXT, qrz_name TEXT, qrz_qth TEXT, qrz_notes TEXT)''')
|
||||
conn.commit()
|
||||
conn.close()
|
||||
|
||||
def never_seen_before(nodeID):
|
||||
# check if we have seen this node before and sent a hello message
|
||||
conn = sqlite3.connect(qrz_db)
|
||||
c = conn.cursor()
|
||||
try:
|
||||
c.execute("SELECT * FROM qrz WHERE qrz_call = ?", (nodeID,))
|
||||
row = c.fetchone()
|
||||
conn.close()
|
||||
if row is None:
|
||||
# we have not seen this node before
|
||||
return True
|
||||
else:
|
||||
# we have seen this node before
|
||||
return False
|
||||
except sqlite3.OperationalError as e:
|
||||
if "no such table" in str(e):
|
||||
initalize_qrz_database()
|
||||
logger.warning("QRZ database table not found, created new table")
|
||||
# we have not seen this node before
|
||||
return True
|
||||
else:
|
||||
raise
|
||||
|
||||
def hello(nodeID, name):
|
||||
# send a hello message
|
||||
conn = sqlite3.connect(qrz_db)
|
||||
c = conn.cursor()
|
||||
try:
|
||||
c.execute("INSERT INTO qrz (qrz_call, qrz_name) VALUES (?, ?)", (nodeID, str(name)))
|
||||
except sqlite3.OperationalError as e:
|
||||
if "no such table" in str(e):
|
||||
initalize_qrz_database()
|
||||
c.execute("INSERT INTO qrz (qrz_call, qrz_name) VALUES (?, ?)", (nodeID, str(name)))
|
||||
else:
|
||||
raise
|
||||
conn.commit()
|
||||
conn.close()
|
||||
return True
|
||||
|
||||
|
||||
|
||||
@@ -91,6 +91,14 @@ if 'smtp' not in config:
|
||||
config['smtp'] = {'sysopEmails': '', 'enableSMTP': 'False', 'enableImap': 'False'}
|
||||
config.write(open(config_file, 'w'))
|
||||
|
||||
if 'checklist' not in config:
|
||||
config['checklist'] = {'enabled': 'False', 'checklist_db': 'data/checklist.db'}
|
||||
config.write(open(config_file, 'w'))
|
||||
|
||||
if 'qrz' not in config:
|
||||
config['qrz'] = {'enabled': 'False', 'qrz_db': 'data/qrz.db', 'qrz_hello_string': 'send CMD or DM me for more info.'}
|
||||
config.write(open(config_file, 'w'))
|
||||
|
||||
# interface1 settings
|
||||
interface1_type = config['interface'].get('type', 'serial')
|
||||
port1 = config['interface'].get('port', '')
|
||||
@@ -241,8 +249,10 @@ try:
|
||||
emergencyAlertBrodcastEnabled = config['location'].getboolean('eAlertBroadcastEnabled', False) # default False
|
||||
wxAlertBroadcastEnabled = config['location'].getboolean('wxAlertBroadcastEnabled', False) # default False
|
||||
enableGBalerts = config['location'].getboolean('enableGBalerts', False) # default False
|
||||
enableDEalerts = config['location'].getboolean('enableDEalerts', False) # default False
|
||||
wxAlertsEnabled = config['location'].getboolean('NOAAalertsEnabled', True) # default True
|
||||
mySAME = config['location'].get('mySAME', '').split(',') # default empty
|
||||
myRegionalKeysDE = config['location'].get('myRegionalKeysDE', '110000000000').split(',') # default city Berlin
|
||||
forecastDuration = config['location'].getint('NOAAforecastDuration', 4) # NOAA forcast days
|
||||
numWxAlerts = config['location'].getint('NOAAalertCount', 2) # default 2 alerts
|
||||
enableExtraLocationWx = config['location'].getboolean('enableExtraLocationWx', False) # default False
|
||||
@@ -258,7 +268,18 @@ try:
|
||||
bbs_admin_list = config['bbs'].get('bbs_admin_list', '').split(',')
|
||||
bbs_link_enabled = config['bbs'].getboolean('bbslink_enabled', False)
|
||||
bbs_link_whitelist = config['bbs'].get('bbslink_whitelist', '').split(',')
|
||||
|
||||
# checklist
|
||||
checklist_enabled = config['checklist'].getboolean('enabled', False)
|
||||
checklist_db = config['checklist'].get('checklist_db', 'data/checklist.db')
|
||||
reverse_in_out = config['checklist'].getboolean('reverse_in_out', False)
|
||||
|
||||
# qrz hello
|
||||
qrz_hello_enabled = config['qrz'].getboolean('enabled', False)
|
||||
qrz_db = config['qrz'].get('qrz_db', 'data/qrz.db')
|
||||
qrz_hello_string = config['qrz'].get('qrz_hello_string', 'send CMD or DM me for more info.')
|
||||
train_qrz = config['qrz'].getboolean('training', True)
|
||||
|
||||
# E-Mail Settings
|
||||
sysopEmails = config['smtp'].get('sysopEmails', '').split(',')
|
||||
enableSMTP = config['smtp'].getboolean('enableSMTP', False)
|
||||
|
||||
@@ -75,10 +75,14 @@ if location_enabled:
|
||||
from modules.locationdata import * # from the spudgunman/meshing-around repo
|
||||
trap_list = trap_list + trap_list_location # items tide, whereami, wxc, wx
|
||||
help_message = help_message + ", whereami, wx, wxc, rlist"
|
||||
if enableGBalerts:
|
||||
if enableGBalerts and not enableDEalerts:
|
||||
from modules.globalalert import * # from the spudgunman/meshing-around repo
|
||||
trap_list = trap_list + trap_list_location_eu
|
||||
logger.warning(f"System: GB Alerts not functional at this time need to find a source API")
|
||||
#help_message = help_message + ", ukalert, ukwx, ukflood"
|
||||
if enableDEalerts and not enableGBalerts:
|
||||
from modules.globalalert import * # from the spudgunman/meshing-around repo
|
||||
trap_list = trap_list + trap_list_location_de
|
||||
#help_message = help_message + ", dealert, dewx, deflood"
|
||||
|
||||
# Open-Meteo Configuration for worldwide weather
|
||||
if use_meteo_wxApi:
|
||||
@@ -186,12 +190,24 @@ if store_forward_enabled:
|
||||
trap_list = trap_list + ("messages",)
|
||||
help_message = help_message + ", messages"
|
||||
|
||||
# QRZ Configuration
|
||||
if qrz_hello_enabled:
|
||||
from modules.qrz import * # from the spudgunman/meshing-around repo
|
||||
#trap_list = trap_list + trap_list_qrz # items qrz, qrz?, qrzcall
|
||||
#help_message = help_message + ", qrz"
|
||||
|
||||
# CheckList Configuration
|
||||
if checklist_enabled:
|
||||
from modules.checklist import * # from the spudgunman/meshing-around repo
|
||||
trap_list = trap_list + trap_list_checklist # items checkin, checkout, checklist, purgein, purgeout
|
||||
help_message = help_message + ", checkin, checkout"
|
||||
|
||||
# Radio Monitor Configuration
|
||||
if radio_detection_enabled:
|
||||
from modules.radio import * # from the spudgunman/meshing-around repo
|
||||
|
||||
# File Monitor Configuration
|
||||
if file_monitor_enabled or read_news_enabled:
|
||||
if file_monitor_enabled or read_news_enabled or bee_enabled:
|
||||
from modules.filemon import * # from the spudgunman/meshing-around repo
|
||||
if read_news_enabled:
|
||||
trap_list = trap_list + trap_list_filemon # items readnews
|
||||
@@ -423,8 +439,35 @@ def get_closest_nodes(nodeInt=1,returnCount=3):
|
||||
else:
|
||||
logger.warning(f"System: No nodes found in closest_nodes on interface {nodeInt}")
|
||||
return ERROR_FETCHING_DATA
|
||||
|
||||
def handleFavoritNode(nodeInt=1, nodeID=0, aor=False):
|
||||
#aor is add or remove if True add, if False remove
|
||||
interface = globals()[f'interface{nodeInt}']
|
||||
myNodeNumber = globals().get(f'myNodeNum{nodeInt}')
|
||||
if aor:
|
||||
interface.getNode(myNodeNumber).addFavorite(nodeID)
|
||||
logger.info(f"System: Added {nodeID} to favorites")
|
||||
else:
|
||||
interface.getNode(myNodeNumber).removeFavorite(nodeID)
|
||||
logger.info(f"System: Removed {nodeID} from favorites")
|
||||
|
||||
def getFavoritNodes(nodeInt=1):
|
||||
interface = globals()[f'interface{nodeInt}']
|
||||
myNodeNumber = globals().get(f'myNodeNum{nodeInt}')
|
||||
favList = []
|
||||
for node in interface.getNode(myNodeNumber).favorites:
|
||||
favList.append(node)
|
||||
return favList
|
||||
|
||||
def handleSentinelIgnore(nodeInt=1, nodeID=0, aor=False):
|
||||
#aor is add or remove if True add, if False remove
|
||||
if aor:
|
||||
sentryIgnoreList.append(str(nodeID))
|
||||
logger.info(f"System: Added {nodeID} to sentry ignore list")
|
||||
else:
|
||||
sentryIgnoreList.remove(str(nodeID))
|
||||
logger.info(f"System: Removed {nodeID} from sentry ignore list")
|
||||
|
||||
|
||||
def messageChunker(message):
|
||||
message_list = []
|
||||
if len(message) > MESSAGE_CHUNK_SIZE:
|
||||
@@ -444,9 +487,9 @@ def messageChunker(message):
|
||||
sentence = ''
|
||||
for char in part:
|
||||
sentence += char
|
||||
if char in '.!?':
|
||||
sentences.append(sentence.strip())
|
||||
sentence = ''
|
||||
# if char in '.!?':
|
||||
# sentences.append(sentence.strip())
|
||||
# sentence = ''
|
||||
if sentence:
|
||||
sentences.append(sentence.strip())
|
||||
|
||||
@@ -480,8 +523,12 @@ def messageChunker(message):
|
||||
final_message_list = []
|
||||
for chunk in message_list:
|
||||
while len(chunk) > MESSAGE_CHUNK_SIZE:
|
||||
final_message_list.append(chunk[:MESSAGE_CHUNK_SIZE])
|
||||
chunk = chunk[MESSAGE_CHUNK_SIZE:]
|
||||
# Find the last space within the chunk size limit
|
||||
split_index = chunk.rfind(' ', 0, MESSAGE_CHUNK_SIZE)
|
||||
if split_index == -1:
|
||||
split_index = MESSAGE_CHUNK_SIZE
|
||||
final_message_list.append(chunk[:split_index])
|
||||
chunk = chunk[split_index:].strip()
|
||||
if chunk:
|
||||
final_message_list.append(chunk)
|
||||
|
||||
@@ -656,6 +703,7 @@ def handleMultiPing(nodeID=0, deviceID=1):
|
||||
|
||||
def handleAlertBroadcast(deviceID=1):
|
||||
alertUk = NO_ALERTS
|
||||
alertDe = NO_ALERTS
|
||||
alertFema = NO_ALERTS
|
||||
wxAlert = NO_ALERTS
|
||||
# only allow API call every 20 minutes
|
||||
@@ -671,6 +719,8 @@ def handleAlertBroadcast(deviceID=1):
|
||||
alertWx = alertBrodcastNOAA()
|
||||
|
||||
if emergencyAlertBrodcastEnabled:
|
||||
if enableDEalerts:
|
||||
alertDe = get_nina_alerts()
|
||||
if enableGBalerts:
|
||||
alertUk = get_govUK_alerts()
|
||||
else:
|
||||
@@ -685,6 +735,7 @@ def handleAlertBroadcast(deviceID=1):
|
||||
|
||||
femaAlert = alertFema
|
||||
ukAlert = alertUk
|
||||
deAlert = alertDe
|
||||
|
||||
if emergencyAlertBrodcastEnabled:
|
||||
if NO_ALERTS not in femaAlert and ERROR_FETCHING_DATA not in femaAlert:
|
||||
@@ -701,6 +752,14 @@ def handleAlertBroadcast(deviceID=1):
|
||||
else:
|
||||
send_message(ukAlert, emergencyAlertBroadcastCh, 0, deviceID)
|
||||
return True
|
||||
|
||||
if NO_ALERTS not in deAlert:
|
||||
if isinstance(emergencyAlertBroadcastCh, list):
|
||||
for channel in emergencyAlertBroadcastCh:
|
||||
send_message(ukAlert, int(channel), 0, deviceID)
|
||||
else:
|
||||
send_message(ukAlert, emergencyAlertBroadcastCh, 0, deviceID)
|
||||
return True
|
||||
|
||||
# pause for 10 seconds
|
||||
time.sleep(10)
|
||||
|
||||
@@ -7,17 +7,26 @@
|
||||
import os
|
||||
import http.server
|
||||
|
||||
# Set the desired IP address
|
||||
server_ip = '127.0.0.1'
|
||||
|
||||
# Set the port for the server
|
||||
PORT = 8420
|
||||
|
||||
# set webRoot index.html location
|
||||
webRoot = "etc/www"
|
||||
# Generate with: openssl req -new -x509 -keyout server.pem -out server.pem -days 365 -nodes
|
||||
SSL = False
|
||||
|
||||
# Set to True to enable logging sdtout
|
||||
webServerLogs = False
|
||||
|
||||
# Generate with: openssl req -new -x509 -keyout server.pem -out server.pem -days 365 -nodes
|
||||
SSL = False
|
||||
# Determine the directory where this script is located.
|
||||
script_dir = os.path.dirname(os.path.realpath(__file__))
|
||||
|
||||
# Go up one level from the modules directory to the project root.
|
||||
project_root = os.path.abspath(os.path.join(script_dir, ".."))
|
||||
|
||||
# Build the absolute path to the webRoot folder; to where index.html is located.
|
||||
webRoot = os.path.join(project_root, "etc", "www")
|
||||
|
||||
if SSL:
|
||||
import ssl
|
||||
@@ -43,7 +52,12 @@ if SSL:
|
||||
exit(1)
|
||||
httpd.socket = ctx.wrap_socket(httpd.socket, server_side=True)
|
||||
|
||||
print(f"Serving reports at http://localhost:{PORT} Press ^C to quit.\n\n")
|
||||
# Create the HTTP server instance with the desired IP address
|
||||
httpd = http.server.HTTPServer((server_ip, PORT), QuietHandler)
|
||||
|
||||
# Print out the URL using the IP address stored in server_ip
|
||||
print(f"Serving reports at http://{server_ip}:{PORT} Press ^C to quit.\n\n")
|
||||
|
||||
if not webServerLogs:
|
||||
print("Server Logs are disabled")
|
||||
# Serve forever, that is until the user interrupts the process
|
||||
|
||||
10
pong_bot.py
10
pong_bot.py
@@ -25,8 +25,7 @@ def auto_response(message, snr, rssi, hop, pkiStatus, message_from_id, channel_n
|
||||
command_handler = {
|
||||
# Command List processes system.trap_list. system.messageTrap() sends any commands to here
|
||||
"ack": lambda: handle_ping(message_from_id, deviceID, message, hop, snr, rssi, isDM, channel_number),
|
||||
"cmd": lambda: help_message,
|
||||
"cmd?": lambda: help_message,
|
||||
"cmd": lambda: handle_cmd(message, message_from_id, deviceID),
|
||||
"cq": lambda: handle_ping(message_from_id, deviceID, message, hop, snr, rssi, isDM, channel_number),
|
||||
"cqcq": lambda: handle_ping(message_from_id, deviceID, message, hop, snr, rssi, isDM, channel_number),
|
||||
"cqcqcq": lambda: handle_ping(message_from_id, deviceID, message, hop, snr, rssi, isDM, channel_number),
|
||||
@@ -56,6 +55,13 @@ def auto_response(message, snr, rssi, hop, pkiStatus, message_from_id, channel_n
|
||||
|
||||
return bot_response
|
||||
|
||||
def handle_cmd(message, message_from_id, deviceID):
|
||||
# why CMD? its just a command list. a terminal would normally use "Help"
|
||||
# I didnt want to invoke the word "help" in Meshtastic due to its possible emergency use
|
||||
if " " in message and message.split(" ")[1] in trap_list:
|
||||
return "🤖 just use the commands directly in chat"
|
||||
return help_message
|
||||
|
||||
def handle_ping(message_from_id, deviceID, message, hop, snr, rssi, isDM, channel_number):
|
||||
global multiPing
|
||||
if "?" in message and isDM:
|
||||
|
||||
@@ -4,6 +4,8 @@ This is not a full turnkey setup for Docker yet but gets you most of the way the
|
||||
## Setup New Image
|
||||
`docker build -t meshing-around .`
|
||||
|
||||
there is also [script/docker/docker-install.bat](script/docker/docker-install.bat) which will automate this.
|
||||
|
||||
## Ollama Image with compose
|
||||
still a WIP
|
||||
`docker compose up -d`
|
||||
@@ -12,6 +14,9 @@ still a WIP
|
||||
To edit the config.ini in the docker you can
|
||||
`docker run -it --entrypoint /bin/bash meshing-around -c "nano /app/config.ini"`
|
||||
|
||||
there is also [script/docker/docker-terminal.bat](script/docker/docker-terminal.bat) which will open nano to edit.
|
||||
ctl+o to write out and exit editor in shell
|
||||
|
||||
## other info
|
||||
1. Ensure your serial port is properly shared.
|
||||
2. Run the Docker container:
|
||||
|
||||
@@ -1,2 +1,2 @@
|
||||
REM launch meshing-around container with a terminal
|
||||
docker run -it --entrypoint /bin/bash meshing-around -c "nano /app/config.ini"
|
||||
docker run -it --entrypoint /bin/bash meshing-around
|
||||
53
script/send-environment-metrics.py
Normal file
53
script/send-environment-metrics.py
Normal file
@@ -0,0 +1,53 @@
|
||||
# file name: send-environment-metrics.py
|
||||
# https://github.com/pdxlocations/Meshtastic-Python-Examples/blob/main/send-environment-metrics.py
|
||||
|
||||
from meshtastic.protobuf import portnums_pb2, telemetry_pb2
|
||||
from meshtastic import BROADCAST_ADDR
|
||||
import time
|
||||
|
||||
# For connection over serial
|
||||
# import meshtastic.serial_interface
|
||||
# interface = meshtastic.serial_interface.SerialInterface()
|
||||
|
||||
# For connection over TCP
|
||||
import meshtastic.tcp_interface
|
||||
interface = meshtastic.tcp_interface.TCPInterface(hostname='127.0.0.1', noProto=False)
|
||||
|
||||
# Create a telemetry data object
|
||||
telemetry_data = telemetry_pb2.Telemetry()
|
||||
telemetry_data.time = int(time.time())
|
||||
telemetry_data.local_stats.upTime = 0
|
||||
telemetry_data.environment_metrics.temperature = 0
|
||||
# telemetry_data.environment_metrics.voltage = 0
|
||||
# telemetry_data.environment_metrics.current = 0
|
||||
# telemetry_data.environment_metrics.relative_humidity = 0
|
||||
# telemetry_data.environment_metrics.barometric_pressure = 0
|
||||
# telemetry_data.environment_metrics.gas_resistance = 0
|
||||
# telemetry_data.environment_metrics.iaq = 0
|
||||
# telemetry_data.environment_metrics.distance = 0
|
||||
# telemetry_data.environment_metrics.lux = 0
|
||||
# telemetry_data.environment_metrics.white_lux = 0
|
||||
# telemetry_data.environment_metrics.ir_lux = 0
|
||||
# telemetry_data.environment_metrics.uv_lux = 0
|
||||
# telemetry_data.environment_metrics.wind_direction = 0
|
||||
# telemetry_data.environment_metrics.wind_speed = 0
|
||||
# telemetry_data.environment_metrics.wind_gust = 0
|
||||
# telemetry_data.environment_metrics.wind_lull = 0
|
||||
# telemetry_data.environment_metrics.weight = 0
|
||||
|
||||
# Read the uptime
|
||||
with open('/proc/uptime', 'r') as uptime:
|
||||
telemetry_data.local_stats.upTime = int(float(uptime.readline().split()[0]))
|
||||
|
||||
# Read the CPU temperature
|
||||
with open('/sys/class/thermal/thermal_zone0/temp', 'r') as cpu_temp:
|
||||
telemetry_data.environment_metrics.temperature = int(cpu_temp.read()) / 1000
|
||||
|
||||
interface.sendData(
|
||||
telemetry_data,
|
||||
destinationId=BROADCAST_ADDR,
|
||||
portNum=portnums_pb2.PortNum.TELEMETRY_APP,
|
||||
wantResponse=False,
|
||||
)
|
||||
|
||||
interface.close()
|
||||
Reference in New Issue
Block a user