Simplify using of UnbufferedSerial(Serial), USBCDC, TCP, SMTP, NTP Fork : https://github.com/YSI-LPS/lib_Transmission

Dependents:   lib_Transmission_Serial_example 2022_TICE_Electrolyse lib_Transmission_TCP_example

lib_Transmission.cpp

Committer:
YSI
Date:
2021-06-29
Revision:
23:457f1242007d
Parent:
22:e99892e6fd8d
Child:
25:01db56f04262

File content as of revision 23:457f1242007d:

#include "lib_Transmission.h"

Transmission::Transmission(
    #if MBED_MAJOR_VERSION > 5
    UnbufferedSerial    *serial,
    #else
    Serial              *serial,
    #endif
    USBCDC              *usb,
    EthernetInterface   *eth,
    string              (*processing)(string),
    void                (*ethup)(void),
    bool                caseIgnore)
    :_serial(serial), _usb(usb), _eth(eth), _processing(processing), _ethup(ethup), _caseIgnore(caseIgnore)
{
    if(_serial) _serial->attach(callback(this, &Transmission::serial_event));
    _evenThread = new Thread(osPriorityNormal, TRANSMISSION_DEFAULT_THREAD_SIZE);
    _evenThread->start(callback(&_queue, &EventQueue::dispatch_forever));
}

Transmission::Transmission(
    #if MBED_MAJOR_VERSION > 5
    UnbufferedSerial    *serial,
    #else
    Serial              *serial,
    #endif
    USBCDC              *usb,
    string              (*processing)(string),
    bool                caseIgnore)
    :_serial(serial), _usb(usb), _processing(processing), _caseIgnore(caseIgnore)
{
    if(_serial) _serial->attach(callback(this, &Transmission::serial_event));
    _evenThread = new Thread(osPriorityNormal, TRANSMISSION_DEFAULT_THREAD_SIZE);
    _evenThread->start(callback(&_queue, &EventQueue::dispatch_forever));
}

Transmission::Transmission(
    #if MBED_MAJOR_VERSION > 5
    UnbufferedSerial    *serial,
    #else
    Serial              *serial,
    #endif
    EthernetInterface   *eth,
    string              (*processing)(string),
    void                (*ethup)(void),
    bool                caseIgnore)
    :_serial(serial), _eth(eth), _processing(processing), _ethup(ethup), _caseIgnore(caseIgnore)
{
    if(_serial) _serial->attach(callback(this, &Transmission::serial_event));
    _evenThread = new Thread(osPriorityNormal, TRANSMISSION_DEFAULT_THREAD_SIZE);
    _evenThread->start(callback(&_queue, &EventQueue::dispatch_forever));
}

Transmission::Transmission(
    #if MBED_MAJOR_VERSION > 5
    UnbufferedSerial    *serial,
    #else
    Serial              *serial,
    #endif
    string              (*processing)(string),
    bool                caseIgnore)
    :_serial(serial), _processing(processing), _caseIgnore(caseIgnore)
{
    if(_serial) _serial->attach(callback(this, &Transmission::serial_event));
    _evenThread = new Thread(osPriorityNormal, TRANSMISSION_DEFAULT_THREAD_SIZE);
    _evenThread->start(callback(&_queue, &EventQueue::dispatch_forever));
}

Transmission::Transmission(
    USBCDC              *usb,
    EthernetInterface   *eth,
    string              (*processing)(string),
    void                (*ethup)(void),
    bool                caseIgnore)
    :_usb(usb), _eth(eth), _processing(processing), _ethup(ethup), _caseIgnore(caseIgnore)
{
    _evenThread = new Thread(osPriorityNormal, TRANSMISSION_DEFAULT_THREAD_SIZE);
    _evenThread->start(callback(&_queue, &EventQueue::dispatch_forever));
}

Transmission::Transmission(
    EthernetInterface   *eth,
    string              (*processing)(string),
    void                (*ethup)(void),
    bool                caseIgnore)
    :_eth(eth), _processing(processing), _ethup(ethup), _caseIgnore(caseIgnore)
{
    _evenThread = new Thread(osPriorityNormal, TRANSMISSION_DEFAULT_THREAD_SIZE);
    _evenThread->start(callback(&_queue, &EventQueue::dispatch_forever));
}
    
