cpu savings. reverted to faster polling for raymarine nmea decoding. using Signal Subscriptions vs polling to reduce load on dbus service.
This commit is contained in:
@@ -24,7 +24,7 @@ set -e
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
PROJECT_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)"
|
||||
|
||||
VERSION="1.1.0"
|
||||
VERSION="2.1.0"
|
||||
OUTPUT_DIR="$SCRIPT_DIR"
|
||||
PACKAGE_NAME="dbus-raymarine-publisher"
|
||||
|
||||
|
||||
@@ -255,9 +255,10 @@ def main():
|
||||
sensor_data=sensor_data,
|
||||
interface_ip=args.interface,
|
||||
on_decode=on_decode if args.debug else None,
|
||||
min_decode_interval=0.05,
|
||||
)
|
||||
listener.start()
|
||||
logger.info("Multicast listener started")
|
||||
logger.info("Multicast listener started (20Hz decode rate)")
|
||||
|
||||
# Create NMEA TCP server (broadcasts all NMEA sentences, more than D-Bus)
|
||||
nmea_tcp_server = None
|
||||
|
||||
@@ -11,7 +11,7 @@ set -e
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
|
||||
VERSION="2.1.0"
|
||||
VERSION="2.2.0"
|
||||
OUTPUT_DIR="$SCRIPT_DIR"
|
||||
PACKAGE_NAME="dbus-anchor-alarm"
|
||||
|
||||
|
||||
@@ -11,7 +11,7 @@ FIRMWARE_VERSION = 0
|
||||
CONNECTED = 1
|
||||
|
||||
# Version
|
||||
VERSION = '2.1.0'
|
||||
VERSION = '2.2.0'
|
||||
|
||||
# Timing
|
||||
MAIN_LOOP_INTERVAL_MS = 1000
|
||||
|
||||
@@ -53,13 +53,27 @@ def _unwrap(v):
|
||||
|
||||
|
||||
class SensorReader:
|
||||
"""Reads navigation sensor data from Venus OS D-Bus services."""
|
||||
"""Reads navigation sensor data from Venus OS D-Bus services using signal subscriptions."""
|
||||
|
||||
def __init__(self, bus):
|
||||
self._bus = bus
|
||||
self._gps_available = False
|
||||
self._proxy_cache = {}
|
||||
|
||||
|
||||
self._latitude = None
|
||||
self._longitude = None
|
||||
self._fix = None
|
||||
self._speed_ms = None
|
||||
self._course = None
|
||||
self._heading = None
|
||||
self._depth_m = None
|
||||
self._wind_speed = None
|
||||
self._wind_direction = None
|
||||
self._last_update = time.time()
|
||||
|
||||
self._poll_initial_values()
|
||||
self._setup_signal_subscriptions()
|
||||
|
||||
def _get_proxy(self, service_name, path):
|
||||
"""Return a cached D-Bus proxy, creating it only once per (service, path)."""
|
||||
key = (service_name, path)
|
||||
@@ -80,6 +94,93 @@ class SensorReader:
|
||||
self._proxy_cache.pop((service_name, path), None)
|
||||
logger.debug('D-Bus read failed: %s %s -- %s', service_name, path, e)
|
||||
return None
|
||||
|
||||
def _poll_initial_values(self):
|
||||
"""Poll initial values once at startup."""
|
||||
self._latitude = self._read_dbus_value(GPS_SERVICE, '/Position/Latitude')
|
||||
self._longitude = self._read_dbus_value(GPS_SERVICE, '/Position/Longitude')
|
||||
self._fix = self._read_dbus_value(GPS_SERVICE, '/Fix')
|
||||
self._speed_ms = self._read_dbus_value(GPS_SERVICE, '/Speed')
|
||||
self._course = self._read_dbus_value(GPS_SERVICE, '/Course')
|
||||
self._heading = self._read_dbus_value(NAVIGATION_SERVICE, '/Heading')
|
||||
self._depth_m = self._read_dbus_value(NAVIGATION_SERVICE, '/Depth')
|
||||
self._wind_speed = self._read_dbus_value(METEO_SERVICE, '/WindSpeed')
|
||||
self._wind_direction = self._read_dbus_value(METEO_SERVICE, '/WindDirection')
|
||||
self._update_gps_available()
|
||||
|
||||
def _setup_signal_subscriptions(self):
|
||||
"""Subscribe to all sensor value changes."""
|
||||
subscriptions = [
|
||||
(GPS_SERVICE, '/Position/Latitude', '_on_latitude_changed'),
|
||||
(GPS_SERVICE, '/Position/Longitude', '_on_longitude_changed'),
|
||||
(GPS_SERVICE, '/Fix', '_on_fix_changed'),
|
||||
(GPS_SERVICE, '/Speed', '_on_speed_changed'),
|
||||
(GPS_SERVICE, '/Course', '_on_course_changed'),
|
||||
(NAVIGATION_SERVICE, '/Heading', '_on_heading_changed'),
|
||||
(NAVIGATION_SERVICE, '/Depth', '_on_depth_changed'),
|
||||
(METEO_SERVICE, '/WindSpeed', '_on_wind_speed_changed'),
|
||||
(METEO_SERVICE, '/WindDirection', '_on_wind_direction_changed'),
|
||||
]
|
||||
|
||||
for service, path, handler_name in subscriptions:
|
||||
try:
|
||||
self._bus.add_signal_receiver(
|
||||
getattr(self, handler_name),
|
||||
signal_name='PropertiesChanged',
|
||||
dbus_interface='com.victronenergy.BusItem',
|
||||
bus_name=service,
|
||||
path=path
|
||||
)
|
||||
except dbus.exceptions.DBusException as e:
|
||||
logger.debug(f'Failed to subscribe to {service}{path}: {e}')
|
||||
|
||||
def _on_latitude_changed(self, changes):
|
||||
if 'Value' in changes:
|
||||
self._latitude = _unwrap(changes['Value'])
|
||||
self._last_update = time.time()
|
||||
|
||||
def _on_longitude_changed(self, changes):
|
||||
if 'Value' in changes:
|
||||
self._longitude = _unwrap(changes['Value'])
|
||||
self._last_update = time.time()
|
||||
|
||||
def _on_fix_changed(self, changes):
|
||||
if 'Value' in changes:
|
||||
self._fix = _unwrap(changes['Value'])
|
||||
self._update_gps_available()
|
||||
|
||||
def _on_speed_changed(self, changes):
|
||||
if 'Value' in changes:
|
||||
self._speed_ms = _unwrap(changes['Value'])
|
||||
|
||||
def _on_course_changed(self, changes):
|
||||
if 'Value' in changes:
|
||||
self._course = _unwrap(changes['Value'])
|
||||
|
||||
def _on_heading_changed(self, changes):
|
||||
if 'Value' in changes:
|
||||
self._heading = _unwrap(changes['Value'])
|
||||
|
||||
def _on_depth_changed(self, changes):
|
||||
if 'Value' in changes:
|
||||
self._depth_m = _unwrap(changes['Value'])
|
||||
|
||||
def _on_wind_speed_changed(self, changes):
|
||||
if 'Value' in changes:
|
||||
self._wind_speed = _unwrap(changes['Value'])
|
||||
|
||||
def _on_wind_direction_changed(self, changes):
|
||||
if 'Value' in changes:
|
||||
self._wind_direction = _unwrap(changes['Value'])
|
||||
|
||||
def _update_gps_available(self):
|
||||
"""Update GPS availability status."""
|
||||
self._gps_available = (
|
||||
self._latitude is not None and
|
||||
self._longitude is not None and
|
||||
self._fix is not None and
|
||||
int(self._fix) >= 1
|
||||
)
|
||||
|
||||
@property
|
||||
def connected(self):
|
||||
@@ -87,45 +188,20 @@ class SensorReader:
|
||||
return self._gps_available
|
||||
|
||||
def read(self):
|
||||
"""Read all sensors and return a SensorSnapshot.
|
||||
|
||||
Each field is None if the corresponding D-Bus read fails.
|
||||
Speed is converted from m/s to knots; depth from meters to feet.
|
||||
"""Return a SensorSnapshot from cached signal values.
|
||||
|
||||
No D-Bus GetValue() calls - all values updated via signals.
|
||||
"""
|
||||
lat = self._read_dbus_value(GPS_SERVICE, '/Position/Latitude')
|
||||
lon = self._read_dbus_value(GPS_SERVICE, '/Position/Longitude')
|
||||
fix = self._read_dbus_value(GPS_SERVICE, '/Fix')
|
||||
|
||||
self._gps_available = (
|
||||
lat is not None and lon is not None
|
||||
and fix is not None and int(fix) >= 1
|
||||
)
|
||||
|
||||
speed_ms = self._read_dbus_value(GPS_SERVICE, '/Speed')
|
||||
speed = float(speed_ms) * MS_TO_KNOTS if speed_ms is not None else None
|
||||
|
||||
course = self._read_dbus_value(GPS_SERVICE, '/Course')
|
||||
if course is not None:
|
||||
course = float(course)
|
||||
|
||||
heading = self._read_dbus_value(NAVIGATION_SERVICE, '/Heading')
|
||||
if heading is not None:
|
||||
heading = float(heading)
|
||||
|
||||
depth_m = self._read_dbus_value(NAVIGATION_SERVICE, '/Depth')
|
||||
depth = float(depth_m) * METERS_TO_FEET if depth_m is not None else None
|
||||
|
||||
wind_speed = self._read_dbus_value(METEO_SERVICE, '/WindSpeed')
|
||||
if wind_speed is not None:
|
||||
wind_speed = float(wind_speed)
|
||||
|
||||
wind_direction = self._read_dbus_value(METEO_SERVICE, '/WindDirection')
|
||||
if wind_direction is not None:
|
||||
wind_direction = float(wind_direction)
|
||||
|
||||
speed = float(self._speed_ms) * MS_TO_KNOTS if self._speed_ms is not None else None
|
||||
course = float(self._course) if self._course is not None else None
|
||||
heading = float(self._heading) if self._heading is not None else None
|
||||
depth = float(self._depth_m) * METERS_TO_FEET if self._depth_m is not None else None
|
||||
wind_speed = float(self._wind_speed) if self._wind_speed is not None else None
|
||||
wind_direction = float(self._wind_direction) if self._wind_direction is not None else None
|
||||
|
||||
return SensorSnapshot(
|
||||
latitude=float(lat) if lat is not None else None,
|
||||
longitude=float(lon) if lon is not None else None,
|
||||
latitude=float(self._latitude) if self._latitude is not None else None,
|
||||
longitude=float(self._longitude) if self._longitude is not None else None,
|
||||
speed=speed,
|
||||
course=course,
|
||||
heading=heading,
|
||||
|
||||
@@ -29,7 +29,7 @@ set -e
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
|
||||
# Default values
|
||||
VERSION="2.1.0"
|
||||
VERSION="2.2.0"
|
||||
OUTPUT_DIR="$SCRIPT_DIR"
|
||||
PACKAGE_NAME="dbus-generator-ramp"
|
||||
|
||||
|
||||
@@ -55,7 +55,7 @@ from ramp_controller import RampController
|
||||
|
||||
|
||||
# Version
|
||||
VERSION = '2.1.0'
|
||||
VERSION = '2.2.0'
|
||||
|
||||
# D-Bus service name for our addon
|
||||
SERVICE_NAME = 'com.victronenergy.generatorramp'
|
||||
@@ -122,6 +122,9 @@ class GeneratorRampController:
|
||||
self._last_run_energy_wh = 0.0
|
||||
self._energy_last_time = None
|
||||
|
||||
# Signal subscription tracking
|
||||
self._power_subscriptions_active = False
|
||||
|
||||
# D-Bus connection
|
||||
self.bus = dbus.SystemBus()
|
||||
|
||||
@@ -595,9 +598,110 @@ class GeneratorRampController:
|
||||
self._read_ac_state()
|
||||
self._read_current_limit()
|
||||
|
||||
# Set up signal subscriptions
|
||||
self._setup_signal_subscriptions()
|
||||
|
||||
except dbus.exceptions.DBusException as e:
|
||||
self.logger.error(f"D-Bus initialization failed: {e}")
|
||||
raise
|
||||
|
||||
def _setup_signal_subscriptions(self):
|
||||
"""Subscribe to D-Bus property changes instead of polling."""
|
||||
try:
|
||||
self.bus.add_signal_receiver(
|
||||
self._on_generator_state_changed,
|
||||
signal_name='PropertiesChanged',
|
||||
dbus_interface='com.victronenergy.BusItem',
|
||||
bus_name=self.generator_service,
|
||||
path='/State'
|
||||
)
|
||||
self.logger.debug(f"Subscribed to generator state changes")
|
||||
|
||||
self.bus.add_signal_receiver(
|
||||
self._on_ac_state_changed,
|
||||
signal_name='PropertiesChanged',
|
||||
dbus_interface='com.victronenergy.BusItem',
|
||||
bus_name=self.vebus_service,
|
||||
path='/Ac/ActiveIn/Connected'
|
||||
)
|
||||
self.logger.debug(f"Subscribed to AC connection changes")
|
||||
except dbus.exceptions.DBusException as e:
|
||||
self.logger.warning(f"Failed to set up signal subscriptions: {e}")
|
||||
|
||||
def _subscribe_power_readings(self):
|
||||
"""Subscribe to power readings when generator is running."""
|
||||
if self._power_subscriptions_active:
|
||||
return
|
||||
|
||||
paths = [
|
||||
'/Ac/ActiveIn/L1/P',
|
||||
'/Ac/ActiveIn/L2/P',
|
||||
'/Ac/Out/L1/P',
|
||||
'/Ac/Out/L2/P',
|
||||
'/Ac/In/1/CurrentLimit',
|
||||
]
|
||||
|
||||
try:
|
||||
for path in paths:
|
||||
self.bus.add_signal_receiver(
|
||||
lambda changes, p=path: self._on_power_changed(p, changes),
|
||||
signal_name='PropertiesChanged',
|
||||
dbus_interface='com.victronenergy.BusItem',
|
||||
bus_name=self.vebus_service,
|
||||
path=path
|
||||
)
|
||||
self._power_subscriptions_active = True
|
||||
self.logger.debug("Subscribed to power readings")
|
||||
except dbus.exceptions.DBusException as e:
|
||||
self.logger.warning(f"Failed to subscribe to power readings: {e}")
|
||||
|
||||
def _unsubscribe_power_readings(self):
|
||||
"""Mark power readings as unsubscribed (signals ignored via flag)."""
|
||||
if self._power_subscriptions_active:
|
||||
self._power_subscriptions_active = False
|
||||
self.logger.debug("Power reading signals now ignored")
|
||||
|
||||
def _on_generator_state_changed(self, changes):
|
||||
"""Handle generator state change signal."""
|
||||
if 'Value' in changes:
|
||||
self.generator_state = int(changes['Value'])
|
||||
self.dbus_service['/Generator/State'] = self.generator_state
|
||||
self.logger.debug(f"Generator state changed: {self.generator_state}")
|
||||
|
||||
def _on_ac_state_changed(self, changes):
|
||||
"""Handle AC connection state change signal."""
|
||||
if 'Value' in changes:
|
||||
self.ac_connected = bool(changes['Value'])
|
||||
self.dbus_service['/AcInput/Connected'] = 1 if self.ac_connected else 0
|
||||
self.logger.debug(f"AC connected: {self.ac_connected}")
|
||||
|
||||
def _on_power_changed(self, path, changes):
|
||||
"""Handle power reading change signal."""
|
||||
if not self._power_subscriptions_active:
|
||||
return
|
||||
|
||||
if 'Value' in changes:
|
||||
value = float(changes['Value'])
|
||||
|
||||
if path == '/Ac/ActiveIn/L1/P':
|
||||
self.current_l1_power = value
|
||||
self.dbus_service['/Power/L1'] = value
|
||||
elif path == '/Ac/ActiveIn/L2/P':
|
||||
self.current_l2_power = value
|
||||
self.dbus_service['/Power/L2'] = value
|
||||
self.dbus_service['/Power/Total'] = self.current_l1_power + self.current_l2_power
|
||||
elif path == '/Ac/Out/L1/P':
|
||||
self.output_l1_power = value
|
||||
self.dbus_service['/OutputPower/L1'] = value
|
||||
elif path == '/Ac/Out/L2/P':
|
||||
self.output_l2_power = value
|
||||
self.dbus_service['/OutputPower/L2'] = value
|
||||
total = self.output_l1_power + self.output_l2_power
|
||||
self.dbus_service['/OutputPower/Total'] = total
|
||||
self.ramp_controller.set_output_power(total)
|
||||
elif path == '/Ac/In/1/CurrentLimit':
|
||||
self.current_limit_setting = value
|
||||
self.dbus_service['/CurrentLimit'] = value
|
||||
|
||||
def _get_proxy(self, service, path):
|
||||
"""Return a cached D-Bus proxy, creating it only once per (service, path)."""
|
||||
@@ -752,12 +856,17 @@ class GeneratorRampController:
|
||||
if not self.enabled:
|
||||
return True
|
||||
|
||||
# Read current states from D-Bus
|
||||
self._read_generator_state()
|
||||
self._read_ac_state()
|
||||
self._read_power()
|
||||
self._read_output_power()
|
||||
self._read_current_limit()
|
||||
# Conditional D-Bus reading based on state
|
||||
if self.state == self.STATE_IDLE:
|
||||
self._read_generator_state()
|
||||
self._unsubscribe_power_readings()
|
||||
else:
|
||||
self._subscribe_power_readings()
|
||||
if self.state == self.STATE_WARMUP and (now - self.state_enter_time) < 0.1:
|
||||
self._read_ac_state()
|
||||
self._read_power()
|
||||
self._read_output_power()
|
||||
self._read_current_limit()
|
||||
|
||||
# Accumulate energy while generator is providing AC power
|
||||
if self.ac_connected and self.generator_state in [
|
||||
|
||||
Reference in New Issue
Block a user