月份: 2021 年 3 月

ESP8266 使用 ping

No Comments

官方所提供的 ping functions 是使用 callback function,並且有處理時間長短上的限制;我是說,函式內應避免使用到 printf 等耗時函式。再加上應用 ping 是有數種不同的目的,所以基本上來講使用上並不那麼就手。故實作 wrapped functions。以下程式碼是失敗的作品,以 C++ 繼承似乎實作不了。若可請分享之。
再下一段程式碼便以 C-style 實作之。

#define PING_MAXTIME_TIMEOUT_S 10 // in seconds, if set interval greater than it, it will taken. this prevent ping from halt.

typedef struct __attribute__((__packed__)) cMyPing: public ping_option{
    // global instances must define in ICACHE_FLASH_ATTR.
    // however, it caused rst cause:2, boot mode:(3,6). So, use IRAM_ATTR instead.

    #if 0
    typedef void (* ping_recv_function)(void* arg, void *pdata);
    typedef void (* ping_sent_function)(void* arg, void *pdata);

    struct ping_option{
        uint32 count;
        uint32 ip;
        uint32 coarse_time;
        ping_recv_function recv_function;
        ping_sent_function sent_function;
        void* reverse;
    };

    struct ping_resp{
        uint32 total_count;
        uint32 resp_time;
        uint32 seqno;
        uint32 timeout_count;
        uint32 bytes;
        uint32 total_bytes;
        uint32 total_time;
        sint8  ping_err;
    };

    ICACHE_FLASH_ATTR void user_ping_recv_cb(void *arg, void *pdata){
        struct ping_resp *ping_resp = (struct ping_resp*)pdata;
        struct ping_option *ping_opt = (struct ping_option*)arg;
        if (ping_resp->ping_err == -1){
            os_printf("ping failed.\r\n");
        }
        else {
            os_printf("ping recv: bytes = %d, time = %d ms.\r\n", ping_resp->bytes, ping_resp->resp_time);
        }
    };

    ICACHE_FLASH_ATTR void user_ping_sent_cb(void *arg, void *pdata){
        os_printf("user ping finish.\r\n"); // seems not called.
    };

    ICACHE_FLASH_ATTR void ping(const char *ping_ip, unsigned count, unsigned interval_s){
        // the interval_s also means timeout of a trial.

        struct ping_option ping_opt;

        ///// ping_opt = (struct ping_option *)os_zalloc(sizeof(struct ping_option));
        for (int i=0; i<sizeof(ping_opt); i++) ((unsigned char*)(&ping_opt))[i]=0;

        ping_opt.count=count; // try to ping how many times
        ping_opt.coarse_time=interval_s; // ping interval
        ping_opt.ip = ipaddr_addr(ping_ip);

        ping_regist_recv(&ping_opt, user_ping_recv_cb);
        ping_regist_sent(&ping_opt, user_ping_sent_cb); // seems not called.

        ping_start(&ping_opt);
    };
    #endif // if 0; prototype

    unsigned total_cnt;
    unsigned success_cnt;
    unsigned failure_cnt;
    unsigned min_resp_ms;
    unsigned max_resp_ms;
    unsigned min_bytes;
    unsigned start_time;

    IRAM_ATTR static void user_ping_recv_cb(void *arg, void *pdata){
        struct ping_resp *ping_resp = (struct ping_resp*)pdata;

        ///// compile-time dynamic cast here is not supposed to work,
        ///// nevertheless, c-cast failed too/not working runtime.
        ///// so the inheritance falls in failure to implement wrapping ping function.
        ///// so conclusion, only c-style structs can implement.
        //cMyPing *ping_opt = dynamic_cast<cMyPing*>(reinterpret_cast<ping_option*>(arg));
        cMyPing *ping_opt = (cMyPing*)arg; // this just try to assume the full cMyPing in c-cast-style.

        if (ping_resp->ping_err == -1){
            ping_opt->failure_cnt++;
        }
        else {
            if (ping_resp->bytes){
                if (!ping_opt->min_bytes) ping_opt->min_bytes=ping_resp->bytes;
                else if (ping_opt->min_bytes>ping_resp->bytes) ping_opt->min_bytes=ping_resp->bytes;
            }
            if (!ping_opt->min_resp_ms){ping_opt->min_resp_ms=ping_opt->max_resp_ms=ping_resp->resp_time;}
            else if (ping_resp->resp_time<ping_opt->min_resp_ms) ping_opt->min_resp_ms=ping_resp->resp_time;
            else if (ping_resp->resp_time>ping_opt->max_resp_ms) ping_opt->max_resp_ms=ping_resp->resp_time;
            ping_opt->success_cnt++;
        }
    };

    void reset_vars(){start_time=min_bytes=total_cnt=success_cnt=failure_cnt=min_resp_ms=max_resp_ms=0;};

    bool Set(const char *ping_ip, unsigned count=1, unsigned interval_s=3){
        if (!isDone()) return false;
        reset_vars();

        // prepare the ping structure
        ping_option *base=dynamic_cast<ping_option*>(this);
        for (int i=0; i<sizeof(ping_option)/4; i++) ((int*)base)[i]=0; // because IRAM_ATTR

        this->total_cnt=count; // try to ping how many times
        this->coarse_time=(interval_s>PING_MAXTIME_TIMEOUT_S? PING_MAXTIME_TIMEOUT_S: interval_s); // ping interval
        this->ip = ipaddr_addr(ping_ip);
        ping_regist_recv(base, (ping_recv_function)(cMyPing::user_ping_recv_cb));

        return true;
    };

    ~cMyPing(){while (!isDone());}; // must careful wdt-reset here. maintained by user, obj can not destroy before ping done.

    cMyPing(){reset_vars();};

    cMyPing(const char *ping_ip, unsigned count=1, unsigned interval_s=3){
        reset_vars();
        this->Set(ping_ip, count, interval_s);
    }

    bool Restart(){ // goes only if a new one or done the previous ping.
        if (isDone()){
            reset_vars();
            start_time=micros();
            ping_start(dynamic_cast<ping_option*>(this));
            return true;
        }
        return false;
    };

    bool wasResponsed(){return success_cnt+failure_cnt;};

    bool isAlive(){return success_cnt;};

    bool isDown(){return total_cnt==failure_cnt;};

    bool isDone(){return success_cnt+failure_cnt==total_cnt;};

    bool wasTimeout(){ // for Ping itself timeout
        if (micros()-start_time>PING_MAXTIME_TIMEOUT_S*1000000){
            return true;
        }
        return false;
    };

    void PrintPresent(){
        printf("cMyPing[resp time[min: %u, max: %u]; count[all: %u, o: %u, x: %u]; bytes[min: %u].]\r\n",\
            min_resp_ms, max_resp_ms, total_cnt, success_cnt, failure_cnt, min_bytes);
    };
} cMyPing;

