Arduino與墨子號 BC26 4G模組的對接開發
阿新 • • 發佈:2020-12-13
最近開始要做物聯網的一個裝置,由於是外用就考慮到了Arduino UNO小板跟4G模組,後面淘寶找到了一款已用的4G模組-墨子號BC26(注意:不是打廣告哦),由於4G模組都是At命令操作的,跟Arduino UNO對接使用有些不方便使用,所以封裝成了一個Arduino的c++類,很簡單,但是也遇到一些奇葩問題,發到csdn,做個備註!
我的c++基礎不是很好,有什麼地方可以優化記憶體的,歡迎各位大佬指出!
Audrion 主類:
#include "BC26Socket.h"
int socketId = 1;
int socketPort = 8888;
String socketHost = "47.92.146.210";
Socket socket;
SoftwareSerial socketSerial = SoftwareSerial(A0, A1); // arduino與bc26通訊的串列埠
boolean isCreateConnect = false; // 是否建立連線成功
boolean isConnect = false; // 是否連線到伺服器
boolean isConnectNetWork = false; // 是否連線到網路
long connectTime = 0L; // 開始連線時間
long lastSendHeartbeatTime = 0L; // 最後傳送心跳包時間
long lastHeartbeatTime = 0L; // 最後接收到心跳時間
void socketConnect(); // socket連成功監聽
void socketClose(); // socket斷開監聽
void socketMessage(String msg, int length); // socket訊息監聽
void sendHeartbeat(); // 傳送心跳檢測
void checkConnect(); // 檢測連線
void checkHeartbeat(); // 檢測心跳
const char* Heartbeat = "hb" ; // 心跳
void setup() {
Serial.begin(9600);
socketSerial.begin(9600);
socketSerial.setTimeout(100); // 設定每次讀取間隔時間 , socket採用readString方法進行讀取資料
socket.initSocket(socketId, socketHost, socketPort, &socketSerial);
socket.addSocketConnectListener(socketConnect);
socket.addSocketCloseListener(socketClose);
socket.addSocketMessageListener(socketMessage);
socket.setLocalPort(6666);
socket.seeIpAt();
}
void loop() {
if (!isConnectNetWork) {// 如果未連線到網路 繼續監測網路狀態
isConnectNetWork = socket.checkNetWork();
if (isConnectNetWork) {
Serial.println("netWork OK");
} else {
Serial.println("netWork Error");
delay(1000);
}
} else {
if (!isCreateConnect) { // 如果沒有成功建立socket
Serial.println("start connect");
socket.close();
socket.connect();
connectTime = millis();
isCreateConnect = true;
if (isCreateConnect) {
Serial.println("socket success");
} else {
Serial.println("socket error");
delay(1000);
}
} else {
socket.loopSocket();
checkConnect();
checkHeartbeat();
sendHeartbeat();
String command = Serial.readString();
if (!command.equals("")) {
Serial.println("client:");
Serial.println(command);
socket.send(command);
}
}
}
}
void sendHeartbeat() {
if (isConnect && millis() - lastSendHeartbeatTime >= 1000L * 30) { // 如果連線成功 並且30秒內沒有傳送心跳包
lastSendHeartbeatTime = millis();
socket.send(Heartbeat);
}
}
void checkHeartbeat() {
if (isConnect && millis() - lastHeartbeatTime > 1000L * 60) { // 如果一分鐘未接收到心跳,斷開重連
Serial.println("heartbeat time out");
socketClose();
}
}
// 檢測連線
void checkConnect() {
if (!isConnect && millis() - connectTime > 1000L * 10) { // 如果未連線成功 10秒等待檢測
Serial.println("time out");
socketClose();
}
}
// socket連成功監聽 該方法不知道為啥有些時候不會進入,可自行使用心跳機制完善
void socketConnect() {
isConnect = true;
lastHeartbeatTime = millis();
Serial.println("connect success");
}
// socket斷開監聽
void socketClose() {
isConnect = false;
isCreateConnect = false;
isConnectNetWork = false;
Serial.println("connect close");
}
// socket訊息監聽
void socketMessage(String msg, int length) {
Serial.print("server:");
Serial.println(length);
Serial.println(msg);
if (msg.length() == length) {
if (msg.equals(Heartbeat)) {
lastHeartbeatTime = millis();
}
}
}
4G BC26封裝.h檔案
#include <SoftwareSerial.h>
#include <Arduino.h>
class Socket {
public :
void initSocket(int socketId, String host, int port, SoftwareSerial *serial); // 初始化socket
void addSocketConnectListener(void(*socketConnectListener)()); // 新增socket連線成功回撥
void addSocketCloseListener(void(*socketCloseListener)()); // 新增socket斷開連接回調
void addSocketMessageListener(void(*socketMessageListener)(String,int)); // 新增socket訊息回撥
void setLocalPort(int localPort); // 設定socket本地接收埠
void useUDP(); // 設定使用udp
void useTCP(); // 設定使用tcp
void useIPV4(); // 使用ipv4
void useIPV6(); // 使用ipv6
void testAT(); // 測試AT指令
void seeIpAt(); // 檢視ip AT指令
void loopSocket(); // 輪詢socket訊息
boolean connect(); // 開始建立連線socket
boolean checkNetWork(); // 檢測是否連線到網路
boolean close(); // 關閉socket
boolean send(String msg); // 傳送訊息
private:
boolean connectBC26(); // 開始連線bc26
boolean analysisBC26(String *types, int typesLength); // 解析bc26訊息
void readBC26(); // 讀取bc26
void sendBC26(String command); // 傳送命令到bc26
private :
int mSocketId; // socket通道id
int mPort; // socket連線埠
int mLocalPort = 0; // socket本地接收埠 0:bc26自動選擇
int mAccessMode = 1; // 0:buff模式需要手動去讀取 1:push模式主動推送(現在主要使用該方式)
int mProtocolType = 0; // 0:IPV4 1:IPV6
int mReadLength = 0; // 讀取到的資料的有效長度
String mServiceType = String("TCP"); // 連線方式 "TCP","UDP"
String mHost; // socket連線地址
String mReadDoc[5]; //儲存讀取到的資料
SoftwareSerial *mSerial = NULL;
void(*connectListener)(); // 新增socket連線成功回撥
void(*closeListener)(); // 新增socket斷開連接回調
void(*messageListener)(String,int); // 新增socket訊息回撥
};
4G BC26封裝.cpp實現檔案
#include "BC26Socket.h"
// 初始化 socket 連線資訊
void Socket::initSocket(int socketId, String host, int port, SoftwareSerial *serial) {
mSocketId = socketId;
mHost = host;
mPort = port;
mSerial = serial;
}
// 新增socket連線成功回撥
void Socket::addSocketConnectListener(void(*socketConnectListener)()) {
connectListener = socketConnectListener;
}
// 新增socket斷開連接回調
void Socket::addSocketCloseListener(void(*socketCloseListener)()) {
closeListener = socketCloseListener;
}
// 新增socket訊息回撥
void Socket::addSocketMessageListener(void(*socketMessageListener)(String, int)) {
messageListener = socketMessageListener;
}
void Socket::setLocalPort(int localPort) {
mLocalPort = localPort;
}
void Socket::useUDP() {
mServiceType = String("TCP");
}
void Socket::useTCP() {
mServiceType = String("TCP");
}
void Socket::useIPV4() {
mProtocolType = 0;
}
void Socket::useIPV6() {
mProtocolType = 1;
}
void Socket::testAT() {
sendBC26("AT");
delay(300);
readBC26();
analysisBC26(NULL, 0);
}
void Socket::seeIpAt() {
sendBC26("AT+CGPADDR");
delay(300);
readBC26();
analysisBC26(NULL, 0);
}
// 輪詢socket訊息
void Socket::loopSocket() {
readBC26();
analysisBC26(NULL, 0);
}
// 檢測網路狀態
boolean Socket::checkNetWork() {
sendBC26("AT+CGATT?");
delay(300);
readBC26();
String types[] = {"+CGATT: 1"};
return analysisBC26(types, 1);
}
// 開始建立連線socket
boolean Socket::connect() {
if (mHost == NULL || mHost.length() == 0) {
Serial.println("error1");
return false;
}
if (mSerial == NULL) {
Serial.println("error2");
return false;
}
return connectBC26();
}
// 開始連線bc26
boolean Socket::connectBC26() {
String command = "AT+QIOPEN=1,";
command += mSocketId;
command += ",\"";
command += mServiceType;
command += "\",\"";
command += mHost;
command += "\",";
command += mPort;
command += ",";
command += mLocalPort;
command += ",";
command += mAccessMode;
command += ",";
command += mProtocolType;
sendBC26(command);
command = "";
delay(300);
readBC26();
String types[1] = {"OK"};
return analysisBC26(types, 1);
}
// 關閉socket
boolean Socket::close() {
String command = "AT+QICLOSE=";
command += mSocketId;
sendBC26(command);
command = "";
delay(300);
readBC26();
String types[1] = {"CLOSE OK"};
return analysisBC26(types, 1);
}
// 傳送訊息
boolean Socket::send(String msg) {
String command = "AT+QISEND=";
command += mSocketId;
command += ",";
command += msg.length();
command += ",";
command += msg;
sendBC26(command);
command = "";
delay(300);
readBC26();
String types[1] = {"SEND OK"};
return analysisBC26(types, 1);
}
// 傳送命令到bc26
void Socket::sendBC26(String command) {
mSerial->println(command);
// Serial.println("+ send:");
// Serial.println(command);
}
// 讀取bc26
void Socket::readBC26() {
String command2 = mSerial->readString();
mReadLength = 0;
if (!command2.equals("")) {
// Serial.println("返回資料:");
command2.replace("\r\n", "|");
// Serial.println(command2);
do {
int indexof = command2.indexOf("|");
if (indexof == -1) {
if (command2.length() > 0) {
mReadDoc[mReadLength] = command2;
mReadLength++;
command2 = "";
}
} else {
String str = command2.substring(0, indexof);
if (str.length() > 0) {
mReadDoc[mReadLength] = str;
mReadLength++;
str = "";
}
command2 = command2.substring(indexof + 1, command2.length());
}
} while (command2.length() > 0);
// command2 = "";
// if (mReadLength > 0) {
// Serial.print("- receive:");
// Serial.print(mReadLength);
// Serial.println("):");
// Serial.print("\t");
// for (int i = 0; i < mReadLength; i++) {
// Serial.print("|");
// Serial.print(mReadDoc[i]);
// }
// }
// Serial.println();
}
}
// 解析bc26訊息
boolean Socket::analysisBC26(String *types, int typesLen) {
int haveSize = 0;
for (int i = 0; i < mReadLength; i++) {
String type = mReadDoc[i];
for (int s = 0; s < typesLen; s++) {
String item = types[s];
if (type.equals(item)) {
haveSize++;
}
}
if (type.startsWith("+")) {
String types = type;
types.trim();
types.replace(" ", "");
// Serial.println("get+:" + types);
if (type.startsWith("+QIOPEN:")) { // 如果開頭是連線成功
// Serial.println("連線狀態更改:" + types);
String head = "+QIOPEN:";
String command1 = head + mSocketId + ",0";
String command2 = head + mSocketId + ",566";
if (types.equals(command1)) { // 連線成功 可進行通訊
connectListener();
} else if (types.equals(command2)) { // 連線超時
closeListener();
}
} else if (type.startsWith("+QIURC:")) { // 如果開頭是socket關閉
// Serial.println("BC23上報:" + types);
String command1 = "+QIURC:\"recv\"," ;
command1 += mSocketId;
command1 += ",";
String command2 = "+QIURC:\"closed\"," ;
command2 += mSocketId;
if (types.startsWith(command1)) { // scket接收到訊息
String lengthStr = "0";
if (type.lastIndexOf(",") != -1) {
lengthStr = type.substring(type.lastIndexOf(",") + 1, type.length());
}
int length = lengthStr.toInt();
String msg = "";
if (i + 1 < mReadLength) {
msg = mReadDoc[i + 1];
}
messageListener(msg, length);
} else if (types.equals(command2)) { // socket 關閉
closeListener();
}
}
}
}
return haveSize >= mReadLength;
}
需要注意一下,可能我c++學藝不精問題吧,大家使用的使用盡量讓Arduino可用動態記憶體大於50%,不然續寫4g資料跟解析資料的時候會造成字串丟失哦!
正式執行時,建議把除錯的log程式碼都註釋掉,這樣好像可以留出大部分的動態記憶體!