Transmission::Transmission(
    USBCDC              *usb,
    string              (*processing)(string),
    bool                caseIgnore)
    :_usb(usb), _processing(processing), _caseIgnore(caseIgnore)
{}

string Transmission::ip(string ip)
{
    if(!_eth) return "0.0.0.0";
    ostringstream address;
    SocketAddress socket;
    _eth->get_ip_address(&socket);
    address << (socket.get_ip_address()?socket.get_ip_address():"0.0.0.0") << ":" << message.PORT;
    if(ip == address.str())
    {
        _eth->get_netmask(&socket);
        address << " " << (socket.get_ip_address()?socket.get_ip_address():"0.0.0.0");
        _eth->get_gateway(&socket);
        address << " " << (socket.get_ip_address()?socket.get_ip_address():"0.0.0.0");
    }
    return address.str();
}

string Transmission::ip(const bool set, const char* ip, const uint16_t port, const char* mask, const char* gateway, const uint16_t timeout)
{
    if(!_eth) return "00:00:00:00:00:00";
    if(message.SET && set)
    {
        if(message.PORT != port)
        {
            message.CONNECT = false;
            _serverTCP.sigio(NULL);
            eth_error("serverTCP_close", _serverTCP.close());
        }
        eth_error("Ethernet_disconnect", _eth->disconnect());
    }
    message.SET = set;
    message.IP = ip;
    message.PORT = port;
    message.MASK = mask;
    message.GATEWAY = gateway;
    message.TIMEOUT = timeout;
    message.DHCP = message.IP.empty();
    eth_connect();
    return _eth->get_mac_address()?_eth->get_mac_address():"00:00:00:00:00:00";
}

bool Transmission::eth_connect(void)
{
    if(!_eth) return false;
    if(message.SET)
    {
        switch(_eth->get_connection_status())
        {
            case NSAPI_STATUS_DISCONNECTED:
                eth_error("Ethernet_blocking", _eth->set_blocking(false));
                eth_error("Ethernet_dhcp", _eth->set_dhcp(message.DHCP));
                if(!message.DHCP) eth_error("Ethernet_static", _eth->set_network(SocketAddress(message.IP.c_str()), SocketAddress(message.MASK.c_str()), SocketAddress(message.GATEWAY.c_str())));
                _eth->attach(callback(this, &Transmission::eth_event));
                eth_error("Ethernet_connect", _eth->connect()); break;
            case NSAPI_STATUS_GLOBAL_UP: return message.CONNECT;break;
            default:                                            break;
        }
    }
    else if(_eth->get_connection_status() != NSAPI_STATUS_DISCONNECTED) eth_error("Ethernet_disconnect", _eth->disconnect());
    return false;
}

void Transmission::eth_event(nsapi_event_t status, intptr_t param)
{
    eth_status("Ethernet_event", param);
    switch(param)
    {
        case NSAPI_STATUS_DISCONNECTED: message.status = RED_DISCONNECTED;      break;
        case NSAPI_STATUS_CONNECTING:if(message.status == BLUE_CLIENT) eth_error("clientTCP_disconnect", _clientTCP->close());
                                        message.status = YELLOW_CONNECTING;     break;
        case NSAPI_STATUS_GLOBAL_UP:    message.status = GREEN_GLOBAL_UP;
                                        if(!message.CONNECT) serverTCP_connect();
                                        else                 serverTCP_event(); break;
        default:                                                                break;
    }
}

bool Transmission::serverTCP_connect(void)
{
    if(!message.CONNECT)
        if(eth_error("serverTCP_open", _serverTCP.open(_eth)) == NSAPI_ERROR_OK)
            if(eth_error("serverTCP_bind", _serverTCP.bind(message.PORT)) == NSAPI_ERROR_OK)
                if(eth_error("serverTCP_listen", _serverTCP.listen()) == NSAPI_ERROR_OK)
                {
                    _serverTCP.set_blocking(false);
                    _serverTCP.sigio(callback(this, &Transmission::serverTCP_event));
                    message.CONNECT = true;
                    if(_ethup) _queue.call(_ethup);
                }
    return message.CONNECT;
}

