Compare commits

...

46 Commits

Author SHA1 Message Date
SpudGunMan
bc9ada91b4 Update mesh_bot.py 2025-08-12 11:54:17 -07:00
SpudGunMan
28f06f0a21 Update config.template 2025-08-12 11:50:05 -07:00
SpudGunMan
267fe392e3 tuypo 2025-08-12 11:42:13 -07:00
SpudGunMan
6c1f7940ca refactor coastal weather
changes to config.ini template if you use tide or mwx
2025-08-12 11:35:42 -07:00
SpudGunMan
2fc9281394 Update system.py 2025-08-04 18:54:46 -07:00
SpudGunMan
b5bd1008c2 HowHigh? divideBy3
https://github.com/SpudGunMan/meshing-around/discussions/170
2025-08-03 17:43:10 -07:00
SpudGunMan
ee1db5b7be Update locationdata.py 2025-08-02 19:21:46 -07:00
SpudGunMan
7395b96337 Update locationdata.py 2025-07-30 10:16:33 -07:00
SpudGunMan
f3c6f77b23 Update mesh_bot.py 2025-07-30 10:13:14 -07:00
SpudGunMan
f6e04a42a0 Update system.py 2025-07-30 10:11:34 -07:00
SpudGunMan
3fcd588d02 bugs and docs
Consolidated Tide with MWX fixed up readme and cleaned up rlist in help
2025-07-30 10:05:04 -07:00
SpudGunMan
e1b47484f2 NOAA Coastal Marine Forcast data
using older but handy products with new mwx
2025-07-30 08:34:20 -07:00
SpudGunMan
14798cb992 alertDe 2025-07-22 09:28:08 -07:00
Kelly
41c8f0044b Merge pull request #163 from SudoRand/efficient-chunking
Allow chunker to consolidate lines when possible
2025-07-22 08:23:42 -07:00
SpudGunMan
45eefb24d8 enhance retry 2025-07-22 07:02:08 -07:00
SpudGunMan
410d32947c Update system.py 2025-07-21 20:13:07 -07:00
SpudGunMan
748652ac62 onDisconnect
correcting multiple issues adding config.ini feature for dont_retry_disconnect

https://github.com/SpudGunMan/meshing-around/issues/137
https://github.com/SpudGunMan/meshing-around/issues/156
2025-07-21 20:10:59 -07:00
SpudGunMan
d715cb6b4d Update system.py 2025-07-21 04:33:04 -07:00
SpudGunMan
1895a365ae fix retry and failure
correcting multiple issues with some bad code

https://github.com/SpudGunMan/meshing-around/issues/137

https://github.com/SpudGunMan/meshing-around/issues/156
2025-07-20 05:41:50 -07:00
SpudGunMan
cc58a38165 Update system.py 2025-07-18 21:24:55 -07:00
SpudGunMan
a8ccb05d56 Update system.py
bug of undefined for interface retry
2025-07-16 08:57:39 -07:00
SpudGunMan
a90a533a30 USGS Alerts
documented
2025-07-15 21:20:37 -07:00
SpudGunMan
57a4e5d68c Update README.md 2025-07-15 21:12:23 -07:00
SpudGunMan
7c99b684ad riverflow
never got documented well
2025-07-15 21:11:48 -07:00
SpudGunMan
b957c89d70 Update README.md 2025-07-15 20:32:51 -07:00
SpudGunMan
9b986dd57a Update locationdata.py
allow FIPS only
2025-07-15 20:30:43 -07:00
SpudGunMan
9e348332e5 SAME code back in iPAWS
the state only FIPS codes are too wide
2025-07-15 19:09:27 -07:00
SpudGunMan
0cfe759ef6 Update mesh_bot.py 2025-07-15 15:34:32 -07:00
SpudGunMan
e95902ef98 fix Excessive queries to FEMA
issue raised https://github.com/SpudGunMan/meshing-around/issues/165

Co-Authored-By: DEVAFRS <180097515+devafrs@users.noreply.github.com>
2025-07-15 15:22:53 -07:00
SpudGunMan
c7df4d88d1 Update config.template
Co-Authored-By: Russell Schmidt <836646+rfschmid@users.noreply.github.com>
2025-07-15 14:28:25 -07:00
SpudGunMan
6d01c5a986 further adjustments for 2.7.2
https://github.com/SpudGunMan/meshing-around/pull/164#pullrequestreview-3021705851

and

https: //github.com/SpudGunMan/meshing-around/issues/162
Co-Authored-By: SudoRand <25190078+sudorand@users.noreply.github.com>
2025-07-15 11:14:29 -07:00
SpudGunMan
3f882dcfcd fix message.log
fixing issue for log in https://github.com/SpudGunMan/meshing-around/pull/161

Co-Authored-By: SudoRand <25190078+sudorand@users.noreply.github.com>
2025-07-15 09:41:34 -07:00
SpudGunMan
b146fd6f64 Revert "enhance sysinfo"
This reverts commit 8709e5aed5.
2025-07-14 22:55:41 -07:00
SpudGunMan
8709e5aed5 enhance sysinfo
ChUtil/Node value
2025-07-14 22:13:49 -07:00
SpudGunMan
caf8a2708b Update log.py
fix time display
2025-07-14 22:04:22 -07:00
SpudGunMan
9b4200c198 Update config.template
adjustments for 2.7.2 firmware might change again
2025-07-14 21:54:30 -07:00
SudoRand
097cae6e94 Allow chunker to consolidate lines when possible
This allows the chunker to consolidate lines into significantly
fewer messages in many cases without exceeding the max chunk size.

Without this change, the chunker will either emit all lines in
one message (if it fits in a single chunk) or else each line will be in
a separate message. This often creates a long series of short messages,
which doesn't transmit as quickly or display as compact.

Instead, this consolidates as many lines as possible into each
message, while being sure to stay within the chunk size limit.
This should reduce the load on the mesh, and it's also more readable.
2025-07-14 12:27:14 -06:00
SpudGunMan
0a260b28b6 Update llm.py
last time?
2025-06-26 19:30:33 -07:00
SpudGunMan
3f5c6f2e9a Update llm.py 2025-06-26 19:03:13 -07:00
SpudGunMan
8a4f7a904a Update llm.py 2025-06-26 18:49:16 -07:00
SpudGunMan
0bc3d392cf fix Interface logic
a condition where TCP interfaces can fail leaving a none condition. this should resolve the errored interface better.
2025-06-25 07:30:46 -07:00
SpudGunMan
5eaef8b5b8 Update sysEnv.sh
enhance with git update check

Co-Authored-By: Johannes le Roux <dade@dade.co.za>
2025-06-25 07:25:04 -07:00
SpudGunMan
3a0007771d Update update.sh 2025-06-22 20:04:18 -07:00
SpudGunMan
67ba2b1fb5 Create update.sh 2025-06-22 19:59:31 -07:00
SpudGunMan
f2e7a9aa5c Update config.template 2025-06-18 12:15:44 -07:00
SpudGunMan
9d22270dde highfly_ignoreList
some nodes have bad altimeters
2025-06-18 12:14:14 -07:00
12 changed files with 436 additions and 226 deletions

195
README.md
View File

