├── LICENSE ├── README.md ├── decode_url.py └── nodeSlurp.sh /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 Kelly 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # node-slurper 2 | Meshtastic bulk node management tool, run from linux command line 3 | 4 | $ ./nodeSlurp.sh 5 | 6 | ``` 7 | o o o 8 | |\ | | 9 | | \ | o-o o-O o-o 10 | | \| | | | | |-' 11 | o o o-o o-o o-o 12 | 13 | o-o o 14 | | | 15 | o-o | o o o-o o-o o-o o-o 16 | | | | | | | | |-' | 17 | o--o o o--o o O-o o-o o 18 | | 19 | o 20 | 21 | slurping node... 22 | 23 | node data slurped for node 425675309 named "DEMO" 24 | channel keys: 25 | "psk": "ABC123=", "name": "Banter", "channelNum": 0, "id": 0, "uplinkEnabled": false, "downlinkEnabled" 26 | "psk": "ABC123=", "name": "Chat", "channelNum": 0, "id": 0, "uplinkEnabled": false, "downlinkEnabled" 27 | fixed pin: 123456 28 | wifi password: password 29 | mqtt password: lpassword 30 | admin key: ABC123= 31 | dm private key: ABC123= 32 | dm public key: ABC123= 33 | node data saved to 425675309-Info.txt and 425675309.yaml 34 | ``` 35 | -------------------------------------------------------------------------------- /decode_url.py: -------------------------------------------------------------------------------- 1 | # URLs are of the form https://meshtastic.org/d/#{base64_channel_set} 2 | # base64_channel_set is a base64 encoded string that contains the channel settings 3 | # The channel settings are a protobuf message that contains the channel name, key, and other settings 4 | import base64 5 | from urllib.parse import urlparse 6 | import json 7 | 8 | try: 9 | from meshtastic.protobuf import apponly_pb2 # pip install meshtastic 10 | MESHTASTIC_AVAILABLE = True 11 | except ImportError: 12 | print("Meshtastic protobuf library not found. pip install meshtastic to enable protobuf decoding") 13 | MESHTASTIC_AVAILABLE = False 14 | 15 | def decode_mesh_url(url): 16 | # Parse the URL to get the fragment 17 | parsed_url = urlparse(url) 18 | b64 = parsed_url.fragment 19 | 20 | print(f"Extracted base64 string: {b64}") 21 | 22 | # Add back any missing padding 23 | missing_padding = len(b64) % 4 24 | if missing_padding: 25 | b64 += "=" * (4 - missing_padding) 26 | 27 | print(f"Base64 string with padding: {b64}") 28 | 29 | # Decode the base64 string 30 | try: 31 | decoded_bytes = base64.urlsafe_b64decode(b64) 32 | 33 | if MESHTASTIC_AVAILABLE: 34 | # Parse the protobuf message 35 | try: 36 | channel_set = apponly_pb2.ChannelSet.FromString(decoded_bytes) 37 | print(channel_set) 38 | except Exception as e: 39 | print(f"Error parsing protobuf: {e}") 40 | else: 41 | # Try to decode as JSON 42 | try: 43 | print("Meshtastic protobuf library not found. Decoding as JSON") 44 | json_data = json.loads(decoded_bytes.decode('utf-8')) 45 | print(json.dumps(json_data, indent=2)) 46 | except json.JSONDecodeError as e: 47 | print(f"Error decoding JSON: {e}") 48 | print(f"Binary data: {decoded_bytes}") 49 | except Exception as e: 50 | print(f"Error decoding base64 string: {e}") 51 | 52 | if __name__ == "__main__": 53 | url = input("Enter URL: ") 54 | decode_mesh_url(url) 55 | -------------------------------------------------------------------------------- /nodeSlurp.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # node-slurper.sh 3 | # 2024 Kelly Keeton K7MHI - MIT License 4 | # Tool uses meshtastic CLI methods to slurp node data from a meshtastic device 5 | 6 | # User defined variables 7 | injectKeys=true 8 | shutdown=true 9 | 10 | function injectData() { 11 | # inject commands into the node update the following commands with your data 12 | # uncomment and add your keys to the end of line 13 | echo "" 14 | echo "attempting to inject commands into the node, please wait..." 15 | echo "set lora profile" 16 | #meshtastic --set lora.role client_mute 17 | #sleep 1 18 | #meshtastic --set lora.usepreset true 19 | #meshtastic --set lora.profile LongFast 20 | #sleep 1 21 | #meshtastic --set lora.overrideFrequency 915000000 22 | #sleep 1 23 | #meshtastic --set lora.channel 20 24 | #sleep 1 25 | echo "set mqtt" 26 | #meshtastic --set mqtt.enabled true 27 | #sleep 1 28 | #meshtastic --set mqtt.server mqtt.example.com 29 | #sleep 1 30 | #meshtastic --set mqtt.user mqttuser 31 | #sleep 1 32 | #meshtastic --set mqtt.password mqttPass 33 | #sleep 1 34 | echo "set bluetooth" 35 | #meshtastic --set bluetooth.enabled true 36 | #sleep 1 37 | #meshtastic --set bluetooth.pin fixedPin 38 | #sleep 1 39 | echo "setting channel" 40 | #meshtastic --ch-add MeshAround --ch-set psk base64:Do/Th3/MeSh/ArOuNd/Now== 41 | #sleep 1 42 | meshtastic --ch-set module_settings.position_precision 32 --ch-index 0 43 | sleep 1 44 | echo "adding security keys" 45 | #meshtastic --set security.admin_key base64: 46 | #sleep 1 47 | #meshtastic --ch-add admin --ch-set psk base64: 48 | #sleep 1 49 | echo "enable radio" 50 | #meshtastic --set lora.region US 51 | 52 | if [ "$shutdown" = true ]; then 53 | meshtastic --shutdown 54 | echo "done, with commands - powering off node" 55 | fi 56 | echo "" 57 | echo "waiting for new device or press ctrl-c to exit" 58 | } 59 | 60 | # end of user defined variables 61 | 62 | cwd=$(pwd) 63 | 64 | function slurpData() { 65 | # no device detected 66 | if grep -q "No Serial Meshtastic device detected" $cwd/node-info.txt; then 67 | echo "plug a device in via USB" 68 | else 69 | # parse the node-info.txt for node number 70 | nodeNum=$(grep -o '"myNodeNum": [0-9]*' $cwd/node-info.txt| grep -o '[0-9]*') 71 | 72 | #move the node-info.txt to nodeNum-Info.txt 73 | mv $cwd/node-info.txt $cwd/$nodeNum-Info.txt 74 | 75 | #move the node.yaml to nodeNum.yaml 76 | mv $cwd/node.yaml $cwd/$nodeNum.yaml 77 | 78 | # channel keys string 79 | channelKeys=$(grep -o '"psk": ".*", "name": ".*"' $cwd/$nodeNum-Info.txt | grep -o '"psk": ".*"') 80 | 81 | # parse the owner from the node.yaml 82 | owner=$(grep -o 'owner: ".*"' $cwd/$nodeNum.yaml | grep -o '".*"') 83 | 84 | # lora profile 85 | channelNum=$(grep -o 'channelNum: [0-9]*' $cwd/$nodeNum.yaml | grep -o '[0-9]*') 86 | overrideFrequency=$(grep -o 'overrideFrequency: [0-9]*' $cwd/$nodeNum.yaml | grep -o '[0-9]*') 87 | modemPreset=$(grep -o 'modemPreset: .*' $cwd/$nodeNum.yaml | grep -o ' .*') 88 | role=$(grep -o 'role: .*' $cwd/$nodeNum.yaml | grep -o ' .*') 89 | 90 | # MQTT password 91 | mqttPass=$(grep -o 'password: .*' $cwd/$nodeNum.yaml | grep -o ' .*') 92 | 93 | # bluetooth pin 94 | fixedPin=$(grep -o 'fixedPin: [0-9]*' $cwd/$nodeNum.yaml | grep -o '[0-9]*') 95 | 96 | # wifi password 97 | wifiPsk=$(grep -o 'wifiPsk: .*' $cwd/$nodeNum.yaml | grep -o ' .*') 98 | 99 | # admin key string 100 | adminKey=$(grep -o ' \-.*' $cwd/$nodeNum.yaml | grep -o ' .*') 101 | 102 | # dm private key 103 | dmPrivateKey=$(grep -o 'privateKey: .*' $cwd/$nodeNum.yaml | grep -o ' .*') 104 | 105 | # dm public key 106 | dmPublicKey=$(grep -o 'publicKey: .*' $cwd/$nodeNum.yaml | grep -o ' .*') 107 | 108 | # display the important node data to stdout 109 | echo "node data slurped for node $nodeNum named $owner" 110 | echo "channel keys:" 111 | echo "$channelKeys" 112 | echo "fixed pin:$fixedPin" 113 | echo "wifi password:$wifiPsk" 114 | echo "mqtt password:$mqttPass" 115 | echo "admin key:$adminKey" 116 | echo "dm private key:$dmPrivateKey" 117 | echo "dm public key:$dmPublicKey" 118 | printf "lora profile: $modemPreset $role channel $channelNum freq $overrideFrequency" 119 | echo "node data saved to $nodeNum-Info.txt and $nodeNum.yaml" 120 | echo "" 121 | if [ "$injectKeys" = false ]; then 122 | echo "waiting for new device or press ctrl-c to exit" 123 | fi 124 | fi 125 | } 126 | 127 | function saveData() { 128 | # collect node-info.txt 129 | meshtastic --info > $cwd/node-info.txt 130 | # collect node.yaml 131 | meshtastic --export-config > $cwd/node.yaml 132 | 133 | # evaluate the node-info.txt for meshtastic version 134 | if grep -q "pip install --upgrade meshtastic" $cwd/node-info.txt; then 135 | echo "meshtastic tools need to be updated run one of the following commands" 136 | echo "pip install --upgrade meshtastic" 137 | echo "pip install --upgrade meshtastic --break-system-packages" 138 | fi 139 | 140 | # no device detected 141 | if grep -q "No Serial Meshtastic device detected" $cwd/node-info.txt; then 142 | echo "No device detected" 143 | else 144 | # parse the node-info.txt for node number 145 | nodeNum=$(grep -o '"myNodeNum": [0-9]*' $cwd/node-info.txt | grep -o '[0-9]*') 146 | echo "node $nodeNum detected" 147 | fi 148 | } 149 | 150 | # start of main script 151 | 152 | echo " " 153 | echo " o o o " 154 | echo " |\ | | " 155 | echo " | \ | o-o o-O o-o " 156 | echo " | \| | | | | |-' " 157 | echo " o o o-o o-o o-o " 158 | echo " " 159 | echo " o-o o " 160 | echo "| | " 161 | echo " o-o | o o o-o o-o o-o o-o " 162 | echo " | | | | | | | |-' | " 163 | echo "o--o o o--o o O-o o-o o " 164 | echo " | " 165 | echo " o " 166 | echo " " 167 | echo " waiting for node to connect " 168 | echo " " 169 | echo " spudgunman24" 170 | echo " " 171 | 172 | # look for meshtastic has a bin in the path 173 | if whereis meshtastic | grep -q bin/meshtastic; then 174 | 175 | # Assume a device is connected already 176 | echo " trying to slurp, could take a moment..." 177 | # collect node-info.txt and node.yaml 178 | saveData 179 | 180 | # slurp the data 181 | slurpData 182 | 183 | if [ "$injectKeys" = true ]; then 184 | injectData 185 | fi 186 | 187 | # detect new device loop for any subsequent devices 188 | while true; do 189 | 190 | # detect new USB serial device 191 | ls /dev/tty* > $cwd/.nodeSlurp.before 192 | sleep 2 193 | ls /dev/tty* > $cwd/.nodeSlurp.after 194 | 195 | if diff $cwd/.nodeSlurp.before $cwd/.nodeSlurp.after; then 196 | sleep 1 197 | else 198 | echo "new device detected trying to slurp, could take a moment..." 199 | echo "" 200 | 201 | # collect node-info.txt and node.yaml 202 | saveData 203 | 204 | # slurp the data 205 | slurpData 206 | 207 | if [ "$injectKeys" = true ]; then 208 | injectData 209 | fi 210 | fi 211 | done 212 | else 213 | # if it is not found 214 | echo "meshtastic not found" 215 | echo "sudo apt-get install -y python3-pip" 216 | echo "pip install -U meshtastic" 217 | echo "(or) pip install -U meshtastic --break-system-packages" 218 | exit 1 219 | fi 220 | 221 | exit 0 222 | # end of script 223 | --------------------------------------------------------------------------------