void Transmission::serverTCP_event(void)
{
    _queue.call(this, &Transmission::serverTCP_accept);
}

void Transmission::serverTCP_accept(void)
{
    if(message.status == GREEN_GLOBAL_UP)
    {
        nsapi_error_t ack = NSAPI_ERROR_WOULD_BLOCK;
        message.status = MAGENTA_ACCEPT;
        _clientTCP = _serverTCP.accept(&ack);
        switch(ack)
        {
            case NSAPI_ERROR_OK:
                _clientTCP->set_timeout(message.TIMEOUT);   // config client bloquante avec timeout sinon limite de transmission a 1072 octets
                message.status = BLUE_CLIENT;
            break;
            case NSAPI_ERROR_NO_CONNECTION:
                eth_state();
                serverTCP_event();
            break;
            default:
                eth_state();
                if(ack < NSAPI_ERROR_WOULD_BLOCK) eth_error("serverTCP_accept", ack);
            break;
        }
    }
}

void Transmission::eth_state(void)
{
    switch(_eth->get_connection_status())
    {
        case NSAPI_STATUS_DISCONNECTED: message.status = RED_DISCONNECTED;  break;
        case NSAPI_STATUS_CONNECTING:   message.status = YELLOW_CONNECTING; break;
        case NSAPI_STATUS_GLOBAL_UP:    message.status = GREEN_GLOBAL_UP;   break;
        default:                                                            break;
    }
}

void Transmission::serial_event(void)
{
    char caractere;
    static uint16_t size = 0;
    static char buffer[TRANSMISSION_DEFAULT_BUFFER_SIZE] = {0};
    _serial->read(&caractere, 1);
    if((caractere == '\n') || (size == (TRANSMISSION_DEFAULT_BUFFER_SIZE-1)))
    {
        buffer[size] = '\0';
        size = 0;
        if(_processing) _queue.call(this, &Transmission::preprocessing, buffer, SERIAL_DELIVERY);
    }
    else if((caractere > 31) && (caractere < 127) && (size < (TRANSMISSION_DEFAULT_BUFFER_SIZE-1))) buffer[size++] = caractere;
}

Transmission::enum_trans_status Transmission::recv(void)
{
    if(_usb)
    {
        if(_usb->ready())
        {
            uint8_t buffer[TRANSMISSION_DEFAULT_BUFFER_SIZE] = {0};
            uint32_t ack = 0, size = 0;
            do{
                ack = NSAPI_ERROR_OK;
                _usb->receive_nb(&buffer[size], 1, &ack);   // un peu plus rapide sur les petits transferts
                size += ack;
            }while((ack > NSAPI_ERROR_OK) && (size < TRANSMISSION_DEFAULT_BUFFER_SIZE-1) && (buffer[size-ack] != '\n'));
            if(size && _processing) preprocessing((char *)buffer, USB_DELIVERY);
        } else _usb->connect();
    }
    if(eth_connect() && (message.status == BLUE_CLIENT))
    {
        char buffer[TRANSMISSION_DEFAULT_BUFFER_SIZE] = {0};
        nsapi_error_t ack = 0, size = 0;
        do{
            ack = _clientTCP->recv(&buffer[size], TRANSMISSION_DEFAULT_BUFFER_SIZE-size);
            if(ack > NSAPI_ERROR_OK) size += ack;
        }while((ack == 536) && (size < TRANSMISSION_DEFAULT_BUFFER_SIZE));
        if(ack < NSAPI_ERROR_WOULD_BLOCK) eth_error("clientTCP_recv", ack);
        if((ack == NSAPI_ERROR_OK) || (ack == NSAPI_ERROR_NO_CONNECTION))
        {
            eth_error("clientTCP_disconnect", _clientTCP->close());
            eth_state();
            serverTCP_event();
        }
        if(size && _processing) preprocessing(buffer, TCP_DELIVERY);
    }
    else if(!_usb) for(int i = 0; ((i < 10) && (message.status != BLUE_CLIENT)); i++) ThisThread::sleep_for(10ms);
    return message.status;
}

