/** * @file SFE_CC3000.cpp * @brief Library for the SparkFun CC3000 shield and breakout boards * @author Shawn Hymel (SparkFun Electronics) * @author Revisions by Jacob Rosenthal (https://github.com/jacobrosenthal) * * @copyright This code is public domain but you buy me a beer if you use * this and we meet someday (Beerware license). * * This library interfaces the TI CC3000 to Arduino over SPI. The library relies * on the Arduino built-in SPI commands. To use the library, instantiate an * SFE_CC3000 object, call the init() function, and then call connect() with the * necessary connection details. */ #include #include #include "common.h" #include "SFE_CC3000.h" #include "SFE_CC3000_Callbacks.h" #include "SFE_CC3000_SPI.h" #include "hci.h" #include "netapp.h" #include "nvmem.h" #include "socket.h" #include "wlan.h" /* Global variables */ uint8_t g_int_pin; uint8_t g_int_num; uint8_t g_en_pin; uint8_t g_cs_pin; bool g_socket_connected; uint8_t g_saved_data_mode; uint8_t g_saved_bit_order; uint8_t g_saved_clock_div; #if (DEBUG == 1) volatile long g_debug_interrupt; #endif volatile unsigned long ulSmartConfigFinished; volatile unsigned long ucStopSmartConfig; volatile unsigned long ulCC3000Connected; volatile unsigned long ulCC3000DHCP; volatile unsigned long ulCC3000DHCP_configured; volatile unsigned long OkToDoShutDown; netapp_pingreport_args_t g_ping_report = {0}; /** * @brief Constructor - Instantiates SFE_CC3000 object * * @param[in] int_pin pin needed for MCU interrupt * @param[in] en_pin pin used for CC3000 enable * @param[in] cs_pin pin for SPI chip select */ SFE_CC3000::SFE_CC3000(uint8_t int_pin, uint8_t en_pin, uint8_t cs_pin) { /* Set initialization state */ is_initialized_ = false; /* Initialize access point scan variables */ num_access_points_ = 0; access_point_count_ = 0; /* Initialize status global variables */ ulSmartConfigFinished = 0; ucStopSmartConfig = 0; ulCC3000Connected = 0; ulCC3000DHCP = 0; ulCC3000DHCP_configured = 0; OkToDoShutDown = 0; g_socket_connected = false; #if (DEBUG == 1) g_debug_interrupt = 0; #endif /* Set pin definitions */ g_int_pin = int_pin; g_en_pin = en_pin; g_cs_pin = cs_pin; } /** * @brief Destructor */ SFE_CC3000::~SFE_CC3000() { } /** * @brief Configure SPI for MCU to CC3000 * * @return True if SPI initialization completed successfully. False otherwise. */ bool SFE_CC3000::init() { /* Check if CC3000 SPI is already initialized */ if (is_initialized_) { return true; } /* Determine available interrupt pins on supported microcontrollers */ #if defined(__AVR_ATmega8__) || defined(__AVR_ATmega168__) || \ defined(__AVR_ATmega328P__) || defined (__AVR_ATmega328__) switch (g_int_pin) { case 2: g_int_num = 0; break; case 3: g_int_num = 1; break; default: # if (DEBUG == 1) Serial.println("ERROR: Interrupt line not attached to pin 2 or 3"); # endif return false; } #elif defined(__AVR_ATmega1281__) || defined(__AVR_ATmega2561__) || \ defined(__AVR_ATmega2560__) || defined(__AVR_ATmega1280__) switch (g_int_pin) { case 2: g_int_num = 0; break; case 3: g_int_num = 1; break; case 18: g_int_num = 5; break; case 19: g_int_num = 4; break; case 20: g_int_num = 3; break; case 21: g_int_num = 2; break; default: # if (DEBUG == 1) Serial.println("ERROR: Interrupt not pin 2, 3, 18, 19, 20, or 21"); # endif return false; } #elif defined(__AVR_ATmega32U4__) switch (g_int_pin) { case 3: g_int_num = 0; break; case 2: g_int_num = 1; break; case 0: g_int_num = 2; break; case 1: g_int_num = 3; break; case 7: g_int_num = 4; break; default: # if (DEBUG == 1) Serial.println("ERROR: Interrupt not pin 0, 1, 2, 3, or 7"); # endif return false; } #else # if (DEBUG == 1) Serial.println("ERROR: Microcontroller not supported"); # endif return false; #endif /* Initialize interrupt, CS, and enable pins */ pinMode(g_int_pin, INPUT); pinMode(g_en_pin, OUTPUT); pinMode(g_cs_pin, OUTPUT); digitalWrite(g_en_pin, LOW); /* Setup SPI */ SPI.begin(); SPI.setDataMode(SPI_MODE1); SPI.setBitOrder(MSBFIRST); SPI.setClockDivider(SPI_CLK_DIV); /* De-assert CS */ digitalWrite(g_cs_pin, HIGH); /* Initialize CC3000 library - provide callback definitions */ wlan_init( cc3000AsyncCallback, sendFirmwarePatch, sendDriverPatch, sendBootLoaderPatch, readWlanInterruptPin, enableWlanInterrupt, disableWlanInterrupt, writeWlanPin); /* CC3000 needs a delay before starting WLAN or it gets stuck sometimes */ delay(100); /* Start CC3000 - asserts enable pin and blocks until init is complete */ wlan_start(0); /* Mask out non-required events */ wlan_set_event_mask(HCI_EVNT_WLAN_KEEPALIVE | HCI_EVNT_WLAN_UNSOL_INIT); is_initialized_ = true; return true; } /** * @brief Reads the firmware version from the CC3000 * * @param[out] fw_ver firmware version in 2 bytes. [0] is major and [1] is minor * @return True is firmware could be read from the CC3000. False otherwise. */ bool SFE_CC3000::getFirmwareVersion(unsigned char *fw_ver) { /* If CC3000 is not initialized, return false. */ if (!getInitStatus()) { return false; } /* Read firmware version from the CC3000 */ if (nvmem_read_sp_version(fw_ver) != CC3000_SUCCESS) { return false; } return true; } /** * @brief Reads the MAC address from the CC3000 * * @param[out] mac_addr six char buffer containing the MAC address * @return True if MAC address could be read from the CC3000. False otherwise. */ bool SFE_CC3000::getMacAddress(unsigned char *mac_addr) { /* If CC3000 is not initialized, return false. */ if (!getInitStatus()) { return false; } /* Read MAC address from the CC3000 */ if (nvmem_get_mac_address(mac_addr) != CC3000_SUCCESS) { return false; } return true; } /** * @brief Scans area for access points. Blocks operation to allow for scan. * * To scan for APs, first call scanAccessPoints() with an appropriate scan time * (recommended scan_time = 4000ms). Create an AccessPointInfo struct and pass * that to getNextAccessPoint(). Continue to call getNextAccessPoint() until it * returns false (there are no more APs to scan). * * @param[in] scan_time time to scan networks in milliseconds * @return True if scan succeeded. False otherwise. */ bool SFE_CC3000::scanAccessPoints(unsigned int scan_time) { int i; unsigned long channel_timeouts[SCAN_NUM_CHANNELS]; /* If CC3000 is not initialized, return false. */ if (!getInitStatus()) { return false; } /* Create channel interval list for AP scanning */ for (i = 0; i < SCAN_NUM_CHANNELS; i++) { channel_timeouts[0] = SCAN_CHANNEL_TIMEOUT; } /* Setup access point scan */ if (wlan_ioctl_set_scan_params( scan_time, SCAN_MIN_DWELL_TIME, SCAN_MAX_DWELL_TIME, SCAN_NUM_PROBE_REQS, SCAN_CHANNEL_MASK, SCAN_RSSI_THRESHOLD, SCAN_NSR_THRESHOLD, SCAN_DEFAULT_TX_POWER, channel_timeouts ) != CC3000_SUCCESS) { return false; } /* Wait for scan to complete */ delay(scan_time + 500); /* Re-initialize AP counters */ num_access_points_ = 0; access_point_count_ = 0; /* Get first scan result in order to obtain the total number of APs */ if (wlan_ioctl_get_scan_results(0, (unsigned char *)&ap_scan_result_) != CC3000_SUCCESS ){ return false; } num_access_points_ = ap_scan_result_.num_networks; /* Stop scan */ if (wlan_ioctl_set_scan_params( 0, SCAN_MIN_DWELL_TIME, SCAN_MAX_DWELL_TIME, SCAN_NUM_PROBE_REQS, SCAN_CHANNEL_MASK, SCAN_RSSI_THRESHOLD, SCAN_NSR_THRESHOLD, SCAN_DEFAULT_TX_POWER, channel_timeouts ) != CC3000_SUCCESS) { return false; } return true; } /** * @brief Fills out AP info struct with next access data. * * To scan for APs, first call scanAccessPoints() with an appropriate scan time * (recommended scan_time = 4000ms). Create an AccessPointInfo struct and pass * that to getNextAccessPoint(). Continue to call getNextAccessPoint() until it * returns false (there are no more APs to scan). * * @param[out] ap_info struct containing information about the next AP * @return True if next AP obtained. False if no more APs available. */ bool SFE_CC3000::getNextAccessPoint(AccessPointInfo &ap_info) { /* If CC3000 is not initialized, return false. */ if (!getInitStatus()) { return false; } /* If results are invalid (e.g. no results), return false */ if (!ap_scan_result_.is_valid) { return false; } /* If we have exhausted all of the networks to list, return false */ if (access_point_count_ >= num_access_points_) { return false; } /* Fill out AP info with last AP surveyed */ ap_info.rssi = ap_scan_result_.rssi; ap_info.security_mode = ap_scan_result_.security_mode; strncpy(ap_info.ssid, (char *)ap_scan_result_.ssid, ap_scan_result_.ssid_length); ap_info.ssid[ap_scan_result_.ssid_length] = '\0'; memcpy(ap_info.bssid, ap_scan_result_.bssid, BSSID_LENGTH); /* Get next set of results */ if (wlan_ioctl_get_scan_results(0, (unsigned char *)&ap_scan_result_) != CC3000_SUCCESS ) { return false; } /* Increment AP counter */ access_point_count_++; return true; } /** * @brief Connects to a WAP using the given SSID and password * * @param[in] ssid the SSID for the wireless network * @param[in] security type of security for the network * @param[in] password optional ASCII password if connecting to a secured AP * @param[in] timeout optional time (ms) to wait before stopping. 0 = no timeout * @return True if connected to wireless network. False otherwise. */ bool SFE_CC3000::connect( char *ssid, unsigned int security, char *password, unsigned int timeout) { unsigned long time; /* If CC3000 is not initialized, return false. */ if (!getInitStatus()) { return false; } /* If already connected, return false. */ if (getDHCPStatus()) { return false; } /* If security mode is not a predefined type, return false. */ if ( !( security == WLAN_SEC_UNSEC || security == WLAN_SEC_WEP || security == WLAN_SEC_WPA || security == WLAN_SEC_WPA2) ) { return false; } /* Set connection profile to manual (no fast or auto connect) */ if (wlan_ioctl_set_connection_policy(DISABLE, DISABLE, DISABLE) != CC3000_SUCCESS) { return false; } /* Connect to the given access point*/ time = millis(); while (getConnectionStatus() == false) { /* Attempt to connect to an AP */ delay(10); if (security == WLAN_SEC_UNSEC) { if (wlan_connect( WLAN_SEC_UNSEC, ssid, strlen(ssid), 0, 0, 0) == CC3000_SUCCESS) { break; } } else { if (wlan_connect( security, ssid, strlen(ssid), 0, (unsigned char*)password, strlen(password)) == CC3000_SUCCESS) { break; } } /* Check against timeout. Return if out of time. */ if (timeout != 0) { if ( (millis() - time) > timeout ) { return false; } } } /* Wait for DHCP */ while (getDHCPStatus() == false) { if (timeout != 0) { if ( (millis() - time) > timeout ) { return false; } } } /* Get connection information */ netapp_ipconfig(&connection_info_); return true; } /** * @brief Begins SmartConfig. The user needs to run the SmartConfig phone app. * * @param[in] timeout optional time (ms) to wait before stopping. 0 = no timeout * @return True if connected to wireless network. False otherwise. */ bool SFE_CC3000::startSmartConfig(unsigned int timeout) { char cc3000_prefix[] = {'T', 'T', 'T'}; unsigned long time; /* Reset all global connection variables */ ulSmartConfigFinished = 0; ulCC3000Connected = 0; ulCC3000DHCP = 0; OkToDoShutDown=0; /* If CC3000 is not initialized, return false. */ if (!getInitStatus()) { return false; } /* Set connection profile to manual (no fast or auto connect) */ if (wlan_ioctl_set_connection_policy(DISABLE, DISABLE, DISABLE) != CC3000_SUCCESS) { return false; } /* Delete old connection profiles */ if (wlan_ioctl_del_profile(255) != CC3000_SUCCESS) { return false; } /* Wait until CC3000 is disconnected */ while (getConnectionStatus()) { delay(1); } /* Sets the prefix for SmartConfig. Should always be "TTT" */ if (wlan_smart_config_set_prefix((char*)cc3000_prefix) != CC3000_SUCCESS) { return false; } /* Start the SmartConfig process */ if (wlan_smart_config_start(0) != CC3000_SUCCESS) { return false; } /* Wait for SmartConfig to complete */ time = millis(); while (ulSmartConfigFinished == 0) { if (timeout != 0) { if ( (millis() - time) > timeout ) { return false; } } } /* Configure to connect automatically to AP from SmartConfig process */ if (wlan_ioctl_set_connection_policy(DISABLE, DISABLE, ENABLE) != CC3000_SUCCESS) { return false; } /* Reset CC3000 */ wlan_stop(); delay(400); wlan_start(0); /* Wait for connection and DHCP-assigned IP address */ while (getDHCPStatus() == false) { if (timeout != 0) { if ( (millis() - time) > timeout ) { return false; } } } /* If we make it this far, we need to tell the SmartConfig app to stop */ mdnsAdvertiser(1, DEVICE_NAME, strlen(DEVICE_NAME)); /* Get connection information */ netapp_ipconfig(&connection_info_); return true; } /** * @brief Attempts to connect to network stored in memory. * * FastConnect attempts to connect to the WiFi network (AP) stored in non- * volatile memory. The user needs to run SmartConfig first in order to create * a network profile in memory before running FastConnect. * * @param[in] timeout optional time (ms) to wait before stopping. 0 = no timeout * @return True if connected to wireless network. False otherwise. */ bool SFE_CC3000::fastConnect(unsigned int timeout) { unsigned long time; /* Reset all global connection variables */ ulSmartConfigFinished = 0; ulCC3000Connected = 0; ulCC3000DHCP = 0; OkToDoShutDown=0; /* If CC3000 is not initialized, return false. */ if (!getInitStatus()) { return false; } /* Set connection profile to auto-connect */ if (wlan_ioctl_set_connection_policy(DISABLE, DISABLE, ENABLE) != CC3000_SUCCESS) { return false; } /* Wait for connection and DHCP-assigned IP address */ time = millis(); while (getDHCPStatus() == false) { if (timeout != 0) { if ( (millis() - time) > timeout ) { return false; } } } /* Get connection information */ netapp_ipconfig(&connection_info_); return true; } /** * @brief Disconnects from the AP * * @return True if disconnected successfully. False otherwise. */ bool SFE_CC3000::disconnect() { /* If CC3000 is not initialized, return false. */ if (!getInitStatus()) { return false; } /* Attempt to disconnect from the network */ if (wlan_disconnect() == CC3000_SUCCESS) { return true; } else { return false; } } /** * @brief Looks up the IP address of a given hostname * * @param[in] hostname the name of the host or website (e.g. www.google.com) * @param[out] ip_address returned IP address of the hostname * @return True if lookup completed successfully. False otherwise. */ bool SFE_CC3000::dnsLookup(char *hostname, IPAddress *ip_address) { unsigned long ret_ip_addr = 0; /* If CC3000 is not initialized, return false. */ if (!getInitStatus()) { return false; } /* If not connected, return false. */ if (!getConnectionStatus()) { return false; } /* If DHCP has not been assigned, return false. */ if (!getDHCPStatus()) { return false; } /* Attempt to get IP address by hostname */ if (!gethostbyname(hostname, strlen(hostname), &ret_ip_addr)) { return false; } *ip_address = IPAddress( (uint8_t)(ret_ip_addr >> 24) & 0xFF, (uint8_t)(ret_ip_addr >> 16) & 0xFF, (uint8_t)(ret_ip_addr >> 8) & 0xFF, (uint8_t)ret_ip_addr & 0xFF); return true; } /** * @brief Pings IP address [attempts] times and returns a ping report * * @param[in] ip_address the IP address to ping * @param[out] ping_report returned ping report with statistics * @param[in] attempts optional number of times to ping the address * @param[in] size optional size of ping buffer (up to 1400 bytes) * @param[in] timeout optional time to wait for ping response (milliseconds) * @return True if ping command succeeded. False otherwise. */ bool SFE_CC3000::ping( IPAddress ip_address, PingReport &ping_report, unsigned int attempts, unsigned int size, unsigned int timeout) { unsigned long ip_addr; /* If CC3000 is not initialized, return false. */ if (!getInitStatus()) { return false; } /* If not connected, return false. */ if (!getConnectionStatus()) { return false; } /* If DHCP has not been assigned, return false. */ if (!getDHCPStatus()) { return false; } /* Create unsigned long IP address out of char array */ ip_addr = (unsigned long)ip_address[0] | ((unsigned long)ip_address[1] << 8) | ((unsigned long)ip_address[2] << 16) | ((unsigned long)ip_address[3] << 24); /* Send pings and wait for report */ if (netapp_ping_send(&ip_addr, attempts, size, timeout) != CC3000_SUCCESS) { return false; } delay((timeout * attempts) * 2); /* Copy output of ping report to return sruct */ memcpy(&ping_report, &g_ping_report, sizeof(PingReport)); return true; } /** * @brief Returns the status of DHCP * * @return True if DHCP has assigned an IP address. False otherwise. */ bool SFE_CC3000::getDHCPStatus() { if (ulCC3000DHCP == 1) { return true; } else { return false; } } /** * @brief Returns the status of connection to an access point * * @return True if connected. False otherwise. */ bool SFE_CC3000::getConnectionStatus() { if (ulCC3000Connected == 1) { return true; } else { return false; } } /** * @brief Returns the initialization state of the CC3000 * * @return True if initialized. False otherwise. */ bool SFE_CC3000::getInitStatus() { return is_initialized_; } /** * @brief Fills out ConnectionInfo struct with AP connection details * * @param[out] info struct containing information about the AP connection * @return True if connection is valid. False otherwise. */ bool SFE_CC3000::getConnectionInfo(ConnectionInfo &info) { uint8_t i; uint8_t max; /* If CC3000 is not initialized, return false. */ if (!getInitStatus()) { return false; } /* If not connected, return false. */ if (!getConnectionStatus()) { return false; } /* If DHCP has not been assigned, return false. */ if (!getDHCPStatus()) { return false; } /* Copy IP address to return struct. Reverse byte order. */ max = sizeof(connection_info_.aucIP); for (i = 0; i < max; i++) { info.ip_address[i] = connection_info_.aucIP[max - 1 - i]; } /* Copy subnet mask to return struct. Reverse byte order. */ max = sizeof(connection_info_.aucSubnetMask); for (i = 0; i < max; i++) { info.subnet_mask[i] = connection_info_.aucSubnetMask[max - 1 - i]; } /* Copy default gateway to return struct. Reverse byte order. */ max = sizeof(connection_info_.aucDefaultGateway); for (i = 0; i < max; i++) { info.default_gateway[i] = connection_info_.aucDefaultGateway[max - 1 - i]; } /* Copy DHCP server address to return struct. Reverse byte order. */ max = sizeof(connection_info_.aucDHCPServer); for (i = 0; i < max; i++) { info.dhcp_server[i] = connection_info_.aucDHCPServer[max - 1 - i]; } /* Copy DNS server address to return struct. Reverse byte order. */ max = sizeof(connection_info_.aucDNSServer); for (i = 0; i < max; i++) { info.dns_server[i] = connection_info_.aucDNSServer[max - 1 - i]; } /* Copy MAC address to return struct. Reverse byte order. */ max = sizeof(connection_info_.uaMacAddr); for (i = 0; i < max; i++) { info.mac_address[i] = connection_info_.uaMacAddr[max - 1 - i]; } /* Copy SSID to return struct. Keep byte order. */ memcpy(info.ssid, connection_info_.uaSSID, 32); return true; }