در این پست نحوه ساخت یک وب سرور توسط برد Wemos Lolin32 (OLED) و نمایش مقادیر ضربان قلب و اکسیژن خون به صورت بر خط و با استفاده از تکنولوژی WebSocket توضیح داده میشود.
پروتکل HTTP
این پروتکل جهت ارتباط میان وب سرور و وب کلاینت استفاده میشود. برای مثال زمانی که شما آدرس embeddedlab.ir را در نوار آدرس مرورگر خود تایپ میکنید یک درخواست HTTP GET به سرورهای وب embeddedlab.ir ارسال میشود. سرورها این درخواست را با ارسال یک صفحه وب پاسخ میدهند. همچنین به عنوان مثال زمانی که یک عکس را در اینستاگرام آپلود میکنید، مرورگر شما یک درخواست HTTP POST به همراه عکس مورد نظر به سرورهای اینستاگرام ارسال میکند. سپس سرورها با دریافت این درخواست، عکس مورد نظر را بر روی دیتابیس خود ذخیره کرده و آدرس عکس ذخیره شده را به مرورگر شما ارسال میکنند و در نهایت مرورگر، عکس مورد نظر را به صفحه وب اضافه کرده و نمایش میدهد. در واقع پروتکل HTTP به مرورگر و سرور اجازه میدهد با استفاده از یک زبان مشترک و بدون نگرانی به انتقال دادهها و درخواستهای خود بپردازند.
پروتکل WebSocket
پروتکل HTTP جهت مواردی مانند دانلود صفحههای وب یا آپلود عکس بسیار مناسب میباشد. ولی برای هربار انتقال داده میان سرور و کلاینت نیاز به شروع یک ارتباط TCP با سرور میباشد. با استفاده از پروتکل WebSocket ارتباط TCP میان سرور و کلاینت به صورت پیوسته برقرار میماند و در هر زمانی که نیاز به انتقال داده وجود داشته باشد به سرعت میتوانید این کار را انجام دهید.
اتصال برد Wemos Lolin32 (OLED) به Wi-Fi
ماژول ESP میتواند در سه حالت مختلف کار کند : 1. Wi-Fi station 2. Wi-Fi access point و 3. حالت 1 و 2 به صورت همزمان.
حالت Wi-Fi station
کد WiFi Station
#include <ESP8266WiFi.h> // Include the Wi-Fi library
const char* ssid = “SSID”; // The SSID (name) of the Wi-Fi network you want to connect to
const char* password = “PASSWORD”; // The password of the Wi-Fi network
void setup() {
Serial.begin(115200); // Start the Serial communication to send messages to the computer
delay(10);
Serial.println(‘\n’);
WiFi.begin(ssid, password); // Connect to the network Serial.print(“Connecting to “);
Serial.print(ssid);
Serial.println(” …”);
int i = 0;
while (WiFi.status() != WL_CONNECTED)
{ // Wait for the Wi-Fi to connect
delay(1000);
Serial.print(++i);
Serial.print(‘ ‘); }
Serial.println(‘\n’);
Serial.println(“Connection established!”);
Serial.print(“IP address:\t”);
Serial.println(WiFi.localIP()); // Send the IP address of the ESP8266 to the computer
}
void loop() { }
با استفاده از کد بالا میتوانید به یک Access point بیسیم در یک شبکه داخلی متصل شوید. در واقع ماژول ESP تبدیل به یکی از کلاینتهای موجود در شبکه میشود.
توابع مورد نیاز برای اتصال به شبکه Wi-Fi در کتابخانه ESP8266WiFi.h قابل دسترس میباشد. با استفاده از تابع WiFi.begin(ssid, password) به Access Point با نام ssid و پسورد password متصل میشوید. همچنین ()WiFi.status وضعیت اتصال را نشان میدهد. درنهایت با باز کردن پنچره Serial Monitor در محیط Arduino میتوانید IP گرفته شده توسط ماژول ESP را نیز مشاهده نمایید.
حالت Access Point
ماژول ESP قابلیت تنظیم شدن به صورت Access Point را نیز دارد. در این حالت، امکان اتصال کلاینتهای دیگر به ESP فراهم میباشد.
کد حالت Access Point
#include <ESP8266WiFi.h> // Include the Wi-Fi library
const char *ssid = “ESP8266 Access Point”; // The name of the Wi-Fi network that will be created
const char *password = “thereisnospoon”; // The password required to connect to it, leave blank for an open network
void setup()
{ Serial.begin(115200);
delay(10);
Serial.println(‘\n’);
WiFi.softAP(ssid, password); // Start the access point
Serial.print(“Access Point \””);
Serial.print(ssid);
Serial.println(“\” started”);
Serial.print(“IP address:\t”);
Serial.println(WiFi.softAPIP()); // Send the IP address of the ESP8266 to the computer
}
void loop() { }
در کد بالا با استفاده از تابع WiFi.softAP(ssid, password) ماژول را در حالت Access Point تنظیم مینماییم. متغییرهای ssid و password نیز به ترتیب نام و پسورد Access point مورد نظر را نشان میدهند.
راهاندازی Web Server بر روی ESP
کد Web Server
#include <ESP8266WiFi.h>
#include <WiFiClient.h>
#include <ESP8266WebServer.h> // Include the WebServer library
const char* ssid = “SSID”; // The SSID (name) of the Wi-Fi network you want to connect to
const char* password = “PASSWORD”; // The password of the Wi-Fi network
ESP8266WebServer server(80); // Create a webserver object that listens for HTTP request on port 80
void handleRoot(); // function prototypes for HTTP handlers
void handleNotFound();
void setup(void){
Serial.begin(115200); // Start the Serial communication to send messages to the computer
delay(10);
Serial.println(‘\n’);
WiFi.begin(ssid, password); // Connect to the network
Serial.print(“Connecting to “);
Serial.print(ssid); Serial.println(” …”);
int i = 0;
while (WiFi.status() != WL_CONNECTED) { // Wait for the Wi-Fi to connect
delay(1000);
Serial.print(++i); Serial.print(‘ ‘);
}
Serial.println(‘\n’);
Serial.println(“Connection established!”);
Serial.print(“IP address:\t”);
Serial.println(WiFi.localIP()); // Send the IP address of the ESP8266 to the computer
}
server.on(“/”, handleRoot); // Call the ‘handleRoot’ function when a client requests URI “/”
server.onNotFound(handleNotFound); // When a client requests an unknown URI (i.e. something other than “/”), call function “handleNotFound”
server.begin(); // Actually start the server
Serial.println(“HTTP server started”);
}
void loop(void){
server.handleClient(); // Listen for HTTP requests from clients
}
void handleRoot() {
server.send(200, “text/plain”, “Hello world!”); // Send HTTP status 200 (Ok) and send some text to the browser/client
}
void handleNotFound(){
server.send(404, “text/plain”, “404: Not found”); // Send HTTP status 404 (Not Found) when there’s no handler for the URI in the request
}
در کد بالا با استفاده از دستور ESP8266WebServer server(80) یک شی از کلاس webserver میسازیم [1]. با استفاده از این شی به درخواستهای HTTP ارسالی بر روی پورت 80 گوش میدهیم.
با استفاده از دستور ()server.handleClient به سرور اعلام میکنیم که باید درخواستهای HTTP ارسالی از کلاینتهای مختلف را دریافت کند. همچنین با استفاده از دستور ()server.on مشخص میکنیم که در صورت دریافت هر درخواست با مشخصات معلوم باید چه تابعی صدا زده شود.
در نتیجه دستور server.on(“/”, handleRoot) به معنی این است که زمانی که سرور درخواست URI با مقدار “/” را دریافت کرد، تابع handleRoot صدا زده شود. یا دستور server.onNotFound(handleNotFound) مشخص میکند که زمانی که کلاینت درخواست نامشخصی داشت، تابع handleNotFound صدا زده شود.
دستور ()server.send در توابع ()handleRoot و ()handleNotFound، داده ارسالی به کلاینت را مشخص میکند. به عنوان مثال در دستور server.send(200, “text/plain”, “Hello world!”)، پارامتر اول مقدار HTTP Response Code، پارامتر دوم نوع محتوی و پارامتر سوم محتوی ارسالی را مشخص میکند.
نمایش ضربان قلب و اکسیژن خون با استفاده از WebSocket
در این بخش با استفاده از کد زیر مقادیر ضربان قلب و اکسیژن خون توسط حسگر خوانده شده و با استفاده از پروتکل WebSocket، بدون تاخیر به کلاینت ارسال میشوند.
کد نمایش ضربان قلب و اکسیژن خون با استفاده از WebSocket
#include <WiFi.h>
#include <WebServer.h>
#include <DFRobot_MAX30102.h>
#include <WebSocketsServer.h>
DFRobot_MAX30102 particleSensor;
/*Put your SSID & Password*/
const char* ssid = “PORTAL”; // Enter SSID here
const char* password = “goodlife”; //Enter Password here
long sensorUpdateFrequency = 4000;
long timeNow = 0;
long timePrev = 0;
WebServer server(80);
WebSocketsServer webSocket = WebSocketsServer(81);
void onBeatDetected()
{
Serial.println(“Beat!”);
}
void setup() {
Serial.begin(115200);
Wire.begin(5, 4);
//pinMode(19, OUTPUT);
delay(100);
Serial.println(“Connecting to “);
Serial.println(ssid);
//connect to your local wi-fi network
WiFi.begin(ssid, password);
//check wi-fi is connected to wi-fi network
while (WiFi.status() != WL_CONNECTED) {
delay(1000);
Serial.print(“.”);
}
Serial.println(“”);
Serial.println(“WiFi connected..!”);
Serial.print(“Got IP: “); Serial.println(WiFi.localIP());
server.on(“/”, handle_OnConnect);
server.onNotFound(handle_NotFound);
server.begin();
Serial.println(“HTTP server started”);
webSocket.begin();
Serial.println(“websocket started”);
Serial.print(“Initializing pulse oximeter..”);
// Initialize sensor
if (!particleSensor.begin()) //Use default I2C port, 400kHz speed
{
Serial.println(“MAX30105 was not found. Please check wiring/power. “);
while (1);
}
particleSensor.sensorConfiguration(/*ledBrightness=*/50, /*sampleAverage=*/SAMPLEAVG_4, \
/*ledMode=*/MODE_MULTILED, /*sampleRate=*/SAMPLERATE_100, \
/*pulseWidth=*/PULSEWIDTH_411, /*adcRange=*/ADCRANGE_16384);
// Register a callback for the beat detection
}
int32_t SPO2; //SPO2
int8_t SPO2Valid; //Flag to display if SPO2 calculation is valid
int32_t heartRate; //Heart-rate
int8_t heartRateValid; //Flag to display if heart-rate calculation is valid
void loop() {
server.handleClient();
webSocket.loop();
timeNow = millis();
if (timeNow – timePrev >= sensorUpdateFrequency)
{
timePrev = timeNow;
// if it is time, call the updateSensors() function
updateSensors();
}
}
void updateSensors()
{
Serial.println(F(“Wait about four seconds”));
particleSensor.heartrateAndOxygenSaturation(/**SPO2=*/&SPO2, /**SPO2Valid=*/&SPO2Valid, /**heartRate=*/&heartRate, /**heartRateValid=*/&heartRateValid);
Serial.print(F(“heartRate=”));
Serial.print(heartRate, DEC);
Serial.print(F(“, heartRateValid=”));
Serial.print(heartRateValid, DEC);
Serial.print(F(“; SPO2=”));
Serial.print(SPO2, DEC);
Serial.print(F(“, SPO2Valid=”));
Serial.println(SPO2Valid, DEC);
//if any value is isnan (not a number) then there is an error
if (isnan(heartRate) || isnan(SPO2))
{
Serial.println(“Error reading from the DHT11.”);
}
else
{
String data = “”;
data = String(data + heartRate );
data = String(data + “|”);
data = String(data + SPO2);
webSocket.broadcastTXT(data); // send the data
Serial.println(data); // display the data in the serial monitor
}
} // void updateSensors()
void handle_OnConnect() {
server.send(200, “text/html”, SendHTML());
}
void handle_NotFound(){
server.send(404, “text/plain”, “Not found”);
}
String SendHTML(){
String ptr = R”=====(
<!DOCTYPE html>
<html>
<head>
<meta name=’viewport’ content=’width=device-width, initial-scale=1.0’/>
<meta charset=’utf-8′>
<style>
html { font-family: ‘Open Sans’, sans-serif; display: block; margin: 0px auto; text-align: center;color: #444444;}
body{margin: 0px;}
h1 {margin: 50px auto 30px;}
.side-by-side{display: table-cell;vertical-align: middle;position: relative;}
.text{font-weight: 600;font-size: 19px;width: 200px;}
.reading{font-weight: 300;font-size: 50px;padding-right: 25px;}
.BPM .reading{color: #FF0000;}
.SpO2 .reading{color: #955BA5;}
.superscript{font-size: 17px;font-weight: 600;position: absolute;top: 10px;}
.data{padding: 10px;}
.container{display: table;margin: 0 auto;}
.icon{width:65px}
h2 {
font-family: Arial;
font-size: 2.5rem;
text-align: center;
}
</style>
<title>ESP8266 Part 10</title>
</head>
<body>
<div id=’main’>
<h1>ESP32 Patient Health Monitoring</h1>
<div class=’container’>
<div class=’data Heart Rate’>
<div class=’side-by-side icon’>
<svg enable-background=’new 0 0 40.542 40.541’height=40.541px id=Layer_1 version=1.1 viewBox=’0 0 40.542 40.541’width=40.542px x=0px xml:space=preserve xmlns=http://www.w3.org/2000/svg xmlns:xlink=http://www.w3.org/1999/xlink y=0px><g><path d=’M34.313,20.271c0-0.552,0.447-1,1-1h5.178c-0.236-4.841-2.163-9.228-5.214-12.593l-3.425,3.424c-0.195,0.195-0.451,0.293-0.707,0.293s-0.512-0.098-0.707-0.293c-0.391-0.391-0.391-1.023,0-1.414l3.425-3.424c-3.375-3.059-7.776-4.987-12.634-5.215c0.015,0.067,0.041,0.13,0.041,0.202v4.687c0,0.552-0.447,1-1,1s-1-0.448-1-1V0.25c0-0.071,0.026-0.134,0.041-0.202C14.39,0.279,9.936,2.256,6.544,5.385l3.576,3.577c0.391,0.391,0.391,1.024,0,1.414c-0.195,0.195-0.451,0.293-0.707,0.293s-0.512-0.098-0.707-0.293L5.142,6.812c-2.98,3.348-4.858,7.682-5.092,12.459h4.804c0.552,0,1,0.448,1,1s-0.448,1-1,1H0.05c0.525,10.728,9.362,19.271,20.22,19.271c10.857,0,19.696-8.543,20.22-19.271h-5.178C34.76,21.271,34.313,20.823,34.313,20.271z M23.084,22.037c-0.559,1.561-2.274,2.372-3.833,1.814c-1.561-0.557-2.373-2.272-1.815-3.833c0.372-1.041,1.263-1.737,2.277-1.928L25.2,7.202L22.497,19.05C23.196,19.843,23.464,20.973,23.084,22.037z’fill=#26B999 /></g></svg>
</div>
<div class=’side-by-side text’>Heart Rate</div>
<div class=’side-by-side reading’>
<p><span id=’heartRate’>00</span></p>
<span class=’superscript’>BPM</span></div>
</div>
<div class=’data Blood Oxygen’>
<div class=’side-by-side icon’>
<svg enable-background=’new 0 0 58.422 40.639’height=40.639px id=Layer_1 version=1.1 viewBox=’0 0 58.422 40.639’width=58.422px x=0px xml:space=preserve xmlns=http://www.w3.org/2000/svg xmlns:xlink=http://www.w3.org/1999/xlink y=0px><g><path d=’M58.203,37.754l0.007-0.004L42.09,9.935l-0.001,0.001c-0.356-0.543-0.969-0.902-1.667-0.902c-0.655,0-1.231,0.32-1.595,0.808l-0.011-0.007l-0.039,0.067c-0.021,0.03-0.035,0.063-0.054,0.094L22.78,37.692l0.008,0.004c-0.149,0.28-0.242,0.594-0.242,0.934c0,1.102,0.894,1.995,1.994,1.995v0.015h31.888c1.101,0,1.994-0.893,1.994-1.994C58.422,38.323,58.339,38.024,58.203,37.754z’fill=#955BA5 /><path d=’M19.704,38.674l-0.013-0.004l13.544-23.522L25.13,1.156l-0.002,0.001C24.671,0.459,23.885,0,22.985,0c-0.84,0-1.582,0.41-2.051,1.038l-0.016-0.01L20.87,1.114c-0.025,0.039-0.046,0.082-0.068,0.124L0.299,36.851l0.013,0.004C0.117,37.215,0,37.62,0,38.059c0,1.412,1.147,2.565,2.565,2.565v0.015h16.989c-0.091-0.256-0.149-0.526-0.149-0.813C19.405,39.407,19.518,39.019,19.704,38.674z’fill=#955BA5 /></g></svg>
</div>
<div class=’side-by-side text’>Blood Oxygen</div>
<div class=’side-by-side reading’>
<p><span id=’SPO2′>00</span></p>
<span class=’superscript’>%</span></div>
</div>
<p>Data = <span id=’recData’>00</span></p>
</div>
<br />
</div>
</body>
<script>
var Socket;
function init()
{
Socket = new WebSocket(‘ws://’ + window.location.hostname + ‘:81/’);
Socket.onmessage = function(event) { processReceivedCommand(event); };
}
function processReceivedCommand(evt)
{
var data = evt.data;
document.getElementById(‘recData’).innerHTML = data;
var tmp = data.split(‘|’);
document.getElementById(‘heartRate’).innerHTML = tmp[0];
document.getElementById(‘SPO2’).innerHTML = tmp[1];
}
window.onload = function(e) { init(); }
</script>
</html>
)=====”;
return ptr;
}
در این کد با استفاده از کتابخانه DFRobot_MAX30102.h اطلاعات حسگر Max30102 خوانده میشود. جهت اطلاعات بیشتر در مورد نحوه کارکرد این حسگر به این پست مراجعه نمایید.
برای کار با پروتکل WebSocket از کتابخانه WebSocketsServer.h استفاده میکنیم. دستور WebSocketsServer webSocket(81) یک سرور وب سوکت روی پورت 81 ایجاد میکند و با استفاده از دستور ()webSocket.begin سرور وب سوکت شروع به کار میکند.
با استفاده از دستور webSocket.broadcastTXT(data) مقدار متغیر data از طریق پروتکل webSocket به وسیله سرور به تمام کلاینتهای متصل ارسال میگردد. همچنین، دستور ()webSocket.loop رخداد رویداد وب سوکت را به صورت مداوم (با هر بار اجرای حلقه loop) بررسی میکند.
جهت تکمیل ارتباط وب سوکت بین کلاینت و سرور باید در کلاینت نیز کد مربوط به وب سوکت نوشته شود. برای این امر از جاوا اسکریپت استفاده مینماییم.
با استفاده از دستور Socket = new WebSocket(‘ws://’ + window.location.hostname + ‘:81/’) یک شی websocket جدید با نام Socket ساخته میشود. پارامتر این متد سازنده، آدرس سرور وب سوکت را مشخص مینماید. با استفاده از دستور Socket.onmessage = function(event) { processReceivedCommand(event); } مشخص میکنیم که در صورت دریافت یک پیام وب سوکت از سرور، تابع processReceivedCommand(event) فراخوانی شود. پارامتر event از این تابع دارای محتوی پیام دریافتی میباشد.
در تابع processReceivedCommand(evt) مقادیر پیام دریافتی وب سوکت از سرور با استفاده از دستور
var data = evt.data
در متغییر data قرار گرفته و با استفاده از دستور
document.getElementById(‘recData’).innerHTML = data
محتوی المان <p> با ID با مقدار recData در کد html، با مقدار data جایگزین میگردد.
درنهایت بدین صورت مقادیر خوانده شده از حسگر بدون تاخیر بر روی مرورگر کلاینت نمایش داده میشود.
منابع
[1] “ESP8266 Web Server,” [Online]. Available: https://github.com/esp8266/Arduino/blob/master/libraries/ESP8266WebServer/README.rst. [Accessed 25 04 2021].