/* This source file is part of the ATMEL AVR-UC3-SoftwareFramework-1.7.0 Release */ /*! \page License * Copyright (C) 2009, H&D Wireless AB All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * 1. Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * * 2. Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * 3. The name of H&D Wireless AB may not be used to endorse or promote products derived * from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY H&D WIRELESS AB ``AS IS'' AND ANY EXPRESS OR IMPLIED * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE EXPRESSLY AND * SPECIFICALLY DISCLAIMED. IN NO EVENT SHALL ATMEL BE LIABLE FOR ANY DIRECT, * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include "wl_cm.h" #include "util.h" #include #include "debug.h" /** Roaming configuration parameters **/ /*! The ROAMING_RSSI_THRESHOLD setting defines how bad the current * signal strength should be before we'll consider roaming to an AP * with better signal strength. The objective is to stay on the * current AP as long as the RSSI is decent, even if there are other * APs in the same BSS with better RSSI available. * If ROAMING_RSSI_THRESHOLD is too high we might roam unecessarily. * If ROAMING_RSSI_THRESHOLD is too low we might not roam in time to * avoid packet loss. This also impacts power consumption, staying * too long with an AP with poor RSSI will consume more power. * Unit is dBm. */ #define ROAMING_RSSI_THRESHOLD -65 /*! The ROAMING_RSSI_DIFF setting defines how much better * than the currently associated AP a new AP must be before * we'll attempt to roam over to the new AP. * If ROAMING_RSSI_DIFF is too high it might be too hard * to roam (important if the STA is expected to move * quickly through different AP coverage areas). * If ROAMING_RSSI_DIFF is too low we might bounce between * two APs with similar signal strengths. * Unit is dBm. */ #define ROAMING_RSSI_DIFF 10 # include "printf-stdarg.h" #include "ard_utils.h" #include "debug.h" /** \defgroup wl_cm Connection Manager * * These functions are used to configure and control the WiFi connetion * manager. * * * @{ */ struct cm_candidate { struct wl_ssid_t ssid; struct wl_mac_addr_t bssid; }; struct cm { cm_scan_cb_t *scan_cb; cm_conn_cb_t *conn_cb; cm_disconn_cb_t *disconn_cb; void* ctx; uint8_t enabled; struct cm_candidate candidate; }; /** * This function can be modified to pick a network based on * application specific criteria. * * If the SSID can not be found in the scan list it will be * assumed to be a hidden SSID and the wl_connect() command * will be called to attempt to probe for the network and * connect to it. */ static struct wl_network_t* find_best_candidate(struct cm* cm) { struct wl_network_list_t* netlist; struct wl_network_t *best_net = NULL; uint8_t i; if (wl_get_network_list(&netlist) != WL_SUCCESS) return NULL; if (netlist->cnt == 0) return NULL; for (i = 0; i < netlist->cnt; i++) { /* match on ssid */ if (cm->candidate.ssid.len) if (!equal_ssid(&cm->candidate.ssid, &netlist->net[i]->ssid)) continue; /* match bssid */ if (strncmp((char*) cm->candidate.bssid.octet, "\xff\xff\xff\xff\xff\xff", 6)) if (!equal_bssid(&cm->candidate.bssid, &netlist->net[i]->bssid)) continue; /* check for best rssi. */ if ( best_net && ( best_net->rssi > netlist->net[i]->rssi) ) { continue; } best_net = netlist->net[i]; } return best_net; } /** * */ static void select_net(struct cm* cm) { struct wl_network_t *candidate_net; struct wl_network_t *current_net; struct wl_ssid_t *ssid_p; int ret; /* Nothing to do */ if (0 == cm->candidate.ssid.len) { return; } current_net = wl_get_current_network(); candidate_net = find_best_candidate(cm); /* Connected to the candidate? ... */ if ( current_net == candidate_net ) { if ( current_net ) { /* ...yes, dont change. */ return; } } /* Roaming checks */ if (current_net && candidate_net) { /* Are we changing BSSs? */ if ( equal_ssid(&candidate_net->ssid, ¤t_net->ssid)) { /* ...no. Does the currently connected * net have a decent RSSI?...*/ if ( current_net->rssi > ROAMING_RSSI_THRESHOLD ) { /* ...yes, stay with it. */ return; } /* ...no. Does the candidate have * sufficiently better RSSI to * motivate a switch to it? */ if ( candidate_net->rssi < current_net->rssi + ROAMING_RSSI_DIFF) { return; } /* ...yes, try to roam to candidate_net */ CM_DPRINTF("CM: Roaming from rssi %d to %d\n", current_net->rssi, candidate_net->rssi); } } /* a candidate is found */ if (candidate_net) { /* We connect to a specific bssid here because * find_best_candidate() might have picked a * particulare AP among many with the same SSID. * wl_connect() would pick one of them at random. */ ret = wl_connect_bssid(candidate_net->bssid); } /* no candidate found */ else { CM_DPRINTF("CM: No candidate found for ssid \"%s\"\n", ssid2str(&cm->candidate.ssid)); /* Might be a hidden SSID so we try to connect to it. * wl_connect() will trigger a directed scan * for the SSID in this case. */ ssid_p = &cm->candidate.ssid; ret = wl_connect(ssid_p->ssid, ssid_p->len); } switch (ret) { case WL_SUCCESS : return; case WL_BUSY: wl_disconnect(); return; case WL_RETRY: break; default : CM_DPRINTF("CM: failed to connect\n"); break; } /* some operation failed or no candidate found */ if (wl_scan() != WL_SUCCESS) CM_DPRINTF("CM: failed to scan\n"); } /** * */ static void wl_scan_complete_cb(void* ctx) { struct cm *cm = ctx; CM_DPRINTF("CM: scan completed\n"); if (cm->scan_cb) cm->scan_cb(cm->ctx); if ( 0 == cm->enabled ) { return; } select_net(cm); } /** * */ static void wl_media_connected_cb(void* ctx) { struct cm *cm = ctx; struct wl_network_t *net = wl_get_current_network(); CM_DPRINTF("CM: connected to %s\n", ssid2str(&net->ssid)); LINK_LED_ON(); ERROR_LED_OFF(); if (cm->conn_cb) cm->conn_cb(net, cm->ctx); } /** * */ static void wl_conn_failure_cb(void* ctx) { struct cm *cm = ctx; CM_DPRINTF("CM: connect failed, scanning\n"); ERROR_LED_ON(); LINK_LED_OFF(); if ( 0 == cm->enabled ) { return; } if (wl_scan() != WL_SUCCESS) /* should never happen */ CM_DPRINTF("CM: could not start scan after connect fail!\n"); } /** * */ static void wl_conn_lost_cb(void* ctx) { struct cm *cm = ctx; CM_DPRINTF("CM: connection lost, scanning\n"); LINK_LED_OFF(); if (cm->disconn_cb) cm->disconn_cb(cm->ctx); if ( 0 == cm->enabled ) { return; } if (wl_scan() != WL_SUCCESS) /* should never happen */ CM_DPRINTF("CM: could not start scan after connect lost!\n"); } /** * */ static void wl_event_cb(struct wl_event_t event, void* ctx) { struct cm *cm = ctx; switch (event.id) { case WL_EVENT_MEDIA_CONNECTED: wl_media_connected_cb(cm); break; case WL_EVENT_CONN_FAILURE: wl_conn_failure_cb(cm); break; case WL_EVENT_MEDIA_DISCONNECTED: CM_DPRINTF("CM: disconnected\n"); wl_conn_lost_cb(cm); break; case WL_EVENT_SCAN_COMPLETE: wl_scan_complete_cb(cm); break; default: CM_DPRINTF("CM: unhandled event\n"); }; } static struct cm *cm = NULL; /** * Doesn't actually start the CM, just initializing. CM will run whenever * an valid ssid is set through wl_cm_set_network() and wl_cm_start() * has been called. */ wl_err_t wl_cm_init(cm_scan_cb_t scan_cb, cm_conn_cb_t conn_cb, cm_disconn_cb_t disconn_cb, void* ctx) { if (cm != NULL) return WL_FAILURE; cm = calloc(1, sizeof(struct cm)); if (cm == NULL) { CM_DPRINTF("CM: out of memory\n"); return WL_FAILURE; } if (wl_register_event_cb(wl_event_cb, cm) != WL_SUCCESS) { CM_DPRINTF("CM: could not register event cb\n"); return WL_FAILURE; } cm->scan_cb = scan_cb; cm->conn_cb = conn_cb; cm->disconn_cb = disconn_cb; cm->enabled = 0; cm->ctx = ctx; CM_DPRINTF("CM: initialized\n"); return WL_SUCCESS; } wl_err_t wl_cm_start(void) { if (NULL == cm) return WL_FAILURE; cm->enabled = 1; return WL_SUCCESS; } wl_err_t wl_cm_stop(void) { if (NULL == cm) return WL_FAILURE; cm->enabled = 0; return WL_SUCCESS; } /** * Set the desired network which the connection manager should try to * connect to. * * The ssid and bssid of the desired network should be specified. The ssid and * bssid will be matched against the networks found during scan. If any * parameter is null, it will always match. If both parameters are null, * the first found network will be chosen. * * @param ssid The ssid of the desired network. If null, any ssid will match. * @param bssid The bssid of the desired network. If null, any bssid will match. * */ wl_err_t wl_cm_set_network(struct wl_ssid_t *ssid, struct wl_mac_addr_t *bssid) { if (cm == NULL) return WL_FAILURE; if (ssid) memcpy(&cm->candidate.ssid, ssid, sizeof(cm->candidate.ssid)); else cm->candidate.ssid.len = 0; if (bssid) memcpy(&cm->candidate.bssid, bssid, sizeof(cm->candidate.bssid)); else memset(&cm->candidate.bssid, 0xff, sizeof(cm->candidate.bssid)); if (cm->candidate.ssid.len) wl_scan(); return WL_SUCCESS; } /* * @} */