From: Daniel Drake Add support for OLPC mesh connectivity Index: sugar-0.84.5/src/jarabe/desktop/meshbox.py =================================================================== --- sugar-0.84.5.orig/src/jarabe/desktop/meshbox.py +++ sugar-0.84.5/src/jarabe/desktop/meshbox.py @@ -34,6 +34,7 @@ from sugar.graphics.menuitem import Menu from sugar.activity.activityhandle import ActivityHandle from sugar.activity import activityfactory from sugar.util import unique_id +from sugar import profile from jarabe.model import neighborhood from jarabe.view.buddyicon import BuddyIcon @@ -47,18 +48,27 @@ from jarabe.model import network from jarabe.model import shell from jarabe.model.network import Settings from jarabe.model.network import WirelessSecurity +from jarabe.model.network import OlpcMesh as OlpcMeshSettings +from jarabe.model.olpcmesh import OlpcMeshManager _NM_SERVICE = 'org.freedesktop.NetworkManager' _NM_IFACE = 'org.freedesktop.NetworkManager' _NM_PATH = '/org/freedesktop/NetworkManager' _NM_DEVICE_IFACE = 'org.freedesktop.NetworkManager.Device' _NM_WIRELESS_IFACE = 'org.freedesktop.NetworkManager.Device.Wireless' +_NM_OLPC_MESH_IFACE = 'org.freedesktop.NetworkManager.Device.OlpcMesh' _NM_ACCESSPOINT_IFACE = 'org.freedesktop.NetworkManager.AccessPoint' _NM_ACTIVE_CONN_IFACE = 'org.freedesktop.NetworkManager.Connection.Active' -_ICON_NAME = 'network-wireless' +_AP_ICON_NAME = 'network-wireless' +_OLPC_MESH_ICON_NAME = 'network-mesh' class AccessPointView(CanvasPulsingIcon): + __gsignals__ = { + 'properties-initialized': + (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, ([])), + } + def __init__(self, device, model): CanvasPulsingIcon.__init__(self, size=style.STANDARD_ICON_SIZE, cache=True) @@ -123,7 +133,7 @@ class AccessPointView(CanvasPulsingIcon) dbus_interface=_NM_WIRELESS_IFACE) def _create_palette(self): - icon_name = get_icon_state(_ICON_NAME, self._strength) + icon_name = get_icon_state(_AP_ICON_NAME, self._strength) self._palette_icon = Icon(icon_name=icon_name, icon_size=style.STANDARD_ICON_SIZE, badge_name=self.props.badge_name) @@ -202,6 +212,7 @@ class AccessPointView(CanvasPulsingIcon) def __get_all_props_reply_cb(self, properties): self._update_properties(properties) + self.emit('properties-initialized') def __get_all_props_error_cb(self, err): logging.debug('Error getting the access point properties: %s', err) @@ -230,9 +241,9 @@ class AccessPointView(CanvasPulsingIcon) if connection: connection.set_connected() - icon_name = '%s-connected' % _ICON_NAME + icon_name = '%s-connected' % _AP_ICON_NAME else: - icon_name = _ICON_NAME + icon_name = _AP_ICON_NAME icon_name = get_icon_state(icon_name, self._strength) if icon_name: @@ -376,6 +387,12 @@ class AccessPointView(CanvasPulsingIcon) def __activate_error_cb(self, err): logging.debug('Failed to activate connection: %s', err) + def get_mode(self): + return self._mode + + def get_ssid(self): + return self._name + def set_filter(self, query): self._greyed_out = self._name.lower().find(query) == -1 self._update_state() @@ -400,6 +417,158 @@ class AccessPointView(CanvasPulsingIcon) path=self._device.object_path, dbus_interface=_NM_WIRELESS_IFACE) +class OlpcMeshView(CanvasPulsingIcon): + def __init__(self, mesh_mgr, channel): + CanvasPulsingIcon.__init__(self, icon_name=_OLPC_MESH_ICON_NAME, + size=style.STANDARD_ICON_SIZE, cache=True) + self._bus = dbus.SystemBus() + self._channel = channel + self._mesh_mgr = mesh_mgr + self._disconnect_item = None + self._connect_item = None + self._greyed_out = False + self._name = '' + self._device_state = None + self._connection = None + self._active = False + device = mesh_mgr.meshdev + + self.connect('button-release-event', self.__button_release_event_cb) + + interface_props = dbus.Interface(device, + 'org.freedesktop.DBus.Properties') + interface_props.Get(_NM_DEVICE_IFACE, 'State', + reply_handler=self.__get_device_state_reply_cb, + error_handler=self.__get_device_state_error_cb) + interface_props.Get(_NM_OLPC_MESH_IFACE, 'ActiveChannel', + reply_handler=self.__get_active_channel_reply_cb, + error_handler=self.__get_active_channel_error_cb) + + self._bus.add_signal_receiver(self.__device_state_changed_cb, + signal_name='StateChanged', + path=device.object_path, + dbus_interface=_NM_DEVICE_IFACE) + self._bus.add_signal_receiver(self.__wireless_properties_changed_cb, + signal_name='PropertiesChanged', + path=device.object_path, + dbus_interface=_NM_OLPC_MESH_IFACE) + + pulse_color = XoColor('%s,%s' % (style.COLOR_BUTTON_GREY.get_svg(), + style.COLOR_TRANSPARENT.get_svg())) + self.props.pulse_color = pulse_color + self.props.base_color = profile.get_color() + self._palette = self._create_palette() + self.set_palette(self._palette) + + def _create_palette(self): + p = palette.Palette(_("Mesh Network") + " " + str(self._channel)) + + self._connect_item = MenuItem(_('Connect'), 'dialog-ok') + self._connect_item.connect('activate', self.__connect_activate_cb) + p.menu.append(self._connect_item) + + self._disconnect_item = MenuItem(_('Disconnect'), 'media-eject') + self._disconnect_item.connect('activate', + self._disconnect_activate_cb) + p.menu.append(self._disconnect_item) + + return p + + def __get_device_state_reply_cb(self, state): + self._device_state = state + self._update() + + def __get_device_state_error_cb(self, err): + logging.debug('Error getting the device state: %s', err) + + def __device_state_changed_cb(self, new_state, old_state, reason): + self._device_state = new_state + self._update() + + def __get_active_channel_reply_cb(self, channel): + self._active = (channel == self._channel) + self._update() + + def __get_active_channel_error_cb(self, err): + logging.debug('Error getting the active channel: %s', err) + + def __wireless_properties_changed_cb(self, properties): + if 'ActiveChannel' in properties: + channel = properties['ActiveChannel'] + self._active = (channel == self._channel) + self._update() + + def _update(self): + if self._active: + state = self._device_state + else: + state = network.DEVICE_STATE_UNKNOWN + + if state == network.DEVICE_STATE_ACTIVATED: + connection = network.find_connection(self._name) + if connection: + connection.set_connected() + + if state == network.DEVICE_STATE_PREPARE or \ + state == network.DEVICE_STATE_CONFIG or \ + state == network.DEVICE_STATE_NEED_AUTH or \ + state == network.DEVICE_STATE_IP_CONFIG: + if self._disconnect_item: + self._disconnect_item.show() + self._connect_item.hide() + self._palette.props.secondary_text = _('Connecting...') + self.props.pulsing = True + elif state == network.DEVICE_STATE_ACTIVATED: + if self._disconnect_item: + self._disconnect_item.show() + self._connect_item.hide() + self._palette.props.secondary_text = _('Connected') + self.props.pulsing = False + else: + if self._disconnect_item: + self._disconnect_item.hide() + self._connect_item.show() + self._palette.props.secondary_text = None + self.props.pulsing = False + + def _update_color(self): + if self._greyed_out: + self.props.base_color = XoColor('#D5D5D5,#D5D5D5') + else: + self.props.base_color = profile.get_color() + + def _disconnect_activate_cb(self, item): + pass + + def __connect_activate_cb(self, icon): + self._connect() + + def __button_release_event_cb(self, icon, event): + self._connect() + + def _connect(self): + self._mesh_mgr.user_activate_channel(self._channel) + + def __activate_reply_cb(self, connection): + logging.debug('Connection activated: %s', connection) + + def __activate_error_cb(self, err): + logging.debug('Failed to activate connection: %s', err) + + def set_filter(self, query): + self._greyed_out = (query != '') + self._update_color() + + def disconnect(self): + self._bus.remove_signal_receiver(self.__device_state_changed_cb, + signal_name='StateChanged', + path=self._device.object_path, + dbus_interface=_NM_DEVICE_IFACE) + self._bus.remove_signal_receiver(self.__wireless_properties_changed_cb, + signal_name='PropertiesChanged', + path=self._device.object_path, + dbus_interface=_NM_OLPC_MESH_IFACE) + class ActivityView(hippo.CanvasBox): def __init__(self, model): hippo.CanvasBox.__init__(self) @@ -692,6 +861,8 @@ class NetworkManagerObserver(object): device_type = props.Get(_NM_DEVICE_IFACE, 'DeviceType') if device_type == network.DEVICE_TYPE_802_11_WIRELESS: self._devices[device_o] = DeviceObserver(self._box, device) + elif device_type == network.DEVICE_TYPE_802_11_OLPC_MESH: + self._box.enable_olpc_mesh(device) def _get_device_path_error_cb(self, err): logging.error('Failed to get device type: %s', err) @@ -704,6 +875,13 @@ class NetworkManagerObserver(object): observer = self._devices[device_o] observer.disconnect() del self._devices[device_o] + return + + device = self._bus.get_object(_NM_SERVICE, device_o) + props = dbus.Interface(device, 'org.freedesktop.DBus.Properties') + device_type = props.Get(_NM_DEVICE_IFACE, 'DeviceType') + if device_type == network.DEVICE_TYPE_802_11_OLPC_MESH: + self._box.enable_olpc_mesh(device) class MeshBox(gtk.VBox): __gtype_name__ = 'SugarMeshBox' @@ -718,7 +896,7 @@ class MeshBox(gtk.VBox): self._model = neighborhood.get_model() self._buddies = {} self._activities = {} - self._mesh = {} + self._mesh = [] self._buddy_to_activity = {} self._suspended = True self._query = '' @@ -843,12 +1021,8 @@ class MeshBox(gtk.VBox): def add_access_point(self, device, ap): icon = AccessPointView(device, ap) - self._layout.add(icon) - - if hasattr(icon, 'set_filter'): - icon.set_filter(self._query) - self.access_points[ap.object_path] = icon + icon.connect('properties-initialized', self._ap_initialized_cb, ap.object_path) def remove_access_point(self, ap_o): if ap_o in self.access_points: @@ -857,18 +1031,56 @@ class MeshBox(gtk.VBox): self._layout.remove(icon) del self.access_points[ap_o] else: - logging.error('Can not remove access point %s' % ap_o) + logging.debug('Can not remove access point %s' % ap_o) + + @staticmethod + def _is_olpc_mesh(ap): + return ap.get_mode() == network.NM_802_11_MODE_ADHOC \ + and ap.get_ssid() == 'olpc-mesh' + + def _ap_initialized_cb(self, ap, ap_o): + if len(self._mesh) > 0 and self._is_olpc_mesh(ap): + logging.debug("ignoring OLPC mesh IBSS") + ap.disconnect() + del self.access_points[ap_o] + return + + self._layout.add(ap) + if hasattr(ap, 'set_filter'): + ap.set_filter(self._query) + + def _add_olpc_mesh_icon(self, mesh_mgr, channel): + icon = OlpcMeshView(mesh_mgr, channel) + self._layout.add(icon) + self._mesh.append(icon) + + def enable_olpc_mesh(self, meshdev): + mesh_mgr = OlpcMeshManager(meshdev) + self._add_olpc_mesh_icon(mesh_mgr, 1) + self._add_olpc_mesh_icon(mesh_mgr, 6) + self._add_olpc_mesh_icon(mesh_mgr, 11) + for ap_o in self.access_points.keys(): + ap = self.access_points[ap_o] + if _is_olpc_mesh(ap): + logging.debug("removing OLPC mesh IBSS") + ap.disconnect() + del self.access_points[ap_o] + + def disable_olpc_mesh(self, meshdev): + for icon in self._mesh: + self._layout.remove(icon) + self._mesh = [] def suspend(self): if not self._suspended: self._suspended = True - for ap in self.access_points.values(): + for ap in self.access_points.values() + self._mesh: ap.props.paused = True def resume(self): if self._suspended: self._suspended = False - for ap in self.access_points.values(): + for ap in self.access_points.values() + self._mesh: ap.props.paused = False def _toolbar_query_changed_cb(self, toolbar, query): Index: sugar-0.84.5/src/jarabe/model/network.py =================================================================== --- sugar-0.84.5.orig/src/jarabe/model/network.py +++ sugar-0.84.5/src/jarabe/model/network.py @@ -26,6 +26,7 @@ from sugar import env DEVICE_TYPE_802_3_ETHERNET = 1 DEVICE_TYPE_802_11_WIRELESS = 2 +DEVICE_TYPE_802_11_OLPC_MESH = 6 DEVICE_STATE_UNKNOWN = 0 DEVICE_STATE_UNMANAGED = 1 @@ -99,6 +100,8 @@ class WirelessSecurity(object): return wireless_security class Wireless(object): + nm_name = "802-11-wireless" + def __init__(self): self.ssid = None self.security = None @@ -109,6 +112,23 @@ class Wireless(object): wireless['security'] = self.security return wireless +class OlpcMesh(object): + nm_name = "802-11-olpc-mesh" + + def __init__(self, channel, anycast_addr): + self.channel = channel + self.anycast_addr = anycast_addr + + def get_dict(self): + ret = { + "ssid": dbus.ByteArray("olpc-mesh"), + "channel": self.channel, + } + + if self.anycast_addr: + ret["dhcp-anycast-address"] = dbus.ByteArray(self.anycast_addr) + return ret + class Connection(object): def __init__(self): self.id = None @@ -127,18 +147,25 @@ class Connection(object): return connection class Settings(object): - def __init__(self): + def __init__(self, wireless_cfg=None, ip4_method=None): self.connection = Connection() - self.wireless = Wireless() + self.ipv4_method = None self.wireless_security = None + if wireless_cfg is not None: + self.wireless = wireless_cfg + else: + self.wireless = Wireless() + def get_dict(self): settings = {} settings['connection'] = self.connection.get_dict() - settings['802-11-wireless'] = self.wireless.get_dict() + settings[self.wireless.nm_name] = self.wireless.get_dict() if self.wireless_security is not None: settings['802-11-wireless-security'] = \ self.wireless_security.get_dict() + if self.ipv4_method is not None: + settings['ipv4'] = { 'method': self.ipv4_method } return settings class Secrets(object): Index: sugar-0.84.5/extensions/deviceicon/network.py =================================================================== --- sugar-0.84.5.orig/extensions/deviceicon/network.py +++ sugar-0.84.5/extensions/deviceicon/network.py @@ -32,6 +32,7 @@ from sugar.graphics.palette import Palet from sugar.graphics.toolbutton import ToolButton from sugar.graphics.tray import TrayIcon from sugar.graphics import xocolor +from sugar import profile from jarabe.model import network from jarabe.frame.frameinvoker import FrameWidgetInvoker @@ -45,6 +46,7 @@ _NM_PATH = '/org/freedesktop/NetworkMana _NM_DEVICE_IFACE = 'org.freedesktop.NetworkManager.Device' _NM_WIRED_IFACE = 'org.freedesktop.NetworkManager.Device.Wired' _NM_WIRELESS_IFACE = 'org.freedesktop.NetworkManager.Device.Wireless' +_NM_OLPC_MESH_IFACE = 'org.freedesktop.NetworkManager.Device.OlpcMesh' _NM_ACCESSPOINT_IFACE = 'org.freedesktop.NetworkManager.AccessPoint' _NM_ACTIVE_CONN_IFACE = 'org.freedesktop.NetworkManager.Connection.Active' @@ -108,21 +110,31 @@ class WirelessPalette(Palette): def set_connecting(self): self.props.secondary_text = _('Connecting...') - def set_connected(self, frequency, iaddress): + def _set_connected(self, iaddress): self.set_content(self._info) self.props.secondary_text = _('Connected') - self._set_channel(frequency) self._set_ip_address(iaddress) self._disconnect_item.show() + def set_connected_with_frequency(self, frequency, iaddress): + self._set_connected(iaddress) + self._set_frequency(frequency) + + def set_connected_with_channel(self, channel, iaddress): + self._set_connected(iaddress) + self._set_channel(channel) + def __disconnect_activate_cb(self, menuitem): self.emit('deactivate-connection') - def _set_channel(self, frequency): + def _set_frequency(self, frequency): try: channel = frequency_to_channel(frequency) except KeyError: channel = 0 + self._set_channel(channel) + + def _set_channel(self, channel): self._channel_label.set_text("%s: %d" % (_("Channel"), channel)) def _set_ip_address(self, ip_address): @@ -345,7 +357,7 @@ class WirelessDeviceView(ToolButton): self._icon.props.pulsing = True elif state == network.DEVICE_STATE_ACTIVATED: address = self._device_props.Get(_NM_DEVICE_IFACE, 'Ip4Address') - self._palette.set_connected(self._frequency, address) + self._palette.set_connected_with_frequency(self._frequency, address) self._icon.props.pulsing = False def _update_color(self): @@ -369,6 +381,129 @@ class WirelessDeviceView(ToolButton): break +class OlpcMeshDeviceView(ToolButton): + + _ICON_NAME = 'network-mesh' + FRAME_POSITION_RELATIVE = 302 + + def __init__(self, device): + ToolButton.__init__(self) + + self._bus = dbus.SystemBus() + self._device = device + self._device_props = None + self._device_state = None + self._channel = 0 + + self._icon = PulsingIcon(icon_name=self._ICON_NAME) + self._icon.props.pulse_color = xocolor.XoColor( \ + "%s,%s" % (style.COLOR_BUTTON_GREY.get_svg(), + style.COLOR_TRANSPARENT.get_svg())) + self._icon.props.base_color = profile.get_color() + + self.set_icon_widget(self._icon) + self._icon.show() + + self.set_palette_invoker(FrameWidgetInvoker(self)) + self._palette = WirelessPalette(_("Mesh Network")) + self._palette.connect('deactivate-connection', + self.__deactivate_connection) + self.set_palette(self._palette) + self._palette.set_group_id('frame') + + self._device_props = dbus.Interface(self._device, + 'org.freedesktop.DBus.Properties') + self._device_props.GetAll(_NM_DEVICE_IFACE, byte_arrays=True, + reply_handler=self.__get_device_props_reply_cb, + error_handler=self.__get_device_props_error_cb) + self._device_props.Get(_NM_OLPC_MESH_IFACE, 'ActiveChannel', + reply_handler=self.__get_active_channel_reply_cb, + error_handler=self.__get_active_channel_error_cb) + + self._bus.add_signal_receiver(self.__state_changed_cb, + signal_name='StateChanged', + path=self._device.object_path, + dbus_interface=_NM_DEVICE_IFACE) + self._bus.add_signal_receiver(self.__wireless_properties_changed_cb, + signal_name='PropertiesChanged', + path=device.object_path, + dbus_interface=_NM_OLPC_MESH_IFACE) + + def disconnect(self): + self._bus.remove_signal_receiver(self.__state_changed_cb, + signal_name='StateChanged', + path=self._device.object_path, + dbus_interface=_NM_DEVICE_IFACE) + self._bus.remove_signal_receiver(self.__wireless_properties_changed_cb, + signal_name='PropertiesChanged', + path=self._device.object_path, + dbus_interface=_NM_OLPC_MESH_IFACE) + + def __get_device_props_reply_cb(self, properties): + if 'State' in properties: + self._device_state = properties['State'] + self._update() + + def __get_device_props_error_cb(self, err): + logging.error('Error getting the device properties: %s', err) + + def __get_active_channel_reply_cb(self, channel): + self._channel = channel + self._update_text() + + def __get_active_channel_error_cb(self, err): + logging.debug('Error getting the active channel: %s', err) + + def __state_changed_cb(self, new_state, old_state, reason): + self._device_state = new_state + self._update() + + def __wireless_properties_changed_cb(self, properties): + if 'ActiveChannel' in properties: + self._channel = properties['ActiveChannel'] + self._update_text() + + def _update_text(self): + self._palette.props.primary_text = _("Mesh Network") + " " + str(self._channel) + + def _update(self): + state = self._device_state + + if state == network.DEVICE_STATE_PREPARE or \ + state == network.DEVICE_STATE_CONFIG or \ + state == network.DEVICE_STATE_NEED_AUTH or \ + state == network.DEVICE_STATE_IP_CONFIG: + self._palette.set_connecting() + self._icon.props.pulsing = True + elif state == network.DEVICE_STATE_ACTIVATED: + address = self._device_props.Get(_NM_DEVICE_IFACE, 'Ip4Address') + self._palette.set_connected_with_channel(self._channel, address) + self._icon.props.pulsing = False + + def __deactivate_connection(self, palette, data=None): + obj = self._bus.get_object(_NM_SERVICE, _NM_PATH) + netmgr = dbus.Interface(obj, _NM_IFACE) + netmgr_props = dbus.Interface( + netmgr, 'org.freedesktop.DBus.Properties') + active_connections_o = netmgr_props.Get(_NM_IFACE, + 'ActiveConnections') + + for conn_o in active_connections_o: + # The connection path for a mesh connection is the device itself. + obj = self._bus.get_object(_NM_IFACE, conn_o) + props = dbus.Interface(obj, 'org.freedesktop.DBus.Properties') + ap_op = props.Get(_NM_ACTIVE_CONN_IFACE, 'SpecificObject') + + try: + obj = self._bus.get_object(_NM_IFACE, ap_op) + props = dbus.Interface(obj, 'org.freedesktop.DBus.Properties') + type = props.Get(_NM_DEVICE_IFACE, 'DeviceType') + if type == network.DEVICE_TYPE_802_11_OLPC_MESH: + netmgr.DeactivateConnection(conn_o) + break + except dbus.exceptions.DBusException: + pass + class WiredDeviceView(TrayIcon): _ICON_NAME = 'network-wired' @@ -388,11 +523,12 @@ class WiredDeviceView(TrayIcon): class WirelessDeviceObserver(object): - def __init__(self, device, tray): + def __init__(self, device, tray, view_class=WirelessDeviceView): self._bus = dbus.SystemBus() self._device = device self._device_view = None self._tray = tray + self._view_class = view_class props = dbus.Interface(self._device, 'org.freedesktop.DBus.Properties') props.GetAll(_NM_DEVICE_IFACE, byte_arrays=True, @@ -427,7 +563,7 @@ class WirelessDeviceObserver(object): state == network.DEVICE_STATE_IP_CONFIG or \ state == network.DEVICE_STATE_ACTIVATED: if self._device_view is None: - self._device_view = WirelessDeviceView(self._device) + self._device_view = self._view_class(self._device) self._tray.add_device(self._device_view) else: if self._device_view is not None: @@ -528,6 +664,9 @@ class NetworkManagerObserver(object): elif device_type == network.DEVICE_TYPE_802_11_WIRELESS: device = WirelessDeviceObserver(nm_device, self._tray) self._devices[device_op] = device + elif device_type == network.DEVICE_TYPE_802_11_OLPC_MESH: + device = WirelessDeviceObserver(nm_device, self._tray, view_class=OlpcMeshDeviceView) + self._devices[device_op] = device def __device_added_cb(self, device_op): self._check_device(device_op) Index: sugar-0.84.5/src/jarabe/model/olpcmesh.py =================================================================== --- /dev/null +++ sugar-0.84.5/src/jarabe/model/olpcmesh.py @@ -0,0 +1,197 @@ +# Copyright (C) 2009 One Laptop per Child +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +import logging + +import dbus +import gobject + +from jarabe.model import network +from jarabe.model.network import Settings +from jarabe.model.network import OlpcMesh as OlpcMeshSettings +from sugar.util import unique_id + +_NM_SERVICE = 'org.freedesktop.NetworkManager' +_NM_IFACE = 'org.freedesktop.NetworkManager' +_NM_PATH = '/org/freedesktop/NetworkManager' +_NM_DEVICE_IFACE = 'org.freedesktop.NetworkManager.Device' +_NM_OLPC_MESH_IFACE = 'org.freedesktop.NetworkManager.Device.OlpcMesh' + +_XS_ANYCAST = "\xc0\x27\xc0\x27\xc0\x00" + +DEVICE_STATE_UNKNOWN = 0 +DEVICE_STATE_UNMANAGED = 1 +DEVICE_STATE_UNAVAILABLE = 2 +DEVICE_STATE_DISCONNECTED = 3 +DEVICE_STATE_PREPARE = 4 +DEVICE_STATE_CONFIG = 5 +DEVICE_STATE_NEED_AUTH = 6 +DEVICE_STATE_IP_CONFIG = 7 +DEVICE_STATE_ACTIVATED = 8 +DEVICE_STATE_FAILED = 9 + +class OlpcMeshManager(object): + def __init__(self, meshdev): + self._bus = dbus.SystemBus() + self.meshdev = meshdev + + # let's make sure we know the companion device early on + # (synchronous request) + props = dbus.Interface(meshdev, 'org.freedesktop.DBus.Properties') + ethdev_o = props.Get(_NM_OLPC_MESH_IFACE, 'Companion') + self.ethdev = self._bus.get_object(_NM_SERVICE, ethdev_o) + + # asynchronously learn about mesh status + props.Get(_NM_DEVICE_IFACE, 'State', + reply_handler=self.__get_mesh_state_reply_cb, + error_handler=self.__get_state_error_cb) + # and companion + props = dbus.Interface(self.ethdev, 'org.freedesktop.DBus.Properties') + props.Get(_NM_DEVICE_IFACE, 'State', + reply_handler=self.__get_eth_state_reply_cb, + error_handler=self.__get_state_error_cb) + + # this is a stack of connections that we'll iterate through until + # we find one that works + self._connection_queue = [] + + self._bus.add_signal_receiver(self.__ethdev_state_changed_cb, + signal_name='StateChanged', + path=self.ethdev.object_path, + dbus_interface=_NM_DEVICE_IFACE) + + self._bus.add_signal_receiver(self.__mshdev_state_changed_cb, + signal_name='StateChanged', + path=self.meshdev.object_path, + dbus_interface=_NM_DEVICE_IFACE) + + self._idle_source = 0 + self._meshdev_state = DEVICE_STATE_UNKNOWN + self._ethdev_state = DEVICE_STATE_UNKNOWN + + if len(network.get_settings().connections) == 0: + # if there are no configured APs, start meshing immediately + self.start_automesh() + else: + # otherwise, start our timer system which basically looks for 10 + # seconds of inactivity on both devices, then starts automesh + self._idle_source = gobject.timeout_add_seconds(10, self._idle_check) + + def __get_state_error_cb(self, err): + logging.debug('Error getting the device state: %s', err) + + def __get_mesh_state_reply_cb(self, state): + self._meshdev_state = state + self._maybe_schedule_idle_check() + + def __get_eth_state_reply_cb(self, state): + self._ethdev_state = state + self._maybe_schedule_idle_check() + + def __ethdev_state_changed_cb(self, new_state, old_state, reason): + self._ethdev_state = new_state + self._maybe_schedule_idle_check() + + # If a connection is activated on the eth device, stop trying our + # automatic connections + if new_state >= DEVICE_STATE_PREPARE \ + and new_state <= DEVICE_STATE_ACTIVATED \ + and len(self._connection_queue) > 0: + self._connection_queue = [] + + def __mshdev_state_changed_cb(self, new_state, old_state, reason): + self._meshdev_state = new_state + self._maybe_schedule_idle_check() + + if new_state == DEVICE_STATE_FAILED: + # try next connection in queue + self._activate_from_queue() + elif new_state == DEVICE_STATE_ACTIVATED \ + and len(self._connection_queue) > 0: + # clear connection queue, we were successful + self._connection_queue = [] + + def _maybe_schedule_idle_check(self): + # if both devices are disconnected, wait 10 seconds for some activity + # and if nothing happens then we can start automatic meshing + if self._meshdev_state == DEVICE_STATE_DISCONNECTED \ + and self._ethdev_state == DEVICE_STATE_DISCONNECTED: + if self._idle_source != 0: + gobject.source_remove(self._idle_source) + self._idle_source = gobject.timeout_add_seconds(10, self._idle_check) + + def _idle_check(self): + if self._meshdev_state == DEVICE_STATE_DISCONNECTED \ + and self._ethdev_state == DEVICE_STATE_DISCONNECTED: + logging.debug("starting automesh due to inactivity") + self.start_automesh() + return False + + def _make_connection(self, channel, anycast_addr=None): + settings = Settings(wireless_cfg=OlpcMeshSettings(channel, anycast_addr)) + if not anycast_addr: + settings.ipv4_method = "link-local" + settings.connection.id = 'olpc-mesh-' + str(channel) + settings.connection.uuid = unique_id() + settings.connection.type = '802-11-olpc-mesh' + connection = network.add_connection(settings.connection.id, settings) + return connection + + def __activate_reply_cb(self, connection): + logging.debug('Connection activated: %s', connection) + + def __activate_error_cb(self, err): + logging.debug('Failed to activate connection: %s', err) + + def activate_connection(self, channel, anycast_addr=None): + logging.debug("activate channel %d anycast %s" % (channel, repr(anycast_addr))) + obj = self._bus.get_object(_NM_SERVICE, _NM_PATH) + netmgr = dbus.Interface(obj, _NM_IFACE) + connection = self._make_connection(channel, anycast_addr) + + netmgr.ActivateConnection(network.SETTINGS_SERVICE, connection.path, + self.meshdev.object_path, + self.meshdev.object_path, + reply_handler=self.__activate_reply_cb, + error_handler=self.__activate_error_cb) + + def _activate_from_queue(self): + if len(self._connection_queue) == 0: + return + + channel, anycast = self._connection_queue.pop() + self.activate_connection(channel, anycast) + + # Activate a mesh connection on a user-specified channel + # Looks for XS first, then resorts to simple mesh + def user_activate_channel(self, channel): + # Empties any existing queue in order to abort any connection attempt + # that was already happening + self._connection_queue = [ (channel, None), (channel, _XS_ANYCAST) ] + self._activate_from_queue() + + # Start meshing, intended for when there are no better networks to + # connect to. First looks for XS on all channels, then falls back to + # simple mesh on channel 1. + def start_automesh(self): + # Empties any existing queue in order to abort any connection attempt + # that was already happening + self._connection_queue = [ + (1, None), + (11, _XS_ANYCAST), (6, _XS_ANYCAST), (1, _XS_ANYCAST) + ] + self._activate_from_queue() +