• 【Arduino】esp01 Relay 转接板自动ping ip断电重启


    找到一个RGB颜色代码的网站       颜色选择表

    目录

    创作前情

    项目规划

    实现过程

     软件实现

    app界面如下:

    arduino代码如下:

     硬件实现

    后续优化


    创作前情

        目前使用树莓派装haos来做智能家居服务器,不知道是硬件问题还是哪里的设定问题,时不时挂掉,访问不了,需要手动断电重启,才能恢复,奈何找不到原因,本来是使用了一个米家智能插座来手动断电重启,但需要到用的时候才发现访问不了,需要进入米家重启,体验太差,所以想着如果可以有一个设备可以自己侦测是否正常,如果有问题,自动断电重启,这样会好很多。

    项目规划

       方案一:使用esphome 来控制断电重启,最方便的一种,但目前没找到,使用esphome 来检测homeassistant是否挂掉的方法,故放弃。

      方案二: 接入米家,继续使用米家插座来控制断电重启,也是需要先侦测到访问异常,再通过改造米家传感器接入,相对复杂,

      方案三:借助blinker平台接入,本身使用arduino编程,相对熟悉很多,使用esp8266 ping库直接来侦测访问是否异常,点灯app可以控制断电以及各种状态,

    实现过程

    •  软件实现

      选择方案三来做,可以在之前浇水的代码上修改来实现相关功能,目前已实现如下功能

    1. 检测访问是否正常,如果不正常则自动断电重启,
    2. app界面可以手动控制继电器状态,达到手动重启,
    3. app显示 ip地址、信号强度,异常次数,最近一次异常时间以及目前访问状态

    app界面如下:

    arduino代码如下:

    1. /* *****************************************************************
    2. Blinker 库下载地址:
    3. https://github.com/blinker-iot/blinker-library/archive/master.zip
    4. Blinker 是一套跨硬件、跨平台的物联网解决方案,提供APP端、设备端、
    5. 服务器端支持,使用公有云服务进行数据传输存储。可用于智能家居、
    6. 数据监测等领域,可以帮助用户更好更快地搭建物联网项目。
    7. 如果使用 ESP8266 接入 Blinker,
    8. 请确保安装了 2.7.4 或更新的 ESP8266/Arduino 支持包。
    9. https://github.com/esp8266/Arduino/releases
    10. 如果使用 ESP32 接入 Blinker,
    11. 请确保安装了 1.0.5 或更新的 ESP32/Arduino 支持包。
    12. https://github.com/espressif/arduino-esp32/releases
    13. 文档: https://diandeng.tech/doc
    14. * *****************************************************************/
    15. #define BLINKER_WIFI
    16. #include
    17. #include
    18. #include
    19. #include
    20. #include
    21. #include
    22. extern "C"
    23. {
    24. #include // needed for icmp packet definitions
    25. }
    26. // Set global to avoid object removing after setup() routine
    27. Pinger pinger;
    28. auto timer = timer_create_default(); // create a timer with default settings
    29. bool ip_timer_flag = true;
    30. bool ip_app_check_flag = true;
    31. bool ip_lost_flag = false;
    32. bool loss_true = false;
    33. bool otaflag = false;
    34. uint32_t read_time = 0;
    35. uint16_t blinker_year, error_time_final, error_time = 0;
    36. uint8_t blinker_month, blinker_mday, blinker_wday, blinker_hour, blinker_min, blinker_sec;
    37. uint8_t a0 = 1, a1 = 1, b0 = 1, b1 = 1, c0 = 1, c1 = 1, d0 = 0, d1 = 1;
    38. uint16_t check_timer_time = 30, check_error_time = 8, clen_error_timer = 600;
    39. String fh, fh0;
    40. char auth[] = "*****************";
    41. char ssid[] = "************";
    42. char pswd[] = "****************";
    43. //---------------------------------引脚配置-----------------------------
    44. #define VCCPIN D3 // 指示灯
    45. #define BUTTON_1 "ButtonKey"
    46. #define BUTTON_2 "onoff"
    47. #define BUTTON_3 "reset"
    48. #define BUTTON_4 "status"
    49. #define BUTTON_5 "ota"
    50. #define TEXTE_time "tex-time"
    51. #define RUN_time "tex-run"
    52. #define TEXTE_ip "tex-ip"
    53. BlinkerButton Button1(BUTTON_1);
    54. BlinkerButton Button2(BUTTON_2);
    55. BlinkerButton Button3(BUTTON_3);
    56. BlinkerButton Button4(BUTTON_4);
    57. BlinkerButton Button5(BUTTON_5);
    58. BlinkerText Text_ip(TEXTE_ip);
    59. BlinkerText Text_time(TEXTE_time);
    60. BlinkerText Text_runtime(RUN_time);
    61. BlinkerNumber Number1("num-xinhao"); // 定义信号强度键名
    62. BlinkerNumber Number2("num-error"); // 定义信号强度键名
    63. BlinkerNumber Number3("error"); // 定义信号强度键名
    64. void button1_callback(const String &state)
    65. {
    66. // digitalWrite(LED_BUILTIN, !digitalRead(LED_BUILTIN));
    67. BLINKER_LOG("get button state: ", state);
    68. if (state == BLINKER_CMD_ON)
    69. {
    70. BLINKER_LOG("Toggle on!");
    71. ip_app_check_flag = true;
    72. a0 = 1;
    73. a1 = 1;
    74. // Button1.icon("icon_1");
    75. Button1.color("#FF6666");
    76. Button1.text("检测");
    77. Button1.print("on");
    78. }
    79. else if (state == BLINKER_CMD_OFF)
    80. {
    81. BLINKER_LOG("Toggle off!");
    82. ip_app_check_flag = false;
    83. a0 = 0;
    84. a1 = 1;
    85. // Button1.icon("icon_1");
    86. Button1.color("#FF6600");
    87. Button1.text("不检测");
    88. Button1.print("off");
    89. }
    90. }
    91. void button2_callback(const String &state)
    92. {
    93. // digitalWrite(LED_BUILTIN, !digitalRead(LED_BUILTIN));
    94. BLINKER_LOG("get button state: ", state);
    95. if (state == BLINKER_CMD_ON)
    96. {
    97. BLINKER_LOG("Toggle on!");
    98. digitalWrite(VCCPIN, LOW);
    99. b0 = 1;
    100. b1 = 1;
    101. Button2.color("#FF6666");
    102. Button2.text("打开");
    103. Button2.print("on");
    104. }
    105. else if (state == BLINKER_CMD_OFF)
    106. {
    107. BLINKER_LOG("Toggle off!");
    108. digitalWrite(VCCPIN, HIGH);
    109. b0 = 0;
    110. b1 = 1;
    111. Button2.color("#FF6600");
    112. Button2.text("关闭");
    113. Button2.print("off");
    114. }
    115. }
    116. void button3_callback(const String &state)
    117. {
    118. // digitalWrite(LED_BUILTIN, !digitalRead(LED_BUILTIN));
    119. BLINKER_LOG("get button state: ", state);
    120. if (state == BLINKER_CMD_BUTTON_TAP)
    121. {
    122. BLINKER_LOG("Button tap!");
    123. error_time_final = 0;
    124. // Button3.text("Your button name or describe");
    125. Button3.print();
    126. }
    127. }
    128. void button4_callback(const String &state)
    129. {
    130. // digitalWrite(LED_BUILTIN, !digitalRead(LED_BUILTIN));
    131. BLINKER_LOG("get button state: ", state);
    132. }
    133. void button5_callback(const String &state)
    134. {
    135. BLINKER_LOG("button5_callback get button state: ", state);
    136. if (state == BLINKER_CMD_ON)
    137. {
    138. d0 = 1;
    139. d1 = 1;
    140. otaflag = true;
    141. Button5.color("#00b0FF");
    142. Button5.text("OTA");
    143. Button5.print("on");
    144. }
    145. else if (state == BLINKER_CMD_OFF)
    146. {
    147. d0 = 0;
    148. d1 = 1;
    149. otaflag = false;
    150. Button5.color("#607E8B");
    151. Button5.text("NO OTA");
    152. Button5.print("off");
    153. }
    154. }
    155. void heartbeat()
    156. {
    157. huidiaoneirong();
    158. }
    159. /*********************************************************************************************************
    160. 按键状态回调函数
    161. *********************************************************************************************************/
    162. void huidiaoneirong()
    163. {
    164. Number1.icon("fal fa-wifi");
    165. Number1.print(WiFi.RSSI());
    166. Number2.print(error_time_final);
    167. Number3.print(error_time);
    168. Text_ip.print(WiFi.localIP().toString().c_str());
    169. Text_time.print(fh);
    170. Text_runtime.print(fh0);
    171. if ((a1 == 1) && (a0 == 1))
    172. {
    173. a1 = 0;
    174. Button1.color("#66CC66");
    175. Button1.text("检测");
    176. Button1.print("on");
    177. }
    178. else if ((a1 == 1) && (a0 == 0))
    179. {
    180. a1 = 0;
    181. Button1.color("#FF6600");
    182. Button1.text("不检测");
    183. Button1.print("off");
    184. }
    185. if ((b1 == 1) && (b0 == 1))
    186. {
    187. b1 = 0;
    188. Button2.print("on");
    189. Button2.color("#66CC66");
    190. Button2.text("供电开");
    191. }
    192. else if ((b1 == 1) && (b0 == 0))
    193. {
    194. b1 = 0;
    195. Button2.color("#FF6600");
    196. Button2.text("供电关");
    197. Button2.print("off");
    198. }
    199. if ((c1 == 1) && (c0 == 1))
    200. {
    201. c1 = 0;
    202. Button4.print("on");
    203. Button4.color("#66CC66");
    204. Button4.text("正常连接");
    205. }
    206. else if ((c1 == 1) && (c0 == 0))
    207. {
    208. c1 = 0;
    209. Button4.color("#FF6600");
    210. Button4.text("异常连接");
    211. Button4.print("off");
    212. }
    213. if ((d1 == 1) && (d0 == 1))
    214. {
    215. d1 = 0;
    216. Button5.color("#00b0FF");
    217. Button5.text("OTA");
    218. Button5.print("on");
    219. }
    220. else if ((d1 == 1) && (d0 == 0))
    221. {
    222. d1 = 0;
    223. Button5.color("#607E8B");
    224. Button5.text("NO OTA");
    225. Button5.print("off");
    226. }
    227. }
    228. void dataRead(const String &data)
    229. {
    230. BLINKER_LOG("Blinker readString: ", data);
    231. Blinker.vibrate();
    232. uint32_t BlinkerTime = millis();
    233. Blinker.print("millis", BlinkerTime);
    234. }
    235. void setup()
    236. {
    237. Serial.begin(115200);
    238. BLINKER_DEBUG.stream(Serial);
    239. pinMode(LED_BUILTIN, OUTPUT);
    240. digitalWrite(LED_BUILTIN, LOW);
    241. delay(500);
    242. digitalWrite(LED_BUILTIN, HIGH);
    243. delay(500);
    244. digitalWrite(LED_BUILTIN, LOW);
    245. delay(500);
    246. digitalWrite(LED_BUILTIN, HIGH);
    247. delay(500);
    248. digitalWrite(LED_BUILTIN, LOW);
    249. delay(500);
    250. digitalWrite(LED_BUILTIN, HIGH);
    251. Blinker.begin(auth, ssid, pswd);
    252. Blinker.attachData(dataRead);
    253. Blinker.attachHeartbeat(heartbeat);
    254. Button1.attach(button1_callback);
    255. Button2.attach(button2_callback);
    256. Button3.attach(button3_callback);
    257. Button4.attach(button4_callback);
    258. Button5.attach(button5_callback);
    259. pinsetup();
    260. ping_ip_set();
    261. // ota_set();
    262. }
    263. void loop()
    264. {
    265. Blinker.run();
    266. get_time_now(); // 更新系统时间
    267. get_time_now_to_app(); // 运行时间
    268. checek_ip();
    269. }
    270. /************************
    271. void ota_set()
    272. {
    273. // ArduinoOTA.setHostname("ESP8266");
    274. // ArduinoOTA.setPassword("1234");
    275. ArduinoOTA.begin();
    276. ArduinoOTA.onStart([]() {
    277. // display.clear();
    278. // display.setFont(ArialMT_Plain_10);
    279. // display.setTextAlignment(TEXT_ALIGN_CENTER_BOTH);
    280. // display.drawString(display.getWidth() / 2, display.getHeight() / 2 - 10, "OTA Update");
    281. // display.display();
    282. Serial.println("OTA start");
    283. });
    284. ArduinoOTA.onProgress([](unsigned int progress, unsigned int total) {
    285. // display.drawProgressBar(4, 32, 120, 8, progress / (total / 100) );
    286. // display.display();
    287. Serial.println(progress / (total / 100));
    288. });
    289. ArduinoOTA.onEnd([]() {
    290. // display.clear();
    291. // display.setFont(ArialMT_Plain_10);
    292. // display.setTextAlignment(TEXT_ALIGN_CENTER_BOTH);
    293. // display.drawString(display.getWidth() / 2, display.getHeight() / 2, "Restart");
    294. // display.display();
    295. Serial.println("OTA Restart");
    296. });
    297. Serial.println("OTA ready");
    298. }
    299. void otaloop() {
    300. ArduinoOTA.handle();
    301. //Serial.println("OTA start");
    302. }
    303. *******************************/
    304. /*********************************************************************************************************
    305. 获取时间
    306. *********************************************************************************************************/
    307. void get_time_now()
    308. {
    309. blinker_year = Blinker.year(); // 年
    310. blinker_month = Blinker.month(); // 月
    311. blinker_mday = Blinker.mday(); // 日
    312. blinker_wday = Blinker.wday(); // 星期
    313. blinker_hour = Blinker.hour(); // 时
    314. blinker_min = Blinker.minute(); // 分
    315. blinker_sec = Blinker.second(); // 秒
    316. }
    317. /*********************************************************************************************************
    318. 获取运行时间并用app显示
    319. *********************************************************************************************************/
    320. void get_time_now_to_app()
    321. {
    322. String tianbl, shibl, fenbl, miaobl;
    323. uint8_t tian1 = 0, shi1 = 0, fen1 = 0, miao1 = 0;
    324. time_t run_time = Blinker.runTime(); // 获取运行时间,单位为秒
    325. tian1 = run_time / 86400; // 转化为天
    326. shi1 = (run_time - tian1 * 86400) / 3600; // 转化为时
    327. fen1 = (run_time - tian1 * 86400 - shi1 * 3600) / 60; // 转化为分
    328. miao1 = run_time - tian1 * 86400 - shi1 * 3600 - fen1 * 60; // 转化为秒
    329. if (tian1 < 10)
    330. {
    331. tianbl = String("") + "0" + tian1;
    332. }
    333. else
    334. {
    335. tianbl = String("") + tian1;
    336. }
    337. if (shi1 < 10)
    338. {
    339. shibl = String("") + "0" + shi1;
    340. }
    341. else
    342. {
    343. shibl = String("") + shi1;
    344. }
    345. if (fen1 < 10)
    346. {
    347. fenbl = String("") + "0" + fen1;
    348. }
    349. else
    350. {
    351. fenbl = String("") + fen1;
    352. }
    353. if (miao1 < 10)
    354. {
    355. miaobl = String("") + "0" + miao1;
    356. }
    357. else
    358. {
    359. miaobl = String("") + miao1;
    360. }
    361. if (tian1 == 0 & shi1 == 0 & fen1 == 0)
    362. {
    363. fh0 = String("") + miaobl + "秒";
    364. }
    365. else if (tian1 == 0 & shi1 == 0)
    366. {
    367. fh0 = String("") + fenbl + "分" + miaobl + "秒";
    368. }
    369. else if (tian1 == 0)
    370. {
    371. fh0 = String("") + shibl + "时" + fenbl + "分" + miaobl + "秒";
    372. }
    373. else
    374. {
    375. fh0 = String("") + tianbl + "天" + shibl + "时" + fenbl + "分" + miaobl + "秒";
    376. }
    377. }
    378. /*********************************************************************************************************
    379. 获取最近一次出错时间
    380. *********************************************************************************************************/
    381. void error_time_record()
    382. {
    383. String month12, day12, hourbl2, minbl2;
    384. if (blinker_month < 10)
    385. {
    386. month12 = String("") + "0" + blinker_month;
    387. }
    388. else
    389. {
    390. month12 = String("") + blinker_month;
    391. }
    392. if (blinker_mday < 10)
    393. {
    394. day12 = String("") + "0" + blinker_mday;
    395. }
    396. else
    397. {
    398. day12 = String("") + blinker_mday;
    399. }
    400. if (blinker_hour < 10)
    401. {
    402. hourbl2 = String("") + "0" + blinker_hour;
    403. }
    404. else
    405. {
    406. hourbl2 = String("") + blinker_hour;
    407. }
    408. if (blinker_min < 10)
    409. {
    410. minbl2 = String("") + "0" + blinker_min;
    411. }
    412. else
    413. {
    414. minbl2 = String("") + blinker_min;
    415. }
    416. fh = String("") + month12 + "/" + day12 + "/" + hourbl2 + ":" + minbl2;
    417. }
    418. /*********************************************************************************************************
    419. ping检测
    420. *********************************************************************************************************/
    421. void checek_ip()
    422. {
    423. if (ip_timer_flag == true) // 检测ip timer
    424. {
    425. Serial.println("");
    426. Serial.println("###########################################################################");
    427. Serial.printf(" 检测时间 %d : %d : %d \n", blinker_hour, blinker_min, blinker_sec);
    428. Serial.println("###########################################################################");
    429. ip_timer_flag = false;
    430. if (ip_app_check_flag == true) // app控制检测标志
    431. {
    432. ping_ip(); // 检测ip主程序
    433. }
    434. Blinker.run();
    435. }
    436. timer.tick();
    437. }
    438. void pinsetup()
    439. {
    440. pinMode(VCCPIN, OUTPUT);
    441. digitalWrite(VCCPIN, LOW); // 蜂鸣器关闭
    442. timer.every(check_timer_time * 1000, ip_check_timer); // 开启打印寄存器
    443. }
    444. bool ip_check_timer(void *)
    445. {
    446. ip_timer_flag = true;
    447. return true; // repeat? true
    448. }
    449. void delay_blinker(int n)
    450. {
    451. for (uint16_t i = 0; i < n; i++)
    452. {
    453. Blinker.delay(500);
    454. Blinker.run();
    455. timer.tick();
    456. }
    457. }
    458. void ping_ip()
    459. {
    460. // int16_t check_timer_time = 10, check_error_time= 5, clen_error_timer = 300;
    461. Serial.printf("检测间隔时间: %d s 判断异常次数 : %d 清除异常时间 : %d s \n", check_timer_time, clen_error_timer / check_timer_time, clen_error_timer);
    462. Serial.printf("访问 192.168.31.43\n");
    463. Serial.printf("当前是否访问正常 : %d 访问异常次数 : %d 最终访问异常次数 : %d\n", ip_lost_flag, error_time, error_time_final);
    464. if (pinger.Ping("192.168.31.43") == false)
    465. {
    466. }
    467. delay_blinker(8);
    468. if (loss_true == true) // 侦测到异常
    469. {
    470. error_time++;
    471. Serial.println("###########################################################################");
    472. Serial.printf("访问出错: %d 次\n", error_time);
    473. if ((ip_lost_flag == false) && (error_time == (clen_error_timer / check_timer_time))) // 超过异常次数时断电恢复
    474. {
    475. c0 = 0; // app 显示访问异常
    476. c1 = 1; // app s设定显示访问异常
    477. error_time = 0;
    478. digitalWrite(VCCPIN, HIGH);
    479. Blinker.delay(1500);
    480. digitalWrite(VCCPIN, LOW);
    481. ip_lost_flag = true; // 访问异常标志位打开
    482. error_time_final++; // 异常次数+1
    483. error_time_record(); // 记录异常时间
    484. Serial.println("###########################################################################");
    485. Serial.printf(" 第 %d 次访问出错\n", error_time_final);
    486. Serial.println("###########################################################################");
    487. }
    488. /*************************************************************************************************
    489. if (read_time == 0 || (millis() - read_time) >= clen_error_timer * 1000) // 超过一定时间,容错清0
    490. {
    491. read_time = millis();
    492. error_time = 0;
    493. Serial.println("############################超时容错清0####################################");
    494. }
    495. *************************************************************************************************/
    496. }
    497. else
    498. {
    499. error_time = 0; // 错误次数清0,重新计数
    500. if (ip_lost_flag == true) // 如果是异常,第一次恢复后计数清0以及标志位恢复
    501. {
    502. Serial.println("###########################################################################");
    503. Serial.println(" 恢复正常访问");
    504. Serial.println("###########################################################################");
    505. c0 = 1; // app 显示访问正常
    506. c1 = 1; // app s设定显示访问正常
    507. error_time = 0;
    508. ip_lost_flag = false;
    509. }
    510. }
    511. }
    512. void ping_ip_set()
    513. {
    514. // Begin serial connection at 9600 baud
    515. Serial.begin(115200);
    516. // Connect to WiFi access point
    517. bool stationConnected = WiFi.begin("************", "********************");
    518. // Check if connection errors
    519. if (!stationConnected)
    520. {
    521. Serial.println("Error, unable to connect specified WiFi network.");
    522. }
    523. // Wait connection completed
    524. Serial.print("Connecting to AP...");
    525. while (WiFi.status() != WL_CONNECTED)
    526. {
    527. delay(500);
    528. Serial.print(".");
    529. }
    530. Serial.print("Ok\n");
    531. pinger.OnReceive([](const PingerResponse &response)
    532. {
    533. if (response.ReceivedResponse)
    534. {
    535. Serial.printf("来自 %s: bytes=%d time=%lums TTL=%d\n",response.DestIPAddress.toString().c_str(),response.EchoMessageSize - sizeof(struct icmp_echo_hdr),response.ResponseTime,response.TimeToLive);
    536. }
    537. else
    538. {
    539. Serial.printf("请求超时\n");
    540. }
    541. // Return true to continue the ping sequence.
    542. // If current event returns false, the ping sequence is interrupted.
    543. return true; });
    544. pinger.OnEnd([](const PingerResponse &response)
    545. {
    546. // Evaluate lost packet percentage
    547. float loss = 100;
    548. if (response.TotalReceivedResponses > 0)
    549. {
    550. loss = (response.TotalSentRequests - response.TotalReceivedResponses) * 100 / response.TotalSentRequests;
    551. }
    552. if ( loss != 100) {
    553. loss_true = false;
    554. } else {
    555. loss_true = true;
    556. }
    557. // Print packet trip data
    558. Serial.printf("统计信息 %s: 数据包: 发送 = %lu, 接收 = %lu, 丢失 = %lu (%.2f%% 丢失)\n",response.DestIPAddress.toString().c_str(), response.TotalSentRequests, response.TotalReceivedResponses, response.TotalSentRequests - response.TotalReceivedResponses, loss);
    559. //Serial.printf(" Packets: Sent = %lu, Received = %lu, Lost = %lu (%.2f%% loss),\n", response.TotalSentRequests, response.TotalReceivedResponses, response.TotalSentRequests - response.TotalReceivedResponses, loss);
    560. // Print time information
    561. if (response.TotalReceivedResponses > 0)
    562. {
    563. Serial.printf("网络延迟: 最短 = %lums, 最长 = %lums, 平均 = %.2fms\n",response.MinResponseTime,response.MaxResponseTime,response.AvgResponseTime);
    564. //Serial.printf(" Minimum = %lums, Maximum = %lums, Average = %.2fms\n",response.MinResponseTime,response.MaxResponseTime,response.AvgResponseTime);
    565. }
    566. // Print host data
    567. //Serial.printf("Destination host data:\n");
    568. //Serial.printf(" IP address: %s\n", response.DestIPAddress.toString().c_str());
    569. if (response.DestMacAddress != nullptr)
    570. {
    571. Serial.printf( " MAC address: " MACSTR "\n",MAC2STR(response.DestMacAddress->addr));
    572. }
    573. if (response.DestHostname != "")
    574. {
    575. //Serial.printf(" DNS name: %s\n",response.DestHostname.c_str());
    576. }
    577. return true; });
    578. // Ping default gateway
    579. Serial.printf("\n\nPinging default gateway with IP %s\n", WiFi.gatewayIP().toString().c_str());
    580. if (pinger.Ping(WiFi.gatewayIP()) == false)
    581. {
    582. Serial.println("Error during last ping command.");
    583. }
    584. }

    串口log打印如下:

    可以协助判断运行状态,经过这两三天的调试,确认检测时间30s,也就是没个30s ping树莓派ip一次,会连续判断20次,如果20次都是访问不了的,就控制继电器断电,然后再上电,如果有20次判断有访问正常,则重新计数访问异常次数。

     

    •  硬件实现

    由于esp01 relay需要接线,需要改装数据线,所以想着画块板子通过两条usb线连接,方便后续改变,板子已下单,待收到后验证,

    板子回来了,实际效果图如下:

     

    后续优化

       优化的部分暂时未想好,目前可以基本满足需求。

  • 相关阅读:
    各位帅哥美女,微博爬虫用python到底怎么写啊
    栈、队列、矩阵的总结
    IO流:字符输入流Reader的超详细用法及底层原理
    漏洞复现--鸿运主动安全监控云平台任意文件下载
    如何有效防止公司内部的信息泄露?
    计算机毕业设计之java+SSM动物园门票预订网站系统
    #案例:演示键盘操作!
    1001 害死人不偿命的(3n+1)猜想
    MQ 消息丢失、重复、积压问题,如何解决?
    ubuntu磁盘扩容
  • 原文地址:https://blog.csdn.net/yyandad/article/details/127656333