以下是再版的程式碼,已經會動作了。不過我們愕然發現,要掌控 ping 並不容易。
由執行結果得知,所設定的 interval,並非單單指 timeout 的時間距。而是,它輪詢的時間,或說,它一開始便會送出第一次 ping,但,它會等到 timeout 後,不管有沒有成功,才會送出下一次的 ping。當然若成功,會立即回應該次數據。請將 interval 設大,例如 count 做 30 次,interval 8 秒就可看出端倪。因此,還須再改版。

#define PING_MAXTIME_TIMEOUT_S 10 // in seconds, if set interval greater than it, it will taken. this prevent ping from halt.

typedef class cMyPing{
    // global instances must define in ICACHE_FLASH_ATTR.
    // however, it caused rst cause:2, boot mode:(3,6). So, use IRAM_ATTR instead.

    #if 0 // prototype
    typedef void (* ping_recv_function)(void* arg, void *pdata);
    typedef void (* ping_sent_function)(void* arg, void *pdata);

    struct ping_option{
        uint32 count;
        uint32 ip;
        uint32 coarse_time;
        ping_recv_function recv_function;
        ping_sent_function sent_function;
        void* reverse;
    };

    struct ping_resp{
        uint32 total_count;
        uint32 resp_time;
        uint32 seqno;
        uint32 timeout_count;
        uint32 bytes;
        uint32 total_bytes;
        uint32 total_time;
        sint8  ping_err;
    };

    ICACHE_FLASH_ATTR void user_ping_recv_cb(void *arg, void *pdata){
        struct ping_resp *ping_resp = (struct ping_resp*)pdata;
        struct ping_option *ping_opt = (struct ping_option*)arg;
        if (ping_resp->ping_err == -1){
            os_printf("ping failed.\r\n");
        }
        else {
            os_printf("ping recv: bytes = %d, time = %d ms.\r\n", ping_resp->bytes, ping_resp->resp_time);
        }
    };

    ICACHE_FLASH_ATTR void user_ping_sent_cb(void *arg, void *pdata){
        os_printf("user ping finish.\r\n"); // seems not called.
    };

    ICACHE_FLASH_ATTR void ping(const char *ping_ip, unsigned count, unsigned interval_s){
        // the interval_s also means timeout of a trial.

        struct ping_option ping_opt;

        ///// ping_opt = (struct ping_option *)os_zalloc(sizeof(struct ping_option));
        for (int i=0; i<sizeof(ping_opt); i++) ((unsigned char*)(&ping_opt))[i]=0;

        ping_opt.count=count; // try to ping how many times
        ping_opt.coarse_time=interval_s; // ping interval
        ping_opt.ip = ipaddr_addr(ping_ip);

        ping_regist_recv(&ping_opt, user_ping_recv_cb);
        ping_regist_sent(&ping_opt, user_ping_sent_cb); // seems not called.

        ping_start(&ping_opt);
    };
    #endif // if 0; prototype

    typedef struct scMyPingObj{
        ping_option po;
        unsigned success_cnt;
        unsigned failure_cnt;
        unsigned min_resp_ms;
        unsigned max_resp_ms;
        unsigned min_bytes;
        unsigned start_time;
    } scMyPingObj;

    public:

    scMyPingObj obj;

    IRAM_ATTR static void user_ping_recv_cb(void *arg, void *pdata){ // must IRAM_ATTR rather than ICACHE_FLASH_ATTR
        struct ping_resp *ping_resp = (struct ping_resp*)pdata;
        scMyPingObj *ping_opt = (scMyPingObj*)arg;

        if (ping_resp->ping_err == -1){
            ping_opt->failure_cnt++;
        }
        else {
            if (ping_resp->bytes){
                if (!ping_opt->min_bytes) ping_opt->min_bytes=ping_resp->bytes;
                else if (ping_opt->min_bytes>ping_resp->bytes) ping_opt->min_bytes=ping_resp->bytes;
            }
            if (!ping_opt->min_resp_ms){ping_opt->min_resp_ms=ping_opt->max_resp_ms=ping_resp->resp_time;}
            else if (ping_resp->resp_time<ping_opt->min_resp_ms) ping_opt->min_resp_ms=ping_resp->resp_time;
            else if (ping_resp->resp_time>ping_opt->max_resp_ms) ping_opt->max_resp_ms=ping_resp->resp_time;
            ping_opt->success_cnt++;
        }
    };

    void reset_vars(){
        obj.start_time=\
        obj.min_bytes=\
        obj.success_cnt=\
        obj.failure_cnt=\
        obj.min_resp_ms=\
        obj.max_resp_ms=0;
    };

    bool Set(const char *ping_ip, unsigned count=1, unsigned interval_s=3){
        if (!isDone()) return false;
        reset_vars();

        // prepare the ping structure
        ping_option *base=(ping_option*)&(this->obj);
        for (int i=0; i<sizeof(ping_option)/4; i++) ((int*)base)[i]=0; // because IRAM_ATTR
        // what if not multiple of 4? add tail stub in the object.

        this->obj.po.count=count; // try to ping how many times
        this->obj.po.coarse_time=(interval_s>PING_MAXTIME_TIMEOUT_S? PING_MAXTIME_TIMEOUT_S: interval_s); // ping interval
        this->obj.po.ip = ipaddr_addr(ping_ip);
        ping_regist_recv(base, (ping_recv_function)(cMyPing::user_ping_recv_cb));

        return true;
    };

    ~cMyPing(){while (!isDone());}; // must careful wdt-reset here. maintained by user, instance can not destroy before ping done.

    cMyPing(){reset_vars();};

    cMyPing(const char *ping_ip, unsigned count=1, unsigned interval_s=3){
        reset_vars();
        this->Set(ping_ip, count, interval_s);
    };

    bool Restart(){ // goes only if a new one or done the previous ping.
        if (isDone()){
            reset_vars();
            obj.start_time=micros();
            ping_start((ping_option*)&obj);
            return true;
        }
        return false;
    };

    bool wasResponsed(){return obj.success_cnt+obj.failure_cnt;};

    bool isAlive(){return obj.success_cnt;};

    bool isDown(){return obj.po.count==obj.failure_cnt;};

    bool isDone(){
        unsigned x=obj.success_cnt+obj.failure_cnt;
        return (wasTimeout() || (x==0) || (x==obj.po.count));
    };

    bool wasTimeout(){ // for Ping itself timeout
        if (!obj.start_time) return true;
        if (micros()-obj.start_time>PING_MAXTIME_TIMEOUT_S*1000000){
            return true;
        }
        return false;
    };

    void PrintPresent(){
        printf("\r\ncMyPing::ping_option[count= %d, ip= %s, tmo= %d.]\r\n\r\n",\
            obj.po.count, IPAddress(obj.po.ip).toString().c_str(), obj.po.coarse_time);
        printf("cMyPing[wasResponsed= %d, isAlive= %d, isDown= %d, isDone= %d, wasTimeout= %d.]\r\n\r\n",\
            wasResponsed(), isAlive(), isDown(), isDone(), wasTimeout());
        printf("cMyPing[resp time[min: %u, max: %u]; count[all: %u, o: %u, x: %u]; bytes[min: %u].]\r\n\r\n",\
            obj.min_resp_ms, obj.max_resp_ms, obj.po.count, obj.success_cnt, obj.failure_cnt, obj.min_bytes);
    };
} cMyPing;

