日韩欧美一区二区三区在线观看-日韩欧美一区二区三区在线视频-日韩欧美一区二区三区中文精品-日韩欧美一区二区在线观看-高清完整视频在线播放-高清无遮挡在线观看

/ EN
13922884048

技術(shù)交流

Technology Exchange
/
/

基于樹莓派RP2040游戲機(jī)的簡易網(wǎng)絡(luò)氣象站

發(fā)布時間:2022-09-06作者來源:薩科微瀏覽:3224


一、設(shè)計目標(biāo)

1.RP2040 Game Kit板通過提供的ESP32-S2的WiFi模塊連接網(wǎng)絡(luò)。

2.在RP2040 Game Kit上顯示某一個城市的氣象信息 - 時間、天氣實況、生活指數(shù)、天氣預(yù)報...

3.通過RP2040 Game Kit上的按鍵和四向搖桿配合能夠切換顯示不同城市的信息 ,做到能切換顯示、刷新數(shù)據(jù)、修改城市名。

4. 通過顯示屏與搖桿按鍵交互來模擬一個九鍵鍵盤,實現(xiàn)城市名的自主輸入,輸入錯誤也會有錯誤提示。

5. 搭配上圖片來豐富顯示內(nèi)容,包括天氣氣象符號、各生活指數(shù)示意圖等。

二、準(zhǔn)備工作

1?硬件連接

Rp2040游戲機(jī)與esp32-s2模塊的連線如下圖所示。

    pico                        esp32s2
    tx = Pin(16)   -->    RXD_PIN (GPIO_NUM_21)
    rx = Pin(17)   -->    RXD_PIN (GPIO_NUM_21)
    3V3               -->    3V3
    GND             -->    GND

2. 開發(fā)環(huán)境

(1)thonny。安裝過程具體可參考 https://class.eetree.cn/live_pc/l_60fe7f4fe4b0a27d0e360f74

(2)  Vscode的插件Espressif IDF v1.3.0。

3. 參考例程

(1)ESP32 IDF v4.3.1:樂鑫ESP開發(fā)環(huán)境,本項目參考了其中的http request,uart,wifi station 例程。具體可參考官方文檔ESP-IDF 編程指南。

(2)硬禾學(xué)堂2022寒假在家練:基于樹莓派RP2040的嵌入式系統(tǒng)學(xué)習(xí)平臺,相關(guān)內(nèi)容可參考https://www.eetree.cn/project/detail/698

4.源代碼目錄結(jié)構(gòu)

(1)Rp2040

-/
   -weather_main.py 主函數(shù)
   -draw.py 畫圖部分
   -http_deal.py http數(shù)據(jù)處理部分
   -location.py 鍵盤鍵位內(nèi)容
   -button.py 按鍵
   -board.py 引腳定義
   -vga2_8x8.py字體小
   -vga1_16x32.py字體大
   -vga1_8x16.py 字體中
   -weather_picture_small/ 天氣現(xiàn)象圖片(小)
   -weather_picture_big/ 天氣現(xiàn)象圖片(大)
   -index of living/ 生活指數(shù)插圖

(2)ESP32-S2

    - http_request/
             - CMakeLists.txt
             - sdkconfig
             - main/       - CMakeLists.txt
                                - http_main.c   esp32主函數(shù),http請求,json解析
                                - http.h        
                                - uart.c        串口通信部分
                                - uart.h
                                - wifi.c        wifi連接部分
                                - wifi.h

5.使用說明

(1)先將wifi_name和wifi_passwd分別修改成要連接的熱點的名字和密碼。
(2)分別編譯下載程序到pico和esp32s2(esp32s2可用vscode),具體可參照上面的源代碼目錄結(jié)構(gòu)。
(3)使用杜邦線進(jìn)行硬件連接,具體連線請參照上面的硬件連接。
(4)上電開機(jī)。

6.注意事項

(1)部分wifi可能不能被esp32識別。

(2)使用過程中請保持網(wǎng)絡(luò)順暢,若失去網(wǎng)絡(luò)連接或產(chǎn)生一些其它錯誤,可以試著先按下esp32的reset鍵重啟,再運行RP2040的主程序。

(3)由于使用心知天氣平臺的免費版,暫時只支持國內(nèi)部分城市。

三、軟件流程圖

image.png

 

 

四、實現(xiàn)過程

1?網(wǎng)絡(luò)連接

(1)WiFi連接

wifi名和密碼需提前設(shè)定,具體在RP2040的weather_main.py中修改,如下。

# 在此處修改你要連接的wifi名和密碼wifi_name = "123"wifi_passwd = "12345678"

 

發(fā)送wifi信息給esp32前需進(jìn)行簡單編碼以供esp32識別,具體請參考下面的多機(jī)通信部分。

在esp32接收到信息后立即調(diào)用wifi_init_sta()函數(shù)進(jìn)行wifi連接,這里是在esp32idf的例程 ~\Espressif\frameworks\esp-idf-v4.4.1\examples\wifi\getting_started\station   的基礎(chǔ)上修改的,具體如下。

/**********wifi初始化函數(shù)**************/void wifi_init_sta( char *wifi_ssid , char *wifi_password){
    s_wifi_event_group = xEventGroupCreate();

    // ESP_ERROR_CHECK(esp_netif_init());

    // ESP_ERROR_CHECK(esp_event_loop_create_default());
    esp_netif_create_default_wifi_sta();

    wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();
    ESP_ERROR_CHECK(esp_wifi_init(&cfg));

    esp_event_handler_instance_t instance_any_id;
    esp_event_handler_instance_t instance_got_ip;
    ESP_ERROR_CHECK(esp_event_handler_instance_register(WIFI_EVENT,
                                                        ESP_EVENT_ANY_ID,
                                                        &event_handler,
                                                        NULL,
                                                        &instance_any_id));
    ESP_ERROR_CHECK(esp_event_handler_instance_register(IP_EVENT,
                                                        IP_EVENT_STA_GOT_IP,
                                                        &event_handler,
                                                        NULL,
                                                        &instance_got_ip));

    wifi_config_t wifi_config = {
        .sta = {
            // .ssid = wifi_ssid ,
            // .password = wifi_password ,
            /* Setting a password implies station will connect to all security modes including WEP/WPA.
             * However these modes are deprecated and not advisable to be used. Incase your Access point
             * doesn't support WPA2, these mode can be enabled by commenting below line */
	     .threshold.authmode = WIFI_AUTH_WPA2_PSK,
        },
    };


    memcpy(wifi_config.sta.ssid, wifi_ssid, sizeof(wifi_config.sta.ssid));
    memcpy(wifi_config.sta.password, wifi_password, sizeof(wifi_config.sta.password));


    ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_STA) );
    ESP_ERROR_CHECK(esp_wifi_set_config(WIFI_IF_STA, &wifi_config) );
    ESP_ERROR_CHECK(esp_wifi_start() );

    ESP_LOGI(TAG, "wifi_init_sta finished.");

    /* Waiting until either the connection is established (WIFI_CONNECTED_BIT) or connection failed for the maximum
     * number of re-tries (WIFI_FAIL_BIT). The bits are set by event_handler() (see above) */
    EventBits_t bits = xEventGroupWaitBits(s_wifi_event_group,
            WIFI_CONNECTED_BIT | WIFI_FAIL_BIT,
            pdFALSE,
            pdFALSE,
            portMAX_DELAY);

    /* xEventGroupWaitBits() returns the bits before the call returned, hence we can test which event actually
     * happened. */
    if (bits & WIFI_CONNECTED_BIT) {       
        ESP_LOGI(TAG, "connected to ap SSID:%s password:%s",
                 wifi_ssid, wifi_password);
                  http_get_task();  //連接成功,發(fā)送http請求
        // sendData(TAG,"connectsucess");
    } else if (bits & WIFI_FAIL_BIT) {
        ESP_LOGI(TAG, "Failed to connect to SSID:%s, password:%s",
                 wifi_ssid, wifi_password);
        sendData(TAG,"Connectfail");  //連接失敗,發(fā)送狀態(tài)告知pico
        
    } else {
        ESP_LOGE(TAG, "UNEXPECTED EVENT");
        sendData(TAG,"Connectfail"); //連接失敗,發(fā)送狀態(tài)告知pico
    }

    /* The event will not be processed after unregister */
    ESP_ERROR_CHECK(esp_event_handler_instance_unregister(IP_EVENT, IP_EVENT_STA_GOT_IP, instance_got_ip));
    ESP_ERROR_CHECK(esp_event_handler_instance_unregister(WIFI_EVENT, ESP_EVENT_ANY_ID, instance_any_id));
    vEventGroupDelete(s_wifi_event_group);}

wifi連接成功后就立即發(fā)送http請求,失敗則返回狀態(tài)給RP2040。

(2)http請求

這里參考了esp32idf的例程~\Espressif\frameworks\esp-idf-v4.4.1\examples\protocols\http_request,將其中的循環(huán)任務(wù)改成了單次調(diào)動并根據(jù)不同的請求內(nèi)容增加了參數(shù)判斷,就能根據(jù)需要進(jìn)行http請求,并在發(fā)生錯誤時發(fā)送狀態(tài)給RP2040,具體內(nèi)容在http_main.c中,如下。

//HTTP請求函數(shù)void http_get(char arg){
    const struct addrinfo hints = {
        .ai_family = AF_INET,
        .ai_socktype = SOCK_STREAM,
    };
    struct addrinfo *res;
    struct in_addr *addr;
    int s, r;
    
    char recv_buf[64];
    char mid_buf[1400];   //接受http報文正文部分

    memset(mid_buf,0,sizeof(mid_buf));
    char WEB_PATH[200] = "GET " ;  
    
    // 組合字段構(gòu)成http請求的發(fā)送內(nèi)容,根據(jù)不同的請求進(jìn)行不同的組合
    switch (arg){
       //實時天氣,例:http://api.seniverse.com/v3/weather/now.json?key=your_api_key&location=beijing&language=en&unit=c
       case WEATHER_CURRENT: 
        strcat(WEB_PATH,WEB_PATH_CURRENT_1);
        strcat(WEB_PATH,reqLocation);
        strcat(WEB_PATH,WEB_PATH_CURRENT_2);
        strcat(WEB_PATH,REQUEST_ED);
        break;
        //生活指數(shù),例:http://api.seniverse.com/v3/life/suggestion.json?key=SzOM2PDJp7crLA0Ug&location=haikou&language=en
        case WEATHER_LIFE:         
        strcat(WEB_PATH,WEB_PATH_LIFE_1);
        strcat(WEB_PATH,reqLocation);
        strcat(WEB_PATH,WEB_PATH_LIFE_2);
        strcat(WEB_PATH,REQUEST_ED);
        break;
        //天氣預(yù)報,例:http://api.seniverse.com/v3/weather/daily.json?key=your_api_key&location=beijing&language=zh-Hans&unit=c&start=0&days=5
        case WEATHER_FORECAST:  
        strcat(WEB_PATH,WEB_PATH_FORECAST_1);
        strcat(WEB_PATH,reqLocation);
        strcat(WEB_PATH,WEB_PATH_FORECAST_2);
        strcat(WEB_PATH,REQUEST_ED);
        break;

        default:ESP_LOGI(TAG, "wrong");

    }
 
        
    int err = getaddrinfo(WEB_SERVER, WEB_PORT, &hints, &res);

    if(err != 0 || res == NULL) {
        ESP_LOGE(TAG, "DNS lookup failed err=%d res=%p", err, res);
        vTaskDelay(1000 / portTICK_PERIOD_MS);
        sendData(TAG,"httprequestfail");     //http初始化失敗,告知pico
        
    }else {
        /* Code to print the resolved IP.

           Note: inet_ntoa is non-reentrant, look at ipaddr_ntoa_r for "real" code */
        addr = &((struct sockaddr_in *)res->ai_addr)->sin_addr;
        ESP_LOGI(TAG, "DNS lookup succeeded. IP=%s", inet_ntoa(*addr));

        s = socket(res->ai_family, res->ai_socktype, 0);
        if(s < 0) {
            ESP_LOGE(TAG, "... Failed to allocate socket.");
            freeaddrinfo(res);
            vTaskDelay(1000 / portTICK_PERIOD_MS);
            sendData(TAG,"httprequestfail"); //http初始化失敗,告知pico
        }else{
            ESP_LOGI(TAG, "... allocated socket");

            if(connect(s, res->ai_addr, res->ai_addrlen) != 0) {
                ESP_LOGE(TAG, "... socket connect failed errno=%d", errno);
                close(s);
                freeaddrinfo(res);
                vTaskDelay(4000 / portTICK_PERIOD_MS);
                sendData(TAG,"httprequestfail"); //http初始化失敗,告知pico
            }else{
                ESP_LOGI(TAG, "... connected");
                freeaddrinfo(res);

                if (write(s, WEB_PATH, strlen(WEB_PATH)) < 0) {
                    ESP_LOGE(TAG, "... socket send failed");
                    close(s);
                    vTaskDelay(4000 / portTICK_PERIOD_MS);
                    sendData(TAG,"httprequestfail"); //http初始化失敗,告知pico
                }else{
                    ESP_LOGI(TAG, "... socket send success");

                    struct timeval receiving_timeout;
                    receiving_timeout.tv_sec = 5;
                    receiving_timeout.tv_usec = 0;
                    if (setsockopt(s, SOL_SOCKET, SO_RCVTIMEO, &receiving_timeout,
                        sizeof(receiving_timeout)) < 0) {
                        ESP_LOGE(TAG, "... failed to set socket receiving timeout");
                        close(s);
                        vTaskDelay(4000 / portTICK_PERIOD_MS);
                        sendData(TAG,"httprequestfail"); //http初始化失敗,告知pico
                    }else{
                        ESP_LOGI(TAG, "... set socket receiving timeout success");

                       
                        /* Read HTTP response */
                        do {
                            bzero(recv_buf, sizeof(recv_buf));
                            r = read(s, recv_buf, sizeof(recv_buf)-1);
                            strcat(mid_buf,recv_buf);
                            for(int i = 0; i < r; i++) {
                                putchar(recv_buf[i]);
                            }
                        } while(r > 0);
                        // ESP_LOGI(TAG,"return=%s",mid_buf);
                        //json格式轉(zhuǎn)化 
                        cjson_to_struct_info(mid_buf,arg);


                        ESP_LOGI(TAG, "... done reading from socket. Last read return=%d errno=%d.", r, errno);
                        close(s);
                        
                    }
                }
            }

        }
        
    }    }

由于要請求的內(nèi)容有三項(天氣實況、生活指數(shù)、天氣預(yù)報),分別對應(yīng)三個不同的請求行:

