Created
September 12, 2025 15:33
-
-
Save j0uni/0efd26dbf9fac1dc92bb57fdbb993434 to your computer and use it in GitHub Desktop.
Meshtastic local + remote admin location lat/lon set with Python
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| import meshtastic | |
| from meshtastic.serial_interface import SerialInterface | |
| import time | |
| # Connect to a local Meshtastic device (e.g., via serial/USB) | |
| iface = SerialInterface() | |
| # Replace with the target node ID you want to configure | |
| target_node = "!" + "1309E2A0" | |
| # Example coordinates (latitude, longitude, altitude) | |
| latitude = 61.49 | |
| longitude = 23.85 | |
| altitude = 100 # meters | |
| # Configuration options | |
| SKIP_LOCAL_DEVICE = True # Set to True to skip local device, focus only on remote | |
| REMOTE_ONLY_MODE = True # Set to True for remote admin only | |
| try: | |
| if REMOTE_ONLY_MODE: | |
| print("=== REMOTE ADMIN MODE - SETTING REMOTE DEVICE ONLY ===") | |
| else: | |
| print("=== FIXING DEVICE POSITION ===") | |
| print(f"Target coordinates: {latitude}, {longitude}, {altitude}m") | |
| print(f"Remote device target: {target_node}") | |
| if not SKIP_LOCAL_DEVICE: | |
| # Step 0: EMERGENCY - Clear any corrupted position first | |
| print("\n0. Clearing any corrupted position data...") | |
| try: | |
| iface.localNode.removeFixedPosition() | |
| print("✅ Cleared existing fixed position") | |
| time.sleep(2) | |
| except Exception as e: | |
| print(f"⚠️ Could not clear position: {e}") | |
| # Step 1: Get current local position | |
| print("\n1. Getting current local position...") | |
| local_node_info = iface.getMyNodeInfo() | |
| if local_node_info and 'position' in local_node_info and local_node_info['position']: | |
| initial_lat = local_node_info['position']['latitude'] | |
| initial_lon = local_node_info['position']['longitude'] | |
| initial_alt = local_node_info['position']['altitude'] | |
| print(f"Initial local position: {initial_lat}, {initial_lon}, {initial_alt}") | |
| # Check for invalid coordinates | |
| if initial_lat > 90 or initial_lat < -90 or initial_lon > 180 or initial_lon < -180: | |
| print("❌ INVALID COORDINATES DETECTED - Will fix immediately!") | |
| elif initial_lat == 0.0 and initial_lon == 0.0: | |
| print("❌ LOCAL DEVICE IS AT 0,0 - Will fix this!") | |
| else: | |
| print("✅ Local device has valid position (will update to target coordinates)") | |
| else: | |
| print("❌ No initial position found on local device") | |
| # Step 2: Set LOCAL device fixed position using the correct API | |
| print(f"\n2. Setting LOCAL device (serial connected) fixed position...") | |
| try: | |
| # WORKAROUND: The library has a bug - it divides by 1e-7 instead of multiplying by 1e7 | |
| # So we need to pass pre-converted integer values to avoid the bug | |
| # Convert to the correct integer format ourselves | |
| lat_i = int(latitude * 1e7) # Correct conversion | |
| lon_i = int(longitude * 1e7) # Correct conversion | |
| alt_i = int(altitude) | |
| print(f" Working around library bug - using integer coordinates:") | |
| print(f" lat_i: {lat_i}, lon_i: {lon_i}, alt: {alt_i}") | |
| # Pass integers to avoid the buggy float conversion in the library | |
| iface.localNode.setFixedPosition(lat=lat_i, lon=lon_i, alt=alt_i) | |
| print(f"✅ LOCAL device fixed position set to {latitude}, {longitude}, {altitude}") | |
| # Write the configuration to make it persistent | |
| try: | |
| iface.localNode.writeConfig('position') | |
| print("✅ Configuration written to device") | |
| except Exception as config_error: | |
| print(f"⚠️ Configuration write error: {config_error}") | |
| print("✅ Fixed position was set but may not be persistent") | |
| # Force send position_app message to broadcast the new position | |
| print("\n 📡 Force broadcasting position via position_app...") | |
| try: | |
| position_data = f"POSITION_UPDATE:LAT:{latitude:.6f},LON:{longitude:.6f},ALT:{altitude}" | |
| position_bytes = position_data.encode('utf-8') | |
| iface.sendData( | |
| data=position_bytes, | |
| destinationId='^all', # Broadcast to all nodes | |
| portNum=meshtastic.portnums_pb2.POSITION_APP, | |
| wantAck=False | |
| ) | |
| print(f" ✅ Position broadcast sent: {position_data}") | |
| # Also send a direct position packet | |
| iface.sendPosition( | |
| latitude=latitude, | |
| longitude=longitude, | |
| altitude=altitude, | |
| destinationId='^all' # Broadcast to all | |
| ) | |
| print(f" ✅ Direct position packet broadcast") | |
| except Exception as broadcast_error: | |
| print(f" ⚠️ Position broadcast error: {broadcast_error}") | |
| except Exception as e: | |
| print(f"❌ Error setting local device fixed position: {e}") | |
| else: | |
| print("\n🔄 SKIPPING LOCAL DEVICE (SKIP_LOCAL_DEVICE = True)") | |
| print(" Focusing only on remote admin device...") | |
| # Step 3: FOCUS ON REMOTE DEVICE FIXED POSITION | |
| print(f"\n3. Setting REMOTE device {target_node} fixed position...") | |
| # Use lowercase node ID as found in mesh | |
| remote_target = target_node.lower() | |
| print(f" Using node ID: {remote_target}") | |
| try: | |
| # CORRECT METHOD: Use getNode().setFixedPosition() as per library design | |
| print(" 📡 Using correct library method: getNode().setFixedPosition()...") | |
| # Get the remote node object (this handles admin channel setup) | |
| remote_node = iface.getNode(remote_target, requestChannels=False, timeout=10) | |
| if remote_node: | |
| print(f" ✅ Successfully connected to remote node {remote_target}") | |
| # Use the same workaround for the library bug (pass integers) | |
| lat_i = int(latitude * 1e7) # Correct conversion to avoid library bug | |
| lon_i = int(longitude * 1e7) # Correct conversion to avoid library bug | |
| alt_i = int(altitude) | |
| print(f" 📍 Setting remote fixed position with coordinates:") | |
| print(f" lat_i: {lat_i}, lon_i: {lon_i}, alt: {alt_i}") | |
| # Call setFixedPosition on the remote node (library handles admin messages) | |
| remote_node.setFixedPosition(lat=lat_i, lon=lon_i, alt=alt_i) | |
| print(f" ✅ REMOTE FIXED POSITION SET!") | |
| print(f" Library automatically sent admin message to {remote_target}") | |
| else: | |
| print(f" ❌ Could not connect to remote node {remote_target}") | |
| print(" Device may be offline or not accepting admin connections") | |
| except Exception as e: | |
| print(f" ❌ Error setting remote fixed position: {e}") | |
| print(" This could mean:") | |
| print(" - Remote device is not reachable") | |
| print(" - Admin channel not configured") | |
| print(" - Device doesn't accept remote admin commands") | |
| # Step 4: Wait for remote device to process commands | |
| print(f"\n4. Waiting for remote device to process admin commands...") | |
| time.sleep(15) # Give more time for remote processing | |
| # Step 5: Verify positions | |
| print(f"\n=== VERIFYING POSITIONS ===") | |
| if not SKIP_LOCAL_DEVICE: | |
| # Check LOCAL device position | |
| print(f"\n5. Checking LOCAL device (serial connected) position...") | |
| try: | |
| updated_local_info = iface.getMyNodeInfo() | |
| if updated_local_info and 'position' in updated_local_info and updated_local_info['position']: | |
| received_lat = updated_local_info['position']['latitude'] | |
| received_lon = updated_local_info['position']['longitude'] | |
| received_alt = updated_local_info['position']['altitude'] | |
| print(f"Updated local device position:") | |
| print(f" Latitude: {received_lat}") | |
| print(f" Longitude: {received_lon}") | |
| print(f" Altitude: {received_alt}") | |
| # Check if it's still at 0,0 | |
| if received_lat == 0.0 and received_lon == 0.0: | |
| print("❌ LOCAL DEVICE STILL AT 0,0 - Fixed position setting failed!") | |
| else: | |
| # Compare with target values | |
| lat_diff = abs(latitude - received_lat) | |
| lon_diff = abs(longitude - received_lon) | |
| alt_diff = abs(altitude - received_alt) | |
| print(f"\nPosition comparison:") | |
| print(f" Latitude difference: {lat_diff:.7f}") | |
| print(f" Longitude difference: {lon_diff:.7f}") | |
| print(f" Altitude difference: {alt_diff}") | |
| # Check if positions match (within reasonable tolerance) | |
| tolerance = 0.0001 # About 10 meters | |
| if lat_diff < tolerance and lon_diff < tolerance and alt_diff < 50: | |
| print("✅ LOCAL DEVICE: Fixed position set correctly!") | |
| else: | |
| print("⚠️ LOCAL DEVICE: Position updated but doesn't match target exactly") | |
| if lat_diff < 0.001 and lon_diff < 0.001: # Within 100m | |
| print(" (Still within reasonable GPS accuracy)") | |
| else: | |
| print("❌ No position information available for local device") | |
| except Exception as e: | |
| print(f"Error getting updated local node info: {e}") | |
| else: | |
| print("\n🔄 SKIPPED LOCAL DEVICE VERIFICATION") | |
| # Check REMOTE device position - using mesh nodes directly | |
| print(f"\n6. Checking REMOTE device {target_node} position...") | |
| remote_target = target_node.lower() | |
| try: | |
| # Check mesh nodes for remote device | |
| nodes = iface.nodes | |
| print(f" Checking mesh nodes for {remote_target}...") | |
| if remote_target in nodes: | |
| node_data = nodes[remote_target] | |
| print(f" ✅ Found remote device in mesh") | |
| if 'position' in node_data and node_data['position']: | |
| pos = node_data['position'] | |
| remote_lat = pos.get('latitude', 0) | |
| remote_lon = pos.get('longitude', 0) | |
| remote_alt = pos.get('altitude', 0) | |
| print(f" Remote device position:") | |
| print(f" Latitude: {remote_lat}") | |
| print(f" Longitude: {remote_lon}") | |
| print(f" Altitude: {remote_alt}") | |
| # Check if it's still at 0,0 | |
| if remote_lat == 0.0 and remote_lon == 0.0: | |
| print(" ❌ REMOTE DEVICE STILL AT 0,0!") | |
| print(" Admin commands were not processed by remote device") | |
| print(" Remote device may require manual configuration") | |
| else: | |
| # Compare with target values | |
| lat_diff = abs(latitude - remote_lat) | |
| lon_diff = abs(longitude - remote_lon) | |
| alt_diff = abs(altitude - remote_alt) | |
| print(f" Position comparison:") | |
| print(f" Latitude difference: {lat_diff:.7f}") | |
| print(f" Longitude difference: {lon_diff:.7f}") | |
| print(f" Altitude difference: {alt_diff}") | |
| # Check if positions match | |
| tolerance = 0.001 # About 100 meters tolerance | |
| if lat_diff < tolerance and lon_diff < tolerance: | |
| print(" ✅ SUCCESS! REMOTE DEVICE: Fixed position set correctly!") | |
| else: | |
| print(" ⚠️ REMOTE DEVICE: Position changed but doesn't match target exactly") | |
| if lat_diff < 0.01 and lon_diff < 0.01: # Within ~1km | |
| print(" (Position is in reasonable range)") | |
| else: | |
| print(" ❌ No position data available for remote device") | |
| print(f" Node data: {node_data}") | |
| else: | |
| print(f" ❌ Remote device {remote_target} not found in mesh") | |
| print(f" Available nodes: {list(nodes.keys())}") | |
| except Exception as e: | |
| print(f" ❌ Error checking remote device: {e}") | |
| print(f"\n=== SUMMARY ===") | |
| print("✅ Script completed successfully") | |
| print("📍 Local device fixed position should now be set correctly") | |
| print("📡 Position command sent to remote device") | |
| print("⚠️ Remote device may require manual configuration if command wasn't processed") | |
| except Exception as e: | |
| print(f"❌ Error: {e}") | |
| finally: | |
| # Close interface | |
| iface.close() | |
| print("\n🔌 Connection closed") |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment