wtorek, 13 listopada 2018

[30] STM32F7 - HTTP Sterowanie przez stronę internetową

W tym poście chciałbym opisać sposób sterowania elementami układu przez stronę internetową.

[Źródło: http://www.st.com/en/evaluation-tools/32f746gdiscovery.html]

Opis programu:


Program ma za zadanie sterowanie urządzeniem przez stronę internetową. W przykładzie zaimplementowałem takie funkcje jak:
  • odczyt stanu przekaźników/pinów wraz z możliwością sterowania;
  • odczyt danych z czujników pomiarowych;
  • odczytanie czasu z urządzenia;
  • zmiana koloru tła na ekranie;
  • zmiana napisu na wyświetlaczu;
  • graficzna prezentacja temperatury;
Cały projekt działa w oparciu o system FreeRtos.

Na samym początku musimy uruchomić zadanie odpowiedzialne za ustanowienie połączenia i stworzenie serwera HTTP pod zadanym adresem:


  1. void StartDefaultTask(void const * argument)
  2. {
  3.     MX_LWIP_Init();

  4.     /* USER CODE BEGIN 5 */
  5.     struct netconn *netConn;
  6.     err_t err;

  7.     netConn = netconn_new(NETCONN_TCP);

  8.     if(netConn != NULL)
  9.     {
  10.         socket1.conn = netConn;
  11.         err = netconn_bind(netConn, NULL, 80);
  12.         if (err == ERR_OK)
  13.         {
  14.             netconn_listen(netConn);
  15.             sys_thread_new("tcp_thread1", tcp_thread, (void*)&socket1, DEFAULT_THREAD_STACKSIZE, osPriorityNormal );
  16.         }
  17.         else
  18.         {
  19.             netconn_delete(netConn);
  20.         }
  21.     }
  22.     /* Infinite loop */
  23.     for(;;) { osDelay(1); }
  24.     /* USER CODE END 5 */
  25. }


Drugie zadanie pobiera informację od strony internetowej i dodaje je do kolejki:


  1. void stringOutputTaksAddMailQue(void const * argument)
  2. {
  3.     osEvent event;
  4.     for(;;)
  5.     {
  6.         event = osMailGet(strout_Queue, osWaitForever);
  7.         if (event.status == osEventMail)
  8.         {
  9.             struct_out *queueStruct;
  10.             queueStruct = event.value.p;
  11.         }
  12.     }
  13. }

Poniżej przejdę przez funkcję wywoływane przez stronę internetową:

Na samym początku zmiana koloru tła. Nowy kolor jest wybierany na podstawie przekazanego numeru:


  1. void setScreenBackgroundColor(uint8_t selectedColor)
  2. {
  3.     switch (selectedColor)
  4.     {
  5.       case '1':
  6.           ROCKTECH_FillScreen(LCD_COLOR_RED);
  7.         break;
  8.       case '2':
  9.           ROCKTECH_FillScreen(LCD_COLOR_GREEN);
  10.         break;
  11.       case '3':
  12.           ROCKTECH_FillScreen(LCD_COLOR_BLUE);
  13.         break;
  14.       case '4':
  15.           ROCKTECH_FillScreen(LCD_COLOR_BLACK);
  16.         break;
  17.       case '5':
  18.           ROCKTECH_FillScreen(LCD_COLOR_CYAN);
  19.         break;
  20.       case '6':
  21.           ROCKTECH_FillScreen(LCD_COLOR_MAGENTA);
  22.         break;
  23.       case '7':
  24.           ROCKTECH_FillScreen(LCD_COLOR_YELLOW);
  25.         break;
  26.       case '8':
  27.           ROCKTECH_FillScreen(LCD_COLOR_WHITE);
  28.         break;
  29.       default:
  30.           ROCKTECH_FillScreen(LCD_COLOR_WHITE);
  31.         break;
  32.     }
  33. }


Kolejna funkcja odpowiada za sterowanie przekaźnikami:

  1. static uint8_t changeStateOfRelay(uint8_t relayNumber)
  2. {
  3.     switch (relayNumber)
  4.     {
  5.       case '1':
  6.           return toggleAndCheckRelayState_1();
  7.         break;
  8.       case '2':
  9.           return toggleAndCheckRelayState_2();
  10.         break;
  11.       case '3':
  12.           return toggleAndCheckRelayState_3();
  13.         break;
  14.       case '4':
  15.           return toggleAndCheckRelayState_4();
  16.         break;
  17.       case '5':
  18.           return toggleAndCheckRelayState_5();
  19.         break;
  20.       default:
  21.           return 0;
  22.         break;
  23.     }
  24. }

Po odebraniu komendy o zmianie stanu przekaźnika ustawiamy nowy stan na przeciwny. Następnie odczytujemy aktualny stan przekaźnika, na tej podstawie zwracamy wartość, która określi informację o przesłaniu danych do serwera.

  1. static uint8_t toggleAndCheckRelayState_1()
  2. {
  3.     HAL_GPIO_TogglePin(RELAY_1_PORT, RELAY_1_PIN);
  4.     return checkRelayState_1();
  5. }

  1. void prepareResponseString(uint8_t *responseFramePtr, uint8_t selectedRelay, uint8_t relayState)
  2. {
  3.     sprintf((char *)responseFramePtr, "relay.html?r=x_Ox");
  4.     responseFramePtr[13] = selectedRelay;
  5.     if(relayState == 1)
  6.     {
  7.         responseFramePtr[16] = 'n';
  8.     }
  9.     else if(relayState == 2)
  10.     {
  11.         responseFramePtr[16] = 'f';
  12.     }
  13. }

Funkcja ustawiająca przekaźniki na stronie serwera HTTP:

  1. function GetRelayState(val){
  2.     nocache = "&nocache=" + Math.random() * 1000000;
  3.     var request = new XMLHttpRequest();
  4.     request.onreadystatechange = function(){
  5.         if(this.readyState == 4){
  6.             if(this.status == 200){
  7.                 if(this.responseText != null){
  8.                     var msg = this.responseText;
  9.                     if(val == 1)
  10.                     {
  11.                         if(msg.includes("relay.html?r=1_On") == true)
  12.                         {
  13.                             document.getElementById("controlRelayBtn1").innerHTML = '<input class = "controlRelayBtn" type="button" style="color: #000000; background-color: #00ff00;" onclick="GetRelayState()" value="Przekaźnik 1 ON"/>';
  14.                         }
  15.                         else
  16.                         {
  17.                             document.getElementById("controlRelayBtn1").innerHTML = '<input class = "controlRelayBtn" type="button" style="color: #00ffff; background-color: #ff0000;" onclick="GetRelayState()" value="Przekaźnik 1 OFF"/>';
  18.                         }
  19.                     }
  20.                     else if(val == 2)
  21.                     {
  22.                         if(msg.includes("relay.html?r=2_On") == true)
  23.                         {
  24.                             document.getElementById("controlRelayBtn2").innerHTML = '<input class = "controlRelayBtn" type="button" style="color: #000000; background-color: #00ff00;" onclick="GetRelayState()" value="Przekaźnik 2 ON"/>';
  25.                         }
  26.                         else
  27.                         {
  28.                             document.getElementById("controlRelayBtn2").innerHTML = '<input class = "controlRelayBtn" type="button" style="color: #00ffff; background-color: #ff0000;" onclick="GetRelayState()" value="Przekaźnik 2 OFF"/>';
  29.                         }                      
  30.                     }
  31.                     else if(val == 3)
  32.                     {
  33.                         if(msg.includes("relay.html?r=3_On") == true)
  34.                         {
  35.                             document.getElementById("controlRelayBtn3").innerHTML = '<input class = "controlRelayBtn" type="button" style="color: #000000; background-color: #00ff00;" onclick="GetRelayState()" value="Przekaźnik 3 ON"/>';
  36.                         }
  37.                         else
  38.                         {
  39.                             document.getElementById("controlRelayBtn3").innerHTML = '<input class = "controlRelayBtn" type="button" style="color: #00ffff; background-color: #ff0000;" onclick="GetRelayState()" value="Przekaźnik 3 OFF"/>';
  40.                         }      
  41.                     }
  42.                     else if(val == 4)
  43.                     {
  44.                         if(msg.includes("relay.html?r=4_On") == true)
  45.                         {
  46.                             document.getElementById("controlRelayBtn4").innerHTML = '<input class = "controlRelayBtn" type="button" style="color: #000000; background-color: #00ff00;" onclick="GetRelayState()" value="Przekaźnik 4 ON"/>';
  47.                         }
  48.                         else
  49.                         {
  50.                             document.getElementById("controlRelayBtn4").innerHTML = '<input class = "controlRelayBtn" type="button" style="color: #00ffff; background-color: #ff0000;" onclick="GetRelayState()" value="Przekaźnik 4 OFF"/>';
  51.                         }      
  52.                     }
  53.                     else if(val == 5)
  54.                     {
  55.                         if(msg.includes("relay.html?r=5_On") == true)
  56.                         {
  57.                             document.getElementById("controlRelayBtn5").innerHTML = '<input class = "controlRelayBtn" type="button" style="color: #000000; background-color: #00ff00;" onclick="GetRelayState()" value="Przekaźnik 5 ON"/>';
  58.                         }
  59.                         else
  60.                         {
  61.                             document.getElementById("controlRelayBtn5").innerHTML = '<input class = "controlRelayBtn" type="button" style="color: #00ffff; background-color: #ff0000;" onclick="GetRelayState()" value="Przekaźnik 5 OFF"/>';
  62.                         }      
  63.                     }
  64.                 }
  65.             }
  66.         }
  67.     }
  68.        
  69.     if(val == 1)
  70.     {
  71.         request.open("GET", "relay.html?r=1" + nocache, true);
  72.         request.send(null);
  73.     }
  74.     else if(val == 2)
  75.     {
  76.         request.open("GET", "relay.html?r=2" + nocache, true);
  77.         request.send(null);
  78.     }
  79.     else if(val == 3)
  80.     {
  81.         request.open("GET", "relay.html?r=3" + nocache, true);
  82.         request.send(null);
  83.     }
  84.     else if(val == 4)
  85.     {
  86.         request.open("GET", "relay.html?r=4" + nocache, true);
  87.         request.send(null);
  88.     }
  89.     else if(val == 5)
  90.     {
  91.         request.open("GET", "relay.html?r=5" + nocache, true);
  92.         request.send(null);
  93.     }
  94. }

Dodatkowa funkcja pozwalająca na odczytanie stanu wszystkich przekaźników wygląda następująco:

  1. void prepareResponseStringWithRelaysState(uint8_t *responseFramePtr)
  2. {
  3.     sprintf((char *)responseFramePtr, "GET /relay.html?A=1x;2x;3x;4x;5x");
  4.     if(checkRelayState_1() == 1)    { *(responseFramePtr + 19) = 'O'; }
  5.     else                            { *(responseFramePtr + 19) = 'F'; }
  6.     if(checkRelayState_2() == 1)    { *(responseFramePtr + 22) = 'O'; }
  7.     else                            { *(responseFramePtr + 22) = 'F'; }
  8.     if(checkRelayState_3() == 1)    { *(responseFramePtr + 25) = 'O'; }
  9.     else                            { *(responseFramePtr + 25) = 'F'; }
  10.     if(checkRelayState_4() == 1)    { *(responseFramePtr + 28) = 'O'; }
  11.     else                            { *(responseFramePtr + 28) = 'F'; }
  12.     if(checkRelayState_5() == 1)    { *(responseFramePtr + 31) = 'O'; }
  13.     else                            { *(responseFramePtr + 31) = 'F'; }
  14. }

Funkcja obsługujące wyświetlenie danych ze stanem wszystkich przekaźników na stronie serwera HTTP:

  1. function GetRelaysStates()
  2. {
  3.     nocache = "&nocache=" + Math.random() * 1000000;
  4.     var request = new XMLHttpRequest();
  5.     request.onreadystatechange = function(){
  6.     if(this.readyState == 4){
  7.         if(this.status == 200){
  8.             if(this.responseText != null){
  9.                 var msg = this.responseText;
  10.                 if(msg.indexOf('1O') != -1)
  11.                 {
  12.                     document.getElementById("controlRelayBtn1").innerHTML = '<input class = "controlRelayBtn" type="button" style="color: #000000; background-color: #00ff00;" onclick="GetRelayState()" value="Przekaźnik 1 ON"/>';
  13.                 }
  14.                 else
  15.                 {
  16.                     document.getElementById("controlRelayBtn1").innerHTML = '<input class = "controlRelayBtn" type="button" style="color: #00ffff; background-color: #ff0000;" onclick="GetRelayState()" value="Przekaźnik 1 OFF"/>';
  17.                 }
  18.                        
  19.                 if(msg.indexOf('2O') != -1)
  20.                 {
  21.                     document.getElementById("controlRelayBtn2").innerHTML = '<input class = "controlRelayBtn" type="button" style="color: #000000; background-color: #00ff00;" onclick="GetRelayState()" value="Przekaźnik 2 ON"/>';
  22.                 }
  23.                 else
  24.                 {
  25.                     document.getElementById("controlRelayBtn2").innerHTML = '<input class = "controlRelayBtn" type="button" style="color: #00ffff; background-color: #ff0000;" onclick="GetRelayState()" value="Przekaźnik 2 OFF"/>';
  26.                 }                      
  27.                        
  28.                 if(msg.indexOf('3O') != -1)
  29.                 {
  30.                     document.getElementById("controlRelayBtn3").innerHTML = '<input class = "controlRelayBtn" type="button" style="color: #000000; background-color: #00ff00;" onclick="GetRelayState()" value="Przekaźnik 3 ON"/>';
  31.                 }
  32.                 else
  33.                 {
  34.                     document.getElementById("controlRelayBtn3").innerHTML = '<input class = "controlRelayBtn" type="button" style="color: #00ffff; background-color: #ff0000;" onclick="GetRelayState()" value="Przekaźnik 3 OFF"/>';
  35.                 }      
  36.                 if(msg.indexOf('4O') != -1)
  37.                 {
  38.                     document.getElementById("controlRelayBtn4").innerHTML = '<input class = "controlRelayBtn" type="button" style="color: #000000; background-color: #00ff00;" onclick="GetRelayState()" value="Przekaźnik 4 ON"/>';
  39.                 }
  40.                 else
  41.                 {
  42.                     document.getElementById("controlRelayBtn4").innerHTML = '<input class = "controlRelayBtn" type="button" style="color: #00ffff; background-color: #ff0000;" onclick="GetRelayState()" value="Przekaźnik 4 OFF"/>';
  43.                 }      
  44.                 if(msg.indexOf('5O') != -1)
  45.                 {
  46.                     document.getElementById("controlRelayBtn5").innerHTML = '<input class = "controlRelayBtn" type="button" style="color: #000000; background-color: #00ff00;" onclick="GetRelayState()" value="Przekaźnik 5 ON"/>';
  47.                 }
  48.                 else
  49.                 {
  50.                     document.getElementById("controlRelayBtn5").innerHTML = '<input class = "controlRelayBtn" type="button" style="color: #00ffff; background-color: #ff0000;" onclick="GetRelayState()" value="Przekaźnik 5 OFF"/>';
  51.                 }      
  52.             }
  53.         }
  54.     }
  55. }
  56. request.open("GET", "relay.html?A" + nocache, true);
  57. request.send(null);
  58. }

Następnie mamy przesyłanie tekstu jaki ma zostać wyświetlony na ekranie:

Funkcja odpowiedzialna za przesłanie ramki danych do mikrokontrolera:

  1. function sendDataToDisplay()
  2. {
  3.     xhr.open("GET", "distxt.html?d=" +  document.getElementById("myText").value + '@', true);
  4.     xhr.responseType = "text";
  5.     xhr.send(null);
  6. }

Funkcja odpowiedzialna za wypisanie tekstu na ekranie:

  1. void displayTextOnScreen(uint8_t *sendFramePtr)
  2. {
  3.       uint8_t bufferWithData[100] = {0x00};
  4.  
  5.       for(uint8_t i = 0, j = 0; i<100; i++, j++)
  6.       {
  7.           if(*(sendFramePtr + 19 + j) == '%' && *(sendFramePtr + 19 + j + 1) == '2' && *(sendFramePtr + 19 + j + 2) == '0')
  8.           {
  9.               bufferWithData[i] = ' ';
  10.               j += 2;
  11.           }
  12.           else if(*(sendFramePtr + 19 + j) != '@')
  13.           {
  14.               bufferWithData[i] = *(sendFramePtr + 19 + j);
  15.           }
  16.           else
  17.           {
  18.               bufferWithData[i] = '\n';
  19.               break;
  20.           }
  21.       }
  22.  
  23.       ROCKTECH_DisplayString(0, 40, (uint8_t *)bufferWithData, CENTER_MODE, 0);
  24. }

Odczytywanie parametrów takich jak czas, temperatura, ciśnienie, wilgotność, wysokość. Poniżej przykładowa funkcja odczytująca temperaturę. Wiadomość wyświetlana jest w polu tekstowym na stronie HTTP:

  1. function loadTimeFromDevice(){
  2.     nocache = "&nocache=" + Math.random() * 1000000;
  3.     var request = new XMLHttpRequest();
  4.     request.onreadystatechange = function(){
  5.         if(this.readyState == 4){
  6.             if(this.status == 200){
  7.                 if(this.responseText != null){
  8.                     var msg = this.responseText;
  9.                     var msgToDisplay = msg.substring(17, 33);
  10.                     document.getElementById("readedTimeLbl").innerHTML = "Czas: " + msgToDisplay;
  11.                 }
  12.             }
  13.         }
  14.     }
  15.     request.open("GET", "time.html?T=" + nocache, true);
  16.     request.send(null);
  17. }

Zdekodowanie oraz przesłanie odpowiedzi na stronę:

  1. else if (strncmp((char const *)buf,"GET /time.html?T=",17)==0)
  2. {
  3.     uint8_t responseFrame[36] = {0x00};
  4.     prepareResponseStringWithTimeExampleFunct(&responseFrame[0]);
  5.     netconn_write(newconn, (const unsigned char*)responseFrame, (size_t)35, NETCONN_NOCOPY);
  6. }

Za odczytanie wszystkich parametrów z czujników odpowiada następująca funkcja przesyłająca oraz dekodująca dane od strony serwera HTTP:

  1. function loadAllDataFromDevice(){
  2.     nocache = "&nocache=" + Math.random() * 1000000;
  3.     var request = new XMLHttpRequest();
  4.     request.onreadystatechange = function(){
  5.         if(this.readyState == 4){
  6.             if(this.status == 200){
  7.                 if(this.responseText != null){
  8.                     var msg = this.responseText;
  9.                     var msgToDisplay = msg.substring(21, 37);
  10.                     document.getElementById("readedTimeLbl").innerHTML = "Czas: " + msgToDisplay;
  11.                     msgToDisplay = msg.substring(38, 42);
  12.                     document.getElementById("readedTempLbl").innerHTML = "Temperatura: " + msgToDisplay;
  13.                     msgToDisplay = msg.substring(44, 46);
  14.                     document.getElementById("readedHumidLbl").innerHTML = "Wilgotność: " + msgToDisplay + "%";
  15.                     msgToDisplay = msg.substring(47, 53);
  16.                     document.getElementById("readedPressureLbl").innerHTML = "Ciśnienie: " + msgToDisplay + "hPa";
  17.                     msgToDisplay = msg.substring(54, 59);
  18.                     document.getElementById("readedAltitudeLbl").innerHTML = "Wysokość: " + msgToDisplay + "mnpm";
  19.                 }
  20.             }
  21.         }
  22.     }
  23.     request.open("GET", "alldata.html?AD=" + nocache, true);
  24.     request.send(null);
  25. }

Cały projekt można pobrać z dysku Google pod tym linkiem.