void Transmission::preprocessing(char *buffer, const enum_trans_delivery delivery)
{
    string cmd(buffer);
    for(char &c : cmd) if(_caseIgnore && (c >= 'a') && (c <= 'z')) c += 'A'-'a';
    if((cmd.find("HOST: ") != string::npos) || (cmd.find("Host: ") != string::npos))
        send(_processing(cmd), HTTP_DELIVERY);
    else if(!cmd.empty() && (cmd[0] != 22))
    {
        for(char &c : cmd) if(c == '\n') c = ';';
        istringstream srecv(cmd);
        string ssend;
        while(getline(srecv, cmd, ';'))
        {
            string process = _processing(cmd);
            if(!process.empty()) ssend += (ssend.size() > 0)?(' '+process):process;
        }
        send(ssend, delivery);
    }
}

nsapi_error_t Transmission::send(const string& buffer, const enum_trans_delivery& delivery)
{
    nsapi_error_t ack = NSAPI_ERROR_WOULD_BLOCK;
    string ssend(buffer+"\n");
    if(_usb && !buffer.empty() && ((delivery == USB_DELIVERY) || (delivery == ANY_DELIVERY)))
    {
        _usb->connect();
        if(_usb->ready()) _usb->send((uint8_t*)ssend.c_str(), ssend.size());
    }
    if(_serial  && !buffer.empty() && ((delivery == SERIAL_DELIVERY) || (delivery == ANY_DELIVERY)))
        ack = _serial->write(ssend.c_str(), ssend.length());
    if(_eth && ((delivery == TCP_DELIVERY) || (delivery == HTTP_DELIVERY) || (delivery == ANY_DELIVERY)))
    {
        if((message.status == BLUE_CLIENT) && !buffer.empty())
            eth_error("clientTCP_send", ack = _clientTCP->send(ssend.c_str(), ssend.size()));
        if(delivery == HTTP_DELIVERY)
        {
            eth_error("clientTCP_disconnect", _clientTCP->close());
            eth_state();
            serverTCP_event();
        }
    }
    return ack;
}

string Transmission::get(const string& ssend, const string& server, const int& port)
{
    char buffer[256] = {0};
    if(!server.empty())
    {
        TCPSocket clientTCP;
        clientTCP.set_timeout(2000);
        if(eth_error("clientTCP_open", clientTCP.open(_eth)) == NSAPI_ERROR_OK)
        {
            if(eth_error("clientTCP_connect", clientTCP.connect(SocketAddress(server.c_str(), port))) == NSAPI_ERROR_OK)
            {
                eth_error("clientTCP_send", clientTCP.send(ssend.c_str(), ssend.size()));
                eth_error("clientTCP_recv", clientTCP.recv(buffer, 256));
            }
        }
        eth_error("clientTCP_close", clientTCP.close());
    }
    return buffer;
}

bool Transmission::smtp(const char* mail, const char* from, const char* subject, const char* data, const char* server)
{
    if(!_eth) return false;
    if((!message.DHCP) || (_eth->get_connection_status() != NSAPI_STATUS_GLOBAL_UP)) return false;
    TCPSocket clientSMTP;
    clientSMTP.set_timeout(2000);
    string sMAIL(mail), sFROM(from), sSUBJECT(subject), sDATA(data), sTO(sMAIL.substr(0, sMAIL.find("@")));
    string smtpParams[][7] = {  { "", "HELO Mbed " + sFROM + "\r\n", "MAIL FROM: <Mbed." + sFROM + "@UNIVERSITE-PARIS-SACLAY.FR>\r\n", "RCPT TO: <" + sMAIL + ">\r\n", "DATA\r\n", "From: \"Mbed " + sFROM + "\" <Mbed." + sFROM + "@UNIVERSITE-PARIS-SACLAY.FR>\r\nTo: \"" + sTO + "\" <" + sMAIL + ">\r\nSubject:" + sSUBJECT + "\r\n" + sDATA + "\r\n\r\n.\r\n", "QUIT\r\n" },
                                { "", "HELO Mbed\r\n", "MAIL FROM: <Mbed>\r\n","RCPT TO: <" + sMAIL + ">\r\n", "QUIT\r\n" }};
    string code;
    if(eth_error("clientSMTP_open", clientSMTP.open(_eth)) == NSAPI_ERROR_OK)
    {
        for(const string& ssend : smtpParams[(sFROM.empty())?1:0])
        {
            char buffer[256] = {0};
            if(code.empty()) { if(eth_error("clientSMTP_connect", clientSMTP.connect(SocketAddress(server, 25))) < NSAPI_ERROR_OK)      break; }
            else if(eth_error("clientSMTP_send", clientSMTP.send(ssend.c_str(), ssend.size())) < NSAPI_ERROR_OK)                        break;
            if(eth_error("clientSMTP_recv", clientSMTP.recv(buffer, 256)) < NSAPI_ERROR_OK)                                             break;
            buffer[3] = 0;
            code += buffer;
            if(ssend == "QUIT\r\n") break;
        }
        eth_error("clientSMTP_close", clientSMTP.close());
    }
    if(sFROM.empty()) return code == "220250250250221";
    #if MBED_MAJOR_VERSION > 5
    else if(code != "220250250250354250221") _queue.call_in(60s, this, &Transmission::smtp, mail, from, subject, data, server);
    #else
    else if(code != "220250250250354250221") _queue.call_in(60000, this, &Transmission::smtp, mail, from, subject, data, server);
    #endif
    return code == "220250250250354250221";
}

