上篇文章,介绍了OK3568作为TCP服务端,通过WIFI控制家中电器的开关。
本篇,继续为OK3568作为TCP服务端增加新的功能:通过接收ESP8266客户端传来的家中温湿度数据,在Qt界面中进行实时展示。
温湿度传感器采用DHT11数字温湿度传感器,通过单总线与ESP8266相连,ESP8266采集到温湿度数据后,通过WIFI传送给OK3568开发板,OK3568开发板进行温湿度的展示,包括实时温湿度的展示与历史数据曲线的展示,先来看下最终的展示效果:
1 温湿度传感器
1.1 基础介绍
DHT11是一款有已校准数字信号输出的[温湿度传感器。 该传感器包括一个电阻式测湿元件和一个 NTC测温元件,并与一个高性能 8 位单片机相连接。通过单片机等微处理器简单的电路连接就能够实时的采集本地湿度和温度。
- 湿度精度±5%RH
- 温度精度±2℃
- 湿度量程5~95%RH
- 温度量程-20~+60℃
DHT11 与单片机之间能采用简单的单总线进行通信,仅仅需要一个 I/O 口。传感器内部湿度和温度数据 40Bit 的数据一次性传给单片机,数据采用校验和方式进行校验,有效的保证数据传输的准确性。DHT11 功耗很低,5V 电源电压下,工作平均最大电流 0.5mA。
1.2 数据协议
微控制器与 DHT11之间的通讯和同步,采用单总线数据格式,一次通讯时间4ms左右。
用户MCU发送一次开始信号后,DHT11从低功耗模式转换到高速模式,等待主机开始信号结束后,DHT11发送响应信号,送出40bit的数据,并触发一次信号采集,用户可选择读取部分数据。从模式下,DHT11接收到开始信号触发一次温湿度采集,如果没有接收到主机发送开始信号,DHT11不会主动进行温湿度采集。采集数据后转换到低速模式。
1.1.1 起始信号
总线空闲状态为高电平,主机把总线拉低等待DHT11响应,主机把总线拉低必须大于18毫秒,保证DHT11能检测到起始信号。DHT11接收到主机的开始信号后,等待主机开始信号结束,然后发送80us低电平响应信号。主机发送开始信号结束后,延时等待20-40us后,读取DHT11的响应信号,主机发送开始信号后,可以切换到输入模式,或者输出高电平均可,总线由上拉电阻拉高。
1.1.2 数据数字信号
总线为低电平,说明DHT11发送响应信号,DHT11发送响应信号后,再把总线拉高80us,准备发送数据,每一bit数据都以50us低电平时隙开始,高电平的长短定了数据位是0还是1。格式见下面图示.如果读取响应信号为高电平,则DHT11没有响应,请检查线路是否连接正常。当最后一bit数据传送完毕后,DHT11拉低总线50us,随后总线由上拉电阻拉高进入空闲状态。
1.1.3 温湿度数据格式
一次完整的数据传输为40bit,高位先出。数据分小数部分和整数部分,数据格式:
- 8bit湿度整数数据
- 8bit湿度小数数据
- 8bit温度整数数据
- 8bit温度小数数据
- 8bit校验和
数据传送正确时校验和数据等于“ 8bit 湿度整数数据 +8bit 湿度小数数据+8bit温度整数数据 +8bit 温度小数数据 ”所得结果的末8位。
2 服务端代码编写
2.1 项目工程
2.2 温湿度数据的接收与解析
OK3568的TCP服务端接收到温湿度数据后,先对数据进行解析,再将解析的结果通过Qt的信号传递给不同的显示界面。
使用Qt信号功能,需要在类中声明Q_OBJECT宏定义,并继承QObject类,然后添加一个自定义的信号sendTempHumi
class TCPServer : public QObject
{
Q_OBJECT
public:
TCPServer();
~TCPServer(){};
void CreateTcpServer();
static TCPServer &getInstance()
{
static TCPServer instance;
return instance;
}
private:
bool EpollSetfdAEvent(int epollfd, int op, int fd, int event);
void TcpServerThread();
std::thread m_tcpServerThrd;
int m_esp8266fd = -1;
signals:
void sendTempHumi(float temp, float humi);
};
TCP服务端接收到信号后,对字符串进行分割解析,然后发送信号给各个Qt界面:
printf("[%s] read message from fd:%d ---> %s\n", __func__, fd, buf);
std::vector<std::string> splitStr;
cstr_split(buf, splitStr);
if (splitStr.size() == 3)
{
if (splitStr[0] == "dht11")
{
float temp = atof(splitStr[1].c_str());
float humi = atof(splitStr[2].c_str());
emit sendTempHumi(temp, humi);
}
}
2.3 当前温湿度界面
温湿度数据获取后,显示当前的温湿度数据:
DisplayRealData::DisplayRealData(QWidget *parent) : QtWidgetBase(parent)
{
this->setAttribute(Qt::WA_TranslucentBackground);
m_pixmap = QPixmap(":/images/dht/dht_background.png");
connect(&TCPServer::getInstance(), SIGNAL(sendTempHumi(float, float)), this, SLOT(recvTempHumi(float, float)));
}
void DisplayRealData::paintEvent(QPaintEvent *)
{
QPainter painter(this);
painter.setRenderHints(QPainter::Antialiasing | QPainter::SmoothPixmapTransform);
painter.scale(this->width() * 1.0 / Skin::m_nScreenWidth, this->height() * 1.0 / 343);
painter.drawPixmap(0, 0, m_pixmap);
drawValues(&painter);
}
void DisplayRealData::drawValues(QPainter *painter)
{
QFont font(Skin::m_strAppFontBold);
font.setPixelSize(60);
painter->setFont(font);
painter->setPen(Qt::white);
painter->drawPixmap(440, 62, QPixmap(":/images/dht/temperature.png"));
painter->drawPixmap(440, 187, QPixmap(":/images/dht/humidity.png"));
QString strText = QString::number(m_fTemp ,'f',1);
QRect rectTemp(550, 57, 218, 100);
painter->drawText(rectTemp, Qt::AlignLeft | Qt::AlignVCenter, strText);
strText = QString::number(m_fHumi ,'f',1);
QRect rectHumidity(rectTemp.left(), 180, 218, 100);
painter->drawText(rectHumidity, Qt::AlignLeft | Qt::AlignVCenter, strText);
font.setPixelSize(55);
painter->setFont(font);
int nTextWidth = getTextWidthByFont(painter->font(), "℃");
int nTextHeight = painter->fontMetrics().height();
painter->drawText(rectTemp.right() - nTextWidth, rectTemp.bottom() - nTextHeight, nTextHeight, nTextHeight,
Qt::AlignCenter, QString("℃"));
painter->drawText(rectHumidity.right() - nTextWidth, rectHumidity.bottom() - nTextHeight, nTextHeight, nTextHeight,
Qt::AlignCenter, QString("%"));
}
void DisplayRealData::recvTempHumi(float temp, float humi)
{
m_fTemp = temp;
m_fHumi = humi;
update();
}
2.4 温湿度曲线界面
温湿度数据,除了显示当前的数据,还有一个温湿度曲线界面,可以显示历史的温湿度数据。
DisplayRecordData::DisplayRecordData(QWidget *parent) : QWidget(parent)
{
this->setAttribute(Qt::WA_TranslucentBackground);
InitWidget();
connect(&TCPServer::getInstance(), SIGNAL(sendTempHumi(float, float)), this, SLOT(recvTempHumi(float, float)));
}
void DisplayRecordData::InitWidget()
{
QWidget *widgetBtn = new QWidget(this);
widgetBtn->setObjectName("widgetBtn");
widgetBtn->setFixedSize(66, 155);
widgetBtn->setStyleSheet(QString("#widgetBtn {background-color: #03303c; border: none; border-radius: 33px;}"
"QPushButton {font-family: '%1';font: bold 24px; color: #ffffff;border: none; "
"border-radius: 30px;background-color: none;}"
"QPushButton:checked{background-color: #2698f8;}")
.arg(Skin::m_strAppFontNormal));
QVBoxLayout *verLayoutBtns = new QVBoxLayout(widgetBtn);
verLayoutBtns->setContentsMargins(3, 3, 3, 3);
QButtonGroup *btnGroup = new QButtonGroup(this);
QPushButton *btnTemp = new QPushButton(widgetBtn);
btnGroup->addButton(btnTemp, 0);
btnTemp->setCheckable(true);
btnTemp->setChecked(true);
btnTemp->setText(tr("温度"));
btnTemp->setFixedSize(60, 60);
verLayoutBtns->addWidget(btnTemp);
verLayoutBtns->addStretch();
QPushButton *btnHumidity = new QPushButton(widgetBtn);
btnGroup->addButton(btnHumidity, 1);
btnHumidity->setCheckable(true);
btnHumidity->setText(tr("湿度"));
btnHumidity->setFixedSize(60, 60);
verLayoutBtns->addWidget(btnHumidity);
connect(btnGroup, SIGNAL(buttonClicked(int)), this, SLOT(SltChangePage(int)));
m_stackedWidget = new QtStackedWidget(this);
m_stackedWidget->SetBackground(Qt::transparent);
m_stackedWidget->setPressMove(false);
m_customPlotTemp = new QtCustomPlot(m_stackedWidget);
m_customPlotTemp->setBackgroundColor(Qt::transparent);
m_customPlotTemp->setYStep(5);
m_customPlotTemp->setMaxValue(45);
m_customPlotHumidity = new QtCustomPlot(m_stackedWidget);
m_customPlotHumidity->setBackgroundColor(Qt::transparent);
m_customPlotHumidity->setLabels(tr("时间/S"), tr("湿度/%"));
m_stackedWidget->addWidget(0, m_customPlotTemp);
m_stackedWidget->addWidget(1, m_customPlotHumidity);
QHBoxLayout *horLayoutALL = new QHBoxLayout(this);
horLayoutALL->setContentsMargins(10, 5, 10, 5);
horLayoutALL->setSpacing(30);
horLayoutALL->addWidget(widgetBtn);
horLayoutALL->addWidget(m_stackedWidget, 1);
}
void DisplayRecordData::recvTempHumi(float temp, float humi)
{
m_customPlotTemp->addData(temp);
m_customPlotHumidity->addData(humi);
}
温度曲线界面
湿度曲线界面
3 客户端代码编写
客户端ESP8266程序使用的Arduino IDE进行编程,主代码如下,主要代码逻辑是:
- 连接wifi
- 创建一个温湿度传感器对象
- 循环读取温湿度数据
- 将温湿度数据组成字符串
- 通过TCP socket发送给OK3568
#include <ESP8266WiFi.h>
#include "dht11.h"
#include <string>
const char* ssid = "xxxxx"; //<
const char* password = "xxxxx"; //<
const uint16_t port = 6666;
const char *host = "192.168.5.182";
WiFiClient esp8266Client;
#define DHT11_PIN 13
DHT11 dht11(DHT11_PIN);
void setup()
{
Serial.begin(115200);
WiFi.mode(WIFI_STA);
WiFi.begin(ssid, password);
delay(500);
Serial.print("连接到:");
Serial.println(ssid);
uint8_t i = 0;
while (WiFi.status() != WL_CONNECTED && i++ < 20)
{
delay(500);
}
if (i == 21)
{
Serial.print("没能连接到:");
Serial.println(ssid);
return ;
}
Serial.print("准备好了!使用的网络IP是: ");
Serial.println(WiFi.localIP());
Serial.print("连接到 ");
Serial.println(host);
while (!esp8266Client.connect(host, port))
{
Serial.println("TCP server端连接失败");
Serial.println("请等待5秒后重新连接...");
delay(5000);
//return;
}
Serial.println("TCP server端连接成功");
}
bool read_dht11(float &temp, float &humi)
{
int ret = dht11.read();
if(ret == DHTLIB_ERROR_CHECKSUM) {
Serial.println("(E) Checksum failed");
return false;
}
else if(ret == DHTLIB_ERROR_TIMEOUT) {
Serial.println("(E) Read time out");
return false;
}
temp = dht11.getTemperature();
humi = dht11.getHumidity();
char tmp[256];
sprintf(tmp,"Temperature:%.1f, Humidity:%.1f", temp, humi);
Serial.print(tmp);
Serial.println();
return true;
}
void loop()
{
delay(2000);
float temp;
float humi;
if (read_dht11(temp, humi))
{
//向Tcp server发送数据
char msg[256];
sprintf(msg,"dht11 %.1f %.1f", temp, humi);
Serial.print("发送数据:");
Serial.print(msg);
Serial.println();
esp8266Client.print(msg);
}
}
4 测试
4.1 硬件连接
OK3568开发板连接HDMI显示器。
ESP8266连接一个DHT11温湿度传感器。
两个板子连接同一wifi后,建立TCP通信,然后就可以在OK3568的Qt界面上,显示ESP8266发来的温湿度数据了。
4.2 运行测试
下面这个是命令行的运行结果:可以看到OK3568先建立了一个TCP服务端,然后循环接收到了ESP8266客户端发来的温湿度数据:
Qt界面展示效果:
5 总结
本篇介绍了利用TCP无线通信,实现家中温湿度数据的获取与展示。