From 8ce50e16d50a9993d61a9539c95540f127559460 Mon Sep 17 00:00:00 2001 From: Simon Schampijer Date: Mon, 07 Jun 2010 11:29:57 +0000 Subject: OLPC-Mesh support #2015 - backported from 0.88 - initial patch from Daniel Drake - fixes as well #1890 and #1883 --- diff --git a/extensions/deviceicon/network.py b/extensions/deviceicon/network.py index 14f3474..202c563 100644 --- a/extensions/deviceicon/network.py +++ b/extensions/deviceicon/network.py @@ -1,5 +1,5 @@ # -# Copyright (C) 2008 One Laptop Per Child +# Copyright (C) 2008-2010 One Laptop Per Child # Copyright (C) 2009 Tomeu Vizoso, Simon Schampijer # Copyright (C) 2009 Paraguay Educa, Martin Abente # @@ -37,6 +37,7 @@ from sugar.graphics.toolbutton import ToolButton from sugar.graphics.tray import TrayIcon from sugar.graphics import xocolor from sugar.util import unique_id +from sugar import profile from jarabe.model import network from jarabe.model.network import Settings @@ -52,6 +53,7 @@ _NM_PATH = '/org/freedesktop/NetworkManager' _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_SERIAL_IFACE = 'org.freedesktop.NetworkManager.Device.Serial' _NM_ACCESSPOINT_IFACE = 'org.freedesktop.NetworkManager.AccessPoint' _NM_ACTIVE_CONN_IFACE = 'org.freedesktop.NetworkManager.Connection.Active' @@ -79,7 +81,7 @@ class WirelessPalette(Palette): gobject.TYPE_NONE, ([])), } - def __init__(self, primary_text): + def __init__(self, primary_text, can_create=True): Palette.__init__(self, label=primary_text) self._disconnect_item = None @@ -110,21 +112,29 @@ class WirelessPalette(Palette): self._disconnect_item.connect('activate', self.__disconnect_activate_cb) self.menu.append(self._disconnect_item) - self._adhoc_item = gtk.MenuItem(_('Create new wireless network')) - self._adhoc_item.connect('activate', self.__adhoc_activate_cb) - self.menu.append(self._adhoc_item) - self._adhoc_item.show() + if can_create: + self._adhoc_item = gtk.MenuItem(_('Create new wireless network')) + self._adhoc_item.connect('activate', self.__adhoc_activate_cb) + self.menu.append(self._adhoc_item) + self._adhoc_item.show() 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 set_disconnected(self): self.props.primary_text = '' self.props.secondary_text = '' @@ -137,11 +147,14 @@ class WirelessPalette(Palette): def __adhoc_activate_cb(self, menuitem): self.emit('create-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): @@ -479,7 +492,8 @@ 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 else: self._icon.props.badge_name = None @@ -566,6 +580,128 @@ class WirelessDeviceView(ToolButton): def __activate_error_cb(self, err): logging.debug('Failed to create network: %s', err) +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.error('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): + text = _("Mesh Network") + " " + str(self._channel) + self._palette.props.primary_text = text + + def _update(self): + state = self._device_state + + if state in [network.DEVICE_STATE_PREPARE, + network.DEVICE_STATE_CONFIG, + network.DEVICE_STATE_NEED_AUTH, + 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' @@ -746,12 +882,16 @@ class GsmDeviceView(TrayIcon): self._palette.connection_time_label.set_text(text) class WirelessDeviceObserver(object): - def __init__(self, device, tray): + def __init__(self, device, tray, device_type): self._device = device self._device_view = None self._tray = tray - self._device_view = WirelessDeviceView(self._device) + if device_type == network.DEVICE_TYPE_802_11_WIRELESS: + self._device_view = WirelessDeviceView(self._device) + elif device_type == network.DEVICE_TYPE_802_11_OLPC_MESH: + self._device_view = OlpcMeshDeviceView(self._device) + self._tray.add_device(self._device_view) def disconnect(self): @@ -861,8 +1001,9 @@ class NetworkManagerObserver(object): if device_type == network.DEVICE_TYPE_802_3_ETHERNET: device = WiredDeviceObserver(nm_device, self._tray) self._devices[device_op] = device - elif device_type == network.DEVICE_TYPE_802_11_WIRELESS: - device = WirelessDeviceObserver(nm_device, self._tray) + elif device_type in [network.DEVICE_TYPE_802_11_WIRELESS, + network.DEVICE_TYPE_802_11_OLPC_MESH]: + device = WirelessDeviceObserver(nm_device, self._tray, device_type) self._devices[device_op] = device elif device_type == network.DEVICE_TYPE_GSM_MODEM: device = GsmDeviceObserver(nm_device, self._tray) diff --git a/src/jarabe/desktop/meshbox.py b/src/jarabe/desktop/meshbox.py index edefbfc..29a9cf1 100644 --- a/src/jarabe/desktop/meshbox.py +++ b/src/jarabe/desktop/meshbox.py @@ -36,6 +36,7 @@ from sugar.graphics.menuitem import MenuItem 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 @@ -51,17 +52,20 @@ from jarabe.model.network import Settings from jarabe.model.network import IP4Config from jarabe.model.network import WirelessSecurity from jarabe.model.network import AccessPoint +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 WirelessNetworkView(CanvasPulsingIcon): def __init__(self, initial_ap): @@ -139,7 +143,7 @@ class WirelessNetworkView(CanvasPulsingIcon): and self._name[-15] == '#' 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) @@ -216,9 +220,9 @@ class WirelessNetworkView(CanvasPulsingIcon): if self._mode == network.NM_802_11_MODE_INFRA: 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: @@ -443,6 +447,17 @@ class WirelessNetworkView(CanvasPulsingIcon): return None return self._access_points[ap_path] + def is_olpc_mesh(self): + return self._mode == network.NM_802_11_MODE_ADHOC \ + and self.name == "olpc-mesh" + + def remove_all_aps(self): + for ap in self._access_points.values(): + ap.disconnect() + self._access_points = {} + self._active_ap = None + self.update_strength() + def disconnect(self): self._bus.remove_signal_receiver(self.__device_state_changed_cb, signal_name='StateChanged', @@ -454,6 +469,146 @@ class WirelessNetworkView(CanvasPulsingIcon): 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.mesh_device + + 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): + _palette = palette.Palette(_("Mesh Network %d") % self._channel) + + self._connect_item = MenuItem(_('Connect'), 'dialog-ok') + self._connect_item.connect('activate', self.__connect_activate_cb) + _palette.menu.append(self._connect_item) + + return _palette + + def __get_device_state_reply_cb(self, state): + self._device_state = state + self._update() + + def __get_device_state_error_cb(self, err): + logging.error('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.error('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 in [network.DEVICE_STATE_PREPARE, + network.DEVICE_STATE_CONFIG, + network.DEVICE_STATE_NEED_AUTH, + 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 __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.error('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) @@ -752,6 +907,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) @@ -764,6 +921,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.disable_olpc_mesh(device) class MeshBox(gtk.VBox): @@ -779,7 +943,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 = '' @@ -924,6 +1088,14 @@ class MeshBox(gtk.VBox): del self.wireless_networks[hash] def _ap_props_changed_cb(self, ap, old_hash): + # if we have mesh hardware, ignore OLPC mesh networks that appear as + # normal wifi networks + if len(self._mesh) > 0 and ap.mode == network.NM_802_11_MODE_ADHOC \ + and ap.name == "olpc-mesh": + logging.debug("ignoring OLPC mesh IBSS") + ap.disconnect() + return + if old_hash is None: # new AP finished initializing self._add_ap_to_network(ap) return @@ -958,18 +1130,49 @@ class MeshBox(gtk.VBox): self._remove_net_if_empty(net, ap.network_hash()) return - logging.error('Can not remove access point %s', ap_o) + # it's not an error if the AP isn't found, since we might have ignored + # it (e.g. olpc-mesh adhoc network) + logging.debug('Can not remove access point %s' % ap_o) + + 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, mesh_device): + mesh_mgr = OlpcMeshManager(mesh_device) + self._add_olpc_mesh_icon(mesh_mgr, 1) + self._add_olpc_mesh_icon(mesh_mgr, 6) + self._add_olpc_mesh_icon(mesh_mgr, 11) + + # the OLPC mesh can be recognised as a "normal" wifi network. remove + # any such normal networks if they have been created + for hash, net in self.wireless_networks.iteritems(): + if not net.is_olpc_mesh(): + continue + + logging.debug("removing OLPC mesh IBSS") + net.remove_all_aps() + net.disconnect() + self._layout.remove(net) + del self.wireless_networks[hash] + + def disable_olpc_mesh(self, mesh_device): + for icon in self._mesh: + icon.disconnect() + self._layout.remove(icon) + self._mesh = [] def suspend(self): if not self._suspended: self._suspended = True - for net in self.wireless_networks.values(): + for net in self.wireless_networks.values() + self._mesh: net.props.paused = True def resume(self): if self._suspended: self._suspended = False - for net in self.wireless_networks.values(): + for net in self.wireless_networks.values() + self._mesh: net.props.paused = False def _toolbar_query_changed_cb(self, toolbar, query): diff --git a/src/jarabe/model/Makefile.am b/src/jarabe/model/Makefile.am index 399db65..18d44da 100644 --- a/src/jarabe/model/Makefile.am +++ b/src/jarabe/model/Makefile.am @@ -6,6 +6,7 @@ sugar_PYTHON = \ filetransfer.py \ friends.py \ invites.py \ + olpcmesh.py \ owner.py \ neighborhood.py \ network.py \ diff --git a/src/jarabe/model/network.py b/src/jarabe/model/network.py index 9de954a..4389b87 100644 --- a/src/jarabe/model/network.py +++ b/src/jarabe/model/network.py @@ -33,6 +33,7 @@ from sugar.util import unique_id DEVICE_TYPE_802_3_ETHERNET = 1 DEVICE_TYPE_802_11_WIRELESS = 2 DEVICE_TYPE_GSM_MODEM = 3 +DEVICE_TYPE_802_11_OLPC_MESH = 6 DEVICE_STATE_UNKNOWN = 0 DEVICE_STATE_UNMANAGED = 1 @@ -117,6 +118,8 @@ class WirelessSecurity(object): return wireless_security class Wireless(object): + nm_name = "802-11-wireless" + def __init__(self): self.ssid = None self.security = None @@ -133,6 +136,23 @@ class Wireless(object): wireless['band'] = self.band 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 @@ -199,16 +219,21 @@ class Gsm(object): return gsm class Settings(object): - def __init__(self): + def __init__(self, wireless_cfg=None): self.connection = Connection() self.wireless = Wireless() self.ip4_config = 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() diff --git a/src/jarabe/model/olpcmesh.py b/src/jarabe/model/olpcmesh.py new file mode 100644 index 0000000..114bf22 --- /dev/null +++ b/src/jarabe/model/olpcmesh.py @@ -0,0 +1,214 @@ +# Copyright (C) 2009-2010 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, mesh_device): + self._bus = dbus.SystemBus() + + self.mesh_device = mesh_device + self.eth_device = self._get_companion_device() + + self._connection_queue = [] + """Stack of connections that we'll iterate through until we find one + that works. + + """ + + props = dbus.Interface(self.mesh_device, + 'org.freedesktop.DBus.Properties') + props.Get(_NM_DEVICE_IFACE, 'State', + reply_handler=self.__get_mesh_state_reply_cb, + error_handler=self.__get_state_error_cb) + + props = dbus.Interface(self.eth_device, + '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) + + self._bus.add_signal_receiver(self.__eth_device_state_changed_cb, + signal_name='StateChanged', + path=self.eth_device.object_path, + dbus_interface=_NM_DEVICE_IFACE) + + self._bus.add_signal_receiver(self.__mshdev_state_changed_cb, + signal_name='StateChanged', + path=self.mesh_device.object_path, + dbus_interface=_NM_DEVICE_IFACE) + + self._idle_source = 0 + self._mesh_device_state = DEVICE_STATE_UNKNOWN + self._eth_device_state = DEVICE_STATE_UNKNOWN + + if self._have_configured_connections(): + self._start_automesh_timer() + else: + self._start_automesh() + + def _get_companion_device(self): + props = dbus.Interface(self.mesh_device, + 'org.freedesktop.DBus.Properties') + eth_device_o = props.Get(_NM_OLPC_MESH_IFACE, 'Companion') + return self._bus.get_object(_NM_SERVICE, eth_device_o) + + def _have_configured_connections(self): + return len(network.get_settings().connections) > 0 + + def _start_automesh_timer(self): + """Start our timer system which basically looks for 10 seconds of + inactivity on both devices, then starts automesh. + + """ + if self._idle_source != 0: + gobject.source_remove(self._idle_source) + 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._mesh_device_state = state + self._maybe_schedule_idle_check() + + def __get_eth_state_reply_cb(self, state): + self._eth_device_state = state + self._maybe_schedule_idle_check() + + def __eth_device_state_changed_cb(self, new_state, old_state, reason): + """If a connection is activated on the eth device, stop trying our + automatic connections. + + """ + self._eth_device_state = new_state + self._maybe_schedule_idle_check() + + 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._mesh_device_state = new_state + self._maybe_schedule_idle_check() + + if new_state == DEVICE_STATE_FAILED: + self._try_next_connection_from_queue() + elif new_state == DEVICE_STATE_ACTIVATED \ + and len(self._connection_queue) > 0: + self._empty_connection_queue() + + def _maybe_schedule_idle_check(self): + if self._mesh_device_state == DEVICE_STATE_DISCONNECTED \ + and self._eth_device_state == DEVICE_STATE_DISCONNECTED: + self._start_automesh_timer() + + def _idle_check(self): + if self._mesh_device_state == DEVICE_STATE_DISCONNECTED \ + and self._eth_device_state == DEVICE_STATE_DISCONNECTED: + logging.debug("starting automesh due to inactivity") + self._start_automesh() + return False + + def _make_connection(self, channel, anycast_address=None): + wireless_config = OlpcMeshSettings(channel, anycast_address) + settings = Settings(wireless_cfg=wireless_config) + if not anycast_address: + settings.ip4_config = network.IP4Config() + settings.ip4_config.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.error('Failed to activate connection: %s', err) + + def _activate_connection(self, channel, anycast_address=None): + logging.debug("activate channel %d anycast %r", + channel, anycast_address) + proxy = self._bus.get_object(_NM_SERVICE, _NM_PATH) + network_manager = dbus.Interface(proxy, _NM_IFACE) + connection = self._make_connection(channel, anycast_address) + + network_manager.ActivateConnection(network.SETTINGS_SERVICE, + connection.path, + self.mesh_device.object_path, + self.mesh_device.object_path, + reply_handler=self.__activate_reply_cb, + error_handler=self.__activate_error_cb) + + def _try_next_connection_from_queue(self): + if len(self._connection_queue) == 0: + return + + channel, anycast = self._connection_queue.pop() + self._activate_connection(channel, anycast) + + def _empty_connection_queue(self): + self._connection_queue = [] + + def user_activate_channel(self, channel): + """Activate a mesh connection on a user-specified channel. + Looks for XS first, then resorts to simple mesh.""" + self._empty_connection_queue() + self._connection_queue.append((channel, None)) + self._connection_queue.append((channel, _XS_ANYCAST)) + self._try_next_connection_from_queue() + + def _start_automesh(self): + """Start meshing automatically, intended 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.""" + self._empty_connection_queue() + self._connection_queue.append((1, None)) + self._connection_queue.append((11, _XS_ANYCAST)) + self._connection_queue.append((6, _XS_ANYCAST)) + self._connection_queue.append((1, _XS_ANYCAST)) + self._try_next_connection_from_queue() + -- cgit v0.9.1