mirror of
https://github.com/jkingsman/Remote-Terminal-for-MeshCore.git
synced 2026-03-28 17:43:05 +01:00
More code cleanup and optimization
This commit is contained in:
10
app/radio.py
10
app/radio.py
@@ -2,7 +2,7 @@ import asyncio
|
||||
import glob
|
||||
import logging
|
||||
import platform
|
||||
from contextlib import asynccontextmanager
|
||||
from contextlib import asynccontextmanager, nullcontext
|
||||
from pathlib import Path
|
||||
|
||||
from meshcore import MeshCore
|
||||
@@ -24,12 +24,6 @@ class RadioDisconnectedError(RadioOperationError):
|
||||
"""Raised when the radio disconnects between pre-check and lock acquisition."""
|
||||
|
||||
|
||||
@asynccontextmanager
|
||||
async def _noop_context():
|
||||
"""No-op async context manager for optional nesting."""
|
||||
yield
|
||||
|
||||
|
||||
def detect_serial_devices() -> list[str]:
|
||||
"""Detect available serial devices based on platform."""
|
||||
devices: list[str] = []
|
||||
@@ -196,7 +190,7 @@ class RadioManager:
|
||||
self._release_operation_lock(name)
|
||||
raise RadioDisconnectedError("Radio disconnected")
|
||||
|
||||
poll_context = _noop_context()
|
||||
poll_context = nullcontext()
|
||||
if pause_polling:
|
||||
from app.radio_sync import pause_polling as pause_polling_context
|
||||
|
||||
|
||||
@@ -59,16 +59,14 @@ class ContactRepository:
|
||||
""",
|
||||
(
|
||||
contact.get("public_key", "").lower(),
|
||||
contact.get("name") or contact.get("adv_name"),
|
||||
contact.get("name"),
|
||||
contact.get("type", 0),
|
||||
contact.get("flags", 0),
|
||||
contact.get("last_path") or contact.get("out_path"),
|
||||
contact.get("last_path_len")
|
||||
if "last_path_len" in contact
|
||||
else contact.get("out_path_len", -1),
|
||||
contact.get("last_path"),
|
||||
contact.get("last_path_len", -1),
|
||||
contact.get("last_advert"),
|
||||
contact.get("lat") if contact.get("lat") is not None else contact.get("adv_lat"),
|
||||
contact.get("lon") if contact.get("lon") is not None else contact.get("adv_lon"),
|
||||
contact.get("lat"),
|
||||
contact.get("lon"),
|
||||
contact.get("last_seen", int(time.time())),
|
||||
contact.get("on_radio", False),
|
||||
contact.get("last_contacted"),
|
||||
|
||||
@@ -177,6 +177,33 @@ async def send_advertisement() -> dict:
|
||||
return {"status": "ok"}
|
||||
|
||||
|
||||
async def _attempt_reconnect() -> dict:
|
||||
"""Shared reconnection logic for reboot and reconnect endpoints."""
|
||||
if radio_manager.is_reconnecting:
|
||||
return {
|
||||
"status": "pending",
|
||||
"message": "Reconnection already in progress",
|
||||
"connected": False,
|
||||
}
|
||||
|
||||
success = await radio_manager.reconnect()
|
||||
if not success:
|
||||
raise HTTPException(
|
||||
status_code=503, detail="Failed to reconnect. Check radio connection and power."
|
||||
)
|
||||
|
||||
try:
|
||||
await radio_manager.post_connect_setup()
|
||||
except Exception as e:
|
||||
logger.exception("Post-connect setup failed after reconnect")
|
||||
raise HTTPException(
|
||||
status_code=503,
|
||||
detail=f"Radio connected but setup failed: {e}",
|
||||
) from e
|
||||
|
||||
return {"status": "ok", "message": "Reconnected successfully", "connected": True}
|
||||
|
||||
|
||||
@router.post("/reboot")
|
||||
async def reboot_radio() -> dict:
|
||||
"""Reboot the radio, or reconnect if not currently connected.
|
||||
@@ -184,7 +211,6 @@ async def reboot_radio() -> dict:
|
||||
If connected: sends reboot command, connection will temporarily drop and auto-reconnect.
|
||||
If not connected: attempts to reconnect (same as /reconnect endpoint).
|
||||
"""
|
||||
# If connected, send reboot command
|
||||
if radio_manager.is_connected:
|
||||
logger.info("Rebooting radio")
|
||||
async with radio_manager.radio_operation("reboot_radio") as mc:
|
||||
@@ -194,32 +220,8 @@ async def reboot_radio() -> dict:
|
||||
"message": "Reboot command sent. Radio will reconnect automatically.",
|
||||
}
|
||||
|
||||
# Not connected - attempt to reconnect
|
||||
if radio_manager.is_reconnecting:
|
||||
return {
|
||||
"status": "pending",
|
||||
"message": "Reconnection already in progress",
|
||||
"connected": False,
|
||||
}
|
||||
|
||||
logger.info("Radio not connected, attempting reconnect")
|
||||
success = await radio_manager.reconnect()
|
||||
|
||||
if success:
|
||||
try:
|
||||
await radio_manager.post_connect_setup()
|
||||
except Exception as e:
|
||||
logger.exception("Post-connect setup failed after reconnect")
|
||||
raise HTTPException(
|
||||
status_code=503,
|
||||
detail=f"Radio connected but setup failed: {e}",
|
||||
) from e
|
||||
|
||||
return {"status": "ok", "message": "Reconnected successfully", "connected": True}
|
||||
else:
|
||||
raise HTTPException(
|
||||
status_code=503, detail="Failed to reconnect. Check radio connection and power."
|
||||
)
|
||||
return await _attempt_reconnect()
|
||||
|
||||
|
||||
@router.post("/reconnect")
|
||||
@@ -234,7 +236,6 @@ async def reconnect_radio() -> dict:
|
||||
if radio_manager.is_setup_complete:
|
||||
return {"status": "ok", "message": "Already connected", "connected": True}
|
||||
|
||||
# Connected but setup incomplete — retry setup
|
||||
logger.info("Radio connected but setup incomplete, retrying setup")
|
||||
try:
|
||||
await radio_manager.post_connect_setup()
|
||||
@@ -246,28 +247,5 @@ async def reconnect_radio() -> dict:
|
||||
detail=f"Radio connected but setup failed: {e}",
|
||||
) from e
|
||||
|
||||
if radio_manager.is_reconnecting:
|
||||
return {
|
||||
"status": "pending",
|
||||
"message": "Reconnection already in progress",
|
||||
"connected": False,
|
||||
}
|
||||
|
||||
logger.info("Manual reconnect requested")
|
||||
success = await radio_manager.reconnect()
|
||||
|
||||
if success:
|
||||
try:
|
||||
await radio_manager.post_connect_setup()
|
||||
except Exception as e:
|
||||
logger.exception("Post-connect setup failed after reconnect")
|
||||
raise HTTPException(
|
||||
status_code=503,
|
||||
detail=f"Radio connected but setup failed: {e}",
|
||||
) from e
|
||||
|
||||
return {"status": "ok", "message": "Reconnected successfully", "connected": True}
|
||||
else:
|
||||
raise HTTPException(
|
||||
status_code=503, detail="Failed to reconnect. Check radio connection and power."
|
||||
)
|
||||
return await _attempt_reconnect()
|
||||
|
||||
@@ -6,7 +6,7 @@ import {
|
||||
type Conversation,
|
||||
type Favorite,
|
||||
} from '../types';
|
||||
import { getStateKey, type ConversationTimes } from '../utils/conversationState';
|
||||
import { getStateKey, type ConversationTimes, type SortOrder } from '../utils/conversationState';
|
||||
import { getContactDisplayName } from '../utils/pubkey';
|
||||
import { ContactAvatar } from './ContactAvatar';
|
||||
import { isFavorite } from '../utils/favorites';
|
||||
@@ -14,8 +14,6 @@ import { Input } from './ui/input';
|
||||
import { Button } from './ui/button';
|
||||
import { cn } from '@/lib/utils';
|
||||
|
||||
type SortOrder = 'alpha' | 'recent';
|
||||
|
||||
type FavoriteItem = { type: 'channel'; channel: Channel } | { type: 'contact'; contact: Contact };
|
||||
|
||||
type ConversationRow = {
|
||||
|
||||
@@ -32,12 +32,6 @@ body,
|
||||
}
|
||||
|
||||
body {
|
||||
font-family:
|
||||
system-ui,
|
||||
-apple-system,
|
||||
BlinkMacSystemFont,
|
||||
'Segoe UI',
|
||||
sans-serif;
|
||||
/* Prevent overscroll/bounce on mobile */
|
||||
overscroll-behavior: none;
|
||||
padding: var(--safe-area-top) var(--safe-area-right) var(--safe-area-bottom-capped)
|
||||
@@ -50,13 +44,6 @@ body {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
/* Fallback for browsers without dvh support */
|
||||
@supports not (height: 1dvh) {
|
||||
.h-dvh {
|
||||
height: 100vh;
|
||||
}
|
||||
}
|
||||
|
||||
/* Mobile sidebar override - ensures sidebar fills Sheet container */
|
||||
[data-state] .sidebar {
|
||||
width: 100%;
|
||||
|
||||
@@ -15,8 +15,8 @@ const REPEATER_AVATAR = {
|
||||
textColor: '#ffffff',
|
||||
};
|
||||
|
||||
// Simple hash function for strings
|
||||
function hashString(str: string): number {
|
||||
// DJB2 hash function for strings
|
||||
export function hashString(str: string): number {
|
||||
let hash = 0;
|
||||
for (let i = 0; i < str.length; i++) {
|
||||
const char = str.charCodeAt(i);
|
||||
|
||||
@@ -13,7 +13,7 @@ const LAST_MESSAGE_KEY = 'remoteterm-lastMessageTime';
|
||||
const SORT_ORDER_KEY = 'remoteterm-sortOrder';
|
||||
|
||||
export type ConversationTimes = Record<string, number>;
|
||||
type SortOrder = 'recent' | 'alpha';
|
||||
export type SortOrder = 'recent' | 'alpha';
|
||||
|
||||
// In-memory cache of last message times (loaded from server on init)
|
||||
let lastMessageTimesCache: ConversationTimes = {};
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { MeshCoreDecoder, PayloadType } from '@michaelhart/meshcore-decoder';
|
||||
import { CONTACT_TYPE_REPEATER, type Contact, type RawPacket } from '../types';
|
||||
import { hashString } from './contactAvatar';
|
||||
|
||||
// =============================================================================
|
||||
// TYPES
|
||||
@@ -114,15 +115,6 @@ export const PACKET_LEGEND_ITEMS = [
|
||||
// UTILITY FUNCTIONS (Data Layer)
|
||||
// =============================================================================
|
||||
|
||||
function simpleHash(str: string): string {
|
||||
let hash = 0;
|
||||
for (let i = 0; i < str.length; i++) {
|
||||
hash = (hash << 5) - hash + str.charCodeAt(i);
|
||||
hash = hash & hash;
|
||||
}
|
||||
return Math.abs(hash).toString(16).padStart(8, '0');
|
||||
}
|
||||
|
||||
export function parsePacket(hexData: string): ParsedPacket | null {
|
||||
try {
|
||||
const decoded = MeshCoreDecoder.decode(hexData);
|
||||
@@ -182,7 +174,7 @@ export function getPacketLabel(payloadType: number): PacketLabel {
|
||||
}
|
||||
|
||||
export function generatePacketKey(parsed: ParsedPacket, rawPacket: RawPacket): string {
|
||||
const contentHash = (parsed.messageHash || simpleHash(rawPacket.data)).slice(0, 8);
|
||||
const contentHash = (parsed.messageHash || hashString(rawPacket.data).toString(16).padStart(8, '0')).slice(0, 8);
|
||||
|
||||
if (parsed.payloadType === PayloadType.Advert && parsed.advertPubkey) {
|
||||
return `ad:${parsed.advertPubkey.slice(0, 12)}`;
|
||||
|
||||
Reference in New Issue
Block a user