73 Commits

Author SHA1 Message Date
Joel Krauska 8bea2bd744 ruff 2025-11-25 18:12:04 -08:00
Pablo Revilla 7edb7b5c38 Fix the net page as it was not showing the date information 2025-11-25 09:31:56 -08:00
Pablo Revilla 17a1265842 Fix the net page as it was not showing the date information 2025-11-25 09:05:37 -08:00
Pablo Revilla cc03d237bb Fix the net page as it was not showing the date information 2025-11-24 19:38:46 -08:00
Pablo Revilla 06cc15a03c Fix the net page as it was not showing the date information 2025-11-24 18:46:28 -08:00
Pablo Revilla 99cb5654e4 More changes... almost ready for release.
Ranamed 2 pages for easy or reading.
2025-11-24 16:11:11 -08:00
Pablo Revilla 3c8fa0185e Remamed new_node to node. shorter and descriptive. 2025-11-24 10:20:06 -08:00
Pablo Revilla 1e85aa01c6 Remamed new_node to node. shorter and descriptive. 2025-11-24 10:16:30 -08:00
Pablo Revilla 535c5c8ada Remamed new_node to node. shorter and descriptive. 2025-11-24 10:06:43 -08:00
Pablo Revilla 4a5b982e6f Remamed new_node to node. shorter and descriptive. 2025-11-24 09:25:09 -08:00
Pablo Revilla a71f371c85 Remamed new_node to node. shorter and descriptive. 2025-11-24 09:12:47 -08:00
Pablo Revilla 4150953b96 Remamed new_node to node. shorter and descriptive. 2025-11-24 09:09:51 -08:00
Pablo Revilla 0eed8f8001 Finishing up all the pages for the 3.0 release.
Now all pages are functional.
2025-11-21 15:41:25 -08:00
Pablo Revilla 14aabc3b10 Merge remote-tracking branch 'origin/dev-v3' into dev-v3 2025-11-21 13:47:37 -08:00
Pablo Revilla fc01cb6a85 Finishing up all the pages for the 3.0 release.
Now all pages are functional.
2025-11-21 13:47:22 -08:00
Joel Krauska 5214b80816 another compatibility fix when _us is empty and we need to sort by BOTH old and new 2025-11-21 12:24:11 -08:00
Joel Krauska 73cd325b35 Make the robots do our bidding 2025-11-21 12:07:14 -08:00
Joel Krauska f89686fb88 fix 0 epoch dates in /chat 2025-11-21 12:06:50 -08:00
Joel Krauska 0c89b3ec22 use sys.executable 2025-11-21 12:06:50 -08:00
Joel Krauska 9cd1975278 pyproject.toml requirements 2025-11-21 12:06:50 -08:00
Pablo Revilla 052a9460ca Finishing up all the pages for the 3.0 release.
Now all pages are functional.
2025-11-21 11:49:11 -08:00
Pablo Revilla af6bb0fa64 Merge remote-tracking branch 'origin/dev-v3' into dev-v3 2025-11-21 11:43:07 -08:00
Pablo Revilla 8fae62e51a Finishing up all the pages for the 3.0 release.
Now all pages are functional.
2025-11-21 11:41:50 -08:00
Joel Krauska 4af1aac6ec more ruff 2025-11-21 11:37:24 -08:00
Joel Krauska ed695684d9 fix ruff format 2025-11-21 11:36:48 -08:00
Pablo Revilla 5e0852e558 Finishing up all the pages for the 3.0 release.
Now all pages are functional.
2025-11-21 11:10:16 -08:00
Pablo Revilla e135630f8d Finishing up all the pages for the 3.0 release.
Now all pages are functional.
2025-11-21 10:57:18 -08:00
Pablo Revilla 5f5ae75d84 Worked on /api/packet. Needed to modify
- Added new api endpoint /api/packets_seen
- Modified web.py and store.py to support changes to APIs.
- Started to work on new_node.html and new_packet.html for presentation of data.
2025-11-19 11:18:34 -08:00
Pablo Revilla 39c0dd589d Worked on /api/packet. Needed to modify
- Added new api endpoint /api/packets_seen
- Modified web.py and store.py to support changes to APIs.
- Started to work on new_node.html and new_packet.html for presentation of data.
2025-11-13 15:28:45 -08:00
Pablo Revilla 7411c7e8ee Worked on /api/packet. Needed to modify
- Store.py to read the new time data
- api.py to present the new time data
- firehose.html chat.html and map.html now use the new apis and the time is the browser local time
2025-11-10 21:51:48 -08:00
Pablo Revilla 27daa92694 Worked on /api/packet. Needed to modify
- Store.py to read the new time data
- api.py to present the new time data
- firehose.html chat.html and map.html now use the new apis and the time is the browser local time
2025-11-10 21:33:50 -08:00
Óscar García Amor d4f251f1b6 Improves container build (#94) 2025-11-10 10:32:06 -08:00
Pablo Revilla ac4ac9264f Worked on /api/packet. Needed to modify
- Store.py to read the new time data
- api.py to present the new time data
- firehose.html chat.html and map.html now use the new apis and the time is the browser local time
2025-11-05 20:46:12 -08:00
Pablo Revilla 4a3f205d26 Worked on /api/packet. Needed to modify
- Store.py to read the new time data
- api.py to present the new time data
- firehose.html chat.html and map.html now use the new apis and the time is the browser local time
2025-11-05 19:07:23 -08:00
Joel Krauska 9fa874762e Add us first/last timestamps to node table too 2025-11-05 16:48:29 -08:00
Joel Krauska e0d8ceecac Alembic was blocking mqtt logs 2025-11-05 14:36:50 -08:00
Joel Krauska 67738105c8 Summary of 3.0.0 stuff 2025-11-04 21:33:08 -08:00
Joel Krauska 04e76ebd28 graphviz for dot in Container 2025-11-04 21:12:22 -08:00
Joel Krauska 04051bc00a setup-dev 2025-11-04 21:11:38 -08:00
Joel Krauska f903c82c79 Docker Docs 2025-11-04 21:04:20 -08:00
Joel Krauska 4b9dfba03d ruff 2025-11-04 20:57:58 -08:00
Joel Krauska 70f727a6dd backups and cleanups are different 2025-11-04 20:55:55 -08:00
Joel Krauska bc70b5c39d DB Backups 2025-11-04 20:51:50 -08:00
Jim Schrempp a65de73b3a Traceroute Return Path logged and displayed (#97)
* traceroute returns are now logged and /packetlist now graphs the correct data for a return route
* now using alembic to update schema
* HOWTO - Alembic

---------

Co-authored-by: Joel Krauska <jkrauska@gmail.com>
2025-11-04 20:36:24 -08:00
Joel Krauska cc053951b1 make /app owned by ap0p 2025-11-04 20:16:41 -08:00
Joel Krauska fcff4f5849 checkout and containerfile 2025-11-04 20:04:05 -08:00
Joel Krauska 4e9f121514 fix symlink 2025-11-04 20:00:58 -08:00
Joel Krauska c0ed5031e6 symlink 2025-11-04 20:00:01 -08:00
Joel Krauska a6b1e30d29 auto build containers 2025-11-04 19:57:49 -08:00
Joel Krauska 24de8e73fb Container using slim/uv 2025-11-04 19:51:19 -08:00
Joel Krauska b86af326af improve migrations and fix logging problem with mqtt 2025-11-04 16:27:18 -08:00
Joel Krauska 0a0ec5c45f remove unused loop 2025-11-03 20:23:21 -08:00
Joel Krauska 9ac045a1c5 fallback if missing config 2025-11-03 20:18:17 -08:00
Joel Krauska e343d6aa15 mvrun work 2025-11-03 20:13:36 -08:00
Óscar García Amor 4de92da1ae Set dbcleanup.log location configurable 2025-11-03 20:07:34 -08:00
Óscar García Amor 74369deaea Improves arguments in mvrun.py 2025-11-03 20:07:34 -08:00
Joel Krauska 44671a1358 vuln 2025-11-03 20:05:48 -08:00
Joel Krauska 64261d3bc4 ruff and docs 2025-11-03 20:05:04 -08:00
Joel Krauska fe59b42a53 break out api calls in to their own file to reduce footprint 2025-11-03 19:07:39 -08:00
Joel Krauska f8ed76b41e alembic log format 2025-11-03 15:07:17 -08:00
Joel Krauska 7d0d704412 health endpoint 2025-11-03 15:05:28 -08:00
Joel Krauska 991794ed3d ignore other database files 2025-11-03 15:00:05 -08:00
Joel Krauska 87ade281ba update api docs 2025-11-03 14:52:34 -08:00
Joel Krauska 4c3858958b rm 2025-11-03 14:50:55 -08:00
Joel Krauska 0139169c7d more doc tidy 2025-11-03 14:50:43 -08:00
Joel Krauska 0b438366f1 add readme in docs: 2025-11-03 14:48:09 -08:00
Joel Krauska 9e38a3a394 remove old migrate script 2025-11-03 14:47:45 -08:00
Joel Krauska cf55334165 move technical docs 2025-11-03 14:47:29 -08:00
Joel Krauska dda94aa2cb add /version json endpoint 2025-11-03 14:26:49 -08:00
Joel Krauska 64169787b3 modify alembic to support cleaner migrations 2025-11-03 14:11:42 -08:00
Joel Krauska fa28f6b63f Remove old index notes script -- no longer needed 2025-11-03 13:29:11 -08:00
Joel Krauska 5ca3b472a6 Store UTC int time in DB (#81)
* use UTC int time
2025-11-03 13:26:44 -08:00
Joel Krauska 8ec44ad552 Add alembic DB schema management (#86)
* Use alembic
* add creation helper
* example migration tool
2025-11-03 12:53:34 -08:00
53 changed files with 2352 additions and 2788 deletions
-30
View File
@@ -4,19 +4,6 @@
The project serves as a real-time monitoring and diagnostic tool for the Meshtastic mesh network. It provides detailed insights into network activity, including message traffic, node positions, and telemetry data. The project serves as a real-time monitoring and diagnostic tool for the Meshtastic mesh network. It provides detailed insights into network activity, including message traffic, node positions, and telemetry data.
### Version 3.0.1 — December 2025
#### 🌐 Multi-Language Support (i18n)
- New `/api/lang` endpoint for serving translations
- Section-based translation loading (e.g., `?section=firehose`)
- Default language controlled via config file language section
- JSON-based translation files for easy expansion
- Core pages updated to support `data-translate-lang` attributes
### 🛠 Improvements
- Updated UI elements across multiple templates for localization readiness
- General cleanup to support future language additions
### Version 3.0.0 update - November 2025 ### Version 3.0.0 update - November 2025
**Major Infrastructure Improvements:** **Major Infrastructure Improvements:**
@@ -97,20 +84,6 @@ Samples of currently running instances:
- https://meshview-salzburg.jmt.gr/ (Salzburg / Austria) - https://meshview-salzburg.jmt.gr/ (Salzburg / Austria)
--- ---
### Updating from 2.x to 3.x
We are adding the use of Alembic. If using GitHub
Update your codebase by running the pull command
```bash
cd meshview
git pull origin master
```
Install Alembic in your environment
```bash
./env/bin/pip install alembic
```
Start your scripts or services. This process will update your database with the latest schema.
## Installing ## Installing
### Using Docker (Recommended) ### Using Docker (Recommended)
@@ -211,9 +184,6 @@ acme_challenge =
# The domain name of your site. # The domain name of your site.
domain = domain =
# Select language (this represents the name of the json file in the /lang directory)
language = es
# Site title to show in the browser title bar and headers. # Site title to show in the browser title bar and headers.
title = Bay Area Mesh title = Bay Area Mesh
File diff suppressed because one or more lines are too long
+1 -7
View File
@@ -770,7 +770,6 @@ class SharedContact(google.protobuf.message.Message):
NODE_NUM_FIELD_NUMBER: builtins.int NODE_NUM_FIELD_NUMBER: builtins.int
USER_FIELD_NUMBER: builtins.int USER_FIELD_NUMBER: builtins.int
SHOULD_IGNORE_FIELD_NUMBER: builtins.int SHOULD_IGNORE_FIELD_NUMBER: builtins.int
MANUALLY_VERIFIED_FIELD_NUMBER: builtins.int
node_num: builtins.int node_num: builtins.int
""" """
The node number of the contact The node number of the contact
@@ -779,10 +778,6 @@ class SharedContact(google.protobuf.message.Message):
""" """
Add this contact to the blocked / ignored list Add this contact to the blocked / ignored list
""" """
manually_verified: builtins.bool
"""
Set the IS_KEY_MANUALLY_VERIFIED bit
"""
@property @property
def user(self) -> meshtastic.protobuf.mesh_pb2.User: def user(self) -> meshtastic.protobuf.mesh_pb2.User:
""" """
@@ -795,10 +790,9 @@ class SharedContact(google.protobuf.message.Message):
node_num: builtins.int = ..., node_num: builtins.int = ...,
user: meshtastic.protobuf.mesh_pb2.User | None = ..., user: meshtastic.protobuf.mesh_pb2.User | None = ...,
should_ignore: builtins.bool = ..., should_ignore: builtins.bool = ...,
manually_verified: builtins.bool = ...,
) -> None: ... ) -> None: ...
def HasField(self, field_name: typing.Literal["user", b"user"]) -> builtins.bool: ... def HasField(self, field_name: typing.Literal["user", b"user"]) -> builtins.bool: ...
def ClearField(self, field_name: typing.Literal["manually_verified", b"manually_verified", "node_num", b"node_num", "should_ignore", b"should_ignore", "user", b"user"]) -> None: ... def ClearField(self, field_name: typing.Literal["node_num", b"node_num", "should_ignore", b"should_ignore", "user", b"user"]) -> None: ...
global___SharedContact = SharedContact global___SharedContact = SharedContact
+2 -2
View File
@@ -15,14 +15,14 @@ from meshtastic.protobuf import channel_pb2 as meshtastic_dot_protobuf_dot_chann
from meshtastic.protobuf import config_pb2 as meshtastic_dot_protobuf_dot_config__pb2 from meshtastic.protobuf import config_pb2 as meshtastic_dot_protobuf_dot_config__pb2
DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n!meshtastic/protobuf/apponly.proto\x12\x13meshtastic.protobuf\x1a!meshtastic/protobuf/channel.proto\x1a meshtastic/protobuf/config.proto\"\x81\x01\n\nChannelSet\x12\x36\n\x08settings\x18\x01 \x03(\x0b\x32$.meshtastic.protobuf.ChannelSettings\x12;\n\x0blora_config\x18\x02 \x01(\x0b\x32&.meshtastic.protobuf.Config.LoRaConfigBc\n\x14org.meshtastic.protoB\rAppOnlyProtosZ\"github.com/meshtastic/go/generated\xaa\x02\x14Meshtastic.Protobufs\xba\x02\x00\x62\x06proto3') DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n!meshtastic/protobuf/apponly.proto\x12\x13meshtastic.protobuf\x1a!meshtastic/protobuf/channel.proto\x1a meshtastic/protobuf/config.proto\"\x81\x01\n\nChannelSet\x12\x36\n\x08settings\x18\x01 \x03(\x0b\x32$.meshtastic.protobuf.ChannelSettings\x12;\n\x0blora_config\x18\x02 \x01(\x0b\x32&.meshtastic.protobuf.Config.LoRaConfigBb\n\x13\x63om.geeksville.meshB\rAppOnlyProtosZ\"github.com/meshtastic/go/generated\xaa\x02\x14Meshtastic.Protobufs\xba\x02\x00\x62\x06proto3')
_globals = globals() _globals = globals()
_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) _builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals)
_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'meshtastic.protobuf.apponly_pb2', _globals) _builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'meshtastic.protobuf.apponly_pb2', _globals)
if _descriptor._USE_C_DESCRIPTORS == False: if _descriptor._USE_C_DESCRIPTORS == False:
DESCRIPTOR._options = None DESCRIPTOR._options = None
DESCRIPTOR._serialized_options = b'\n\024org.meshtastic.protoB\rAppOnlyProtosZ\"github.com/meshtastic/go/generated\252\002\024Meshtastic.Protobufs\272\002\000' DESCRIPTOR._serialized_options = b'\n\023com.geeksville.meshB\rAppOnlyProtosZ\"github.com/meshtastic/go/generated\252\002\024Meshtastic.Protobufs\272\002\000'
_globals['_CHANNELSET']._serialized_start=128 _globals['_CHANNELSET']._serialized_start=128
_globals['_CHANNELSET']._serialized_end=257 _globals['_CHANNELSET']._serialized_end=257
# @@protoc_insertion_point(module_scope) # @@protoc_insertion_point(module_scope)
+2 -2
View File
@@ -13,14 +13,14 @@ _sym_db = _symbol_database.Default()
DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x1emeshtastic/protobuf/atak.proto\x12\x13meshtastic.protobuf\"\xa5\x02\n\tTAKPacket\x12\x15\n\ris_compressed\x18\x01 \x01(\x08\x12-\n\x07\x63ontact\x18\x02 \x01(\x0b\x32\x1c.meshtastic.protobuf.Contact\x12)\n\x05group\x18\x03 \x01(\x0b\x32\x1a.meshtastic.protobuf.Group\x12+\n\x06status\x18\x04 \x01(\x0b\x32\x1b.meshtastic.protobuf.Status\x12\'\n\x03pli\x18\x05 \x01(\x0b\x32\x18.meshtastic.protobuf.PLIH\x00\x12,\n\x04\x63hat\x18\x06 \x01(\x0b\x32\x1c.meshtastic.protobuf.GeoChatH\x00\x12\x10\n\x06\x64\x65tail\x18\x07 \x01(\x0cH\x00\x42\x11\n\x0fpayload_variant\"\\\n\x07GeoChat\x12\x0f\n\x07message\x18\x01 \x01(\t\x12\x0f\n\x02to\x18\x02 \x01(\tH\x00\x88\x01\x01\x12\x18\n\x0bto_callsign\x18\x03 \x01(\tH\x01\x88\x01\x01\x42\x05\n\x03_toB\x0e\n\x0c_to_callsign\"_\n\x05Group\x12-\n\x04role\x18\x01 \x01(\x0e\x32\x1f.meshtastic.protobuf.MemberRole\x12\'\n\x04team\x18\x02 \x01(\x0e\x32\x19.meshtastic.protobuf.Team\"\x19\n\x06Status\x12\x0f\n\x07\x62\x61ttery\x18\x01 \x01(\r\"4\n\x07\x43ontact\x12\x10\n\x08\x63\x61llsign\x18\x01 \x01(\t\x12\x17\n\x0f\x64\x65vice_callsign\x18\x02 \x01(\t\"_\n\x03PLI\x12\x12\n\nlatitude_i\x18\x01 \x01(\x0f\x12\x13\n\x0blongitude_i\x18\x02 \x01(\x0f\x12\x10\n\x08\x61ltitude\x18\x03 \x01(\x05\x12\r\n\x05speed\x18\x04 \x01(\r\x12\x0e\n\x06\x63ourse\x18\x05 \x01(\r*\xc0\x01\n\x04Team\x12\x14\n\x10Unspecifed_Color\x10\x00\x12\t\n\x05White\x10\x01\x12\n\n\x06Yellow\x10\x02\x12\n\n\x06Orange\x10\x03\x12\x0b\n\x07Magenta\x10\x04\x12\x07\n\x03Red\x10\x05\x12\n\n\x06Maroon\x10\x06\x12\n\n\x06Purple\x10\x07\x12\r\n\tDark_Blue\x10\x08\x12\x08\n\x04\x42lue\x10\t\x12\x08\n\x04\x43yan\x10\n\x12\x08\n\x04Teal\x10\x0b\x12\t\n\x05Green\x10\x0c\x12\x0e\n\nDark_Green\x10\r\x12\t\n\x05\x42rown\x10\x0e*\x7f\n\nMemberRole\x12\x0e\n\nUnspecifed\x10\x00\x12\x0e\n\nTeamMember\x10\x01\x12\x0c\n\x08TeamLead\x10\x02\x12\x06\n\x02HQ\x10\x03\x12\n\n\x06Sniper\x10\x04\x12\t\n\x05Medic\x10\x05\x12\x13\n\x0f\x46orwardObserver\x10\x06\x12\x07\n\x03RTO\x10\x07\x12\x06\n\x02K9\x10\x08\x42`\n\x14org.meshtastic.protoB\nATAKProtosZ\"github.com/meshtastic/go/generated\xaa\x02\x14Meshtastic.Protobufs\xba\x02\x00\x62\x06proto3') DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x1emeshtastic/protobuf/atak.proto\x12\x13meshtastic.protobuf\"\xa5\x02\n\tTAKPacket\x12\x15\n\ris_compressed\x18\x01 \x01(\x08\x12-\n\x07\x63ontact\x18\x02 \x01(\x0b\x32\x1c.meshtastic.protobuf.Contact\x12)\n\x05group\x18\x03 \x01(\x0b\x32\x1a.meshtastic.protobuf.Group\x12+\n\x06status\x18\x04 \x01(\x0b\x32\x1b.meshtastic.protobuf.Status\x12\'\n\x03pli\x18\x05 \x01(\x0b\x32\x18.meshtastic.protobuf.PLIH\x00\x12,\n\x04\x63hat\x18\x06 \x01(\x0b\x32\x1c.meshtastic.protobuf.GeoChatH\x00\x12\x10\n\x06\x64\x65tail\x18\x07 \x01(\x0cH\x00\x42\x11\n\x0fpayload_variant\"\\\n\x07GeoChat\x12\x0f\n\x07message\x18\x01 \x01(\t\x12\x0f\n\x02to\x18\x02 \x01(\tH\x00\x88\x01\x01\x12\x18\n\x0bto_callsign\x18\x03 \x01(\tH\x01\x88\x01\x01\x42\x05\n\x03_toB\x0e\n\x0c_to_callsign\"_\n\x05Group\x12-\n\x04role\x18\x01 \x01(\x0e\x32\x1f.meshtastic.protobuf.MemberRole\x12\'\n\x04team\x18\x02 \x01(\x0e\x32\x19.meshtastic.protobuf.Team\"\x19\n\x06Status\x12\x0f\n\x07\x62\x61ttery\x18\x01 \x01(\r\"4\n\x07\x43ontact\x12\x10\n\x08\x63\x61llsign\x18\x01 \x01(\t\x12\x17\n\x0f\x64\x65vice_callsign\x18\x02 \x01(\t\"_\n\x03PLI\x12\x12\n\nlatitude_i\x18\x01 \x01(\x0f\x12\x13\n\x0blongitude_i\x18\x02 \x01(\x0f\x12\x10\n\x08\x61ltitude\x18\x03 \x01(\x05\x12\r\n\x05speed\x18\x04 \x01(\r\x12\x0e\n\x06\x63ourse\x18\x05 \x01(\r*\xc0\x01\n\x04Team\x12\x14\n\x10Unspecifed_Color\x10\x00\x12\t\n\x05White\x10\x01\x12\n\n\x06Yellow\x10\x02\x12\n\n\x06Orange\x10\x03\x12\x0b\n\x07Magenta\x10\x04\x12\x07\n\x03Red\x10\x05\x12\n\n\x06Maroon\x10\x06\x12\n\n\x06Purple\x10\x07\x12\r\n\tDark_Blue\x10\x08\x12\x08\n\x04\x42lue\x10\t\x12\x08\n\x04\x43yan\x10\n\x12\x08\n\x04Teal\x10\x0b\x12\t\n\x05Green\x10\x0c\x12\x0e\n\nDark_Green\x10\r\x12\t\n\x05\x42rown\x10\x0e*\x7f\n\nMemberRole\x12\x0e\n\nUnspecifed\x10\x00\x12\x0e\n\nTeamMember\x10\x01\x12\x0c\n\x08TeamLead\x10\x02\x12\x06\n\x02HQ\x10\x03\x12\n\n\x06Sniper\x10\x04\x12\t\n\x05Medic\x10\x05\x12\x13\n\x0f\x46orwardObserver\x10\x06\x12\x07\n\x03RTO\x10\x07\x12\x06\n\x02K9\x10\x08\x42_\n\x13\x63om.geeksville.meshB\nATAKProtosZ\"github.com/meshtastic/go/generated\xaa\x02\x14Meshtastic.Protobufs\xba\x02\x00\x62\x06proto3')
_globals = globals() _globals = globals()
_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) _builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals)
_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'meshtastic.protobuf.atak_pb2', _globals) _builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'meshtastic.protobuf.atak_pb2', _globals)
if _descriptor._USE_C_DESCRIPTORS == False: if _descriptor._USE_C_DESCRIPTORS == False:
DESCRIPTOR._options = None DESCRIPTOR._options = None
DESCRIPTOR._serialized_options = b'\n\024org.meshtastic.protoB\nATAKProtosZ\"github.com/meshtastic/go/generated\252\002\024Meshtastic.Protobufs\272\002\000' DESCRIPTOR._serialized_options = b'\n\023com.geeksville.meshB\nATAKProtosZ\"github.com/meshtastic/go/generated\252\002\024Meshtastic.Protobufs\272\002\000'
_globals['_TEAM']._serialized_start=721 _globals['_TEAM']._serialized_start=721
_globals['_TEAM']._serialized_end=913 _globals['_TEAM']._serialized_end=913
_globals['_MEMBERROLE']._serialized_start=915 _globals['_MEMBERROLE']._serialized_start=915
+2 -2
View File
@@ -13,14 +13,14 @@ _sym_db = _symbol_database.Default()
DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n(meshtastic/protobuf/cannedmessages.proto\x12\x13meshtastic.protobuf\"-\n\x19\x43\x61nnedMessageModuleConfig\x12\x10\n\x08messages\x18\x01 \x01(\tBo\n\x14org.meshtastic.protoB\x19\x43\x61nnedMessageConfigProtosZ\"github.com/meshtastic/go/generated\xaa\x02\x14Meshtastic.Protobufs\xba\x02\x00\x62\x06proto3') DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n(meshtastic/protobuf/cannedmessages.proto\x12\x13meshtastic.protobuf\"-\n\x19\x43\x61nnedMessageModuleConfig\x12\x10\n\x08messages\x18\x01 \x01(\tBn\n\x13\x63om.geeksville.meshB\x19\x43\x61nnedMessageConfigProtosZ\"github.com/meshtastic/go/generated\xaa\x02\x14Meshtastic.Protobufs\xba\x02\x00\x62\x06proto3')
_globals = globals() _globals = globals()
_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) _builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals)
_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'meshtastic.protobuf.cannedmessages_pb2', _globals) _builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'meshtastic.protobuf.cannedmessages_pb2', _globals)
if _descriptor._USE_C_DESCRIPTORS == False: if _descriptor._USE_C_DESCRIPTORS == False:
DESCRIPTOR._options = None DESCRIPTOR._options = None
DESCRIPTOR._serialized_options = b'\n\024org.meshtastic.protoB\031CannedMessageConfigProtosZ\"github.com/meshtastic/go/generated\252\002\024Meshtastic.Protobufs\272\002\000' DESCRIPTOR._serialized_options = b'\n\023com.geeksville.meshB\031CannedMessageConfigProtosZ\"github.com/meshtastic/go/generated\252\002\024Meshtastic.Protobufs\272\002\000'
_globals['_CANNEDMESSAGEMODULECONFIG']._serialized_start=65 _globals['_CANNEDMESSAGEMODULECONFIG']._serialized_start=65
_globals['_CANNEDMESSAGEMODULECONFIG']._serialized_end=110 _globals['_CANNEDMESSAGEMODULECONFIG']._serialized_end=110
# @@protoc_insertion_point(module_scope) # @@protoc_insertion_point(module_scope)
+7 -7
View File
@@ -13,22 +13,22 @@ _sym_db = _symbol_database.Default()
DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n!meshtastic/protobuf/channel.proto\x12\x13meshtastic.protobuf\"\xc1\x01\n\x0f\x43hannelSettings\x12\x17\n\x0b\x63hannel_num\x18\x01 \x01(\rB\x02\x18\x01\x12\x0b\n\x03psk\x18\x02 \x01(\x0c\x12\x0c\n\x04name\x18\x03 \x01(\t\x12\n\n\x02id\x18\x04 \x01(\x07\x12\x16\n\x0euplink_enabled\x18\x05 \x01(\x08\x12\x18\n\x10\x64ownlink_enabled\x18\x06 \x01(\x08\x12<\n\x0fmodule_settings\x18\x07 \x01(\x0b\x32#.meshtastic.protobuf.ModuleSettings\">\n\x0eModuleSettings\x12\x1a\n\x12position_precision\x18\x01 \x01(\r\x12\x10\n\x08is_muted\x18\x02 \x01(\x08\"\xb3\x01\n\x07\x43hannel\x12\r\n\x05index\x18\x01 \x01(\x05\x12\x36\n\x08settings\x18\x02 \x01(\x0b\x32$.meshtastic.protobuf.ChannelSettings\x12/\n\x04role\x18\x03 \x01(\x0e\x32!.meshtastic.protobuf.Channel.Role\"0\n\x04Role\x12\x0c\n\x08\x44ISABLED\x10\x00\x12\x0b\n\x07PRIMARY\x10\x01\x12\r\n\tSECONDARY\x10\x02\x42\x63\n\x14org.meshtastic.protoB\rChannelProtosZ\"github.com/meshtastic/go/generated\xaa\x02\x14Meshtastic.Protobufs\xba\x02\x00\x62\x06proto3') DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n!meshtastic/protobuf/channel.proto\x12\x13meshtastic.protobuf\"\xc1\x01\n\x0f\x43hannelSettings\x12\x17\n\x0b\x63hannel_num\x18\x01 \x01(\rB\x02\x18\x01\x12\x0b\n\x03psk\x18\x02 \x01(\x0c\x12\x0c\n\x04name\x18\x03 \x01(\t\x12\n\n\x02id\x18\x04 \x01(\x07\x12\x16\n\x0euplink_enabled\x18\x05 \x01(\x08\x12\x18\n\x10\x64ownlink_enabled\x18\x06 \x01(\x08\x12<\n\x0fmodule_settings\x18\x07 \x01(\x0b\x32#.meshtastic.protobuf.ModuleSettings\"E\n\x0eModuleSettings\x12\x1a\n\x12position_precision\x18\x01 \x01(\r\x12\x17\n\x0fis_client_muted\x18\x02 \x01(\x08\"\xb3\x01\n\x07\x43hannel\x12\r\n\x05index\x18\x01 \x01(\x05\x12\x36\n\x08settings\x18\x02 \x01(\x0b\x32$.meshtastic.protobuf.ChannelSettings\x12/\n\x04role\x18\x03 \x01(\x0e\x32!.meshtastic.protobuf.Channel.Role\"0\n\x04Role\x12\x0c\n\x08\x44ISABLED\x10\x00\x12\x0b\n\x07PRIMARY\x10\x01\x12\r\n\tSECONDARY\x10\x02\x42\x62\n\x13\x63om.geeksville.meshB\rChannelProtosZ\"github.com/meshtastic/go/generated\xaa\x02\x14Meshtastic.Protobufs\xba\x02\x00\x62\x06proto3')
_globals = globals() _globals = globals()
_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) _builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals)
_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'meshtastic.protobuf.channel_pb2', _globals) _builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'meshtastic.protobuf.channel_pb2', _globals)
if _descriptor._USE_C_DESCRIPTORS == False: if _descriptor._USE_C_DESCRIPTORS == False:
DESCRIPTOR._options = None DESCRIPTOR._options = None
DESCRIPTOR._serialized_options = b'\n\024org.meshtastic.protoB\rChannelProtosZ\"github.com/meshtastic/go/generated\252\002\024Meshtastic.Protobufs\272\002\000' DESCRIPTOR._serialized_options = b'\n\023com.geeksville.meshB\rChannelProtosZ\"github.com/meshtastic/go/generated\252\002\024Meshtastic.Protobufs\272\002\000'
_CHANNELSETTINGS.fields_by_name['channel_num']._options = None _CHANNELSETTINGS.fields_by_name['channel_num']._options = None
_CHANNELSETTINGS.fields_by_name['channel_num']._serialized_options = b'\030\001' _CHANNELSETTINGS.fields_by_name['channel_num']._serialized_options = b'\030\001'
_globals['_CHANNELSETTINGS']._serialized_start=59 _globals['_CHANNELSETTINGS']._serialized_start=59
_globals['_CHANNELSETTINGS']._serialized_end=252 _globals['_CHANNELSETTINGS']._serialized_end=252
_globals['_MODULESETTINGS']._serialized_start=254 _globals['_MODULESETTINGS']._serialized_start=254
_globals['_MODULESETTINGS']._serialized_end=316 _globals['_MODULESETTINGS']._serialized_end=323
_globals['_CHANNEL']._serialized_start=319 _globals['_CHANNEL']._serialized_start=326
_globals['_CHANNEL']._serialized_end=498 _globals['_CHANNEL']._serialized_end=505
_globals['_CHANNEL_ROLE']._serialized_start=450 _globals['_CHANNEL_ROLE']._serialized_start=457
_globals['_CHANNEL_ROLE']._serialized_end=498 _globals['_CHANNEL_ROLE']._serialized_end=505
# @@protoc_insertion_point(module_scope) # @@protoc_insertion_point(module_scope)
+5 -5
View File
@@ -127,23 +127,23 @@ class ModuleSettings(google.protobuf.message.Message):
DESCRIPTOR: google.protobuf.descriptor.Descriptor DESCRIPTOR: google.protobuf.descriptor.Descriptor
POSITION_PRECISION_FIELD_NUMBER: builtins.int POSITION_PRECISION_FIELD_NUMBER: builtins.int
IS_MUTED_FIELD_NUMBER: builtins.int IS_CLIENT_MUTED_FIELD_NUMBER: builtins.int
position_precision: builtins.int position_precision: builtins.int
""" """
Bits of precision for the location sent in position packets. Bits of precision for the location sent in position packets.
""" """
is_muted: builtins.bool is_client_muted: builtins.bool
""" """
Controls whether or not the client / device should mute the current channel Controls whether or not the phone / clients should mute the current channel
Useful for noisy public channels you don't necessarily want to disable Useful for noisy public channels you don't necessarily want to disable
""" """
def __init__( def __init__(
self, self,
*, *,
position_precision: builtins.int = ..., position_precision: builtins.int = ...,
is_muted: builtins.bool = ..., is_client_muted: builtins.bool = ...,
) -> None: ... ) -> None: ...
def ClearField(self, field_name: typing.Literal["is_muted", b"is_muted", "position_precision", b"position_precision"]) -> None: ... def ClearField(self, field_name: typing.Literal["is_client_muted", b"is_client_muted", "position_precision", b"position_precision"]) -> None: ...
global___ModuleSettings = ModuleSettings global___ModuleSettings = ModuleSettings
+2 -2
View File
@@ -15,14 +15,14 @@ from meshtastic.protobuf import localonly_pb2 as meshtastic_dot_protobuf_dot_loc
from meshtastic.protobuf import mesh_pb2 as meshtastic_dot_protobuf_dot_mesh__pb2 from meshtastic.protobuf import mesh_pb2 as meshtastic_dot_protobuf_dot_mesh__pb2
DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n$meshtastic/protobuf/clientonly.proto\x12\x13meshtastic.protobuf\x1a#meshtastic/protobuf/localonly.proto\x1a\x1emeshtastic/protobuf/mesh.proto\"\xc4\x03\n\rDeviceProfile\x12\x16\n\tlong_name\x18\x01 \x01(\tH\x00\x88\x01\x01\x12\x17\n\nshort_name\x18\x02 \x01(\tH\x01\x88\x01\x01\x12\x18\n\x0b\x63hannel_url\x18\x03 \x01(\tH\x02\x88\x01\x01\x12\x35\n\x06\x63onfig\x18\x04 \x01(\x0b\x32 .meshtastic.protobuf.LocalConfigH\x03\x88\x01\x01\x12\x42\n\rmodule_config\x18\x05 \x01(\x0b\x32&.meshtastic.protobuf.LocalModuleConfigH\x04\x88\x01\x01\x12:\n\x0e\x66ixed_position\x18\x06 \x01(\x0b\x32\x1d.meshtastic.protobuf.PositionH\x05\x88\x01\x01\x12\x15\n\x08ringtone\x18\x07 \x01(\tH\x06\x88\x01\x01\x12\x1c\n\x0f\x63\x61nned_messages\x18\x08 \x01(\tH\x07\x88\x01\x01\x42\x0c\n\n_long_nameB\r\n\x0b_short_nameB\x0e\n\x0c_channel_urlB\t\n\x07_configB\x10\n\x0e_module_configB\x11\n\x0f_fixed_positionB\x0b\n\t_ringtoneB\x12\n\x10_canned_messagesBf\n\x14org.meshtastic.protoB\x10\x43lientOnlyProtosZ\"github.com/meshtastic/go/generated\xaa\x02\x14Meshtastic.Protobufs\xba\x02\x00\x62\x06proto3') DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n$meshtastic/protobuf/clientonly.proto\x12\x13meshtastic.protobuf\x1a#meshtastic/protobuf/localonly.proto\x1a\x1emeshtastic/protobuf/mesh.proto\"\xc4\x03\n\rDeviceProfile\x12\x16\n\tlong_name\x18\x01 \x01(\tH\x00\x88\x01\x01\x12\x17\n\nshort_name\x18\x02 \x01(\tH\x01\x88\x01\x01\x12\x18\n\x0b\x63hannel_url\x18\x03 \x01(\tH\x02\x88\x01\x01\x12\x35\n\x06\x63onfig\x18\x04 \x01(\x0b\x32 .meshtastic.protobuf.LocalConfigH\x03\x88\x01\x01\x12\x42\n\rmodule_config\x18\x05 \x01(\x0b\x32&.meshtastic.protobuf.LocalModuleConfigH\x04\x88\x01\x01\x12:\n\x0e\x66ixed_position\x18\x06 \x01(\x0b\x32\x1d.meshtastic.protobuf.PositionH\x05\x88\x01\x01\x12\x15\n\x08ringtone\x18\x07 \x01(\tH\x06\x88\x01\x01\x12\x1c\n\x0f\x63\x61nned_messages\x18\x08 \x01(\tH\x07\x88\x01\x01\x42\x0c\n\n_long_nameB\r\n\x0b_short_nameB\x0e\n\x0c_channel_urlB\t\n\x07_configB\x10\n\x0e_module_configB\x11\n\x0f_fixed_positionB\x0b\n\t_ringtoneB\x12\n\x10_canned_messagesBe\n\x13\x63om.geeksville.meshB\x10\x43lientOnlyProtosZ\"github.com/meshtastic/go/generated\xaa\x02\x14Meshtastic.Protobufs\xba\x02\x00\x62\x06proto3')
_globals = globals() _globals = globals()
_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) _builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals)
_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'meshtastic.protobuf.clientonly_pb2', _globals) _builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'meshtastic.protobuf.clientonly_pb2', _globals)
if _descriptor._USE_C_DESCRIPTORS == False: if _descriptor._USE_C_DESCRIPTORS == False:
DESCRIPTOR._options = None DESCRIPTOR._options = None
DESCRIPTOR._serialized_options = b'\n\024org.meshtastic.protoB\020ClientOnlyProtosZ\"github.com/meshtastic/go/generated\252\002\024Meshtastic.Protobufs\272\002\000' DESCRIPTOR._serialized_options = b'\n\023com.geeksville.meshB\020ClientOnlyProtosZ\"github.com/meshtastic/go/generated\252\002\024Meshtastic.Protobufs\272\002\000'
_globals['_DEVICEPROFILE']._serialized_start=131 _globals['_DEVICEPROFILE']._serialized_start=131
_globals['_DEVICEPROFILE']._serialized_end=583 _globals['_DEVICEPROFILE']._serialized_end=583
# @@protoc_insertion_point(module_scope) # @@protoc_insertion_point(module_scope)
File diff suppressed because one or more lines are too long
+70 -19
View File
@@ -64,7 +64,6 @@ class Config(google.protobuf.message.Message):
Description: Infrastructure node for extending network coverage by relaying messages with minimal overhead. Not visible in Nodes list. Description: Infrastructure node for extending network coverage by relaying messages with minimal overhead. Not visible in Nodes list.
Technical Details: Mesh packets will simply be rebroadcasted over this node. Nodes configured with this role will not originate NodeInfo, Position, Telemetry Technical Details: Mesh packets will simply be rebroadcasted over this node. Nodes configured with this role will not originate NodeInfo, Position, Telemetry
or any other packet type. They will simply rebroadcast any mesh packets on the same frequency, channel num, spread factor, and coding rate. or any other packet type. They will simply rebroadcast any mesh packets on the same frequency, channel num, spread factor, and coding rate.
Deprecated in v2.7.11 because it creates "holes" in the mesh rebroadcast chain.
""" """
TRACKER: Config.DeviceConfig._Role.ValueType # 5 TRACKER: Config.DeviceConfig._Role.ValueType # 5
""" """
@@ -156,7 +155,6 @@ class Config(google.protobuf.message.Message):
Description: Infrastructure node for extending network coverage by relaying messages with minimal overhead. Not visible in Nodes list. Description: Infrastructure node for extending network coverage by relaying messages with minimal overhead. Not visible in Nodes list.
Technical Details: Mesh packets will simply be rebroadcasted over this node. Nodes configured with this role will not originate NodeInfo, Position, Telemetry Technical Details: Mesh packets will simply be rebroadcasted over this node. Nodes configured with this role will not originate NodeInfo, Position, Telemetry
or any other packet type. They will simply rebroadcast any mesh packets on the same frequency, channel num, spread factor, and coding rate. or any other packet type. They will simply rebroadcast any mesh packets on the same frequency, channel num, spread factor, and coding rate.
Deprecated in v2.7.11 because it creates "holes" in the mesh rebroadcast chain.
""" """
TRACKER: Config.DeviceConfig.Role.ValueType # 5 TRACKER: Config.DeviceConfig.Role.ValueType # 5
""" """
@@ -940,20 +938,80 @@ class Config(google.protobuf.message.Message):
DESCRIPTOR: google.protobuf.descriptor.Descriptor DESCRIPTOR: google.protobuf.descriptor.Descriptor
class _DeprecatedGpsCoordinateFormat: class _GpsCoordinateFormat:
ValueType = typing.NewType("ValueType", builtins.int) ValueType = typing.NewType("ValueType", builtins.int)
V: typing_extensions.TypeAlias = ValueType V: typing_extensions.TypeAlias = ValueType
class _DeprecatedGpsCoordinateFormatEnumTypeWrapper(google.protobuf.internal.enum_type_wrapper._EnumTypeWrapper[Config.DisplayConfig._DeprecatedGpsCoordinateFormat.ValueType], builtins.type): class _GpsCoordinateFormatEnumTypeWrapper(google.protobuf.internal.enum_type_wrapper._EnumTypeWrapper[Config.DisplayConfig._GpsCoordinateFormat.ValueType], builtins.type):
DESCRIPTOR: google.protobuf.descriptor.EnumDescriptor DESCRIPTOR: google.protobuf.descriptor.EnumDescriptor
UNUSED: Config.DisplayConfig._DeprecatedGpsCoordinateFormat.ValueType # 0 DEC: Config.DisplayConfig._GpsCoordinateFormat.ValueType # 0
class DeprecatedGpsCoordinateFormat(_DeprecatedGpsCoordinateFormat, metaclass=_DeprecatedGpsCoordinateFormatEnumTypeWrapper):
""" """
Deprecated in 2.7.4: Unused GPS coordinates are displayed in the normal decimal degrees format:
DD.DDDDDD DDD.DDDDDD
"""
DMS: Config.DisplayConfig._GpsCoordinateFormat.ValueType # 1
"""
GPS coordinates are displayed in the degrees minutes seconds format:
DD°MM'SS"C DDD°MM'SS"C, where C is the compass point representing the locations quadrant
"""
UTM: Config.DisplayConfig._GpsCoordinateFormat.ValueType # 2
"""
Universal Transverse Mercator format:
ZZB EEEEEE NNNNNNN, where Z is zone, B is band, E is easting, N is northing
"""
MGRS: Config.DisplayConfig._GpsCoordinateFormat.ValueType # 3
"""
Military Grid Reference System format:
ZZB CD EEEEE NNNNN, where Z is zone, B is band, C is the east 100k square, D is the north 100k square,
E is easting, N is northing
"""
OLC: Config.DisplayConfig._GpsCoordinateFormat.ValueType # 4
"""
Open Location Code (aka Plus Codes).
"""
OSGR: Config.DisplayConfig._GpsCoordinateFormat.ValueType # 5
"""
Ordnance Survey Grid Reference (the National Grid System of the UK).
Format: AB EEEEE NNNNN, where A is the east 100k square, B is the north 100k square,
E is the easting, N is the northing
""" """
UNUSED: Config.DisplayConfig.DeprecatedGpsCoordinateFormat.ValueType # 0 class GpsCoordinateFormat(_GpsCoordinateFormat, metaclass=_GpsCoordinateFormatEnumTypeWrapper):
"""
How the GPS coordinates are displayed on the OLED screen.
"""
DEC: Config.DisplayConfig.GpsCoordinateFormat.ValueType # 0
"""
GPS coordinates are displayed in the normal decimal degrees format:
DD.DDDDDD DDD.DDDDDD
"""
DMS: Config.DisplayConfig.GpsCoordinateFormat.ValueType # 1
"""
GPS coordinates are displayed in the degrees minutes seconds format:
DD°MM'SS"C DDD°MM'SS"C, where C is the compass point representing the locations quadrant
"""
UTM: Config.DisplayConfig.GpsCoordinateFormat.ValueType # 2
"""
Universal Transverse Mercator format:
ZZB EEEEEE NNNNNNN, where Z is zone, B is band, E is easting, N is northing
"""
MGRS: Config.DisplayConfig.GpsCoordinateFormat.ValueType # 3
"""
Military Grid Reference System format:
ZZB CD EEEEE NNNNN, where Z is zone, B is band, C is the east 100k square, D is the north 100k square,
E is easting, N is northing
"""
OLC: Config.DisplayConfig.GpsCoordinateFormat.ValueType # 4
"""
Open Location Code (aka Plus Codes).
"""
OSGR: Config.DisplayConfig.GpsCoordinateFormat.ValueType # 5
"""
Ordnance Survey Grid Reference (the National Grid System of the UK).
Format: AB EEEEE NNNNN, where A is the east 100k square, B is the north 100k square,
E is the easting, N is the northing
"""
class _DisplayUnits: class _DisplayUnits:
ValueType = typing.NewType("ValueType", builtins.int) ValueType = typing.NewType("ValueType", builtins.int)
@@ -1163,13 +1221,12 @@ class Config(google.protobuf.message.Message):
WAKE_ON_TAP_OR_MOTION_FIELD_NUMBER: builtins.int WAKE_ON_TAP_OR_MOTION_FIELD_NUMBER: builtins.int
COMPASS_ORIENTATION_FIELD_NUMBER: builtins.int COMPASS_ORIENTATION_FIELD_NUMBER: builtins.int
USE_12H_CLOCK_FIELD_NUMBER: builtins.int USE_12H_CLOCK_FIELD_NUMBER: builtins.int
USE_LONG_NODE_NAME_FIELD_NUMBER: builtins.int
screen_on_secs: builtins.int screen_on_secs: builtins.int
""" """
Number of seconds the screen stays on after pressing the user button or receiving a message Number of seconds the screen stays on after pressing the user button or receiving a message
0 for default of one minute MAXUINT for always on 0 for default of one minute MAXUINT for always on
""" """
gps_format: global___Config.DisplayConfig.DeprecatedGpsCoordinateFormat.ValueType gps_format: global___Config.DisplayConfig.GpsCoordinateFormat.ValueType
""" """
Deprecated in 2.7.4: Unused Deprecated in 2.7.4: Unused
How the GPS coordinates are formatted on the OLED screen. How the GPS coordinates are formatted on the OLED screen.
@@ -1217,16 +1274,11 @@ class Config(google.protobuf.message.Message):
If false (default), the device will display the time in 24-hour format on screen. If false (default), the device will display the time in 24-hour format on screen.
If true, the device will display the time in 12-hour format on screen. If true, the device will display the time in 12-hour format on screen.
""" """
use_long_node_name: builtins.bool
"""
If false (default), the device will use short names for various display screens.
If true, node names will show in long format
"""
def __init__( def __init__(
self, self,
*, *,
screen_on_secs: builtins.int = ..., screen_on_secs: builtins.int = ...,
gps_format: global___Config.DisplayConfig.DeprecatedGpsCoordinateFormat.ValueType = ..., gps_format: global___Config.DisplayConfig.GpsCoordinateFormat.ValueType = ...,
auto_screen_carousel_secs: builtins.int = ..., auto_screen_carousel_secs: builtins.int = ...,
compass_north_top: builtins.bool = ..., compass_north_top: builtins.bool = ...,
flip_screen: builtins.bool = ..., flip_screen: builtins.bool = ...,
@@ -1237,9 +1289,8 @@ class Config(google.protobuf.message.Message):
wake_on_tap_or_motion: builtins.bool = ..., wake_on_tap_or_motion: builtins.bool = ...,
compass_orientation: global___Config.DisplayConfig.CompassOrientation.ValueType = ..., compass_orientation: global___Config.DisplayConfig.CompassOrientation.ValueType = ...,
use_12h_clock: builtins.bool = ..., use_12h_clock: builtins.bool = ...,
use_long_node_name: builtins.bool = ...,
) -> None: ... ) -> None: ...
def ClearField(self, field_name: typing.Literal["auto_screen_carousel_secs", b"auto_screen_carousel_secs", "compass_north_top", b"compass_north_top", "compass_orientation", b"compass_orientation", "displaymode", b"displaymode", "flip_screen", b"flip_screen", "gps_format", b"gps_format", "heading_bold", b"heading_bold", "oled", b"oled", "screen_on_secs", b"screen_on_secs", "units", b"units", "use_12h_clock", b"use_12h_clock", "use_long_node_name", b"use_long_node_name", "wake_on_tap_or_motion", b"wake_on_tap_or_motion"]) -> None: ... def ClearField(self, field_name: typing.Literal["auto_screen_carousel_secs", b"auto_screen_carousel_secs", "compass_north_top", b"compass_north_top", "compass_orientation", b"compass_orientation", "displaymode", b"displaymode", "flip_screen", b"flip_screen", "gps_format", b"gps_format", "heading_bold", b"heading_bold", "oled", b"oled", "screen_on_secs", b"screen_on_secs", "units", b"units", "use_12h_clock", b"use_12h_clock", "wake_on_tap_or_motion", b"wake_on_tap_or_motion"]) -> None: ...
@typing.final @typing.final
class LoRaConfig(google.protobuf.message.Message): class LoRaConfig(google.protobuf.message.Message):
+2 -2
View File
@@ -13,14 +13,14 @@ _sym_db = _symbol_database.Default()
DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n+meshtastic/protobuf/connection_status.proto\x12\x13meshtastic.protobuf\"\xd5\x02\n\x16\x44\x65viceConnectionStatus\x12<\n\x04wifi\x18\x01 \x01(\x0b\x32).meshtastic.protobuf.WifiConnectionStatusH\x00\x88\x01\x01\x12\x44\n\x08\x65thernet\x18\x02 \x01(\x0b\x32-.meshtastic.protobuf.EthernetConnectionStatusH\x01\x88\x01\x01\x12\x46\n\tbluetooth\x18\x03 \x01(\x0b\x32..meshtastic.protobuf.BluetoothConnectionStatusH\x02\x88\x01\x01\x12@\n\x06serial\x18\x04 \x01(\x0b\x32+.meshtastic.protobuf.SerialConnectionStatusH\x03\x88\x01\x01\x42\x07\n\x05_wifiB\x0b\n\t_ethernetB\x0c\n\n_bluetoothB\t\n\x07_serial\"p\n\x14WifiConnectionStatus\x12<\n\x06status\x18\x01 \x01(\x0b\x32,.meshtastic.protobuf.NetworkConnectionStatus\x12\x0c\n\x04ssid\x18\x02 \x01(\t\x12\x0c\n\x04rssi\x18\x03 \x01(\x05\"X\n\x18\x45thernetConnectionStatus\x12<\n\x06status\x18\x01 \x01(\x0b\x32,.meshtastic.protobuf.NetworkConnectionStatus\"{\n\x17NetworkConnectionStatus\x12\x12\n\nip_address\x18\x01 \x01(\x07\x12\x14\n\x0cis_connected\x18\x02 \x01(\x08\x12\x19\n\x11is_mqtt_connected\x18\x03 \x01(\x08\x12\x1b\n\x13is_syslog_connected\x18\x04 \x01(\x08\"L\n\x19\x42luetoothConnectionStatus\x12\x0b\n\x03pin\x18\x01 \x01(\r\x12\x0c\n\x04rssi\x18\x02 \x01(\x05\x12\x14\n\x0cis_connected\x18\x03 \x01(\x08\"<\n\x16SerialConnectionStatus\x12\x0c\n\x04\x62\x61ud\x18\x01 \x01(\r\x12\x14\n\x0cis_connected\x18\x02 \x01(\x08\x42\x66\n\x14org.meshtastic.protoB\x10\x43onnStatusProtosZ\"github.com/meshtastic/go/generated\xaa\x02\x14Meshtastic.Protobufs\xba\x02\x00\x62\x06proto3') DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n+meshtastic/protobuf/connection_status.proto\x12\x13meshtastic.protobuf\"\xd5\x02\n\x16\x44\x65viceConnectionStatus\x12<\n\x04wifi\x18\x01 \x01(\x0b\x32).meshtastic.protobuf.WifiConnectionStatusH\x00\x88\x01\x01\x12\x44\n\x08\x65thernet\x18\x02 \x01(\x0b\x32-.meshtastic.protobuf.EthernetConnectionStatusH\x01\x88\x01\x01\x12\x46\n\tbluetooth\x18\x03 \x01(\x0b\x32..meshtastic.protobuf.BluetoothConnectionStatusH\x02\x88\x01\x01\x12@\n\x06serial\x18\x04 \x01(\x0b\x32+.meshtastic.protobuf.SerialConnectionStatusH\x03\x88\x01\x01\x42\x07\n\x05_wifiB\x0b\n\t_ethernetB\x0c\n\n_bluetoothB\t\n\x07_serial\"p\n\x14WifiConnectionStatus\x12<\n\x06status\x18\x01 \x01(\x0b\x32,.meshtastic.protobuf.NetworkConnectionStatus\x12\x0c\n\x04ssid\x18\x02 \x01(\t\x12\x0c\n\x04rssi\x18\x03 \x01(\x05\"X\n\x18\x45thernetConnectionStatus\x12<\n\x06status\x18\x01 \x01(\x0b\x32,.meshtastic.protobuf.NetworkConnectionStatus\"{\n\x17NetworkConnectionStatus\x12\x12\n\nip_address\x18\x01 \x01(\x07\x12\x14\n\x0cis_connected\x18\x02 \x01(\x08\x12\x19\n\x11is_mqtt_connected\x18\x03 \x01(\x08\x12\x1b\n\x13is_syslog_connected\x18\x04 \x01(\x08\"L\n\x19\x42luetoothConnectionStatus\x12\x0b\n\x03pin\x18\x01 \x01(\r\x12\x0c\n\x04rssi\x18\x02 \x01(\x05\x12\x14\n\x0cis_connected\x18\x03 \x01(\x08\"<\n\x16SerialConnectionStatus\x12\x0c\n\x04\x62\x61ud\x18\x01 \x01(\r\x12\x14\n\x0cis_connected\x18\x02 \x01(\x08\x42\x65\n\x13\x63om.geeksville.meshB\x10\x43onnStatusProtosZ\"github.com/meshtastic/go/generated\xaa\x02\x14Meshtastic.Protobufs\xba\x02\x00\x62\x06proto3')
_globals = globals() _globals = globals()
_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) _builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals)
_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'meshtastic.protobuf.connection_status_pb2', _globals) _builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'meshtastic.protobuf.connection_status_pb2', _globals)
if _descriptor._USE_C_DESCRIPTORS == False: if _descriptor._USE_C_DESCRIPTORS == False:
DESCRIPTOR._options = None DESCRIPTOR._options = None
DESCRIPTOR._serialized_options = b'\n\024org.meshtastic.protoB\020ConnStatusProtosZ\"github.com/meshtastic/go/generated\252\002\024Meshtastic.Protobufs\272\002\000' DESCRIPTOR._serialized_options = b'\n\023com.geeksville.meshB\020ConnStatusProtosZ\"github.com/meshtastic/go/generated\252\002\024Meshtastic.Protobufs\272\002\000'
_globals['_DEVICECONNECTIONSTATUS']._serialized_start=69 _globals['_DEVICECONNECTIONSTATUS']._serialized_start=69
_globals['_DEVICECONNECTIONSTATUS']._serialized_end=410 _globals['_DEVICECONNECTIONSTATUS']._serialized_end=410
_globals['_WIFICONNECTIONSTATUS']._serialized_start=412 _globals['_WIFICONNECTIONSTATUS']._serialized_start=412
+17 -19
View File
@@ -13,30 +13,28 @@ _sym_db = _symbol_database.Default()
DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n#meshtastic/protobuf/device_ui.proto\x12\x13meshtastic.protobuf\"\xff\x05\n\x0e\x44\x65viceUIConfig\x12\x0f\n\x07version\x18\x01 \x01(\r\x12\x19\n\x11screen_brightness\x18\x02 \x01(\r\x12\x16\n\x0escreen_timeout\x18\x03 \x01(\r\x12\x13\n\x0bscreen_lock\x18\x04 \x01(\x08\x12\x15\n\rsettings_lock\x18\x05 \x01(\x08\x12\x10\n\x08pin_code\x18\x06 \x01(\r\x12)\n\x05theme\x18\x07 \x01(\x0e\x32\x1a.meshtastic.protobuf.Theme\x12\x15\n\ralert_enabled\x18\x08 \x01(\x08\x12\x16\n\x0e\x62\x61nner_enabled\x18\t \x01(\x08\x12\x14\n\x0cring_tone_id\x18\n \x01(\r\x12/\n\x08language\x18\x0b \x01(\x0e\x32\x1d.meshtastic.protobuf.Language\x12\x34\n\x0bnode_filter\x18\x0c \x01(\x0b\x32\x1f.meshtastic.protobuf.NodeFilter\x12:\n\x0enode_highlight\x18\r \x01(\x0b\x32\".meshtastic.protobuf.NodeHighlight\x12\x18\n\x10\x63\x61libration_data\x18\x0e \x01(\x0c\x12*\n\x08map_data\x18\x0f \x01(\x0b\x32\x18.meshtastic.protobuf.Map\x12\x36\n\x0c\x63ompass_mode\x18\x10 \x01(\x0e\x32 .meshtastic.protobuf.CompassMode\x12\x18\n\x10screen_rgb_color\x18\x11 \x01(\r\x12\x1b\n\x13is_clockface_analog\x18\x12 \x01(\x08\x12K\n\ngps_format\x18\x13 \x01(\x0e\x32\x37.meshtastic.protobuf.DeviceUIConfig.GpsCoordinateFormat\"V\n\x13GpsCoordinateFormat\x12\x07\n\x03\x44\x45\x43\x10\x00\x12\x07\n\x03\x44MS\x10\x01\x12\x07\n\x03UTM\x10\x02\x12\x08\n\x04MGRS\x10\x03\x12\x07\n\x03OLC\x10\x04\x12\x08\n\x04OSGR\x10\x05\x12\x07\n\x03MLS\x10\x06\"\xa7\x01\n\nNodeFilter\x12\x16\n\x0eunknown_switch\x18\x01 \x01(\x08\x12\x16\n\x0eoffline_switch\x18\x02 \x01(\x08\x12\x19\n\x11public_key_switch\x18\x03 \x01(\x08\x12\x11\n\thops_away\x18\x04 \x01(\x05\x12\x17\n\x0fposition_switch\x18\x05 \x01(\x08\x12\x11\n\tnode_name\x18\x06 \x01(\t\x12\x0f\n\x07\x63hannel\x18\x07 \x01(\x05\"~\n\rNodeHighlight\x12\x13\n\x0b\x63hat_switch\x18\x01 \x01(\x08\x12\x17\n\x0fposition_switch\x18\x02 \x01(\x08\x12\x18\n\x10telemetry_switch\x18\x03 \x01(\x08\x12\x12\n\niaq_switch\x18\x04 \x01(\x08\x12\x11\n\tnode_name\x18\x05 \x01(\t\"=\n\x08GeoPoint\x12\x0c\n\x04zoom\x18\x01 \x01(\x05\x12\x10\n\x08latitude\x18\x02 \x01(\x05\x12\x11\n\tlongitude\x18\x03 \x01(\x05\"U\n\x03Map\x12+\n\x04home\x18\x01 \x01(\x0b\x32\x1d.meshtastic.protobuf.GeoPoint\x12\r\n\x05style\x18\x02 \x01(\t\x12\x12\n\nfollow_gps\x18\x03 \x01(\x08*>\n\x0b\x43ompassMode\x12\x0b\n\x07\x44YNAMIC\x10\x00\x12\x0e\n\nFIXED_RING\x10\x01\x12\x12\n\x0e\x46REEZE_HEADING\x10\x02*%\n\x05Theme\x12\x08\n\x04\x44\x41RK\x10\x00\x12\t\n\x05LIGHT\x10\x01\x12\x07\n\x03RED\x10\x02*\xc0\x02\n\x08Language\x12\x0b\n\x07\x45NGLISH\x10\x00\x12\n\n\x06\x46RENCH\x10\x01\x12\n\n\x06GERMAN\x10\x02\x12\x0b\n\x07ITALIAN\x10\x03\x12\x0e\n\nPORTUGUESE\x10\x04\x12\x0b\n\x07SPANISH\x10\x05\x12\x0b\n\x07SWEDISH\x10\x06\x12\x0b\n\x07\x46INNISH\x10\x07\x12\n\n\x06POLISH\x10\x08\x12\x0b\n\x07TURKISH\x10\t\x12\x0b\n\x07SERBIAN\x10\n\x12\x0b\n\x07RUSSIAN\x10\x0b\x12\t\n\x05\x44UTCH\x10\x0c\x12\t\n\x05GREEK\x10\r\x12\r\n\tNORWEGIAN\x10\x0e\x12\r\n\tSLOVENIAN\x10\x0f\x12\r\n\tUKRAINIAN\x10\x10\x12\r\n\tBULGARIAN\x10\x11\x12\t\n\x05\x43ZECH\x10\x12\x12\n\n\x06\x44\x41NISH\x10\x13\x12\x16\n\x12SIMPLIFIED_CHINESE\x10\x1e\x12\x17\n\x13TRADITIONAL_CHINESE\x10\x1f\x42\x64\n\x14org.meshtastic.protoB\x0e\x44\x65viceUIProtosZ\"github.com/meshtastic/go/generated\xaa\x02\x14Meshtastic.Protobufs\xba\x02\x00\x62\x06proto3') DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n#meshtastic/protobuf/device_ui.proto\x12\x13meshtastic.protobuf\"\xda\x04\n\x0e\x44\x65viceUIConfig\x12\x0f\n\x07version\x18\x01 \x01(\r\x12\x19\n\x11screen_brightness\x18\x02 \x01(\r\x12\x16\n\x0escreen_timeout\x18\x03 \x01(\r\x12\x13\n\x0bscreen_lock\x18\x04 \x01(\x08\x12\x15\n\rsettings_lock\x18\x05 \x01(\x08\x12\x10\n\x08pin_code\x18\x06 \x01(\r\x12)\n\x05theme\x18\x07 \x01(\x0e\x32\x1a.meshtastic.protobuf.Theme\x12\x15\n\ralert_enabled\x18\x08 \x01(\x08\x12\x16\n\x0e\x62\x61nner_enabled\x18\t \x01(\x08\x12\x14\n\x0cring_tone_id\x18\n \x01(\r\x12/\n\x08language\x18\x0b \x01(\x0e\x32\x1d.meshtastic.protobuf.Language\x12\x34\n\x0bnode_filter\x18\x0c \x01(\x0b\x32\x1f.meshtastic.protobuf.NodeFilter\x12:\n\x0enode_highlight\x18\r \x01(\x0b\x32\".meshtastic.protobuf.NodeHighlight\x12\x18\n\x10\x63\x61libration_data\x18\x0e \x01(\x0c\x12*\n\x08map_data\x18\x0f \x01(\x0b\x32\x18.meshtastic.protobuf.Map\x12\x36\n\x0c\x63ompass_mode\x18\x10 \x01(\x0e\x32 .meshtastic.protobuf.CompassMode\x12\x18\n\x10screen_rgb_color\x18\x11 \x01(\r\x12\x1b\n\x13is_clockface_analog\x18\x12 \x01(\x08\"\xa7\x01\n\nNodeFilter\x12\x16\n\x0eunknown_switch\x18\x01 \x01(\x08\x12\x16\n\x0eoffline_switch\x18\x02 \x01(\x08\x12\x19\n\x11public_key_switch\x18\x03 \x01(\x08\x12\x11\n\thops_away\x18\x04 \x01(\x05\x12\x17\n\x0fposition_switch\x18\x05 \x01(\x08\x12\x11\n\tnode_name\x18\x06 \x01(\t\x12\x0f\n\x07\x63hannel\x18\x07 \x01(\x05\"~\n\rNodeHighlight\x12\x13\n\x0b\x63hat_switch\x18\x01 \x01(\x08\x12\x17\n\x0fposition_switch\x18\x02 \x01(\x08\x12\x18\n\x10telemetry_switch\x18\x03 \x01(\x08\x12\x12\n\niaq_switch\x18\x04 \x01(\x08\x12\x11\n\tnode_name\x18\x05 \x01(\t\"=\n\x08GeoPoint\x12\x0c\n\x04zoom\x18\x01 \x01(\x05\x12\x10\n\x08latitude\x18\x02 \x01(\x05\x12\x11\n\tlongitude\x18\x03 \x01(\x05\"U\n\x03Map\x12+\n\x04home\x18\x01 \x01(\x0b\x32\x1d.meshtastic.protobuf.GeoPoint\x12\r\n\x05style\x18\x02 \x01(\t\x12\x12\n\nfollow_gps\x18\x03 \x01(\x08*>\n\x0b\x43ompassMode\x12\x0b\n\x07\x44YNAMIC\x10\x00\x12\x0e\n\nFIXED_RING\x10\x01\x12\x12\n\x0e\x46REEZE_HEADING\x10\x02*%\n\x05Theme\x12\x08\n\x04\x44\x41RK\x10\x00\x12\t\n\x05LIGHT\x10\x01\x12\x07\n\x03RED\x10\x02*\xb4\x02\n\x08Language\x12\x0b\n\x07\x45NGLISH\x10\x00\x12\n\n\x06\x46RENCH\x10\x01\x12\n\n\x06GERMAN\x10\x02\x12\x0b\n\x07ITALIAN\x10\x03\x12\x0e\n\nPORTUGUESE\x10\x04\x12\x0b\n\x07SPANISH\x10\x05\x12\x0b\n\x07SWEDISH\x10\x06\x12\x0b\n\x07\x46INNISH\x10\x07\x12\n\n\x06POLISH\x10\x08\x12\x0b\n\x07TURKISH\x10\t\x12\x0b\n\x07SERBIAN\x10\n\x12\x0b\n\x07RUSSIAN\x10\x0b\x12\t\n\x05\x44UTCH\x10\x0c\x12\t\n\x05GREEK\x10\r\x12\r\n\tNORWEGIAN\x10\x0e\x12\r\n\tSLOVENIAN\x10\x0f\x12\r\n\tUKRAINIAN\x10\x10\x12\r\n\tBULGARIAN\x10\x11\x12\t\n\x05\x43ZECH\x10\x12\x12\x16\n\x12SIMPLIFIED_CHINESE\x10\x1e\x12\x17\n\x13TRADITIONAL_CHINESE\x10\x1f\x42\x63\n\x13\x63om.geeksville.meshB\x0e\x44\x65viceUIProtosZ\"github.com/meshtastic/go/generated\xaa\x02\x14Meshtastic.Protobufs\xba\x02\x00\x62\x06proto3')
_globals = globals() _globals = globals()
_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) _builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals)
_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'meshtastic.protobuf.device_ui_pb2', _globals) _builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'meshtastic.protobuf.device_ui_pb2', _globals)
if _descriptor._USE_C_DESCRIPTORS == False: if _descriptor._USE_C_DESCRIPTORS == False:
DESCRIPTOR._options = None DESCRIPTOR._options = None
DESCRIPTOR._serialized_options = b'\n\024org.meshtastic.protoB\016DeviceUIProtosZ\"github.com/meshtastic/go/generated\252\002\024Meshtastic.Protobufs\272\002\000' DESCRIPTOR._serialized_options = b'\n\023com.geeksville.meshB\016DeviceUIProtosZ\"github.com/meshtastic/go/generated\252\002\024Meshtastic.Protobufs\272\002\000'
_globals['_COMPASSMODE']._serialized_start=1278 _globals['_COMPASSMODE']._serialized_start=1113
_globals['_COMPASSMODE']._serialized_end=1340 _globals['_COMPASSMODE']._serialized_end=1175
_globals['_THEME']._serialized_start=1342 _globals['_THEME']._serialized_start=1177
_globals['_THEME']._serialized_end=1379 _globals['_THEME']._serialized_end=1214
_globals['_LANGUAGE']._serialized_start=1382 _globals['_LANGUAGE']._serialized_start=1217
_globals['_LANGUAGE']._serialized_end=1702 _globals['_LANGUAGE']._serialized_end=1525
_globals['_DEVICEUICONFIG']._serialized_start=61 _globals['_DEVICEUICONFIG']._serialized_start=61
_globals['_DEVICEUICONFIG']._serialized_end=828 _globals['_DEVICEUICONFIG']._serialized_end=663
_globals['_DEVICEUICONFIG_GPSCOORDINATEFORMAT']._serialized_start=742 _globals['_NODEFILTER']._serialized_start=666
_globals['_DEVICEUICONFIG_GPSCOORDINATEFORMAT']._serialized_end=828 _globals['_NODEFILTER']._serialized_end=833
_globals['_NODEFILTER']._serialized_start=831 _globals['_NODEHIGHLIGHT']._serialized_start=835
_globals['_NODEFILTER']._serialized_end=998 _globals['_NODEHIGHLIGHT']._serialized_end=961
_globals['_NODEHIGHLIGHT']._serialized_start=1000 _globals['_GEOPOINT']._serialized_start=963
_globals['_NODEHIGHLIGHT']._serialized_end=1126 _globals['_GEOPOINT']._serialized_end=1024
_globals['_GEOPOINT']._serialized_start=1128 _globals['_MAP']._serialized_start=1026
_globals['_GEOPOINT']._serialized_end=1189 _globals['_MAP']._serialized_end=1111
_globals['_MAP']._serialized_start=1191
_globals['_MAP']._serialized_end=1276
# @@protoc_insertion_point(module_scope) # @@protoc_insertion_point(module_scope)
+1 -100
View File
@@ -169,10 +169,6 @@ class _LanguageEnumTypeWrapper(google.protobuf.internal.enum_type_wrapper._EnumT
""" """
Czech Czech
""" """
DANISH: _Language.ValueType # 19
"""
Danish
"""
SIMPLIFIED_CHINESE: _Language.ValueType # 30 SIMPLIFIED_CHINESE: _Language.ValueType # 30
""" """
Simplified Chinese (experimental) Simplified Chinese (experimental)
@@ -263,10 +259,6 @@ CZECH: Language.ValueType # 18
""" """
Czech Czech
""" """
DANISH: Language.ValueType # 19
"""
Danish
"""
SIMPLIFIED_CHINESE: Language.ValueType # 30 SIMPLIFIED_CHINESE: Language.ValueType # 30
""" """
Simplified Chinese (experimental) Simplified Chinese (experimental)
@@ -285,91 +277,6 @@ class DeviceUIConfig(google.protobuf.message.Message):
DESCRIPTOR: google.protobuf.descriptor.Descriptor DESCRIPTOR: google.protobuf.descriptor.Descriptor
class _GpsCoordinateFormat:
ValueType = typing.NewType("ValueType", builtins.int)
V: typing_extensions.TypeAlias = ValueType
class _GpsCoordinateFormatEnumTypeWrapper(google.protobuf.internal.enum_type_wrapper._EnumTypeWrapper[DeviceUIConfig._GpsCoordinateFormat.ValueType], builtins.type):
DESCRIPTOR: google.protobuf.descriptor.EnumDescriptor
DEC: DeviceUIConfig._GpsCoordinateFormat.ValueType # 0
"""
GPS coordinates are displayed in the normal decimal degrees format:
DD.DDDDDD DDD.DDDDDD
"""
DMS: DeviceUIConfig._GpsCoordinateFormat.ValueType # 1
"""
GPS coordinates are displayed in the degrees minutes seconds format:
DD°MM'SS"C DDD°MM'SS"C, where C is the compass point representing the locations quadrant
"""
UTM: DeviceUIConfig._GpsCoordinateFormat.ValueType # 2
"""
Universal Transverse Mercator format:
ZZB EEEEEE NNNNNNN, where Z is zone, B is band, E is easting, N is northing
"""
MGRS: DeviceUIConfig._GpsCoordinateFormat.ValueType # 3
"""
Military Grid Reference System format:
ZZB CD EEEEE NNNNN, where Z is zone, B is band, C is the east 100k square, D is the north 100k square,
E is easting, N is northing
"""
OLC: DeviceUIConfig._GpsCoordinateFormat.ValueType # 4
"""
Open Location Code (aka Plus Codes).
"""
OSGR: DeviceUIConfig._GpsCoordinateFormat.ValueType # 5
"""
Ordnance Survey Grid Reference (the National Grid System of the UK).
Format: AB EEEEE NNNNN, where A is the east 100k square, B is the north 100k square,
E is the easting, N is the northing
"""
MLS: DeviceUIConfig._GpsCoordinateFormat.ValueType # 6
"""
Maidenhead Locator System
Described here: https://en.wikipedia.org/wiki/Maidenhead_Locator_System
"""
class GpsCoordinateFormat(_GpsCoordinateFormat, metaclass=_GpsCoordinateFormatEnumTypeWrapper):
"""
How the GPS coordinates are displayed on the OLED screen.
"""
DEC: DeviceUIConfig.GpsCoordinateFormat.ValueType # 0
"""
GPS coordinates are displayed in the normal decimal degrees format:
DD.DDDDDD DDD.DDDDDD
"""
DMS: DeviceUIConfig.GpsCoordinateFormat.ValueType # 1
"""
GPS coordinates are displayed in the degrees minutes seconds format:
DD°MM'SS"C DDD°MM'SS"C, where C is the compass point representing the locations quadrant
"""
UTM: DeviceUIConfig.GpsCoordinateFormat.ValueType # 2
"""
Universal Transverse Mercator format:
ZZB EEEEEE NNNNNNN, where Z is zone, B is band, E is easting, N is northing
"""
MGRS: DeviceUIConfig.GpsCoordinateFormat.ValueType # 3
"""
Military Grid Reference System format:
ZZB CD EEEEE NNNNN, where Z is zone, B is band, C is the east 100k square, D is the north 100k square,
E is easting, N is northing
"""
OLC: DeviceUIConfig.GpsCoordinateFormat.ValueType # 4
"""
Open Location Code (aka Plus Codes).
"""
OSGR: DeviceUIConfig.GpsCoordinateFormat.ValueType # 5
"""
Ordnance Survey Grid Reference (the National Grid System of the UK).
Format: AB EEEEE NNNNN, where A is the east 100k square, B is the north 100k square,
E is the easting, N is the northing
"""
MLS: DeviceUIConfig.GpsCoordinateFormat.ValueType # 6
"""
Maidenhead Locator System
Described here: https://en.wikipedia.org/wiki/Maidenhead_Locator_System
"""
VERSION_FIELD_NUMBER: builtins.int VERSION_FIELD_NUMBER: builtins.int
SCREEN_BRIGHTNESS_FIELD_NUMBER: builtins.int SCREEN_BRIGHTNESS_FIELD_NUMBER: builtins.int
SCREEN_TIMEOUT_FIELD_NUMBER: builtins.int SCREEN_TIMEOUT_FIELD_NUMBER: builtins.int
@@ -388,7 +295,6 @@ class DeviceUIConfig(google.protobuf.message.Message):
COMPASS_MODE_FIELD_NUMBER: builtins.int COMPASS_MODE_FIELD_NUMBER: builtins.int
SCREEN_RGB_COLOR_FIELD_NUMBER: builtins.int SCREEN_RGB_COLOR_FIELD_NUMBER: builtins.int
IS_CLOCKFACE_ANALOG_FIELD_NUMBER: builtins.int IS_CLOCKFACE_ANALOG_FIELD_NUMBER: builtins.int
GPS_FORMAT_FIELD_NUMBER: builtins.int
version: builtins.int version: builtins.int
""" """
A version integer used to invalidate saved files when we make incompatible changes. A version integer used to invalidate saved files when we make incompatible changes.
@@ -439,10 +345,6 @@ class DeviceUIConfig(google.protobuf.message.Message):
Clockface analog style Clockface analog style
true for analog clockface, false for digital clockface true for analog clockface, false for digital clockface
""" """
gps_format: global___DeviceUIConfig.GpsCoordinateFormat.ValueType
"""
How the GPS coordinates are formatted on the OLED screen.
"""
@property @property
def node_filter(self) -> global___NodeFilter: def node_filter(self) -> global___NodeFilter:
""" """
@@ -482,10 +384,9 @@ class DeviceUIConfig(google.protobuf.message.Message):
compass_mode: global___CompassMode.ValueType = ..., compass_mode: global___CompassMode.ValueType = ...,
screen_rgb_color: builtins.int = ..., screen_rgb_color: builtins.int = ...,
is_clockface_analog: builtins.bool = ..., is_clockface_analog: builtins.bool = ...,
gps_format: global___DeviceUIConfig.GpsCoordinateFormat.ValueType = ...,
) -> None: ... ) -> None: ...
def HasField(self, field_name: typing.Literal["map_data", b"map_data", "node_filter", b"node_filter", "node_highlight", b"node_highlight"]) -> builtins.bool: ... def HasField(self, field_name: typing.Literal["map_data", b"map_data", "node_filter", b"node_filter", "node_highlight", b"node_highlight"]) -> builtins.bool: ...
def ClearField(self, field_name: typing.Literal["alert_enabled", b"alert_enabled", "banner_enabled", b"banner_enabled", "calibration_data", b"calibration_data", "compass_mode", b"compass_mode", "gps_format", b"gps_format", "is_clockface_analog", b"is_clockface_analog", "language", b"language", "map_data", b"map_data", "node_filter", b"node_filter", "node_highlight", b"node_highlight", "pin_code", b"pin_code", "ring_tone_id", b"ring_tone_id", "screen_brightness", b"screen_brightness", "screen_lock", b"screen_lock", "screen_rgb_color", b"screen_rgb_color", "screen_timeout", b"screen_timeout", "settings_lock", b"settings_lock", "theme", b"theme", "version", b"version"]) -> None: ... def ClearField(self, field_name: typing.Literal["alert_enabled", b"alert_enabled", "banner_enabled", b"banner_enabled", "calibration_data", b"calibration_data", "compass_mode", b"compass_mode", "is_clockface_analog", b"is_clockface_analog", "language", b"language", "map_data", b"map_data", "node_filter", b"node_filter", "node_highlight", b"node_highlight", "pin_code", b"pin_code", "ring_tone_id", b"ring_tone_id", "screen_brightness", b"screen_brightness", "screen_lock", b"screen_lock", "screen_rgb_color", b"screen_rgb_color", "screen_timeout", b"screen_timeout", "settings_lock", b"settings_lock", "theme", b"theme", "version", b"version"]) -> None: ...
global___DeviceUIConfig = DeviceUIConfig global___DeviceUIConfig = DeviceUIConfig
+2 -2
View File
@@ -19,14 +19,14 @@ from meshtastic.protobuf import telemetry_pb2 as meshtastic_dot_protobuf_dot_tel
from meshtastic.protobuf import nanopb_pb2 as meshtastic_dot_protobuf_dot_nanopb__pb2 from meshtastic.protobuf import nanopb_pb2 as meshtastic_dot_protobuf_dot_nanopb__pb2
DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n$meshtastic/protobuf/deviceonly.proto\x12\x13meshtastic.protobuf\x1a!meshtastic/protobuf/channel.proto\x1a meshtastic/protobuf/config.proto\x1a#meshtastic/protobuf/localonly.proto\x1a\x1emeshtastic/protobuf/mesh.proto\x1a#meshtastic/protobuf/telemetry.proto\x1a meshtastic/protobuf/nanopb.proto\"\x99\x01\n\x0cPositionLite\x12\x12\n\nlatitude_i\x18\x01 \x01(\x0f\x12\x13\n\x0blongitude_i\x18\x02 \x01(\x0f\x12\x10\n\x08\x61ltitude\x18\x03 \x01(\x05\x12\x0c\n\x04time\x18\x04 \x01(\x07\x12@\n\x0flocation_source\x18\x05 \x01(\x0e\x32\'.meshtastic.protobuf.Position.LocSource\"\x94\x02\n\x08UserLite\x12\x13\n\x07macaddr\x18\x01 \x01(\x0c\x42\x02\x18\x01\x12\x11\n\tlong_name\x18\x02 \x01(\t\x12\x12\n\nshort_name\x18\x03 \x01(\t\x12\x34\n\x08hw_model\x18\x04 \x01(\x0e\x32\".meshtastic.protobuf.HardwareModel\x12\x13\n\x0bis_licensed\x18\x05 \x01(\x08\x12;\n\x04role\x18\x06 \x01(\x0e\x32-.meshtastic.protobuf.Config.DeviceConfig.Role\x12\x12\n\npublic_key\x18\x07 \x01(\x0c\x12\x1c\n\x0fis_unmessagable\x18\t \x01(\x08H\x00\x88\x01\x01\x42\x12\n\x10_is_unmessagable\"\xf0\x02\n\x0cNodeInfoLite\x12\x0b\n\x03num\x18\x01 \x01(\r\x12+\n\x04user\x18\x02 \x01(\x0b\x32\x1d.meshtastic.protobuf.UserLite\x12\x33\n\x08position\x18\x03 \x01(\x0b\x32!.meshtastic.protobuf.PositionLite\x12\x0b\n\x03snr\x18\x04 \x01(\x02\x12\x12\n\nlast_heard\x18\x05 \x01(\x07\x12:\n\x0e\x64\x65vice_metrics\x18\x06 \x01(\x0b\x32\".meshtastic.protobuf.DeviceMetrics\x12\x0f\n\x07\x63hannel\x18\x07 \x01(\r\x12\x10\n\x08via_mqtt\x18\x08 \x01(\x08\x12\x16\n\thops_away\x18\t \x01(\rH\x00\x88\x01\x01\x12\x13\n\x0bis_favorite\x18\n \x01(\x08\x12\x12\n\nis_ignored\x18\x0b \x01(\x08\x12\x10\n\x08next_hop\x18\x0c \x01(\r\x12\x10\n\x08\x62itfield\x18\r \x01(\rB\x0c\n\n_hops_away\"\xa1\x03\n\x0b\x44\x65viceState\x12\x30\n\x07my_node\x18\x02 \x01(\x0b\x32\x1f.meshtastic.protobuf.MyNodeInfo\x12(\n\x05owner\x18\x03 \x01(\x0b\x32\x19.meshtastic.protobuf.User\x12\x36\n\rreceive_queue\x18\x05 \x03(\x0b\x32\x1f.meshtastic.protobuf.MeshPacket\x12\x0f\n\x07version\x18\x08 \x01(\r\x12\x38\n\x0frx_text_message\x18\x07 \x01(\x0b\x32\x1f.meshtastic.protobuf.MeshPacket\x12\x13\n\x07no_save\x18\t \x01(\x08\x42\x02\x18\x01\x12\x19\n\rdid_gps_reset\x18\x0b \x01(\x08\x42\x02\x18\x01\x12\x34\n\x0brx_waypoint\x18\x0c \x01(\x0b\x32\x1f.meshtastic.protobuf.MeshPacket\x12M\n\x19node_remote_hardware_pins\x18\r \x03(\x0b\x32*.meshtastic.protobuf.NodeRemoteHardwarePin\"}\n\x0cNodeDatabase\x12\x0f\n\x07version\x18\x01 \x01(\r\x12\\\n\x05nodes\x18\x02 \x03(\x0b\x32!.meshtastic.protobuf.NodeInfoLiteB*\x92?\'\x92\x01$std::vector<meshtastic_NodeInfoLite>\"N\n\x0b\x43hannelFile\x12.\n\x08\x63hannels\x18\x01 \x03(\x0b\x32\x1c.meshtastic.protobuf.Channel\x12\x0f\n\x07version\x18\x02 \x01(\r\"\x86\x02\n\x11\x42\x61\x63kupPreferences\x12\x0f\n\x07version\x18\x01 \x01(\r\x12\x11\n\ttimestamp\x18\x02 \x01(\x07\x12\x30\n\x06\x63onfig\x18\x03 \x01(\x0b\x32 .meshtastic.protobuf.LocalConfig\x12=\n\rmodule_config\x18\x04 \x01(\x0b\x32&.meshtastic.protobuf.LocalModuleConfig\x12\x32\n\x08\x63hannels\x18\x05 \x01(\x0b\x32 .meshtastic.protobuf.ChannelFile\x12(\n\x05owner\x18\x06 \x01(\x0b\x32\x19.meshtastic.protobuf.UserBn\n\x14org.meshtastic.protoB\nDeviceOnlyZ\"github.com/meshtastic/go/generated\xaa\x02\x14Meshtastic.Protobufs\xba\x02\x00\x92?\x0b\xc2\x01\x08<vector>b\x06proto3') DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n$meshtastic/protobuf/deviceonly.proto\x12\x13meshtastic.protobuf\x1a!meshtastic/protobuf/channel.proto\x1a meshtastic/protobuf/config.proto\x1a#meshtastic/protobuf/localonly.proto\x1a\x1emeshtastic/protobuf/mesh.proto\x1a#meshtastic/protobuf/telemetry.proto\x1a meshtastic/protobuf/nanopb.proto\"\x99\x01\n\x0cPositionLite\x12\x12\n\nlatitude_i\x18\x01 \x01(\x0f\x12\x13\n\x0blongitude_i\x18\x02 \x01(\x0f\x12\x10\n\x08\x61ltitude\x18\x03 \x01(\x05\x12\x0c\n\x04time\x18\x04 \x01(\x07\x12@\n\x0flocation_source\x18\x05 \x01(\x0e\x32\'.meshtastic.protobuf.Position.LocSource\"\x94\x02\n\x08UserLite\x12\x13\n\x07macaddr\x18\x01 \x01(\x0c\x42\x02\x18\x01\x12\x11\n\tlong_name\x18\x02 \x01(\t\x12\x12\n\nshort_name\x18\x03 \x01(\t\x12\x34\n\x08hw_model\x18\x04 \x01(\x0e\x32\".meshtastic.protobuf.HardwareModel\x12\x13\n\x0bis_licensed\x18\x05 \x01(\x08\x12;\n\x04role\x18\x06 \x01(\x0e\x32-.meshtastic.protobuf.Config.DeviceConfig.Role\x12\x12\n\npublic_key\x18\x07 \x01(\x0c\x12\x1c\n\x0fis_unmessagable\x18\t \x01(\x08H\x00\x88\x01\x01\x42\x12\n\x10_is_unmessagable\"\xf0\x02\n\x0cNodeInfoLite\x12\x0b\n\x03num\x18\x01 \x01(\r\x12+\n\x04user\x18\x02 \x01(\x0b\x32\x1d.meshtastic.protobuf.UserLite\x12\x33\n\x08position\x18\x03 \x01(\x0b\x32!.meshtastic.protobuf.PositionLite\x12\x0b\n\x03snr\x18\x04 \x01(\x02\x12\x12\n\nlast_heard\x18\x05 \x01(\x07\x12:\n\x0e\x64\x65vice_metrics\x18\x06 \x01(\x0b\x32\".meshtastic.protobuf.DeviceMetrics\x12\x0f\n\x07\x63hannel\x18\x07 \x01(\r\x12\x10\n\x08via_mqtt\x18\x08 \x01(\x08\x12\x16\n\thops_away\x18\t \x01(\rH\x00\x88\x01\x01\x12\x13\n\x0bis_favorite\x18\n \x01(\x08\x12\x12\n\nis_ignored\x18\x0b \x01(\x08\x12\x10\n\x08next_hop\x18\x0c \x01(\r\x12\x10\n\x08\x62itfield\x18\r \x01(\rB\x0c\n\n_hops_away\"\xa1\x03\n\x0b\x44\x65viceState\x12\x30\n\x07my_node\x18\x02 \x01(\x0b\x32\x1f.meshtastic.protobuf.MyNodeInfo\x12(\n\x05owner\x18\x03 \x01(\x0b\x32\x19.meshtastic.protobuf.User\x12\x36\n\rreceive_queue\x18\x05 \x03(\x0b\x32\x1f.meshtastic.protobuf.MeshPacket\x12\x0f\n\x07version\x18\x08 \x01(\r\x12\x38\n\x0frx_text_message\x18\x07 \x01(\x0b\x32\x1f.meshtastic.protobuf.MeshPacket\x12\x13\n\x07no_save\x18\t \x01(\x08\x42\x02\x18\x01\x12\x19\n\rdid_gps_reset\x18\x0b \x01(\x08\x42\x02\x18\x01\x12\x34\n\x0brx_waypoint\x18\x0c \x01(\x0b\x32\x1f.meshtastic.protobuf.MeshPacket\x12M\n\x19node_remote_hardware_pins\x18\r \x03(\x0b\x32*.meshtastic.protobuf.NodeRemoteHardwarePin\"}\n\x0cNodeDatabase\x12\x0f\n\x07version\x18\x01 \x01(\r\x12\\\n\x05nodes\x18\x02 \x03(\x0b\x32!.meshtastic.protobuf.NodeInfoLiteB*\x92?\'\x92\x01$std::vector<meshtastic_NodeInfoLite>\"N\n\x0b\x43hannelFile\x12.\n\x08\x63hannels\x18\x01 \x03(\x0b\x32\x1c.meshtastic.protobuf.Channel\x12\x0f\n\x07version\x18\x02 \x01(\r\"\x86\x02\n\x11\x42\x61\x63kupPreferences\x12\x0f\n\x07version\x18\x01 \x01(\r\x12\x11\n\ttimestamp\x18\x02 \x01(\x07\x12\x30\n\x06\x63onfig\x18\x03 \x01(\x0b\x32 .meshtastic.protobuf.LocalConfig\x12=\n\rmodule_config\x18\x04 \x01(\x0b\x32&.meshtastic.protobuf.LocalModuleConfig\x12\x32\n\x08\x63hannels\x18\x05 \x01(\x0b\x32 .meshtastic.protobuf.ChannelFile\x12(\n\x05owner\x18\x06 \x01(\x0b\x32\x19.meshtastic.protobuf.UserBm\n\x13\x63om.geeksville.meshB\nDeviceOnlyZ\"github.com/meshtastic/go/generated\xaa\x02\x14Meshtastic.Protobufs\xba\x02\x00\x92?\x0b\xc2\x01\x08<vector>b\x06proto3')
_globals = globals() _globals = globals()
_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) _builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals)
_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'meshtastic.protobuf.deviceonly_pb2', _globals) _builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'meshtastic.protobuf.deviceonly_pb2', _globals)
if _descriptor._USE_C_DESCRIPTORS == False: if _descriptor._USE_C_DESCRIPTORS == False:
DESCRIPTOR._options = None DESCRIPTOR._options = None
DESCRIPTOR._serialized_options = b'\n\024org.meshtastic.protoB\nDeviceOnlyZ\"github.com/meshtastic/go/generated\252\002\024Meshtastic.Protobufs\272\002\000\222?\013\302\001\010<vector>' DESCRIPTOR._serialized_options = b'\n\023com.geeksville.meshB\nDeviceOnlyZ\"github.com/meshtastic/go/generated\252\002\024Meshtastic.Protobufs\272\002\000\222?\013\302\001\010<vector>'
_USERLITE.fields_by_name['macaddr']._options = None _USERLITE.fields_by_name['macaddr']._options = None
_USERLITE.fields_by_name['macaddr']._serialized_options = b'\030\001' _USERLITE.fields_by_name['macaddr']._serialized_options = b'\030\001'
_DEVICESTATE.fields_by_name['no_save']._options = None _DEVICESTATE.fields_by_name['no_save']._options = None
+2 -2
View File
@@ -13,14 +13,14 @@ _sym_db = _symbol_database.Default()
DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n%meshtastic/protobuf/interdevice.proto\x12\x13meshtastic.protobuf\"s\n\nSensorData\x12.\n\x04type\x18\x01 \x01(\x0e\x32 .meshtastic.protobuf.MessageType\x12\x15\n\x0b\x66loat_value\x18\x02 \x01(\x02H\x00\x12\x16\n\x0cuint32_value\x18\x03 \x01(\rH\x00\x42\x06\n\x04\x64\x61ta\"_\n\x12InterdeviceMessage\x12\x0e\n\x04nmea\x18\x01 \x01(\tH\x00\x12\x31\n\x06sensor\x18\x02 \x01(\x0b\x32\x1f.meshtastic.protobuf.SensorDataH\x00\x42\x06\n\x04\x64\x61ta*\xd5\x01\n\x0bMessageType\x12\x07\n\x03\x41\x43K\x10\x00\x12\x15\n\x10\x43OLLECT_INTERVAL\x10\xa0\x01\x12\x0c\n\x07\x42\x45\x45P_ON\x10\xa1\x01\x12\r\n\x08\x42\x45\x45P_OFF\x10\xa2\x01\x12\r\n\x08SHUTDOWN\x10\xa3\x01\x12\r\n\x08POWER_ON\x10\xa4\x01\x12\x0f\n\nSCD41_TEMP\x10\xb0\x01\x12\x13\n\x0eSCD41_HUMIDITY\x10\xb1\x01\x12\x0e\n\tSCD41_CO2\x10\xb2\x01\x12\x0f\n\nAHT20_TEMP\x10\xb3\x01\x12\x13\n\x0e\x41HT20_HUMIDITY\x10\xb4\x01\x12\x0f\n\nTVOC_INDEX\x10\xb5\x01\x42g\n\x14org.meshtastic.protoB\x11InterdeviceProtosZ\"github.com/meshtastic/go/generated\xaa\x02\x14Meshtastic.Protobufs\xba\x02\x00\x62\x06proto3') DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n%meshtastic/protobuf/interdevice.proto\x12\x13meshtastic.protobuf\"s\n\nSensorData\x12.\n\x04type\x18\x01 \x01(\x0e\x32 .meshtastic.protobuf.MessageType\x12\x15\n\x0b\x66loat_value\x18\x02 \x01(\x02H\x00\x12\x16\n\x0cuint32_value\x18\x03 \x01(\rH\x00\x42\x06\n\x04\x64\x61ta\"_\n\x12InterdeviceMessage\x12\x0e\n\x04nmea\x18\x01 \x01(\tH\x00\x12\x31\n\x06sensor\x18\x02 \x01(\x0b\x32\x1f.meshtastic.protobuf.SensorDataH\x00\x42\x06\n\x04\x64\x61ta*\xd5\x01\n\x0bMessageType\x12\x07\n\x03\x41\x43K\x10\x00\x12\x15\n\x10\x43OLLECT_INTERVAL\x10\xa0\x01\x12\x0c\n\x07\x42\x45\x45P_ON\x10\xa1\x01\x12\r\n\x08\x42\x45\x45P_OFF\x10\xa2\x01\x12\r\n\x08SHUTDOWN\x10\xa3\x01\x12\r\n\x08POWER_ON\x10\xa4\x01\x12\x0f\n\nSCD41_TEMP\x10\xb0\x01\x12\x13\n\x0eSCD41_HUMIDITY\x10\xb1\x01\x12\x0e\n\tSCD41_CO2\x10\xb2\x01\x12\x0f\n\nAHT20_TEMP\x10\xb3\x01\x12\x13\n\x0e\x41HT20_HUMIDITY\x10\xb4\x01\x12\x0f\n\nTVOC_INDEX\x10\xb5\x01\x42\x66\n\x13\x63om.geeksville.meshB\x11InterdeviceProtosZ\"github.com/meshtastic/go/generated\xaa\x02\x14Meshtastic.Protobufs\xba\x02\x00\x62\x06proto3')
_globals = globals() _globals = globals()
_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) _builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals)
_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'meshtastic.protobuf.interdevice_pb2', _globals) _builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'meshtastic.protobuf.interdevice_pb2', _globals)
if _descriptor._USE_C_DESCRIPTORS == False: if _descriptor._USE_C_DESCRIPTORS == False:
DESCRIPTOR._options = None DESCRIPTOR._options = None
DESCRIPTOR._serialized_options = b'\n\024org.meshtastic.protoB\021InterdeviceProtosZ\"github.com/meshtastic/go/generated\252\002\024Meshtastic.Protobufs\272\002\000' DESCRIPTOR._serialized_options = b'\n\023com.geeksville.meshB\021InterdeviceProtosZ\"github.com/meshtastic/go/generated\252\002\024Meshtastic.Protobufs\272\002\000'
_globals['_MESSAGETYPE']._serialized_start=277 _globals['_MESSAGETYPE']._serialized_start=277
_globals['_MESSAGETYPE']._serialized_end=490 _globals['_MESSAGETYPE']._serialized_end=490
_globals['_SENSORDATA']._serialized_start=62 _globals['_SENSORDATA']._serialized_start=62
+2 -2
View File
@@ -15,14 +15,14 @@ from meshtastic.protobuf import config_pb2 as meshtastic_dot_protobuf_dot_config
from meshtastic.protobuf import module_config_pb2 as meshtastic_dot_protobuf_dot_module__config__pb2 from meshtastic.protobuf import module_config_pb2 as meshtastic_dot_protobuf_dot_module__config__pb2
DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n#meshtastic/protobuf/localonly.proto\x12\x13meshtastic.protobuf\x1a meshtastic/protobuf/config.proto\x1a\'meshtastic/protobuf/module_config.proto\"\xfa\x03\n\x0bLocalConfig\x12\x38\n\x06\x64\x65vice\x18\x01 \x01(\x0b\x32(.meshtastic.protobuf.Config.DeviceConfig\x12<\n\x08position\x18\x02 \x01(\x0b\x32*.meshtastic.protobuf.Config.PositionConfig\x12\x36\n\x05power\x18\x03 \x01(\x0b\x32\'.meshtastic.protobuf.Config.PowerConfig\x12:\n\x07network\x18\x04 \x01(\x0b\x32).meshtastic.protobuf.Config.NetworkConfig\x12:\n\x07\x64isplay\x18\x05 \x01(\x0b\x32).meshtastic.protobuf.Config.DisplayConfig\x12\x34\n\x04lora\x18\x06 \x01(\x0b\x32&.meshtastic.protobuf.Config.LoRaConfig\x12>\n\tbluetooth\x18\x07 \x01(\x0b\x32+.meshtastic.protobuf.Config.BluetoothConfig\x12\x0f\n\x07version\x18\x08 \x01(\r\x12<\n\x08security\x18\t \x01(\x0b\x32*.meshtastic.protobuf.Config.SecurityConfig\"\xf0\x07\n\x11LocalModuleConfig\x12:\n\x04mqtt\x18\x01 \x01(\x0b\x32,.meshtastic.protobuf.ModuleConfig.MQTTConfig\x12>\n\x06serial\x18\x02 \x01(\x0b\x32..meshtastic.protobuf.ModuleConfig.SerialConfig\x12[\n\x15\x65xternal_notification\x18\x03 \x01(\x0b\x32<.meshtastic.protobuf.ModuleConfig.ExternalNotificationConfig\x12K\n\rstore_forward\x18\x04 \x01(\x0b\x32\x34.meshtastic.protobuf.ModuleConfig.StoreForwardConfig\x12\x45\n\nrange_test\x18\x05 \x01(\x0b\x32\x31.meshtastic.protobuf.ModuleConfig.RangeTestConfig\x12\x44\n\ttelemetry\x18\x06 \x01(\x0b\x32\x31.meshtastic.protobuf.ModuleConfig.TelemetryConfig\x12M\n\x0e\x63\x61nned_message\x18\x07 \x01(\x0b\x32\x35.meshtastic.protobuf.ModuleConfig.CannedMessageConfig\x12<\n\x05\x61udio\x18\t \x01(\x0b\x32-.meshtastic.protobuf.ModuleConfig.AudioConfig\x12O\n\x0fremote_hardware\x18\n \x01(\x0b\x32\x36.meshtastic.protobuf.ModuleConfig.RemoteHardwareConfig\x12K\n\rneighbor_info\x18\x0b \x01(\x0b\x32\x34.meshtastic.protobuf.ModuleConfig.NeighborInfoConfig\x12Q\n\x10\x61mbient_lighting\x18\x0c \x01(\x0b\x32\x37.meshtastic.protobuf.ModuleConfig.AmbientLightingConfig\x12Q\n\x10\x64\x65tection_sensor\x18\r \x01(\x0b\x32\x37.meshtastic.protobuf.ModuleConfig.DetectionSensorConfig\x12\x46\n\npaxcounter\x18\x0e \x01(\x0b\x32\x32.meshtastic.protobuf.ModuleConfig.PaxcounterConfig\x12\x0f\n\x07version\x18\x08 \x01(\rBe\n\x14org.meshtastic.protoB\x0fLocalOnlyProtosZ\"github.com/meshtastic/go/generated\xaa\x02\x14Meshtastic.Protobufs\xba\x02\x00\x62\x06proto3') DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n#meshtastic/protobuf/localonly.proto\x12\x13meshtastic.protobuf\x1a meshtastic/protobuf/config.proto\x1a\'meshtastic/protobuf/module_config.proto\"\xfa\x03\n\x0bLocalConfig\x12\x38\n\x06\x64\x65vice\x18\x01 \x01(\x0b\x32(.meshtastic.protobuf.Config.DeviceConfig\x12<\n\x08position\x18\x02 \x01(\x0b\x32*.meshtastic.protobuf.Config.PositionConfig\x12\x36\n\x05power\x18\x03 \x01(\x0b\x32\'.meshtastic.protobuf.Config.PowerConfig\x12:\n\x07network\x18\x04 \x01(\x0b\x32).meshtastic.protobuf.Config.NetworkConfig\x12:\n\x07\x64isplay\x18\x05 \x01(\x0b\x32).meshtastic.protobuf.Config.DisplayConfig\x12\x34\n\x04lora\x18\x06 \x01(\x0b\x32&.meshtastic.protobuf.Config.LoRaConfig\x12>\n\tbluetooth\x18\x07 \x01(\x0b\x32+.meshtastic.protobuf.Config.BluetoothConfig\x12\x0f\n\x07version\x18\x08 \x01(\r\x12<\n\x08security\x18\t \x01(\x0b\x32*.meshtastic.protobuf.Config.SecurityConfig\"\xf0\x07\n\x11LocalModuleConfig\x12:\n\x04mqtt\x18\x01 \x01(\x0b\x32,.meshtastic.protobuf.ModuleConfig.MQTTConfig\x12>\n\x06serial\x18\x02 \x01(\x0b\x32..meshtastic.protobuf.ModuleConfig.SerialConfig\x12[\n\x15\x65xternal_notification\x18\x03 \x01(\x0b\x32<.meshtastic.protobuf.ModuleConfig.ExternalNotificationConfig\x12K\n\rstore_forward\x18\x04 \x01(\x0b\x32\x34.meshtastic.protobuf.ModuleConfig.StoreForwardConfig\x12\x45\n\nrange_test\x18\x05 \x01(\x0b\x32\x31.meshtastic.protobuf.ModuleConfig.RangeTestConfig\x12\x44\n\ttelemetry\x18\x06 \x01(\x0b\x32\x31.meshtastic.protobuf.ModuleConfig.TelemetryConfig\x12M\n\x0e\x63\x61nned_message\x18\x07 \x01(\x0b\x32\x35.meshtastic.protobuf.ModuleConfig.CannedMessageConfig\x12<\n\x05\x61udio\x18\t \x01(\x0b\x32-.meshtastic.protobuf.ModuleConfig.AudioConfig\x12O\n\x0fremote_hardware\x18\n \x01(\x0b\x32\x36.meshtastic.protobuf.ModuleConfig.RemoteHardwareConfig\x12K\n\rneighbor_info\x18\x0b \x01(\x0b\x32\x34.meshtastic.protobuf.ModuleConfig.NeighborInfoConfig\x12Q\n\x10\x61mbient_lighting\x18\x0c \x01(\x0b\x32\x37.meshtastic.protobuf.ModuleConfig.AmbientLightingConfig\x12Q\n\x10\x64\x65tection_sensor\x18\r \x01(\x0b\x32\x37.meshtastic.protobuf.ModuleConfig.DetectionSensorConfig\x12\x46\n\npaxcounter\x18\x0e \x01(\x0b\x32\x32.meshtastic.protobuf.ModuleConfig.PaxcounterConfig\x12\x0f\n\x07version\x18\x08 \x01(\rBd\n\x13\x63om.geeksville.meshB\x0fLocalOnlyProtosZ\"github.com/meshtastic/go/generated\xaa\x02\x14Meshtastic.Protobufs\xba\x02\x00\x62\x06proto3')
_globals = globals() _globals = globals()
_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) _builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals)
_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'meshtastic.protobuf.localonly_pb2', _globals) _builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'meshtastic.protobuf.localonly_pb2', _globals)
if _descriptor._USE_C_DESCRIPTORS == False: if _descriptor._USE_C_DESCRIPTORS == False:
DESCRIPTOR._options = None DESCRIPTOR._options = None
DESCRIPTOR._serialized_options = b'\n\024org.meshtastic.protoB\017LocalOnlyProtosZ\"github.com/meshtastic/go/generated\252\002\024Meshtastic.Protobufs\272\002\000' DESCRIPTOR._serialized_options = b'\n\023com.geeksville.meshB\017LocalOnlyProtosZ\"github.com/meshtastic/go/generated\252\002\024Meshtastic.Protobufs\272\002\000'
_globals['_LOCALCONFIG']._serialized_start=136 _globals['_LOCALCONFIG']._serialized_start=136
_globals['_LOCALCONFIG']._serialized_end=642 _globals['_LOCALCONFIG']._serialized_end=642
_globals['_LOCALMODULECONFIG']._serialized_start=645 _globals['_LOCALMODULECONFIG']._serialized_start=645
File diff suppressed because one or more lines are too long
+8 -66
View File
@@ -453,9 +453,9 @@ class _HardwareModelEnumTypeWrapper(google.protobuf.internal.enum_type_wrapper._
""" """
Seeed Tracker L1 EINK driver Seeed Tracker L1 EINK driver
""" """
MUZI_R1_NEO: _HardwareModel.ValueType # 101 QWANTZ_TINY_ARMS: _HardwareModel.ValueType # 101
""" """
Muzi Works R1 Neo Reserved ID for future and past use
""" """
T_DECK_PRO: _HardwareModel.ValueType # 102 T_DECK_PRO: _HardwareModel.ValueType # 102
""" """
@@ -465,10 +465,9 @@ class _HardwareModelEnumTypeWrapper(google.protobuf.internal.enum_type_wrapper._
""" """
Lilygo TLora Pager Lilygo TLora Pager
""" """
M5STACK_RESERVED: _HardwareModel.ValueType # 104 GAT562_MESH_TRIAL_TRACKER: _HardwareModel.ValueType # 104
""" """
M5Stack Reserved GAT562 Mesh Trial Tracker
0x68
""" """
WISMESH_TAG: _HardwareModel.ValueType # 105 WISMESH_TAG: _HardwareModel.ValueType # 105
""" """
@@ -495,34 +494,6 @@ class _HardwareModelEnumTypeWrapper(google.protobuf.internal.enum_type_wrapper._
""" """
New Heltec LoRA32 with ESP32-S3 CPU New Heltec LoRA32 with ESP32-S3 CPU
""" """
M5STACK_C6L: _HardwareModel.ValueType # 111
"""
M5Stack C6L
"""
M5STACK_CARDPUTER_ADV: _HardwareModel.ValueType # 112
"""
M5Stack Cardputer Adv
"""
HELTEC_WIRELESS_TRACKER_V2: _HardwareModel.ValueType # 113
"""
ESP32S3 main controller with GPS and TFT screen.
"""
T_WATCH_ULTRA: _HardwareModel.ValueType # 114
"""
LilyGo T-Watch Ultra
"""
THINKNODE_M3: _HardwareModel.ValueType # 115
"""
Elecrow ThinkNode M3
"""
WISMESH_TAP_V2: _HardwareModel.ValueType # 116
"""
RAK WISMESH_TAP_V2 with ESP32-S3 CPU
"""
RAK3401: _HardwareModel.ValueType # 117
"""
RAK3401
"""
PRIVATE_HW: _HardwareModel.ValueType # 255 PRIVATE_HW: _HardwareModel.ValueType # 255
""" """
------------------------------------------------------------------------------------------------------------------------------------------ ------------------------------------------------------------------------------------------------------------------------------------------
@@ -959,9 +930,9 @@ SEEED_WIO_TRACKER_L1_EINK: HardwareModel.ValueType # 100
""" """
Seeed Tracker L1 EINK driver Seeed Tracker L1 EINK driver
""" """
MUZI_R1_NEO: HardwareModel.ValueType # 101 QWANTZ_TINY_ARMS: HardwareModel.ValueType # 101
""" """
Muzi Works R1 Neo Reserved ID for future and past use
""" """
T_DECK_PRO: HardwareModel.ValueType # 102 T_DECK_PRO: HardwareModel.ValueType # 102
""" """
@@ -971,10 +942,9 @@ T_LORA_PAGER: HardwareModel.ValueType # 103
""" """
Lilygo TLora Pager Lilygo TLora Pager
""" """
M5STACK_RESERVED: HardwareModel.ValueType # 104 GAT562_MESH_TRIAL_TRACKER: HardwareModel.ValueType # 104
""" """
M5Stack Reserved GAT562 Mesh Trial Tracker
0x68
""" """
WISMESH_TAG: HardwareModel.ValueType # 105 WISMESH_TAG: HardwareModel.ValueType # 105
""" """
@@ -1001,34 +971,6 @@ HELTEC_V4: HardwareModel.ValueType # 110
""" """
New Heltec LoRA32 with ESP32-S3 CPU New Heltec LoRA32 with ESP32-S3 CPU
""" """
M5STACK_C6L: HardwareModel.ValueType # 111
"""
M5Stack C6L
"""
M5STACK_CARDPUTER_ADV: HardwareModel.ValueType # 112
"""
M5Stack Cardputer Adv
"""
HELTEC_WIRELESS_TRACKER_V2: HardwareModel.ValueType # 113
"""
ESP32S3 main controller with GPS and TFT screen.
"""
T_WATCH_ULTRA: HardwareModel.ValueType # 114
"""
LilyGo T-Watch Ultra
"""
THINKNODE_M3: HardwareModel.ValueType # 115
"""
Elecrow ThinkNode M3
"""
WISMESH_TAP_V2: HardwareModel.ValueType # 116
"""
RAK WISMESH_TAP_V2 with ESP32-S3 CPU
"""
RAK3401: HardwareModel.ValueType # 117
"""
RAK3401
"""
PRIVATE_HW: HardwareModel.ValueType # 255 PRIVATE_HW: HardwareModel.ValueType # 255
""" """
------------------------------------------------------------------------------------------------------------------------------------------ ------------------------------------------------------------------------------------------------------------------------------------------
File diff suppressed because one or more lines are too long
+1 -8
View File
@@ -874,7 +874,6 @@ class ModuleConfig(google.protobuf.message.Message):
HEALTH_MEASUREMENT_ENABLED_FIELD_NUMBER: builtins.int HEALTH_MEASUREMENT_ENABLED_FIELD_NUMBER: builtins.int
HEALTH_UPDATE_INTERVAL_FIELD_NUMBER: builtins.int HEALTH_UPDATE_INTERVAL_FIELD_NUMBER: builtins.int
HEALTH_SCREEN_ENABLED_FIELD_NUMBER: builtins.int HEALTH_SCREEN_ENABLED_FIELD_NUMBER: builtins.int
DEVICE_TELEMETRY_ENABLED_FIELD_NUMBER: builtins.int
device_update_interval: builtins.int device_update_interval: builtins.int
""" """
Interval in seconds of how often we should try to send our Interval in seconds of how often we should try to send our
@@ -935,11 +934,6 @@ class ModuleConfig(google.protobuf.message.Message):
""" """
Enable/Disable the health telemetry module on-device display Enable/Disable the health telemetry module on-device display
""" """
device_telemetry_enabled: builtins.bool
"""
Enable/Disable the device telemetry module to send metrics to the mesh
Note: We will still send telemtry to the connected phone / client every minute over the API
"""
def __init__( def __init__(
self, self,
*, *,
@@ -956,9 +950,8 @@ class ModuleConfig(google.protobuf.message.Message):
health_measurement_enabled: builtins.bool = ..., health_measurement_enabled: builtins.bool = ...,
health_update_interval: builtins.int = ..., health_update_interval: builtins.int = ...,
health_screen_enabled: builtins.bool = ..., health_screen_enabled: builtins.bool = ...,
device_telemetry_enabled: builtins.bool = ...,
) -> None: ... ) -> None: ...
def ClearField(self, field_name: typing.Literal["air_quality_enabled", b"air_quality_enabled", "air_quality_interval", b"air_quality_interval", "device_telemetry_enabled", b"device_telemetry_enabled", "device_update_interval", b"device_update_interval", "environment_display_fahrenheit", b"environment_display_fahrenheit", "environment_measurement_enabled", b"environment_measurement_enabled", "environment_screen_enabled", b"environment_screen_enabled", "environment_update_interval", b"environment_update_interval", "health_measurement_enabled", b"health_measurement_enabled", "health_screen_enabled", b"health_screen_enabled", "health_update_interval", b"health_update_interval", "power_measurement_enabled", b"power_measurement_enabled", "power_screen_enabled", b"power_screen_enabled", "power_update_interval", b"power_update_interval"]) -> None: ... def ClearField(self, field_name: typing.Literal["air_quality_enabled", b"air_quality_enabled", "air_quality_interval", b"air_quality_interval", "device_update_interval", b"device_update_interval", "environment_display_fahrenheit", b"environment_display_fahrenheit", "environment_measurement_enabled", b"environment_measurement_enabled", "environment_screen_enabled", b"environment_screen_enabled", "environment_update_interval", b"environment_update_interval", "health_measurement_enabled", b"health_measurement_enabled", "health_screen_enabled", b"health_screen_enabled", "health_update_interval", b"health_update_interval", "power_measurement_enabled", b"power_measurement_enabled", "power_screen_enabled", b"power_screen_enabled", "power_update_interval", b"power_update_interval"]) -> None: ...
@typing.final @typing.final
class CannedMessageConfig(google.protobuf.message.Message): class CannedMessageConfig(google.protobuf.message.Message):
+2 -2
View File
@@ -15,14 +15,14 @@ from meshtastic.protobuf import config_pb2 as meshtastic_dot_protobuf_dot_config
from meshtastic.protobuf import mesh_pb2 as meshtastic_dot_protobuf_dot_mesh__pb2 from meshtastic.protobuf import mesh_pb2 as meshtastic_dot_protobuf_dot_mesh__pb2
DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x1emeshtastic/protobuf/mqtt.proto\x12\x13meshtastic.protobuf\x1a meshtastic/protobuf/config.proto\x1a\x1emeshtastic/protobuf/mesh.proto\"j\n\x0fServiceEnvelope\x12/\n\x06packet\x18\x01 \x01(\x0b\x32\x1f.meshtastic.protobuf.MeshPacket\x12\x12\n\nchannel_id\x18\x02 \x01(\t\x12\x12\n\ngateway_id\x18\x03 \x01(\t\"\x83\x04\n\tMapReport\x12\x11\n\tlong_name\x18\x01 \x01(\t\x12\x12\n\nshort_name\x18\x02 \x01(\t\x12;\n\x04role\x18\x03 \x01(\x0e\x32-.meshtastic.protobuf.Config.DeviceConfig.Role\x12\x34\n\x08hw_model\x18\x04 \x01(\x0e\x32\".meshtastic.protobuf.HardwareModel\x12\x18\n\x10\x66irmware_version\x18\x05 \x01(\t\x12\x41\n\x06region\x18\x06 \x01(\x0e\x32\x31.meshtastic.protobuf.Config.LoRaConfig.RegionCode\x12H\n\x0cmodem_preset\x18\x07 \x01(\x0e\x32\x32.meshtastic.protobuf.Config.LoRaConfig.ModemPreset\x12\x1b\n\x13has_default_channel\x18\x08 \x01(\x08\x12\x12\n\nlatitude_i\x18\t \x01(\x0f\x12\x13\n\x0blongitude_i\x18\n \x01(\x0f\x12\x10\n\x08\x61ltitude\x18\x0b \x01(\x05\x12\x1a\n\x12position_precision\x18\x0c \x01(\r\x12\x1e\n\x16num_online_local_nodes\x18\r \x01(\r\x12!\n\x19has_opted_report_location\x18\x0e \x01(\x08\x42`\n\x14org.meshtastic.protoB\nMQTTProtosZ\"github.com/meshtastic/go/generated\xaa\x02\x14Meshtastic.Protobufs\xba\x02\x00\x62\x06proto3') DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x1emeshtastic/protobuf/mqtt.proto\x12\x13meshtastic.protobuf\x1a meshtastic/protobuf/config.proto\x1a\x1emeshtastic/protobuf/mesh.proto\"j\n\x0fServiceEnvelope\x12/\n\x06packet\x18\x01 \x01(\x0b\x32\x1f.meshtastic.protobuf.MeshPacket\x12\x12\n\nchannel_id\x18\x02 \x01(\t\x12\x12\n\ngateway_id\x18\x03 \x01(\t\"\x83\x04\n\tMapReport\x12\x11\n\tlong_name\x18\x01 \x01(\t\x12\x12\n\nshort_name\x18\x02 \x01(\t\x12;\n\x04role\x18\x03 \x01(\x0e\x32-.meshtastic.protobuf.Config.DeviceConfig.Role\x12\x34\n\x08hw_model\x18\x04 \x01(\x0e\x32\".meshtastic.protobuf.HardwareModel\x12\x18\n\x10\x66irmware_version\x18\x05 \x01(\t\x12\x41\n\x06region\x18\x06 \x01(\x0e\x32\x31.meshtastic.protobuf.Config.LoRaConfig.RegionCode\x12H\n\x0cmodem_preset\x18\x07 \x01(\x0e\x32\x32.meshtastic.protobuf.Config.LoRaConfig.ModemPreset\x12\x1b\n\x13has_default_channel\x18\x08 \x01(\x08\x12\x12\n\nlatitude_i\x18\t \x01(\x0f\x12\x13\n\x0blongitude_i\x18\n \x01(\x0f\x12\x10\n\x08\x61ltitude\x18\x0b \x01(\x05\x12\x1a\n\x12position_precision\x18\x0c \x01(\r\x12\x1e\n\x16num_online_local_nodes\x18\r \x01(\r\x12!\n\x19has_opted_report_location\x18\x0e \x01(\x08\x42_\n\x13\x63om.geeksville.meshB\nMQTTProtosZ\"github.com/meshtastic/go/generated\xaa\x02\x14Meshtastic.Protobufs\xba\x02\x00\x62\x06proto3')
_globals = globals() _globals = globals()
_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) _builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals)
_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'meshtastic.protobuf.mqtt_pb2', _globals) _builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'meshtastic.protobuf.mqtt_pb2', _globals)
if _descriptor._USE_C_DESCRIPTORS == False: if _descriptor._USE_C_DESCRIPTORS == False:
DESCRIPTOR._options = None DESCRIPTOR._options = None
DESCRIPTOR._serialized_options = b'\n\024org.meshtastic.protoB\nMQTTProtosZ\"github.com/meshtastic/go/generated\252\002\024Meshtastic.Protobufs\272\002\000' DESCRIPTOR._serialized_options = b'\n\023com.geeksville.meshB\nMQTTProtosZ\"github.com/meshtastic/go/generated\252\002\024Meshtastic.Protobufs\272\002\000'
_globals['_SERVICEENVELOPE']._serialized_start=121 _globals['_SERVICEENVELOPE']._serialized_start=121
_globals['_SERVICEENVELOPE']._serialized_end=227 _globals['_SERVICEENVELOPE']._serialized_end=227
_globals['_MAPREPORT']._serialized_start=230 _globals['_MAPREPORT']._serialized_start=230
+2 -2
View File
@@ -13,14 +13,14 @@ _sym_db = _symbol_database.Default()
DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\"meshtastic/protobuf/paxcount.proto\x12\x13meshtastic.protobuf\"5\n\x08Paxcount\x12\x0c\n\x04wifi\x18\x01 \x01(\r\x12\x0b\n\x03\x62le\x18\x02 \x01(\r\x12\x0e\n\x06uptime\x18\x03 \x01(\rBd\n\x14org.meshtastic.protoB\x0ePaxcountProtosZ\"github.com/meshtastic/go/generated\xaa\x02\x14Meshtastic.Protobufs\xba\x02\x00\x62\x06proto3') DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\"meshtastic/protobuf/paxcount.proto\x12\x13meshtastic.protobuf\"5\n\x08Paxcount\x12\x0c\n\x04wifi\x18\x01 \x01(\r\x12\x0b\n\x03\x62le\x18\x02 \x01(\r\x12\x0e\n\x06uptime\x18\x03 \x01(\rBc\n\x13\x63om.geeksville.meshB\x0ePaxcountProtosZ\"github.com/meshtastic/go/generated\xaa\x02\x14Meshtastic.Protobufs\xba\x02\x00\x62\x06proto3')
_globals = globals() _globals = globals()
_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) _builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals)
_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'meshtastic.protobuf.paxcount_pb2', _globals) _builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'meshtastic.protobuf.paxcount_pb2', _globals)
if _descriptor._USE_C_DESCRIPTORS == False: if _descriptor._USE_C_DESCRIPTORS == False:
DESCRIPTOR._options = None DESCRIPTOR._options = None
DESCRIPTOR._serialized_options = b'\n\024org.meshtastic.protoB\016PaxcountProtosZ\"github.com/meshtastic/go/generated\252\002\024Meshtastic.Protobufs\272\002\000' DESCRIPTOR._serialized_options = b'\n\023com.geeksville.meshB\016PaxcountProtosZ\"github.com/meshtastic/go/generated\252\002\024Meshtastic.Protobufs\272\002\000'
_globals['_PAXCOUNT']._serialized_start=59 _globals['_PAXCOUNT']._serialized_start=59
_globals['_PAXCOUNT']._serialized_end=112 _globals['_PAXCOUNT']._serialized_end=112
# @@protoc_insertion_point(module_scope) # @@protoc_insertion_point(module_scope)
+2 -2
View File
@@ -13,14 +13,14 @@ _sym_db = _symbol_database.Default()
DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\"meshtastic/protobuf/portnums.proto\x12\x13meshtastic.protobuf*\xf6\x04\n\x07PortNum\x12\x0f\n\x0bUNKNOWN_APP\x10\x00\x12\x14\n\x10TEXT_MESSAGE_APP\x10\x01\x12\x17\n\x13REMOTE_HARDWARE_APP\x10\x02\x12\x10\n\x0cPOSITION_APP\x10\x03\x12\x10\n\x0cNODEINFO_APP\x10\x04\x12\x0f\n\x0bROUTING_APP\x10\x05\x12\r\n\tADMIN_APP\x10\x06\x12\x1f\n\x1bTEXT_MESSAGE_COMPRESSED_APP\x10\x07\x12\x10\n\x0cWAYPOINT_APP\x10\x08\x12\r\n\tAUDIO_APP\x10\t\x12\x18\n\x14\x44\x45TECTION_SENSOR_APP\x10\n\x12\r\n\tALERT_APP\x10\x0b\x12\x18\n\x14KEY_VERIFICATION_APP\x10\x0c\x12\r\n\tREPLY_APP\x10 \x12\x11\n\rIP_TUNNEL_APP\x10!\x12\x12\n\x0ePAXCOUNTER_APP\x10\"\x12\x0e\n\nSERIAL_APP\x10@\x12\x15\n\x11STORE_FORWARD_APP\x10\x41\x12\x12\n\x0eRANGE_TEST_APP\x10\x42\x12\x11\n\rTELEMETRY_APP\x10\x43\x12\x0b\n\x07ZPS_APP\x10\x44\x12\x11\n\rSIMULATOR_APP\x10\x45\x12\x12\n\x0eTRACEROUTE_APP\x10\x46\x12\x14\n\x10NEIGHBORINFO_APP\x10G\x12\x0f\n\x0b\x41TAK_PLUGIN\x10H\x12\x12\n\x0eMAP_REPORT_APP\x10I\x12\x13\n\x0fPOWERSTRESS_APP\x10J\x12\x18\n\x14RETICULUM_TUNNEL_APP\x10L\x12\x0f\n\x0b\x43\x41YENNE_APP\x10M\x12\x10\n\x0bPRIVATE_APP\x10\x80\x02\x12\x13\n\x0e\x41TAK_FORWARDER\x10\x81\x02\x12\x08\n\x03MAX\x10\xff\x03\x42^\n\x14org.meshtastic.protoB\x08PortnumsZ\"github.com/meshtastic/go/generated\xaa\x02\x14Meshtastic.Protobufs\xba\x02\x00\x62\x06proto3') DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\"meshtastic/protobuf/portnums.proto\x12\x13meshtastic.protobuf*\xf6\x04\n\x07PortNum\x12\x0f\n\x0bUNKNOWN_APP\x10\x00\x12\x14\n\x10TEXT_MESSAGE_APP\x10\x01\x12\x17\n\x13REMOTE_HARDWARE_APP\x10\x02\x12\x10\n\x0cPOSITION_APP\x10\x03\x12\x10\n\x0cNODEINFO_APP\x10\x04\x12\x0f\n\x0bROUTING_APP\x10\x05\x12\r\n\tADMIN_APP\x10\x06\x12\x1f\n\x1bTEXT_MESSAGE_COMPRESSED_APP\x10\x07\x12\x10\n\x0cWAYPOINT_APP\x10\x08\x12\r\n\tAUDIO_APP\x10\t\x12\x18\n\x14\x44\x45TECTION_SENSOR_APP\x10\n\x12\r\n\tALERT_APP\x10\x0b\x12\x18\n\x14KEY_VERIFICATION_APP\x10\x0c\x12\r\n\tREPLY_APP\x10 \x12\x11\n\rIP_TUNNEL_APP\x10!\x12\x12\n\x0ePAXCOUNTER_APP\x10\"\x12\x0e\n\nSERIAL_APP\x10@\x12\x15\n\x11STORE_FORWARD_APP\x10\x41\x12\x12\n\x0eRANGE_TEST_APP\x10\x42\x12\x11\n\rTELEMETRY_APP\x10\x43\x12\x0b\n\x07ZPS_APP\x10\x44\x12\x11\n\rSIMULATOR_APP\x10\x45\x12\x12\n\x0eTRACEROUTE_APP\x10\x46\x12\x14\n\x10NEIGHBORINFO_APP\x10G\x12\x0f\n\x0b\x41TAK_PLUGIN\x10H\x12\x12\n\x0eMAP_REPORT_APP\x10I\x12\x13\n\x0fPOWERSTRESS_APP\x10J\x12\x18\n\x14RETICULUM_TUNNEL_APP\x10L\x12\x0f\n\x0b\x43\x41YENNE_APP\x10M\x12\x10\n\x0bPRIVATE_APP\x10\x80\x02\x12\x13\n\x0e\x41TAK_FORWARDER\x10\x81\x02\x12\x08\n\x03MAX\x10\xff\x03\x42]\n\x13\x63om.geeksville.meshB\x08PortnumsZ\"github.com/meshtastic/go/generated\xaa\x02\x14Meshtastic.Protobufs\xba\x02\x00\x62\x06proto3')
_globals = globals() _globals = globals()
_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) _builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals)
_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'meshtastic.protobuf.portnums_pb2', _globals) _builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'meshtastic.protobuf.portnums_pb2', _globals)
if _descriptor._USE_C_DESCRIPTORS == False: if _descriptor._USE_C_DESCRIPTORS == False:
DESCRIPTOR._options = None DESCRIPTOR._options = None
DESCRIPTOR._serialized_options = b'\n\024org.meshtastic.protoB\010PortnumsZ\"github.com/meshtastic/go/generated\252\002\024Meshtastic.Protobufs\272\002\000' DESCRIPTOR._serialized_options = b'\n\023com.geeksville.meshB\010PortnumsZ\"github.com/meshtastic/go/generated\252\002\024Meshtastic.Protobufs\272\002\000'
_globals['_PORTNUM']._serialized_start=60 _globals['_PORTNUM']._serialized_start=60
_globals['_PORTNUM']._serialized_end=690 _globals['_PORTNUM']._serialized_end=690
# @@protoc_insertion_point(module_scope) # @@protoc_insertion_point(module_scope)
+2 -2
View File
@@ -13,14 +13,14 @@ _sym_db = _symbol_database.Default()
DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\"meshtastic/protobuf/powermon.proto\x12\x13meshtastic.protobuf\"\xe0\x01\n\x08PowerMon\"\xd3\x01\n\x05State\x12\x08\n\x04None\x10\x00\x12\x11\n\rCPU_DeepSleep\x10\x01\x12\x12\n\x0e\x43PU_LightSleep\x10\x02\x12\x0c\n\x08Vext1_On\x10\x04\x12\r\n\tLora_RXOn\x10\x08\x12\r\n\tLora_TXOn\x10\x10\x12\x11\n\rLora_RXActive\x10 \x12\t\n\x05\x42T_On\x10@\x12\x0b\n\x06LED_On\x10\x80\x01\x12\x0e\n\tScreen_On\x10\x80\x02\x12\x13\n\x0eScreen_Drawing\x10\x80\x04\x12\x0c\n\x07Wifi_On\x10\x80\x08\x12\x0f\n\nGPS_Active\x10\x80\x10\"\x88\x03\n\x12PowerStressMessage\x12;\n\x03\x63md\x18\x01 \x01(\x0e\x32..meshtastic.protobuf.PowerStressMessage.Opcode\x12\x13\n\x0bnum_seconds\x18\x02 \x01(\x02\"\x9f\x02\n\x06Opcode\x12\t\n\x05UNSET\x10\x00\x12\x0e\n\nPRINT_INFO\x10\x01\x12\x0f\n\x0b\x46ORCE_QUIET\x10\x02\x12\r\n\tEND_QUIET\x10\x03\x12\r\n\tSCREEN_ON\x10\x10\x12\x0e\n\nSCREEN_OFF\x10\x11\x12\x0c\n\x08\x43PU_IDLE\x10 \x12\x11\n\rCPU_DEEPSLEEP\x10!\x12\x0e\n\nCPU_FULLON\x10\"\x12\n\n\x06LED_ON\x10\x30\x12\x0b\n\x07LED_OFF\x10\x31\x12\x0c\n\x08LORA_OFF\x10@\x12\x0b\n\x07LORA_TX\x10\x41\x12\x0b\n\x07LORA_RX\x10\x42\x12\n\n\x06\x42T_OFF\x10P\x12\t\n\x05\x42T_ON\x10Q\x12\x0c\n\x08WIFI_OFF\x10`\x12\x0b\n\x07WIFI_ON\x10\x61\x12\x0b\n\x07GPS_OFF\x10p\x12\n\n\x06GPS_ON\x10qBd\n\x14org.meshtastic.protoB\x0ePowerMonProtosZ\"github.com/meshtastic/go/generated\xaa\x02\x14Meshtastic.Protobufs\xba\x02\x00\x62\x06proto3') DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\"meshtastic/protobuf/powermon.proto\x12\x13meshtastic.protobuf\"\xe0\x01\n\x08PowerMon\"\xd3\x01\n\x05State\x12\x08\n\x04None\x10\x00\x12\x11\n\rCPU_DeepSleep\x10\x01\x12\x12\n\x0e\x43PU_LightSleep\x10\x02\x12\x0c\n\x08Vext1_On\x10\x04\x12\r\n\tLora_RXOn\x10\x08\x12\r\n\tLora_TXOn\x10\x10\x12\x11\n\rLora_RXActive\x10 \x12\t\n\x05\x42T_On\x10@\x12\x0b\n\x06LED_On\x10\x80\x01\x12\x0e\n\tScreen_On\x10\x80\x02\x12\x13\n\x0eScreen_Drawing\x10\x80\x04\x12\x0c\n\x07Wifi_On\x10\x80\x08\x12\x0f\n\nGPS_Active\x10\x80\x10\"\x88\x03\n\x12PowerStressMessage\x12;\n\x03\x63md\x18\x01 \x01(\x0e\x32..meshtastic.protobuf.PowerStressMessage.Opcode\x12\x13\n\x0bnum_seconds\x18\x02 \x01(\x02\"\x9f\x02\n\x06Opcode\x12\t\n\x05UNSET\x10\x00\x12\x0e\n\nPRINT_INFO\x10\x01\x12\x0f\n\x0b\x46ORCE_QUIET\x10\x02\x12\r\n\tEND_QUIET\x10\x03\x12\r\n\tSCREEN_ON\x10\x10\x12\x0e\n\nSCREEN_OFF\x10\x11\x12\x0c\n\x08\x43PU_IDLE\x10 \x12\x11\n\rCPU_DEEPSLEEP\x10!\x12\x0e\n\nCPU_FULLON\x10\"\x12\n\n\x06LED_ON\x10\x30\x12\x0b\n\x07LED_OFF\x10\x31\x12\x0c\n\x08LORA_OFF\x10@\x12\x0b\n\x07LORA_TX\x10\x41\x12\x0b\n\x07LORA_RX\x10\x42\x12\n\n\x06\x42T_OFF\x10P\x12\t\n\x05\x42T_ON\x10Q\x12\x0c\n\x08WIFI_OFF\x10`\x12\x0b\n\x07WIFI_ON\x10\x61\x12\x0b\n\x07GPS_OFF\x10p\x12\n\n\x06GPS_ON\x10qBc\n\x13\x63om.geeksville.meshB\x0ePowerMonProtosZ\"github.com/meshtastic/go/generated\xaa\x02\x14Meshtastic.Protobufs\xba\x02\x00\x62\x06proto3')
_globals = globals() _globals = globals()
_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) _builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals)
_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'meshtastic.protobuf.powermon_pb2', _globals) _builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'meshtastic.protobuf.powermon_pb2', _globals)
if _descriptor._USE_C_DESCRIPTORS == False: if _descriptor._USE_C_DESCRIPTORS == False:
DESCRIPTOR._options = None DESCRIPTOR._options = None
DESCRIPTOR._serialized_options = b'\n\024org.meshtastic.protoB\016PowerMonProtosZ\"github.com/meshtastic/go/generated\252\002\024Meshtastic.Protobufs\272\002\000' DESCRIPTOR._serialized_options = b'\n\023com.geeksville.meshB\016PowerMonProtosZ\"github.com/meshtastic/go/generated\252\002\024Meshtastic.Protobufs\272\002\000'
_globals['_POWERMON']._serialized_start=60 _globals['_POWERMON']._serialized_start=60
_globals['_POWERMON']._serialized_end=284 _globals['_POWERMON']._serialized_end=284
_globals['_POWERMON_STATE']._serialized_start=73 _globals['_POWERMON_STATE']._serialized_start=73
+2 -2
View File
@@ -13,14 +13,14 @@ _sym_db = _symbol_database.Default()
DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n)meshtastic/protobuf/remote_hardware.proto\x12\x13meshtastic.protobuf\"\xdf\x01\n\x0fHardwareMessage\x12\x37\n\x04type\x18\x01 \x01(\x0e\x32).meshtastic.protobuf.HardwareMessage.Type\x12\x11\n\tgpio_mask\x18\x02 \x01(\x04\x12\x12\n\ngpio_value\x18\x03 \x01(\x04\"l\n\x04Type\x12\t\n\x05UNSET\x10\x00\x12\x0f\n\x0bWRITE_GPIOS\x10\x01\x12\x0f\n\x0bWATCH_GPIOS\x10\x02\x12\x11\n\rGPIOS_CHANGED\x10\x03\x12\x0e\n\nREAD_GPIOS\x10\x04\x12\x14\n\x10READ_GPIOS_REPLY\x10\x05\x42\x64\n\x14org.meshtastic.protoB\x0eRemoteHardwareZ\"github.com/meshtastic/go/generated\xaa\x02\x14Meshtastic.Protobufs\xba\x02\x00\x62\x06proto3') DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n)meshtastic/protobuf/remote_hardware.proto\x12\x13meshtastic.protobuf\"\xdf\x01\n\x0fHardwareMessage\x12\x37\n\x04type\x18\x01 \x01(\x0e\x32).meshtastic.protobuf.HardwareMessage.Type\x12\x11\n\tgpio_mask\x18\x02 \x01(\x04\x12\x12\n\ngpio_value\x18\x03 \x01(\x04\"l\n\x04Type\x12\t\n\x05UNSET\x10\x00\x12\x0f\n\x0bWRITE_GPIOS\x10\x01\x12\x0f\n\x0bWATCH_GPIOS\x10\x02\x12\x11\n\rGPIOS_CHANGED\x10\x03\x12\x0e\n\nREAD_GPIOS\x10\x04\x12\x14\n\x10READ_GPIOS_REPLY\x10\x05\x42\x63\n\x13\x63om.geeksville.meshB\x0eRemoteHardwareZ\"github.com/meshtastic/go/generated\xaa\x02\x14Meshtastic.Protobufs\xba\x02\x00\x62\x06proto3')
_globals = globals() _globals = globals()
_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) _builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals)
_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'meshtastic.protobuf.remote_hardware_pb2', _globals) _builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'meshtastic.protobuf.remote_hardware_pb2', _globals)
if _descriptor._USE_C_DESCRIPTORS == False: if _descriptor._USE_C_DESCRIPTORS == False:
DESCRIPTOR._options = None DESCRIPTOR._options = None
DESCRIPTOR._serialized_options = b'\n\024org.meshtastic.protoB\016RemoteHardwareZ\"github.com/meshtastic/go/generated\252\002\024Meshtastic.Protobufs\272\002\000' DESCRIPTOR._serialized_options = b'\n\023com.geeksville.meshB\016RemoteHardwareZ\"github.com/meshtastic/go/generated\252\002\024Meshtastic.Protobufs\272\002\000'
_globals['_HARDWAREMESSAGE']._serialized_start=67 _globals['_HARDWAREMESSAGE']._serialized_start=67
_globals['_HARDWAREMESSAGE']._serialized_end=290 _globals['_HARDWAREMESSAGE']._serialized_end=290
_globals['_HARDWAREMESSAGE_TYPE']._serialized_start=182 _globals['_HARDWAREMESSAGE_TYPE']._serialized_start=182
+2 -2
View File
@@ -13,14 +13,14 @@ _sym_db = _symbol_database.Default()
DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x1fmeshtastic/protobuf/rtttl.proto\x12\x13meshtastic.protobuf\"\x1f\n\x0bRTTTLConfig\x12\x10\n\x08ringtone\x18\x01 \x01(\tBg\n\x14org.meshtastic.protoB\x11RTTTLConfigProtosZ\"github.com/meshtastic/go/generated\xaa\x02\x14Meshtastic.Protobufs\xba\x02\x00\x62\x06proto3') DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x1fmeshtastic/protobuf/rtttl.proto\x12\x13meshtastic.protobuf\"\x1f\n\x0bRTTTLConfig\x12\x10\n\x08ringtone\x18\x01 \x01(\tBf\n\x13\x63om.geeksville.meshB\x11RTTTLConfigProtosZ\"github.com/meshtastic/go/generated\xaa\x02\x14Meshtastic.Protobufs\xba\x02\x00\x62\x06proto3')
_globals = globals() _globals = globals()
_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) _builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals)
_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'meshtastic.protobuf.rtttl_pb2', _globals) _builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'meshtastic.protobuf.rtttl_pb2', _globals)
if _descriptor._USE_C_DESCRIPTORS == False: if _descriptor._USE_C_DESCRIPTORS == False:
DESCRIPTOR._options = None DESCRIPTOR._options = None
DESCRIPTOR._serialized_options = b'\n\024org.meshtastic.protoB\021RTTTLConfigProtosZ\"github.com/meshtastic/go/generated\252\002\024Meshtastic.Protobufs\272\002\000' DESCRIPTOR._serialized_options = b'\n\023com.geeksville.meshB\021RTTTLConfigProtosZ\"github.com/meshtastic/go/generated\252\002\024Meshtastic.Protobufs\272\002\000'
_globals['_RTTTLCONFIG']._serialized_start=56 _globals['_RTTTLCONFIG']._serialized_start=56
_globals['_RTTTLCONFIG']._serialized_end=87 _globals['_RTTTLCONFIG']._serialized_end=87
# @@protoc_insertion_point(module_scope) # @@protoc_insertion_point(module_scope)
+2 -2
View File
@@ -13,14 +13,14 @@ _sym_db = _symbol_database.Default()
DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n&meshtastic/protobuf/storeforward.proto\x12\x13meshtastic.protobuf\"\xc0\x07\n\x0fStoreAndForward\x12@\n\x02rr\x18\x01 \x01(\x0e\x32\x34.meshtastic.protobuf.StoreAndForward.RequestResponse\x12@\n\x05stats\x18\x02 \x01(\x0b\x32/.meshtastic.protobuf.StoreAndForward.StatisticsH\x00\x12?\n\x07history\x18\x03 \x01(\x0b\x32,.meshtastic.protobuf.StoreAndForward.HistoryH\x00\x12\x43\n\theartbeat\x18\x04 \x01(\x0b\x32..meshtastic.protobuf.StoreAndForward.HeartbeatH\x00\x12\x0e\n\x04text\x18\x05 \x01(\x0cH\x00\x1a\xcd\x01\n\nStatistics\x12\x16\n\x0emessages_total\x18\x01 \x01(\r\x12\x16\n\x0emessages_saved\x18\x02 \x01(\r\x12\x14\n\x0cmessages_max\x18\x03 \x01(\r\x12\x0f\n\x07up_time\x18\x04 \x01(\r\x12\x10\n\x08requests\x18\x05 \x01(\r\x12\x18\n\x10requests_history\x18\x06 \x01(\r\x12\x11\n\theartbeat\x18\x07 \x01(\x08\x12\x12\n\nreturn_max\x18\x08 \x01(\r\x12\x15\n\rreturn_window\x18\t \x01(\r\x1aI\n\x07History\x12\x18\n\x10history_messages\x18\x01 \x01(\r\x12\x0e\n\x06window\x18\x02 \x01(\r\x12\x14\n\x0clast_request\x18\x03 \x01(\r\x1a.\n\tHeartbeat\x12\x0e\n\x06period\x18\x01 \x01(\r\x12\x11\n\tsecondary\x18\x02 \x01(\r\"\xbc\x02\n\x0fRequestResponse\x12\t\n\x05UNSET\x10\x00\x12\x10\n\x0cROUTER_ERROR\x10\x01\x12\x14\n\x10ROUTER_HEARTBEAT\x10\x02\x12\x0f\n\x0bROUTER_PING\x10\x03\x12\x0f\n\x0bROUTER_PONG\x10\x04\x12\x0f\n\x0bROUTER_BUSY\x10\x05\x12\x12\n\x0eROUTER_HISTORY\x10\x06\x12\x10\n\x0cROUTER_STATS\x10\x07\x12\x16\n\x12ROUTER_TEXT_DIRECT\x10\x08\x12\x19\n\x15ROUTER_TEXT_BROADCAST\x10\t\x12\x10\n\x0c\x43LIENT_ERROR\x10@\x12\x12\n\x0e\x43LIENT_HISTORY\x10\x41\x12\x10\n\x0c\x43LIENT_STATS\x10\x42\x12\x0f\n\x0b\x43LIENT_PING\x10\x43\x12\x0f\n\x0b\x43LIENT_PONG\x10\x44\x12\x10\n\x0c\x43LIENT_ABORT\x10jB\t\n\x07variantBk\n\x14org.meshtastic.protoB\x15StoreAndForwardProtosZ\"github.com/meshtastic/go/generated\xaa\x02\x14Meshtastic.Protobufs\xba\x02\x00\x62\x06proto3') DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n&meshtastic/protobuf/storeforward.proto\x12\x13meshtastic.protobuf\"\xc0\x07\n\x0fStoreAndForward\x12@\n\x02rr\x18\x01 \x01(\x0e\x32\x34.meshtastic.protobuf.StoreAndForward.RequestResponse\x12@\n\x05stats\x18\x02 \x01(\x0b\x32/.meshtastic.protobuf.StoreAndForward.StatisticsH\x00\x12?\n\x07history\x18\x03 \x01(\x0b\x32,.meshtastic.protobuf.StoreAndForward.HistoryH\x00\x12\x43\n\theartbeat\x18\x04 \x01(\x0b\x32..meshtastic.protobuf.StoreAndForward.HeartbeatH\x00\x12\x0e\n\x04text\x18\x05 \x01(\x0cH\x00\x1a\xcd\x01\n\nStatistics\x12\x16\n\x0emessages_total\x18\x01 \x01(\r\x12\x16\n\x0emessages_saved\x18\x02 \x01(\r\x12\x14\n\x0cmessages_max\x18\x03 \x01(\r\x12\x0f\n\x07up_time\x18\x04 \x01(\r\x12\x10\n\x08requests\x18\x05 \x01(\r\x12\x18\n\x10requests_history\x18\x06 \x01(\r\x12\x11\n\theartbeat\x18\x07 \x01(\x08\x12\x12\n\nreturn_max\x18\x08 \x01(\r\x12\x15\n\rreturn_window\x18\t \x01(\r\x1aI\n\x07History\x12\x18\n\x10history_messages\x18\x01 \x01(\r\x12\x0e\n\x06window\x18\x02 \x01(\r\x12\x14\n\x0clast_request\x18\x03 \x01(\r\x1a.\n\tHeartbeat\x12\x0e\n\x06period\x18\x01 \x01(\r\x12\x11\n\tsecondary\x18\x02 \x01(\r\"\xbc\x02\n\x0fRequestResponse\x12\t\n\x05UNSET\x10\x00\x12\x10\n\x0cROUTER_ERROR\x10\x01\x12\x14\n\x10ROUTER_HEARTBEAT\x10\x02\x12\x0f\n\x0bROUTER_PING\x10\x03\x12\x0f\n\x0bROUTER_PONG\x10\x04\x12\x0f\n\x0bROUTER_BUSY\x10\x05\x12\x12\n\x0eROUTER_HISTORY\x10\x06\x12\x10\n\x0cROUTER_STATS\x10\x07\x12\x16\n\x12ROUTER_TEXT_DIRECT\x10\x08\x12\x19\n\x15ROUTER_TEXT_BROADCAST\x10\t\x12\x10\n\x0c\x43LIENT_ERROR\x10@\x12\x12\n\x0e\x43LIENT_HISTORY\x10\x41\x12\x10\n\x0c\x43LIENT_STATS\x10\x42\x12\x0f\n\x0b\x43LIENT_PING\x10\x43\x12\x0f\n\x0b\x43LIENT_PONG\x10\x44\x12\x10\n\x0c\x43LIENT_ABORT\x10jB\t\n\x07variantBj\n\x13\x63om.geeksville.meshB\x15StoreAndForwardProtosZ\"github.com/meshtastic/go/generated\xaa\x02\x14Meshtastic.Protobufs\xba\x02\x00\x62\x06proto3')
_globals = globals() _globals = globals()
_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) _builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals)
_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'meshtastic.protobuf.storeforward_pb2', _globals) _builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'meshtastic.protobuf.storeforward_pb2', _globals)
if _descriptor._USE_C_DESCRIPTORS == False: if _descriptor._USE_C_DESCRIPTORS == False:
DESCRIPTOR._options = None DESCRIPTOR._options = None
DESCRIPTOR._serialized_options = b'\n\024org.meshtastic.protoB\025StoreAndForwardProtosZ\"github.com/meshtastic/go/generated\252\002\024Meshtastic.Protobufs\272\002\000' DESCRIPTOR._serialized_options = b'\n\023com.geeksville.meshB\025StoreAndForwardProtosZ\"github.com/meshtastic/go/generated\252\002\024Meshtastic.Protobufs\272\002\000'
_globals['_STOREANDFORWARD']._serialized_start=64 _globals['_STOREANDFORWARD']._serialized_start=64
_globals['_STOREANDFORWARD']._serialized_end=1024 _globals['_STOREANDFORWARD']._serialized_end=1024
_globals['_STOREANDFORWARD_STATISTICS']._serialized_start=366 _globals['_STOREANDFORWARD_STATISTICS']._serialized_start=366
File diff suppressed because one or more lines are too long
+1 -15
View File
@@ -203,10 +203,6 @@ class _TelemetrySensorTypeEnumTypeWrapper(google.protobuf.internal.enum_type_wra
""" """
TSL2561 light sensor TSL2561 light sensor
""" """
BH1750: _TelemetrySensorType.ValueType # 45
"""
BH1750 light sensor
"""
class TelemetrySensorType(_TelemetrySensorType, metaclass=_TelemetrySensorTypeEnumTypeWrapper): class TelemetrySensorType(_TelemetrySensorType, metaclass=_TelemetrySensorTypeEnumTypeWrapper):
""" """
@@ -393,10 +389,6 @@ TSL2561: TelemetrySensorType.ValueType # 44
""" """
TSL2561 light sensor TSL2561 light sensor
""" """
BH1750: TelemetrySensorType.ValueType # 45
"""
BH1750 light sensor
"""
global___TelemetrySensorType = TelemetrySensorType global___TelemetrySensorType = TelemetrySensorType
@typing.final @typing.final
@@ -1034,7 +1026,6 @@ class LocalStats(google.protobuf.message.Message):
NUM_TX_RELAY_CANCELED_FIELD_NUMBER: builtins.int NUM_TX_RELAY_CANCELED_FIELD_NUMBER: builtins.int
HEAP_TOTAL_BYTES_FIELD_NUMBER: builtins.int HEAP_TOTAL_BYTES_FIELD_NUMBER: builtins.int
HEAP_FREE_BYTES_FIELD_NUMBER: builtins.int HEAP_FREE_BYTES_FIELD_NUMBER: builtins.int
NUM_TX_DROPPED_FIELD_NUMBER: builtins.int
uptime_seconds: builtins.int uptime_seconds: builtins.int
""" """
How long the device has been running since the last reboot (in seconds) How long the device has been running since the last reboot (in seconds)
@@ -1089,10 +1080,6 @@ class LocalStats(google.protobuf.message.Message):
""" """
Number of bytes free in the heap Number of bytes free in the heap
""" """
num_tx_dropped: builtins.int
"""
Number of packets that were dropped because the transmit queue was full.
"""
def __init__( def __init__(
self, self,
*, *,
@@ -1109,9 +1096,8 @@ class LocalStats(google.protobuf.message.Message):
num_tx_relay_canceled: builtins.int = ..., num_tx_relay_canceled: builtins.int = ...,
heap_total_bytes: builtins.int = ..., heap_total_bytes: builtins.int = ...,
heap_free_bytes: builtins.int = ..., heap_free_bytes: builtins.int = ...,
num_tx_dropped: builtins.int = ...,
) -> None: ... ) -> None: ...
def ClearField(self, field_name: typing.Literal["air_util_tx", b"air_util_tx", "channel_utilization", b"channel_utilization", "heap_free_bytes", b"heap_free_bytes", "heap_total_bytes", b"heap_total_bytes", "num_online_nodes", b"num_online_nodes", "num_packets_rx", b"num_packets_rx", "num_packets_rx_bad", b"num_packets_rx_bad", "num_packets_tx", b"num_packets_tx", "num_rx_dupe", b"num_rx_dupe", "num_total_nodes", b"num_total_nodes", "num_tx_dropped", b"num_tx_dropped", "num_tx_relay", b"num_tx_relay", "num_tx_relay_canceled", b"num_tx_relay_canceled", "uptime_seconds", b"uptime_seconds"]) -> None: ... def ClearField(self, field_name: typing.Literal["air_util_tx", b"air_util_tx", "channel_utilization", b"channel_utilization", "heap_free_bytes", b"heap_free_bytes", "heap_total_bytes", b"heap_total_bytes", "num_online_nodes", b"num_online_nodes", "num_packets_rx", b"num_packets_rx", "num_packets_rx_bad", b"num_packets_rx_bad", "num_packets_tx", b"num_packets_tx", "num_rx_dupe", b"num_rx_dupe", "num_total_nodes", b"num_total_nodes", "num_tx_relay", b"num_tx_relay", "num_tx_relay_canceled", b"num_tx_relay_canceled", "uptime_seconds", b"uptime_seconds"]) -> None: ...
global___LocalStats = LocalStats global___LocalStats = LocalStats
+2 -2
View File
@@ -13,14 +13,14 @@ _sym_db = _symbol_database.Default()
DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n meshtastic/protobuf/xmodem.proto\x12\x13meshtastic.protobuf\"\xbf\x01\n\x06XModem\x12\x34\n\x07\x63ontrol\x18\x01 \x01(\x0e\x32#.meshtastic.protobuf.XModem.Control\x12\x0b\n\x03seq\x18\x02 \x01(\r\x12\r\n\x05\x63rc16\x18\x03 \x01(\r\x12\x0e\n\x06\x62uffer\x18\x04 \x01(\x0c\"S\n\x07\x43ontrol\x12\x07\n\x03NUL\x10\x00\x12\x07\n\x03SOH\x10\x01\x12\x07\n\x03STX\x10\x02\x12\x07\n\x03\x45OT\x10\x04\x12\x07\n\x03\x41\x43K\x10\x06\x12\x07\n\x03NAK\x10\x15\x12\x07\n\x03\x43\x41N\x10\x18\x12\t\n\x05\x43TRLZ\x10\x1a\x42\x62\n\x14org.meshtastic.protoB\x0cXmodemProtosZ\"github.com/meshtastic/go/generated\xaa\x02\x14Meshtastic.Protobufs\xba\x02\x00\x62\x06proto3') DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n meshtastic/protobuf/xmodem.proto\x12\x13meshtastic.protobuf\"\xbf\x01\n\x06XModem\x12\x34\n\x07\x63ontrol\x18\x01 \x01(\x0e\x32#.meshtastic.protobuf.XModem.Control\x12\x0b\n\x03seq\x18\x02 \x01(\r\x12\r\n\x05\x63rc16\x18\x03 \x01(\r\x12\x0e\n\x06\x62uffer\x18\x04 \x01(\x0c\"S\n\x07\x43ontrol\x12\x07\n\x03NUL\x10\x00\x12\x07\n\x03SOH\x10\x01\x12\x07\n\x03STX\x10\x02\x12\x07\n\x03\x45OT\x10\x04\x12\x07\n\x03\x41\x43K\x10\x06\x12\x07\n\x03NAK\x10\x15\x12\x07\n\x03\x43\x41N\x10\x18\x12\t\n\x05\x43TRLZ\x10\x1a\x42\x61\n\x13\x63om.geeksville.meshB\x0cXmodemProtosZ\"github.com/meshtastic/go/generated\xaa\x02\x14Meshtastic.Protobufs\xba\x02\x00\x62\x06proto3')
_globals = globals() _globals = globals()
_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) _builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals)
_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'meshtastic.protobuf.xmodem_pb2', _globals) _builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'meshtastic.protobuf.xmodem_pb2', _globals)
if _descriptor._USE_C_DESCRIPTORS == False: if _descriptor._USE_C_DESCRIPTORS == False:
DESCRIPTOR._options = None DESCRIPTOR._options = None
DESCRIPTOR._serialized_options = b'\n\024org.meshtastic.protoB\014XmodemProtosZ\"github.com/meshtastic/go/generated\252\002\024Meshtastic.Protobufs\272\002\000' DESCRIPTOR._serialized_options = b'\n\023com.geeksville.meshB\014XmodemProtosZ\"github.com/meshtastic/go/generated\252\002\024Meshtastic.Protobufs\272\002\000'
_globals['_XMODEM']._serialized_start=58 _globals['_XMODEM']._serialized_start=58
_globals['_XMODEM']._serialized_end=249 _globals['_XMODEM']._serialized_end=249
_globals['_XMODEM_CONTROL']._serialized_start=166 _globals['_XMODEM_CONTROL']._serialized_start=166
+2 -2
View File
@@ -3,8 +3,8 @@
import subprocess import subprocess
from pathlib import Path from pathlib import Path
__version__ = "3.0.1" __version__ = "3.0.0"
__release_date__ = "2025-12-4" __release_date__ = "2025-11-05"
def get_git_revision(): def get_git_revision():
+45 -112
View File
@@ -22,68 +22,47 @@
} }
}, },
"chat": { "chat": {
"chat_title": "Chats:",
"replying_to": "Replying to:", "replying_to": "Replying to:",
"view_packet_details": "View packet details" "view_packet_details": "View packet details"
}, },
"nodelist": { "nodelist": {
"search_placeholder": "Search by name or ID...", "search_placeholder": "Search by name or ID...",
"all_roles": "All Roles", "all_roles": "All Roles",
"all_channels": "All Channels", "all_channels": "All Channels",
"all_hw": "All HW Models", "all_hw_models": "All HW Models",
"all_firmware": "All Firmware", "all_firmware": "All Firmware",
"export_csv": "Export CSV",
"show_favorites": "⭐ Show Favorites", "clear_filters": "Clear Filters",
"show_all": "Show All", "showing": "Showing",
"export_csv": "Export CSV", "nodes": "nodes",
"clear_filters": "Clear Filters", "short": "Short",
"long_name": "Long Name",
"showing_nodes": "Showing", "hw_model": "HW Model",
"nodes_suffix": "nodes", "firmware": "Firmware",
"role": "Role",
"loading_nodes": "Loading nodes...", "last_lat": "Last Latitude",
"error_loading_nodes": "Error loading nodes", "last_long": "Last Longitude",
"no_nodes_found": "No nodes found", "channel": "Channel",
"last_update": "Last Update",
"short_name": "Short", "loading_nodes": "Loading nodes...",
"long_name": "Long Name", "no_nodes": "No nodes found",
"hw_model": "HW Model", "error_nodes": "Error loading nodes"
"firmware": "Firmware",
"role": "Role",
"last_lat": "Last Latitude",
"last_long": "Last Longitude",
"channel": "Channel",
"last_seen": "Last Seen",
"favorite": "Favorite",
"time_just_now": "just now",
"time_min_ago": "min ago",
"time_hr_ago": "hr ago",
"time_day_ago": "day ago",
"time_days_ago": "days ago"
}, },
"net": { "net": {
"net_title": "Weekly Net:", "number_of_checkins": "Number of Check-ins:",
"total_messages": "Number of messages:", "view_packet_details": "View packet details",
"view_packet_details": "More details" "view_all_packets_from_node": "View all packets from this node",
"no_packets_found": "No packets found."
}, },
"map": { "map": {
"show_routers_only": "Show Routers Only", "channel": "Channel:",
"share_view": "Share This View", "model": "Model:",
"reset_filters": "Reset Filters To Defaults", "role": "Role:",
"channel_label": "Channel:",
"model_label": "Model:",
"role_label": "Role:",
"last_seen": "Last seen:", "last_seen": "Last seen:",
"firmware": "Firmware:", "firmware": "Firmware:",
"link_copied": "Link Copied!", "show_routers_only": "Show Routers Only",
"legend_traceroute": "Traceroute (with arrows)", "share_view": "Share This View"
"legend_neighbor": "Neighbor"
}, },
"stats": "stats":
{ {
"mesh_stats_summary": "Mesh Statistics - Summary (all available in Database)", "mesh_stats_summary": "Mesh Statistics - Summary (all available in Database)",
@@ -103,20 +82,21 @@
"all_channels": "All Channels", "all_channels": "All Channels",
"node_id": "Node ID" "node_id": "Node ID"
}, },
"top": { "top":
"top_traffic_nodes": "Top Nodes Traffic", {
"channel": "Channel", "top_traffic_nodes": "Top Traffic Nodes (last 24 hours)",
"search": "Search", "chart_description_1": "This chart shows a bell curve (normal distribution) based on the total \"Times Seen\" values for all nodes. It helps visualize how frequently nodes are heard, relative to the average.",
"search_placeholder": "Search nodes...", "chart_description_2": "This \"Times Seen\" value is the closest that we can get to Mesh utilization by node.",
"mean_label": "Mean:",
"stddev_label": "Standard Deviation:",
"long_name": "Long Name", "long_name": "Long Name",
"short_name": "Short Name", "short_name": "Short Name",
"packets_sent": "Sent (24h)", "channel": "Channel",
"times_seen": "Seen (24h)", "packets_sent": "Packets Sent",
"avg_gateways": "Avg Gateways", "times_seen": "Times Seen",
"showing_nodes": "Showing", "seen_percent": "Seen % of Mean",
"nodes_suffix": "nodes" "no_nodes": "No top traffic nodes available."
}, },
"nodegraph": "nodegraph":
{ {
"channel_label": "Channel:", "channel_label": "Channel:",
@@ -139,6 +119,7 @@
"to": "To", "to": "To",
"port": "Port", "port": "Port",
"links": "Links", "links": "Links",
"unknown_app": "UNKNOWN APP", "unknown_app": "UNKNOWN APP",
"text_message": "Text Message", "text_message": "Text Message",
"position": "Position", "position": "Position",
@@ -150,59 +131,11 @@
"telemetry": "Telemetry", "telemetry": "Telemetry",
"trace_route": "Trace Route", "trace_route": "Trace Route",
"neighbor_info": "Neighbor Info", "neighbor_info": "Neighbor Info",
"direct_to_mqtt": "direct to MQTT", "direct_to_mqtt": "direct to MQTT",
"all": "All", "all": "All",
"map": "Map", "map": "Map",
"graph": "Graph" "graph": "Graph"
}, }
"node": {
"specifications": "Specifications",
"node_id": "Node ID",
"long_name": "Long Name",
"short_name": "Short Name",
"hw_model": "Hardware Model",
"firmware": "Firmware",
"role": "Role",
"channel": "Channel",
"latitude": "Latitude",
"longitude": "Longitude",
"last_update": "Last Update",
"battery_voltage": "Battery & Voltage",
"air_channel": "Air & Channel Utilization",
"environment": "Environment Metrics",
"neighbors_chart": "Neighbors (Signal-to-Noise)",
"expand": "Expand",
"export_csv": "Export CSV",
"time": "Time",
"packet_id": "Packet ID",
"from": "From",
"to": "To",
"port": "Port",
"direct_to_mqtt": "Direct to MQTT",
"all_broadcast": "All"
},
"packet": {
"loading": "Loading packet information...",
"packet_id_label": "Packet ID",
"from_node": "From Node",
"to_node": "To Node",
"channel": "Channel",
"port": "Port",
"raw_payload": "Raw Payload",
"decoded_telemetry": "Decoded Telemetry",
"location": "Location",
"seen_by": "Seen By",
"gateway": "Gateway",
"rssi": "RSSI",
"snr": "SNR",
"hops": "Hop",
"time": "Time",
"packet_source": "Packet Source",
"distance": "Distance",
"node_id_short": "Node ID",
"all_broadcast": "All",
"direct_to_mqtt": "Direct to MQTT"
}
} }
+71 -122
View File
@@ -1,16 +1,16 @@
{ {
"base": { "base": {
"chat": "Conversaciones", "conversations": "Conversaciones",
"nodes": "Nodos", "nodes": "Nodos",
"everything": "Mostrar todo", "everything": "Mostrar Todo",
"graphs": "Gráficos de la Malla", "graph": "Gráficos de la Malla",
"net": "Red Semanal", "net": "Red Semanal",
"map": "Mapa en Vivo", "map": "Mapa en Vivo",
"stats": "Estadísticas", "stats": "Estadísticas",
"top": "Nodos con Mayor Tráfico", "top": "Nodos con Mayor Tráfico",
"footer": "Visita <strong><a href=\"https://github.com/pablorevilla-meshtastic/meshview\">Meshview</a></strong> en Github.", "footer": "Visita <strong><a href=\"https://github.com/pablorevilla-meshtastic/meshview\">Meshview</a></strong> en Github.",
"node_id": "ID de Nodo", "node id": "ID de Nodo",
"go_to_node": "Ir al nodo", "go to node": "Ir al nodo",
"all": "Todos", "all": "Todos",
"portnum_options": { "portnum_options": {
"1": "Mensaje de Texto", "1": "Mensaje de Texto",
@@ -21,65 +21,48 @@
"71": "Información de Vecinos" "71": "Información de Vecinos"
} }
}, },
"chat": { "chat": {
"chat_title": "Conversaciones:", "replying_to": "Respondiendo a:",
"replying_to": "Respondiendo a:", "view_packet_details": "Ver detalles del paquete"
"view_packet_details": "Ver detalles del paquete"
}, },
"nodelist": { "nodelist": {
"search_placeholder": "Buscar por nombre o ID...", "search_placeholder": "Buscar por nombre o ID...",
"all_roles": "Todos los roles", "all_roles": "Todos los Roles",
"all_channels": "Todos los canales", "all_channels": "Todos los Canales",
"all_hw": "Todos los modelos", "all_hw_models": "Todos los Modelos",
"all_firmware": "Todo el firmware", "all_firmware": "Todo el Firmware",
"show_favorites": "⭐ Mostrar favoritos",
"show_all": "⭐ Mostrar todos",
"export_csv": "Exportar CSV", "export_csv": "Exportar CSV",
"clear_filters": "Limpiar filtros", "clear_filters": "Limpiar Filtros",
"showing_nodes": "Mostrando", "showing": "Mostrando",
"nodes_suffix": "nodos", "nodes": "nodos",
"loading_nodes": "Cargando nodos...", "short": "Corto",
"error_loading_nodes": "Error al cargar nodos", "long_name": "Largo",
"no_nodes_found": "No se encontraron nodos", "hw_model": "Modelo",
"short_name": "Corto",
"long_name": "Nombre largo",
"hw_model": "Modelo HW",
"firmware": "Firmware", "firmware": "Firmware",
"role": "Rol", "role": "Rol",
"last_lat": "Última latitud", "last_lat": "Última Latitud",
"last_long": "Última longitud", "last_long": "Última Longitud",
"channel": "Canal", "channel": "Canal",
"last_seen": "Última vez visto", "last_update": "Última Actualización",
"favorite": "Favorito", "loading_nodes": "Cargando nodos...",
"time_just_now": "justo ahora", "no_nodes": "No se encontraron nodos",
"time_min_ago": "min atrás", "error_nodes": "Error al cargar nodos"
"time_hr_ago": "h atrás",
"time_day_ago": "día atrás",
"time_days_ago": "días atrás"
}, },
"net": { "net": {
"net_title": "Red Semanal:", "number_of_checkins": "Número de registros:",
"total_messages": "Número de mensajes:", "view_packet_details": "Ver detalles del paquete",
"view_packet_details": "Más Detalles" "view_all_packets_from_node": "Ver todos los paquetes de este nodo",
"no_packets_found": "No se encontraron paquetes."
}, },
"map": { "map": {
"filter_routers_only": "Mostrar solo enrutadores", "channel": "Canal:",
"share_view": "Compartir esta vista", "model": "Modelo:",
"reset_filters": "Restablecer filtros", "role": "Rol:",
"channel_label": "Canal:",
"model_label": "Modelo:",
"role_label": "Rol:",
"last_seen": "Visto por última vez:", "last_seen": "Visto por última vez:",
"firmware": "Firmware:", "firmware": "Firmware:",
"link_copied": "¡Enlace copiado!", "show_routers_only": "Mostrar solo enrutadores",
"legend_traceroute": "Ruta de traceroute (flechas de dirección)", "share_view": "Compartir esta vista"
"legend_neighbor": "Vínculo de vecinos"
}, },
"stats": { "stats": {
"mesh_stats_summary": "Estadísticas de la Malla - Resumen (completas en la base de datos)", "mesh_stats_summary": "Estadísticas de la Malla - Resumen (completas en la base de datos)",
"total_nodes": "Nodos Totales", "total_nodes": "Nodos Totales",
@@ -97,22 +80,22 @@
"export_csv": "Exportar CSV", "export_csv": "Exportar CSV",
"all_channels": "Todos los Canales" "all_channels": "Todos los Canales"
}, },
"top": { "top": {
"top_traffic_nodes": "Tráfico de Nodos (24h)", "top_traffic_nodes": "Tráfico (últimas 24 horas)",
"channel": "Canal", "chart_description_1": "Este gráfico muestra una curva normal (distribución normal) basada en el valor total de \"Veces Visto\" para todos los nodos. Ayuda a visualizar con qué frecuencia se detectan los nodos en relación con el promedio.",
"search": "Buscar", "chart_description_2": "Este valor de \"Veces Visto\" es lo más aproximado que tenemos al nivel de uso de la malla por nodo.",
"search_placeholder": "Buscar nodos...", "mean_label": "Media:",
"stddev_label": "Desviación Estándar:",
"long_name": "Nombre Largo", "long_name": "Nombre Largo",
"short_name": "Nombre Corto", "short_name": "Nombre Corto",
"packets_sent": "Enviados (24h)", "channel": "Canal",
"times_seen": "Visto (24h)", "packets_sent": "Paquetes Enviados",
"avg_gateways": "Promedio de Gateways", "times_seen": "Veces Visto",
"showing_nodes": "Mostrando", "seen_percent": "% Visto respecto a la Media",
"nodes_suffix": "nodos" "no_nodes": "No hay nodos con mayor tráfico disponibles."
}, },
"nodegraph":
"nodegraph": { {
"channel_label": "Canal:", "channel_label": "Canal:",
"search_placeholder": "Buscar nodo...", "search_placeholder": "Buscar nodo...",
"search_button": "Buscar", "search_button": "Buscar",
@@ -126,68 +109,34 @@
"unknown": "Desconocido", "unknown": "Desconocido",
"node_not_found": "¡Nodo no encontrado en el canal actual!" "node_not_found": "¡Nodo no encontrado en el canal actual!"
}, },
"firehose":
{
"live_feed": "📡 Flujo en Vivo",
"pause": "Pausar",
"resume": "Continuar",
"time": "Hora",
"packet_id": "ID del Paquete",
"from": "De",
"to": "Para",
"port": "Puerto",
"links": "Enlaces",
"firehose": { "unknown_app": "APLICACIÓN DESCONOCIDA",
"live_feed": "📡 Flujo en vivo", "text_message": "Mensaje de Texto",
"pause": "Pausar", "position": "Posición",
"resume": "Reanudar", "node_info": "Información del Nodo",
"time": "Hora", "routing": "Enrutamiento",
"packet_id": "ID de paquete", "administration": "Administración",
"from": "De", "waypoint": "Punto de Ruta",
"to": "A", "store_forward": "Almacenar y Reenviar",
"port": "Puerto", "telemetry": "Telemetría",
"direct_to_mqtt": "Directo a MQTT", "trace_route": "Rastreo de Ruta",
"all_broadcast": "Todos" "neighbor_info": "Información de Vecinos",
},
"node": { "direct_to_mqtt": "Directo a MQTT",
"specifications": "Especificaciones", "all": "Todos",
"node_id": "ID de Nodo", "map": "Mapa",
"long_name": "Nombre Largo", "graph": "Gráfico"
"short_name": "Nombre Corto", }
"hw_model": "Modelo de Hardware",
"firmware": "Firmware",
"role": "Rol",
"channel": "Canal",
"latitude": "Latitud",
"longitude": "Longitud",
"last_update": "Última Actualización",
"battery_voltage": "Batería y voltaje",
"air_channel": "Utilización del aire y del canal",
"environment": "Métricas Ambientales",
"neighbors_chart": "Vecinos (Relación Señal/Ruido)",
"expand": "Ampliar",
"export_csv": "Exportar CSV",
"time": "Hora",
"packet_id": "ID del Paquete",
"from": "De",
"to": "A",
"port": "Puerto",
"direct_to_mqtt": "Directo a MQTT",
"all_broadcast": "Todos"
},
"packet": {
"loading": "Cargando información del paquete...",
"packet_id_label": "ID del Paquete",
"from_node": "De",
"to_node": "A",
"channel": "Canal",
"port": "Puerto",
"raw_payload": "Payload sin procesar",
"decoded_telemetry": "Telemetría Decodificada",
"location": "Ubicación",
"seen_by": "Visto por",
"gateway": "Gateway",
"rssi": "RSSI",
"snr": "SNR",
"hops": "Saltos",
"time": "Hora",
"packet_source": "Origen del Paquete",
"distance": "Distancia",
"node_id_short": "ID de Nodo",
"all_broadcast": "Todos",
"direct_to_mqtt": "Directo a MQTT",
"signal": "Señal"
}
} }
+100
View File
@@ -0,0 +1,100 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>API Documentation - Config</title>
<link rel="stylesheet" href="https://unpkg.com/swagger-ui-dist/swagger-ui.css" />
<style>
body { margin: 0; background: #ffffff; color: #000; }
#swagger-ui { background: #ffffff; color: #000; }
.swagger-ui { background-color: #ffffff !important; color: #000 !important; }
.swagger-ui .topbar,
.swagger-ui .info,
.swagger-ui .opblock-summary-description,
.swagger-ui .parameters-col_description,
.swagger-ui .response-col_description { color: #000 !important; }
.swagger-ui .opblock { background-color: #f9f9f9 !important; border-color: #ddd !important; }
.swagger-ui .opblock-summary { background-color: #eaeaea !important; color: #000 !important; }
.swagger-ui .opblock-section-header { color: #000 !important; }
.swagger-ui .parameters,
.swagger-ui .response { background-color: #fafafa !important; color: #000 !important; }
.swagger-ui table { color: #000 !important; }
.swagger-ui a { color: #1a0dab !important; }
.swagger-ui input,
.swagger-ui select,
.swagger-ui textarea { background-color: #fff !important; color: #000 !important; border: 1px solid #ccc !important; }
</style>
</head>
<body>
<div id="swagger-ui"></div>
<script src="https://unpkg.com/swagger-ui-dist/swagger-ui-bundle.js"></script>
<script>
const spec = {
openapi: "3.0.0",
info: {
title: "Site Config API",
version: "1.0.0",
description: "API for retrieving the site configuration. This endpoint does not take any parameters."
},
paths: {
"/api/config": {
get: {
summary: "Get site configuration",
description: "Returns the current site configuration object.",
responses: {
"200": {
description: "Successful response",
content: {
"application/json": {
schema: {
type: "object",
properties: {
site_config: {
type: "object",
additionalProperties: true,
example: {
site_name: "MeshView",
firehose_interval: 1000,
starting: "/nodes",
theme: "dark"
}
}
}
}
}
}
},
"500": {
description: "Server error",
content: {
"application/json": {
schema: {
type: "object",
properties: {
error: { type: "string", example: "Internal server error" }
}
}
}
}
}
}
}
}
}
};
window.onload = () => {
SwaggerUIBundle({
spec,
dom_id: '#swagger-ui',
presets: [
SwaggerUIBundle.presets.apis,
SwaggerUIBundle.SwaggerUIStandalonePreset
],
layout: "BaseLayout"
});
};
</script>
</body>
</html>
+109
View File
@@ -0,0 +1,109 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>API Documentation - Edges</title>
<link rel="stylesheet" href="https://unpkg.com/swagger-ui-dist/swagger-ui.css" />
<style>
body {
margin: 0;
background: #ffffff;
color: #000000;
}
#swagger-ui { background: #ffffff; color: #000; }
.swagger-ui { background-color: #ffffff !important; color: #000 !important; }
.swagger-ui .topbar,
.swagger-ui .info,
.swagger-ui .opblock-summary-description,
.swagger-ui .parameters-col_description,
.swagger-ui .response-col_description { color: #000 !important; }
.swagger-ui .opblock { background-color: #f9f9f9 !important; border-color: #ddd !important; }
.swagger-ui .opblock-summary { background-color: #eaeaea !important; color: #000 !important; }
.swagger-ui .opblock-section-header { color: #000 !important; }
.swagger-ui .parameters,
.swagger-ui .response { background-color: #fafafa !important; color: #000 !important; }
.swagger-ui table { color: #000 !important; }
.swagger-ui a { color: #1a0dab !important; }
.swagger-ui input,
.swagger-ui select,
.swagger-ui textarea { background-color: #fff !important; color: #000 !important; border: 1px solid #ccc !important; }
</style>
</head>
<body>
<div id="swagger-ui"></div>
<script src="https://unpkg.com/swagger-ui-dist/swagger-ui-bundle.js"></script>
<script>
const spec = {
openapi: "3.0.0",
info: {
title: "Network Edges API",
version: "1.0.0",
description: "API for retrieving network edges derived from traceroutes and neighbor info packets, with optional type filtering."
},
paths: {
"/api/edges": {
get: {
summary: "Get network edges",
description: "Returns edges between nodes in the network. Optionally filter by type (`traceroute` or `neighbor`).",
parameters: [
{
name: "type",
in: "query",
required: false,
description: "Optional filter to only return edges of this type (`traceroute` or `neighbor`).",
schema: { type: "string", enum: ["traceroute", "neighbor"] }
}
],
responses: {
"200": {
description: "Successful response",
content: {
"application/json": {
schema: {
type: "object",
properties: {
edges: {
type: "array",
items: {
type: "object",
properties: {
from: { type: "integer", example: 101 },
to: { type: "integer", example: 102 },
type: { type: "string", example: "traceroute" }
}
}
}
}
}
}
}
},
"400": {
description: "Invalid request parameters",
content: {
"application/json": {
schema: { type: "object", properties: { error: { type: "string" } } }
}
}
}
}
}
}
}
};
window.onload = () => {
SwaggerUIBundle({
spec,
dom_id: '#swagger-ui',
presets: [
SwaggerUIBundle.presets.apis,
SwaggerUIBundle.SwaggerUIStandalonePreset
],
layout: "BaseLayout"
});
};
</script>
</body>
</html>
+134
View File
@@ -0,0 +1,134 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>API Documentation - Nodes</title>
<link rel="stylesheet" href="https://unpkg.com/swagger-ui-dist/swagger-ui.css" />
<style>
body { margin: 0; background: #ffffff; color: #000; }
#swagger-ui { background: #ffffff; color: #000; }
.swagger-ui { background-color: #ffffff !important; color: #000 !important; }
.swagger-ui .topbar,
.swagger-ui .info,
.swagger-ui .opblock-summary-description,
.swagger-ui .parameters-col_description,
.swagger-ui .response-col_description { color: #000 !important; }
.swagger-ui .opblock { background-color: #f9f9f9 !important; border-color: #ddd !important; }
.swagger-ui .opblock-summary { background-color: #eaeaea !important; color: #000 !important; }
.swagger-ui .opblock-section-header { color: #000 !important; }
.swagger-ui .parameters,
.swagger-ui .response { background-color: #fafafa !important; color: #000 !important; }
.swagger-ui table { color: #000 !important; }
.swagger-ui a { color: #1a0dab !important; }
.swagger-ui input,
.swagger-ui select,
.swagger-ui textarea { background-color: #fff !important; color: #000 !important; border: 1px solid #ccc !important; }
</style>
</head>
<body>
<div id="swagger-ui"></div>
<script src="https://unpkg.com/swagger-ui-dist/swagger-ui-bundle.js"></script>
<script>
const spec = {
openapi: "3.0.0",
info: {
title: "Network Nodes API",
version: "1.0.0",
description: "API for retrieving nodes in the network with optional filters by last seen time."
},
paths: {
"/api/nodes": {
get: {
summary: "Get network nodes",
description: "Returns a list of nodes with optional filtering by recent activity.",
parameters: [
{
name: "hours",
in: "query",
required: false,
description: "Return nodes seen in the last X hours.",
schema: { type: "integer", example: 24 }
},
{
name: "days",
in: "query",
required: false,
description: "Return nodes seen in the last X days.",
schema: { type: "integer", example: 7 }
},
{
name: "last_seen_after",
in: "query",
required: false,
description: "Return nodes last seen after this ISO8601 timestamp.",
schema: { type: "string", format: "date-time", example: "2025-08-25T14:00:00" }
}
],
responses: {
"200": {
description: "Successful response",
content: {
"application/json": {
schema: {
type: "object",
properties: {
nodes: {
type: "array",
items: {
type: "object",
properties: {
node_id: { type: "integer", example: 101 },
long_name: { type: "string", example: "Node Alpha" },
short_name: { type: "string", example: "A" },
channel: { type: "string", example: "2" },
last_seen: { type: "string", format: "date-time", example: "2025-08-25T12:00:00" },
last_lat: { type: "number", format: "float", example: 37.7749 },
last_long: { type: "number", format: "float", example: -122.4194 },
hardware: { type: "string", example: "Heltec V3" },
firmware: { type: "string", example: "1.0.5" },
role: { type: "string", example: "router" }
}
}
}
}
}
}
}
},
"400": {
description: "Invalid request parameters",
content: {
"application/json": {
schema: { type: "object", properties: { error: { type: "string" } } }
}
}
},
"500": {
description: "Server error",
content: {
"application/json": {
schema: { type: "object", properties: { error: { type: "string" } } }
}
}
}
}
}
}
}
};
window.onload = () => {
SwaggerUIBundle({
spec,
dom_id: '#swagger-ui',
presets: [
SwaggerUIBundle.presets.apis,
SwaggerUIBundle.SwaggerUIStandalonePreset
],
layout: "BaseLayout"
});
};
</script>
</body>
</html>
+167
View File
@@ -0,0 +1,167 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>API Documentation - Packets</title>
<link rel="stylesheet" href="https://unpkg.com/swagger-ui-dist/swagger-ui.css" />
<style>
body {
margin: 0;
background: #ffffff;
color: #000000;
}
#swagger-ui {
background: #ffffff;
color: #000000;
}
/* Override Swagger UI colors for white background */
.swagger-ui {
background-color: #ffffff !important;
color: #000000 !important;
}
.swagger-ui .topbar,
.swagger-ui .info,
.swagger-ui .opblock-summary-description,
.swagger-ui .parameters-col_description,
.swagger-ui .response-col_description {
color: #000000 !important;
}
.swagger-ui .opblock {
background-color: #f9f9f9 !important;
border-color: #ddd !important;
}
.swagger-ui .opblock-summary {
background-color: #eaeaea !important;
color: #000 !important;
}
.swagger-ui .opblock-section-header {
color: #000 !important;
}
.swagger-ui .parameters,
.swagger-ui .response {
background-color: #fafafa !important;
color: #000 !important;
}
.swagger-ui table {
color: #000 !important;
}
.swagger-ui a {
color: #1a0dab !important; /* classic link blue */
}
.swagger-ui input,
.swagger-ui select,
.swagger-ui textarea {
background-color: #fff !important;
color: #000 !important;
border: 1px solid #ccc !important;
}
</style>
</head>
<body>
<div id="swagger-ui"></div>
<script src="https://unpkg.com/swagger-ui-dist/swagger-ui-bundle.js"></script>
<script>
const spec = {
openapi: "3.0.0",
info: {
title: "Packets API",
version: "1.0.0",
description: "API for retrieving packet records with optional filters."
},
paths: {
"/api/packets": {
get: {
summary: "Get packets",
description: "Returns a list of recent packets, optionally filtered by a timestamp and limited by count.",
parameters: [
{
name: "limit",
in: "query",
required: false,
description: "Maximum number of packets to return. Default is 200.",
schema: {
type: "integer",
default: 200
}
},
{
name: "since",
in: "query",
required: false,
description: "Only return packets imported after this ISO8601 timestamp (e.g., `2025-08-12T14:15:20`).",
schema: {
type: "string",
format: "date-time"
}
}
],
responses: {
"200": {
description: "Successful response",
content: {
"application/json": {
schema: {
type: "object",
properties: {
packets: {
type: "array",
items: {
type: "object",
properties: {
id: { type: "integer", example: 196988973 },
from_node_id: { type: "integer", example: 2381019191 },
to_node_id: { type: "integer", example: 1234567890 },
portnum: { type: "integer", example: 1 },
import_time: { type: "string", format: "date-time", example: "2025-08-12T14:15:20.503827" },
payload: { type: "string", example: "Hello Mesh" }
}
}
}
}
}
}
}
},
"500": {
description: "Internal server error",
content: {
"application/json": {
schema: {
type: "object",
properties: {
error: { type: "string", example: "Failed to fetch packets" }
}
}
}
}
}
}
}
}
}
};
window.onload = () => {
SwaggerUIBundle({
spec,
dom_id: '#swagger-ui',
presets: [
SwaggerUIBundle.presets.apis,
SwaggerUIBundle.SwaggerUIStandalonePreset
],
layout: "BaseLayout"
});
};
</script>
</body>
</html>
+210
View File
@@ -0,0 +1,210 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>API Documentation - Packet Stats</title>
<link rel="stylesheet" href="https://unpkg.com/swagger-ui-dist/swagger-ui.css" />
<style>
body {
margin: 0;
background: #ffffff;
color: #000000;
}
#swagger-ui {
background: #ffffff;
color: #000000;
}
/* Override Swagger UI colors for white background */
.swagger-ui {
background-color: #ffffff !important;
color: #000000 !important;
}
.swagger-ui .topbar,
.swagger-ui .info,
.swagger-ui .opblock-summary-description,
.swagger-ui .parameters-col_description,
.swagger-ui .response-col_description {
color: #000000 !important;
}
.swagger-ui .opblock {
background-color: #f9f9f9 !important;
border-color: #ddd !important;
}
.swagger-ui .opblock-summary {
background-color: #eaeaea !important;
color: #000 !important;
}
.swagger-ui .opblock-section-header {
color: #000 !important;
}
.swagger-ui .parameters,
.swagger-ui .response {
background-color: #fafafa !important;
color: #000 !important;
}
.swagger-ui table {
color: #000 !important;
}
.swagger-ui a {
color: #1a0dab !important; /* classic link blue */
}
.swagger-ui input,
.swagger-ui select,
.swagger-ui textarea {
background-color: #fff !important;
color: #000 !important;
border: 1px solid #ccc !important;
}
</style>
</head>
<body>
<div id="swagger-ui"></div>
<script src="https://unpkg.com/swagger-ui-dist/swagger-ui-bundle.js"></script>
<script>
const spec = {
openapi: "3.0.0",
info: {
title: "Packet Statistics API",
version: "1.0.0",
description: "API for retrieving packet statistics over a given period with optional filters."
},
paths: {
"/api/stats": {
get: {
summary: "Get packet statistics",
description: "Returns packet statistics for a given period type and length, with optional filters.",
parameters: [
{
name: "period_type",
in: "query",
required: false,
description: "Type of period to group by (`hour` or `day`). Default is `hour`.",
schema: {
type: "string",
enum: ["hour", "day"]
}
},
{
name: "length",
in: "query",
required: false,
description: "Number of periods to include. Default is 24.",
schema: {
type: "integer",
default: 24
}
},
{
name: "channel",
in: "query",
required: false,
description: "Filter by channel name.",
schema: {
type: "string"
}
},
{
name: "portnum",
in: "query",
required: false,
description: "Filter by port number.",
schema: {
type: "integer"
}
},
{
name: "to_node",
in: "query",
required: false,
description: "Filter by destination node ID.",
schema: {
type: "integer"
}
},
{
name: "from_node",
in: "query",
required: false,
description: "Filter by source node ID.",
schema: {
type: "integer"
}
}
],
responses: {
"200": {
description: "Successful response",
content: {
"application/json": {
schema: {
type: "object",
properties: {
hourly: {
type: "object",
properties: {
period_type: { type: "string" },
length: { type: "integer" },
filters: { type: "object" },
data: {
type: "array",
items: {
type: "object",
properties: {
period: { type: "string", example: "2025-08-06 19:00" },
node_id: { type: "integer" },
long_name: { type: "string" },
short_name: { type: "string" },
packets: { type: "integer" }
}
}
}
}
}
}
}
}
}
},
"400": {
description: "Invalid request parameters",
content: {
"application/json": {
schema: {
type: "object",
properties: {
error: { type: "string" }
}
}
}
}
}
}
}
}
}
};
window.onload = () => {
SwaggerUIBundle({
spec,
dom_id: '#swagger-ui',
presets: [
SwaggerUIBundle.presets.apis,
SwaggerUIBundle.SwaggerUIStandalonePreset
],
layout: "BaseLayout"
});
};
</script>
</body>
</html>
+72 -254
View File
@@ -1,273 +1,91 @@
<!DOCTYPE html> <!DOCTYPE html>
<html> <html lang="en">
<head> <head>
<meta charset="utf-8"> <meta charset="UTF-8">
<title>Meshview API Documentation</title> <meta name="viewport" content="width=device-width, initial-scale=1">
<style> <title>API Index</title>
<style>
body { body {
background: #121212; font-family: Arial, sans-serif;
color: #eee; background-color: #1e1e1e;
font-family: monospace; color: #eaeaea;
margin: 20px; margin: 0;
line-height: 1.5; padding: 0;
} }
h1, h2, h3 { color: #79c0ff; } header {
code { background: #2a2a2a;
background: #1e1e1e; padding: 20px;
padding: 3px 6px; text-align: center;
border-radius: 4px; font-size: 1.6em;
color: #ffd479; font-weight: bold;
font-size: 0.95rem;
} }
.endpoint { .container {
border: 1px solid #333; max-width: 800px;
padding: 12px; margin: 30px auto;
margin-bottom: 18px; padding: 20px;
border-radius: 8px;
background: #1a1a1a;
} }
.method { ul {
display: inline-block; list-style: none;
padding: 2px 8px; padding: 0;
border-radius: 4px;
margin-right: 6px;
font-weight: bold;
} }
.get { background: #0066cc; } li {
.path { font-weight: bold; color: #fff; } background: #272b2f;
table { border: 1px solid #474b4e;
width: 100%; padding: 15px 20px;
border-collapse: collapse; margin-bottom: 15px;
margin-top: 8px; border-radius: 10px;
transition: background 0.2s;
} }
th, td { li:hover {
border: 1px solid #444; background: #33383d;
padding: 6px 10px;
} }
th { a {
background: #222; color: #4cafef;
color: #9ddcff; text-decoration: none;
font-weight: bold;
font-size: 1.1em;
} }
.example { p {
margin-top: 10px; margin: 8px 0 0 0;
padding: 10px; font-size: 0.9em;
background: #161616; color: #bbbbbb;
border-radius: 6px;
border: 1px solid #333;
} }
</style> </style>
</head> </head>
<body> <body>
<h1>Meshview API Documentation</h1> <header>
<p>This page describes all REST endpoints provided by Meshview.</p> API Index
</header>
<div class="container">
<!------------------------------ NODES ------------------------------> <ul>
<h2>/api/nodes</h2> <li>
<a href="/api-chat">Chat API</a>
<div class="endpoint"> <p> View chat messages.</p>
<span class="method get">GET</span> </li>
<span class="path">/api/nodes</span> <li>
<p>Returns a list of mesh nodes.</p> <a href="/api-nodes">Node API</a>
<p>Retrieve node information.</p>
<h3>Query Parameters</h3> </li>
<table> <li>
<tr><th>Parameter</th><th>Description</th></tr> <a href="/api-packets">Packet API</a>
<tr><td>role</td><td>Filter by node role</td></tr> <p>Access raw packet data.</p>
<tr><td>channel</td><td>Filter by channel</td></tr> </li>
<tr><td>hw_model</td><td>Hardware model filter</td></tr> <li>
<tr><td>days_active</td><td>Only nodes seen within X days</td></tr> <a href="/api-stats">Statistics API </a>
</table> <p>View system and traffic statistics.</p>
</li>
<div class="example"> <li>
<b>Example:</b><br> <a href="/api-edges">Edges API</a>
<code>/api/nodes?days_active=3</code> <p>Get edges details.</p>
</div> </li>
<li>
<a href="/api-config">Configuration API</a>
<p>Get and update configuration details.</p>
</li>
</ul>
</div> </div>
<!------------------------------ PACKETS ------------------------------>
<h2>/api/packets</h2>
<div class="endpoint">
<span class="method get">GET</span>
<span class="path">/api/packets</span>
<p>Fetch packets with many filters. Returns decoded packet data.</p>
<h3>Query Parameters</h3>
<table>
<tr><th>Parameter</th><th>Description</th></tr>
<tr><td>packet_id</td><td>Return exactly one packet</td></tr>
<tr><td>limit</td><td>Max number of results (1100)</td></tr>
<tr><td>since</td><td>Only packets newer than import_time_us</td></tr>
<tr><td>from_node_id</td><td>Filter by sender node</td></tr>
<tr><td>to_node_id</td><td>Filter by destination node</td></tr>
<tr><td>node_id</td><td>Legacy: match either from or to</td></tr>
<tr><td>portnum</td><td>Filter by port number</td></tr>
<tr><td>contains</td><td>Substring filter for payload</td></tr>
</table>
<div class="example">
<b>Example:</b><br>
<code>/api/packets?from_node_id=123&limit=100</code>
</div>
</div>
<!------------------------------ PACKETS SEEN ------------------------------>
<h2>/api/packets_seen/{packet_id}</h2>
<div class="endpoint">
<span class="method get">GET</span>
<span class="path">/api/packets_seen/&lt;packet_id&gt;</span>
<p>Returns list of gateways that heard the packet (RSSI/SNR/hops).</p>
<div class="example">
<b>Example:</b><br>
<code>/api/packets_seen/3314808102</code>
</div>
</div>
<!------------------------------ STATS ------------------------------>
<h2>/api/stats</h2>
<div class="endpoint">
<span class="method get">GET</span>
<span class="path">/api/stats</span>
<p>Returns aggregated packet statistics for a node or globally.</p>
<h3>Query Parameters</h3>
<table>
<tr><th>Parameter</th><th>Description</th></tr>
<tr><td>period_type</td><td>"hour" or "day"</td></tr>
<tr><td>length</td><td>How many hours/days</td></tr>
<tr><td>node</td><td>Node ID for combined sent+seen stats</td></tr>
<tr><td>from_node</td><td>Filter by sender</td></tr>
<tr><td>to_node</td><td>Filter by receiver</td></tr>
<tr><td>portnum</td><td>Filter by port</td></tr>
<tr><td>channel</td><td>Filter by channel</td></tr>
</table>
<div class="example">
<b>Example:</b><br>
<code>/api/stats?node=1128180332&period_type=day&length=1</code>
</div>
</div>
<!------------------------------ STATS COUNT ------------------------------>
<h2>/api/stats/count</h2>
<div class="endpoint">
<span class="method get">GET</span>
<span class="path">/api/stats/count</span>
<p>
Returns <b>total packets</b> and <b>total packet_seen entries</b>.
When no filters are provided, it returns global totals.
When filters are specified, they narrow the time, channel,
direction, or specific packet.
</p>
<h3>Query Parameters</h3>
<table>
<tr><th>Parameter</th><th>Description</th></tr>
<tr><td>period_type</td><td>"hour" or "day"</td></tr>
<tr><td>length</td><td>Number of hours or days (depends on period_type)</td></tr>
<tr><td>channel</td><td>Filter by channel</td></tr>
<tr><td>from_node</td><td>Only packets sent by this node</td></tr>
<tr><td>to_node</td><td>Only packets received by this node</td></tr>
<tr><td>packet_id</td><td>Filter seen counts for specific packet_id</td></tr>
</table>
<div class="example">
<b>Example:</b><br>
<code>/api/stats/count?from_node=1128180332&period_type=day&length=1</code>
</div>
</div>
<!------------------------------ EDGES ------------------------------>
<h2>/api/edges</h2>
<div class="endpoint">
<span class="method get">GET</span>
<span class="path">/api/edges</span>
<p>Returns traceroute and/or neighbor edges for graph rendering.</p>
<h3>Query Parameters</h3>
<table>
<tr><th>Parameter</th><th>Description</th></tr>
<tr><td>type</td><td>"traceroute", "neighbor", or omitted for both</td></tr>
</table>
<div class="example">
<b>Example:</b><br>
<code>/api/edges?type=neighbor</code>
</div>
</div>
<!------------------------------ CONFIG ------------------------------>
<h2>/api/config</h2>
<div class="endpoint">
<span class="method get">GET</span>
<span class="path">/api/config</span>
<p>Returns Meshview configuration (site, MQTT, cleanup, etc.).</p>
<div class="example">
<code>/api/config</code>
</div>
</div>
<!------------------------------ LANG ------------------------------>
<h2>/api/lang</h2>
<div class="endpoint">
<span class="method get">GET</span>
<span class="path">/api/lang</span>
<p>Returns translated text for the UI.</p>
<h3>Parameters</h3>
<table>
<tr><th>lang</th><td>Language code (e.g. "en")</td></tr>
<tr><th>section</th><td>Optional UI section (firehose, map, top...)</td></tr>
</table>
<div class="example">
<code>/api/lang?lang=en&section=firehose</code>
</div>
</div>
<!------------------------------ HEALTH ------------------------------>
<h2>/health</h2>
<div class="endpoint">
<span class="method get">GET</span>
<span class="path">/health</span>
<p>Returns API + database status.</p>
</div>
<!------------------------------ VERSION ------------------------------>
<h2>/version</h2>
<div class="endpoint">
<span class="method get">GET</span>
<span class="path">/version</span>
<p>Returns Meshview version and Git revision.</p>
</div>
<br><br>
<hr>
<p style="text-align:center; color:#666;">Meshview API — generated documentation</p>
</body> </body>
</html> </html>
+16 -35
View File
@@ -190,51 +190,32 @@ body { margin: 0; font-family: monospace; background: #121212; color: #eee; }
activeBlinks.set(marker,interval); activeBlinks.set(marker,interval);
} }
let lastImportTimeUs = null; let lastImportTime=null;
async function fetchLatestPacket(){ async function fetchLatestPacket(){
try{ try{
const res = await fetch(`/api/packets?limit=1`); const res=await fetch(`/api/packets?limit=1`);
const data = await res.json(); const data=await res.json();
lastImportTimeUs = data.packets?.[0]?.import_time_us || 0; lastImportTime=data.packets?.[0]?.import_time || new Date().toISOString();
}catch(err){ }catch(err){ console.error(err); }
console.error(err);
}
} }
async function fetchNewPackets(){ async function fetchNewPackets(){
if (!lastImportTimeUs) return; if(!lastImportTime) return;
try{ try{
const res = await fetch(`/api/packets?since=${lastImportTimeUs}`); const res=await fetch(`/api/packets?since=${lastImportTime}`);
const data = await res.json(); const data=await res.json();
if(!data.packets || !data.packets.length) return; if(!data.packets || !data.packets.length) return;
let latest=lastImportTime;
let latest = lastImportTimeUs; data.packets.forEach(packet=>{
if(packet.import_time && packet.import_time>latest) latest=packet.import_time;
data.packets.forEach(packet => { const marker=markerById[packet.from_node_id];
const nodeData=nodeMap.get(packet.from_node_id);
// Track newest microsecond timestamp if(marker && nodeData) blinkNode(marker,nodeData.long_name,packet.portnum);
if (packet.import_time_us && packet.import_time_us > latest) {
latest = packet.import_time_us;
}
// Look up marker and blink it
const marker = markerById[packet.from_node_id];
const nodeData = nodeMap.get(packet.from_node_id);
if (marker && nodeData) {
blinkNode(marker, nodeData.long_name, packet.portnum);
}
}); });
lastImportTime=latest;
lastImportTimeUs = latest; }catch(err){ console.error(err); }
}catch(err){
console.error(err);
}
} }
if(mapInterval>0){ fetchLatestPacket(); setInterval(fetchNewPackets,mapInterval*1000); } if(mapInterval>0){ fetchLatestPacket(); setInterval(fetchNewPackets,mapInterval*1000); }
})(); })();
+209
View File
@@ -0,0 +1,209 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>Mesh Nodes Live Map</title>
<link rel="stylesheet" href="https://unpkg.com/leaflet@1.9.4/dist/leaflet.css" />
<style>
body { margin: 0; }
#map { height: 100vh; width: 100%; }
#legend {
position: absolute; bottom: 10px; right: 10px;
background: rgba(0,0,0,0.7);
color: white; padding: 8px 12px;
font-family: monospace; font-size: 13px;
border-radius: 5px; z-index: 1000;
}
.legend-item { display: flex; align-items: center; margin-bottom: 4px; }
.legend-color { width: 16px; height: 16px; margin-right: 6px; border-radius: 4px; }
.pulse-label span {
background: rgba(0,0,0,0.6);
padding: 2px 4px;
border-radius: 3px;
pointer-events: none;
font-family: monospace;
font-size: 12px;
}
</style>
</head>
<body>
<div id="map"></div>
<div id="legend"></div>
<script src="https://unpkg.com/leaflet@1.9.4/dist/leaflet.js"></script>
<script>
const map = L.map("map");
L.tileLayer("https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png", { maxZoom: 19, attribution: "© OpenStreetMap" }).addTo(map);
const nodeMarkers = new Map();
let lastPacketTime = null;
const portColors = {
1:"red",
67:"cyan",
3:"orange",
70:"purple",
4:"yellow",
71:"brown",
73:"pink"
};
const portLabels = {
1:"Text",
67:"Telemetry",
3:"Position",
70:"Traceroute",
4:"Node Info",
71:"Neighbour Info",
73:"Map Report"
};
function getPulseColor(portnum) { return portColors[portnum] || "green"; }
// Legend
const legend = document.getElementById("legend");
for (const [port, color] of Object.entries(portColors)) {
const item = document.createElement("div");
item.className = "legend-item";
const colorBox = document.createElement("div");
colorBox.className = "legend-color";
colorBox.style.background = color;
const label = document.createElement("span");
label.textContent = `${portLabels[port] || "Custom"} (${port})`;
item.appendChild(colorBox);
item.appendChild(label);
legend.appendChild(item);
}
// Pulse marker
function pulseMarker(marker, highlightColor = "red") {
if (!marker || marker.activePulse) return;
marker.activePulse = true;
const originalColor = marker.options.originalColor;
const originalRadius = marker.options.originalRadius;
marker.bringToFront();
const nodeInfo = marker.options.nodeInfo || {};
const portLabel = marker.currentPortLabel || "";
const displayName = `${nodeInfo.long_name || nodeInfo.short_name || "Unknown"}${portLabel ? ` (<i>${portLabel}</i>)` : ""}`;
marker.bindTooltip(displayName, {
permanent: true,
direction: 'top',
className: 'pulse-label',
offset: [0, -10],
html: true
}).openTooltip();
const flashDuration = 2000, fadeDuration = 1000, flashInterval = 100, maxRadius = originalRadius + 5;
let flashTime = 0;
const flashTimer = setInterval(() => {
flashTime += flashInterval;
const isOn = (flashTime / flashInterval) % 2 === 0;
marker.setStyle({ fillColor: isOn ? highlightColor : originalColor, radius: isOn ? maxRadius : originalRadius });
if (flashTime >= flashDuration) {
clearInterval(flashTimer);
const fadeStart = performance.now();
function fade(now) {
const t = Math.min((now - fadeStart) / fadeDuration, 1);
const radius = originalRadius + (maxRadius - originalRadius) * (1 - t);
marker.setStyle({ fillColor: highlightColor, radius: radius, fillOpacity: 1 });
if (t < 1) requestAnimationFrame(fade);
else {
marker.setStyle({ fillColor: originalColor, radius: originalRadius, fillOpacity: 1 });
marker.unbindTooltip();
marker.activePulse = false;
}
}
requestAnimationFrame(fade);
}
}, flashInterval);
}
// --- Load nodes ---
async function loadNodes() {
try {
const res = await fetch("/api/nodes");
if (!res.ok) throw new Error(`HTTP error ${res.status}`);
const data = await res.json();
const nodes = data.nodes || [];
nodes.forEach(node => {
const lat = node.last_lat / 1e7;
const lng = node.last_long / 1e7;
if (lat && lng && !isNaN(lat) && !isNaN(lng)) {
const color = "blue";
const marker = L.circleMarker([lat,lng], {
radius:7, color:"white", fillColor:color, fillOpacity:1, weight:0.7
}).addTo(map);
marker.options.originalColor = color;
marker.options.originalRadius = 7;
marker.options.nodeInfo = node;
marker.bindPopup(`<b>${node.long_name||node.short_name||"Unknown"}</b><br>ID: ${node.node_id}<br>Role: ${node.role}`);
nodeMarkers.set(node.node_id, marker);
} else {
nodeMarkers.set(node.node_id, {options:{nodeInfo:node}});
}
});
const markersWithCoords = Array.from(nodeMarkers.values()).filter(m=>m instanceof L.CircleMarker);
if(markersWithCoords.length>0) await setMapBoundsFromConfig();
else map.setView([37.77,-122.42],9);
} catch(err){
console.error("Failed to load nodes:", err);
}
}
// --- Map bounds ---
async function setMapBoundsFromConfig() {
try {
const res = await fetch("/api/config");
const config = await res.json();
const topLat = parseFloat(config.site.map_top_left_lat);
const topLon = parseFloat(config.site.map_top_left_lon);
const bottomLat = parseFloat(config.site.map_bottom_right_lat);
const bottomLon = parseFloat(config.site.map_bottom_right_lon);
if ([topLat, topLon, bottomLat, bottomLon].some(v => isNaN(v))) {
throw new Error("Map bounds contain NaN");
}
map.fitBounds([[topLat, topLon], [bottomLat, bottomLon]]);
} catch(err) {
console.error("Failed to load map bounds from config:", err);
map.setView([37.77,-122.42],9);
}
}
// --- Poll packets ---
async function pollPackets() {
try {
let url = "/api/packets?limit=10";
if (lastPacketTime) url += `&since=${lastPacketTime}`;
const res = await fetch(url);
if (!res.ok) throw new Error(`HTTP error ${res.status}`);
const data = await res.json();
const packets = data.packets || [];
if (packets.length) lastPacketTime = packets[0].import_time;
packets.forEach(pkt => {
const marker = nodeMarkers.get(pkt.from_node_id);
if (marker instanceof L.CircleMarker) { // only real markers
marker.currentPortLabel = portLabels[pkt.portnum] || `${pkt.portnum}`;
pulseMarker(marker, getPulseColor(pkt.portnum));
}
});
} catch(err){
console.error("Failed to fetch packets:", err);
}
}
// --- Initialize ---
loadNodes().then(() => setInterval(pollPackets, 1000));
</script>
</body>
</html>
+21 -21
View File
@@ -1,5 +1,6 @@
from datetime import datetime, timedelta from datetime import datetime, timedelta
from sqlalchemy import select, and_, or_, func, cast, Text
from sqlalchemy import and_, func, or_, select, text
from sqlalchemy.orm import lazyload from sqlalchemy.orm import lazyload
from meshview import database, models from meshview import database, models
@@ -26,12 +27,18 @@ async def get_fuzzy_nodes(query):
async def get_packets( async def get_packets(
from_node_id=None, from_node_id=None,
to_node_id=None, to_node_id=None,
node_id=None, # legacy node_id=None, # legacy: match either from OR to
portnum=None, portnum=None,
after=None, after=None,
contains=None, # substring search contains=None, # NEW: SQL-level substring match
limit=50, limit=50,
): ):
"""
SQLAlchemy 2.0 async ORM version.
Supports strict from/to/node filtering, substring payload search,
portnum, since, and limit.
"""
async with database.async_session() as session: async with database.async_session() as session:
stmt = select(models.Packet) stmt = select(models.Packet)
conditions = [] conditions = []
@@ -44,40 +51,36 @@ async def get_packets(
if to_node_id is not None: if to_node_id is not None:
conditions.append(models.Packet.to_node_id == to_node_id) conditions.append(models.Packet.to_node_id == to_node_id)
# Legacy node_id (either direction) # Legacy node ID filter: match either direction
if node_id is not None: if node_id is not None:
conditions.append( conditions.append(
or_( or_(models.Packet.from_node_id == node_id, models.Packet.to_node_id == node_id)
models.Packet.from_node_id == node_id,
models.Packet.to_node_id == node_id,
)
) )
# Port filter # Port filter
if portnum is not None: if portnum is not None:
conditions.append(models.Packet.portnum == portnum) conditions.append(models.Packet.portnum == portnum)
# Timestamp filter using microseconds # Timestamp filter
if after is not None: if after is not None:
conditions.append(models.Packet.import_time_us > after) conditions.append(models.Packet.import_time_us > after)
# Case-insensitive substring search on payload (BLOB → TEXT) # Case-insensitive substring search on UTF-8 payload (stored as BLOB)
if contains: if contains:
contains_lower = f"%{contains.lower()}%" contains_lower = contains.lower()
payload_text = cast(models.Packet.payload, Text) conditions.append(func.lower(models.Packet.payload).like(f"%{contains_lower}%"))
conditions.append(func.lower(payload_text).like(contains_lower))
# Apply WHERE conditions # Apply all conditions
if conditions: if conditions:
stmt = stmt.where(and_(*conditions)) stmt = stmt.where(and_(*conditions))
# Order by newest first # Order newest → oldest
stmt = stmt.order_by(models.Packet.import_time_us.desc()) stmt = stmt.order_by(models.Packet.import_time_us.desc())
# Limit # Apply limit
stmt = stmt.limit(limit) stmt = stmt.limit(limit)
# Run query # Execute query
result = await session.execute(stmt) result = await session.execute(stmt)
return result.scalars().all() return result.scalars().all()
@@ -260,12 +263,11 @@ async def get_node_traffic(node_id: int):
return [] return []
async def get_nodes(node_id=None, role=None, channel=None, hw_model=None, days_active=None): async def get_nodes(role=None, channel=None, hw_model=None, days_active=None):
""" """
Fetches nodes from the database based on optional filtering criteria. Fetches nodes from the database based on optional filtering criteria.
Parameters: Parameters:
node_id
role (str, optional): The role of the node (converted to uppercase for consistency). role (str, optional): The role of the node (converted to uppercase for consistency).
channel (str, optional): The communication channel associated with the node. channel (str, optional): The communication channel associated with the node.
hw_model (str, optional): The hardware model of the node. hw_model (str, optional): The hardware model of the node.
@@ -281,8 +283,6 @@ async def get_nodes(node_id=None, role=None, channel=None, hw_model=None, days_a
query = select(Node) query = select(Node)
# Apply filters based on provided parameters # Apply filters based on provided parameters
if node_id is not None:
query = query.where(Node.node_id == node_id)
if role is not None: if role is not None:
query = query.where(Node.role == role.upper()) # Ensure role is uppercase query = query.where(Node.role == role.upper()) # Ensure role is uppercase
if channel is not None: if channel is not None:
+29 -61
View File
@@ -53,9 +53,8 @@
<!-- ⭐ CHAT TITLE WITH ICON, aligned to container ⭐ --> <!-- ⭐ CHAT TITLE WITH ICON, aligned to container ⭐ -->
<div class="container px-2"> <div class="container px-2">
<h2 style="color:white; margin:0 0 10px 0;"> <h2 data-translate="chat_title" style="color:white; margin:0 0 10px 0;">
<span class="icon">💬</span> 💬 Chat
<span data-translate="chat_title"></span>
</h2> </h2>
</div> </div>
@@ -72,45 +71,24 @@ document.addEventListener("DOMContentLoaded", async () => {
const packetMap = new Map(); const packetMap = new Map();
let chatLang = {}; let chatLang = {};
/* ========================================================== function applyTranslations(dict, root = document) {
TRANSLATIONS FOR CHAT PAGE
========================================================== */
function applyTranslations(dict, root=document) {
root.querySelectorAll("[data-translate]").forEach(el => { root.querySelectorAll("[data-translate]").forEach(el => {
const key = el.dataset.translate; const key = el.dataset.translate;
const val = dict[key]; const val = dict[key];
if (!val) return; if (!val) return;
if (el.placeholder) el.placeholder = val; if (el.placeholder) el.placeholder = val;
else if (el.tagName === "INPUT" && el.value) el.value = val;
else if (key === "footer") el.innerHTML = val;
else el.textContent = val; else el.textContent = val;
}); });
} }
async function loadChatLang() {
try {
const cfg = await window._siteConfigPromise;
const langCode = cfg?.site?.language || "en";
const res = await fetch(`/api/lang?lang=${langCode}&section=chat`);
chatLang = await res.json();
// Apply to existing DOM
applyTranslations(chatLang);
} catch (err) {
console.error("Chat translation load failed:", err);
}
}
/* ==========================================================
SAFE HTML
========================================================== */
function escapeHtml(text) { function escapeHtml(text) {
const div = document.createElement("div"); const div = document.createElement("div");
div.textContent = text ?? ""; div.textContent = text ?? "";
return div.innerHTML; return div.innerHTML;
} }
/* ==========================================================
RENDERING PACKETS
========================================================== */
function renderPacket(packet, highlight = false) { function renderPacket(packet, highlight = false) {
if (renderedPacketIds.has(packet.id)) return; if (renderedPacketIds.has(packet.id)) return;
renderedPacketIds.add(packet.id); renderedPacketIds.add(packet.id);
@@ -161,31 +139,20 @@ document.addEventListener("DOMContentLoaded", async () => {
const div = document.createElement("div"); const div = document.createElement("div");
div.className = "row chat-packet" + (highlight ? " flash" : ""); div.className = "row chat-packet" + (highlight ? " flash" : "");
div.dataset.packetId = packet.id; div.dataset.packetId = packet.id;
div.innerHTML = ` div.innerHTML = `
<span class="col-2 timestamp" title="${packet.import_time_us}"> <span class="col-2 timestamp" title="${packet.import_time_us}">${formattedTimestamp}</span>
${formattedTimestamp}
</span>
<span class="col-2 channel"> <span class="col-2 channel">
<a href="/packet/${packet.id}" title="${chatLang.view_packet_details || 'View details'}">🔎</a> <a href="/packet/${packet.id}" title="${chatLang.view_packet_details || 'View details'}">🔎</a>
${escapeHtml(packet.channel || "")} ${escapeHtml(packet.channel || "")}
</span> </span>
<span class="col-3 nodename"> <span class="col-3 nodename">
<a href="/node/${packet.from_node_id}"> <a href="/node/${packet.from_node_id}">
${escapeHtml((packet.long_name || "").trim() || `Node ${packet.from_node_id}`)} ${escapeHtml((packet.long_name || "").trim() || `Node ${packet.from_node_id}`)}
</a> </a>
</span> </span>
<span class="col-5 message">${escapeHtml(packet.payload)}${replyHtml}</span>
<span class="col-5 message">
${escapeHtml(packet.payload)}${replyHtml}
</span>
`; `;
chatContainer.prepend(div); chatContainer.prepend(div);
// Translate newly added DOM
applyTranslations(chatLang, div); applyTranslations(chatLang, div);
if (highlight) setTimeout(() => div.classList.remove("flash"), 2500); if (highlight) setTimeout(() => div.classList.remove("flash"), 2500);
@@ -194,27 +161,26 @@ document.addEventListener("DOMContentLoaded", async () => {
function renderPacketsEnsureDescending(packets, highlight=false) { function renderPacketsEnsureDescending(packets, highlight=false) {
if (!Array.isArray(packets) || packets.length===0) return; if (!Array.isArray(packets) || packets.length===0) return;
const sortedDesc = packets.slice().sort((a,b)=>{ const sortedDesc = packets.slice().sort((a,b)=>{
const aTime = a.import_time_us || (new Date(a.import_time).getTime() * 1000); const aTime =
const bTime = b.import_time_us || (new Date(b.import_time).getTime() * 1000); (a.import_time_us && a.import_time_us > 0)
? a.import_time_us
: (a.import_time ? new Date(a.import_time).getTime() * 1000 : 0);
const bTime =
(b.import_time_us && b.import_time_us > 0)
? b.import_time_us
: (b.import_time ? new Date(b.import_time).getTime() * 1000 : 0);
return bTime - aTime; return bTime - aTime;
}); });
for (let i=sortedDesc.length-1; i>=0; i--) { for (let i=sortedDesc.length-1; i>=0; i--) renderPacket(sortedDesc[i], highlight);
renderPacket(sortedDesc[i], highlight);
}
} }
/* ==========================================================
FETCHING PACKETS
========================================================== */
async function fetchInitial() { async function fetchInitial() {
try { try {
const resp = await fetch("/api/packets?portnum=1&limit=100"); const resp = await fetch("/api/packets?portnum=1&limit=100");
const data = await resp.json(); const data = await resp.json();
if (data?.packets?.length) renderPacketsEnsureDescending(data.packets); if (data?.packets?.length) renderPacketsEnsureDescending(data.packets);
lastTime = data?.latest_import_time || lastTime; lastTime = data?.latest_import_time || lastTime;
} catch(err){ } catch(err){ console.error("Initial fetch error:", err); }
console.error("Initial fetch error:", err);
}
} }
async function fetchUpdates() { async function fetchUpdates() {
@@ -226,19 +192,21 @@ document.addEventListener("DOMContentLoaded", async () => {
const data = await resp.json(); const data = await resp.json();
if (data?.packets?.length) renderPacketsEnsureDescending(data.packets, true); if (data?.packets?.length) renderPacketsEnsureDescending(data.packets, true);
lastTime = data?.latest_import_time || lastTime; lastTime = data?.latest_import_time || lastTime;
} catch(err){ } catch(err){ console.error("Fetch updates error:", err); }
console.error("Fetch updates error:", err);
}
} }
/* ========================================================== async function loadChatLang() {
INIT try {
========================================================== */ const cfg = await window._siteConfigPromise;
await loadChatLang(); // load translations FIRST const langCode = cfg?.site?.language || "en";
await fetchInitial(); // then fetch initial packets const res = await fetch(`/api/lang?lang=${langCode}&section=chat`);
chatLang = await res.json();
applyTranslations(chatLang);
} catch(err){ console.error("Chat translation load failed:", err); }
}
await Promise.all([loadChatLang(), fetchInitial()]);
setInterval(fetchUpdates, 5000); setInterval(fetchUpdates, 5000);
}); });
</script> </script>
{% endblock %} {% endblock %}
+97 -194
View File
@@ -12,16 +12,6 @@
border-radius: 6px; border-radius: 6px;
} }
.port-tag {
display: inline-block;
padding: 2px 6px;
border-radius: 6px;
font-size: 0.75rem;
font-weight: 500;
color: #fff;
}
/* Packet table */
.packet-table { .packet-table {
width: 100%; width: 100%;
border-collapse: collapse; border-collapse: collapse;
@@ -41,7 +31,6 @@
.packet-table tr:nth-of-type(odd) { background-color: #272b2f; } .packet-table tr:nth-of-type(odd) { background-color: #272b2f; }
.packet-table tr:nth-of-type(even) { background-color: #212529; } .packet-table tr:nth-of-type(even) { background-color: #212529; }
/* Port tag */
.port-tag { .port-tag {
display: inline-block; display: inline-block;
padding: 1px 6px; padding: 1px 6px;
@@ -50,22 +39,29 @@
font-weight: 500; font-weight: 500;
color: #fff; color: #fff;
} }
.port-0 { background-color: #6c757d; }
.port-1 { background-color: #007bff; }
.port-3 { background-color: #28a745; }
.port-4 { background-color: #ffc107; }
.port-5 { background-color: #dc3545; }
.port-6 { background-color: #20c997; }
.port-65 { background-color: #ff66b3; }
.port-67 { background-color: #17a2b8; }
.port-70 { background-color: #6f42c1; }
.port-71 { background-color: #fd7e14; }
.to-mqtt { font-style: italic; color: #aaa; } .to-mqtt { font-style: italic; color: #aaa; }
/* Payload rows */
.payload-row { display: none; background-color: #1b1e22; } .payload-row { display: none; background-color: #1b1e22; }
.payload-cell { .payload-cell {
padding: 8px 12px; padding: 8px 12px;
font-family: monospace; font-family: monospace;
white-space: pre-wrap; white-space: pre-wrap;
color: #b0bec5; color: #b0bec5;
border-top: none;
} }
.packet-table tr.expanded + .payload-row { .packet-table tr.expanded + .payload-row { display: table-row; }
display: table-row;
}
/* Toggle arrow */
.toggle-btn { .toggle-btn {
cursor: pointer; cursor: pointer;
color: #aaa; color: #aaa;
@@ -74,7 +70,7 @@
} }
.toggle-btn:hover { color: #fff; } .toggle-btn:hover { color: #fff; }
/* Inline link next to port tag */ /* Link next to port tag */
.inline-link { .inline-link {
margin-left: 6px; margin-left: 6px;
font-weight: bold; font-weight: bold;
@@ -88,16 +84,9 @@
{% block body %} {% block body %}
<div class="container"> <div class="container">
<form class="d-flex align-items-center justify-content-between mb-3"> <form class="d-flex align-items-center justify-content-between mb-3">
<h2 class="mb-0" data-translate-lang="live_feed">📡 Live Feed</h2> <h5 class="mb-0" data-translate-lang="live_feed">📡 Live Feed</h5>
<button type="button" id="pause-button" class="btn btn-sm btn-outline-secondary" data-translate-lang="pause">Pause</button>
<button type="button"
id="pause-button"
class="btn btn-sm btn-outline-secondary"
data-translate-lang="pause">
Pause
</button>
</form> </form>
<table class="packet-table"> <table class="packet-table">
@@ -112,71 +101,34 @@
</thead> </thead>
<tbody id="packet_list"></tbody> <tbody id="packet_list"></tbody>
</table> </table>
</div> </div>
<script> <script>
/* ====================================================== let lastImportTimeUs = null;
FIREHOSE TRANSLATION SYSTEM (isolated from base) let updatesPaused = false;
====================================================== */ let nodeMap = {};
let updateInterval = 3000;
let firehoseTranslations = {}; let firehoseTranslations = {};
function applyTranslationsFirehose(translations, root=document) { function applyTranslations(translations, root=document) {
root root.querySelectorAll("[data-translate-lang]").forEach(el => {
.querySelectorAll("[data-translate-lang]") const key = el.dataset.translateLang;
.forEach(el => { if (translations[key]) el.textContent = translations[key];
const key = el.dataset.translateLang; });
if (!translations[key]) return;
if (el.tagName === "INPUT" && el.placeholder !== undefined) {
el.placeholder = translations[key];
} else {
el.textContent = translations[key];
}
});
} }
async function loadTranslationsFirehose() { async function loadTranslations() {
try { try {
const cfg = await window._siteConfigPromise; const cfg = await window._siteConfigPromise;
const lang = cfg?.site?.language || "en"; const langCode = cfg?.site?.language || "en";
const res = await fetch(`/api/lang?lang=${langCode}&section=firehose`);
const res = await fetch(`/api/lang?lang=${lang}&section=firehose`);
firehoseTranslations = await res.json(); firehoseTranslations = await res.json();
applyTranslations(firehoseTranslations, document);
applyTranslationsFirehose(firehoseTranslations);
} catch (err) { } catch (err) {
console.error("Firehose translation load failed:", err); console.error("Firehose translation load failed:", err);
} }
} }
/* ======================================================
NODE LOOKUP
====================================================== */
let nodeMap = {};
async function loadNodes() {
try {
const res = await fetch("/api/nodes");
const data = await res.json();
for (const n of data.nodes || []) {
nodeMap[n.node_id] = n.long_name || n.short_name || n.id || n.node_id;
}
nodeMap[4294967295] = firehoseTranslations.all_broadcast || "All";
} catch (err) {
console.error("Failed loading nodes:", err);
}
}
function nodeName(id) {
return nodeMap[id] || id;
}
/* ======================================================
PORT COLORS & NAMES
====================================================== */
const PORT_MAP = { const PORT_MAP = {
0: "UNKNOWN APP", 0: "UNKNOWN APP",
1: "Text Message", 1: "Text Message",
@@ -188,38 +140,51 @@ const PORT_MAP = {
65: "Store Forward", 65: "Store Forward",
67: "Telemetry", 67: "Telemetry",
70: "Trace Route", 70: "Trace Route",
71: "Neighbor Info" 71: "Neighbor Info",
}; };
const PORT_COLORS = { const PORT_COLORS = {
0: "#6c757d", 0: "#6c757d",
1: "#007bff", 1: "#007bff",
3: "#28a745", 3: "#28a745",
4: "#ffc107", 4: "#ffc107",
5: "#dc3545", 5: "#dc3545",
6: "#20c997", 6: "#20c997",
65: "#6610f2", 65: "#6610f2",
67: "#17a2b8", 67: "#17a2b8",
68: "#fd7e14", 68: "#fd7e14",
69: "#6f42c1", 69: "#6f42c1",
70: "#ff4444", 70: "#ff4444",
71: "#ff66cc", 71: "#ff66cc",
72: "#00cc99", 72: "#00cc99",
73: "#9999ff", 73: "#9999ff",
74: "#cc00cc", 74: "#cc00cc",
75: "#ffbb33", 75: "#ffbb33",
76: "#00bcd4", 76: "#00bcd4",
77: "#8bc34a", 77: "#8bc34a",
78: "#795548" 78: "#795548"
}; };
// Load node names
async function loadNodes() {
const res = await fetch("/api/nodes");
if (!res.ok) return;
const data = await res.json();
for (const n of data.nodes || []) {
nodeMap[n.node_id] = n.long_name || n.short_name || n.id || n.node_id;
}
nodeMap[4294967295] = "All";
}
function nodeName(id) {
if (id === 4294967295) return `<span class="to-mqtt">All</span>`;
return nodeMap[id] || id;
}
function portLabel(portnum, payload, linksHtml) { function portLabel(portnum, payload, linksHtml) {
const name = PORT_MAP[portnum] || "Unknown"; const name = PORT_MAP[portnum] || "Unknown";
const color = PORT_COLORS[portnum] || "#6c757d"; const color = PORT_COLORS[portnum] || "#6c757d";
const safePayload = payload ? payload.replace(/"/g, "&quot;") : "";
const safePayload = payload
? payload.replace(/"/g, "&quot;")
: "";
return ` return `
<span class="port-tag" style="background-color:${color}" title="${safePayload}"> <span class="port-tag" style="background-color:${color}" title="${safePayload}">
@@ -230,46 +195,31 @@ function portLabel(portnum, payload, linksHtml) {
`; `;
} }
/* ======================================================
TIME FORMAT
====================================================== */
function formatLocalTime(importTimeUs) { function formatLocalTime(importTimeUs) {
const ms = importTimeUs / 1000; const ms = importTimeUs / 1000;
return new Date(ms).toLocaleTimeString([], { const date = new Date(ms);
hour: "2-digit", return date.toLocaleTimeString([], { hour: "2-digit", minute: "2-digit", second: "2-digit" });
minute: "2-digit",
second: "2-digit"
});
} }
/* ======================================================
FIREHOSE FETCHING
====================================================== */
let lastImportTimeUs = null;
let updatesPaused = false;
let updateInterval = 3000;
async function configureFirehose() { async function configureFirehose() {
try { try {
const cfg = await window._siteConfigPromise; const cfg = await window._siteConfigPromise;
const sec = cfg?.site?.firehose_interval; const intervalSec = cfg?.site?.firehose_interval;
if (sec && !isNaN(sec)) updateInterval = sec * 1000; if (intervalSec && !isNaN(intervalSec)) updateInterval = parseInt(intervalSec) * 1000;
} catch {} } catch (err) {
console.warn("Failed to read firehose interval:", err);
}
} }
async function fetchUpdates() { async function fetchUpdates() {
if (updatesPaused) return; if (updatesPaused) return;
const url = new URL("/api/packets", window.location.origin); const url = new URL("/api/packets", window.location.origin);
if (lastImportTimeUs) url.searchParams.set("since", lastImportTimeUs);
url.searchParams.set("limit", 50); url.searchParams.set("limit", 50);
if (lastImportTimeUs)
url.searchParams.set("since", lastImportTimeUs);
try { try {
const res = await fetch(url); const res = await fetch(url);
if (!res.ok) return; if (!res.ok) return;
const data = await res.json(); const data = await res.json();
const packets = data.packets || []; const packets = data.packets || [];
if (!packets.length) return; if (!packets.length) return;
@@ -277,34 +227,19 @@ async function fetchUpdates() {
const list = document.getElementById("packet_list"); const list = document.getElementById("packet_list");
for (const pkt of packets.reverse()) { for (const pkt of packets.reverse()) {
const from = pkt.from_node_id === 4294967295
? `<span class="to-mqtt">All</span>`
: `<a href="/node/${pkt.from_node_id}" style="text-decoration:underline; color:inherit;">${nodeMap[pkt.from_node_id] || pkt.from_node_id}</a>`;
/* FROM — includes translation */ const to = pkt.to_node_id === 1
const from = ? `<span class="to-mqtt">direct to MQTT</span>`
pkt.from_node_id === 4294967295 : pkt.to_node_id === 4294967295
? `<span class="to-mqtt" data-translate-lang="all_broadcast"> ? `<span class="to-mqtt">All</span>`
${firehoseTranslations.all_broadcast || "All"} : `<a href="/node/${pkt.to_node_id}" style="text-decoration:underline; color:inherit;">${nodeMap[pkt.to_node_id] || pkt.to_node_id}</a>`;
</span>`
: `<a href="/node/${pkt.from_node_id}" style="text-decoration:underline; color:inherit;">
${nodeMap[pkt.from_node_id] || pkt.from_node_id}
</a>`;
/* TO — includes translation */
const to =
pkt.to_node_id === 1
? `<span class="to-mqtt" data-translate-lang="direct_to_mqtt">
${firehoseTranslations.direct_to_mqtt || "direct to MQTT"}
</span>`
: pkt.to_node_id === 4294967295
? `<span class="to-mqtt" data-translate-lang="all_broadcast">
${firehoseTranslations.all_broadcast || "All"}
</span>`
: `<a href="/node/${pkt.to_node_id}" style="text-decoration:underline; color:inherit;">
${nodeMap[pkt.to_node_id] || pkt.to_node_id}
</a>`;
// Inline link next to port tag
let inlineLinks = ""; let inlineLinks = "";
// Position link
if (pkt.portnum === 3 && pkt.payload) { if (pkt.portnum === 3 && pkt.payload) {
const latMatch = pkt.payload.match(/latitude_i:\s*(-?\d+)/); const latMatch = pkt.payload.match(/latitude_i:\s*(-?\d+)/);
const lonMatch = pkt.payload.match(/longitude_i:\s*(-?\d+)/); const lonMatch = pkt.payload.match(/longitude_i:\s*(-?\d+)/);
@@ -312,57 +247,36 @@ async function fetchUpdates() {
if (latMatch && lonMatch) { if (latMatch && lonMatch) {
const lat = parseInt(latMatch[1]) / 1e7; const lat = parseInt(latMatch[1]) / 1e7;
const lon = parseInt(lonMatch[1]) / 1e7; const lon = parseInt(lonMatch[1]) / 1e7;
inlineLinks += ` <a class="inline-link" inlineLinks += ` <a class="inline-link" href="https://www.google.com/maps?q=${lat},${lon}" target="_blank">📍</a>`;
href="https://www.google.com/maps?q=${lat},${lon}"
target="_blank">📍</a>`;
} }
} }
// Traceroute link
if (pkt.portnum === 70) { if (pkt.portnum === 70) {
let traceId = pkt.id; let traceId = pkt.id;
const match = pkt.payload.match(/ID:\s*(\d+)/i); const match = pkt.payload.match(/ID:\s*(\d+)/i);
if (match) traceId = match[1]; if (match) traceId = match[1];
inlineLinks += ` <a class="inline-link" inlineLinks += ` <a class="inline-link" href="/graph/traceroute/${traceId}" target="_blank"></a>`;
href="/graph/traceroute/${traceId}"
target="_blank">⮕</a>`;
} }
const safePayload = (pkt.payload || "") const safePayload = (pkt.payload || "").replace(/</g, "&lt;").replace(/>/g, "&gt;");
.replace(/</g, "&lt;") const localTime = formatLocalTime(pkt.import_time_us);
.replace(/>/g, "&gt;");
const html = ` const html = `
<tr class="packet-row"> <tr class="packet-row" data-id="${pkt.id}">
<td>${localTime}</td>
<td>${formatLocalTime(pkt.import_time_us)}</td> <td><span class="toggle-btn"></span> <a href="/packet/${pkt.id}" style="text-decoration:underline; color:inherit;">${pkt.id}</a></td>
<td>
<span class="toggle-btn"></span>
<a href="/packet/${pkt.id}"
style="text-decoration:underline; color:inherit;">
${pkt.id}
</a>
</td>
<td>${from}</td> <td>${from}</td>
<td>${to}</td> <td>${to}</td>
<td>${portLabel(pkt.portnum, pkt.payload, inlineLinks)}</td> <td>${portLabel(pkt.portnum, pkt.payload, inlineLinks)}</td>
</tr> </tr>
<tr class="payload-row"> <tr class="payload-row">
<td colspan="5" class="payload-cell">${safePayload}</td> <td colspan="5" class="payload-cell">${safePayload}</td>
</tr> </tr>`;
`;
list.insertAdjacentHTML("afterbegin", html); list.insertAdjacentHTML("afterbegin", html);
} }
// Limit table size
while (list.rows.length > 400) list.deleteRow(-1); while (list.rows.length > 400) list.deleteRow(-1);
lastImportTimeUs = packets[packets.length - 1].import_time_us; lastImportTimeUs = packets[packets.length - 1].import_time_us;
} catch (err) { } catch (err) {
@@ -370,40 +284,29 @@ async function fetchUpdates() {
} }
} }
/* ====================================================== // --- Initialize ---
INITIALIZE PAGE
====================================================== */
document.addEventListener("DOMContentLoaded", async () => { document.addEventListener("DOMContentLoaded", async () => {
const pauseBtn = document.getElementById("pause-button"); const pauseBtn = document.getElementById("pause-button");
pauseBtn.addEventListener("click", () => { pauseBtn.addEventListener("click", () => {
updatesPaused = !updatesPaused; updatesPaused = !updatesPaused;
pauseBtn.textContent = updatesPaused
pauseBtn.textContent = ? (firehoseTranslations.resume || "Resume")
updatesPaused : (firehoseTranslations.pause || "Pause");
? (firehoseTranslations.resume || "Resume")
: (firehoseTranslations.pause || "Pause");
}); });
document.addEventListener("click", e => { document.addEventListener("click", (e) => {
const btn = e.target.closest(".toggle-btn"); const btn = e.target.closest(".toggle-btn");
if (!btn) return; if (!btn) return;
const row = btn.closest(".packet-row"); const row = btn.closest(".packet-row");
row.classList.toggle("expanded"); row.classList.toggle("expanded");
btn.textContent = row.classList.contains("expanded") ? "▼" : "▶";
btn.textContent =
row.classList.contains("expanded") ? "▼" : "▶";
}); });
await loadTranslationsFirehose(); await loadTranslations();
await configureFirehose(); await configureFirehose();
await loadNodes(); await loadNodes();
fetchUpdates(); fetchUpdates();
setInterval(fetchUpdates, updateInterval); setInterval(fetchUpdates, updateInterval);
}); });
</script> </script>
{% endblock %} {% endblock %}
+138 -362
View File
@@ -7,162 +7,59 @@
<style> <style>
.legend { background:white;padding:8px;line-height:1.5;border-radius:5px;box-shadow:0 0 10px rgba(0,0,0,0.3);font-size:14px;color:black; } .legend { background:white;padding:8px;line-height:1.5;border-radius:5px;box-shadow:0 0 10px rgba(0,0,0,0.3);font-size:14px;color:black; }
.legend i { width:12px;height:12px;display:inline-block;margin-right:6px;border-radius:50%; } .legend i { width:12px;height:12px;display:inline-block;margin-right:6px;border-radius:50%; }
#filter-container { text-align:center;margin-top:10px; } #filter-container { text-align:center;margin-top:10px; }
.filter-checkbox { margin:0 10px; } .filter-checkbox { margin:0 10px; }
#share-button, #reset-filters-button {
#share-button,
#reset-filters-button {
padding:5px 15px;border:none;border-radius:4px;font-size:14px;cursor:pointer;color:white; padding:5px 15px;border:none;border-radius:4px;font-size:14px;cursor:pointer;color:white;
} }
#share-button { margin-left:20px; background-color:#4CAF50; } #share-button { margin-left:20px; background-color:#4CAF50; }
#share-button:hover { background-color:#45a049; } #share-button:hover { background-color:#45a049; }
#share-button:active { background-color:#3d8b40; } #share-button:active { background-color:#3d8b40; }
#reset-filters-button { margin-left:10px; background-color:#f44336; } #reset-filters-button { margin-left:10px; background-color:#f44336; }
#reset-filters-button:hover { background-color:#da190b; } #reset-filters-button:hover { background-color:#da190b; }
#reset-filters-button:active { background-color:#c41e0d; } #reset-filters-button:active { background-color:#c41e0d; }
.blinking-tooltip { background:white;color:black;border:1px solid black;border-radius:4px;padding:2px 5px; } .blinking-tooltip { background:white;color:black;border:1px solid black;border-radius:4px;padding:2px 5px; }
</style> </style>
{% endblock %} {% endblock %}
{% block body %} {% block body %}
<div id="map" style="width:100%;height:calc(100vh - 270px)"></div>
<div id="map" style="width:100%; height:calc(100vh - 270px)"></div>
<div id="map-legend"
class="legend"
style="position:absolute;
bottom:30px;
right:15px;
z-index:500;
pointer-events:none;">
<div>
<i style="background:orange; width:15px; height:3px; border-radius:0;"></i>
<span data-translate-lang="legend_traceroute">Traceroute Path (arrowed)</span>
</div>
<div style="margin-top:6px;">
<i style="background:gray; width:15px; height:3px; border-radius:0;"></i>
<span data-translate-lang="legend_neighbor">Neighbor Link</span>
</div>
</div>
<div id="filter-container"> <div id="filter-container">
<input type="checkbox" class="filter-checkbox" id="filter-routers-only"> <input type="checkbox" class="filter-checkbox" id="filter-routers-only"> Show Routers Only
<span data-translate-lang="show_routers_only">Show Routers Only</span>
</div> </div>
<div style="text-align:center;margin-top:5px;"> <div style="text-align:center;margin-top:5px;">
<button id="share-button" onclick="shareCurrentView()" data-translate-lang="share_view"> <button id="share-button" onclick="shareCurrentView()">🔗 Share This View</button>
🔗 Share This View <button id="reset-filters-button" onclick="resetFiltersToDefaults()">↺ Reset Filters To Defaults</button>
</button>
<button id="reset-filters-button" onclick="resetFiltersToDefaults()" data-translate-lang="reset_filters">
↺ Reset Filters To Defaults
</button>
</div> </div>
<script src="https://unpkg.com/leaflet@1.9.4/dist/leaflet.js" <script src="https://unpkg.com/leaflet@1.9.4/dist/leaflet.js"
integrity="sha256-20nQCchB9co0qIjJZRGuk2/Z9VM+kNiyxNV1lvTlZBo=" integrity="sha256-20nQCchB9co0qIjJZRGuk2/Z9VM+kNiyxNV1lvTlZBo="
crossorigin=""></script> crossorigin=""></script>
<script src="https://unpkg.com/leaflet-polylinedecorator@1.6.0/dist/leaflet.polylinedecorator.js" <script src="https://unpkg.com/leaflet-polylinedecorator@1.6.0/dist/leaflet.polylinedecorator.js"
integrity="sha384-FhPn/2P/fJGhQLeNWDn9B/2Gml2bPOrKJwFqJXgR3xOPYxWg5mYQ5XZdhUSugZT0" integrity="sha384-FhPn/2P/fJGhQLeNWDn9B/2Gml2bPOrKJwFqJXgR3xOPYxWg5mYQ5XZdhUSugZT0"
crossorigin></script> crossorigin></script>
<script> <script>
/* ====================================================== // ---------------------- Map Initialization ----------------------
MAP PAGE TRANSLATION SYSTEM
====================================================== */
let mapTranslations = {};
async function loadTranslationsMap() {
try {
const cfg = await window._siteConfigPromise;
const lang = cfg?.site?.language || "en";
const res = await fetch(`/api/lang?lang=${lang}&section=map`);
mapTranslations = await res.json();
applyTranslationsMap();
} catch (err) {
console.error("Map translation load failed:", err);
}
}
function applyTranslationsMap(root = document) {
root.querySelectorAll("[data-translate-lang]").forEach(el => {
const key = el.dataset.translateLang;
const val = mapTranslations[key];
if (!val) return;
if (el.tagName === "INPUT" && el.placeholder !== undefined) {
el.placeholder = val;
} else {
el.textContent = val;
}
});
}
/* ======================================================
EXISTING MAP LOGIC
====================================================== */
var map = L.map('map'); var map = L.map('map');
L.tileLayer('https://tile.openstreetmap.org/{z}/{x}/{y}.png', L.tileLayer('https://tile.openstreetmap.org/{z}/{x}/{y}.png', { maxZoom:19, attribution:'&copy; OpenStreetMap' }).addTo(map);
{ maxZoom:19, attribution:'&copy; OpenStreetMap' }).addTo(map);
// Data structures // ---------------------- Globals ----------------------
var nodes = [], markers = {}, markerById = {}, nodeMap = new Map(); var nodes=[], markers={}, markerById={}, nodeMap = new Map();
var edgeLayer = L.layerGroup().addTo(map), selectedNodeId = null; var edgesData=[], edgeLayer = L.layerGroup().addTo(map), selectedNodeId = null;
var activeBlinks = new Map(), lastImportTime = null; var activeBlinks = new Map(), lastImportTime = null;
var mapInterval = 0; var mapInterval = 0;
const portMap = {1:"Text",67:"Telemetry",3:"Position",70:"Traceroute",4:"Node Info",71:"Neighbour Info",73:"Map Report"};
const portMap = { const palette = ["#e6194b","#4363d8","#f58231","#911eb4","#46f0f0","#f032e6","#bcf60c","#fabebe","#008080","#e6beff","#9a6324","#fffac8","#800000","#aaffc3","#808000","#ffd8b1","#000075","#808080"];
1:"Text",
67:"Telemetry",
3:"Position",
70:"Traceroute",
4:"Node Info",
71:"Neighbour Info",
73:"Map Report"
};
const palette = ["#e6194b","#4363d8","#f58231","#911eb4","#46f0f0","#f032e6","#bcf60c","#fabebe",
"#008080","#e6beff","#9a6324","#fffac8","#800000","#aaffc3","#808000","#ffd8b1",
"#000075","#808080"];
const colorMap = new Map(); let nextColorIndex = 0; const colorMap = new Map(); let nextColorIndex = 0;
const channelSet = new Set(); const channelSet = new Set();
map.on("popupopen", function (e) { // ---------------------- Helpers ----------------------
const popupEl = e.popup.getElement(); function timeAgo(date){ const diff=Date.now()-new Date(date), s=Math.floor(diff/1000), m=Math.floor(s/60), h=Math.floor(m/60), d=Math.floor(h/24); return d>0?d+"d":h>0?h+"h":m>0?m+"m":s+"s"; }
if (popupEl) applyTranslationsMap(popupEl); function hashToColor(str){ if(colorMap.has(str)) return colorMap.get(str); const c=palette[nextColorIndex++%palette.length]; colorMap.set(str,c); return c; }
}); function isInvalidCoord(n){ return !n||!n.lat||!n.long||n.lat===0||n.long===0||Number.isNaN(n.lat)||Number.isNaN(n.long); }
function timeAgo(date){
const diff = Date.now() - new Date(date);
const s = Math.floor(diff/1000), m = Math.floor(s/60),
h = Math.floor(m/60), d = Math.floor(h/24);
return d>0?d+"d":h>0?h+"h":m>0?m+"m":s+"s";
}
function hashToColor(str){
if(colorMap.has(str)) return colorMap.get(str);
const c = palette[nextColorIndex++ % palette.length];
colorMap.set(str,c);
return c;
}
function isInvalidCoord(n){
return !n || !n.lat || !n.long || n.lat === 0 || n.long === 0 ||
Number.isNaN(n.lat) || Number.isNaN(n.long);
}
/* ======================================================
PACKET FETCHING (unchanged)
====================================================== */
// ---------------------- Packet Fetching ----------------------
function fetchLatestPacket(){ function fetchLatestPacket(){
fetch(`/api/packets?limit=1`) fetch(`/api/packets?limit=1`)
.then(r=>r.json()) .then(r=>r.json())
@@ -175,7 +72,6 @@ function fetchLatestPacket(){
function fetchNewPackets(){ function fetchNewPackets(){
if(mapInterval <= 0) return; if(mapInterval <= 0) return;
if(lastImportTime===null) return; if(lastImportTime===null) return;
const url = new URL(`/api/packets`, window.location.origin); const url = new URL(`/api/packets`, window.location.origin);
url.searchParams.set("since", lastImportTime); url.searchParams.set("since", lastImportTime);
url.searchParams.set("limit", 50); url.searchParams.set("limit", 50);
@@ -185,22 +81,19 @@ function fetchNewPackets(){
.then(data=>{ .then(data=>{
if(!data.packets || data.packets.length===0) return; if(!data.packets || data.packets.length===0) return;
let latest = lastImportTime; let latest = lastImportTime;
data.packets.forEach(pkt=>{ data.packets.forEach(pkt=>{
if(pkt.import_time_us > latest) latest = pkt.import_time_us; if(pkt.import_time_us > latest) latest = pkt.import_time_us;
const marker = markerById[pkt.from_node_id]; const marker = markerById[pkt.from_node_id];
const nodeData = nodeMap.get(pkt.from_node_id); const nodeData = nodeMap.get(pkt.from_node_id);
if(marker && nodeData) blinkNode(marker,nodeData.long_name,pkt.portnum); if(marker && nodeData) blinkNode(marker,nodeData.long_name,pkt.portnum);
}); });
lastImportTime = latest; lastImportTime = latest;
}) })
.catch(console.error); .catch(console.error);
} }
// ---------------------- Polling ----------------------
let packetInterval=null; let packetInterval=null;
function startPacketFetcher(){ function startPacketFetcher(){
if(mapInterval<=0) return; if(mapInterval<=0) return;
if(!packetInterval){ if(!packetInterval){
@@ -208,58 +101,65 @@ function startPacketFetcher(){
packetInterval=setInterval(fetchNewPackets,mapInterval*1000); packetInterval=setInterval(fetchNewPackets,mapInterval*1000);
} }
} }
function stopPacketFetcher(){ function stopPacketFetcher(){
if(packetInterval){ if(packetInterval){
clearInterval(packetInterval); clearInterval(packetInterval);
packetInterval=null; packetInterval=null;
} }
} }
document.addEventListener("visibilitychange",()=>{ document.addEventListener("visibilitychange",()=>{
document.hidden?stopPacketFetcher():startPacketFetcher(); document.hidden?stopPacketFetcher():startPacketFetcher();
}); });
// ---------------------- WAIT FOR CONFIG ----------------------
async function waitForConfig() { async function waitForConfig() {
while (typeof window._siteConfigPromise === "undefined") { while (typeof window._siteConfigPromise === "undefined") {
console.log("Waiting for _siteConfigPromise...");
await new Promise(r => setTimeout(r, 100)); await new Promise(r => setTimeout(r, 100));
} }
try { try {
const cfg = await window._siteConfigPromise; const cfg = await window._siteConfigPromise;
return cfg.site || {}; if (!cfg || !cfg.site) throw new Error("Config missing site object");
return cfg.site;
} catch (err) { } catch (err) {
console.error("Error loading site config:", err); console.error("Error loading site config:", err);
return {}; return {};
} }
} }
// ---------------------- Load Config & Start Polling ----------------------
async function initMapPolling() { async function initMapPolling() {
try { try {
const site = await waitForConfig(); const site = await waitForConfig();
mapInterval = parseInt(site.map_interval, 10) || 0; mapInterval = parseInt(site.map_interval, 10) || 0;
// ---- Check URL params ----
const params = new URLSearchParams(window.location.search); const params = new URLSearchParams(window.location.search);
const lat = parseFloat(params.get('lat')); const lat = parseFloat(params.get('lat'));
const lng = parseFloat(params.get('lng')); const lng = parseFloat(params.get('lng'));
const zoom = parseInt(params.get('zoom'), 10); const zoom = parseInt(params.get('zoom'), 10);
if (!isNaN(lat) && !isNaN(lng) && !isNaN(zoom)) { if (!isNaN(lat) && !isNaN(lng) && !isNaN(zoom)) {
map.setView([lat, lng], zoom); map.setView([lat, lng], zoom);
window.configBoundsApplied = true; window.configBoundsApplied = true;
setTimeout(() => map.invalidateSize(), 100); setTimeout(() => map.invalidateSize(), 100);
} }
else { else {
const tl = [parseFloat(site.map_top_left_lat), parseFloat(site.map_top_left_lon)]; const topLeft = [parseFloat(site.map_top_left_lat), parseFloat(site.map_top_left_lon)];
const br = [parseFloat(site.map_bottom_right_lat), parseFloat(site.map_bottom_right_lon)]; const bottomRight = [parseFloat(site.map_bottom_right_lat), parseFloat(site.map_bottom_right_lon)];
if (topLeft.every(isFinite) && bottomRight.every(isFinite)) {
if (tl.every(isFinite) && br.every(isFinite)) { map.fitBounds([topLeft, bottomRight]);
map.fitBounds([tl, br]);
window.configBoundsApplied = true; window.configBoundsApplied = true;
setTimeout(() => map.invalidateSize(), 100); setTimeout(() => map.invalidateSize(), 100);
} }
} }
if (mapInterval > 0) startPacketFetcher(); if (mapInterval > 0) {
console.log(`Starting map polling every ${mapInterval}s`);
startPacketFetcher();
} else {
console.log("Map polling disabled (map_interval=0)");
}
} catch (err) { } catch (err) {
console.error("Failed to load /api/config:", err); console.error("Failed to load /api/config:", err);
@@ -268,222 +168,116 @@ async function initMapPolling() {
initMapPolling(); initMapPolling();
/* ====================================================== // ---------------------- Load Nodes + Edges ----------------------
LOAD NODES fetch('/api/nodes?days_active=3').then(r=>r.json()).then(data=>{
====================================================== */ if(!data.nodes) return;
nodes = data.nodes.map(n=>({
fetch('/api/nodes?days_active=3') key: n.node_id!==null?n.node_id:n.id,
.then(r=>r.json()) id: n.id,
.then(data=>{ node_id: n.node_id,
if(!data.nodes) return; lat: n.last_lat?n.last_lat/1e7:null,
long: n.last_long?n.last_long/1e7:null,
nodes = data.nodes.map(n=>({ long_name: n.long_name||"",
key: n.node_id ?? n.id, short_name: n.short_name||"",
id: n.id, channel: n.channel||"",
node_id: n.node_id, hw_model: n.hw_model||"",
lat: n.last_lat ? n.last_lat/1e7 : null, role: n.role||"",
long: n.last_long ? n.last_long/1e7 : null, firmware: n.firmware||"",
long_name: n.long_name || "", last_update: n.last_update||"",
short_name: n.short_name || "", isRouter: n.role? n.role.toLowerCase().includes("router"):false
channel: n.channel || "", }));
hw_model: n.hw_model || "", nodes.forEach(n=>{ nodeMap.set(n.key,n); if(n.channel) channelSet.add(n.channel); });
role: n.role || "", renderNodesOnMap();
firmware: n.firmware || "", createChannelFilters();
last_update: n.last_update || "", return fetch('/api/edges');
isRouter: (n.role||"").toLowerCase().includes("router") }).then(r=>r?r.json():null).then(data=>{
})); if(data && data.edges) edgesData=data.edges;
}).catch(console.error);
nodes.forEach(n=>{
nodeMap.set(n.key, n);
if(n.channel) channelSet.add(n.channel);
});
renderNodesOnMap();
createChannelFilters();
})
.catch(console.error);
/* ======================================================
RENDER NODES
====================================================== */
// ---------------------- Render Nodes ----------------------
function renderNodesOnMap(){ function renderNodesOnMap(){
const bounds = L.latLngBounds();
nodes.forEach(node=>{ nodes.forEach(node=>{
if(isInvalidCoord(node)) return; if(isInvalidCoord(node)) return;
const color = hashToColor(node.channel); const color = hashToColor(node.channel);
const opts = { radius: node.isRouter?9:7, color:"white", fillColor:color, fillOpacity:1, weight:0.7 };
const marker = L.circleMarker([node.lat,node.long], { const marker = L.circleMarker([node.lat,node.long],opts).addTo(map);
radius: node.isRouter ? 9 : 7,
color: "white",
fillColor: color,
fillOpacity: 1,
weight: 0.7
}).addTo(map);
marker.nodeId = node.key; marker.nodeId = node.key;
marker.originalColor = color; marker.originalColor = color;
markerById[node.key] = marker; markerById[node.key] = marker;
const popup = `<b><a href="/node/${node.node_id}">${node.long_name}</a> (${node.short_name})</b><br>
const popup = ` <b>Channel:</b> ${node.channel}<br>
<b><a href="/node/${node.node_id}">${node.long_name}</a> (${node.short_name})</b><br> <b>Model:</b> ${node.hw_model}<br>
<b>Role:</b> ${node.role}<br>
<b data-translate-lang="channel_label"></b> ${node.channel}<br> ${node.last_update? `<b>Last seen:</b> ${timeAgo(node.last_update)}<br>`:""}
<b data-translate-lang="model_label"></b> ${node.hw_model}<br> ${node.firmware? `<b>Firmware:</b> ${node.firmware}<br>`:""}`;
<b data-translate-lang="role_label"></b> ${node.role}<br> marker.on('click',()=>{ onNodeClick(node); marker.bindPopup(popup).openPopup(); setTimeout(()=>marker.closePopup(),3000); });
bounds.extend(marker.getLatLng());
${
node.last_update
? `<b data-translate-lang="last_seen"></b> ${timeAgo(node.last_update)}<br>`
: ""
}
${
node.firmware
? `<b data-translate-lang="firmware"></b> ${node.firmware}<br>`
: ""
}
`;
marker.on('click', () => {
onNodeClick(node);
marker.bindPopup(popup).openPopup();
});
}); });
if(!window.configBoundsApplied && bounds.isValid()){
setTimeout(() => applyTranslationsMap(), 50); map.fitBounds(bounds);
setTimeout(()=>map.invalidateSize(),100);
}
} }
/* ====================================================== // ---------------------- Render Edges ----------------------
⭐ NEW: DYNAMIC EDGE LOADING function onNodeClick(node){
====================================================== */
async function onNodeClick(node){
selectedNodeId = node.key; selectedNodeId = node.key;
edgeLayer.clearLayers(); edgeLayer.clearLayers();
edgesData.forEach(edge=>{
try { if(edge.from!==node.key && edge.to!==node.key) return;
const res = await fetch(`/api/edges?node_id=${node.key}`); const f=nodeMap.get(edge.from), t=nodeMap.get(edge.to);
const data = await res.json(); if(!f||!t||isInvalidCoord(f)||isInvalidCoord(t)) return;
const edges = data.edges || []; const color=edge.type==="neighbor"?"gray":"orange";
const l=L.polyline([[f.lat,f.long],[t.lat,t.long]],{color,weight:3}).addTo(edgeLayer);
edges.forEach(edge=>{ if(edge.type==="traceroute"){
const f = nodeMap.get(edge.from); L.polylineDecorator(l,{patterns:[{offset:'100%',repeat:0,symbol:L.Symbol.arrowHead({pixelSize:5,polygon:false,pathOptions:{stroke:true,color}})}]}).addTo(edgeLayer);
const t = nodeMap.get(edge.to); }
});
if(!f || !t || isInvalidCoord(f) || isInvalidCoord(t)) return;
const color = edge.type === "neighbor" ? "gray" : "orange";
const line = L.polyline([[f.lat, f.long], [t.lat, t.long]], {
color, weight: 3
}).addTo(edgeLayer);
if(edge.type === "traceroute"){
L.polylineDecorator(line, {
patterns: [
{
offset: '100%',
repeat: 0,
symbol: L.Symbol.arrowHead({
pixelSize:5,
polygon:false,
pathOptions:{stroke:true,color}
})
}
]
}).addTo(edgeLayer);
}
});
} catch(err){
console.error("Failed to load edges for node", node.key, err);
}
} }
map.on('click',e=>{ if(!e.originalEvent.target.classList.contains('leaflet-interactive')){ edgeLayer.clearLayers(); selectedNodeId=null; } });
map.on('click', e=>{ // ---------------------- Packet Blinking ----------------------
if(!e.originalEvent.target.classList.contains('leaflet-interactive')){
edgeLayer.clearLayers();
selectedNodeId=null;
}
});
/* ======================================================
BLINKING
====================================================== */
function blinkNode(marker,longName,portnum){ function blinkNode(marker,longName,portnum){
if(!map.hasLayer(marker)) return; if(!map.hasLayer(marker)) return;
if(activeBlinks.has(marker)){ clearInterval(activeBlinks.get(marker)); marker.setStyle({fillColor:marker.originalColor}); if(marker.tooltip) map.removeLayer(marker.tooltip); }
if(activeBlinks.has(marker)){ let blinkCount=0;
clearInterval(activeBlinks.get(marker)); const portName = portMap[portnum]||`Port ${portnum}`;
marker.setStyle({ fillColor: marker.originalColor }); const tooltip = L.tooltip({permanent:true,direction:'top',offset:[0,-marker.options.radius-5],className:'blinking-tooltip'})
if(marker.tooltip) map.removeLayer(marker.tooltip); .setContent(`${longName} (${portName})`).setLatLng(marker.getLatLng()).addTo(map);
}
let blinkCount = 0;
const tooltip = L.tooltip({
permanent:true,
direction:'top',
offset:[0,-marker.options.radius-5],
className:'blinking-tooltip'
})
.setContent(`${longName} (${portMap[portnum] || "Port "+portnum})`)
.setLatLng(marker.getLatLng())
.addTo(map);
marker.tooltip = tooltip; marker.tooltip = tooltip;
const interval = setInterval(()=>{ const interval = setInterval(()=>{
if(map.hasLayer(marker)){ if(map.hasLayer(marker)){ marker.setStyle({fillColor: blinkCount%2===0?'yellow':marker.originalColor}); marker.bringToFront(); }
marker.setStyle({
fillColor: blinkCount%2===0 ? 'yellow' : marker.originalColor
});
marker.bringToFront();
}
blinkCount++; blinkCount++;
if(blinkCount>7){ clearInterval(interval); marker.setStyle({fillColor:marker.originalColor}); map.removeLayer(tooltip); activeBlinks.delete(marker); }
if(blinkCount>7){
clearInterval(interval);
marker.setStyle({ fillColor: marker.originalColor });
map.removeLayer(tooltip);
activeBlinks.delete(marker);
}
},500); },500);
activeBlinks.set(marker,interval);
activeBlinks.set(marker, interval);
} }
/* ====================================================== // ---------------------- Channel Filters ----------------------
CHANNEL FILTERS
====================================================== */
function createChannelFilters(){ function createChannelFilters(){
const filterContainer = document.getElementById("filter-container"); const filterContainer = document.getElementById("filter-container");
const saved = JSON.parse(localStorage.getItem("mapFilters") || "{}"); const savedState = JSON.parse(localStorage.getItem("mapFilters") || "{}");
channelSet.forEach(channel=>{ channelSet.forEach(channel=>{
const cb=document.createElement("input"); const checkbox = document.createElement("input");
cb.type="checkbox"; checkbox.type = "checkbox";
cb.className="filter-checkbox"; checkbox.className = "filter-checkbox";
cb.id=`filter-channel-${channel}`; checkbox.id = `filter-channel-${channel}`;
cb.checked = saved[channel] !== false; checkbox.checked = savedState[channel] !== false;
checkbox.addEventListener("change", saveFiltersToLocalStorage);
checkbox.addEventListener("change", updateNodeVisibility);
filterContainer.appendChild(checkbox);
cb.addEventListener("change", saveFiltersToLocalStorage); const label = document.createElement("label");
cb.addEventListener("change", updateNodeVisibility); label.htmlFor = checkbox.id;
label.innerText = channel;
filterContainer.appendChild(cb);
const label=document.createElement("label");
label.htmlFor=cb.id;
label.innerText=channel;
label.style.color = hashToColor(channel); label.style.color = hashToColor(channel);
filterContainer.appendChild(label); filterContainer.appendChild(label);
}); });
const routerOnly=document.getElementById("filter-routers-only"); const routerOnly = document.getElementById("filter-routers-only");
routerOnly.checked = saved["routersOnly"] || false; routerOnly.checked = savedState["routersOnly"] || false;
routerOnly.addEventListener("change", saveFiltersToLocalStorage); routerOnly.addEventListener("change", saveFiltersToLocalStorage);
routerOnly.addEventListener("change", updateNodeVisibility); routerOnly.addEventListener("change", updateNodeVisibility);
@@ -492,69 +286,51 @@ function createChannelFilters(){
function saveFiltersToLocalStorage(){ function saveFiltersToLocalStorage(){
const state = {}; const state = {};
channelSet.forEach(ch=>{ channelSet.forEach(ch => {
state[ch] = document.getElementById(`filter-channel-${ch}`).checked; const el = document.getElementById(`filter-channel-${ch}`);
state[ch] = el.checked;
}); });
state["routersOnly"] = document.getElementById("filter-routers-only").checked; state["routersOnly"] = document.getElementById("filter-routers-only").checked;
localStorage.setItem("mapFilters", JSON.stringify(state)); localStorage.setItem("mapFilters", JSON.stringify(state));
} }
function updateNodeVisibility(){ function updateNodeVisibility(){
const routerOnly = document.getElementById("filter-routers-only").checked; const showRoutersOnly = document.getElementById("filter-routers-only").checked;
const activeChannels = [...channelSet].filter(ch => const activeChannels = Array.from(channelSet).filter(ch=>document.getElementById(`filter-channel-${ch}`).checked);
document.getElementById(`filter-channel-${ch}`).checked
);
nodes.forEach(n=>{ nodes.forEach(n=>{
const marker = markerById[n.key]; const marker = markerById[n.key];
if(marker){ if(marker){
const visible = const visible = (!showRoutersOnly || n.isRouter) && activeChannels.includes(n.channel);
(!routerOnly || n.isRouter) && if(visible) map.addLayer(marker); else map.removeLayer(marker);
activeChannels.includes(n.channel);
visible ? map.addLayer(marker) : map.removeLayer(marker);
} }
}); });
} }
/* ====================================================== // ---------------------- Share / Reset ----------------------
SHARE / RESET
====================================================== */
function shareCurrentView() { function shareCurrentView() {
const c = map.getCenter(); const center = map.getCenter();
const url = `${window.location.origin}/map?lat=${c.lat.toFixed(6)}&lng=${c.lng.toFixed(6)}&zoom=${map.getZoom()}`; const zoom = map.getZoom();
const lat = center.lat.toFixed(6);
const lng = center.lng.toFixed(6);
navigator.clipboard.writeText(url).then(()=>{ const shareUrl = `${window.location.origin}/map?lat=${lat}&lng=${lng}&zoom=${zoom}`;
const btn = document.getElementById('share-button'); navigator.clipboard.writeText(shareUrl).then(() => {
const old = btn.textContent; const button = document.getElementById('share-button');
btn.textContent = '✓ ' + (mapTranslations.link_copied || 'Link Copied!'); const originalText = button.textContent;
btn.style.backgroundColor = '#2196F3'; button.textContent = '✓ Link Copied!';
button.style.backgroundColor = '#2196F3';
setTimeout(()=>{ setTimeout(() => {
btn.textContent = old; button.textContent = originalText;
btn.style.backgroundColor = '#4CAF50'; button.style.backgroundColor = '#4CAF50';
}, 2000); }, 2000);
}); }).catch(() => alert('Share this link:\n' + shareUrl));
} }
function resetFiltersToDefaults(){ function resetFiltersToDefaults(){
document.getElementById("filter-routers-only").checked = false; document.getElementById("filter-routers-only").checked = false;
channelSet.forEach(ch => { channelSet.forEach(ch=>document.getElementById(`filter-channel-${ch}`).checked = true);
document.getElementById(`filter-channel-${ch}`).checked = true;
});
saveFiltersToLocalStorage(); saveFiltersToLocalStorage();
updateNodeVisibility(); updateNodeVisibility();
} }
/* ======================================================
TRANSLATION LOAD
====================================================== */
document.addEventListener("DOMContentLoaded", () => {
loadTranslationsMap();
});
</script> </script>
{% endblock %} {% endblock %}
+69 -118
View File
@@ -23,29 +23,20 @@
.channel { font-style: italic; color: #bbb; } .channel { font-style: italic; color: #bbb; }
.channel a { font-style: normal; color: #999; } .channel a { font-style: normal; color: #999; }
@keyframes flash { 0% { background-color: #ffe066; } 100% { background-color: inherit; } }
.chat-packet.flash { animation: flash 3.5s ease-out; }
.replying-to { font-size: 0.8em; color: #aaa; margin-top: 2px; padding-left: 10px; }
.replying-to .reply-preview { color: #aaa; }
#weekly-message { margin: 15px 0; font-weight: bold; color: #ffeb3b; } #weekly-message { margin: 15px 0; font-weight: bold; color: #ffeb3b; }
#total-count { margin-bottom: 10px; font-style: italic; color: #ccc; } #total-count { margin-bottom: 10px; font-style: italic; color: #ccc; }
{% endblock %} {% endblock %}
{% block body %} {% block body %}
<div class="container"> <div class="container">
<!-- ⭐ NET TITLE WITH ICON ⭐ --> <div id="weekly-message">Loading weekly message...</div>
<div class="container px-2"> <div id="total-count">Total messages: 0</div>
<h2 style="color:white; margin:0 0 10px 0;">
<span class="icon">💬</span>
<span data-translate-lang="net_title"></span>
</h2>
</div>
<!-- Weekly network message -->
<div id="weekly-message"></div>
<!-- Total message count -->
<div id="total-count">
<span data-translate-lang="total_messages">Total messages:</span>
<span id="total-count-value">0</span>
</div>
<div id="chat-container"> <div id="chat-container">
<div class="container" id="chat-log"></div> <div class="container" id="chat-log"></div>
@@ -54,180 +45,140 @@
<script> <script>
document.addEventListener("DOMContentLoaded", async () => { document.addEventListener("DOMContentLoaded", async () => {
const chatContainer = document.querySelector("#chat-log"); const chatContainer = document.querySelector("#chat-log");
const totalCountEl = document.querySelector("#total-count");
const weeklyMessageEl = document.querySelector("#weekly-message"); const weeklyMessageEl = document.querySelector("#weekly-message");
const totalCountValueEl = document.querySelector("#total-count-value"); if (!chatContainer || !totalCountEl || !weeklyMessageEl) {
console.error("Required elements not found");
if (!chatContainer || !weeklyMessageEl || !totalCountValueEl) {
console.error("Required elements missing");
return; return;
} }
const renderedPacketIds = new Set(); const renderedPacketIds = new Set();
let netTranslations = {}; const packetMap = new Map();
let chatTranslations = {};
let netTag = ""; let netTag = "";
/* ----------------------------------- function updateTotalCount() {
Escape HTML safely totalCountEl.textContent = `Total messages: ${renderedPacketIds.size}`;
----------------------------------- */ }
function escapeHtml(text) { function escapeHtml(text) {
const div = document.createElement("div"); const div = document.createElement("div");
div.textContent = text ?? ""; div.textContent = text ?? "";
return div.innerHTML; return div.innerHTML;
} }
/* ----------------------------------- function applyTranslations(translations, root = document) {
Apply translations
----------------------------------- */
function applyTranslations(trans, root=document) {
root.querySelectorAll("[data-translate-lang]").forEach(el => { root.querySelectorAll("[data-translate-lang]").forEach(el => {
const key = el.dataset.translateLang; const key = el.dataset.translateLang;
if (trans[key]) el.textContent = trans[key]; if (translations[key]) el.textContent = translations[key];
}); });
root.querySelectorAll("[data-translate-lang-title]").forEach(el => { root.querySelectorAll("[data-translate-lang-title]").forEach(el => {
const key = el.dataset.translateLangTitle; const key = el.dataset.translateLangTitle;
if (trans[key]) el.title = trans[key]; if (translations[key]) el.title = translations[key];
}); });
} }
/* -----------------------------------
Update count
----------------------------------- */
function updateTotalCount() {
totalCountValueEl.textContent = renderedPacketIds.size;
}
/* -----------------------------------
Render single packet
----------------------------------- */
function renderPacket(packet) { function renderPacket(packet) {
if (renderedPacketIds.has(packet.id)) return; if (renderedPacketIds.has(packet.id)) return;
renderedPacketIds.add(packet.id); renderedPacketIds.add(packet.id);
packetMap.set(packet.id, packet);
const date = new Date(packet.import_time_us / 1000); const date = new Date(packet.import_time_us / 1000);
const formattedTime = date.toLocaleTimeString([], { hour: "numeric", minute: "2-digit", second: "2-digit", hour12: true });
const formattedDate = `${(date.getMonth() + 1).toString().padStart(2, "0")}/${date.getDate().toString().padStart(2, "0")}/${date.getFullYear()}`;
const formattedTimestamp = `${formattedTime} - ${formattedDate}`;
const timeStr = date.toLocaleTimeString([], { let replyHtml = "";
hour: "numeric", if (packet.reply_id) {
minute: "2-digit", const parent = packetMap.get(packet.reply_id);
second: "2-digit", if (parent) {
hour12: true replyHtml = `<div class="replying-to">
}); <div class="reply-preview">
<i data-translate-lang="replying_to"></i>
const dateStr = <strong>${escapeHtml((parent.long_name || "").trim() || `Node ${parent.from_node_id}`)}</strong>:
`${String(date.getMonth()+1).padStart(2,"0")}/`+ ${escapeHtml(parent.payload || "")}
`${String(date.getDate()).padStart(2,"0")}/`+ </div>
date.getFullYear(); </div>`;
} else {
const timestamp = `${timeStr} - ${dateStr}`; replyHtml = `<div class="replying-to">
<i data-translate-lang="replying_to"></i>
const fromName = <a href="/packet/${packet.reply_id}">${packet.reply_id}</a>
(packet.long_name || "").trim() || </div>`;
`${netTranslations.node_fallback} ${packet.from_node_id}`; }
}
const div = document.createElement("div"); const div = document.createElement("div");
div.className = "row chat-packet"; div.className = "row chat-packet";
div.dataset.packetId = packet.id; div.dataset.packetId = packet.id;
div.innerHTML = ` div.innerHTML = `
<span class="col-2 timestamp" title="${packet.import_time_us}"> <span class="col-2 timestamp" title="${packet.import_time_us}">${formattedTimestamp}</span>
${timestamp}
</span>
<span class="col-2 channel"> <span class="col-2 channel">
<a href="/packet/${packet.id}" <a href="/packet/${packet.id}" data-translate-lang-title="view_packet_details">✉️</a>
data-translate-lang-title="view_packet_details">✉️</a>
${escapeHtml(packet.channel || "")} ${escapeHtml(packet.channel || "")}
</span> </span>
<span class="col-3 nodename"> <span class="col-3 nodename">
<a href="/packet_list/${packet.from_node_id}"> <a href="/packet_list/${packet.from_node_id}">
${escapeHtml(fromName)} ${escapeHtml((packet.long_name || "").trim() || `Node ${packet.from_node_id}`)}
</a> </a>
</span> </span>
<span class="col-5 message">${escapeHtml(packet.payload)}${replyHtml}</span>
<span class="col-5 message">
${escapeHtml(packet.payload).replace(/\n/g,"<br>")}
</span>
`; `;
chatContainer.prepend(div); chatContainer.prepend(div);
applyTranslations(netTranslations, div); applyTranslations(chatTranslations, div);
updateTotalCount(); updateTotalCount();
} }
/* -----------------------------------
Sort descending by time
----------------------------------- */
function renderPacketsEnsureDescending(packets) { function renderPacketsEnsureDescending(packets) {
if (!packets || !packets.length) return; if (!Array.isArray(packets) || packets.length === 0) return;
const sorted = packets.slice().sort((a, b) => b.import_time_us - a.import_time_us); const sortedDesc = packets.slice().sort((a, b) => b.import_time_us - a.import_time_us);
for (let i = sorted.length - 1; i >= 0; i--) { for (let i = sortedDesc.length - 1; i >= 0; i--) renderPacket(sortedDesc[i]);
renderPacket(sorted[i]);
}
} }
/* -----------------------------------
Fetch initial net-tagged packets
----------------------------------- */
async function fetchInitialPackets(tag) { async function fetchInitialPackets(tag) {
if (!tag) return; if (!tag) {
console.warn("No net_tag defined, skipping packet fetch.");
return;
}
try { try {
const sixDaysAgoMs = Date.now() - 6*24*60*60*1000; console.log("Fetching packets for netTag:", tag);
const sixDaysAgoMs = Date.now() - (6 * 24 * 60 * 60 * 1000);
const sinceUs = Math.floor(sixDaysAgoMs * 1000); const sinceUs = Math.floor(sixDaysAgoMs * 1000);
const resp = await fetch(`/api/packets?portnum=1&contains=${encodeURIComponent(tag)}&since=${sinceUs}`);
const url =
`/api/packets?portnum=1&contains=${encodeURIComponent(tag)}&since=${sinceUs}`;
const resp = await fetch(url);
const data = await resp.json(); const data = await resp.json();
console.log("Packets received:", data?.packets?.length);
if (data?.packets?.length) if (data?.packets?.length) renderPacketsEnsureDescending(data.packets);
renderPacketsEnsureDescending(data.packets);
} catch (err) { } catch (err) {
console.error("Initial fetch error:", err); console.error("Initial fetch error:", err);
} }
} }
/* -----------------------------------
Load translations from section=net
----------------------------------- */
async function loadTranslations(cfg) { async function loadTranslations(cfg) {
try { try {
const lang = cfg?.site?.language || "en"; const langCode = cfg?.site?.language || "en";
const res = await fetch(`/api/lang?lang=${lang}&section=net`); const res = await fetch(`/api/lang?lang=${langCode}&section=chat`);
netTranslations = await res.json(); chatTranslations = await res.json();
applyTranslations(netTranslations, document); applyTranslations(chatTranslations, document);
} catch (err) { } catch (err) {
console.error("Failed loading translations", err); console.error("Chat translation load failed:", err);
} }
} }
/* ----------------------------------- // --- MAIN LOGIC ---
MAIN
----------------------------------- */
try { try {
const cfg = await window._siteConfigPromise; const cfg = await window._siteConfigPromise; // ✅ Already fetched by base.html
const site = cfg?.site || {}; const site = cfg?.site || {};
// Populate from config
netTag = site.net_tag || ""; netTag = site.net_tag || "";
weeklyMessageEl.textContent = site.weekly_net_message || "Weekly message not set.";
weeklyMessageEl.textContent = site.weekly_net_message || "";
await loadTranslations(cfg); await loadTranslations(cfg);
await fetchInitialPackets(netTag); await fetchInitialPackets(netTag);
} catch (err) { } catch (err) {
console.error("Initialization failed:", err); console.error("Initialization failed:", err);
weeklyMessageEl.textContent = "Failed to load site config.";
weeklyMessageEl.textContent =
netTranslations.failed_to_load_site_config ||
"Failed to load site config.";
} }
}); });
</script> </script>
{% endblock %} {% endblock %}
File diff suppressed because it is too large Load Diff
+68 -231
View File
@@ -1,24 +1,12 @@
{% extends "base.html" %} {% extends "base.html" %}
{% block css %} {% block css %}
<style>
table { table {
width: 80%; width: 80%;
border-collapse: collapse; border-collapse: collapse;
margin: 1em auto; margin: 1em auto;
} }
/* Ensure table centered visually */
#node-list {
display: flex;
justify-content: center;
}
#node-list table {
margin-left: auto;
margin-right: auto;
}
th, td { th, td {
padding: 10px; padding: 10px;
border: 1px solid #333; border: 1px solid #333;
@@ -97,7 +85,6 @@ select, .export-btn, .search-box, .clear-btn {
color: white; color: white;
} }
/* Favorite stars */
.favorite-star { .favorite-star {
cursor: pointer; cursor: pointer;
font-size: 1.2em; font-size: 1.2em;
@@ -113,7 +100,6 @@ select, .export-btn, .search-box, .clear-btn {
color: #ffd700; color: #ffd700;
} }
/* Favorite filter button */
.favorites-btn { .favorites-btn {
background-color: #ffd700; background-color: #ffd700;
color: #000; color: #000;
@@ -128,165 +114,49 @@ select, .export-btn, .search-box, .clear-btn {
background-color: #ff6b6b; background-color: #ff6b6b;
color: white; color: white;
} }
/* --------------------------------------------- */
/* MOBILE CARD VIEW */
/* --------------------------------------------- */
@media (max-width: 768px) {
/* Hide desktop table */
#node-list table {
display: none;
}
/* Show mobile card list */
#mobile-node-list {
display: block !important;
width: 100%;
padding: 0 10px;
}
.node-card {
background: #1e1e1e;
border: 1px solid #333;
border-radius: 8px;
padding: 12px 15px;
margin-bottom: 12px;
color: white;
}
.node-card-header {
display: flex;
justify-content: space-between;
align-items: center;
font-size: 1.2em;
font-weight: bold;
margin-bottom: 8px;
}
.node-card-field {
margin: 4px 0;
font-size: 0.9em;
}
.node-card-field b {
color: #9fd4ff;
}
.favorite-star {
font-size: 1.4em;
}
}
</style>
{% endblock %} {% endblock %}
{% block body %} {% block body %}
<div class="filter-container"> <div class="filter-container">
<input type="text" id="search-box" class="search-box" placeholder="Search by name or ID..." />
<input <select id="role-filter"><option value="">All Roles</option></select>
type="text" <select id="channel-filter"><option value="">All Channels</option></select>
id="search-box" <select id="hw-filter"><option value="">All HW Models</option></select>
class="search-box" <select id="firmware-filter"><option value="">All Firmware</option></select>
data-translate-lang="search_placeholder"
placeholder="Search by name or ID..."
/>
<select id="role-filter"> <button class="favorites-btn" id="favorites-btn">⭐ Show Favorites</button>
<option value="" data-translate-lang="all_roles">All Roles</option> <button class="export-btn" id="export-btn">Export CSV</button>
</select> <button class="clear-btn" id="clear-btn">Clear Filters</button>
<select id="channel-filter">
<option value="" data-translate-lang="all_channels">All Channels</option>
</select>
<select id="hw-filter">
<option value="" data-translate-lang="all_hw">All HW Models</option>
</select>
<select id="firmware-filter">
<option value="" data-translate-lang="all_firmware">All Firmware</option>
</select>
<button class="favorites-btn" id="favorites-btn" data-translate-lang="show_favorites">
⭐ Show Favorites
</button>
<button class="export-btn" id="export-btn" data-translate-lang="export_csv">
Export CSV
</button>
<button class="clear-btn" id="clear-btn" data-translate-lang="clear_filters">
Clear Filters
</button>
</div> </div>
<div class="count-container"> <div class="count-container">
<span data-translate-lang="showing_nodes">Showing</span> Showing <span id="node-count">0</span> nodes
<span id="node-count">0</span>
<span data-translate-lang="nodes_suffix">nodes</span>
</div> </div>
<!-- Desktop table -->
<div id="node-list"> <div id="node-list">
<table> <table>
<thead> <thead>
<tr> <tr>
<th data-translate-lang="short_name">Short <span class="sort-icon"></span></th> <th>Short <span class="sort-icon"></span></th>
<th data-translate-lang="long_name">Long Name <span class="sort-icon"></span></th> <th>Long Name <span class="sort-icon"></span></th>
<th data-translate-lang="hw_model">HW Model <span class="sort-icon"></span></th> <th>HW Model <span class="sort-icon"></span></th>
<th data-translate-lang="firmware">Firmware <span class="sort-icon"></span></th> <th>Firmware <span class="sort-icon"></span></th>
<th data-translate-lang="role">Role <span class="sort-icon"></span></th> <th>Role <span class="sort-icon"></span></th>
<th data-translate-lang="last_lat">Last Latitude <span class="sort-icon"></span></th> <th>Last Latitude <span class="sort-icon"></span></th>
<th data-translate-lang="last_long">Last Longitude <span class="sort-icon"></span></th> <th>Last Longitude <span class="sort-icon"></span></th>
<th data-translate-lang="channel">Channel <span class="sort-icon"></span></th> <th>Channel <span class="sort-icon"></span></th>
<th data-translate-lang="last_seen">Last Seen <span class="sort-icon"></span></th> <th>Last Seen <span class="sort-icon"></span></th>
<th data-translate-lang="favorite"></th> <th> </th>
</tr> </tr>
</thead> </thead>
<tbody id="node-table-body"> <tbody id="node-table-body">
<tr> <tr><td colspan="10" style="text-align:center; color:white;">Loading nodes...</td></tr>
<td colspan="10" style="text-align:center; color:white;" data-translate-lang="loading_nodes">
Loading nodes...
</td>
</tr>
</tbody> </tbody>
</table> </table>
</div> </div>
<!-- Mobile Card View -->
<div id="mobile-node-list" style="display:none;"></div>
<script> <script>
// =====================================================
// TRANSLATIONS
// =====================================================
let nodelistTranslations = {};
function applyTranslationsNodelist() {
document.querySelectorAll("[data-translate-lang]").forEach(el => {
const key = el.dataset.translateLang;
if (nodelistTranslations[key]) {
if (el.tagName === "INPUT" && el.placeholder) {
el.placeholder = nodelistTranslations[key];
} else {
el.textContent = nodelistTranslations[key];
}
}
});
}
async function loadTranslationsNodelist() {
try {
const cfg = await window._siteConfigPromise;
const lang = cfg?.site?.language || "en";
const res = await fetch(`/api/lang?lang=${lang}&section=nodelist`);
nodelistTranslations = await res.json();
applyTranslationsNodelist();
} catch (err) {
console.error("Failed to load nodelist translations:", err);
}
}
// ===================================================== // =====================================================
// GLOBALS // GLOBALS
// ===================================================== // =====================================================
@@ -301,13 +171,18 @@ const keyMap = [
"last_lat","last_long","channel","last_seen_us" "last_lat","last_long","channel","last_seen_us"
]; ];
// =====================================================
// FAVORITES SYSTEM (localStorage)
// =====================================================
function getFavorites() { function getFavorites() {
const favorites = localStorage.getItem('nodelist_favorites'); const favorites = localStorage.getItem('nodelist_favorites');
return favorites ? JSON.parse(favorites) : []; return favorites ? JSON.parse(favorites) : [];
} }
function saveFavorites(favs) { function saveFavorites(favs) {
localStorage.setItem('nodelist_favorites', JSON.stringify(favs)); localStorage.setItem('nodelist_favorites', JSON.stringify(favs));
} }
function toggleFavorite(nodeId) { function toggleFavorite(nodeId) {
let favs = getFavorites(); let favs = getFavorites();
const idx = favs.indexOf(nodeId); const idx = favs.indexOf(nodeId);
@@ -315,10 +190,14 @@ function toggleFavorite(nodeId) {
else favs.push(nodeId); else favs.push(nodeId);
saveFavorites(favs); saveFavorites(favs);
} }
function isFavorite(nodeId) { function isFavorite(nodeId) {
return getFavorites().includes(nodeId); return getFavorites().includes(nodeId);
} }
// =====================================================
// "TIME AGO" FORMATTER
// =====================================================
function timeAgo(usTimestamp) { function timeAgo(usTimestamp) {
if (!usTimestamp) return "N/A"; if (!usTimestamp) return "N/A";
const ms = usTimestamp / 1000; const ms = usTimestamp / 1000;
@@ -330,19 +209,14 @@ function timeAgo(usTimestamp) {
const hrs = Math.floor(mins / 60); const hrs = Math.floor(mins / 60);
if (hrs < 24) return `${hrs} hr ago`; if (hrs < 24) return `${hrs} hr ago`;
const days = Math.floor(hrs / 24); const days = Math.floor(hrs / 24);
return `${days} days ago`; return `${days} day${days > 1 ? "s" : ""} ago`;
} }
// ===================================================== // =====================================================
// DOM LOADED // DOM LOADED: FETCH NODES
// ===================================================== // =====================================================
document.addEventListener("DOMContentLoaded", async function() { document.addEventListener("DOMContentLoaded", async function() {
await loadTranslationsNodelist();
const tbody = document.getElementById("node-table-body"); const tbody = document.getElementById("node-table-body");
const mobileList = document.getElementById("mobile-node-list");
const roleFilter = document.getElementById("role-filter"); const roleFilter = document.getElementById("role-filter");
const channelFilter = document.getElementById("channel-filter"); const channelFilter = document.getElementById("channel-filter");
const hwFilter = document.getElementById("hw-filter"); const hwFilter = document.getElementById("hw-filter");
@@ -358,19 +232,13 @@ document.addEventListener("DOMContentLoaded", async function() {
if (!res.ok) throw new Error("Failed to fetch nodes"); if (!res.ok) throw new Error("Failed to fetch nodes");
const data = await res.json(); const data = await res.json();
allNodes = data.nodes.map(n => ({ allNodes = data.nodes;
...n,
firmware: n.firmware || n.firmware_version || ""
}));
populateFilters(allNodes); populateFilters(allNodes);
renderTable(allNodes); renderTable(allNodes);
updateSortIcons(); updateSortIcons();
} catch (err) { } catch (err) {
tbody.innerHTML = `<tr> tbody.innerHTML = `<tr><td colspan="10" style="text-align:center; color:red;">Error loading nodes: ${err.message}</td></tr>`;
<td colspan="10" style="text-align:center; color:red;">
${nodelistTranslations.error_loading_nodes || "Error loading nodes"}
</td></tr>`;
} }
roleFilter.addEventListener("change", applyFilters); roleFilter.addEventListener("change", applyFilters);
@@ -382,8 +250,8 @@ document.addEventListener("DOMContentLoaded", async function() {
clearBtn.addEventListener("click", clearFilters); clearBtn.addEventListener("click", clearFilters);
favoritesBtn.addEventListener("click", toggleFavoritesFilter); favoritesBtn.addEventListener("click", toggleFavoritesFilter);
// Favorite star click handler // STAR CLICK HANDLER
document.addEventListener("click", e => { tbody.addEventListener("click", e => {
if (e.target.classList.contains('favorite-star')) { if (e.target.classList.contains('favorite-star')) {
const nodeId = parseInt(e.target.dataset.nodeId); const nodeId = parseInt(e.target.dataset.nodeId);
const isFav = isFavorite(nodeId); const isFav = isFavorite(nodeId);
@@ -396,10 +264,10 @@ document.addEventListener("DOMContentLoaded", async function() {
e.target.textContent = "★"; e.target.textContent = "★";
} }
toggleFavorite(nodeId); toggleFavorite(nodeId);
applyFilters();
} }
}); });
// SORTING
headers.forEach((th, index) => { headers.forEach((th, index) => {
th.addEventListener("click", () => { th.addEventListener("click", () => {
let key = keyMap[index]; let key = keyMap[index];
@@ -409,6 +277,9 @@ document.addEventListener("DOMContentLoaded", async function() {
}); });
}); });
// =====================================================
// FILTER POPULATION
// =====================================================
function populateFilters(nodes) { function populateFilters(nodes) {
const roles = new Set(), channels = new Set(), hws = new Set(), fws = new Set(); const roles = new Set(), channels = new Set(), hws = new Set(), fws = new Set();
@@ -434,15 +305,19 @@ document.addEventListener("DOMContentLoaded", async function() {
}); });
} }
// =====================================================
// FAVORITES FILTER
// =====================================================
function toggleFavoritesFilter() { function toggleFavoritesFilter() {
showOnlyFavorites = !showOnlyFavorites; showOnlyFavorites = !showOnlyFavorites;
favoritesBtn.textContent = showOnlyFavorites favoritesBtn.textContent = showOnlyFavorites ? "⭐ Show All" : "⭐ Show Favorites";
? "Show All"
: "⭐ Show Favorites";
favoritesBtn.classList.toggle("active", showOnlyFavorites); favoritesBtn.classList.toggle("active", showOnlyFavorites);
applyFilters(); applyFilters();
} }
// =====================================================
// APPLY FILTERS + SORT
// =====================================================
function applyFilters() { function applyFilters() {
const searchTerm = searchBox.value.trim().toLowerCase(); const searchTerm = searchBox.value.trim().toLowerCase();
@@ -451,7 +326,6 @@ document.addEventListener("DOMContentLoaded", async function() {
const channelMatch = !channelFilter.value || n.channel === channelFilter.value; const channelMatch = !channelFilter.value || n.channel === channelFilter.value;
const hwMatch = !hwFilter.value || n.hw_model === hwFilter.value; const hwMatch = !hwFilter.value || n.hw_model === hwFilter.value;
const fwMatch = !firmwareFilter.value || n.firmware === firmwareFilter.value; const fwMatch = !firmwareFilter.value || n.firmware === firmwareFilter.value;
const searchMatch = const searchMatch =
!searchTerm || !searchTerm ||
(n.long_name && n.long_name.toLowerCase().includes(searchTerm)) || (n.long_name && n.long_name.toLowerCase().includes(searchTerm)) ||
@@ -464,24 +338,19 @@ document.addEventListener("DOMContentLoaded", async function() {
}); });
filtered = sortNodes(filtered, sortColumn, sortAsc); filtered = sortNodes(filtered, sortColumn, sortAsc);
renderTable(filtered); renderTable(filtered);
updateSortIcons(); updateSortIcons();
} }
// =====================================================
// RENDER TABLE
// =====================================================
function renderTable(nodes) { function renderTable(nodes) {
tbody.innerHTML = ""; tbody.innerHTML = "";
mobileList.innerHTML = "";
const isMobile = window.innerWidth <= 768;
if (!nodes.length) { if (!nodes.length) {
tbody.innerHTML = `<tr> tbody.innerHTML = `<tr><td colspan="10" style="text-align:center; color:white;">No nodes found</td></tr>`;
<td colspan="10" style="text-align:center; color:white;">
${nodelistTranslations.no_nodes_found || "No nodes found"}
</td>
</tr>`;
mobileList.innerHTML = `<div style="text-align:center; color:white;">No nodes found</div>`;
countSpan.textContent = 0; countSpan.textContent = 0;
return; return;
} }
@@ -490,7 +359,6 @@ document.addEventListener("DOMContentLoaded", async function() {
const isFav = isFavorite(node.node_id); const isFav = isFavorite(node.node_id);
const star = isFav ? "★" : "☆"; const star = isFav ? "★" : "☆";
// DESKTOP TABLE ROW
const row = document.createElement("tr"); const row = document.createElement("tr");
row.innerHTML = ` row.innerHTML = `
<td>${node.short_name || "N/A"}</td> <td>${node.short_name || "N/A"}</td>
@@ -503,53 +371,18 @@ document.addEventListener("DOMContentLoaded", async function() {
<td>${node.channel || "N/A"}</td> <td>${node.channel || "N/A"}</td>
<td>${timeAgo(node.last_seen_us)}</td> <td>${timeAgo(node.last_seen_us)}</td>
<td style="text-align:center;"> <td style="text-align:center;">
<span class="favorite-star ${isFav ? "active" : ""}" data-node-id="${node.node_id}"> <span class="favorite-star ${isFav ? "active" : ""}" data-node-id="${node.node_id}">${star}</span>
${star}
</span>
</td> </td>
`; `;
tbody.appendChild(row); tbody.appendChild(row);
// MOBILE CARD VIEW
const card = document.createElement("div");
card.className = "node-card";
card.innerHTML = `
<div class="node-card-header">
<span>${node.short_name || node.long_name || node.node_id}</span>
<span class="favorite-star ${isFav ? "active" : ""}" data-node-id="${node.node_id}">
${star}
</span>
</div>
<div class="node-card-field"><b>ID:</b> ${node.node_id}</div>
<div class="node-card-field"><b>Name:</b> ${node.long_name || "N/A"}</div>
<div class="node-card-field"><b>HW:</b> ${node.hw_model || "N/A"}</div>
<div class="node-card-field"><b>Firmware:</b> ${node.firmware || "N/A"}</div>
<div class="node-card-field"><b>Role:</b> ${node.role || "N/A"}</div>
<div class="node-card-field"><b>Location:</b>
${node.last_lat ? (node.last_lat / 1e7).toFixed(5) : "N/A"},
${node.last_long ? (node.last_long / 1e7).toFixed(5) : "N/A"}
</div>
<div class="node-card-field"><b>Channel:</b> ${node.channel}</div>
<div class="node-card-field"><b>Last Seen:</b> ${timeAgo(node.last_seen_us)}</div>
<a href="/node/${node.node_id}" style="color:#9fd4ff; text-decoration:underline; margin-top:5px; display:block;">
View Node →
</a>
`;
mobileList.appendChild(card);
}); });
// Toggle correct view
if (isMobile) {
mobileList.style.display = "block";
} else {
mobileList.style.display = "none";
}
countSpan.textContent = nodes.length; countSpan.textContent = nodes.length;
} }
// =====================================================
// CLEAR FILTERS
// =====================================================
function clearFilters() { function clearFilters() {
roleFilter.value = ""; roleFilter.value = "";
channelFilter.value = ""; channelFilter.value = "";
@@ -559,7 +392,6 @@ document.addEventListener("DOMContentLoaded", async function() {
sortColumn = "short_name"; sortColumn = "short_name";
sortAsc = true; sortAsc = true;
showOnlyFavorites = false; showOnlyFavorites = false;
favoritesBtn.textContent = "⭐ Show Favorites"; favoritesBtn.textContent = "⭐ Show Favorites";
favoritesBtn.classList.remove("active"); favoritesBtn.classList.remove("active");
@@ -567,18 +399,17 @@ document.addEventListener("DOMContentLoaded", async function() {
updateSortIcons(); updateSortIcons();
} }
// =====================================================
// EXPORT CSV
// =====================================================
function exportToCSV() { function exportToCSV() {
const rows = []; const rows = [];
const headerList = Array.from(headers).map(h => const headerList = Array.from(headers).map(h => `"${h.innerText.replace(/▲|▼/g,'')}"`);
`"${h.innerText.replace(/▲|▼/g,'')}"`
);
rows.push(headerList.join(",")); rows.push(headerList.join(","));
const trs = tbody.querySelectorAll("tr"); const trs = tbody.querySelectorAll("tr");
trs.forEach(tr => { trs.forEach(tr => {
const cells = Array.from(tr.children).map(td => const cells = Array.from(tr.children).map(td => `"${td.innerText.replace(/"/g,'""')}"`);
`"${td.innerText.replace(/"/g,'""')}"`
);
rows.push(cells.join(",")); rows.push(cells.join(","));
}); });
@@ -589,11 +420,15 @@ document.addEventListener("DOMContentLoaded", async function() {
a.click(); a.click();
} }
// =====================================================
// SORT NODES
// =====================================================
function sortNodes(nodes, key, asc) { function sortNodes(nodes, key, asc) {
return [...nodes].sort((a, b) => { return [...nodes].sort((a, b) => {
let A = a[key]; let A = a[key];
let B = b[key]; let B = b[key];
// special handling for timestamp
if (key === "last_seen_us") { if (key === "last_seen_us") {
A = A || 0; A = A || 0;
B = B || 0; B = B || 0;
@@ -605,12 +440,14 @@ document.addEventListener("DOMContentLoaded", async function() {
}); });
} }
// =====================================================
// SORT ICONS
// =====================================================
function updateSortIcons() { function updateSortIcons() {
headers.forEach((th, i) => { headers.forEach((th, i) => {
const span = th.querySelector(".sort-icon"); const span = th.querySelector(".sort-icon");
if (!span) return; if (!span) return;
span.textContent = span.textContent = keyMap[i] === sortColumn ? (sortAsc ? "▲" : "▼") : "";
keyMap[i] === sortColumn ? (sortAsc ? "▲" : "▼") : "";
}); });
} }
}); });
+115 -140
View File
@@ -1,6 +1,6 @@
{% extends "base.html" %} {% extends "base.html" %}
{% block title %}Packet Details{% endblock %} {% block title %}Packet Details{%endblock%}
{% block css %} {% block css %}
{{ super() }} {{ super() }}
@@ -48,7 +48,7 @@
display: none; display: none;
} }
/* --- SOURCE MARKER --- */ /* --- SOURCE MARKER (slightly bigger) --- */
.source-marker { .source-marker {
width: 24px; width: 24px;
height: 24px; height: 24px;
@@ -97,27 +97,26 @@
{% block body %} {% block body %}
<div class="container mt-4 mb-5 packet-container"> <div class="container mt-4 mb-5 packet-container">
<div id="loading" data-translate-lang="loading">Loading packet information...</div> <div id="loading">Loading packet information...</div>
<div id="packet-card" class="packet-card d-none"></div> <div id="packet-card" class="packet-card d-none"></div>
<div id="map"></div> <div id="map"></div>
<div id="seen-container" class="mt-4 d-none"> <div id="seen-container" class="mt-4 d-none">
<h5 style="color:#ccc; margin:15px 0 10px 0;"> <h5 style="color:#ccc; margin:15px 0 10px 0;">
📡 <span data-translate-lang="seen_by">Seen By</span> 📡 Seen By <span id="seen-count" style="color:#4da6ff;"></span>
<span id="seen-count" style="color:#4da6ff;"></span>
</h5> </h5>
<div class="table-responsive"> <div class="table-responsive">
<table class="table table-dark table-sm seen-table"> <table class="table table-dark table-sm seen-table">
<thead> <thead>
<tr> <tr>
<th data-translate-lang="gateway">Gateway</th> <th>Gateway</th>
<th data-translate-lang="rssi">RSSI</th> <th>RSSI</th>
<th data-translate-lang="snr">SNR</th> <th>SNR</th>
<th data-translate-lang="hops">Hops</th> <th>Hop</th>
<th data-translate-lang="channel">Channel</th> <th>Channel</th>
<th data-translate-lang="time">Time</th> <th>Time</th>
</tr> </tr>
</thead> </thead>
<tbody id="seen-table-body"></tbody> <tbody id="seen-table-body"></tbody>
@@ -127,39 +126,8 @@
</div> </div>
<script> <script>
/* ======================================================
PACKET PAGE TRANSLATION
====================================================== */
let packetTranslations = {};
async function loadTranslationsPacket() {
try {
const cfg = await window._siteConfigPromise;
const lang = cfg?.site?.language || "en";
const res = await fetch(`/api/lang?lang=${lang}&section=packet`);
packetTranslations = await res.json();
applyTranslationsPacket(packetTranslations);
} catch (err) {
console.error("Packet translations failed:", err);
}
}
function applyTranslationsPacket(dict, root = document) {
root.querySelectorAll("[data-translate-lang]").forEach(el => {
const key = el.dataset.translateLang;
if (dict[key]) el.textContent = dict[key];
});
}
/* ======================================================
PACKET PAGE MAIN
====================================================== */
document.addEventListener("DOMContentLoaded", async () => { document.addEventListener("DOMContentLoaded", async () => {
await loadTranslationsPacket(); // <-- IMPORTANT
const packetCard = document.getElementById("packet-card"); const packetCard = document.getElementById("packet-card");
const loading = document.getElementById("loading"); const loading = document.getElementById("loading");
const mapDiv = document.getElementById("map"); const mapDiv = document.getElementById("map");
@@ -172,12 +140,12 @@ document.addEventListener("DOMContentLoaded", async () => {
----------------------------------------------*/ ----------------------------------------------*/
const match = window.location.pathname.match(/\/packet\/(\d+)/); const match = window.location.pathname.match(/\/packet\/(\d+)/);
if (!match) { if (!match) {
loading.textContent = packetTranslations.invalid_url || "Invalid packet URL"; loading.textContent = "Invalid packet URL";
return; return;
} }
const packetId = match[1]; const packetId = match[1];
/* PORT LABELS (NOT TRANSLATED) */ /* PORT NAME MAP */
const PORT_NAMES = { const PORT_NAMES = {
0:"UNKNOWN APP", 0:"UNKNOWN APP",
1:"Text", 1:"Text",
@@ -196,31 +164,28 @@ document.addEventListener("DOMContentLoaded", async () => {
const packetRes = await fetch(`/api/packets?packet_id=${packetId}`); const packetRes = await fetch(`/api/packets?packet_id=${packetId}`);
const packetData = await packetRes.json(); const packetData = await packetRes.json();
if (!packetData.packets.length) { if (!packetData.packets.length) {
loading.textContent = packetTranslations.not_found || "Packet not found."; loading.textContent = "Packet not found.";
return; return;
} }
const p = packetData.packets[0]; const p = packetData.packets[0];
/* --------------------------------------------- /* ---------------------------------------------
Load nodes for names & positions Fetch all nodes
----------------------------------------------*/ ----------------------------------------------*/
const nodesRes = await fetch("/api/nodes"); const nodesRes = await fetch("/api/nodes");
const nodesData = await nodesRes.json(); const nodesData = await nodesRes.json();
const nodeLookup = {}; const nodeLookup = {};
(nodesData.nodes || []).forEach(n => nodeLookup[n.node_id] = n); (nodesData.nodes || []).forEach(n => nodeLookup[n.node_id] = n);
const fromNodeObj = nodeLookup[p.from_node_id]; const fromNodeObj = nodeLookup[p.from_node_id];
const toNodeObj = nodeLookup[p.to_node_id]; const toNodeObj = nodeLookup[p.to_node_id];
const fromNodeLabel = fromNodeObj?.long_name || p.from_node_id; const fromNodeLabel = fromNodeObj?.long_name || p.from_node_id;
const toNodeLabel = const toNodeLabel =
p.to_node_id == 4294967295 p.to_node_id == 4294967295 ? "All" : (toNodeObj?.long_name || p.to_node_id);
? (packetTranslations.all_broadcast || "All")
: (toNodeObj?.long_name || p.to_node_id);
/* --------------------------------------------- /* ---------------------------------------------
Parse payload for lat/lon Parse payload for lat/lon if this *packet* is a position packet
----------------------------------------------*/ ----------------------------------------------*/
let lat = null, lon = null; let lat = null, lon = null;
const parsed = {}; const parsed = {};
@@ -230,14 +195,14 @@ document.addEventListener("DOMContentLoaded", async () => {
const [k, v] = line.split(":").map(x=>x.trim()); const [k, v] = line.split(":").map(x=>x.trim());
if (k && v !== undefined) { if (k && v !== undefined) {
parsed[k] = v; parsed[k] = v;
if (k === "latitude_i") lat = Number(v) / 1e7; if (k === "latitude_i") lat = Number(v) / 1e7;
if (k === "longitude_i") lon = Number(v) / 1e7; if (k === "longitude_i") lon = Number(v) / 1e7;
} }
}); });
} }
/* --------------------------------------------- /* ---------------------------------------------
Render card Render packet header & details
----------------------------------------------*/ ----------------------------------------------*/
const time = p.import_time_us const time = p.import_time_us
? new Date(p.import_time_us / 1000).toLocaleString() ? new Date(p.import_time_us / 1000).toLocaleString()
@@ -251,47 +216,42 @@ document.addEventListener("DOMContentLoaded", async () => {
packetCard.innerHTML = ` packetCard.innerHTML = `
<div class="card-header"> <div class="card-header">
<span> <span>Packet ID: <i>${p.id}</i></span>
<span data-translate-lang="packet_id_label">${packetTranslations.packet_id_label || "Packet ID:"}</span>
<i>${p.id}</i>
</span>
<small>${time}</small> <small>${time}</small>
</div> </div>
<div class="card-body"> <div class="card-body">
<dl> <dl>
<dt data-translate-lang="from_node">${packetTranslations.from_node || "From Node"}:</dt> <dt>From Node:</dt>
<dd><a href="/node/${p.from_node_id}">${fromNodeLabel}</a></dd> <dd><a href="/node/${p.from_node_id}">${fromNodeLabel}</a></dd>
<dt>To Node:</dt>
<dt data-translate-lang="to_node">${packetTranslations.to_node || "To Node"}:</dt>
<dd>${ <dd>${
p.to_node_id === 4294967295 p.to_node_id === 4294967295
? `<i data-translate-lang="all_broadcast">${packetTranslations.all_broadcast || "All"}</i>` ? `<i>All</i>`
: p.to_node_id === 1 : p.to_node_id === 1
? `<i data-translate-lang="direct_to_mqtt">${packetTranslations.direct_to_mqtt || "Direct to MQTT"}</i>` ? `<i>Direct to MQTT</i>`
: `<a href="/node/${p.to_node_id}">${toNodeLabel}</a>` : `<a href="/node/${p.to_node_id}">${toNodeLabel}</a>`
}</dd> }</dd>
<dt data-translate-lang="channel">${packetTranslations.channel || "Channel"}:</dt>
<dd>${p.channel ?? "—"}</dd>
<dt data-translate-lang="port">${packetTranslations.port || "Port"}:</dt> <dt>Channel:</dt><dd>${p.channel ?? "—"}</dd>
<dt>Port:</dt>
<dd><i>${PORT_NAMES[p.portnum] || "UNKNOWN APP"}</i> (${p.portnum})</dd> <dd><i>${PORT_NAMES[p.portnum] || "UNKNOWN APP"}</i> (${p.portnum})</dd>
<dt data-translate-lang="raw_payload">${packetTranslations.raw_payload || "From Raw Payload"}:</dt> <dt>Raw Payload:</dt>
<dd><pre>${escapeHtml(p.payload ?? "—")}</pre></dd> <dd><pre>${escapeHtml(p.payload ?? "—")}</pre></dd>
${ ${
telemetryExtras.length telemetryExtras.length
? `<dt data-translate-lang="decoded_telemetry">${packetTranslations.decoded_telemetry || "Decoded Telemetry"}</dt> ? `<dt>Decoded Telemetry</dt>
<dd><pre>${telemetryExtras.join("\n")}</pre></dd>` <dd><pre>${telemetryExtras.join("\n")}</pre></dd>`
: "" : ""
} }
${ ${
lat && lon lat && lon
? `<dt data-translate-lang="location">${packetTranslations.location || "Location:"}</dt> ? `<dt>Location:</dt><dd>${lat.toFixed(6)}, ${lon.toFixed(6)}</dd>`
<dd>${lat.toFixed(6)}, ${lon.toFixed(6)}</dd>`
: "" : ""
} }
</dl> </dl>
@@ -302,18 +262,22 @@ document.addEventListener("DOMContentLoaded", async () => {
packetCard.classList.remove("d-none"); packetCard.classList.remove("d-none");
/* --------------------------------------------- /* ---------------------------------------------
Map setup Map initialization
----------------------------------------------*/ ----------------------------------------------*/
const map = L.map("map"); const map = L.map("map");
mapDiv.style.display = "block"; mapDiv.style.display = "block";
L.tileLayer("https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png", { maxZoom: 19 }) L.tileLayer("https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png", {
.addTo(map); maxZoom: 19
}).addTo(map);
const allBounds = []; const allBounds = [];
/* --------------------------------------------- /* ---------------------------------------------
Determine packet source location ALWAYS SHOW SOURCE POSITION
Priority:
1) position from packet payload
2) fallback: last_lat/last_long from /api/nodes
----------------------------------------------*/ ----------------------------------------------*/
let srcLat = lat; let srcLat = lat;
let srcLon = lon; let srcLon = lon;
@@ -340,123 +304,135 @@ document.addEventListener("DOMContentLoaded", async () => {
sourceMarker.bindPopup(` sourceMarker.bindPopup(`
<div style="font-size:0.9em"> <div style="font-size:0.9em">
<b data-translate-lang="packet_source">${packetTranslations.packet_source || "Packet Source"}</b><br> <b>Packet Source</b><br>
Lat: ${srcLat.toFixed(6)}<br> Lat: ${srcLat.toFixed(6)}<br>
Lon: ${srcLon.toFixed(6)}<br> Lon: ${srcLon.toFixed(6)}<br>
<span data-translate-lang="from_node">${packetTranslations.from_node || "From Node:"}</span> ${fromNodeLabel}<br> From Node: ${fromNodeLabel}<br>
<span data-translate-lang="channel">${packetTranslations.channel || "Channel:"}</span> ${p.channel ?? "—"}<br> Channel: ${p.channel ?? "—"}<br>
<span data-translate-lang="port">${packetTranslations.port || "Port:"}</span> ${PORT_NAMES[p.portnum] || "UNKNOWN"} (${p.portnum}) Port: ${PORT_NAMES[p.portnum] || "UNKNOWN"} (${p.portnum})
</div> </div>
`); `);
} else { } else {
map.setView([0,0], 2); map.setView([0,0], 2);
} }
/* --------------------------------------------- /* ---------------------------------------------
Colors for hops (warm → cold) Color for hop indicator markers (warm → cold)
----------------------------------------------*/ ----------------------------------------------*/
function hopColor(hopValue){ function hopColor(hopValue){
const colors = [ const colors = [
"#ff3b30","#ff6b22","#ff9f0c","#ffd60a", "#ff3b30",
"#87d957","#57d9c4","#3db2ff","#1e63ff" "#ff6b22",
"#ff9f0c",
"#ffd60a",
"#87d957",
"#57d9c4",
"#3db2ff",
"#1e63ff"
]; ];
let h = Number(hopValue); let h = Number(hopValue);
if (isNaN(h)) return "#aaa"; if (isNaN(h)) return "#aaa";
return colors[Math.min(Math.max(h, 0), 7)]; if (h < 0) h = 0;
if (h > 7) h = 7;
return colors[h];
} }
/* --------------------------------------------- /* Distance helper */
Distance helper function haversine(lat1,lon1,lat2,lon2){
----------------------------------------------*/ const R=6371;
function haversine(lat1, lon1, lat2, lon2){ const dLat=(lat2-lat1)*Math.PI/180;
const R = 6371; const dLon=(lon2-lon1)*Math.PI/180;
const dLat = (lat2-lat1)*Math.PI/180; const a=Math.sin(dLat/2)**2+
const dLon = (lon2-lon1)*Math.PI/180;
const a = Math.sin(dLat/2)**2 +
Math.cos(lat1*Math.PI/180)* Math.cos(lat1*Math.PI/180)*
Math.cos(lat2*Math.PI/180)* Math.cos(lat2*Math.PI/180)*
Math.sin(dLon/2)**2; Math.sin(dLon/2)**2;
return R * (2*Math.atan2(Math.sqrt(a), Math.sqrt(1-a))); return R*(2*Math.atan2(Math.sqrt(a),Math.sqrt(1-a)));
} }
/* --------------------------------------------- /* ---------------------------------------------
Load packets_seen Fetch packets_seen
----------------------------------------------*/ ----------------------------------------------*/
const seenRes = await fetch(`/api/packets_seen/${packetId}`); const seenRes = await fetch(`/api/packets_seen/${packetId}`);
const seenData = await seenRes.json(); const seenData = await seenRes.json();
const seenList = seenData.seen ?? []; const seenList = seenData.seen ?? [];
/* sort by hop_start descending (warm → cold) */
const seenSorted = seenList.slice().sort((a,b)=>{ const seenSorted = seenList.slice().sort((a,b)=>{
return (b.hop_start ?? -999) - (a.hop_start ?? -999); const A=a.hop_start??-999;
const B=b.hop_start??-999;
return B-A;
}); });
if (seenSorted.length){ if (seenSorted.length){
seenContainer.classList.remove("d-none"); seenContainer.classList.remove("d-none");
seenCountSpan.textContent = `(${seenSorted.length})`; seenCountSpan.textContent=`(${seenSorted.length} gateways)`;
} }
/* --------------------------------------------- /* ---------------------------------------------
Render gateway table + map markers Gateway markers and seen table
----------------------------------------------*/ ----------------------------------------------*/
seenTableBody.innerHTML = seenSorted.map(s=>{ seenTableBody.innerHTML = seenSorted.map(s=>{
const node = nodeLookup[s.node_id]; const node=nodeLookup[s.node_id];
const label = node?.long_name || s.node_id; const label=node?(node.long_name||node.node_id):s.node_id;
const timeStr = s.import_time_us const timeStr = s.import_time_us
? new Date(s.import_time_us/1000).toLocaleTimeString() ? new Date(s.import_time_us/1000).toLocaleTimeString()
: "—"; : "—";
if (node?.last_lat && node.last_long){ if(node?.last_lat && node.last_long){
const rlat = node.last_lat/1e7; const rlat=node.last_lat/1e7;
const rlon = node.last_long/1e7; const rlon=node.last_long/1e7;
allBounds.push([rlat, rlon]); allBounds.push([rlat,rlon]);
const start = Number(s.hop_start ?? 0);
const limit = Number(s.hop_limit ?? 0);
const hopValue = start - limit;
const hopValue = (s.hop_start ?? 0) - (s.hop_limit ?? 0);
const color = hopColor(hopValue); const color = hopColor(hopValue);
const marker = L.marker([rlat,rlon],{ const iconHtml = `
icon: L.divIcon({ <div style="
html: ` background:${color};
<div style=" width:24px;
background:${color}; height:24px;
width:24px; height:24px; border-radius:50%;
border-radius:50%; display:flex;
display:flex; align-items:center;
align-items:center; justify-content:center;
justify-content:center; color:white;
color:white; font-size:11px;
font-size:11px; font-weight:700;
font-weight:700; border:2px solid rgba(0,0,0,0.35);
border:2px solid rgba(0,0,0,0.35); box-shadow:0 0 5px rgba(0,0,0,0.45);
box-shadow:0 0 5px rgba(0,0,0,0.45); ">${hopValue}</div>`;
">${hopValue}</div>`,
className: "", const marker=L.marker([rlat,rlon],{
icon:L.divIcon({
html:iconHtml,
className:"",
iconSize:[24,24], iconSize:[24,24],
iconAnchor:[12,12] iconAnchor:[12,12]
}) })
}).addTo(map); }).addTo(map);
let distKm = null, distMi = null; let distKm=null,distMi=null;
if (srcLat && srcLon){ if(srcLat&&srcLon){
distKm = haversine(srcLat, srcLon, rlat, rlon); distKm=haversine(srcLat,srcLon,rlat,rlon);
distMi = distKm * 0.621371; distMi=distKm*0.621371;
} }
marker.bindPopup(` marker.bindPopup(`
<div style="font-size:0.9em"> <div style="font-size:0.9em">
<b>${label}</b><br> <b>${node?.long_name || s.node_id}</b><br>
<span data-translate-lang="node_id_short">${packetTranslations.node_id_short || "Node ID"}</span>: Node ID: <a href="/node/${s.node_id}">${s.node_id}</a><br>
<a href="/node/${s.node_id}">${s.node_id}</a><br>
HW: ${node?.hw_model ?? "—"}<br> HW: ${node?.hw_model ?? "—"}<br>
<span data-translate-lang="channel">${packetTranslations.channel || "Channel"}</span>: ${s.channel ?? "—"}<br><br> Channel: ${s.channel ?? "—"}<br><br>
<b>Signal</b><br>
<b data-translate-lang="signal">${packetTranslations.signal || "Signal"}</b><br>
RSSI: ${s.rx_rssi ?? "—"}<br> RSSI: ${s.rx_rssi ?? "—"}<br>
SNR: ${s.rx_snr ?? "—"}<br><br> SNR: ${s.rx_snr ?? "—"}<br><br>
<b>Hops</b>: ${hopValue}<br>
<b data-translate-lang="hops">${packetTranslations.hops || "Hops"}</b>: ${hopValue}<br> <b>Distance</b><br>
<b data-translate-lang="distance">${packetTranslations.distance || "Distance"}:</b><br>
${ ${
distKm distKm
? `${distKm.toFixed(2)} km (${distMi.toFixed(2)} mi)` ? `${distKm.toFixed(2)} km (${distMi.toFixed(2)} mi)`
@@ -478,17 +454,17 @@ document.addEventListener("DOMContentLoaded", async () => {
}).join(""); }).join("");
/* --------------------------------------------- /* ---------------------------------------------
Fit map around all markers Fit map to all markers
----------------------------------------------*/ ----------------------------------------------*/
if (allBounds.length > 0){ if(allBounds.length>0){
map.fitBounds(allBounds, { padding:[40,40] }); map.fitBounds(allBounds,{padding:[40,40]});
} }
/* --------------------------------------------- /* ---------------------------------------------
Escape HTML helper Escape HTML
----------------------------------------------*/ ----------------------------------------------*/
function escapeHtml(unsafe) { function escapeHtml(unsafe) {
return (unsafe ?? "").replace(/[&<"'>]/g, m => ({ return (unsafe??"").replace(/[&<"'>]/g,m=>({
"&":"&amp;", "&":"&amp;",
"<":"&lt;", "<":"&lt;",
">":"&gt;", ">":"&gt;",
@@ -499,5 +475,4 @@ document.addEventListener("DOMContentLoaded", async () => {
}); });
</script> </script>
{% endblock %} {% endblock %}
+114 -139
View File
@@ -15,7 +15,7 @@
display: flex; display: flex;
flex-wrap: wrap; flex-wrap: wrap;
align-items: center; align-items: center;
gap: 20px; gap: 10px;
margin-bottom: 15px; margin-bottom: 15px;
} }
@@ -38,6 +38,14 @@
color: #ddd; color: #ddd;
} }
.filter-bar {
display: flex;
flex-direction: row;
align-items: center;
gap: 20px;
}
table th { background-color: #333; } table th { background-color: #333; }
table tbody tr:nth-child(odd) { background-color: #272b2f; } table tbody tr:nth-child(odd) { background-color: #272b2f; }
table tbody tr:nth-child(even) { background-color: #212529; } table tbody tr:nth-child(even) { background-color: #212529; }
@@ -57,42 +65,37 @@
{% block body %} {% block body %}
<h1 data-translate-lang="top_traffic_nodes">Top Nodes Traffic</h1> <h1>Top Nodes Traffic</h1>
<div class="top-container"> <div class="top-container">
<div class="filter-bar"> <div class="filter-bar">
<div> <div>
<label for="channelFilter" data-translate-lang="channel">Channel:</label> <label for="channelFilter">Channel:</label>
<select id="channelFilter" class="form-select form-select-sm" style="width:auto;"></select> <select id="channelFilter" class="form-select form-select-sm" style="width:auto;"></select>
</div>
<div>
<label for="nodeSearch" data-translate-lang="search">Search:</label>
<input id="nodeSearch" type="text" class="form-control form-control-sm"
placeholder="Search nodes..."
data-translate-lang="search_placeholder"
style="width:180px; display:inline-block;">
</div>
</div> </div>
<!-- ⭐ ADDED NODE COUNT ⭐ --> <div>
<div id="count-container" style="margin-bottom:10px; font-weight:bold;"> <label for="nodeSearch">Search:</label>
<span data-translate-lang="showing_nodes">Showing</span> <input id="nodeSearch" type="text" class="form-control form-control-sm"
<span id="node-count">0</span> placeholder="Search nodes..."
<span data-translate-lang="nodes_suffix">nodes</span> style="width:180px; display:inline-block;">
</div> </div>
</div>
<div class="table-responsive"> <div class="table-responsive">
<table id="nodesTable"> <table id="nodesTable">
<thead> <thead>
<tr> <tr>
<th data-translate-lang="long_name">Long Name</th> <th>Long Name</th>
<th data-translate-lang="short_name">Short Name</th> <th>Short Name</th>
<th data-translate-lang="channel">Channel</th> <th>Channel</th>
<th data-translate-lang="packets_sent">Sent (24h)</th> <th>Sent (24h)</th>
<th data-translate-lang="times_seen">Seen (24h)</th> <th>Seen (24h)</th>
<th data-translate-lang="avg_gateways">Avg Gateways</th> <th>Avg Gateways</th>
</tr> </tr>
</thead> </thead>
<tbody></tbody> <tbody></tbody>
@@ -102,105 +105,74 @@
</div> </div>
<script> <script>
/* ====================================================== let allNodes = [];
TOP PAGE TRANSLATION (isolated from base)
====================================================== */
let topTranslations = {};
function applyTranslationsTop(dict, root=document) { async function loadChannels() {
root.querySelectorAll("[data-translate-lang]").forEach(el => { try {
const key = el.dataset.translateLang; const res = await fetch("/api/channels");
if (!dict[key]) return; const data = await res.json();
const channels = data.channels || [];
// input placeholder support const select = document.getElementById("channelFilter");
if (el.tagName === "INPUT" && el.placeholder !== undefined) {
el.placeholder = dict[key]; // Default LongFast first
} else { if (channels.includes("LongFast")) {
el.textContent = dict[key]; const opt = document.createElement("option");
opt.value = "LongFast";
opt.textContent = "LongFast";
select.appendChild(opt);
}
for (const ch of channels) {
if (ch === "LongFast") continue;
const opt = document.createElement("option");
opt.value = ch;
opt.textContent = ch;
select.appendChild(opt);
}
select.addEventListener("change", renderTable);
} catch (err) {
console.error("Error loading channels:", err);
} }
});
}
async function loadTranslationsTop() {
try {
const cfg = await window._siteConfigPromise;
const lang = cfg?.site?.language || "en";
const res = await fetch(`/api/lang?lang=${lang}&section=top`);
topTranslations = await res.json();
applyTranslationsTop(topTranslations);
} catch (err) {
console.error("TOP translation load failed:", err);
} }
}
/* ====================================================== async function loadNodes() {
PAGE LOGIC try {
====================================================== */ const res = await fetch("/api/nodes");
let allNodes = []; const data = await res.json();
allNodes = data.nodes || [];
async function loadChannels() { } catch (err) {
try { console.error("Error loading nodes:", err);
const res = await fetch("/api/channels");
const data = await res.json();
const channels = data.channels || [];
const select = document.getElementById("channelFilter");
// LongFast first
if (channels.includes("LongFast")) {
const opt = document.createElement("option");
opt.value = "LongFast";
opt.textContent = "LongFast";
select.appendChild(opt);
} }
}
for (const ch of channels) { async function fetchNodeStats(nodeId) {
if (ch === "LongFast") continue; try {
const opt = document.createElement("option"); const url = `/api/stats/count?from_node=${nodeId}&period_type=day&length=1`;
opt.value = ch; const res = await fetch(url);
opt.textContent = ch; const data = await res.json();
select.appendChild(opt);
const sent = data.total_packets || 0;
const seen = data.total_seen || 0;
const avg = seen / Math.max(sent, 1);
return {
sent,
seen,
avg: avg
};
} catch (err) {
console.error("Stat error", err);
return { sent: 0, seen: 0, avg: 0 };
} }
select.addEventListener("change", renderTable);
} catch (err) {
console.error("Error loading channels:", err);
} }
}
async function loadNodes() { function avgClass(v) {
try { if (v >= 10) return "good-x"; // Very strong node
const res = await fetch("/api/nodes"); if (v >= 2) return "ok-x"; // Normal node
const data = await res.json(); return "bad-x"; // Weak node
allNodes = data.nodes || [];
} catch (err) {
console.error("Error loading nodes:", err);
} }
}
async function fetchNodeStats(nodeId) {
try {
const res = await fetch(`/api/stats/count?from_node=${nodeId}&period_type=day&length=1`);
const data = await res.json();
const sent = data.total_packets || 0;
const seen = data.total_seen || 0;
const avg = seen / Math.max(sent, 1);
return { sent, seen, avg };
} catch (err) {
console.error("Stat error:", err);
return { sent: 0, seen: 0, avg: 0 };
}
}
function avgClass(v) {
if (v >= 10) return "good-x";
if (v >= 2) return "ok-x";
return "bad-x";
}
async function renderTable() { async function renderTable() {
const tbody = document.querySelector("#nodesTable tbody"); const tbody = document.querySelector("#nodesTable tbody");
@@ -209,10 +181,10 @@ async function renderTable() {
const channel = document.getElementById("channelFilter").value; const channel = document.getElementById("channelFilter").value;
const searchText = document.getElementById("nodeSearch").value.trim().toLowerCase(); const searchText = document.getElementById("nodeSearch").value.trim().toLowerCase();
// Filter by channel // Filter nodes by channel FIRST
let filtered = allNodes.filter(n => n.channel === channel); let filtered = allNodes.filter(n => n.channel === channel);
// Filter by search // Then apply search
if (searchText !== "") { if (searchText !== "") {
filtered = filtered.filter(n => filtered = filtered.filter(n =>
(n.long_name && n.long_name.toLowerCase().includes(searchText)) || (n.long_name && n.long_name.toLowerCase().includes(searchText)) ||
@@ -221,10 +193,12 @@ async function renderTable() {
); );
} }
// Placeholder rows first // --- Create placeholder rows ---
const rowRefs = filtered.map(n => { const rowRefs = filtered.map(n => {
const tr = document.createElement("tr"); const tr = document.createElement("tr");
tr.addEventListener("click", () => window.location.href = `/node/${n.node_id}`); tr.addEventListener("click", () => {
window.location.href = `/node/${n.node_id}`;
});
const tdLong = document.createElement("td"); const tdLong = document.createElement("td");
const a = document.createElement("a"); const a = document.createElement("a");
@@ -241,13 +215,13 @@ async function renderTable() {
tdChannel.textContent = n.channel || ""; tdChannel.textContent = n.channel || "";
const tdSent = document.createElement("td"); const tdSent = document.createElement("td");
tdSent.textContent = "..."; tdSent.textContent = "Loading...";
const tdSeen = document.createElement("td"); const tdSeen = document.createElement("td");
tdSeen.textContent = "..."; tdSeen.textContent = "Loading...";
const tdAvg = document.createElement("td"); const tdAvg = document.createElement("td");
tdAvg.textContent = "..."; tdAvg.textContent = "Loading...";
tr.appendChild(tdLong); tr.appendChild(tdLong);
tr.appendChild(tdShort); tr.appendChild(tdShort);
@@ -261,49 +235,50 @@ async function renderTable() {
return { node: n, tr, tdSent, tdSeen, tdAvg }; return { node: n, tr, tdSent, tdSeen, tdAvg };
}); });
// Fetch stats // --- Stats fetch ---
const statsList = await Promise.all( const statsList = await Promise.all(
rowRefs.map(ref => fetchNodeStats(ref.node.node_id)) rowRefs.map(ref => fetchNodeStats(ref.node.node_id))
); );
// Update rows // --- Update + cleanup empty nodes ---
let combined = rowRefs.map((ref, i) => { let combined = rowRefs.map((ref, i) => {
const stats = statsList[i]; const stats = statsList[i];
ref.tdSent.textContent = stats.sent; ref.tdSent.textContent = stats.sent;
ref.tdSeen.textContent = stats.seen; ref.tdSeen.textContent = stats.seen;
ref.tdAvg.innerHTML = ref.tdAvg.innerHTML = `<span class="${avgClass(stats.avg)}">${stats.avg.toFixed(1)}</span>`;
`<span class="${avgClass(stats.avg)}">${stats.avg.toFixed(1)}</span>`;
return { tr: ref.tr, sent: stats.sent, seen: stats.seen }; return {
tr: ref.tr,
sent: stats.sent,
seen: stats.seen
};
}); });
// Remove nodes with no activity // Remove nodes with no traffic
combined = combined.filter(r => !(r.sent === 0 && r.seen === 0)); combined = combined.filter(r => !(r.sent === 0 && r.seen === 0));
// Sort by seen // Sort by traffic (seen)
combined.sort((a, b) => b.seen - a.seen); combined.sort((a, b) => b.seen - a.seen);
// Rebuild table
tbody.innerHTML = ""; tbody.innerHTML = "";
for (const r of combined) tbody.appendChild(r.tr); for (const r of combined) {
tbody.appendChild(r.tr);
// ⭐ UPDATE COUNT ⭐ }
document.getElementById("node-count").textContent = combined.length;
} }
/* ======================================================
INITIALIZE PAGE
====================================================== */ (async () => {
document.addEventListener("DOMContentLoaded", async () => {
await loadTranslationsTop(); // ⭐ MUST run first
await loadNodes(); await loadNodes();
await loadChannels(); await loadChannels();
document.getElementById("channelFilter").value = "LongFast"; document.getElementById("channelFilter").value = "LongFast";
document.getElementById("nodeSearch").addEventListener("input", renderTable); document.getElementById("nodeSearch").addEventListener("input", renderTable);
renderTable(); renderTable();
}); })();
</script> </script>
{% endblock %} {% endblock %}
+26 -18
View File
@@ -12,7 +12,7 @@ from google.protobuf import text_format
from google.protobuf.message import Message from google.protobuf.message import Message
from jinja2 import Environment, PackageLoader, Undefined, select_autoescape from jinja2 import Environment, PackageLoader, Undefined, select_autoescape
from markupsafe import Markup from markupsafe import Markup
import pathlib
from meshtastic.protobuf.portnums_pb2 import PortNum from meshtastic.protobuf.portnums_pb2 import PortNum
from meshview import config, database, decode_payload, migrations, models, store from meshview import config, database, decode_payload, migrations, models, store
from meshview.__version__ import ( from meshview.__version__ import (
@@ -200,23 +200,6 @@ async def redirect_packet_list(request):
packet_id = request.match_info["packet_id"] packet_id = request.match_info["packet_id"]
raise web.HTTPFound(location=f"/node/{packet_id}") raise web.HTTPFound(location=f"/node/{packet_id}")
# Generic static HTML route
@routes.get("/{page}")
async def serve_page(request):
page = request.match_info["page"]
# default to index.html if no extension
if not page.endswith(".html"):
page = f"{page}.html"
html_file = pathlib.Path(__file__).parent / "static" / page
if not html_file.exists():
raise web.HTTPNotFound(text=f"Page '{page}' not found")
content = html_file.read_text(encoding="utf-8")
return web.Response(text=content, content_type="text/html")
@routes.get("/net") @routes.get("/net")
async def net(request): async def net(request):
@@ -411,6 +394,31 @@ async def graph_traceroute(request):
) )
'''
@routes.get("/stats")
async def stats(request):
try:
total_packets = await store.get_total_packet_count()
total_nodes = await store.get_total_node_count()
total_packets_seen = await store.get_total_packet_seen_count()
template = env.get_template("stats.html")
return web.Response(
text=template.render(
total_packets=total_packets,
total_nodes=total_nodes,
total_packets_seen=total_packets_seen,
),
content_type="text/html",
)
except Exception as e:
return web.Response(
text=f"An error occurred: {str(e)}",
status=500,
content_type="text/plain",
)
'''
async def run_server(): async def run_server():
# Wait for database migrations to complete before starting web server # Wait for database migrations to complete before starting web server
logger.info("Checking database schema status...") logger.info("Checking database schema status...")
+15 -49
View File
@@ -48,7 +48,6 @@ async def api_channels(request: web.Request):
async def api_nodes(request): async def api_nodes(request):
try: try:
# Optional query parameters # Optional query parameters
node_id = request.query.get("node_id")
role = request.query.get("role") role = request.query.get("role")
channel = request.query.get("channel") channel = request.query.get("channel")
hw_model = request.query.get("hw_model") hw_model = request.query.get("hw_model")
@@ -62,7 +61,7 @@ async def api_nodes(request):
# Fetch nodes from database # Fetch nodes from database
nodes = await store.get_nodes( nodes = await store.get_nodes(
node_id=node_id, role=role, channel=channel, hw_model=hw_model, days_active=days_active role=role, channel=channel, hw_model=hw_model, days_active=days_active
) )
# Prepare the JSON response # Prepare the JSON response
@@ -215,7 +214,6 @@ async def api_packets(request):
"portnum": int(p.portnum), "portnum": int(p.portnum),
"long_name": getattr(p.from_node, "long_name", ""), "long_name": getattr(p.from_node, "long_name", ""),
"payload": (p.payload or "").strip(), "payload": (p.payload or "").strip(),
"to_long_name": getattr(p.to_node, "long_name", ""),
} }
reply_id = getattr( reply_id = getattr(
@@ -424,76 +422,44 @@ async def api_edges(request):
since = datetime.datetime.now() - datetime.timedelta(hours=48) since = datetime.datetime.now() - datetime.timedelta(hours=48)
filter_type = request.query.get("type") filter_type = request.query.get("type")
# NEW → optional single-node filter
node_filter_str = request.query.get("node_id")
node_filter = None
if node_filter_str:
try:
node_filter = int(node_filter_str)
except ValueError:
return web.json_response(
{"error": "node_id must be integer"},
status=400
)
edges = {} edges = {}
traceroute_count = 0
neighbor_packet_count = 0
edges_added_tr = 0
edges_added_neighbor = 0
# --- Traceroute edges --- # Only build traceroute edges if requested
if filter_type in (None, "traceroute"): if filter_type in (None, "traceroute"):
async for tr in store.get_traceroutes(since): async for tr in store.get_traceroutes(since):
traceroute_count += 1
try: try:
route = decode_payload.decode_payload(PortNum.TRACEROUTE_APP, tr.route) route = decode_payload.decode_payload(PortNum.TRACEROUTE_APP, tr.route)
except Exception: except Exception as e:
logger.error(f"Error decoding Traceroute {tr.id}: {e}")
continue continue
path = [tr.packet.from_node_id] + list(route.route) path = [tr.packet.from_node_id] + list(route.route)
path.append(tr.packet.to_node_id if tr.done else tr.gateway_node_id) path.append(tr.packet.to_node_id if tr.done else tr.gateway_node_id)
for a, b in zip(path, path[1:], strict=False): for a, b in zip(path, path[1:], strict=False):
if (a, b) not in edges: edges[(a, b)] = "traceroute"
edges[(a, b)] = "traceroute"
edges_added_tr += 1
# --- Neighbor edges --- # Only build neighbor edges if requested
if filter_type in (None, "neighbor"): if filter_type in (None, "neighbor"):
packets = await store.get_packets(portnum=71) packets = await store.get_packets(portnum=PortNum.NEIGHBORINFO_APP, after=since)
neighbor_packet_count = len(packets)
for packet in packets: for packet in packets:
try: try:
_, neighbor_info = decode_payload.decode(packet) _, neighbor_info = decode_payload.decode(packet)
except Exception: for node in neighbor_info.neighbors:
continue edges.setdefault((node.node_id, packet.from_node_id), "neighbor")
except Exception as e:
logger.error(
f"Error decoding NeighborInfo packet {getattr(packet, 'id', '?')}: {e}"
)
for node in neighbor_info.neighbors: # Convert edges dict to list format for JSON response
edge = (node.node_id, packet.from_node_id)
if edge not in edges:
edges[edge] = "neighbor"
edges_added_neighbor += 1
# Convert to list
edges_list = [ edges_list = [
{"from": frm, "to": to, "type": edge_type} {"from": frm, "to": to, "type": edge_type} for (frm, to), edge_type in edges.items()
for (frm, to), edge_type in edges.items()
] ]
# NEW → apply node_id filtering
if node_filter is not None:
edges_list = [
e for e in edges_list
if e["from"] == node_filter or e["to"] == node_filter
]
return web.json_response({"edges": edges_list}) return web.json_response({"edges": edges_list})
@routes.get("/api/config") @routes.get("/api/config")
async def api_config(request): async def api_config(request):
try: try: