Repository for import to local machine

Dependencies:   DMBasicGUI DMSupport

EthernetHandler.cpp

Committer:
jmitc91516
Date:
2017-07-31
Revision:
8:26e49e6955bd
Parent:
1:a5258871b33d

File content as of revision 8:26e49e6955bd:

#include "EthernetHandler.h"

#define DO_USB_IN_THIS_THREAD // If #define'd, we do our own USB comms to the GC here in this thread.
                              // If not, we signal the main thread to do it
                              

#define USE_LED_FOR_DEBUGGING

#ifdef USE_LED_FOR_DEBUGGING

#include "gpio_api.h"
#include "wait_api.h"
#include "toolchain.h"
#include "mbed_interface.h"

// We turn on LED 1 when we have received, from the client, a command to send to the GC.
// We turn it off when we send the response back to the client.
static void SetLed1(bool turnLedOn)
{
    gpio_t led_1; gpio_init_out(&led_1, LED1);

    if(turnLedOn) {
        gpio_write(&led_1, 0); // zero appears to mean "turn LED 1 on"
    } else {
        gpio_write(&led_1, 1); // one appears to turn it off
    }
}

#endif // USE_LED_FOR_DEBUGGING


#include "GuiLib.h"
/*
    Displays the specified text string at the specified location in the currently-displayed easyGUI page.
    
    Defined in main.cpp - and omits the 'ALLOW_DEBUG_PRINTS' #define
*/
extern void SpecialDebugPrint(char *stuffToPrint, GuiConst_INT16S X, GuiConst_INT16S Y);

/*
    Tells the caller whether or not a specified GC command represents the start of a method,
    and that therefore a new method is now being sent to the GC.
    
    Params: pointer to a null-terminated string containing the command in question
    
    Returns true if the command is one that occurs at the start of a method (and nowhere else),
    false if not.
    
    Defined in main.cpp
*/
bool IsStartOfMethodCommand(char *gcCommand);

/*
    Tells the caller whether or not a specified GC command represents the end of a method,
    and that therefore a new method has just been sent to the GC.
    
    Params: pointer to a null-terminated string containing the command in question
    
    Returns true if the command is one that occurs at the end of a method (and nowhere else),
    false if not.
    
    Defined in main.cpp
*/
bool IsEndOfMethodCommand(char *gcCommand);

/*
    Tells the caller whether or not a specified GC command is a control command,
    which received "DACK" in acknowledgement, and is therefore likely 
    to have caused the GC to change its state.
    
    Params: pointer to a null-terminated string containing the command in question
    
    Returns true if the command is a control command and if it succeeded, false if not.
    
    Defined in main.cpp
*/
bool IsSuccessfulControlCommand(char *gcCommand, char *gcResponse);


osThreadId EthernetHandler::mainThreadId = 0;
bool EthernetHandler::mainThreadIdSet = false;

TCPSocketServer* EthernetHandler::ethernetServer = NULL;

USBDeviceConnected* EthernetHandler::usbDevice = NULL;
USBHostGC* EthernetHandler::usbHostGC = NULL;

char EthernetHandler::gcCommand[20] = "";
char EthernetHandler::gcResponse[20] = "";
    
Timer EthernetHandler::timer;

bool EthernetHandler::receivedGCCommand = false;

// This is the thread signal value we tell the main thread to use to tell us that it has received a response to the latest command.
// We increment this each time we pass a new Ethernet command to the main thread, so that we know which command the response refers to.
int EthernetHandler::responseReadyThreadSignalCode = GC_RESPONSE_READY; 

// The maximum possible value for 'responseReadyThreadSignalCode' above - when it exceeds this value, we set it back to GC_RESPONSE_READY
const int EthernetHandler::maxResponseReadyThreadSignalCode = GC_RESPONSE_READY + 1000;


//#define CLIENT_SET_BLOCKING_FALSE

                              
bool EthernetHandler::GCCommandReceived(void)
{ 
#ifdef DO_USB_IN_THIS_THREAD
    return false; // We will deal with this ourselves
#else
    return receivedGCCommand;
#endif
}


/*
    Caller tells us the ID of the thread we are to signal 
    when we have received a command (over the Ethernet link)
    to be sent to the GC.
*/
void EthernetHandler::SetMainThreadId(osThreadId threadId)
{
    mainThreadId = threadId;
    
    mainThreadIdSet = true;
}

/*
    Caller gives us a pointer to the Ethernet server 
    we are to use.
*/
void EthernetHandler::SetEthernetServer(TCPSocketServer* server)
{
    ethernetServer = server;
}

/*
    Caller gives us pointers for the USB link to the GC, 
    to allow us to communicate with it ourselves
*/
void EthernetHandler::SetUsbGC(USBDeviceConnected* newUsbDevice, USBHostGC* newUsbHostGC)
{
    usbDevice = newUsbDevice;
    usbHostGC = newUsbHostGC;
}
    

/*
    Passes back to the caller, the command we have received over the Ethernet link
    to be sent to the GC (assume the caller is going to communicate with the GC).
    
    Args: pointer to a buffer into which we will copy the command
          pointer to an integer into which we will place the value the caller
          must use to tell us that it has given us the response
*/
void EthernetHandler::GetGCCommand(char *commandBuffer, int* responseThreadSignalCodeToUse)
{
    strcpy(commandBuffer, gcCommand);
    
    SetupNextResponseReadyThreadSignalCode();
    
    *responseThreadSignalCodeToUse = responseReadyThreadSignalCode;
}
   
/*
    Set 'responseReadyThreadSignalCode' to the next legal value
*/ 
void EthernetHandler::SetupNextResponseReadyThreadSignalCode(void)
{
    ++responseReadyThreadSignalCode;
    
    if(responseReadyThreadSignalCode > maxResponseReadyThreadSignalCode) {
        responseReadyThreadSignalCode = GC_RESPONSE_READY;
    }
}

    
/*
    Function to allow the caller to pass to us,
    the response it got back from the GC.
    We copy it into our buffer, ready to be sent back 
    to the Ethernet client.
    
    Caller must set this *before* signaling GC_RESPONSE_READY
    *********************************************************
    
    Args: pointer to a buffer containing the GC response
*/
void EthernetHandler::SetGCResponse(char *response)
{
    strcpy(gcResponse, response);
}


/*
    Sends a command (known as a 'report') to the GC, and returns the response.
    
    Args: pointer to (null-terminated) command to use
          pointer to buffer to contain the (also null-terminated) response
          
    No return code.
*/
void EthernetHandler::SetGCDeviceReport(char *cmd, char *response)
{
    if((usbHostGC != NULL) && (usbDevice != NULL)) {
        
        //strcpy(response, "BEFORE"); // Debug

        // Guard against simultaneous calls to usbHostGC->SetDeviceReport - 
        // it is not re-entrant (and nor is the GC)
        while(usbHostGC->ExecutingSetDeviceReport()) {
            Thread::wait(1); // Give other threads a chance
        }

        //strcpy(response, "AFTER 1"); // Debug
        
        usbHostGC->SetDeviceReport(usbDevice, cmd, response);

        //strcpy(response, "AFTER 2"); // Debug
    }
}
    