接下來,自己所預期的程式總算完成了。如下程式碼(有很大的弱點,若 native ping functions 不如預期地動作,則將永遠卡在解構子中。目前已知的 issue:若兩個以上變數 ping 同一個位址,就會發生卡住或 wdt-reset)。在此段程式碼之後,筆者加了一主題,是網路上找到的 AsyncPing,先把相關資訊放上。有空再來研究了。

typedef class cMyPing{
    // the native ping function: the interval which is the waiting time in seconds and then to ping.
    // however for the first time, no waiting and get the result.
    // and the times to ping, if 0, would be 4 times.
    //
    // usage: cMyPing("192.168.1.1").Restart().isAlive(); // for a one-shot. however, must concern about destruct.
    // note: if failed unexpectedly, try to increase the delay in Restart().

    #if 0 // prototype
    typedef void (* ping_recv_function)(void* arg, void *pdata);
    typedef void (* ping_sent_function)(void* arg, void *pdata);

    struct ping_option{
        uint32 count;
        uint32 ip;
        uint32 coarse_time;
        ping_recv_function recv_function;
        ping_sent_function sent_function;
        void* reverse;
    };

    struct ping_resp{
        uint32 total_count;
        uint32 resp_time;
        uint32 seqno;
        uint32 timeout_count;
        uint32 bytes;
        uint32 total_bytes;
        uint32 total_time;
        sint8  ping_err;
    };

    ICACHE_FLASH_ATTR void user_ping_recv_cb(void *arg, void *pdata){
        struct ping_resp *ping_resp = (struct ping_resp*)pdata;
        struct ping_option *ping_opt = (struct ping_option*)arg;
        if (ping_resp->ping_err == -1){
            os_printf("ping failed.\r\n");
        }
        else {
            os_printf("ping recv: bytes = %d, time = %d ms.\r\n", ping_resp->bytes, ping_resp->resp_time);
        }
    };

    ICACHE_FLASH_ATTR void user_ping_sent_cb(void *arg, void *pdata){
        os_printf("user ping finish.\r\n"); // seems not called.
    };

    ICACHE_FLASH_ATTR void ping(const char *ping_ip, unsigned count, unsigned interval_s){
        // the interval_s also means timeout of a trial.

        struct ping_option ping_opt;

        ///// ping_opt = (struct ping_option *)os_zalloc(sizeof(struct ping_option));
        for (int i=0; i<sizeof(ping_opt); i++) ((unsigned char*)(&ping_opt))[i]=0;

        ping_opt.count=count; // try to ping how many times
        ping_opt.coarse_time=interval_s; // ping interval
        ping_opt.ip = ipaddr_addr(ping_ip);

        ping_regist_recv(&ping_opt, user_ping_recv_cb);
        ping_regist_sent(&ping_opt, user_ping_sent_cb); // seems not called.

        ping_start(&ping_opt);
    };
    #endif // if 0; prototype

    typedef struct scMyPingObj{
        ping_option po;
        unsigned success_cnt;
        unsigned failure_cnt;
        unsigned min_resp_ms;
        unsigned max_resp_ms;
        unsigned min_bytes;
        int last_time_failure;
    } scMyPingObj;

    public:

    scMyPingObj obj;

    ICACHE_FLASH_ATTR static void user_ping_recv_cb(void *arg, void *pdata){
        struct ping_resp *ping_resp = (struct ping_resp*)pdata;
        scMyPingObj *ping_opt = (scMyPingObj*)arg;

        #if 1 // for ICACHE_FLASH_ATTR, not for IRAM_ATTR since ping_err is not 4byte.
        if (ping_resp->ping_err == -1){
            ping_opt->failure_cnt++;
            ping_opt->last_time_failure=1;
        }
        #else // for IRAM_ATTR
        if (ping_resp->timeout_count!=ping_opt->failure_cnt){ // however, it not work due to timeout_count wrong.
            ping_opt->failure_cnt=ping_resp->timeout_count;
            ping_opt->last_time_failure=1;
        }
        #endif // if 0

        else {
            if (ping_resp->bytes){
                if (!ping_opt->min_bytes) ping_opt->min_bytes=ping_resp->bytes;
                else if (ping_opt->min_bytes>ping_resp->bytes) ping_opt->min_bytes=ping_resp->bytes;
            }
            if (!ping_opt->min_resp_ms){ping_opt->min_resp_ms=ping_opt->max_resp_ms=ping_resp->resp_time;}
            else if (ping_resp->resp_time<ping_opt->min_resp_ms) ping_opt->min_resp_ms=ping_resp->resp_time;
            else if (ping_resp->resp_time>ping_opt->max_resp_ms) ping_opt->max_resp_ms=ping_resp->resp_time;
            ping_opt->success_cnt++;
            ping_opt->last_time_failure=0;
        }
    };

    void reset_vars(){
        obj.min_bytes=\
        obj.success_cnt=\
        obj.failure_cnt=\
        obj.min_resp_ms=\
        obj.max_resp_ms=0;
        obj.last_time_failure=-1;
    };

    bool Set(const char *ping_ip, unsigned interval_s=1, unsigned count=1){ // count=0 means forever. interval_s=0 means 1.
        if (!isDone()) return false;
        reset_vars();
        if (!count) count=-1;
        if (!interval_s) interval_s=1;

        // prepare the ping structure
        ping_option *base=(ping_option*)&(this->obj);
        for (int i=0; i<sizeof(ping_option)/4; i++) ((int*)base)[i]=0; // because IRAM_ATTR
        // what if not multiple of 4? add tail stub in the object.

        this->obj.po.count=count; // try to ping how many times
        this->obj.po.coarse_time=interval_s; // ping interval
        this->obj.po.ip = ipaddr_addr(ping_ip);
        ping_regist_recv(base, (ping_recv_function)(cMyPing::user_ping_recv_cb));

        return true;
    };

    ~cMyPing(){ // must careful wdt-reset here. maintained by user, instance can not destroy before ping done, or ping mad.
        if (!isDone()) delay(obj.po.coarse_time*(obj.po.count-obj.failure_cnt-obj.success_cnt)*1000);
        while (!isDone()){ // there is a case forever waiting that if the target down and submit ping. hence, the delay used.
            delay(3);
        }
    };

    cMyPing(){reset_vars();};

    cMyPing(const char *ping_ip, unsigned interval_s=1, unsigned count=1){
        reset_vars();
        this->Set(ping_ip, interval_s, count);
    };

    cMyPing& Restart(){ // goes only if a new one or done the previous ping. must check isDone() before able to do Restart().
        if (isDone()){
            reset_vars();
            obj.last_time_failure=1;
            ping_start((ping_option*)&obj);
            delay(50); // use delay for the frist time ping having enough time to response in order to other functions behave correct.
        }
        return *this;
    };

    bool wasResponsed(){return obj.success_cnt+obj.failure_cnt;};

    bool isAlive(){return !obj.last_time_failure;};

    bool isDown(){return ((obj.po.count==obj.failure_cnt) || obj.last_time_failure);};

    bool isDone(){
        unsigned x=obj.success_cnt+obj.failure_cnt;
        return ((obj.last_time_failure==-1) || (x==obj.po.count));
    };

    void PrintPresent(){
        printf("\r\ncMyPing::ping_option[count= %d, ip= %s, tmo= %d.]\r\n",\
            obj.po.count, IPAddress(obj.po.ip).toString().c_str(), obj.po.coarse_time);
        printf("cMyPing[wasResponsed= %d, isAlive= %d, isDown= %d, isDone= %d.]\r\n",\
            wasResponsed(), isAlive(), isDown(), isDone());
        printf("cMyPing[resp time[min: %u, max: %u]; count[all: %u, o: %u, x: %u]; bytes[min: %u].]\r\n\r\n",\
            obj.min_resp_ms, obj.max_resp_ms, obj.po.count, obj.success_cnt, obj.failure_cnt, obj.min_bytes);
    };
} cMyPing;

AsyncPing

  • https://github.com/akaJes/AsyncPing
  • https://www.sigmdel.ca/michel/program/esp8266/arduino/asyncping_en.html

Categories: Arduino

Tags:

PHP Code Snippets Powered By : XYZScripts.com