CC3000 test App

Dependencies:   CC3000HostDriver mbed

Revision:
0:305844973572
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/CC3000TestApp.cpp	Fri Aug 02 15:14:41 2013 +0000
@@ -0,0 +1,793 @@
+/**************************************************************************
+*
+*  CC3000TestApp.cpp - Basic connection test between the TI CC3000
+*                          and an Mbed.
+*
+*  Mbed Version 1.0
+* 
+*  Copyright (C) 2013 
+*
+*  Note: Some or all of this software has been modified in some way to meet the 
+*  requirements of the mbed libs and/or compiler. If you need a fresh copy of the TI
+*  software please check TI's website.
+*  If you need help please take the time to read Chris's notes below. 
+* ( Yes I know they are for an Arduino, but some still do apply.) 
+*  Most important!
+*  The mbed will not supply the current that the CC3000 requires, if you try it will 
+*  show up as the mbed resetting as soon as spi runs.
+*
+*  To associate with your WIFI router you will first need to insert your ssid and key into
+*  functions, ManualConnect() and ManualAddProfile()
+*
+*  Redistribution and use in source and binary forms, with or without
+*  modification, are permitted provided that the following conditions
+*  are met:
+*
+*  Don't sue me if this code blows up your board and burns down your house and incinerates 
+*  all life and property in this and any other universe!
+****************************************************************************\
+
+To connect an mbed to the CC3000 you'll need to make these 6 connections
+(in addition to the WiFi antenna, power etc).
+
+Name /          pin on CC3000EM board / purpose
+
+cs             /  J4-8    SPI Chip Select
+                          The Mbed will set this pin LOW when it wants to 
+                          exchange data with the CC3000. This is
+                          mbed pin 8, but any pin can be used. In this
+                          program it will be called cs.
+                          This is the mbed's pin 8.
+
+MISO           /  J4-9    Data from the module to the mbed
+                          This is mbed's MISO pin, and is how the CC3000
+                          will get bytes to the mbed.
+                          This is the mbed's pin 6. 
+
+WLAN_IRQ       /  J4-10   CC3000 host notify
+                          The CC3000 will drive this pin LOW to let the mbed
+                          know it's ready to send data. In
+                          this program it will be called WLAN_IRQ
+                          This is the mbed's pin 9.
+                          
+MOSI           /  J4-11   Data from the Arduino to the CC3000
+                          This is the Arduino's MOSI pin, and is how the Arduino
+                          will get bytes to the CC3000. 
+                          This is the mbed's pin 5.
+                          
+SCK            /  J4-12   SPI clock
+                          This is the mbed's SCK pin 7. 
+
+WLAN_EN        /  J5-5    Module enable
+                          The Arduino will set this pin HIGH to turn the CC3000
+                          on. Any pin can be used. In this program it will be
+                          called WLAN_EN
+                          This is the mbed's pin 10.
+***************************************************************************/    
+
+/**************************************************************************
+*
+*  ArduinoCC3000Test.ino - Basic connection test between the TI CC3000
+*                          and an Arduino.
+*
+*  Version 1.0
+* 
+*  Copyright (C) 2013 Chris Magagna - cmagagna@yahoo.com
+*
+*  Redistribution and use in source and binary forms, with or without
+*  modification, are permitted provided that the following conditions
+*  are met:
+*
+*  Don't sue me if my code blows up your board and burns down your house
+*
+****************************************************************************
+
+
+
+To connect an Arduino to the CC3000 you'll need to make these 6 connections
+(in addition to the WiFi antenna, power etc).
+
+Name / pin on CC3000 module / pin on CC3000EM board / purpose
+
+SPI_CS     / 12 / J4-8 /  SPI Chip Select
+                          The Arduino will set this pin LOW when it wants to 
+                          exchange data with the CC3000. By convention this is
+                          Arduino pin 10, but any pin can be used. In this
+                          program it will be called WLAN_CS
+
+SPI_DOUT   / 13 / J4-9 /  Data from the module to the Arduino
+                          This is Arduino's MISO pin, and is how the CC3000
+                          will get bytes to the Arduino. For most Arduinos
+                          MISO is pin 12
+
+SPI_IRQ    / 14 / J4-10 / CC3000 host notify
+                          The CC3000 will drive this pin LOW to let the Arduino
+                          know it's ready to send data. For a regular Arduino
+                          (Uno, Nano, Leonardo) this will have to be connected
+                          to pin 2 or 3 so you can use attachInterrupt(). In
+                          this program it will be called WLAN_IRQ
+
+SPI_DIN    / 15 / J4-11   Data from the Arduino to the CC3000
+                          This is the Arduino's MOSI pin, and is how the Arduino
+                          will get bytes to the CC3000. For most Arduinos
+                          MOSI is pin 11
+
+SPI_CLK    / 17 / J4-12   SPI clock
+                          This is the Arduino's SCK pin. For most Arduinos
+                          SCK is pin 13
+
+VBAT_SW_EN / 26 / J5-5    Module enable
+                          The Arduino will set this pin HIGH to turn the CC3000
+                          on. Any pin can be used. In this program it will be
+                          called WLAN_EN
+                          
+                          
+WARNING #1: The CC3000 runs at 3.6V maximum so you can't run it from your
+regular 5V Arduino power pin. Run it from 3.3V!
+
+
+WARNING #2: When transmitting the CC3000 will use up to 275mA current. Most
+Arduinos' 3.3V pins can only supply up to 50mA current, so you'll need a 
+separate power supply for it (or a voltage regulator like the LD1117V33
+connected to your Arduino's 5V power pin).
+
+
+WARNING #3: The CC3000's IO pins are not 5V tolerant. If you're using a 5V
+Arduino you will need a level shifter to convert these signals to 3.3V
+so you don't blow up the module. 
+
+You'll need to shift the pins for WLAN_CS, MOSI, SCK, and WLAN_EN. The other
+2 pins (WLAN_IRQ and MISO) can be connected directly because they're input
+pins for the Arduino, and the Arduino can read 3.3V signals directly.
+
+You can use a level shifter chip like the 74LVC245 or TXB0104 or you can use
+a pair of resistors to make a voltage divider like this:
+
+Arduino pin -----> 560 Ohm -----> 1K Ohm -----> GND
+                             |
+                             |
+                             +---> CC3000 pin
+
+
+****************************************************************************/
+
+
+
+#include "wlan.h" 
+#include "evnt_handler.h"    // callback function declaration
+#include "nvmem.h"
+#include "socket.h"
+#include "netapp.h"
+#include "host_driver_version.h"
+#include "cc3000.h"
+//#include "common.h"
+//#include "demo_config.h"
+//#include "HttpString.h"
+#include "spi.h"
+#include "CC3000TestApp.h"
+#include "CC3000Core.h"
+//#include <msp430.h>
+#include "mbed.h"
+//#include "Board.h"
+//#include "HttpCore.h"
+//#include "Wheel.h"
+//#include "dispatcher.h"
+#include "DigitalClass.h"
+
+#define FALSE 0
+int8_t isInitialized = false;
+InterruptIn intr(p9);
+
+DigitalOut ind1(LED1);
+DigitalOut ind2(LED2);
+DigitalOut ind3(LED3);
+DigitalOut ind4(LED4);
+
+Serial usb(USBTX, USBRX);
+
+void IntSpi()
+{
+    
+    IntSpiGPIOHandler();// spi.cpp
+    
+}
+
+int main()
+{
+
+intr.fall(&IntSpi);
+usb.baud(115200);
+
+// Start CC3000 State Machine
+    resetCC3000StateMachine();
+    
+    
+char cmd;
+
+
+while (1) {
+    printf("\r\n");
+    printf("+-------------------------------------------+\r\n");
+    printf("|      Mbed CC3000 Demo Program             |\r\n");
+    printf("+-------------------------------------------+\r\n");
+    printf("\r\n");
+    printf("  1 - Initialize the CC3000\r\n");
+    printf("  2 - Show RX & TX buffer sizes, & free RAM\r\n");
+    printf("  3 - Start Smart Config\r\n");
+    printf("  4 - Manually connect to AP\r\n");
+    printf("  5 - Manually add connection profile\r\n");
+    printf("  6 - List access points\r\n");
+    printf("  7 - Show CC3000 information\r\n");
+    printf("\r\n");
+
+    //for (;;) {
+        while (1) {
+            if (asyncNotificationWaiting) {
+                asyncNotificationWaiting = false;
+                AsyncEventPrint();
+                }
+     //       }
+        cmd = usb.getc();
+        if (cmd!='\n' && cmd!='\r') {
+            break;
+            }
+        }
+
+
+    switch(cmd) {
+        case '1':       
+            IntSpi();
+            initDriver();
+            isInitialized = true;
+            break;
+        case '2':
+            //ShowBufferSize();
+
+            break;
+        case '3':
+            StartSmartConfig();
+            break;
+        case '4':
+            ManualConnect();
+            break;
+        case '5':
+            ManualAddProfile();
+            break;
+        case '6':
+            ListAccessPoints();
+            break;
+        case '7':
+            ShowInformation();
+            break;
+        default:
+            printf("**Unknown command ");
+            printf("%d",cmd);
+            printf("**\r\n");
+            break;
+        }
+  
+    };
+    return 0;
+}
+
+
+void AsyncEventPrint()
+{
+    switch(lastAsyncEvent) {
+            printf("CC3000 Async event: Simple config done\r\n");
+            break;
+
+        case HCI_EVNT_WLAN_UNSOL_CONNECT:
+            printf("CC3000 Async event: Unsolicited connect\r\n");
+            break;
+
+        case HCI_EVNT_WLAN_UNSOL_DISCONNECT:
+            printf("CC3000 Async event: Unsolicted disconnect\r\n");
+            break;
+
+        case HCI_EVNT_WLAN_UNSOL_DHCP:
+            printf("CC3000 Async event: Got IP address via DHCP: ");
+            printf("%d",dhcpIPAddress[0]);
+            printf(".");
+            printf("%d",dhcpIPAddress[1]);
+            printf(".");
+            printf("%d",dhcpIPAddress[2]);
+            printf(".");
+            printf("%d\r\n",dhcpIPAddress[3]);
+            break;
+
+        case HCI_EVENT_CC3000_CAN_SHUT_DOWN:
+            printf("CC3000 Async event: OK to shut down\r\n");
+            break;
+
+        case HCI_EVNT_WLAN_KEEPALIVE:
+            // Once initialized, the CC3000 will send these keepalive events
+            // every 20 seconds.
+            //printf(F("CC3000 Async event: Keepalive"));
+            //return;
+            break;
+
+        default:
+            printf("AsyncCallback called with unhandled event! ");
+            printf("%x",lastAsyncEvent);
+            printf("\r\n");
+            break;
+        }
+        
+    printf("\r\n");
+    }
+/*
+    This is an example of how you'd connect the CC3000 to an AP without using
+    Smart Config or a stored profile.
+    
+    All the code above wlan_connect() is just for this demo program; if you're
+    always going to connect to your network this way you wouldn't need it.
+*/
+
+void ManualConnect() {
+
+    char ssidName[] = "***********";
+    char AP_KEY[] = "************";
+    int8_t rval;
+
+    if (!isInitialized) {
+        printf("CC3000 not initialized; can't run manual connect.\r\n");
+        return;
+        }
+
+    printf("Starting manual connect...\r\n");
+    
+    printf(("  Disabling auto-connect policy...\r\n"));
+    rval = wlan_ioctl_set_connection_policy(DISABLE, DISABLE, DISABLE);
+
+    printf("  Deleting all existing profiles...\r\n");
+    rval = wlan_ioctl_del_profile(255);
+
+    printf("  Waiting until disconnected...\r\n");
+    while (ulCC3000Connected == 1) {
+        wait_ms(100);
+        }
+    
+    printf("  Manually connecting...\r\n");
+
+    // Parameter 1 is the security type: WLAN_SEC_UNSEC, WLAN_SEC_WEP,
+    //              WLAN_SEC_WPA or WLAN_SEC_WPA2
+    // Parameter 3 is the MAC adddress of the AP. All the TI examples
+    //              use NULL. I suppose you would want to specify this
+    //              if you were security paranoid.
+    rval = wlan_connect(WLAN_SEC_WPA2,
+                ssidName,
+                strlen(ssidName),
+                NULL,
+                (unsigned char *)AP_KEY,
+                strlen(AP_KEY));
+
+    if (rval==0) {
+        printf("  Manual connect success.\r\n");
+        }
+    else {
+        printf("  Unusual return value: ");
+        printf("%d\r\n",rval);
+        }
+        return;
+    }
+/*
+    This is an example of manually adding a WLAN profile to the CC3000. See
+    wlan_ioctl_set_connection_policy() for more details of how profiles are
+    used but basically there's 7 slots where you can store AP info and if
+    the connection policy is set to auto_start then the CC3000 will go
+    through its profile table and try to auto-connect to something it knows
+    about after it boots up.
+    
+    Note the API documentation for wlan_add_profile is wrong. It says it
+    returns 0 on success and -1 on failure. What it really returns is
+    the stored profile number (0-6, since the CC3000 can store 7) or
+    255 on failure.
+    
+    Unfortunately the API doesn't give you any way to see how many profiles
+    are in use or which profile is stored in which slot, so if you want to
+    manage multiple profiles you'll need to do that yourself.
+*/
+
+void ManualAddProfile() {
+    char ssidName[] = "*****************";
+    char AP_KEY[] = "***********";
+
+    if (!isInitialized) {
+        printf("CC3000 not initialized; can't run manual add profile.\r\n");
+        return;
+        }
+
+    printf("Starting manual add profile...\r\n");
+
+    printf("  Disabling auto connection...\r\n");
+    wlan_ioctl_set_connection_policy(DISABLE, DISABLE, DISABLE);
+
+    printf("  Adding profile...\r\n");
+    int8_t rval = wlan_add_profile  (
+                    WLAN_SEC_WPA2,      // WLAN_SEC_UNSEC, WLAN_SEC_WEP, WLAN_SEC_WPA or WLAN_SEC_WPA2
+                    (unsigned char *)ssidName,
+                    strlen(ssidName),
+                    NULL,               // BSSID, TI always uses NULL
+                    0,                  // profile priority
+                    0x18,               // key length for WEP security, undocumented why this needs to be 0x18
+                    0x1e,               // key index, undocumented why this needs to be 0x1e
+                    0x2,                // key management, undocumented why this needs to be 2
+                    (unsigned char *)AP_KEY,    // WPA security key
+                    strlen(AP_KEY)      // WPA security key length
+                    );
+
+    if (rval!=255) {
+
+        // This code is lifted from http://e2e.ti.com/support/low_power_rf/f/851/p/180859/672551.aspx;
+        // the actual API documentation on wlan_add_profile doesn't specify any of this....
+
+        printf("  Manual add profile success, stored in profile: ");
+        printf("%d\r\n",rval);
+
+        printf("  Enabling auto connection...\r\n");
+        wlan_ioctl_set_connection_policy(DISABLE, DISABLE, ENABLE);
+
+        printf("  Stopping CC3000...\r\n");
+        wlan_stop();
+
+        printf("  Stopping for 5 seconds...\r\n");
+        wait_ms(5000);
+
+        printf("  Restarting CC3000...\r\n");
+        wlan_start(0);
+        
+        printf("  Manual add profile done!\r\n");
+
+        }
+    else {
+        printf("  Manual add profile failured (all profiles full?).\r\n");
+        }
+        return; 
+    }
+    
+
+    
+/*
+    All the data in all the fields from netapp_ipconfig() are reversed,
+    e.g. an IP address is read via bytes 3,2,1,0 instead of bytes
+    0,1,2,3 and the MAC address is read via bytes 5,4,3,2,1,0 instead
+    of 0,1,2,3,4,5.
+    
+    N.B. TI is inconsistent here; nvmem_get_mac_address() returns them in
+    the right order etc.
+*/
+
+void ShowInformation() {
+
+    tNetappIpconfigRetArgs inf;
+    char localB[33];
+
+    if (!isInitialized) {
+        printf("CC3000 not initialized; can't get information.\r\n");
+        return;
+        }
+        
+    printf("CC3000 information:");
+    
+    netapp_ipconfig(&inf);
+    
+    printf("  IP address: ");
+    PrintIPBytes(inf.aucIP);
+    
+    printf("  Subnet mask: ");
+    PrintIPBytes(inf.aucSubnetMask);
+    
+    printf("  Gateway: ");
+    PrintIPBytes(inf.aucDefaultGateway);
+    
+    printf("  DHCP server: ");
+    PrintIPBytes(inf.aucDHCPServer);
+    
+    printf("  DNS server: ");
+    PrintIPBytes(inf.aucDNSServer);
+    
+    printf("  MAC address: ");
+    for (int i=(MAC_ADDR_LEN-1); i>=0; i--) {
+        if (i!=(MAC_ADDR_LEN-1)) {
+            printf(":");
+            }
+        printf("%x",inf.uaMacAddr[i]);
+        }
+    printf("\r\n");
+    
+    memset(localB, 0, 32);
+    memcpy(localB, inf.uaSSID, 32);
+
+    printf("  Connected to SSID: ");
+    printf("%s",localB);
+
+    } 
+    
+void PrintIPBytes(unsigned char *ipBytes) {
+
+    printf("%d",ipBytes[3]);
+    printf(".");
+    printf("%d",ipBytes[2]);
+    printf(".");
+    printf("%d",ipBytes[1]);
+    printf(".");
+    printf("%d\r\n",ipBytes[0]);
+    }  
+    
+/*
+    The call wlan_ioctl_get_scan_results returns this structure. I couldn't
+    find it in the TI library so it's defined here. It's 50 bytes with
+    a semi weird arrangement but fortunately it's not as bad as it looks.
+    
+    numNetworksFound - 4 bytes - On the first call to wlan_ioctl_get_scan_results
+                    this will be set to how many APs the CC3000 sees. Although
+                    with 4 bytes the CC3000 could see 4 billion APs in my testing
+                    this number was always 20 or less so there's probably an
+                    internal memory limit.
+    
+    results - 4 bytes - 0=aged results, 1=results valid, 2=no results. Why TI
+                    used 32 bits to store something that could be done in 2,
+                    and how this field is different than isValid below, is
+                    a mystery to me so I just igore this field completely.
+                    
+    isValid & rssi - 1 byte - a packed structure. The top bit (isValid)
+                    indicates whether or not this structure has valid data,
+                    the bottom 7 bits (rssi) are the signal strength of this AP.
+                    
+    securityMode & ssidLength - 1 byte - another packed structure. The top 2
+                    bits (securityMode) show how the AP is configured:
+                        0 - open / no security
+                        1 - WEP
+                        2 - WPA
+                        3 - WPA2
+                    ssidLength is the lower 6 bytes and shows how many characters
+                    (up to 32) of the ssid_name field are valid
+                    
+    frameTime - 2 bytes - how long, in seconds, since the CC3000 saw this AP
+                    beacon
+                    
+    ssid_name - 32 bytes - The ssid name for this AP. Note that this isn't a
+                    regular null-terminated C string so you can't use it
+                    directly with a strcpy() or printf() etc. and you'll
+                    need a 33-byte string to store it (32 valid characters +
+                    null terminator)
+                    
+    bssid - 6 bytes - the MAC address of this AP
+*/
+    
+typedef struct scanResults {
+    unsigned long numNetworksFound;
+    unsigned long results;
+    unsigned isValid:1;
+    unsigned rssi:7;
+    unsigned securityMode:2;
+    unsigned ssidLength:6;
+    unsigned short frameTime;
+    unsigned char ssid_name[32];
+    unsigned char bssid[6];
+    } scanResults;
+
+#define NUM_CHANNELS    16
+
+void ListAccessPoints() {
+    unsigned long aiIntervalList[NUM_CHANNELS];
+    int8_t rval;
+    scanResults sr;
+    int apCounter;
+    char localB[33];
+    
+    if (!isInitialized) {
+        printf(("CC3000 not initialized; can't list access points.\r\n"));
+        return;
+        }
+        
+    printf(("List visible access points\r\n"));
+    
+    printf(("  Setting scan paramters...\r\n"));
+    
+    for (int i=0; i<NUM_CHANNELS; i++) {
+        aiIntervalList[i] = 2000;
+        }
+    
+    rval = wlan_ioctl_set_scan_params(
+            1000,   // enable start application scan
+            100,    // minimum dwell time on each channel
+            100,    // maximum dwell time on each channel
+            5,      // number of probe requests
+            0x7ff,  // channel mask
+            -80,    // RSSI threshold
+            0,      // SNR threshold
+            205,    // probe TX power
+            aiIntervalList  // table of scan intervals per channel
+            );
+    if (rval!=0) {
+        printf("  Got back unusual result from wlan_ioctl_set_scan_params, can't continue: ");
+        printf("%d\r\n",rval);
+        return;
+        }
+        
+    printf("  Sleeping 5 seconds to let the CC3000 discover APs...");
+    wait_ms(5000);
+    
+    printf("  Getting AP count...\r\n");
+    
+    // On the first call to get_scan_results, sr.numNetworksFound will return the
+    // actual # of APs currently seen. Get that # then loop through and print
+    // out what's found.
+    
+    if ((rval=wlan_ioctl_get_scan_results(2000, (unsigned char *)&sr))!=0) {
+        printf("  Got back unusual result from wlan_ioctl_get scan results, can't continue: ");
+        printf("%d\r\n",rval);
+        return;
+        }
+        
+    apCounter = sr.numNetworksFound;
+    printf(("  Number of APs found: "));
+    printf("%d\r\n",apCounter);
+    
+    do {
+        if (sr.isValid) {
+            printf(("    "));
+            switch(sr.securityMode) {
+                case WLAN_SEC_UNSEC:    // 0
+                    printf(("OPEN "));
+                    break;
+                case WLAN_SEC_WEP:      // 1
+                    printf(("WEP  "));
+                    break;
+                case WLAN_SEC_WPA:      // 2
+                    printf(("WPA  "));
+                    break;
+                case WLAN_SEC_WPA2:     // 3
+                    printf(("WPA2 "));
+                    break;
+                    }
+            sprintf(localB, "%3d  ", sr.rssi);
+            printf(localB);
+            memset(localB, 0, 33);
+            memcpy(localB, sr.ssid_name, sr.ssidLength);
+            printf(localB);
+            }
+            
+        if (--apCounter>0) {
+            if ((rval=wlan_ioctl_get_scan_results(2000, (unsigned char *)&sr))!=0) {
+                printf(("  Got back unusual result from wlan_ioctl_get scan results, can't continue: "));
+                printf("%d\r\n",rval);
+                return;
+                }
+            }
+        } while (apCounter>0);
+        
+    printf(("  Access Point list finished."));
+    }  
+    
+
+//*****************************************************************************
+//
+//!  turnLedOn
+//!
+//! @param  ledNum is the LED Number
+//!
+//! @return none
+//!
+//! @brief  Turns a specific LED on
+//
+//*****************************************************************************
+void turnLedOn(char ledNum)
+{
+    switch(ledNum)
+        {
+          case 1:
+              ind1 = 1;
+            break;
+          case 2:
+              ind2 = 1;
+            break;
+          case 3:
+              ind3 = 1;
+            break;
+          case 4:
+              ind4 = 1;
+            break;
+          case 5:
+              ind1 = 1;
+            break;
+          case 6:
+              ind4 = 1;
+            break;
+          case 7:
+              
+            break;
+          case 8:
+              
+            break;
+        }
+
+}
+
+//*****************************************************************************
+//
+//! turnLedOff
+//!
+//! @param  ledNum is the LED Number
+//!
+//! @return none
+//!
+//! @brief  Turns a specific LED Off
+//
+//*****************************************************************************    
+void turnLedOff(char ledNum)
+{              
+    switch(ledNum)
+        {
+          case 1:
+             ind1 = 0; 
+            break;
+          case 2:
+              ind2 = 0; 
+            break;
+          case 3:
+              ind3 = 0; 
+            break;
+          case 4:
+              ind4 = 0; 
+            break;
+          case 5:
+              ind1 = 0; 
+            break;
+          case 6:
+              ind4 = 0; 
+            break;
+          case 7:
+              
+            break;
+          case 8:
+              
+            break;
+        }  
+}
+
+//*****************************************************************************
+//
+//! toggleLed
+//!
+//! @param  ledNum is the LED Number
+//!
+//! @return none
+//!
+//! @brief  Toggles a board LED
+//
+//*****************************************************************************    
+
+void toggleLed(char ledNum)
+{
+    switch(ledNum)
+        {
+          case 1:
+              ind1 = 0;
+            break;
+          case 2:
+              ind2 = 0;
+            break;
+          case 3:
+              ind3 = 0;
+            break;
+          case 4:
+              ind4 = 0;
+            break;
+          case 5:
+              ind1 = 0;
+            break;
+          case 6:
+              ind4 = 0;
+            break;
+          case 7:
+              
+            break;
+          case 8:
+              
+            break;
+        }
+
+}       
\ No newline at end of file