Meshtastic Python CLI checks for an upgrade each time it is run
I recently started experimenting with several Meshtastic nodes based on the popular SX1262 radio and ESP32 microcontroller. If you don't want to use your phone to control them over the bluetooth connection you can use WiFi or serial-via-USB. To do this you use the meshtastic python library which is also a command line tool.
One common function is to query the entire status of the Meshtastic node. You can do this by running meshtastic --info
or in my case meshtastic --tcp 192.168.162.160 --info
since my node is available over my local network. This displays a dump of all the configuration and state of the node. Here's an example of what that looks like
$ meshtastic --tcp 192.168.162.160 --info Connected to radio Owner: hydrogen18fixed (h18f) My info: { "myNodeNum": 1658397140, "rebootCount": 44, "minAppVersion": 30200, "deviceId": "wzonC4GmNlc0H7J/5Zjsgg==", "pioEnv": "tlora-t3s3-v1", "firmwareEdition": "VANILLA", "nodedbCount": 0 }
When I started experimenting I just grabbed the latest Meshtastic version and flashed it to my nodes, as well as the latest Meshtastic python tool. What puzzled me was sometimes the --info
command seemed to take extremely long to run. I figured this might be some issue with communicating with the node but it was hard to reproduce. Yesterday I noticed this little message after my expected output
*** A newer version v2.7.3 is available! Consider running "pip install --upgrade meshtastic" ***
The command line tool for an off-grid mesh networking software package somehow knows it has an update available. How is that possible? Well, Meshtastic is an open source package so we can check the source code. In the file meshtastic/__main__.py
from the linked repository we can find this
# originally from meshtastic/__main__.py line 980 if args.info: print("") # If we aren't trying to talk to our local node, don't show it if args.dest == BROADCAST_ADDR: interface.showInfo() print("") interface.getNode(args.dest, **getNode_kwargs).showInfo() closeNow = True print("") pypi_version = meshtastic.util.check_if_newer_version() if pypi_version: print( f"*** A newer version v{pypi_version} is available!" ' Consider running "pip install --upgrade meshtastic" ***\n' )
The statement if args.info
checks if the --info
command is being requested by the user. Sure enough it calls meshtastic.util.check_if_newer_version()
. So let's look at that function
# originally from meshtastic/util.py line 659 def check_if_newer_version() -> Optional[str]: """Check pip to see if we are running the latest version.""" pypi_version: Optional[str] = None try: url: str = "https://pypi.org/pypi/meshtastic/json" data = requests.get(url, timeout=5).json() pypi_version = data["info"]["version"] except Exception: pass act_version = get_active_version() if pypi_version is None: return None try: parsed_act_version = pkg_version.parse(act_version) parsed_pypi_version = pkg_version.parse(pypi_version) #Note: if handed "None" when we can't download the pypi_version, #this gets a TypeError: #"TypeError: expected string or bytes-like object, got 'NoneType'" #Handle that below? except pkg_version.InvalidVersion: return pypi_version if parsed_pypi_version <= parsed_act_version: return None return pypi_version
This function sends an HTTP GET request using the python requests
module to the URL "https://pypi.org/pypi/meshtastic/json"
. It then parses that as JSON and does some kind of version check. I was curious to see what this endpoint returns so I just downloaded it myself using curl
$ curl "https://pypi.org/pypi/meshtastic/json" > meshtastic_version.json % Total % Received % Xferd Average Speed Time Time Time Current Dload Upload Total Spent Left Speed 100 380k 100 380k 0 0 2479k 0 --:--:-- --:--:-- --:--:-- 2489k $ ls -l meshtastic_version.json -rw-rw-r-- 1 ericu ericu 390049 Sep 19 08:02 meshtastic_version.json
So this endpoint returns 380.9 kilobytes of data on each request, just to check for a new version. I wanted to make sure I wasn't misunderstanding this code so I blocked internet connectivity from my local machine and ran the command again
$ time meshtastic --tcp 192.168.162.160 --info Connected to radio Owner: hydrogen18fixed (h18f) ...SNIP... Channels: Index 0: PRIMARY psk=default { "psk": "AQ==", "uplinkEnabled": true, "downlinkEnabled": true, "moduleSettings": { "positionPrecision": 13, "isClientMuted": false }, "channelNum": 0, "name": "", "id": 0 } Primary channel URL: https://meshtastic.org/e/#CgsSAQEoATABOgIIDRIMCAE4AUADSAFQHmgB real 0m16.186s user 0m0.292s sys 0m0.036s
This time the version upgrade message does not appear! It also takes 16 seconds to run. Most of which most time the tool is just sitting there after having already printed all the data I asked for. So at this point what we have is an open-source off grid messaging software package that includes a mandatory version check on each invocation of the --info
command. It then proceeds to download 380.9 kilobytes of data just to check if the latest version is newer than the current one. There is no way to disable this. It gets worse however because the python requests
module is used to download this data. This means if you installed into a Python virtual environment, you downloaded the requests
module as part of the install. After searching through the code this appears to be the only usage of that module. I think this is one of the best examples of how not implementing a feature could improve the product function.