/*
    Handles the next Ethernet message from the specified client.
    Returns true if there was a message, false if not.
*/
bool EthernetHandler::HandleEthernetMessage(TCPSocketConnection* client)
{
    if(mainThreadIdSet) {

        receivedGCCommand = false;
        
        int n = client->receive(gcCommand, sizeof(gcCommand));
        if (n <= 0) return false;
        
        gcCommand[n] = '\0';
        
        timer.reset();
        timer.start();
    
//#define ALLOW_DEBUG_PRINTS_HERE
#ifdef ALLOW_DEBUG_PRINTS_HERE
    char buff[300];
    sprintf(buff, "ETH - command received: \"%s\"", gcCommand);
    SpecialDebugPrint(buff, 50, 350);
#endif // ALLOW_DEBUG_PRINTS_HERE

        receivedGCCommand = true;
        
#ifdef USE_LED_FOR_DEBUGGING
        // Turn LED 1 on while we signal the main thread that we have a command, and while we wait for the main thread to respond
        // - or, if we are doing USB in this thread, while *we* send the command to the GC and wait for the GC to respond
        SetLed1(true);
#endif // USE_LED_FOR_DEBUGGING


#ifdef DO_USB_IN_THIS_THREAD

        bool commandIdentified = false;

        if(IsStartOfMethodCommand(gcCommand)) {
            commandIdentified = true;
            osSignalSet(mainThreadId, STARTED_DOWNLOADING_METHOD);
        }

        SetGCDeviceReport(gcCommand, gcResponse);

        if(!commandIdentified) {
            if(IsEndOfMethodCommand(gcCommand)) {
                commandIdentified = true;
                osSignalSet(mainThreadId, FINISHED_DOWNLOADING_METHOD);
            }
        }
        
        if(!commandIdentified) {       
            // Tell main thread if CRUN, CHON or CHOF were sent to the GC, and were successful - 
            // i.e. acknowledged with DACK - so that it can update the display.
            // Also now CSTP and CABT
            if((gcResponse[0] == 'D') && (gcResponse[1] == 'A') && (gcResponse[2] == 'C') && (gcResponse[3] == 'K')) { /// i.e. "DACK"
                if(gcCommand[0] == 'C') { // Begins with 'C' - must be a command of some kind
                    if((gcCommand[1] == 'R') && (gcCommand[2] == 'U') && (gcCommand[3] == 'N')) { // i.e. "CRUN"
                        commandIdentified = true;
                        osSignalSet(mainThreadId, CRUN_COMMAND_SENT);
                    }
    
                    if(!commandIdentified) {       
                        if((gcCommand[1] == 'H') && (gcCommand[2] == 'O')) {
                            if(gcCommand[3] == 'N') { // i.e. "CHON"
                                commandIdentified = true;
                                osSignalSet(mainThreadId, CHON_COMMAND_SENT);
                            }
            
                            if(!commandIdentified) {       
                                if(gcCommand[3] == 'F') {  // i.e. "CHOF"
                                    commandIdentified = true;
                                    osSignalSet(mainThreadId, CHOF_COMMAND_SENT);
                                }
                            }
                        }
                    }
                    
                    if(!commandIdentified) {       
                        if(((gcCommand[1] == 'S') && (gcCommand[2] == 'T') && (gcCommand[3] == 'P'))      // i.e. "CSTP"
                        || ((gcCommand[1] == 'A') && (gcCommand[2] == 'B') && (gcCommand[3] == 'T'))) {   // i.e. "CABT"
                            commandIdentified = true;
                            osSignalSet(mainThreadId, ABORT_RUN_COMMAND_SENT);
                        }
                    }                    
                }
            }
        }        
#else
        // Signal the main thread that we have a command to send to the GC
        osSignalSet(mainThreadId, GC_COMMAND_READY);
        
        // Now wait for it to tell us it has 'got' the command - try and keep synchronised
        // (i.e. so that we do not get other commands before the main thread has dealt with this one,
        // and so that the commands and responses do not get out of step)
        Thread::signal_wait(GOT_GC_COMMAND);
        
        
        // Main thread must give us the response *now* - *before* signaling 'response ready' - see above
        // *********************************************************************************************
        
        // We tell the main thread to use a different response code for each command, 
        // to try and ensure that the commands and responses do not get out of step
        
        // Wait forever - do not timeout (no point in continuing without a response)
        Thread::signal_wait(responseReadyThreadSignalCode);
        
        // Main thread has put the response in 'gcResponse'
        
        receivedGCCommand = false; // Ready for the next command
    
#endif // DO_USB_IN_THIS_THREAD
        
                   
#ifdef USE_LED_FOR_DEBUGGING
        // We have received the command response - turn LED 1 off
        SetLed1(false);
#endif // USE_LED_FOR_DEBUGGING

        // Pass the response back to the client
        client->send_all(gcResponse, strlen(gcResponse));
        
        timer.stop();
        
#ifdef ALLOW_DEBUG_PRINTS_HERE
    sprintf(buff, "ETH - response sent: \"%s\"", gcResponse);
    SpecialDebugPrint(buff, 50, 380);

    if((gcResponse[1] == gcCommand[1]) && (gcResponse[2] == gcCommand[2]) && (gcResponse[3] == gcCommand[3])) {
        SpecialDebugPrint("ETH - command and response match", 50, 400);
    } else {
        SpecialDebugPrint("ETH - command and response *** do not match ***", 50, 400);
    }
    
    int read_ms = timer.read_ms();
    if(read_ms > 1000) { // We are interested only in suspiciously long response times (that may make Ellution time out)
        sprintf(buff, "ETH - time taken for %s: %d ms", gcCommand, read_ms);
        SpecialDebugPrint(buff, 50, 420);    
    }
#endif // ALLOW_DEBUG_PRINTS_HERE

        //receivedGCCommand = false; // Ready for the next command
    
        return true;
    }
    
    // 'else'
    return false;
}


/*
    This is the infinite loop that handles Ethernet traffic on its own thread.
    Once started, it never exits.
*/
void EthernetHandler::HandlerFunction(void const *argument)
{
    if(mainThreadIdSet && (ethernetServer != NULL)) {

        // We will now sit in this loop until the end of time, or this thread gets terminated, or the system powers off, whichever happens soonest...
        while(true) {
    
            TCPSocketConnection client;
    
            // Look for a client, but do not hang if there isn't one
            ethernetServer->set_blocking(false); // Timeout after (1.5)s
            bool clientFound = (ethernetServer->accept(client) == 0);
            if(clientFound) {
#ifdef CLIENT_SET_BLOCKING_FALSE
                client.set_blocking(false); // Default timeout (1.5 sec)
                //client.set_blocking(false, 60000); // Timeout after 1 minute (comms somehow works better than with 1.5sec)
#else
                // No - block this thread until we get a command - what else would we do?
                client.set_blocking(true); 
                // But note that, on the face of it, set_blocking(true) means that we will never close the first client connection we open - 
                // if the client goes away, we will just sit waiting forever (in HandleEthernetMessage) for the next command(?). 
                // In this scenario, no subsequent client will be able to establish a connection.
#endif // CLIENT_SET_BLOCKING_FALSE
    
                while (true) 
                {
                    if(!HandleEthernetMessage(&client)) break;
                }
                                        
                client.close();
            } 
        }
    }
}