@@ -72,23 +72,97 @@ Welcome to the Mesh Bot project! This feature-rich bot is designed to enhance yo
## 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), 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
### Quick Setup
#### Clone the Repository
If you dont have git you will need it `sudo apt-get install git`
```sh
git clone https://github.com/spudgunman/meshing-around
```
The code is under active development, so make sure to pull the latest changes regularly!
#### Quick setup
- **Automated Installation**: `install.sh` will automate optional venv and requirements installation.
- **Launch Script**: `launch.sh` only used in a venv install, to launch the bot and the report generator.
#### Docker Installation
## Full list of commands for the bot
### Networking
| Command | Description | ✅ Works Off-Grid |
|---------|-------------|-
| `ping`, `ack` | Return data for signal. Example: `ping 15 #DrivingI5` (activates auto-ping every 20 seconds for count 15) | ✅ |
| `cmd` | Returns the list of commands (the help message) | ✅ |
| `history` | Returns the last commands run by user(s) | ✅ |
| `lheard` | Returns the last 5 heard nodes with SNR. Can also use `sitrep` | ✅ |
| `motd` | Displays the message of the day or sets it. Example: `motd $New Message Of the day` | ✅ |
| `sysinfo` | Returns the bot node telemetry info | ✅ |
| `test` | used to test the limits of data transfer `test 4` sends data to the maxBuffer limit (default 220) | ✅ |
| `whereami` | Returns the address of the sender's location if known |
| `whoami` | Returns details of the node asking, also returned when position exchanged 📍 | ✅ |
| `whois` | Returns details known about node, more data with bbsadmin node | ✅ |
### Radio Propagation & Weather Forcasting
| Command | Description | |
|---------|-------------|-------------------
| `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`| |
| `solar` | Gives an idea of the x-ray flux | |
| `sun` and `moon` | Return info on rise and set local time | ✅ |
| `tide` | Returns the local tides (NOAA data source) | |
| `valert` | Returns USGS Volcano Data | |
| `wx` | Return local weather forecast, NOAA or Open Meteo (which also has `wxc` for metric and imperial) | |
| `wxa` and `wxalert` | Return NOAA alerts. Short title or expanded details | |
| `mwx` | Return the NOAA Coastal Marine Forcast data | |
### Bulletin Board & Mail
| Command | Description | |
|---------|-------------|-
| `bbshelp` | Returns the following help message | ✅ |
| `bbslist` | Lists the messages by ID and subject | ✅ |
| `bbsread` | Reads a message. Example: `bbsread #1` | ✅ |
| `bbspost` | Posts a message to the public board or sends a DM(Mail) Examples: `bbspost $subject #message`, `bbspost @nodeNumber #message`, `bbspost @nodeShortName #message` | ✅ |
| `bbsdelete` | Deletes a message. Example: `bbsdelete #4` | ✅ |
| `bbsinfo` | Provides stats on BBS delivery and messages (sysop) | ✅ |
| `bbslink` | Links Bulletin Messages between BBS Systems | ✅ |
| `email:` | Sends email to address on file for the node or `email: bob@test.net # hello from mesh` | |
| `sms:` | Send sms-email to multiple address on file | |
| `setemail`| Sets the email for easy communciations | |
| `setsms` | Adds the SMS-Email for quick communications | |
| `clearsms` | Clears all SMS-Emails on file for node | |
### Data Lookup
| Command | Description | |
|---------|-------------|-
| `askai` and `ask:` | Ask Ollama LLM AI for a response. Example: `askai what temp do I cook chicken` | ✅ |
| `messages` | Replays the last messages heard, like Store and Forward | ✅ |
| `readnews` | returns the contents of a file (news.txt, by default) via the chunker on air | ✅ |
| `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 | |
|---------|-------------|-
| `blackjack` | Plays Blackjack (Casino 21) | ✅ |
| `dopewars` | Plays the classic drug trader game | ✅ |
| `golfsim` | Plays a 9-hole Golf Simulator | ✅ |
| `hamtest` | FCC/ARRL Quiz `hamtest general` or `hamtest extra` and `score` | ✅ |
| `hangman` | Plays the classic word guess game | ✅ |
| `joke` | Tells a joke | ✅ |
| `lemonstand` | Plays the classic Lemonade Stand finance game | ✅ |
| `mastermind` | Plays the classic code-breaking game | ✅ |
| `videopoker` | Plays basic 5-card hold Video Poker | ✅ |
## Other Install Options
### Docker Installation - handy for windows
See further info on the [docker.md](script/docker/README.md)
#### Manual Install
### Manual Install
Install the required dependencies using pip:
```sh
pip install -r requirements.txt
@@ -148,7 +222,12 @@ enabled = True
lat = 48.50
lon = -123.0
UseMeteoWxAPI = True
riverListDefault = # NOAA Hydrology data, unique identifiers, LID or USGS ID
coastalEnabled = False # NOAA Coastal Data Enable NOAA Coastal Waters Forecasts and Tide
# Find the correct costal weather directory at https://tgftp.nws.noaa.gov/data/forecasts/marine/coastal/
# this map can help https://www.weather.gov/marine select location and then look at the 'Forecast-by-Zone Map'
myCoastalZone = https://tgftp.nws.noaa.gov/data/forecasts/marine/coastal/pz/pzz135.txt # myCoastalZone is the .txt file with the forecast data
castalForecastDays = 3 # number of data points to return, default is 3
```
### Module Settings
@@ -215,20 +294,21 @@ alert_interface = 1
To Alert on Mesh with the EAS API you can set the channels and enable, checks every 20min.
#### FEMA iPAWS/EAS and NINA
This uses USA: SAME, FIPS, ZIP code to locate the alerts in the feed. By default ignoring Test messages.
This uses USA: SAME, FIPS, to locate the alerts in the feed. By default ignoring Test messages.
```ini
eAlertBroadcastEnabled = False # Goverment IPAWS/CAP Alert Broadcast
eAlertBroadcastCh = 2,3 # Goverment Emergency IPAWS/CAP Alert Broadcast Channels
ignoreFEMAenable = True # Ignore any headline that includes followig word list
ignoreFEMAwords = test,exercise
# 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
# comma separated list of FIPS codes to trigger local alert. find your FIPS codes at https://en.wikipedia.org/wiki/Federal_Information_Processing_Standard_state_code
myFIPSList = 57,58,53
# find your SAME https://www.weather.gov/nwr/counties comma separated list of SAME code to further refine local alert.
mySAMEList = 053029,053073
# To use other country services enable only a single optional serivce
enableDEalerts = False # Use DE Alert Broadcast Data see template for filters
myRegionalKeysDE = 110000000000,120510000000
```
#### NOAA EAS
@@ -243,6 +323,20 @@ ignoreEASenable = True # Ignore any headline that includes followig word list
ignoreEASwords = test,advisory
```
#### USGS River flow data and Volcano alerts
Using the USGS water data page locate a water flow device, for example Columbia River at Vancouver, WA - USGS-14144700
Volcano Alerts use lat/long to determine ~1000km radius
```ini
[location]
# USGS Hydrology unique identifiers, LID or USGS ID https://waterdata.usgs.gov
riverListDefault = 14144700
# USGS Volcano alerts Enable USGS Volcano Alert Broadcast
volcanoAlertBroadcastEnabled = False
volcanoAlertBroadcastCh = 2
```
### Repeater Settings
A repeater function for two different nodes and cross-posting messages. The `repeater_channels` is a list of repeater channels that will be consumed and rebroadcast on the same number channel on the other device, node, or interface. Each node should have matching channel numbers. The channel names and PSK do not need to be the same on the nodes. Use this feature responsibly to avoid creating a feedback loop.
@@ -378,81 +472,6 @@ There is no direct support for MQTT in the code, however, reports from Discord a
~~There also seems to be a quicker way to enable MQTT by having your bot node with the enabled [serial](https://meshtastic.org/docs/configuration/module/serial/) module with echo enabled and MQTT uplink and downlink. These two~~
## Full list of commands for the bot
### Networking
| Command | Description | ✅ Works Off-Grid |
|---------|-------------|-
| `ping`, `ack` | Return data for signal. Example: `ping 15 #DrivingI5` (activates auto-ping every 20 seconds for count 15) | ✅ |
| `cmd` | Returns the list of commands (the help message) | ✅ |
| `history` | Returns the last commands run by user(s) | ✅ |
| `lheard` | Returns the last 5 heard nodes with SNR. Can also use `sitrep` | ✅ |
| `motd` | Displays the message of the day or sets it. Example: `motd $New Message Of the day` | ✅ |
| `sysinfo` | Returns the bot node telemetry info | ✅ |
| `test` | used to test the limits of data transfer `test 4` sends data to the maxBuffer limit (default 220) | ✅ |
| `whereami` | Returns the address of the sender's location if known |
| `whoami` | Returns details of the node asking, also returned when position exchanged 📍 | ✅ |
| `whois` | Returns details known about node, more data with bbsadmin node | ✅ |
### Radio Propagation & Weather Forcasting
| Command | Description | |
|---------|-------------|-------------------
| `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`| |
| `solar` | Gives an idea of the x-ray flux | |
| `sun` and `moon` | Return info on rise and set local time | ✅ |
| `tide` | Returns the local tides (NOAA data source) | |
| `valert` | Returns USGS Volcano Data | |
| `wx` and `wxc` | Return local weather forecast (wxc is metric value), NOAA or Open Meteo for weather forecasting | |
| `wxa` and `wxalert` | Return NOAA alerts. Short title or expanded details | |
### Bulletin Board & Mail
| Command | Description | |
|---------|-------------|-
| `bbshelp` | Returns the following help message | ✅ |
| `bbslist` | Lists the messages by ID and subject | ✅ |
| `bbsread` | Reads a message. Example: `bbsread #1` | ✅ |
| `bbspost` | Posts a message to the public board or sends a DM(Mail) Examples: `bbspost $subject #message`, `bbspost @nodeNumber #message`, `bbspost @nodeShortName #message` | ✅ |
| `bbsdelete` | Deletes a message. Example: `bbsdelete #4` | ✅ |
| `bbsinfo` | Provides stats on BBS delivery and messages (sysop) | ✅ |
| `bbslink` | Links Bulletin Messages between BBS Systems | ✅ |
| `email:` | Sends email to address on file for the node or `email: bob@test.net # hello from mesh` | |
| `sms:` | Send sms-email to multiple address on file | |
| `setemail`| Sets the email for easy communciations | |
| `setsms` | Adds the SMS-Email for quick communications | |
| `clearsms` | Clears all SMS-Emails on file for node | |
### Data Lookup
| Command | Description | |
|---------|-------------|-
| `askai` and `ask:` | Ask Ollama LLM AI for a response. Example: `askai what temp do I cook chicken` | ✅ |
| `messages` | Replays the last messages heard, like Store and Forward | ✅ |
| `readnews` | returns the contents of a file (news.txt, by default) via the chunker on air | ✅ |
| `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 | |
|---------|-------------|-
| `blackjack` | Plays Blackjack (Casino 21) | ✅ |
| `dopewars` | Plays the classic drug trader game | ✅ |
| `golfsim` | Plays a 9-hole Golf Simulator | ✅ |
| `hamtest` | FCC/ARRL Quiz `hamtest general` or `hamtest extra` and `score` | ✅ |
| `hangman` | Plays the classic word guess game | ✅ |
| `joke` | Tells a joke | ✅ |
| `lemonstand` | Plays the classic Lemonade Stand finance game | ✅ |
| `mastermind` | Plays the classic code-breaking game | ✅ |
| `videopoker` | Plays basic 5-card hold Video Poker | ✅ |
# Recognition
I used ideas and snippets from other responder bots and want to call them out!

View File

@@ -87,6 +87,9 @@ sysloglevel = DEBUG
# Number of log files to keep in days, 0 to keep all
log_backup_count = 32
#Do not retry enabling interface if it fails, just exit to let OS restart the bot
dont_retry_disconnect = False
[emergencyHandler]
# enable or disable the emergency response handler
enabled = False
@@ -105,13 +108,16 @@ SentryChannel = 2
# holdoff time multiplied by seconds(20) of the watchdog
SentryHoldoff = 9
# list of ignored nodes numbers ex: 2813308004,4258675309
sentryIgnoreList =
sentryIgnoreList =
# HighFlying Node alert
highFlyingAlert = True
# Altitude in meters to trigger the alert
highFlyingAlertAltitude = 2000
# Channel to send Alert when the high flying node is detected
highFlyingAlertChannel = 2
# list of nodes numbers to ignore high flying alert ex: 2813308004,4258675309
highFlyingIgnoreList =
[bbs]
enabled = True
@@ -144,7 +150,17 @@ NOAAalertCount = 2
# use Open-Meteo API for weather data not NOAA useful for non US locations
UseMeteoWxAPI = False
# NOAA Hydrology unique identifiers, LID or USGS ID
# NOAA Coastal Data Enable NOAA Coastal Waters Forecasts and Tide
coastalEnabled = False
# Find the correct costal weather directory at https://tgftp.nws.noaa.gov/data/forecasts/marine/coastal/
# pz = Puget Sound, ph = Honolulu HI, gm = Florida Keys, pk = Alaska
# this map can help https://www.weather.gov/marine select location and then look at the 'Forecast-by-Zone Map'
# myCoastalZone is the .txt file with the forecast data
myCoastalZone = https://tgftp.nws.noaa.gov/data/forecasts/marine/coastal/pz/pzz135.txt
# number of data points to return, default is 3
coastalForecastDays = 3
# USGS Hydrology unique identifiers, LID or USGS ID https://waterdata.usgs.gov
riverListDefault =
# NOAA EAS Alert Broadcast
@@ -159,16 +175,15 @@ enableExtraLocationWx = False
# Goverment Alert Broadcast defaults to FEMA IPAWS
eAlertBroadcastEnabled = False
# comma separated list of FIPS codes to trigger local alert. find your FIPS codes at https://en.wikipedia.org/wiki/Federal_Information_Processing_Standard_state_code
myFIPSList = 57,58,53
# find your SAME https://www.weather.gov/nwr/counties comma separated list of SAME code to further refine local alert.
mySAMEList = 053029,053073
# Goverment Alert Broadcast Channels
eAlertBroadcastCh = 2
# FEMA Alert Broadcast Settings
# Enable Ignore any headline that includes following word list
# Enable Ignore, headline that includes following word list
ignoreFEMAenable = True
ignoreFEMAwords = test,exercise
# 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
# USGS Volcano alerts Enable USGS Volcano Alert Broadcast
volcanoAlertBroadcastEnabled = False
@@ -292,10 +307,10 @@ hangman = True
hamtest = True
[messagingSettings]
# delay in seconds for response to avoid message collision
responseDelay = 1.2
# delay in seconds for splits in messages to avoid message collision
splitDelay = 0.0
# delay in seconds for response to avoid message collision /throttling
responseDelay = 2.2
# delay in seconds for splits in messages to avoid message collision /throttling
splitDelay = 2.5
# message chunk size for sending at high success rate, chunkr allows exceeding by 3 characters
MESSAGE_CHUNK_SIZE = 160
# Request Acknowledgement of message OTA

View File

@@ -356,5 +356,5 @@ exit 0
# after install shenannigans
# add 'bee = True' to config.ini General section. You will likley want to clean the txt up a bit
# wget https://courses.cs.washington.edu/courses/cse163/20wi/files/lectures/L04/bee-movie.txt -O bee.txt
# add 'bee = True' to config.ini General section.
# wget https://gist.github.com/MattIPv4/045239bc27b16b2bcf7a3a9a4648c08a -O bee.txt

View File

@@ -67,6 +67,7 @@ def auto_response(message, snr, rssi, hop, pkiStatus, message_from_id, channel_n
"messages": lambda: handle_messages(message, deviceID, channel_number, msg_history, publicChannel, isDM),
"moon": lambda: handle_moon(message_from_id, deviceID, channel_number),
"motd": lambda: handle_motd(message, message_from_id, isDM),
"mwx": lambda: handle_mwx(message_from_id, deviceID, channel_number),
"ping": lambda: handle_ping(message_from_id, deviceID, message, hop, snr, rssi, isDM, channel_number),
"pinging": lambda: handle_ping(message_from_id, deviceID, message, hop, snr, rssi, isDM, channel_number),
"pong": lambda: "🏓PING!!🛜",
@@ -750,6 +751,12 @@ def handle_riverFlow(message, message_from_id, deviceID):
msg = get_flood_noaa(location[0], location[1], userRiver)
return msg
def handle_mwx(message_from_id, deviceID, cmd):
# NOAA Coastal and Marine Weather
if myCoastalZone is None:
logger.warning("System: Coastal Zone not set, please set in config.ini")
return NO_ALERTS
return get_nws_marine(zone=myCoastalZone, days=castalForecastDays)
def handle_wxc(message_from_id, deviceID, cmd):
location = get_node_location(message_from_id, deviceID)
@@ -1107,16 +1114,15 @@ def onReceive(packet, interface):
if rxType == 'TCPInterface':
rxHost = interface.__dict__.get('hostname', 'unknown')
if hostname1 in rxHost and interface1_type == 'tcp': rxNode = 1
elif multiple_interface and hostname2 in rxHost and interface2_type == 'tcp': rxNode = 2
elif multiple_interface and hostname3 in rxHost and interface3_type == 'tcp': rxNode = 3
elif multiple_interface and hostname4 in rxHost and interface4_type == 'tcp': rxNode = 4
elif multiple_interface and hostname5 in rxHost and interface5_type == 'tcp': rxNode = 5
elif multiple_interface and hostname6 in rxHost and interface6_type == 'tcp': rxNode = 6
elif multiple_interface and hostname7 in rxHost and interface7_type == 'tcp': rxNode = 7
elif multiple_interface and hostname8 in rxHost and interface8_type == 'tcp': rxNode = 8
elif multiple_interface and hostname9 in rxHost and interface9_type == 'tcp': rxNode = 9
if rxHost and hostname1 in rxHost and interface1_type == 'tcp': rxNode = 1
elif multiple_interface and rxHost and hostname2 in rxHost and interface2_type == 'tcp': rxNode = 2
elif multiple_interface and rxHost and hostname3 in rxHost and interface3_type == 'tcp': rxNode = 3
elif multiple_interface and rxHost and hostname4 in rxHost and interface4_type == 'tcp': rxNode = 4
elif multiple_interface and rxHost and hostname5 in rxHost and interface5_type == 'tcp': rxNode = 5
elif multiple_interface and rxHost and hostname6 in rxHost and interface6_type == 'tcp': rxNode = 6
elif multiple_interface and rxHost and hostname7 in rxHost and interface7_type == 'tcp': rxNode = 7
elif multiple_interface and rxHost and hostname8 in rxHost and interface8_type == 'tcp': rxNode = 8
elif multiple_interface and rxHost and hostname9 in rxHost and interface9_type == 'tcp': rxNode = 9
if rxType == 'BLEInterface':
if interface1_type == 'ble': rxNode = 1
elif multiple_interface and interface2_type == 'ble': rxNode = 2
@@ -1225,7 +1231,7 @@ def onReceive(packet, interface):
isDM = True
# check if the message contains a trap word, DMs are always responded to
if (messageTrap(message_string) and not llm_enabled) or messageTrap(message_string.split()[0]):
# log the message to the message log
# log the message to stdout
logger.info(f"Device:{rxNode} Channel: {channel_number} " + CustomFormatter.green + f"Received DM: " + CustomFormatter.white + f"{message_string} " + CustomFormatter.purple +\
"From: " + CustomFormatter.white + f"{get_name_from_number(message_from_id, 'long', rxNode)}")
# respond with DM
@@ -1272,7 +1278,8 @@ def onReceive(packet, interface):
time.sleep(responseDelay)
# log the message to the message log
msgLogger.info(f"Device:{rxNode} Channel:{channel_number} | {get_name_from_number(message_from_id, 'long', rxNode)} | " + message_string.replace('\n', '-nl-'))
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-'))
else:
# message is on a channel
if messageTrap(message_string):
@@ -1398,6 +1405,8 @@ async def start_rx():
logger.debug("System: Location Telemetry Enabled using NOAA API")
if dad_jokes_enabled:
logger.debug("System: Dad Jokes Enabled!")
if coastalEnabled:
logger.debug("System: Coastal Forcast and Tide Enabled!")
if games_enabled:
logger.debug("System: Games Enabled!")
if wikipedia_enabled:
@@ -1427,7 +1436,10 @@ async def start_rx():
if wxAlertBroadcastEnabled:
logger.debug(f"System: Weather Alert Broadcast Enabled on channels {wxAlertBroadcastChannel}")
if emergencyAlertBrodcastEnabled:
logger.debug(f"System: Emergency Alert Broadcast Enabled on channels {emergencyAlertBroadcastCh}")
logger.debug(f"System: Emergency Alert Broadcast Enabled on channels {emergencyAlertBroadcastCh} for FIPS codes {myStateFIPSList}")
# check if the FIPS codes are set
if myStateFIPSList == ['']:
logger.warning(f"System: No FIPS codes set for iPAWS Alerts")
if emergency_responder_enabled:
logger.debug(f"System: Emergency Responder Enabled on channels {emergency_responder_alert_channel} for interface {emergency_responder_alert_interface}")
if volcanoAlertBroadcastEnabled:
@@ -1548,10 +1560,11 @@ async def main():
await asyncio.sleep(0.01)
try:
if __name__ == "__main__":
if __name__ == "__main__":
try:
asyncio.run(main())
except KeyboardInterrupt:
exit_handler()
pass
except KeyboardInterrupt:
exit_handler()
except SystemExit:
pass
# EOF

View File

@@ -146,6 +146,12 @@ def llm_query(input, nodeID=0, location_name=None):
googleResults = []
if not location_name:
location_name = "no location provided "
# remove askai: and ask: from the input
for trap in trap_list_llm:
if input.lower().startswith(trap):
input = input[len(trap):].strip()
break
# add the naughty list here to stop the function before we continue
# add a list of allowed nodes only to use the function

View File

@@ -9,7 +9,7 @@ import bs4 as bs # pip install beautifulsoup4
import xml.dom.minidom
from modules.log import *
trap_list_location = ("whereami", "tide", "wx", "wxc", "wxa", "wxalert", "rlist", "ea", "ealert", "riverflow","valert")
trap_list_location = ("whereami", "wx", "wxa", "wxalert", "rlist", "ea", "ealert", "riverflow", "valert")
def where_am_i(lat=0, lon=0, short=False, zip=False):
whereIam = ""
@@ -453,7 +453,7 @@ def getActiveWeatherAlertsDetailNOAA(lat=0, lon=0):
alerts = alerts.split("\n***\n")[:numWxAlerts]
if alerts == "" or alerts == ['']:
return ERROR_FETCHING_DATA
return NO_ALERTS
# trim off last newline
if alerts[-1] == "\n":
@@ -472,8 +472,6 @@ def getIpawsAlert(lat=0, lon=0, shortAlerts = False):
# set the API URL for IPAWS
namespace = "urn:oasis:names:tc:emergency:cap:1.2"
alert_url = "https://apps.fema.gov/IPAWSOPEN_EAS_SERVICE/rest/feed"
if ipawsPIN != "000000":
alert_url += "?pin=" + ipawsPIN
# get the alerts from FEMA
try:
@@ -491,10 +489,25 @@ def getIpawsAlert(lat=0, lon=0, shortAlerts = False):
# extract alerts from main feed
for entry in alertxml.getElementsByTagName("entry"):
link = entry.getElementsByTagName("link")[0].getAttribute("href")
## state FIPS
## This logic is being added to reduce load on FEMA server.
stateFips = None
for cat in entry.getElementsByTagName("category"):
if cat.getAttribute("label") == "statefips":
stateFips = cat.getAttribute("term")
break
if stateFips is None:
# no stateFIPS found — skip
continue
# check if it matches your list
if stateFips not in myStateFIPSList:
#logger.debug(f"Skipping FEMA record link {link} with stateFIPS code of: {stateFips} because it doesn't match our StateFIPSList {myStateFIPSList}")
continue # skip to next entry
try:
#pin check
if ipawsPIN != "000000":
link += "?pin=" + ipawsPIN
# get the linked alert data from FEMA
linked_data = requests.get(link, timeout=urlTimeoutSeconds)
if not linked_data.ok or not linked_data.text.strip():
@@ -515,6 +528,10 @@ def getIpawsAlert(lat=0, lon=0, shortAlerts = False):
continue
for info in linked_xml.getElementsByTagName("info"):
# only get en-US language alerts (alternative is es-US)
language_nodes = info.getElementsByTagName("language")
if not any(node.firstChild and node.firstChild.nodeValue.strip() == "en-US" for node in language_nodes):
continue # skip if not en-US
# extract values from XML
sameVal = "NONE"
geocode_value = "NONE"
@@ -532,32 +549,31 @@ def getIpawsAlert(lat=0, lon=0, shortAlerts = False):
area_table = info.getElementsByTagName("area")[0]
areaDesc = area_table.getElementsByTagName("areaDesc")[0].childNodes[0].nodeValue
geocode_table = area_table.getElementsByTagName("geocode")[0]
geocode_type = geocode_table.getElementsByTagName("valueName")[0].childNodes[0].nodeValue
geocode_value = geocode_table.getElementsByTagName("value")[0].childNodes[0].nodeValue
if geocode_type == "SAME":
sameVal = geocode_value
except Exception as e:
logger.debug(f"System: iPAWS Error extracting alert data: {link}")
#print(f"DEBUG: {info.toprettyxml()}")
continue
# check if the alert is for the current location, if wanted keep alert
if (sameVal in mySAME) or (geocode_value in mySAME):
# check if the alert is for the SAME location, if wanted keep alert
if (sameVal in mySAMEList) or (geocode_value in mySAMEList) or mySAMEList == ['']:
# ignore the FEMA test alerts
if ignoreFEMAenable:
ignore_alert = False
for word in ignoreFEMAwords:
if word.lower() in headline.lower():
logger.debug(f"System: Ignoring FEMA Alert: {headline} containing {word} at {areaDesc}")
logger.debug(f"System: Filtering FEMA Alert by WORD: {headline} containing {word} at {areaDesc}")
ignore_alert = True
break
if ignore_alert:
continue
if ignore_alert:
continue
# add to alerts list
# add to alert list
alerts.append({
'alertType': alertType,
'alertCode': alertCode,
@@ -567,10 +583,10 @@ def getIpawsAlert(lat=0, lon=0, shortAlerts = False):
'geocode_value': geocode_value,
'description': description
})
# else:
# # these are discarded some day but logged for debugging currently
# logger.debug(f"Debug iPAWS: Type:{alertType} Code:{alertCode} Desc:{areaDesc} GeoType:{geocode_type} GeoVal:{geocode_value}, Headline:{headline}")
else:
logger.debug(f"System: iPAWS Alert not in SAME List: {sameVal} or {geocode_value} for {headline} at {areaDesc}")
continue
# return the numWxAlerts of alerts
if len(alerts) > 0:
for alertItem in alerts[:numWxAlerts]:
@@ -684,4 +700,60 @@ def get_volcano_usgs(lat=0, lon=0):
alerts = abbreviate_noaa(alerts)
return alerts
def get_nws_marine(zone, days=3):
# forcast from NWS coastal products
try:
marine_pzz_data = requests.get(zone, timeout=urlTimeoutSeconds)
if not marine_pzz_data.ok:
logger.warning("Location:Error fetching NWS Marine PZ data")
return ERROR_FETCHING_DATA
except (requests.exceptions.RequestException):
logger.warning("Location:Error fetching NWS Marine PZ data")
return ERROR_FETCHING_DATA
marine_pzz_data = marine_pzz_data.text
#validate data
todayDate = today.strftime("%Y%m%d")
if marine_pzz_data.startswith("Expires:"):
expires = marine_pzz_data.split(";;")[0].split(":")[1]
expires_date = expires[:8]
if expires_date < todayDate:
logger.debug("Location: NWS Marine PZ data expired")
return ERROR_FETCHING_DATA
else:
logger.debug("Location: NWS Marine PZ data not valid")
return ERROR_FETCHING_DATA
# process the marine forecast data
marine_pzz_lines = marine_pzz_data.split("\n")
marine_pzz_report = ""
day_blocks = []
current_block = ""
in_forecast = False
for line in marine_pzz_lines:
if line.startswith(".") and "..." in line:
in_forecast = True
if current_block:
day_blocks.append(current_block.strip())
current_block = ""
current_block += line.strip() + " "
elif in_forecast and line.strip() != "":
current_block += line.strip() + " "
if current_block:
day_blocks.append(current_block.strip())
# Only keep up to pzzDays blocks
for block in day_blocks[:days]:
marine_pzz_report += block + "\n"
# remove last newline
if marine_pzz_report.endswith("\n"):
marine_pzz_report = marine_pzz_report[:-1]
# abbreviate the report
marine_pzz_report = abbreviate_noaa(marine_pzz_report)
if marine_pzz_report == "":
return NO_DATA_NOGPS
return marine_pzz_report

View File

@@ -83,18 +83,14 @@ if log_messages_to_file:
# Pretty Timestamp
def getPrettyTime(seconds):
# convert unix time to minutes, hours, or days, or years for simple display
designator = "s"
if seconds > 0:
seconds = round(seconds / 60)
designator = "m"
if seconds > 60:
seconds = round(seconds / 60)
designator = "h"
if seconds > 24:
seconds = round(seconds / 24)
designator = "d"
if seconds > 365:
seconds = round(seconds / 365)
designator = "y"
return str(seconds) + designator
# convert unix time to minutes, hours, days, or years for simple display
if seconds < 60:
return f"{int(seconds)}s"
elif seconds < 3600:
return f"{int(round(seconds / 60))}m"
elif seconds < 86400:
return f"{int(round(seconds / 3600))}h"
elif seconds < 31536000:
return f"{int(round(seconds / 86400))}d"
else:
return f"{int(round(seconds / 31536000))}y"

View File

@@ -21,8 +21,7 @@ ping_enabled = True # ping feature to respond to pings, ack's etc.
sitrep_enabled = True # sitrep feature to respond to sitreps
lastHamLibAlert = 0 # last alert from hamlib
lastFileAlert = 0 # last alert from file monitor
max_retry_count1 = 4 # max retry count for interface 1
max_retry_count2 = 4 # max retry count for interface 2
max_retry_count1 = max_retry_count2 = max_retry_count3 = max_retry_count4 = max_retry_count5 = max_retry_count6 = max_retry_count7 = max_retry_count8 = max_retry_count9 = 4 # default retry count for interfaces
retry_int1 = False
retry_int2 = False
wiki_return_limit = 3 # limit the number of sentences returned off the first paragraph first hit
@@ -223,6 +222,7 @@ try:
llmModel = config['general'].get('ollamaModel', 'gemma2:2b') # default gemma2:2b
ollamaHostName = config['general'].get('ollamaHostName', 'http://localhost:11434') # default localhost
llmReplyToNonCommands = config['general'].getboolean('llmReplyToNonCommands', True)
dont_retry_disconnect = config['general'].getboolean('dont_retry_disconnect', False) # default False, retry on disconnect
# emergency response
emergency_responder_enabled = config['emergencyHandler'].getboolean('enabled', False)
emergency_responder_alert_channel = config['emergencyHandler'].getint('alert_channel', 2) # default 2
@@ -239,6 +239,7 @@ try:
highfly_enabled = config['sentry'].getboolean('highFlyingAlert', True) # default True
highfly_altitude = config['sentry'].getint('highFlyingAlertAltitude', 2000) # default 2000 meters
highfly_channel = config['sentry'].getint('highFlyingAlertChannel', 2) # default 2
highfly_ignoreList = config['sentry'].get('highFlyingIgnoreList', '').split(',') # default empty
# location
location_enabled = config['location'].getboolean('enabled', True)
@@ -250,6 +251,10 @@ try:
n2yoAPIKey = config['location'].get('n2yoAPIKey', '') # default empty
satListConfig = config['location'].get('satList', '25544').split(',') # default 25544 ISS
riverListDefault = config['location'].get('riverList', '').split(',') # default 12061500 Skagit River
coastalEnabled = config['location'].getboolean('coastalEnabled', False) # default False
myCoastalZone = config['location'].get('myCoastalZone', None) # default None
coastalForecastDays = config['location'].getint('coastalForecastDays', 3) # default 3 days
# location alerts
emergencyAlertBrodcastEnabled = config['location'].getboolean('eAlertBroadcastEnabled', False) # default False
wxAlertBroadcastEnabled = config['location'].getboolean('wxAlertBroadcastEnabled', False) # default False
@@ -258,12 +263,12 @@ try:
wxAlertsEnabled = config['location'].getboolean('NOAAalertsEnabled', True) # default True
ignoreEASenable = config['location'].getboolean('ignoreEASenable', False) # default False
ignoreEASwords = config['location'].get('ignoreEASwords', 'test,advisory').split(',') # default test,advisory
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
ipawsPIN = config['location'].get('ipawsPIN', '000000') # default 000000
myStateFIPSList = config['location'].get('myFIPSList', '').split(',') # default empty
mySAMEList = config['location'].get('mySAMEList', '').split(',') # default empty
ignoreFEMAenable = config['location'].getboolean('ignoreFEMAenable', True) # default True
ignoreFEMAwords = config['location'].get('ignoreFEMAwords', 'test,exercise').split(',') # default test,exercise
wxAlertBroadcastChannel = config['location'].get('wxAlertBroadcastCh', '2').split(',') # default Channel 2

View File

@@ -18,6 +18,7 @@ help_message = "Bot CMD?:"
asyncLoop = asyncio.new_event_loop()
games_enabled = False
multiPingList = [{'message_from_id': 0, 'count': 0, 'type': '', 'deviceID': 0, 'channel_number': 0, 'startCount': 0}]
interface_retry_count = 3
# Ping Configuration
if ping_enabled:
@@ -69,12 +70,12 @@ else:
if enableCmdHistory:
trap_list = trap_list + ("history",)
#help_message = help_message + ", history"
# Location Configuration
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"
trap_list = trap_list + trap_list_location
help_message = help_message + ", whereami, wx"
if enableGBalerts and not enableDEalerts:
from modules.globalalert import * # from the spudgunman/meshing-around repo
logger.warning(f"System: GB Alerts not functional at this time need to find a source API")
@@ -86,17 +87,25 @@ if location_enabled:
# Open-Meteo Configuration for worldwide weather
if use_meteo_wxApi:
trap_list = trap_list + ("wxc",)
help_message = help_message + ", wxc"
from modules.wx_meteo import * # from the spudgunman/meshing-around repo
else:
# NOAA only features
help_message = help_message + ", wxa, tide"
help_message = help_message + ", wxa"
# NOAA alerts needs location module
if wxAlertBroadcastEnabled or emergencyAlertBrodcastEnabled or volcanoAlertBroadcastEnabled:
from modules.locationdata import * # from the spudgunman/meshing-around repo
# limited subset, this should be done better but eh..
trap_list = trap_list + ("wx", "wxc", "wxa", "wxalert", "ea", "ealert", "valert")
trap_list = trap_list + ("wx", "wxa", "wxalert", "ea", "ealert", "valert")
help_message = help_message + ", wxalert, ealert, valert"
# NOAA Coastal Waters Forecasts
if coastalEnabled:
from modules.locationdata import * # from the spudgunman/meshing-around repo
trap_list = trap_list + ("mwx","tide",)
help_message = help_message + ", mwx, tide"
# BBS Configuration
if bbs_enabled:
@@ -255,6 +264,7 @@ if ble_count > 1:
logger.debug(f"System: Initializing Interfaces")
interface1 = interface2 = interface3 = interface4 = interface5 = interface6 = interface7 = interface8 = interface9 = None
retry_int1 = retry_int2 = retry_int3 = retry_int4 = retry_int5 = retry_int6 = retry_int7 = retry_int8 = retry_int9 = False
max_retry_count1 = max_retry_count2 = max_retry_count3 = max_retry_count4 = max_retry_count5 = max_retry_count6 = max_retry_count7 = max_retry_count8 = max_retry_count9 = interface_retry_count
for i in range(1, 10):
interface_type = globals().get(f'interface{i}_type')
if not interface_type or interface_type == 'none' or globals().get(f'interface{i}_enabled') == False:
@@ -540,6 +550,15 @@ def messageChunker(message):
if current_chunk:
message_list.append(current_chunk)
# Consolidate any adjacent messages that can fit in a single chunk.
idx = 0
while idx < len(message_list) - 1:
if len(message_list[idx]) + len(message_list[idx+1]) < MESSAGE_CHUNK_SIZE:
message_list[idx] += '\n' + message_list[idx+1]
del message_list[idx+1]
else:
idx += 1
# Ensure no chunk exceeds MESSAGE_CHUNK_SIZE
final_message_list = []
for chunk in message_list:
@@ -779,7 +798,7 @@ def handleAlertBroadcast(deviceID=1):
send_message(ukAlert, emergencyAlertBroadcastCh, 0, deviceID)
return True
if NO_ALERTS not in deAlert:
if NO_ALERTS not in alertDe:
if isinstance(emergencyAlertBroadcastCh, list):
for channel in emergencyAlertBroadcastCh:
send_message(ukAlert, int(channel), 0, deviceID)
@@ -816,40 +835,9 @@ def handleAlertBroadcast(deviceID=1):
return True
def onDisconnect(interface):
global retry_int1, retry_int2, retry_int3, retry_int4, retry_int5, retry_int6, retry_int7, retry_int8, retry_int9
rxType = type(interface).__name__
if rxType in ['SerialInterface', 'TCPInterface', 'BLEInterface']:
identifier = interface.__dict__.get('devPath', interface.__dict__.get('hostname', 'BLE'))
logger.critical(f"System: Lost Connection to Device {identifier}")
for i in range(1, 10):
if globals().get(f'interface{i}_enabled'):
if (rxType == 'SerialInterface' and globals().get(f'port{i}') in identifier) or \
(rxType == 'TCPInterface' and globals().get(f'hostname{i}') in identifier) or \
(rxType == 'BLEInterface' and globals().get(f'interface{i}_type') == 'ble'):
globals()[f'retry_int{i}'] = True
break
def exit_handler():
# Close the interface and save the BBS messages
logger.debug(f"System: Closing Autoresponder")
try:
logger.debug(f"System: Closing Interface1")
interface1.close()
if multiple_interface:
for i in range(2, 10):
if globals().get(f'interface{i}_enabled'):
logger.debug(f"System: Closing Interface{i}")
globals()[f'interface{i}'].close()
except Exception as e:
logger.error(f"System: closing: {e}")
if bbs_enabled:
save_bbsdb()
save_bbsdm()
logger.debug(f"System: BBS Messages Saved")
logger.debug(f"System: Exiting")
asyncLoop.stop()
asyncLoop.close()
exit (0)
# Handle disconnection of the interface
logger.warning(f"System: Abrupt Disconnection of Interface detected")
interface.close()
# Telemetry Functions
telemetryData = {}
@@ -979,10 +967,11 @@ def consumeMetadata(packet, rxNode=0):
for key in keys:
positionMetadata[nodeID][key] = position_data.get(key, 0)
# if altitude is over 2000 send a log and message for high-flying nodes
if position_data.get('altitude', 0) > highfly_altitude and highfly_enabled:
# if altitude is over 2000 send a log and message for high-flying nodes and not in highfly_ignoreList
if position_data.get('altitude', 0) > highfly_altitude and highfly_enabled and str(nodeID) not in highfly_ignoreList:
logger.info(f"System: High Altitude {position_data['altitude']}m on Device: {rxNode} NodeID: {nodeID}")
send_message(f"High Altitude {position_data['altitude']}m on Device:{rxNode} Node:{get_name_from_number(nodeID,'short',rxNode)}", highfly_channel, 0, rxNode)
altFeet = round(position_data['altitude'] * 3.28084, 2)
send_message(f"High Altitude {altFeet}ft ({position_data['altitude']}m) on Device:{rxNode} Node:{get_name_from_number(nodeID,'short',rxNode)}", highfly_channel, 0, rxNode)
time.sleep(responseDelay)
# Keep the positionMetadata dictionary at a maximum size of 20
@@ -1128,21 +1117,26 @@ async def handleFileWatcher():
pass
async def retry_interface(nodeID):
global max_retry_count
global retry_int1, retry_int2, retry_int3, retry_int4, retry_int5, retry_int6, retry_int7, retry_int8, retry_int9
global max_retry_count1, max_retry_count2, max_retry_count3, max_retry_count4, max_retry_count5, max_retry_count6, max_retry_count7, max_retry_count8, max_retry_count9
interface = globals()[f'interface{nodeID}']
retry_int = globals()[f'retry_int{nodeID}']
max_retry_count = globals()[f'max_retry_count{nodeID}']
if dont_retry_disconnect:
logger.critical(f"System: dont_retry_disconnect is set, not retrying interface{nodeID}")
exit_handler()
if interface is not None:
retry_int = True
max_retry_count -= 1
globals()[f'retry_int{nodeID}'] = True
globals()[f'max_retry_count{nodeID}'] -= 1
logger.debug(f"System: Retrying interface{nodeID} {globals()[f'max_retry_count{nodeID}']} attempts left")
try:
interface.close()
logger.debug(f"System: Retrying interface{nodeID} in 15 seconds")
except Exception as e:
logger.error(f"System: closing interface{nodeID}: {e}")
logger.debug(f"System: Retrying interface{nodeID} in 15 seconds")
if max_retry_count == 0:
if globals()[f'max_retry_count{nodeID}'] == 0:
logger.critical(f"System: Max retry count reached for interface{nodeID}")
exit_handler()
@@ -1152,15 +1146,19 @@ async def retry_interface(nodeID):
if retry_int:
interface = None
globals()[f'interface{nodeID}'] = None
logger.debug(f"System: Retrying Interface{nodeID}")
interface_type = globals()[f'interface{nodeID}_type']
if interface_type == 'serial':
logger.debug(f"System: Retrying Interface{nodeID} Serial on port: {globals().get(f'port{nodeID}')}")
globals()[f'interface{nodeID}'] = meshtastic.serial_interface.SerialInterface(globals().get(f'port{nodeID}'))
elif interface_type == 'tcp':
globals()[f'interface{nodeID}'] = meshtastic.tcp_interface.TCPInterface(globals().get(f'host{nodeID}'))
logger.debug(f"System: Retrying Interface{nodeID} TCP on hostname: {globals().get(f'hostname{nodeID}')}")
globals()[f'interface{nodeID}'] = meshtastic.tcp_interface.TCPInterface(globals().get(f'hostname{nodeID}'))
elif interface_type == 'ble':
logger.debug(f"System: Retrying Interface{nodeID} BLE on mac: {globals().get(f'mac{nodeID}')}")
globals()[f'interface{nodeID}'] = meshtastic.ble_interface.BLEInterface(globals().get(f'mac{nodeID}'))
logger.debug(f"System: Interface{nodeID} Opened!")
# reset the retry_int and retry_count
globals()[f'max_retry_count{nodeID}'] = interface_retry_count
globals()[f'retry_int{nodeID}'] = False
except Exception as e:
logger.error(f"System: Error Opening interface{nodeID} on: {e}")
@@ -1248,3 +1246,24 @@ async def watchdog():
except Exception as e:
logger.error(f"System: retrying interface{i}: {e}")
def exit_handler():
# Close the interface and save the BBS messages
logger.debug(f"System: Closing Autoresponder")
try:
logger.debug(f"System: Closing Interface1")
interface1.close()
if multiple_interface:
for i in range(2, 10):
if globals().get(f'interface{i}_enabled'):
logger.debug(f"System: Closing Interface{i}")
globals()[f'interface{i}'].close()
except Exception as e:
logger.error(f"System: closing: {e}")
if bbs_enabled:
save_bbsdb()
save_bbsdm()
logger.debug(f"System: BBS Messages Saved")
logger.debug(f"System: Exiting")
asyncLoop.stop()
asyncLoop.close()
exit (0)

View File

@@ -217,15 +217,15 @@ def onReceive(packet, interface):
if rxType == 'TCPInterface':
rxHost = interface.__dict__.get('hostname', 'unknown')
if hostname1 in rxHost and interface1_type == 'tcp': rxNode = 1
elif multiple_interface and hostname2 in rxHost and interface2_type == 'tcp': rxNode = 2
elif multiple_interface and hostname3 in rxHost and interface3_type == 'tcp': rxNode = 3
elif multiple_interface and hostname4 in rxHost and interface4_type == 'tcp': rxNode = 4
elif multiple_interface and hostname5 in rxHost and interface5_type == 'tcp': rxNode = 5
elif multiple_interface and hostname6 in rxHost and interface6_type == 'tcp': rxNode = 6
elif multiple_interface and hostname7 in rxHost and interface7_type == 'tcp': rxNode = 7
elif multiple_interface and hostname8 in rxHost and interface8_type == 'tcp': rxNode = 8
elif multiple_interface and hostname9 in rxHost and interface9_type == 'tcp': rxNode = 9
if rxHost and hostname1 in rxHost and interface1_type == 'tcp': rxNode = 1
elif multiple_interface and rxHost and hostname2 in rxHost and interface2_type == 'tcp': rxNode = 2
elif multiple_interface and rxHost and hostname3 in rxHost and interface3_type == 'tcp': rxNode = 3
elif multiple_interface and rxHost and hostname4 in rxHost and interface4_type == 'tcp': rxNode = 4
elif multiple_interface and rxHost and hostname5 in rxHost and interface5_type == 'tcp': rxNode = 5
elif multiple_interface and rxHost and hostname6 in rxHost and interface6_type == 'tcp': rxNode = 6
elif multiple_interface and rxHost and hostname7 in rxHost and interface7_type == 'tcp': rxNode = 7
elif multiple_interface and rxHost and hostname8 in rxHost and interface8_type == 'tcp': rxNode = 8
elif multiple_interface and rxHost and hostname9 in rxHost and interface9_type == 'tcp': rxNode = 9
if rxType == 'BLEInterface':
if interface1_type == 'ble': rxNode = 1
@@ -310,7 +310,7 @@ def onReceive(packet, interface):
isDM = True
# check if the message contains a trap word, DMs are always responded to
if (messageTrap(message_string) and not llm_enabled) or messageTrap(message_string.split()[0]):
# log the message to the message log
# log the message to stdout
logger.info(f"Device:{rxNode} Channel: {channel_number} " + CustomFormatter.green + f"Received DM: " + CustomFormatter.white + f"{message_string} " + CustomFormatter.purple +\
"From: " + CustomFormatter.white + f"{get_name_from_number(message_from_id, 'long', rxNode)}")
# respond with DM
@@ -321,7 +321,8 @@ def onReceive(packet, interface):
time.sleep(responseDelay)
# log the message to the message log
msgLogger.info(f"Device:{rxNode} Channel:{channel_number} | {get_name_from_number(message_from_id, 'long', rxNode)} | " + message_string.replace('\n', '-nl-'))
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-'))
else:
# message is on a channel
if messageTrap(message_string):
@@ -443,10 +444,11 @@ async def main():
await asyncio.sleep(0.01)
try:
if __name__ == "__main__":
if __name__ == "__main__":
try:
asyncio.run(main())
except KeyboardInterrupt:
exit_handler()
pass
except KeyboardInterrupt:
exit_handler()
except SystemExit:
pass
# EOF

View File

@@ -24,4 +24,21 @@ else
fi
# print telemetry data rounded to 2 decimal places
printf "Disk:%s RAM:%.2f%% CPU:%.2f%% CPU-T:%.2f°C (%.2f°F)\n" "$free_space" "$ram_usage" "$cpu_usage" "$temp" "$tempf"
printf "Disk:%s RAM:%.2f%% CPU:%.2f%% CPU-T:%.2f°C (%.2f°F)\n" "$free_space" "$ram_usage" "$cpu_usage" "$temp" "$tempf"
# attempt check for updates
if command -v git &> /dev/null
then
if [ -d ../.git ]; then
# check for updates
git fetch --quiet
local_branch=$(git rev-parse --abbrev-ref HEAD)
if [ "$local_branch" != "HEAD" ] && git show-ref --verify --quiet "refs/remotes/origin/$local_branch"; then
local_commit=$(git rev-parse "$local_branch")
remote_commit=$(git rev-parse "origin/$local_branch")
if [ "$local_commit" != "$remote_commit" ]; then
echo "Bot Update Available!"
fi
fi
fi
fi

46
update.sh Normal file
View File

@@ -0,0 +1,46 @@
#!/bin/bash
# MeshBot Update Script
# Usage: bash update.sh or ./update.sh after making it executable with chmod +x update.sh
# Check if the mesh_bot.service or pong_bot.service
if systemctl is-active --quiet mesh_bot.service; then
echo "Stopping mesh_bot.service..."
systemctl stop mesh_bot.service
fi
if systemctl is-active --quiet pong_bot.service; then
echo "Stopping pong_bot.service..."
systemctl stop pong_bot.service
fi
if systemctl is-active --quiet mesh_bot_reporting.service; then
echo "Stopping mesh_bot_reporting.service..."
systemctl stop mesh_bot_reporting.service
fi
if systemctl is-active --quiet mesh_bot_w3.service; then
echo "Stopping mesh_bot_w3.service..."
systemctl stop mesh_bot_w3.service
fi
# Update the local repository
echo "Updating local repository..."
#git fetch --all
#git reset --hard origin/main # Replace 'main' with your branch name if different
git pull origin main --rebase # Fetch and rebase to keep local changes if any
echo "Local repository updated."
# Install or update dependencies
echo "Installing or updating dependencies..."
pip install -r requirements.txt --upgrade
echo "Dependencies installed or updated."
# Restart the services
echo "Restarting services..."
systemctl start mesh_bot.service
systemctl start pong_bot.service
systemctl start mesh_bot_reporting.service
systemctl start mesh_bot_w3.service
echo "Services restarted."
# Print completion message
echo "Update completed successfully?"
exit 0
# End of script