time_t Transmission::ntp(const char* server)
{
    if(!_eth) return 0;
    if((!message.DHCP) || (_eth->get_connection_status() != NSAPI_STATUS_GLOBAL_UP)) return 0;
    time_t timeStamp = 0;
    UDPSocket clientNTP;
    clientNTP.set_timeout(2000);
    if(eth_error("clientNTP_open", clientNTP.open(_eth)) == NSAPI_ERROR_OK)
    {
        uint32_t buffer[12] = { 0b11011, 0 };  // VN = 3 & Mode = 3
        SocketAddress aSERVER(server, 123);
        string sSERVER(server);
        if(sSERVER != TRANSMISSION_DEFAULT_NTP_SERVER) eth_error("eth_gethostbyname", _eth->gethostbyname(sSERVER.c_str(), &aSERVER));
        if(eth_error("clientNTP_send", clientNTP.sendto(aSERVER, (void*)buffer, sizeof(buffer))) > NSAPI_ERROR_OK)
        {
            if(eth_error("clientNTP_recv", clientNTP.recvfrom(NULL, (void*)buffer, sizeof(buffer))) > NSAPI_ERROR_OK)
            {
                timeStamp = ((buffer[10] & 0xFF) << 24) | ((buffer[10] & 0xFF00) << 8) | ((buffer[10] & 0xFF0000UL) >> 8) | ((buffer[10] & 0xFF000000UL) >> 24);
                timeStamp -= 2208985200U;   // 01/01/1970 Europe
                struct tm * tmTimeStamp = localtime(&timeStamp);
                if (((tmTimeStamp->tm_mon > 2) && (tmTimeStamp->tm_mon < 10)) || ((tmTimeStamp->tm_mon == 2) && ((tmTimeStamp->tm_mday - tmTimeStamp->tm_wday) > 24)) || ((tmTimeStamp->tm_mon == 9) && ((tmTimeStamp->tm_mday - tmTimeStamp->tm_wday) < 25)))
                    timeStamp += 3600;  // DST starts last Sunday of March; 2am (1am UTC), DST ends last Sunday of october; 3am (2am UTC)    
            }
        }
        eth_error("clientNTP_close", clientNTP.close());
    }
    return timeStamp;
}

intptr_t Transmission::eth_status(const string& source, const intptr_t& code)
{
    #ifndef NDEBUG
    ostringstream message;
    message << "\n" << source << "[" << code;
    switch(code)
    {
        case NSAPI_STATUS_LOCAL_UP:             message << "] NSAPI_STATUS_LOCAL_UP          < local IP address set >";     break;
        case NSAPI_STATUS_GLOBAL_UP:            message << "] NSAPI_STATUS_GLOBAL_UP         < global IP address set >";    break;
        case NSAPI_STATUS_DISCONNECTED:         message << "] NSAPI_STATUS_DISCONNECTED      < no connection to network >"; break;
        case NSAPI_STATUS_CONNECTING:           message << "] NSAPI_STATUS_CONNECTING        < connecting to network >";    break;
        case NSAPI_STATUS_ERROR_UNSUPPORTED:    message << "] NSAPI_STATUS_ERROR_UNSUPPORTED < unsupported functionality >";break;
    }
    _serial->write(message.str().c_str(), message.str().size());
    #endif
    return code;
}

