本文将在前面mini2440建立的domoticz、mosquitto为服务器的基础上,以Android做远程客户端来实现对STM32芯片的开发板的LED进行灯控制,当然,这是一个基本功能的实现,如果长期使用的话,还需要做很多其他的工作(例如断网重连,重连多少次重启系统等等,还要有个可以在SD卡上读入的硬件配置信息等等)。
首先展示一下开发板这边情况,硬件方面是在2011年买的“金牛开发板”,主控芯片是STM32F107VC,PHY芯片是DP83848,烧固件的工具是随开发板带来的Jlink,从JTAG口烧录进去。
软件方面,使用RTThread-2.1.0作为操作系统,该系统自带移植的嵌入式TCP/IP协议栈LwIP。
本文中使用的LwIP协议栈版本是1.4.1,是rtthread默认bsp中默认的版本。
bsp工程文件位置:rt-thread\bsp\stm32f107\project.uvproj
bsp已经实现了PHY芯片DP83848的驱动,所以就省了很多事。
使用的集成开发环境是:MDK 4.11 版本
本人在工程中是用了对c99的支持(主要是为了调试)。
本来看介绍,还以为RTThread-2.1.0已经移植好了MQTT客户端程序,但实际上,只是把代码放过来,还没有做什么移植。
所以,要实现本文的目标,就需要对MQTT客户端做个移植。
mqtt的客户端程序在rtthread软件包的位置是:
rt-thread\components\external\paho-mqtt
不知道这个是什么版本的,总之,本人是用在mini2440上已经验证过的版本,就是官方在GitHub上的最新版本,封包解包的那部分用MQTTPacket,可以不用改动,直接就用。
MQTTClient-C这个模块部分,用自己的实现。
放到rt-thread\components\external\paho-mqtt\MQTTClient-C\samples\domoticz目录下。
主要实现有三个部分:
1、对socket的读写的基本操作简单封装。相关内容放到MQTTRTThread.h和MQTTRTThread.c中。
2、对MQTT连接和消息的解析详细过程。相关内容放到MQTTClient.h和MQTTClient.c中。
3、对整个过程的控制,使用rtthread的一个线程实现。相关内容放到DomoticzThread.h和DomoticzThread.c中。
源码放在后面附上。
接下来要说的内容就是把曾在mini2440上用过的domoticz消息解析框架移植过来。
果然keil的arm编译器跟GCC是不同的,要做一些改动。改好的源码在后面附上。
这一部分放在rt-thread\bsp\stm32f107\applications目录下。
在STM32开发板上,也控制了两个LED作为例程。
效果跟mini2440上的是一样的,以后有时间再上图吧。
后面有时间把整个工程打包上传上来。
以下是上述介绍的源码:
dprintf.h
#ifndef __DPRINTF_H__
#define __DPRINTF_H__
#ifdef __cplusplus
extern "C"{
#endif
#ifdef __DEBUG
#include <stdarg.h>
#include <string.h>
//just get file name without the path
static const char *getCurrentFileName(const char * strFileName)
{
const char *p = strrchr(strFileName,'\\');
return p==NULL?strFileName:++p;
}
#define dprintf(fmt,...) rt_kprintf("%s,line:%d,"fmt,getCurrentFileName(__FILE__),__LINE__,##__VA_ARGS__)
#else
#define dprintf(fmt,...)
#endif
#ifdef __cplusplus
}
#endif
#endif
一、MQTTClient-C部分:
1、MQTTRTThread.h
#include <rtthread.h>
//----------------------------------------------------------
typedef struct Timer
{
rt_tick_t end_tick;
} Timer;
void TimerInit(Timer*);
char TimerIsExpired(Timer*);
void TimerCountdownMS(Timer*, unsigned int);
void TimerCountdown(Timer*, unsigned int);
int TimerLeftMS(Timer*);
void DeleteTimer(Timer*);
//-------------------------------------------------------
typedef struct Network
{
int my_socket;
int (*mqttread) (struct Network*, unsigned char*, int, int);
int (*mqttwrite) (struct Network*, unsigned char*, int, int);
} Network;
void NetworkInit(Network*);
int NetworkConnect(Network*, char*, int);
void NetworkDisconnect(Network*);
MQTTRTThread.c
#include "MQTTRTThread.h"
#include <lwip/netdb.h>
#include <lwip/sockets.h>
#include <stdio.h>
//#define __DEBUG
#include "dprintf.h"
void TimerInit(Timer* timer)
{
timer->end_tick = 0;
}
char TimerIsExpired(Timer* timer)
{
return timer->end_tick <= rt_tick_get();
}
void TimerCountdownMS(Timer* timer, unsigned int timeout)
{
timer->end_tick = rt_tick_get() + timeout*RT_TICK_PER_SECOND/1000;
}
void TimerCountdown(Timer* timer, unsigned int timeout)
{
timer->end_tick = rt_tick_get() + timeout*RT_TICK_PER_SECOND;
}
int TimerLeftMS(Timer* timer)
{
return timer->end_tick - rt_tick_get() ;
}
int RTThreadLwIP_read(Network* n, unsigned char* buffer, int len, int timeout_ms)
{
int bytes = 0;
struct timeval interval ;
interval.tv_sec = timeout_ms / 1000;
interval.tv_usec = (timeout_ms % 1000) * 1000;
if (interval.tv_sec < 0 || (interval.tv_sec == 0 && interval.tv_usec <= 0))
{
interval.tv_sec = 0;
interval.tv_usec = 100;
}
lwip_setsockopt(n->my_socket, SOL_SOCKET, SO_RCVTIMEO, (char *)&interval, sizeof(struct timeval));
while (bytes < len)
{
int rc = recv(n->my_socket, &buffer[bytes], (size_t)(len - bytes), MSG_DONTWAIT);
if (rc == -1)
{
//dprintf("errno=%d\n",errno);
//if (errno != ENOTCONN && errno != ECONNRESET)
if (errno != ETIMEDOUT && errno != ECONNRESET)
{
bytes = -1;
}
break;
}
else if (rc == 0)
{
bytes = 0;
break;
}
else
bytes += rc;
}
return bytes;
}
int RTThreadLwIP_write(Network* n, unsigned char* buffer, int len, int timeout_ms)
{
int rc;
struct timeval tv;
tv.tv_sec = timeout_ms / 1000;
tv.tv_usec = (timeout_ms % 1000) * 1000;
if (tv.tv_sec < 0 || (tv.tv_sec == 0 && tv.tv_usec <= 0))
{
tv.tv_sec = 0;
tv.tv_usec = 100;
}
lwip_setsockopt(n->my_socket, SOL_SOCKET, SO_SNDTIMEO, (void *)&tv,sizeof(struct timeval));
rc = lwip_write(n->my_socket, buffer, len);
return rc;
}
void NetworkInit(Network* n)
{
n->my_socket = 0;
n->mqttread = RTThreadLwIP_read;
n->mqttwrite = RTThreadLwIP_write;
}
int NetworkConnect(Network* n, char* addr, int port)
{
struct hostent *host;
struct in_addr ip_addr;
struct sockaddr_in sockaddr;
int rc = -1;
// 第一步 DNS地址解析
rt_kprintf("calling gethostbyname with: %s\r\n", addr);
host = gethostbyname(addr);
ip_addr.s_addr = *(unsigned long *) host->h_addr_list[0];
rt_kprintf("MQTTThread IP Address:%s\r\n" , inet_ntoa(ip_addr));
sockaddr.sin_family = AF_INET;
sockaddr.sin_port = htons(port);
sockaddr.sin_addr = ip_addr;
rt_memset(&(sockaddr.sin_zero), 0, sizeof(sockaddr.sin_zero));
// 第二步 创建套接字
n->my_socket = socket(AF_INET, SOCK_STREAM, 0);
if (n->my_socket != -1)
{
rt_kprintf("n->my_socket: %d\r\n", n->my_socket);
rc = lwip_connect(n->my_socket, (struct sockaddr*)&sockaddr, sizeof(struct sockaddr));
if (rc == -1)
{
rt_kprintf("Connect fail!\n");
lwip_close(n->my_socket);
//rt_free(recv_data);
}
else
{
rt_kprintf("Connect success!\n");
}
}
return rc;
}
void NetworkDisconnect(Network* n)
{
lwip_close(n->my_socket);
}
2、MQTTClient.h
/*******************************************************************************
* Copyright (c) 2014, 2017 IBM Corp.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* and Eclipse Distribution License v1.0 which accompany this distribution.
*
* The Eclipse Public License is available at
* http://www.eclipse.org/legal/epl-v10.html
* and the Eclipse Distribution License is available at
* http://www.eclipse.org/org/documents/edl-v10.php.
*
* Contributors:
* Allan Stockdill-Mander/Ian Craggs - initial API and implementation and/or initial documentation
* Ian Craggs - documentation and platform specific header
* Ian Craggs - add setMessageHandler function
*******************************************************************************/
#if !defined(MQTT_CLIENT_H)
#define MQTT_CLIENT_H
#if defined(__cplusplus)
extern "C" {
#endif
#if defined(WIN32_DLL) || defined(WIN64_DLL)
#define DLLImport __declspec(dllimport)
#define DLLExport __declspec(dllexport)
#elif defined(LINUX_SO)
#define DLLImport extern
#define DLLExport __attribute__ ((visibility ("default")))
#else
#define DLLImport
#define DLLExport
#endif
#include "MQTTPacket.h"
#include "MQTTRTThread.h"
#include "stdio.h"
#if defined(MQTTCLIENT_PLATFORM_HEADER)
/* The following sequence of macros converts the MQTTCLIENT_PLATFORM_HEADER value
* into a string constant suitable for use with include.
*/
#define xstr(s) str(s)
#define str(s) #s
#include xstr(MQTTCLIENT_PLATFORM_HEADER)
#endif
#define MAX_PACKET_ID 65535 /* according to the MQTT specification - do not change! */
#if !defined(MAX_MESSAGE_HANDLERS)
#define MAX_MESSAGE_HANDLERS 5 /* redefinable - how many subscriptions do you want? */
#endif
enum QoS {
QOS0, QOS1, QOS2, SUBFAIL=0x80 };
/* all failure return codes must be negative */
enum returnCode {
BUFFER_OVERFLOW = -2, FAILURE = -1, SUCCESS = 0 };
/* The Platform specific header must define the Network and Timer structures and functions
* which operate on them.
*
typedef struct Network
{
int (*mqttread)(Network*, unsigned char* read_buffer, int, int);
int (*mqttwrite)(Network*, unsigned char* send_buffer, int, int);
} Network;*/
/* The Timer structure must be defined in the platform specific header,
* and have the following functions to operate on it. */
extern void TimerInit(Timer*);
extern char TimerIsExpired(Timer*);
extern void TimerCountdownMS(Timer*, unsigned int);
extern void TimerCountdown(Timer*, unsigned int);
extern int TimerLeftMS(Timer*);
typedef struct MQTTMessage
{
enum QoS qos;
unsigned char retained;
unsigned char dup;
unsigned short id;
void *payload;
size_t payloadlen;
} MQTTMessage;
typedef struct MessageData
{
MQTTMessage* message;
MQTTString* topicName;
} MessageData;
typedef struct MQTTConnackData
{
unsigned char rc;
unsigned char sessionPresent;
} MQTTConnackData;
typedef struct MQTTSubackData
{
enum QoS grantedQoS;
} MQTTSubackData;
typedef void (*messageHandler)(MessageData*);
typedef struct MQTTClient
{
unsigned int next_packetid,
command_timeout_ms;
size_t buf_size,
readbuf_size;
unsigned char *buf,
*readbuf;
unsigned int keepAliveInterval;
char ping_outstanding;
int ping_timeout_times;
int isconnected;
int cleansession;
struct MessageHandlers
{
const char* topicFilter;
void (*fp) (MessageData*);
} messageHandlers[MAX_MESSAGE_HANDLERS]; /* Message handlers are indexed by subscription topic */
void (*defaultMessageHandler) (MessageData*);
Network* ipstack;
Timer last_sent, last_received;
#if defined(MQTT_TASK)
Mutex mutex;
Thread thread;
#endif
} MQTTClient;
#define DefaultClient {0, 0, 0, 0, NULL, NULL, 0, 0, 0}
/**
* Create an MQTT client object
* @param client
* @param network
* @param command_timeout_ms
* @param
*/
DLLExport void MQTTClientInit(MQTTClient* client, Network* network, unsigned int command_timeout_ms,
unsigned char* sendbuf, size_t sendbuf_size, unsigned char* readbuf, size_t readbuf_size);
/** MQTT Connect - send an MQTT connect packet down the network and wait for a Connack
* The nework object must be connected to the network endpoint before calling this
* @param options - connect options
* @return success code
*/
DLLExport int MQTTConnectWithResults(MQTTClient* client, MQTTPacket_connectData* options,
MQTTConnackData* data);
/** MQTT Connect - send an MQTT connect packet down the network and wait for a Connack
* The nework object must be connected to the network endpoint before calling this
* @param options - connect options
* @return success code
*/
DLLExport int MQTTConnect(MQTTClient* client, MQTTPacket_connectData* options);
/** MQTT Publish - send an MQTT publish packet and wait for all acks to complete for all QoSs
* @param client - the client object to use
* @param topic - the topic to publish to
* @param message - the message to send
* @return success code
*/
DLLExport int MQTTPublish(MQTTClient* client, const char*, MQTTMessage*);
/** MQTT SetMessageHandler - set or remove a per topic message handler
* @param client - the client object to use
* @param topicFilter - the topic filter set the message handler for
* @param messageHandler - pointer to the message handler function or NULL to remove
* @return success code
*/
DLLExport int MQTTSetMessageHandler(MQTTClient* c, const char* topicFilter, messageHandler messageHandler);
/** MQTT Subscribe - send an MQTT subscribe packet and wait for suback before returning.
* @param client - the client object to use
* @param topicFilter - the topic filter to subscribe to
* @param message - the message to send
* @return success code
*/
DLLExport int MQTTSubscribe(MQTTClient* client, const char* topicFilter, enum QoS, messageHandler);
/** MQTT Subscribe - send an MQTT subscribe packet and wait for suback before returning.
* @param client - the client object to use
* @param topicFilter - the topic filter to subscribe to
* @param message - the message to send
* @param data - suback granted QoS returned
* @return success code
*/
DLLExport int MQTTSubscribeWithResults(MQTTClient* client, const char* topicFilter, enum QoS, messageHandler, MQTTSubackData* data);
/** MQTT Subscribe - send an MQTT unsubscribe packet and wait for unsuback before returning.
* @param client - the client object to use
* @param topicFilter - the topic filter to unsubscribe from
* @return success code
*/
DLLExport int MQTTUnsubscribe(MQTTClient* client, const char* topicFilter);
/** MQTT Disconnect - send an MQTT disconnect packet and close the connection
* @param client - the client object to use
* @return success code
*/
DLLExport int MQTTDisconnect(MQTTClient* client);
/** MQTT Yield - MQTT background
* @param client - the client object to use
* @param time - the time, in milliseconds, to yield for
* @return success code
*/
DLLExport int MQTTYield(MQTTClient* client, int time);
/** MQTT isConnected
* @param client - the client object to use
* @return truth value indicating whether the client is connected to the server
*/
DLLExport int MQTTIsConnected(MQTTClient* client);
DLLExport int keepalive(MQTTClient* client);
void MQTTCleanSession(MQTTClient* c);
#if defined(MQTT_TASK)
/** MQTT start background thread for a client. After this, MQTTYield should not be called.
* @param client - the client object to use
* @return success code
*/
DLLExport int MQTTStartTask(MQTTClient* client);
#endif
#if defined(__cplusplus)
}
#endif
#endif
MQTTClient.c
/*******************************************************************************
* Copyright (c) 2014, 2017 IBM Corp.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* and Eclipse Distribution License v1.0 which accompany this distribution.
*
* The Eclipse Public License is available at
* http://www.eclipse.org/legal/epl-v10.html
* and the Eclipse Distribution License is available at
* http://www.eclipse.org/org/documents/edl-v10.php.
*
* Contributors:
* Allan Stockdill-Mander/Ian Craggs - initial API and implementation and/or initial documentation
* Ian Craggs - fix for #96 - check rem_len in readPacket
* Ian Craggs - add ability to set message handler separately #6
*******************************************************************************/
#include "MQTTClient.h"
#include <rtthread.h>
#include <lwip/sockets.h>
//#define __DEBUG
#include "dprintf.h"
static void NewMessageData(MessageData* md, MQTTString* aTopicName, MQTTMessage* aMessage) {
md->topicName = aTopicName;
md->message = aMessage;
}
static int getNextPacketId(MQTTClient *c) {
return c->next_packetid = (c->next_packetid == MAX_PACKET_ID) ? 1 : c->next_packetid + 1;
}
static int sendPacket(MQTTClient* c, int length, Timer* timer)
{
int rc = FAILURE,
sent = 0;
while (sent < length && !TimerIsExpired(timer))
{
rc = c->ipstack->mqttwrite(c->ipstack, &c->buf[sent], length, TimerLeftMS(timer));
if (rc < 0) // there was an error writing the data
{
dprintf("error!!!!\n");
break;
}
sent += rc;
}
if (sent == length)
{
TimerCountdown(&c->last_sent, c->keepAliveInterval); // record the fact that we have successfully sent the packet
rc = SUCCESS;
}
else
rc = FAILURE;
return rc;
}
void MQTTClientInit(MQTTClient* c, Network* network, unsigned int command_timeout_ms,
unsigned char* sendbuf, size_t sendbuf_size, unsigned char* readbuf, size_t readbuf_size)
{
int i;
c->ipstack = network;
for (i = 0; i < MAX_MESSAGE_HANDLERS; ++i)
c->messageHandlers[i].topicFilter = 0;
c->command_timeout_ms = command_timeout_ms;
c->buf = sendbuf;
c->buf_size = sendbuf_size;
c->readbuf = readbuf;
c->readbuf_size = readbuf_size;
c->isconnected = 0;
c->cleansession = 0;
c->ping_outstanding = 0;
c->ping_timeout_times = 0;
c->defaultMessageHandler = NULL;
c->next_packetid = 1;
TimerInit(&c->last_sent);
TimerInit(&c->last_received);
#if defined(MQTT_TASK)
MutexInit(&c->mutex);
#endif
}
static int decodePacket(MQTTClient* c, int* value, int timeout)
{
unsigned char i;
int multiplier = 1;
int len = 0;
const int MAX_NO_OF_REMAINING_LENGTH_BYTES = 4;
*value = 0;
do
{
int rc = MQTTPACKET_READ_ERROR;
if (++len > MAX_NO_OF_REMAINING_LENGTH_BYTES)
{
rc = MQTTPACKET_READ_ERROR; /* bad data */
goto exit;
}
rc = c->ipstack->mqttread(c->ipstack, &i, 1, timeout);
if (rc != 1)
goto exit;
*value += (i & 127) * multiplier;
multiplier *= 128;
} while ((i & 128) != 0);
exit:
return len;
}
static int readPacket(MQTTClient* c, Timer* timer)
{
MQTTHeader header = {
0};
int len = 0;
int rem_len = 0;
/* 1. read the header byte. This has the packet type in it */
int rc = c->ipstack->mqttread(c->ipstack, c->readbuf, 1, TimerLeftMS(timer));
if (rc != 1)
goto exit;
len = 1;
/* 2. read the remaining length. This is variable in itself */
decodePacket(c, &rem_len, TimerLeftMS(timer));
len += MQTTPacket_encode(c->readbuf + 1, rem_len); /* put the original remaining length back into the buffer */
if (rem_len > (c->readbuf_size - len))
{
rc = BUFFER_OVERFLOW;
goto exit;
}
/* 3. read the rest of the buffer using a callback to supply the rest of the data */