void http_get_task(void){
    memset(send_data_quene,0,sizeof(send_data_quene));
    http_get(WEATHER_CURRENT); //天氣實況
    vTaskDelay(1000 / portTICK_PERIOD_MS);  //適當(dāng)延時
    http_get(WEATHER_FORECAST); //天氣預(yù)報
    vTaskDelay(1000 / portTICK_PERIOD_MS);
    http_get(WEATHER_LIFE);     //生活指數(shù)
    vTaskDelay(1000 / portTICK_PERIOD_MS);
    ESP_LOGI(TAG,"send_data:%s",send_data_quene);
    sendData(TAG,send_data_quene);//整合發(fā)送}
實時天氣,例:http://api.seniverse.com/v3/weather/now.json?key=your_api_key&location=beijing&language=en&unit=c
生活指數(shù),例:http://api.seniverse.com/v3/life/suggestion.json?key=SzOM2PDJp7crLA0Ug&location=haikou&language=en
天氣預(yù)報,例:http://api.seniverse.com/v3/weather/daily.json?key=your_api_key&location=beijing&language=zh-Hans&unit=c&start=0&days=5
所以在函數(shù)在加入判斷到底發(fā)送哪一個,相應(yīng)的請求行要進(jìn)行不同組合,在http_get()函數(shù)中做判斷,具體內(nèi)容在http.h中,如下。
#define WEB_SERVER "api.seniverse.com"#define WEB_PORT "80"#define reqUserKey "SzOM2PDJp7crLA0Ug"// #define reqLocation "Shenzhen"#define reqUnit "c"//天氣實況#define WEATHER_CURRENT 'C' #define WEB_PATH_CURRENT_1 "/v3/weather/now.json?key=" reqUserKey "&location="  #define WEB_PATH_CURRENT_2 "&language=en&unit=" reqUnit//生活指數(shù)#define WEATHER_LIFE 'L'#define WEB_PATH_LIFE_1 "/v3/life/suggestion.json?key=" reqUserKey "&location="  #define WEB_PATH_LIFE_2 "&language=en"//天氣預(yù)報#define WEATHER_FORECAST 'F'#define WEB_PATH_FORECAST_1 "/v3/weather/daily.json?key=" reqUserKey "&location="  #define WEB_PATH_FORECAST_2 "&language=en&unit=" reqUnit "&start=0&days=5"//http請求尾static const char *REQUEST_ED = " HTTP/1.0\r\n"
        "Host: "WEB_SERVER":"WEB_PORT"\r\n"
        "User-Agent: esp-idf/1.0 esp32\r\n"
        "\r\n";//城市名char *reqLocation ;

由于請求的城市名是會變化的,所以利用C語言strcat函數(shù)進(jìn)行組合,組合好后就可以發(fā)送完整的請求行了。

而接收到的數(shù)據(jù)都是json格式,這里調(diào)用了cjson庫來進(jìn)行解碼,針對不同的http報文有不同的處理方式,具體在http_main.c中,如下。

/***********json格式解析************/void cjson_to_struct_info(char *text,char arg){

    cJSON *root,*psub;
    cJSON *arrayItem;
    //截取有效json
    char *index=strchr(text,'{');
    strcpy(text,index);
 
    root = cJSON_Parse(text);
    
    if(root!=NULL)
    {
        /*******************天氣實況**********/
        if(arg == WEATHER_CURRENT){
            psub = cJSON_GetObjectItem(root, "results");
            arrayItem = cJSON_GetArrayItem(psub,0);
    
            cJSON *locat = cJSON_GetObjectItem(arrayItem, "location");
            cJSON *now = cJSON_GetObjectItem(arrayItem, "now");
            if((locat!=NULL)&&(now!=NULL))
            {
                psub=cJSON_GetObjectItem(locat,"name");
                sprintf(weathe.cit,"%s",psub->valuestring);
                ESP_LOGI(TAG,"city:%s",weathe.cit);
                strcat(send_data_quene,weathe.cit);  //拼接發(fā)送字符串
                strcat(send_data_quene,"+");         //分割符,讓pico識別
    
                psub=cJSON_GetObjectItem(now,"text");
                sprintf(weathe.weather_text,"%s",psub->valuestring);
                ESP_LOGI(TAG,"weather:%s",weathe.weather_text);
                strcat(send_data_quene,weathe.weather_text);
                strcat(send_data_quene,"+");
                
                psub=cJSON_GetObjectItem(now,"code");
                sprintf(weathe.weather_code,"%s",psub->valuestring);
                ESP_LOGI(TAG,"%s",weathe.weather_code);
                strcat(send_data_quene,weathe.weather_code);
                strcat(send_data_quene,"+");
    
                psub=cJSON_GetObjectItem(now,"temperature");
                sprintf(weathe.temperatur,"%s",psub->valuestring);
                ESP_LOGI(TAG,"temperatur:%s",weathe.temperatur);
                strcat(send_data_quene,weathe.temperatur);
                strcat(send_data_quene,"+");                
         
            }else{
                sendData(TAG,"httprequestfail"); //json格式有誤。http請求失敗
            }
        
        }

        /*****************天氣預(yù)報*************************/
        if(arg == WEATHER_FORECAST){
            psub = cJSON_GetObjectItem(root, "results");
            arrayItem = cJSON_GetArrayItem(psub,0);
            
            cJSON *locat = cJSON_GetObjectItem(arrayItem, "location");
            cJSON *daily = cJSON_GetObjectItem(arrayItem, "daily");
            if((locat!=NULL)&&(daily!=NULL))
            {
     
                for(int i = 0;i<3;i++){
                    arrayItem = cJSON_GetArrayItem(daily,i);
                    psub = cJSON_GetObjectItem(arrayItem, "date");
                    sprintf(weathe.daily_weathe[i].date,"%s",psub->valuestring);
                    ESP_LOGI(TAG,"date:%s",weathe.daily_weathe[i].date);
                    strcat(send_data_quene,weathe.daily_weathe[i].date);
                    strcat(send_data_quene,"+");

                    psub = cJSON_GetObjectItem(arrayItem, "text_day");
                    sprintf(weathe.daily_weathe[i].text_day,"%s",psub->valuestring);
                    ESP_LOGI(TAG,"text_day:%s",weathe.daily_weathe[i].text_day);
                    strcat(send_data_quene,weathe.daily_weathe[i].text_day);
                    strcat(send_data_quene,"+");

                    psub = cJSON_GetObjectItem(arrayItem, "code_day");
                    sprintf(weathe.daily_weathe[i].code_day,"%s",psub->valuestring);
                    ESP_LOGI(TAG,"code_day:%s",weathe.daily_weathe[i].code_day);
                    strcat(send_data_quene,weathe.daily_weathe[i].code_day);
                    strcat(send_data_quene,"+");

                    psub = cJSON_GetObjectItem(arrayItem, "text_night");
                    sprintf(weathe.daily_weathe[i].text_night,"%s",psub->valuestring);
                    ESP_LOGI(TAG,"text_night:%s",weathe.daily_weathe[i].text_night);
                    strcat(send_data_quene,weathe.daily_weathe[i].text_night);
                    strcat(send_data_quene,"+");

                    psub = cJSON_GetObjectItem(arrayItem, "code_night");
                    sprintf(weathe.daily_weathe[i].code_night,"%s",psub->valuestring);
                    ESP_LOGI(TAG,"code_night:%s",weathe.daily_weathe[i].code_night);
                    strcat(send_data_quene,weathe.daily_weathe[i].code_night);
                    strcat(send_data_quene,"+");

                    psub = cJSON_GetObjectItem(arrayItem, "high");
                    sprintf(weathe.daily_weathe[i].high,"%s",psub->valuestring);
                    ESP_LOGI(TAG,"high:%s",weathe.daily_weathe[i].high);
                    strcat(send_data_quene,weathe.daily_weathe[i].high);
                    strcat(send_data_quene,"+");

                    psub = cJSON_GetObjectItem(arrayItem, "low");
                    sprintf(weathe.daily_weathe[i].low,"%s",psub->valuestring);
                    ESP_LOGI(TAG,"low:%s",weathe.daily_weathe[i].low);
                    strcat(send_data_quene,weathe.daily_weathe[i].low);
                    strcat(send_data_quene,"+");

                    psub = cJSON_GetObjectItem(arrayItem, "precip");
                    sprintf(weathe.daily_weathe[i].precip,"%s",psub->valuestring);
                    ESP_LOGI(TAG,"precip:%s",weathe.daily_weathe[i].precip);
                    strcat(send_data_quene,weathe.daily_weathe[i].precip);
                    strcat(send_data_quene,"+");

                    psub = cJSON_GetObjectItem(arrayItem, "humidity");
                    sprintf(weathe.daily_weathe[i].humidity,"%s",psub->valuestring);
                    ESP_LOGI(TAG,"humidity:%s",weathe.daily_weathe[i].humidity);
                    strcat(send_data_quene,weathe.daily_weathe[i].humidity);
                    strcat(send_data_quene,"+");
                }
 
            }
            else{
                sendData(TAG,"httprequestfail");  //json格式有誤。http請求失敗
            }

        }
        
         /**************************生活指數(shù)****************************************/
        if(arg == WEATHER_LIFE){
            psub = cJSON_GetObjectItem(root, "results");
            arrayItem = cJSON_GetArrayItem(psub,0);
    
            cJSON *locat = cJSON_GetObjectItem(arrayItem, "location");
            cJSON *suggestion = cJSON_GetObjectItem(arrayItem, "suggestion");
            if((locat!=NULL)&&(suggestion!=NULL))
            {

                cJSON *car_washing=cJSON_GetObjectItem(suggestion,"car_washing");
                psub=cJSON_GetObjectItem(car_washing,"brief");
                sprintf(weathe.car_washing,"%s",psub->valuestring);
                ESP_LOGI(TAG,"car_washing:%s",weathe.car_washing);
                strcat(send_data_quene,weathe.car_washing);
                strcat(send_data_quene,"+");
                
    
                cJSON *dressing=cJSON_GetObjectItem(suggestion,"dressing");
                psub=cJSON_GetObjectItem(dressing,"brief");
                sprintf(weathe.dressing,"%s",psub->valuestring);
                ESP_LOGI(TAG,"dressing:%s",weathe.dressing);
                strcat(send_data_quene,weathe.dressing);
                strcat(send_data_quene,"+");

                cJSON *flu=cJSON_GetObjectItem(suggestion,"flu");
                psub=cJSON_GetObjectItem(flu,"brief");
                sprintf(weathe.flu,"%s",psub->valuestring);
                ESP_LOGI(TAG,"flu:%s",weathe.flu);
                strcat(send_data_quene,weathe.flu);
                strcat(send_data_quene,"+");

                cJSON *sport=cJSON_GetObjectItem(suggestion,"sport");
                psub=cJSON_GetObjectItem(sport,"brief");
                sprintf(weathe.sport,"%s",psub->valuestring);
                ESP_LOGI(TAG,"sport:%s",weathe.sport);
                strcat(send_data_quene,weathe.sport);
                strcat(send_data_quene,"+");

                cJSON *travel=cJSON_GetObjectItem(suggestion,"travel");
                psub=cJSON_GetObjectItem(travel,"brief");
                if (psub->valuestring[0] == '\0'){
                    sprintf(weathe.travel,"%s","No Result");
                }else{
                    sprintf(weathe.travel,"%s",psub->valuestring);
                }
                ESP_LOGI(TAG,"travel:%s",weathe.travel);
                strcat(send_data_quene,weathe.travel);
                strcat(send_data_quene,"+");
                
                cJSON *uv=cJSON_GetObjectItem(suggestion,"uv");
                psub=cJSON_GetObjectItem(uv,"brief");
                sprintf(weathe.uv,"%s",psub->valuestring);
                ESP_LOGI(TAG,"uv:%s",weathe.uv);
                strcat(send_data_quene,weathe.uv);
                // strcat(send_data_quene,"+");
            
 
            }else{
                sendData(TAG,"httprequestfail"); //json格式有誤。http請求失敗
            }
        }
        
    }
    
    cJSON_Delete(root);}

cjson解析完后進(jìn)行組合,將消息發(fā)送給RP2040。

2. 多機(jī)串口通信

本項目涉及到兩個模塊之間的通信問題,在開機(jī)后雙方都各持有一定信息,但需要相互通信才能完成工作。

流程基本為:RP2040發(fā)給esp32需要的wifi名和密碼,esp32在http請求成功后發(fā)給RP2040需要的天氣信息。RP2040可根據(jù)需要發(fā)送城市名給esp32讓其去發(fā)送http請求,esp32在網(wǎng)絡(luò)產(chǎn)生異常后也能及時發(fā)送狀態(tài)給RP2040。

(1)RP2040發(fā)送wifi名和wifi密碼給esp32

image.png

在本項目中esp32主要接收來自三種數(shù)據(jù):城市名,wifi名,wifi密碼,并不復(fù)雜,設(shè)置簡單的識別規(guī)則即可。

wifi名:在消息頭部添加"+"

wif密碼:在消息頭部添加"-"

城市名:不處理

具體處理代碼詳見weather_main.py的initialise_wifi()函數(shù),如下。

    async def initialise_wifi(self):#          初始化界面
        self.drawing.draw_opening()
        await asyncio.sleep_ms(2000)
       #        "+"和"-"用于讓esp32識別是wifi名還是密碼
        self.send_quene = "+" + wifi_name
        self.uart.write(self.send_quene)
        await asyncio.sleep_ms(1000)
        self.send_quene = "-" + wifi_passwd
        self.uart.write(self.send_quene)
        
        self.picture_index = 0
        self.drawing.draw_sending()  #發(fā)送中
        self.err = True
#         檢測wifi是否連接成功        while self.err == True:
            await self.uart_task()

在esp32接收識別后把頭部去掉即可,具體請見esp32的uart.c的rx_task()函數(shù)的相關(guān)部分,如下

void rx_task(void *arg){
               ........................
            if (data[0] == '+'){
                //收到“+”開頭,判斷為wifi名
                Wifi_ssid = &data[1];    //截取
                strcpy (ssid,Wifi_ssid); //轉(zhuǎn)存
        
            }
            else if(data[0] == '-'){
                //收到“-”開頭,判斷為wifi密碼
                Wifi_password = &data[1];  //截取
                strcpy (passwd,Wifi_password);  //轉(zhuǎn)存
                ESP_LOGI(RX_TASK_TAG, "ssid %s password: '%s'", ssid, passwd);

                wifi_init_sta(ssid,passwd);  //wifi初始化
            }else{
                    
                ...............................

            }
            
      
    }

(2)esp32把處理好的數(shù)據(jù)整合發(fā)送給RP2040

image.png

由于更新一次數(shù)據(jù)需要發(fā)送3次http請求,所以方案有3種:

1.收到即發(fā):這樣的話要求RP2040需要嚴(yán)格控制讀取順序,容易出錯。

2.完成一個請求才發(fā)。

3.全部整合在一起再發(fā)。

顯然第二種方法除了整合數(shù)據(jù)之外,還需要進(jìn)行接收信息的判斷,在考慮盡可能少判斷和少發(fā)送次數(shù)的前提下,采用第3種方法。只需設(shè)置合適的分隔符,將所有數(shù)據(jù)一次發(fā)送即可,接收端接收后去除分割符,按照次序讀取即可。

這里我采用"+"作為分隔符,在esp32中利用strcat()函數(shù)拼接(詳見上文json解析部分),利用python中的字符串內(nèi)建函數(shù)split()可以很容易分解并讀取,具體請參考RP2040的http_deal.py。

def data_deal(self):
        if self.text.find(b'+') != -1:
            decode_receiveStr = self.text.decode()   #去編碼,轉(zhuǎn)化為文本
            self.receive_items = decode_receiveStr.split('+')
            print(self.receive_items)
            http_get_data.city_name_text = self.receive_items[0]
            http_get_data.weather_current_text = self.receive_items[1]
            http_get_data.weather_current_code = self.receive_items[2]
            http_get_data.current_temperature = self.receive_items[3]
            http_get_data.date0 = self.receive_items[4]
            http_get_data.date0_day_text = self.receive_items[5]
            http_get_data.date0_day_code = self.receive_items[6]
            http_get_data.date0_night_text = self.receive_items[7]
            http_get_data.date0_night_code = self.receive_items[8]
           ........

(3)RP2040發(fā)送城市名給ESP32

image.png

發(fā)送城市名不做處理直接發(fā)送,在RP2040的wearher_main.py中,先是發(fā)送標(biāo)志生效(self.send_flag = True),然后在uart_task()函數(shù)中發(fā)送,具體如下。

  async def uart_task(self):
         ......
        #         發(fā)送任務(wù)
        if self.send_flag == True:
            self.uart.write(self.send_quene)
            self.send_flag = False

esp32則直接接收,修改reqLocation變量,執(zhí)行http請求,具體見esp32的uart.c的rx_task()函數(shù)。

 void rx_task(void *arg){

            ...................................
     if (rxBytes > 0) {
            data[rxBytes] = 0;
            ESP_LOGI(RX_TASK_TAG, "Read %d bytes: '%s'", rxBytes, data);
            ESP_LOG_BUFFER_HEXDUMP(RX_TASK_TAG, data, rxBytes, ESP_LOG_INFO);
            if (data[0] == '+'){
                //收到“+”開頭,判斷為wifi名
                Wifi_ssid = &data[1];    //截取
                strcpy (ssid,Wifi_ssid); //轉(zhuǎn)存
        
            }
            else if(data[0] == '-'){
                //收到“-”開頭,判斷為wifi密碼
                Wifi_password = &data[1];  //截取
                strcpy (passwd,Wifi_password);  //轉(zhuǎn)存
                ESP_LOGI(RX_TASK_TAG, "ssid %s password: '%s'", ssid, passwd);

                wifi_init_sta(ssid,passwd);  //wifi初始化
            }else{
                //一般字符串,城市名
                reqLocation = data;
                ESP_LOGI(RX_TASK_TAG, "Re: '%s'", reqLocation);
                http_get_task();   //接受到立即發(fā)送請求
                

            }
            
        ......................
        

    }

(4)esp32錯誤消息發(fā)送給RP2040

esp32可能會出現(xiàn)兩種錯誤:wifi連接失敗和http請求失敗,可以直接讓RP2040讀取判斷,讀取后在屏幕上顯示相應(yīng)信息,具體詳見RP2040的weather_main.py的uart_task()函數(shù)中,如下。

    async def uart_task(self):
        self.receive_flag = self.uart.any()
         ...........
            #             wifi連接失敗
            if receiveStr == b'Connectfail':
                self.drawing.draw_wificonnectfail()
                await asyncio.sleep_ms(1500)
                self.err = True                
#             http請求失敗
            elif receiveStr == b'httprequestfail':
                self.drawing.draw_httprequestfail()
                self.err = True
                await asyncio.sleep_ms(1500)
            
             ..........................

3. 顯示

RP2040顯示主要使用st7789c庫,來自(https://github.com/russhughes/st7789_mpy)或(https://github.com/picospuch/RP2040_Game_Kit),以下討論的代碼均在RP2040源代碼的draw.py中。

該庫的優(yōu)勢在于顯示速度快而且能夠顯示jpg圖片,所以可以參考心知天氣平臺的天氣符號代碼與符號對應(yīng)關(guān)系(詳見https://docs.seniverse.com/api/start/code.html),就能夠根據(jù)顯示官方的天氣信息及符號。

根據(jù)官方文檔可知,每一個天氣代碼對應(yīng)一種天氣現(xiàn)象,所以可以利用這個代碼判斷該畫哪一張圖,由于python沒有switch語句而且循環(huán)判斷程序的執(zhí)行效率會很低,所以這里我采用了在類中定義不同的方法(方法名有一定的規(guī)則),然后通過getattr函數(shù)來進(jìn)行實現(xiàn)判斷,我在后面判斷周幾以及的鍵盤鍵位判斷都用到了這個思路。具體詳見RP2040的draw.py的weather類和weekday類,以及l(fā)ocation.py的location類,具體如下。

class weather:
    
    picture_big = "/weather_picture_big/Unknown.jpg"
    picture_small = "/weather_picture_small/Unknown.jpg"
    
    def weather0(self):
        weather.picture_big = "/weather_picture_big/Sunny.jpg"
        weather.picture_small = "/weather_picture_small/Sunny.jpg"
    
    def weather1(self):
        weather.picture_big = "/weather_picture_big/Clear.jpg"
        weather.picture_small = "/weather_picture_small/Clear.jpg"
        
        ................

    def Default(self):
        weather.picture_big = "/weather_picture_big/Unknown.jpg"
        weather.picture_small = "/weather_picture_small/Unknown.jpg"
    
    def getweather(self, weather):
        weather_name = "weather" + str(weather)
        fun = getattr(self, weather_name, self.Default)
        return fun()class draw:

       .........................

   def draw_real_time_weather_picture(self,city_name,weather_current_code,weather_current_text,current_temperature):
        self.display.init()
        self.code.getweather(weather_current_code)  #天氣代碼判斷

          ................

        self.display.jpg(self.code.picture_big ,0 , 0, st7789.FAST)

(1)天氣實況顯示

image.png

天氣實況要顯示的內(nèi)容不多,但要注意心知天氣平臺返回的天氣字段有些會很長(如Thundershower with Hail),就有可能影響顯示,所以這里要先對部分長字段進(jìn)行處理,經(jīng)過觀察后發(fā)現(xiàn)可以采用以下方式處理:

1.將有“Thunder”字段的換成"T","Thundershower"變?yōu)椤癟shower”,這可以接受,有些天氣平臺就是這么表示的。

2.將有空格的字段分兩行顯示。

這樣就能把一行顯示的字符控制在10個以內(nèi),具體詳見RP2040的draw.py中的draw_real_time_weather_picture()函數(shù),如下。

    def draw_real_time_weather_picture(self,city_name,weather_current_code,weather_current_text,current_temperature):

        ..................

        if weather_current_text.find('Thunder') != -1:      
            weather_current_text = weather_current_text.replace('Thunder','T')
            
        if weather_current_text.find(' ') != -1:
            item = weather_current_text.split(' ',1)
            self.display.text(font2,item[0],0,130)
            self.display.text(font2,item[1],0,170)
        else:
            self.display.text(font2,weather_current_text,0,150)
        
          ...........................

(2)生活指數(shù)顯示

image.png               image.png

由于返回的參數(shù)都是英文,生活指數(shù)部分字段長度不定,所以這里分兩頁來顯示,具體詳見draw.py中的draw_index_of_living()函數(shù),如下。

    def draw_index_of_living(self,index1,index2,index3,picture_index):

           ............................#         由于6向指數(shù)很難在同一幅畫面顯示,所以分開顯示
        if picture_index == 2:
            self.display.text(font3,"car_washing",60,0)          #洗車指數(shù)
            self.display.jpg("/index of living/car_washing.jpg" ,0 , 0, st7789.FAST)
            self.display.text(font2,index1,60,20,st7789.BLUE)
            
            self.display.text(font3,"dressing",0,81)                    #穿衣指數(shù)
            self.display.jpg("/index of living/dressing.jpg" ,180 , 81, st7789.FAST)
            self.display.text(font2,index2,0,100,st7789.RED)
            
            self.display.text(font3,"flu",60,161)                #流感指數(shù) 
            self.display.jpg("/index of living/flu.jpg" ,0 , 161, st7789.FAST)
            self.display.text(font2,index3,60,180,st7789.GREEN)
            
        if picture_index == 3:
            self.display.text(font3,"sport",60,0)            #運動指數(shù)
            self.display.jpg("/index of living/sport.jpg" ,0 , 0, st7789.FAST)
            self.display.text(font2,index1,60,20,st7789.BLUE)
            
            self.display.text(font3,"travel",0,81)            #旅游指數(shù)
            self.display.jpg("/index of living/travel.jpg" ,180 , 81, st7789.FAST)
            self.display.text(font2,index2,0,100,st7789.RED)
            
            self.display.text(font3,"uv",60,161)             #紫外線指數(shù)
            self.display.jpg("/index of living/uv.jpg" ,0 , 161, st7789.FAST)
            self.display.text(font2,index3,60,180,st7789.GREEN)

(3)天氣預(yù)報顯示

image.png

天氣預(yù)報要顯示的內(nèi)容是最多的,因此如何合理安排布局并使數(shù)據(jù)直觀是一個挑戰(zhàn)。

這里的天氣圖標(biāo)對應(yīng)的是小版的,使用和上面一樣的在類中定義不同的方法(方法名有一定的規(guī)則),然后通過getattr函數(shù)來進(jìn)行實現(xiàn)判斷的方式來實現(xiàn)。

在每日[敏感詞][敏感詞]氣溫的顯示上,我采用了比較簡約的方法,用紅色字體+H表示[敏感詞]氣溫,用藍(lán)色字體+L表示[敏感詞]氣溫,具體代碼詳見draw_weather_forcast()函數(shù)。

    def draw_weather_forcast(self,date0,date0_day_text,date0_day_code,date0_high_temperature,date0_low_temperature,date0_precip,date0_humidity,
                             date1,date1_day_text,date1_day_code,date1_high_temperature,date1_low_temperature,date1_precip,date1_humidity,
                             date2,date2_day_text,date2_day_code,date2_high_temperature,date2_low_temperature,date2_precip,date2_humidity):       

          .......................

 
        self.display.text(font3,"H"+date0_high_temperature,5,120,st7789.RED)       #[敏感詞]氣溫
        self.display.text(font3,"L"+date0_low_temperature,5,140,st7789.BLUE)       #[敏感詞]氣溫
        self.display.text(font2,"C",43,125)
        self.display.text(font1,"o",40,120)


            .........................

而關(guān)于降水概率(POP)和相對濕度(HR)都是百分?jǐn)?shù),可以采用類似長度條的方式直觀的表現(xiàn)其大小,而且占用空間小。




   def draw_weather_forcast(self,date0,date0_day_text,date0_day_code,date0_high_temperature,date0_low_temperature,date0_precip,date0_humidity,
                             date1,date1_day_text,date1_day_code,date1_high_temperature,date1_low_temperature,date1_precip,date1_humidity,
                             date2,date2_day_text,date2_day_code,date2_high_temperature,date2_low_temperature,date2_precip,date2_humidity):

          ..........................

       self.display.text(font3,"POP:",0,160,st7789.MAGENTA)       #降雨概率POP
        num_date0_precip = float(date0_precip)
        length = int(num_date0_precip * 70)
        self.display.fill_rect(5,183,length,5,st7789.MAGENTA)       #顏色條顯示,越長百分比越大
        self.display.fill_rect(3,182,5,7,st7789.WHITE)
        num_date0_precip = num_date0_precip * 100
        num_date0_precip= int(num_date0_precip)
        self.display.text(font3,str(num_date0_precip)+"%",50,160,st7789.MAGENTA)
        
        self.display.text(font3,"HR:",0,190,st7789.CYAN)          #相對濕度HR
        num_date0_humidity= int(date0_humidity)
        length = int(num_date0_humidity * 70 /100)
        self.display.fill_rect(5,213,length,5,st7789.CYAN)
        self.display.fill_rect(3,212,5,7,st7789.WHITE)
        self.display.text(font3,str(num_date0_humidity)+"%",50,190,st7789.CYAN)

        ....................................

(4)實時時間顯示

若要得到實時時間,可以通過RP2040的RTC(實時時鐘)獲得當(dāng)前時間,調(diào)用也十分方便。不過要顯示時間的話,則需要一個變量來記錄時間的變化,時間數(shù)值變化了才刷新屏幕顯示,這樣就能在屏幕上實現(xiàn)時間變化的效果。本項目使用 self.last 來記錄時間,具體實現(xiàn)代碼詳見weather_main.py的draw_times函數(shù):

    def draw_times(self):
        if self.rtc.datetime() != self.last :
            time_index = self.rtc.datetime()
            #             只在實時天氣界面顯示時間
            if self.picture_index == 1 :
                self.drawing.draw_time(str(time_index[0]),str(time_index[1]),str(time_index[2]),str(time_index[3]),str(time_index[4]),str(time_index[5]),str(time_index[6]))
            self.last = time_index
        gc.collect()

由于時間的顯示是需要實時更新的,所以這個函數(shù)在總進(jìn)程中也要調(diào)用。

    async def process(self):

        self.hardware_init()
        await self.initialise_wifi()  #初始化界面
        
        self.last_hour = self.rtc.datetime()[4]
        self.last = self.rtc.datetime()
        while True:
            self.dir_select()     #遙感檢測
            self.regular_update() #定時更新
            self.draw_times()     #更新時間
            self.city_choose()    #修改城市名
            await self.uart_task()  #串口任務(wù)

 

 

3.操作交互

本項目顯示主要分為兩個模式:一般模式和鍵盤模式。一般模式下主要顯示天氣信息,鍵盤模式下顯示并修改城市名。

(1)一般模式

即接收到http報文后顯示各類天氣信息的模式。

基本操作:搖桿左右移動可切換顯示內(nèi)容,上下移動則無效,通過變量 self-picture-index 決定顯示哪一個畫面,具體內(nèi)容請參考 weather_main.py中的 dir_select()函數(shù),如下。

    def dir_select(self):

        xValue = self.xAxis.read_u16()
        yValue = self.yAxis.read_u16()

        if xValue <1000:
            self.picture_index -= 1
            if self.picture_index < 1 :
                self.picture_index = 4
            self.draw_picture()   #遙感有動作時才更新畫面
        elif xValue >40000:
            self.picture_index += 1
            if self.picture_index > 4 :
                self.picture_index = 1
            self.draw_picture()

        gc.collect()

該模式下B鍵用于刷新天氣數(shù)據(jù),即按下B鍵后就使發(fā)送標(biāo)志生效,發(fā)送城市名給ESP32,讓其發(fā)送http請求,具體詳見weather_main的refresh_callback()函數(shù),如下。

    def refresh_callback(self, p):
        print("k2 pressed")
        self.send_flag = True

A鍵則用于打開鍵盤模式,具體詳見weather_main的keyboard_callback()函數(shù),如下。

    def keyboard_callback(self, p):
        print("kkk pressed")
        self.keyboard_cw = True

(2)鍵盤模式

即顯示一個虛擬的9鍵鍵盤,讓使用者能修改城市名。

基本操作:參考我門平時聽熟悉的9鍵鍵盤,其會把26個英文字母分成不同段安排在不同按鍵中,當(dāng)我們選中按鈕后會彈出一欄字母的分支選擇,在分支欄中再進(jìn)行一次選擇才能把內(nèi)容寫入(當(dāng)然這是以前的9鍵鍵盤,現(xiàn)在的可以模糊選擇),所以基本的邏輯如下。

image.png

 

所以操作的流程為:按A打開鍵盤,用四項搖桿進(jìn)行上下左右鍵位選擇,最左邊一欄為功能鍵,其余為字符鍵,按A選中,選擇不同的功能鍵會有不同的效果,字符鍵分為有效字符和無效字符(.用 ">_<"表示),選擇無效字符是無反應(yīng)的,選擇有效字符后最左側(cè)的功能鍵欄會被替換為分支內(nèi)容,此時只能上下移動搖桿,按A選中寫入字母到發(fā)送序列,按B則回退到9鍵選擇,在選中發(fā)送鍵"ENT”前會一直保留鍵盤界面,按“ENT”后才會退出并發(fā)送城市名給ESP32進(jìn)行http請求。若http請求失敗(城市名有誤,網(wǎng)絡(luò)斷開),則會進(jìn)入httprequestfail界面,若是城市名輸入有誤,此時按A鍵可重新打開鍵盤修改信息。

代碼實現(xiàn)過程:

實現(xiàn)一個虛擬鍵盤本質(zhì)上就是,使用按鍵進(jìn)行信息操作,操作過程通過屏幕顯示出來。

關(guān)于鍵位顯示:移動光標(biāo)的結(jié)果可以用x,y坐標(biāo)表示,對應(yīng)RP2040中的 self.locat_x和self.locat_y,關(guān)鍵在于每一個位置對應(yīng)不同的功能和字段,因此我利用上文說到的在類中定義不同的方法(方法名有一定的規(guī)則),然后通過getattr函數(shù)來進(jìn)行實現(xiàn)判斷,相干內(nèi)容在location.py的location類中,如下。

class location:
    caps = 1       #大小寫開關(guān)
    number = 0     #數(shù)字開關(guān)
    
    def location1_1(self):
       return "123"    #切換為數(shù)字


    def location2_1(self):
        if location.number == 1:
            return "1  "
        elif location.caps == 1:
            return "abc"
        else:
            return "ABC"
        
    def location3_1(self):
        
       if location.number == 1:
            return "2  "
       elif location.caps == 1:
            return "def"
       else:
            return "DEF"
        
    def location4_1(self):
        
        if location.number == 1:
            return "3  "
        elif location.caps == 1:
            return "ghi"
        else:
            return "GHI"
    
    def location1_2(self):
        return "A/a"         #切換大小寫


    def location2_2(self):
        if location.number == 1:
            return "4  "
        elif location.caps == 1:
            return "jkl"
        else:
            return "JKL"
        
    def location3_2(self):
        if location.number == 1:
            return "5  "
        elif location.caps == 1:
            return "mno"
        else:
            return "MNO"
        
    def location4_2(self):
        if location.number == 1:
            return "6   "
        elif location.caps == 1:
            return "pqrs"
        else:
            return "PQRS"
        
    def location1_3(self):
        return "DEL"     #刪除字符
    
    
    def location2_3(self):
       if location.number == 1:
            return "7  "
       elif location.caps == 1:
            return "tuv"
       else:
            return "TUV"
        
    def location3_3(self):
        if location.number == 1:
            return "8   "
        elif location.caps == 1:
            return "wxyz"
        else:
            return "WXYZ"
        
    def location4_3(self):
        if location.number == 1:
            return "9  "
        else:
            return '>_<'   #英文字母不需要這一位      
    def location1_4(self):
        return "ENT"    #發(fā)送字符
    
    
    def location2_4(self):
        return '>_<'    
    def location3_4(self):
        if location.number == 1:
            return "0  "
        else:
            return '>_<' #英文字母不需要這一位
            
    def location4_4(self):
        return '>_<'
        
    def Default(self):
        print("wrong")
        
    def getlocation(self, locationx,locationy):
        location_name = "location" + str(locationx)+"_"+str(locationy)
        fun = getattr(self, location_name, self.Default)
        return fun()

這樣就將鍵位內(nèi)容和鍵位聯(lián)系起來了,就可以實現(xiàn)畫鍵盤(draw.py的draw_keyboard()函數(shù))和高亮按鍵(draw.py的draw_highlight()函數(shù)),如下:

    def draw_keyboard(self):
        a = location()
        self.display.fill_rect(0,101,34,139,st7789.BLACK)
        self.display.fill_rect(36,206,63,33,st7789.BLACK)
        self.display.fill_rect(176,206,63,33,st7789.BLACK)
        self.display.vline(35,100,140,st7789.YELLOW)
        self.display.vline(105,100,140,st7789.YELLOW)
        self.display.vline(175,100,240,st7789.YELLOW)
        self.display.hline(0,100,240,st7789.YELLOW)
        self.display.hline(0,135,240,st7789.YELLOW)
        self.display.hline(0,170,240,st7789.YELLOW)
        self.display.hline(0,205,240,st7789.YELLOW)
        
        self.display.text(font2,a.getlocation(2,1),36,101 )
        self.display.text(font2,a.getlocation(3,1),106,101 )
        self.display.text(font2,a.getlocation(4,1),176,101 )
        
        self.display.text(font2,a.getlocation(2,2),36,136 )
        self.display.text(font2,a.getlocation(3,2),106,136 )
        self.display.text(font2,a.getlocation(4,2),176,136 )
        
        self.display.text(font2,a.getlocation(2,3),36,171 )
        self.display.text(font2,a.getlocation(3,3),106,171 )
        self.display.text(font2,a.getlocation(4,3),176,171 )
        
        self.display.text(font2,a.getlocation(3,4),106,206 )
        
        self.display.text(font3,a.getlocation(1,1),0,101 )
        self.display.text(font3,a.getlocation(1,2),0,136 )
        self.display.text(font3,a.getlocation(1,3),0,171 )
        self.display.text(font3,a.getlocation(1,4),0,206 )
        #     選中按鍵字體變成[敏感詞]表示高亮
    def draw_highlight(self,x,y):
        a = location()
        locat_x = 36 + 70 * (x-2)
        locat_y = 101 + 35 * (y-1)#         功能鍵字體大小偏小需另外處理
        if x == 1:
            self.display.text(font3,a.getlocation(x,y),0,locat_y,st7789.YELLOW)
        else:
            self.display.text(font2,a.getlocation(x,y),locat_x,locat_y,st7789.YELLOW)

那么怎么判斷功能鍵并實行相應(yīng)功能呢?本項目把功能鍵設(shè)置成特定字段(123,A/a,DEL,ENT),并在鍵盤循環(huán)中判斷相應(yīng)字段是否對應(yīng)即可,然后實現(xiàn)相應(yīng)功能,具體詳見weather_main的keyboard()函數(shù),如下。

    def keyboard(self):#         畫出鍵盤
        self.drawing_keyboard()
        self.drawing.draw_quene(self.send_quene)#         修改按鍵AB的回調(diào)函數(shù)        
        self.k1 = button(game_kit.key_a, self.k1_callback)
        self.k2 = button(game_kit.key_b, self.k2_callback)#         保持鍵盤畫面,在確認(rèn)發(fā)送后退出畫面
        while self.keyboard_cw == True:
            self.backup = False
            xValue = self.xAxis.read_u16()
            yValue = self.yAxis.read_u16()
            sleep(0.2)
            if xValue <1000:
                self.locat_x -= 1
                if self.locat_x < 1:
                    self.locat_x = 1
                self.drawing_keyboard()  #每次移動搖桿后更新畫面
            elif xValue >40000:
                self.locat_x += 1
                if self.locat_x > 4:
                    self.locat_x = 4
                self.drawing_keyboard()
            if yValue <1000:
                self.locat_y -= 1
                if self.locat_y < 1:
                    self.locat_y = 1
                self.drawing_keyboard()
            elif yValue >40000:
                self.locat_y += 1
                if self.locat_y > 4:
                    self.locat_y = 4
                self.drawing_keyboard()
            #             選中一格
            if self.chosen :
                self.chosen = False
                a= location()
                s_list=list(self.send_quene )#將字符串轉(zhuǎn)換為列表
                #                 選中發(fā)送鍵
                if a.getlocation(self.locat_x,self.locat_y) == "ENT":
                    self.keyboard_cw = False   #關(guān)閉鍵盤退出循環(huán)
                    #                     選中刪除鍵
                elif a.getlocation(self.locat_x,self.locat_y) == "DEL" :#                     判斷是否已經(jīng)全部刪除完了
                    if len(s_list)!= 0 :
                        s_list.pop(-1)#pop掉列表最后一個值,返回被pop掉的值
                        self.send_quene = ''.join(s_list)#將pop之后的列表通過join()函數(shù)轉(zhuǎn)換為字符串
                        self.drawing.draw_quene(self.send_quene) #更新已寫入內(nèi)容
                        #                         選中有效區(qū)域
                elif a.getlocation(self.locat_x,self.locat_y) != ">_<" :
                    if a.getlocation(self.locat_x,self.locat_y) == "123":   #選中切換數(shù)字
                        location.number = 1
                        self.drawing_keyboard()
                    elif a.getlocation(self.locat_x,self.locat_y) == "A/a": #選中切換大小寫
                        location.number = 0
                        location.caps = 1-location.caps
                        self.drawing_keyboard()
                    else:
                        self.branch_choose(self.locat_x,self.locat_y) #選中字符串

而選擇有效字符后需要顯示分支,按照鍵位信息中的內(nèi)容顯示在原功能欄即可,此時修改搖桿為只能上下移動,并利用按鍵AB實現(xiàn)選擇或回退,邏輯部分詳見weather_main.py的branch_choose()函數(shù),畫圖部分詳見draw.py的draw_branch()函數(shù),如下。

/weather_main.py    def branch_choose(self,x,y):
        index = 1
        a = location()
        index_max = len(a.getlocation(self.locat_x,self.locat_y)) #按鍵內(nèi)容占格數(shù)
        self.drawing_branch(index)
        #         是否按下回退鍵,若按下則回到鍵位選擇
        while self.backup == False  :
            sleep(0.2)
            yValue = self.yAxis.read_u16()
            if yValue <1000:
                index -= 1
                if index < 1:
                    index = 1
                self.drawing_branch(index)
            elif yValue >40000:
                index += 1
                if index > index_max:
                    index = index_max
                self.drawing_branch(index)
                #            選中字符
            if self.chosen :
                self.chosen = False
                self.send_quene = ''.join([self.send_quene,a.getlocation(self.locat_x,self.locat_y)[index-1]]) #將字符加入發(fā)送隊列
                self.backup =True  #退出分支
                
            self.drawing.draw_quene(self.send_quene) #更新寫入內(nèi)容
        self.backup = False
        self.drawing_keyboard()/draw.py    def draw_branch(self,index,str):
    
       self.display.fill_rect(0,101,34,33,st7789.BLACK)
       self.display.fill_rect(0,136,34,33,st7789.BLACK)
       self.display.fill_rect(0,171,34,33,st7789.BLACK)
       self.display.fill_rect(0,206,34,33,st7789.BLACK)
       locat_y = 101 + 35*(index -1)
       for i in range(0,len(str)):
           self.display.text(font2,str[i],0,101 + 35 * i)
       self.display.text(font2,str[index-1],0,locat_y,st7789.YELLOW)

最后在發(fā)送數(shù)據(jù)退出鍵盤模式回到一般模式時,要還原現(xiàn)場,具體操作詳見weather_main.py中的city_choose()函數(shù),如下。

    def city_choose(self):
        if self.keyboard_cw == True:
            self.drawing.clear()  #清屏
            self.drawing.draw_tip("city_name:")
            self.keyboard()
            self.picture_index = 0  
            self.drawing.draw_sending()  #發(fā)送中畫面
            #             將按鍵回調(diào)函數(shù)修改回一般模式下的情況
            self.k1 = button(game_kit.key_a, self.keyboard_callback)
            self.k2 = button(game_kit.key_b, self.refresh_callback)
            self.send_flag = True  #可以發(fā)送
            gc.collect()
            self.last = self.rtc.datetime()  #還原現(xiàn)場,保持時間更新

四、后記

這是我第二次用樹莓派的rp 2040來開發(fā)項目了,這次的過程比上一次要復(fù)雜許多,雖然總的思路很清晰,但這一次新引入的esp32-s2模塊給我?guī)砹巳碌奶魬?zhàn),網(wǎng)絡(luò)編程和多機(jī)通信,很多都是我[敏感詞]次接觸的東西,比如esd32-idf的開發(fā),不過最后我也充分體會到了多機(jī)互聯(lián)的快樂。就本項目而言,未來還有值得提高的地方。

  1. 加入中文顯示。有關(guān)這方面的知識我還不太了解,如果能有中文顯示界面將可以進(jìn)一步優(yōu)化。
  2. 加入WiFi掃描與連接。既然有了鍵盤理論上就能通過操作游戲機(jī)來聯(lián)網(wǎng),這樣使用起來將更加靈活。    
  3. 利用Esp 32的 NVM儲存WiFi的相關(guān)信息,這樣在每次斷電后再恢復(fù)供電時能自動連接WiFi?