nsapi_error_t Transmission::eth_error(const string& source, const nsapi_error_t& code)
{
    #ifndef NDEBUG
    ostringstream message;
    message << "\n" << source << "[" << code;
    switch(code)
    {
        case NSAPI_ERROR_OK:                    message << "] NSAPI_ERROR_OK                 < no error >";                                         break;
        case NSAPI_ERROR_WOULD_BLOCK:           message << "] NSAPI_ERROR_WOULD_BLOCK        < no data is not available but call is non-blocking >";break;
        case NSAPI_ERROR_UNSUPPORTED:           message << "] NSAPI_ERROR_UNSUPPORTED        < unsupported functionality >";                        break;
        case NSAPI_ERROR_PARAMETER:             message << "] NSAPI_ERROR_PARAMETER          < invalid configuration >";                            break;
        case NSAPI_ERROR_NO_CONNECTION:         message << "] NSAPI_ERROR_NO_CONNECTION      < not connected to a network >";                       break;
        case NSAPI_ERROR_NO_SOCKET:             message << "] NSAPI_ERROR_NO_SOCKET          < socket not available for use >";                     break;
        case NSAPI_ERROR_NO_ADDRESS:            message << "] NSAPI_ERROR_NO_ADDRESS         < IP address is not known >";                          break;
        case NSAPI_ERROR_NO_MEMORY:             message << "] NSAPI_ERROR_NO_MEMORY          < memory resource not available >";                    break;
        case NSAPI_ERROR_NO_SSID:               message << "] NSAPI_ERROR_NO_SSID            < ssid not found >";                                   break;
        case NSAPI_ERROR_DNS_FAILURE:           message << "] NSAPI_ERROR_DNS_FAILURE        < DNS failed to complete successfully >";              break;
        case NSAPI_ERROR_DHCP_FAILURE:          message << "] NSAPI_ERROR_DHCP_FAILURE       < DHCP failed to complete successfully >";             break;
        case NSAPI_ERROR_AUTH_FAILURE:          message << "] NSAPI_ERROR_AUTH_FAILURE       < connection to access point failed >";                break;
        case NSAPI_ERROR_DEVICE_ERROR:          message << "] NSAPI_ERROR_DEVICE_ERROR       < failure interfacing with the network processor >";   break;
        case NSAPI_ERROR_IN_PROGRESS:           message << "] NSAPI_ERROR_IN_PROGRESS        < operation (eg connect) in progress >";               break;
        case NSAPI_ERROR_ALREADY:               message << "] NSAPI_ERROR_ALREADY            < operation (eg connect) already in progress >";       break;
        case NSAPI_ERROR_IS_CONNECTED:          message << "] NSAPI_ERROR_IS_CONNECTED       < socket is already connected >";                      break;
        case NSAPI_ERROR_CONNECTION_LOST:       message << "] NSAPI_ERROR_CONNECTION_LOST    < connection lost >";                                  break;
        case NSAPI_ERROR_CONNECTION_TIMEOUT:    message << "] NSAPI_ERROR_CONNECTION_TIMEOUT < connection timed out >";                             break;
        case NSAPI_ERROR_ADDRESS_IN_USE:        message << "] NSAPI_ERROR_ADDRESS_IN_USE     < Address already in use >";                           break;
        case NSAPI_ERROR_TIMEOUT:               message << "] NSAPI_ERROR_TIMEOUT            < operation timed out >";                              break;
        case NSAPI_ERROR_BUSY:                  message << "] NSAPI_ERROR_BUSY               < device is busy and cannot accept new operation >";   break;
        default:                                message << "] NSAPI_ERROR                    < unknow code >";                                      break;
    }
    if(code < NSAPI_ERROR_OK) _serial->write(message.str().c_str(), message.str().size());
    #endif
    return code;
}