ESP8266 使用 ping
官方所提供的 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
發佈留言