由于時間原因以上兩點尚未實現(xiàn),但我相信在不久的將來定能實現(xiàn)。






免責(zé)聲明:本文轉(zhuǎn)載自“電子森林”,本文僅代表作者個人觀點,不代表薩科微及行業(yè)觀點,只為轉(zhuǎn)載與分享,支持保護(hù)知識產(chǎn)權(quán),轉(zhuǎn)載請注明原出處及作者,如有侵權(quán)請聯(lián)系我們刪除。

服務(wù)熱線

0755-83044319

霍爾元件咨詢

肖特基二極管咨詢

TVS/ESD咨詢

獲取產(chǎn)品資料

客服微信

微信服務(wù)號

主站蜘蛛池模板: 青草91视频免费观看| 狠狠色丁香久久综合五月| 欧美性爽xxxⅹbbbb| 美女三级网站| 国产色女人| 午夜精品久久久久久久| 免费看黄在线观看| 黄色网址播放| 最黄毛片| 色www亚洲国产张柏芝| 在线观看亚洲天堂| 色老头网址| 韩国三级hd中文字幕好大| 亚洲午夜久久影院| a资源在线观看| 老师下面很湿很爽很紧| 四虎精品免费永久在线| 久久2017| 天天干天天干天天干天天干天天干| 国产干美女| 日本一区二区在线不卡| 欧美性色欧美a在线观看| 天天干夜夜看| 观看在线人视频| 性欧美视频| 老色批影院| 天天舔天天射天天干| 狼狼鲁狼狼色| 色福利视频| 中文字幕久久精品波多野结| 国产精品午夜久久久久久99热| 啪啪网站色大全免费| 色噜噜狠狠色综合欧洲selulu| 亚洲成a人片在线看| 国产操比视频| 日本大片免aaa费观看视频| 91一区二区三区四区五区| 91天天干| 欧美一级视频在线观看| 九九碰| 国产成人乱码一区二区三区|