我司BQ3568-HM开发板与3861开发板实现TCP通讯
BQ3568-HM 上 TCP代码如下:let tcp = socket.constructTCPSocketInstance();
//绑定BQ3568-HM
tcp.bind({address: '192.168.2.104', port: 3861, family: 1}, err => {
if (err) {
console.log('bind fail');
return;
}
console.log('bind success');
})
//hi3861绑定地址
tcp.connect({ address: { address: '192.168.2.166', port: 3861, family: 1 }, timeout: 6000 }, () => {
console.log('connect success');
tcp.send({
data: 'led on'
//此处省略encoding, 默认为utf-8编码格式
}, err => {
if (err) {
console.log('send fail'+err);
return;
}
console.log('send success');
})
})
hi3861代码如下:
#include <stdio.h>
#include <unistd.h>
#include "ohos_init.h"
#include "cmsis_os2.h"
#include "lwip/sockets.h"
#include "wifi_connect.h"
#define _PROT_ 3861
#define TCP_BACKLOG 10
//在sock_fd 进行监听,在 new_fd 接收新的链接
int sock_fd, new_fd;
char recvbuf;
// char *buf = "Hello! I'm BearPi-HM_Nano TCP Server!";
char *buf = "已连接!";
static void TCPServerTask(void)
{
//服务端地址信息
struct sockaddr_in server_sock;
//客户端地址信息
struct sockaddr_in client_sock;
int sin_size;
struct sockaddr_in *cli_addr;
//连接指定Wifi
// WifiConnect("black4s", "88888888");
WifiConnect("TP-LINK_E976", "18060992926");
//创建socket
if ((sock_fd = socket(AF_INET, SOCK_STREAM, 0)) == -1)
{
perror("socket is error\r\n");
exit(1);
}
bzero(&server_sock, sizeof(server_sock));
server_sock.sin_family = AF_INET;
server_sock.sin_addr.s_addr = htonl(INADDR_ANY);
server_sock.sin_port = htons(_PROT_);
//调用bind函数绑定socket和地址
if (bind(sock_fd, (struct sockaddr *)&server_sock, sizeof(struct sockaddr)) == -1)
{
perror("bind is error\r\n");
exit(1);
}
// 调用listen函数监听(指定port监听)
if (listen(sock_fd, TCP_BACKLOG) == -1)
{
perror("listen is error\r\n");
exit(1);
}
printf("start accept\n");
// 调用accept函数从队列中
while (1)
{
sin_size = sizeof(struct sockaddr_in);
if ((new_fd = accept(sock_fd, (struct sockaddr *)&client_sock, (socklen_t *)&sin_size)) == -1)
{
perror("accept");
continue;
}
cli_addr = malloc(sizeof(struct sockaddr));
printf("accept addr\r\n");
if (cli_addr != NULL)
{
memcpy(cli_addr, &client_sock, sizeof(struct sockaddr));
}
//处理目标
ssize_t ret;
while (1)
{
bzero(recvbuf, sizeof(recvbuf));
if ((ret = recv(new_fd, recvbuf, sizeof(recvbuf), 0))== -1)
{
printf("recv error \r\n");
}
if(strcmp(recvbuf,"led on")==0)
{
printf("led on !!!!!");
};
printf("recv :%s\r\n", recvbuf);
if ((ret = send(new_fd, buf, strlen(buf) + 1, 0)) == -1)
{
perror("send : ");
}
}
close(new_fd);
}
}
static void TCPServerDemo(void)
{
osThreadAttr_t attr;
attr.name = "TCPServerTask";
attr.attr_bits = 0U;
attr.cb_mem = NULL;
attr.cb_size = 0U;
attr.stack_mem = NULL;
attr.stack_size = 10240;
attr.priority = osPriorityNormal;
if (osThreadNew((osThreadFunc_t)TCPServerTask, NULL, &attr) == NULL)
{
printf(" Falied to create UDPServerTask!\n");
}
}
APP_FEATURE_INIT(TCPServerDemo);
注意hi3861收发代码中;
//处理目标
ssize_t ret;
while (1)
{
bzero(recvbuf, sizeof(recvbuf));
if ((ret = recv(new_fd, recvbuf, sizeof(recvbuf), 0))== -1)
{
printf("recv error \r\n");
}
if(strcmp(recvbuf,"led on")==0)
{
printf("led on !!!!!");
};
printf("recv :%s\r\n", recvbuf);
// if ((ret = send(new_fd, buf, strlen(buf) + 1, 0)) == -1)
// {
// perror("send : ");
// }
}
要把send注释掉,因为没有在客户端进行数据接受,会有阻塞,收到不到下次传递的数值 现问题,在BQ3568-HM上发送数据到hi3861上时,第一次可以发送成功,第二次日志显示成功但服务端串口不显示 问题解决,现可以实现hi3861与BQ3568-HM双向TCP传输。
之前问题:在BQ3568-HM客户端处,点击发送事件后创立一个TCP,绑定本机ip端口,连接hi3861IP与端口,发送数据。源代码每点击一次会重复上述步骤,经过调试工具测试BQ3568-HM,发现每次点击端口会变化,第一次发送成功后之后的端口变化hi3861无法接受后续数据。所以应该在生命周期开始时只创建一次TCP且绑定一次,端口才不会变化。BQ3568-HM代码修改后如下(包含一部分UI界面):
@Entry
@Component
struct taideng {
@State imageBgColorA: number = 0
@State ledon : boolean = false
@StorageProp('currentBreakpoint') currentBreakpoint: string = 'sm'
tcp = socket.constructTCPSocketInstance();
aboutToAppear(){
this.tcp.bind({address: '192.168.2.104', port: 3861, family: 1}, err => {
if (err) {
console.log('bind fail');
return;
}
console.log('bind success');
})
}
build() {
Scroll() {
Column() {
PageTitle()
Stack({ alignContent: Alignment.BottomStart }) {
Image(this.ledon?$r("app.media.icon_fraction_lamp_onlineoff"):$r("app.media.icon_fraction_lamp_online"))
.backgroundColor(`rgba(255, 255, 255, ${this.imageBgColorA})`)
.objectFit(ImageFit.Contain)
}
.height(this.currentBreakpoint == 'lg' ? 166 : 280)
List(){
ListItem(){
Column(){
Button({ type: ButtonType.Normal,stateEffect:false }){
Row(){
Text(this.ledon?"已打开":"已关闭"){
}
.fontSize(25)
.margin({ left: 15,right:15 })
Blank(80)
Image(this.ledon?$r("app.media.icon_switch_on"):$r("app.media.icon_switch_off"))
.objectFit(ImageFit.Contain)
.onClick(()=>{
if(this.ledon){
this.tcp.connect({ address: { address: '192.168.2.150', port: 3861, family: 1 }, timeout: 6000 }, () => {
// 电脑ip
// tcp.connect({ address: { address: '192.168.2.125', port: 3861, family: 1 }, timeout: 6000 }, () => {
console.log('connect success');
this.tcp.send({
data: 'Hello, server!'
//此处省略encoding, 默认为utf-8编码格式
}, err => {
if (err) {
console.log('send fail'+JSON.stringify(err));
return;
}
console.log('send success');
})
})
/*sendLEDOFF()*/
}
else{
this.tcp.connect({ address: { address: '192.168.2.150', port: 3861, family: 1 }, timeout: 6000 }, () => {
console.log('connect success');
this.tcp.send({
data : 'Hello'
//此处省略encoding, 默认为utf-8编码格式
}, err => {
if (err) {
console.log('send fail'+JSON.stringify(err));
return;
}
console.log('send success');
})
})
/* sendLEDON()*/
}
this.ledon=!this.ledon
})
}
} .borderRadius(8)
.backgroundColor('#ffd4c9c9')
.width('90%')
.height('10%')
.margin({ left: 15,right:15 })
}
}
}
}
.alignItems(HorizontalAlign.Center)
}
.backgroundColor('#EDF2F5')
.height('100%')
.align(Alignment.Top)
}
}
实现了点灯功能:当BQ3568-HM发送数据led on 时判断成功则GPIO灯亮起,led off 时GPIO灯灭。
/*
* Copyright (c) 2020 Nanjing Xiaoxiongpai Intelligent Technology Co., Ltd.
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include <stdio.h>
#include <unistd.h>
#include "wifiiot_gpio.h"
#include "wifiiot_gpio_ex.h"
#include "ohos_init.h"
#include "cmsis_os2.h"
#include "lwip/sockets.h"
#include "wifi_connect.h"
#define _PROT_ 3861
#define TCP_BACKLOG 10
// 在sock_fd 进行监听,在 new_fd 接收新的链接
int sock_fd, new_fd;
char recvbuf;
// char *buf = "Hello! I'm BearPi-HM_Nano TCP Server!";
char *buf = "send the message";
static void TCPServerTask(void)
{
GpioInit();
// 设置GPIO_2的复用功能为普通GPIO
IoSetFunc(WIFI_IOT_IO_NAME_GPIO_2, WIFI_IOT_IO_FUNC_GPIO_2_GPIO);
// 设置GPIO_2为输出模式
GpioSetDir(WIFI_IOT_GPIO_IDX_2, WIFI_IOT_GPIO_DIR_OUT);
// 服务端地址信息
struct sockaddr_in server_sock;
// 客户端地址信息
struct sockaddr_in client_sock;
int sin_size;
struct sockaddr_in *cli_addr;
// 连接Wifi
//WifiConnect("black4s", "88888888");
WifiConnect("TP-LINK_E976", "18060992926");
// 创建socket
if ((sock_fd = socket(AF_INET, SOCK_STREAM, 0)) == -1)
{
perror("socket is error\r\n");
exit(1);
}
bzero(&server_sock, sizeof(server_sock));
server_sock.sin_family = AF_INET;
server_sock.sin_addr.s_addr = htonl(INADDR_ANY);
server_sock.sin_port = htons(_PROT_);
// 调用bind函数绑定socket和地址
if (bind(sock_fd, (struct sockaddr *)&server_sock, sizeof(struct sockaddr)) == -1)
{
perror("bind is error\r\n");
exit(1);
}
// 调用listen函数监听(指定port监听)
if (listen(sock_fd, TCP_BACKLOG) == -1)
{
perror("listen is error\r\n");
exit(1);
}
printf("start accept\n");
// 调用accept函数从队列中
while (1)
{
sin_size = sizeof(struct sockaddr_in);
if ((new_fd = accept(sock_fd, (struct sockaddr *)&client_sock, (socklen_t *)&sin_size)) == -1)
{
perror("accept");
continue;
}
cli_addr = malloc(sizeof(struct sockaddr));
printf("accept addr\r\n");
if (cli_addr != NULL)
{
memcpy(cli_addr, &client_sock, sizeof(struct sockaddr));
}
// 处理目标
ssize_t ret;
while (1)
{
if ((ret = recv(new_fd, recvbuf, sizeof(recvbuf), 0)) == -1)
{
printf("recv error \r\n");
}
if (strcmp(recvbuf, "led on") == 0)
{
printf("led on !!!!!");
// 设置GPIO_2输出高电平点亮LED灯
GpioSetOutputVal(WIFI_IOT_GPIO_IDX_2, 1);
bzero(recvbuf, sizeof(recvbuf));
};
if (strcmp(recvbuf, "led off") == 0)
{
printf("led off !!!!!");
// 设置GPIO_2输出低电平点亮LED灯
GpioSetOutputVal(WIFI_IOT_GPIO_IDX_2, 0);
bzero(recvbuf, sizeof(recvbuf));
};
if ((ret = send(new_fd, buf, strlen(buf), 0)) == -1)
{
perror("send : ");
}
}
close(new_fd);
}
}
static void TCPServerDemo(void)
{
osThreadAttr_t attr;
attr.name = "TCPServerTask";
attr.attr_bits = 0U;
attr.cb_mem = NULL;
attr.cb_size = 0U;
attr.stack_mem = NULL;
attr.stack_size = 10240;
attr.priority = osPriorityNormal;
if (osThreadNew((osThreadFunc_t)TCPServerTask, NULL, &attr) == NULL)
{
printf(" Falied to create UDPServerTask!\n");
}
}
APP_FEATURE_INIT(TCPServerDemo);
不愧是你{:3_59:} 今日问题:进入台灯页面后,绑定连接TCP传输数据成功,但若想跳转另一界面,需要重新创建TCP连接,hi3861的ip不会改变,但TCP端口会被占用,不能再次声明创立一个TCP连接,所以需要创立一个全局声明变量 globalThis.tcp = socket.constructTCPSocketInstance();在MainAbility.ts的onCreate()函数中,当app被创建时,创建一个全局TCP连接,这样就不会再次创建TCP连接使端口被占用,也可以在页面随意跳转。
MainAbility.ts:
import Ability from '@ohos.application.Ability'
import socket from '@ohos.net.socket';
export default class MainAbility extends Ability {
onCreate(want, launchParam) {
console.log(" MainAbility onCreate")
globalThis.abilityWant = want;
/ /添加全局TCP定义
globalThis.tcp = socket.constructTCPSocketInstance();
}
onDestroy() {
console.log(" MainAbility onDestroy")
}
onWindowStageCreate(windowStage) {
// Main window is created, set main page for this ability
console.log(" MainAbility onWindowStageCreate")
windowStage.loadContent("pages/index", (err, data) => {
if (err.code) {
console.error('Failed to load the content. Cause:' + JSON.stringify(err));
return;
}
console.info('Succeeded in loading the content. Data: ' + JSON.stringify(data))
});
}
onWindowStageDestroy() {
// Main window is destroyed, release UI related resources
console.log(" MainAbility onWindowStageDestroy")
}
onForeground() {
// Ability has brought to foreground
console.log(" MainAbility onForeground")
}
onBackground() {
// Ability has back to background
console.log(" MainAbility onBackground")
}
};
补充:添加E53_IA1模块,令电机转动,实现窗帘的开关,TCP导入相应模块实现代码:
要导入在build.gn中导入E53_IA1.c
// 窗帘开启 语句与执行
if (strcmp(recvbuf, "chuanglian on") == 0)
{
E53_IA1_Init();
E53_IA1_Read_Data();
Motor_StatusSet(ON);
sleep(5);
Motor_StatusSet(OFF);
bzero(recvbuf, sizeof(recvbuf));
};
// 窗帘关闭 语句与执行
if (strcmp(recvbuf, "chuanglian off") == 0)
{
E53_IA1_Init();
E53_IA1_Read_Data();
Motor_StatusSet(ON);
sleep(5);
Motor_StatusSet(OFF);
bzero(recvbuf, sizeof(recvbuf));
};
今日问题:BQ3568-HM通过TCP接收hi3861温湿度的值,并解决遇到问题。
此处进入温湿度获取界面,想实现进入界面温湿度自动上报并同步到界面。跳过UI搭建界面。使用.onAppear(()=>{}挂载事件向TCP发送获取温湿度事件,hi3861获取指令发送温湿度。此处温湿度定义为flout,上报值为char,使用强转换:sprintf(data1,"%0.2f",E53_IA1_Data.Temperature);
具体hi3861代码为:
// 温湿度 语句与执行
if (strcmp(recvbuf, "wendu") == 0)
{
E53_IA1_Init();
E53_IA1_Read_Data();
char data1 ;
sprintf(data1,"%0.2f",E53_IA1_Data.Temperature);
ret = send(new_fd, data1, strlen(data1), 0);
bzero(recvbuf, sizeof(recvbuf));
};
if (strcmp(recvbuf, "shidu") == 0)
{
E53_IA1_Init();
E53_IA1_Read_Data();
char data ;
sprintf(data,"%0.2f",E53_IA1_Data.Humidity);
ret = send(new_fd, data, strlen(data), 0);
bzero(recvbuf, sizeof(recvbuf));
};
if (strcmp(recvbuf, "jiashiqikai") == 0)
{
E53_IA1_Init();
E53_IA1_Read_Data();
Light_StatusSet(ON);
bzero(recvbuf, sizeof(recvbuf));
};
if (strcmp(recvbuf, "jiashiqiguan") == 0)
{
E53_IA1_Init();
E53_IA1_Read_Data();
Light_StatusSet(OFF);
bzero(recvbuf, sizeof(recvbuf));
}; 想实现BQ3568间隔1s获取一次值,则需要间隔1s向hi3861发送指令。此处使用定时器setTimeout()与setInterval(),前者延时s秒执行一次,后者间隔s秒重复执行,开始时都用重复执行,发现TCP传输会有阻塞,然后先用setTimeout()滞后湿度获取500ms,实现实时变化。
dev代码:
import router from '@ohos.router'
@Component
struct PageTitle {
build() {
Row() {
Image($r('app.media.back'))
.width(20)
.height(20)
.onClick(() => {
router.back()
})
Text("窗帘")
.fontSize(22)
.margin({ left: 20 })
}
.padding(12)
.width('100%')
}
}
@Component
struct FoodImageDisplay {
@State imageBgColorA: number = 0
@StorageProp('currentBreakpoint') currentBreakpoint: string = 'sm'
build() {
Stack({ alignContent: Alignment.BottomStart }) {
Image($r("app.media.icon_humidifier"))
.backgroundColor(`rgba(255, 255, 255, ${this.imageBgColorA})`)
.objectFit(ImageFit.Contain)
}
.height(this.currentBreakpoint == 'lg' ? 166 : 280)
}
}
@Component
struct oneTable {
@State message: string = ''
@State wendu: string = '0'
@State shidu: string = '0'
private timer: number = 0
private timer1: number = 0
private timer2: number = 0
@State j :number =0
@Builder IngredientItem() {
Column() {
Flex({ direction: FlexDirection.Column, justifyContent: FlexAlign.SpaceEvenly }) {
Column() {
Row() {
Column(){
Text("室内温度")
.fontSize(18)
.fontWeight(FontWeight.Bold)
.layoutWeight(1)
.align(Alignment.Start)
Blank()
Text(this.wendu)
.onAppear(()=>{
clearInterval(this.timer)
this.timer = setInterval(() => {
globalThis.tcp.connect({ address: globalThis.mixeraddr , timeout: 6000 }, () => {
// 电脑ip
// tcp.connect({ address: { address: '192.168.2.125', port: 3861, family: 1 }, timeout: 6000 }, () => {
console.log('connect success');
globalThis.tcp.send({
data: 'wendu'
//此处省略encoding, 默认为utf-8编码格式
}, err => {
if (err) {
console.log('send fail' + JSON.stringify(err));
return;
}
console.log('send success');
})
})
//接收数据:
globalThis.tcp.on('message', value => {
console.log("on message")
let buffer = value.message
let dataView = new DataView(buffer)
let str = ""
for (let i = 0; i < dataView.byteLength; ++i) {
str += String.fromCharCode(dataView.getUint8(i))
}
console.log("on connect received:" + str)
this.wendu = JSON.stringify(str)+ "℃"
});
}, 1500)
})
}
Blank(80)
Column(){
Text("室内湿度")
.fontSize(18)
.fontWeight(FontWeight.Bold)
.layoutWeight(1)
.align(Alignment.Start)
Blank()
Text(this.shidu)
.onAppear(()=>{
clearInterval(this.timer1)
this.timer1 = setTimeout(() => {
clearInterval(this.timer2)
this.timer2 = setInterval(() => {
globalThis.tcp.connect({ address: globalThis.mixeraddr , timeout: 6000 }, () => {
// 电脑ip
// tcp.connect({ address: { address: '192.168.2.125', port: 3861, family: 1 }, timeout: 6000 }, () => {
console.log('connect success');
globalThis.tcp.send({
data: 'shidu'
//此处省略encoding, 默认为utf-8编码格式
}, err => {
if (err) {
console.log('send fail' + JSON.stringify(err));
return;
}
console.log('send success');
})
})
//接收数据:
globalThis.tcp.on('message', value => {
console.log("on message")
let buffer = value.message
let dataView = new DataView(buffer)
let str = ""
for (let i = 0; i < dataView.byteLength; ++i) {
str += String.fromCharCode(dataView.getUint8(i))
}
console.log("on connect received:" + str)
this.shidu = JSON.stringify(str) + "%rh"
});
}, 1500)
}, 500)
})
.onDisAppear(()=>{
clearInterval(this.timer2);
clearInterval(this.timer);
})
}
}.height('40%')
Divider().strokeWidth(10).color(Color.White)
Text("温湿度加湿器")
.fontSize(18)
.fontWeight(FontWeight.Bold)
.layoutWeight(1)
.align(Alignment.Start)
.height('20%')
Divider().strokeWidth(10).color(Color.White)
Row() {
Button('开', { type: ButtonType.Normal, stateEffect: true })
.borderRadius(8)
.backgroundColor(0x317aff)
.width(90)
.onClick(() => {
this.message = '智能加湿器已打开'
globalThis.tcp.connect({ address: globalThis.mixeraddr , timeout: 6000 }, () => {
// 电脑ip
// tcp.connect({ address: { address: '192.168.2.125', port: 3861, family: 1 }, timeout: 6000 }, () => {
console.log('connect success');
globalThis.tcp.send({
data: 'jiashiqikai'
//此处省略encoding, 默认为utf-8编码格式
}, err => {
if (err) {
console.log('send fail' + JSON.stringify(err));
return;
}
console.log('send success');
})
})
})
Blank(20)
Button('关', { type: ButtonType.Normal, stateEffect: true })
.borderRadius(8)
.backgroundColor(0x317aff)
.width(90)
.onClick(() => {
this.message = '智能加湿器已关闭'
globalThis.tcp.connect({ address: globalThis.mixeraddr , timeout: 6000 }, () => {
// 电脑ip
// tcp.connect({ address: { address: '192.168.2.125', port: 3861, family: 1 }, timeout: 6000 }, () => {
console.log('connect success');
globalThis.tcp.send({
data: 'jiashiqiguan'
//此处省略encoding, 默认为utf-8编码格式
}, err => {
if (err) {
console.log('send fail' + JSON.stringify(err));
return;
}
console.log('send success');
})
})
})
}
.width('60%')
.layoutWeight(2)
.height('15%')
Divider().strokeWidth(10).color(Color.White)
Text(this.message)
.height('15%')
.fontSize(18)
.fontWeight(FontWeight.Bold)
.layoutWeight(1)
.align(Alignment.Start)
}
.height('50%')
}
}
.backgroundColor(Color.White)
.width('96%')
}
build() {
Column() {
this.IngredientItem()
}
}
}
@Preview
@Entry
@Component
struct fengshan {
build() {
Scroll() {
Column() {
PageTitle()
FoodImageDisplay()
Swiper() {
oneTable()
}
.clip(new Rect().width('100%').height('100%').radiusWidth(15).radiusHeight(15))
.itemSpace(20)
.height(380)
.indicatorStyle({ selectedColor: Color.Green })
.margin({ top: 10, right: 10, left: 10 })
}
.alignItems(HorizontalAlign.Center)
}
.backgroundColor('#EDF2F5')
.height('100%')
.align(Alignment.Top)
}
}