Skip to content
Merged

Dev #76

Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 6 additions & 6 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,9 @@ build-backend = "setuptools.build_meta"

[project]
name = "pymc_core"
version = "1.0.11"
version = "1.0.12"
authors = [
{name = "Lloyd Newton", email = "lloyd@rightup.co.uk"},
{name = "Rightup", email = "rightup@pymc.dev"},
]
description = "A Python MeshCore library with SPI LoRa radio support"
readme = "README.md"
Expand Down Expand Up @@ -66,10 +66,10 @@ docs = [
all = ["pymc_core[hardware,websocket,dev,docs]"]

[project.urls]
Homepage = "https://github.com/rightup/pyMC_core"
Documentation = "https://rightup.github.io/rightup/pyMC_core"
Repository = "https://github.com/rightup/pyMC_core.git"
Issues = "https://github.com/rightup/pyMC_core/issues"
Homepage = "https://github.com/pyMC-dev/pyMC_core/"
Documentation = "https://rightup.github.io/pymc_dev/pyMC_core"
Repository = "https://github.com/pyMC-dev/pyMC_core.git"
Issues = "https://github.com/pyMC-dev/pyMC_core/issues"

[tool.setuptools]
package-dir = {"" = "src"}
Expand Down
2 changes: 1 addition & 1 deletion src/pymc_core/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
Clean, simple API for building mesh network applications.
"""

__version__ = "1.0.11"
__version__ = "1.0.12"

# Core mesh functionality
from .node.node import MeshNode
Expand Down
23 changes: 21 additions & 2 deletions src/pymc_core/hardware/tcp_radio.py
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,8 @@ def __init__(
# Stats
self._tx_count = 0
self._rx_count = 0
self._crc_errors = 0
self.crc_error_count = 0

logger.info(
f"TCPLoRaRadio configured: {host}:{port} "
Expand Down Expand Up @@ -359,7 +361,9 @@ def check_radio_health(self) -> bool:

if self._event_loop:
self._event_loop.call_soon_threadsafe(
lambda: self._event_loop.create_task(self.refresh_noise_floor())
lambda: self._event_loop.create_task(
self._refresh_background_metrics()
)
)
return True

Expand All @@ -380,6 +384,8 @@ def get_status(self) -> dict:
"port": self.port,
"tx_count": self._tx_count,
"rx_count": self._rx_count,
"crc_errors": self._crc_errors,
"crc_error_count": self.crc_error_count,
}

async def get_modem_status(self) -> Optional[dict]:
Expand All @@ -390,7 +396,7 @@ async def get_modem_status(self) -> Optional[dict]:
)
if resp and len(resp) >= STATUS_RESP_SIZE:
fields = struct.unpack(STATUS_RESP_FMT, resp[:STATUS_RESP_SIZE])
return {
status = {
"uptime_sec": fields[0],
"rx_count": fields[1],
"tx_count": fields[2],
Expand All @@ -401,8 +407,21 @@ async def get_modem_status(self) -> Optional[dict]:
"temp_c": fields[7],
"radio_state": ["idle/rx", "tx", "error"][min(fields[8], 2)],
}
self._crc_errors = status["crc_errors"]
self.crc_error_count = status["crc_errors"]
return status
return None

async def _refresh_background_metrics(self) -> None:
try:
await self.refresh_noise_floor()
except Exception as e:
logger.debug(f"Noise-floor refresh failed: {e}")
try:
await self.get_modem_status()
except Exception as e:
logger.debug(f"Status refresh failed: {e}")

def get_noise_floor(self) -> Optional[float]:
if not self._initialized:
return 0.0
Expand Down
27 changes: 23 additions & 4 deletions src/pymc_core/hardware/usb_radio.py
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,8 @@ def __init__(
# Stats
self._tx_count = 0
self._rx_count = 0
self._crc_errors = 0
self.crc_error_count = 0

logger.info(
f"USBLoRaRadio configured: port={port}, freq={frequency/1e6:.1f}MHz, "
Expand Down Expand Up @@ -353,7 +355,7 @@ def check_radio_health(self) -> bool:
"""Health check — verify RX thread is alive, restart if dead.

Called by Dispatcher.run_forever() every 60 seconds.
Also triggers a noise floor refresh from the modem.
Also refreshes cached modem metrics from the modem.
"""
if not self._initialized:
return False
Expand All @@ -368,10 +370,12 @@ def check_radio_health(self) -> bool:
self._rx_thread.start()
return False

# Schedule noise floor refresh (non-blocking)
# Schedule modem metric refresh (non-blocking)
if self._event_loop:
self._event_loop.call_soon_threadsafe(
lambda: self._event_loop.create_task(self.refresh_noise_floor())
lambda: self._event_loop.create_task(
self._refresh_background_metrics()
)
)

return True
Expand All @@ -393,6 +397,8 @@ def get_status(self) -> dict:
"port": self.port,
"tx_count": self._tx_count,
"rx_count": self._rx_count,
"crc_errors": self._crc_errors,
"crc_error_count": self.crc_error_count,
}

async def get_modem_status(self) -> Optional[dict]:
Expand All @@ -404,7 +410,7 @@ async def get_modem_status(self) -> Optional[dict]:
)
if resp and len(resp) >= STATUS_RESP_SIZE:
fields = struct.unpack(STATUS_RESP_FMT, resp[:STATUS_RESP_SIZE])
return {
status = {
"uptime_sec": fields[0],
"rx_count": fields[1],
"tx_count": fields[2],
Expand All @@ -417,8 +423,21 @@ async def get_modem_status(self) -> Optional[dict]:
min(fields[8], 2)
],
}
self._crc_errors = status["crc_errors"]
self.crc_error_count = status["crc_errors"]
return status
return None

async def _refresh_background_metrics(self) -> None:
try:
await self.refresh_noise_floor()
except Exception as e:
logger.debug(f"Noise-floor refresh failed: {e}")
try:
await self.get_modem_status()
except Exception as e:
logger.debug(f"Status refresh failed: {e}")

def get_noise_floor(self) -> Optional[float]:
"""Get current noise floor in dBm.

Expand Down
2 changes: 1 addition & 1 deletion tests/test_basic.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@


def test_version():
assert __version__ == "1.0.11"
assert __version__ == "1.0.12"


def test_import():
Expand Down
Loading