基于mini2440嵌入式linux上整合一套Domoticz智能家居系统(十)使用domoticz+mosquitto+Android客户端实现控制STM32板上的LED(一)
来源:互联网 发布:php redis 订阅发布 编辑:程序博客网 时间:2024/06/09 23:21
本文将在前面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上的是一样的,以后有时间再上图吧。
后面有时间把整个工程打包上传上来。
以下是上述介绍的源码:
一、MQTTClient-C部分:
1、MQTTRTThread.h
#include <rtthread.h> //----------------------------------------------------------typedef struct Timer{ int left_ms; rt_timer_t timer_ptr;} Timer;void TimerInit(Timer*);char TimerIsExpired(Timer*);void TimerCountdownMS(Timer*, int);void TimerCountdown(Timer*, 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#ifdef __DEBUG#include <stdarg.h>#include <string.h>static const char *getCurrentFileName(){ const char *p = strrchr(__FILE__,'\\'); return ++p;}#define dprintf(fmt,...) rt_kprintf("%s,line:%d,"fmt,getCurrentFileName(),__LINE__,##__VA_ARGS__)#else#define dprintf(fmt,...)#endif//int RTThreadLwIP_read(Network* n, unsigned char* buffer, int len, int timeout_ms);//int RTThreadLwIP_write(Network* n, unsigned char* buffer, int len, int timeout_ms);//因为10ms才调用一次void SetTimerTimeout(void* timer){ Timer* tmr=(Timer*)timer; if(tmr->left_ms<0) return; tmr->left_ms -= 10; //dprintf("left_ms=%d\n",tmr->left_ms);}void TimerInit(Timer* timer){ timer->left_ms = 0; timer->timer_ptr = 0;}char TimerIsExpired(Timer* timer){ //dprintf("timer->left_ms=%d\n",timer->left_ms); return timer->left_ms<=0?1:0;}void TimerCountdownMS(Timer* timer, int timeout){ static int i=0; if(timer->timer_ptr) { rt_timer_stop(timer->timer_ptr); } else { char str[16]={0}; sprintf(str,"ms%d",i++); timer->timer_ptr = rt_timer_create(str,SetTimerTimeout, timer,1,/* 定时长度为1个OS Tick 。一个tick是1/RT_TICK_PER_SECOND秒,默认是10ms*/ RT_TIMER_FLAG_PERIODIC); } timer->left_ms = timeout; if (timer->timer_ptr != RT_NULL) rt_timer_start(timer->timer_ptr); else { dprintf("err:timer->timer_ptr=NULL\n"); }}void TimerCountdown(Timer* timer, int timeout){ static int i=0; if(timer->timer_ptr) { rt_timer_stop(timer->timer_ptr); } else { char str[16]={0}; sprintf(str,"s%d",i++); timer->timer_ptr = rt_timer_create(str,SetTimerTimeout, timer,1,/* 定时长度为1个OS Tick 。一个tick是1/RT_TICK_PER_SECOND秒,默认是10ms*/ RT_TIMER_FLAG_PERIODIC); } timer->left_ms = timeout*1000; rt_timer_start(timer->timer_ptr);}int TimerLeftMS(Timer* timer){ return timer->left_ms;}void DeleteTimer(Timer* timer){ if(timer->timer_ptr) { rt_timer_delete(timer->timer_ptr); }}void tcp_time_out(void * data){ int * time = (int*)data; if(time && *time>0) { (*time) -= 10; }}int RTThreadLwIP_read(Network* n, unsigned char* buffer, int len, int timeout_ms){ int bytes = 0;#if 0 /* Commented @ 2017-Apr-26 1:23:16 */ 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; }#endif /* Commented */ int time_out = timeout_ms; static int i=0; char str[16]={0}; sprintf(str,"recv_timer_%d",i++); //dprintf("timeout_ms=%d\n",timeout_ms); //lwip_setsockopt(n->my_socket, SOL_SOCKET, SO_RCVTIMEO, (char *)&interval, sizeof(struct timeval)); lwip_setsockopt(n->my_socket, SOL_SOCKET, SO_RCVTIMEO, (char *)&time_out, sizeof(int)); rt_timer_t timer = rt_timer_create(str, tcp_time_out, &time_out, 1,RT_TIMER_FLAG_PERIODIC); rt_timer_start(timer); while (bytes < len) { int rc = lwip_recv(n->my_socket, &buffer[bytes], (size_t)(len - bytes), 0); if (rc == -1) { if (errno != ENOTCONN && errno != ECONNRESET) { bytes = -1; break; } } else if(time_out>0) { bytes += rc; } else { bytes = -1; break; } } rt_timer_delete(timer); return bytes;}int RTThreadLwIP_write(Network* n, unsigned char* buffer, int len, int timeout_ms){ int rc;#if 0 /* Commented by sunq @ 2017-Apr-27 1:06:05 */ struct timeval tv; tv.tv_sec = 0; /* 30 Secs Timeout */ tv.tv_usec = timeout_ms * 1000; // Not init'ing this can cause strange errors lwip_setsockopt(n->my_socket, SOL_SOCKET, SO_RCVTIMEO, (char *)&tv,sizeof(struct timeval));#endif /* Commented by sunq */ int time_out = timeout_ms; static int i=0; char str[16]={0}; sprintf(str,"write_timer_%d",i++); lwip_setsockopt(n->my_socket, SOL_SOCKET, SO_SNDTIMEO, (char *)&time_out, sizeof(int)); rt_timer_t timer = rt_timer_create(str, tcp_time_out, &time_out, 1,RT_TIMER_FLAG_PERIODIC); rt_timer_start(timer); rc = lwip_write(n->my_socket, buffer, len); rt_timer_delete(timer); 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, 2015 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 *******************************************************************************/#if !defined(__MQTT_CLIENT_C_)#define __MQTT_CLIENT_C_#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? */#endifenum QoS { QOS0, QOS1, QOS2 };/* 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*, int);extern void TimerCountdown(Timer*, 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 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 isconnected; 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 ping_timer;#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 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 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 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);#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, 2015 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 *******************************************************************************/#include "MQTTClient.h"#include <rtthread.h> //#define __DEBUG#ifdef __DEBUG#include <stdarg.h>#include <string.h>static const char *getCurrentFileName(){ const char *p = strrchr(__FILE__,'\\'); return ++p;}#define dprintf(fmt,...) rt_kprintf("%s,line:%d,"fmt,getCurrentFileName(),__LINE__,##__VA_ARGS__)#else#define dprintf(fmt,...)#endifstatic 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->ping_timer, 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->ping_outstanding = 0; c->defaultMessageHandler = NULL; c->next_packetid = 1; TimerInit(&c->ping_timer); dprintf("\n");}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){ int rc = FAILURE; MQTTHeader header = {0}; int len = 0; int rem_len = 0; /* 1. read the header byte. This has the packet type in it */ if (c->ipstack->mqttread(c->ipstack, c->readbuf, 1, TimerLeftMS(timer)) != 1) goto exit; len = 1; /* 2. read the remaining length. This is variable in itself */ decodePacket(c, &rem_len, TimerLeftMS(timer)); dprintf("data len ,,,, rem_len=%d\n",rem_len); len += MQTTPacket_encode(c->readbuf + 1, rem_len); /* put the original remaining length back into the buffer */ int data_real_len=0; /* 3. read the rest of the buffer using a callback to supply the rest of the data */ if (rem_len > 0 && ((data_real_len=c->ipstack->mqttread(c->ipstack, c->readbuf + len, rem_len, TimerLeftMS(timer))) != rem_len)) { goto exit; }#if 0 /* Commented @ 2017-Apr-25 1:34:59 */ else { dprintf("data len=%d ,,,, data_real_len=%d\n",rem_len,data_real_len); dprintf("c->readbuf=\n",c->readbuf); int i; for(i=0;i<data_real_len;i++) { rt_kprintf("%02X ,",c->readbuf[i]); if(i%10==9) rt_kprintf("\n"); } }#endif /* Commented */ header.byte = c->readbuf[0]; rc = header.bits.type;exit: return rc;}// assume topic filter and name is in correct format// # can only be at end// + and # can only be next to separatorstatic char isTopicMatched(char* topicFilter, MQTTString* topicName){ char* curf = topicFilter; char* curn = topicName->lenstring.data; char* curn_end = curn + topicName->lenstring.len; while (*curf && curn < curn_end) { if (*curn == '/' && *curf != '/') break; if (*curf != '+' && *curf != '#' && *curf != *curn) break; if (*curf == '+') { // skip until we meet the next separator, or end of string char* nextpos = curn + 1; while (nextpos < curn_end && *nextpos != '/') nextpos = ++curn + 1; } else if (*curf == '#') curn = curn_end - 1; // skip until end of string curf++; curn++; }; return (curn == curn_end) && (*curf == '\0');}int deliverMessage(MQTTClient* c, MQTTString* topicName, MQTTMessage* message){ int i; int rc = FAILURE; // we have to find the right message handler - indexed by topic for (i = 0; i < MAX_MESSAGE_HANDLERS; ++i) { if (c->messageHandlers[i].topicFilter != 0 && (MQTTPacket_equals(topicName, (char*)c->messageHandlers[i].topicFilter) || isTopicMatched((char*)c->messageHandlers[i].topicFilter, topicName))) { if (c->messageHandlers[i].fp != NULL) { MessageData md; NewMessageData(&md, topicName, message); c->messageHandlers[i].fp(&md); rc = SUCCESS; } } } if (rc == FAILURE && c->defaultMessageHandler != NULL) { MessageData md; NewMessageData(&md, topicName, message); c->defaultMessageHandler(&md); rc = SUCCESS; } return rc;}int keepalive(MQTTClient* c){ int rc = FAILURE; if (c->keepAliveInterval == 0) { rc = SUCCESS; goto exit; } if (TimerIsExpired(&c->ping_timer)) { if (!c->ping_outstanding) { Timer timer; int len; TimerInit(&timer); TimerCountdownMS(&timer, 1000); len = MQTTSerialize_pingreq(c->buf, c->buf_size); rc = sendPacket(c, len, &timer); if (len > 0 && rc == SUCCESS) // send the ping packet { c->ping_outstanding = 1; } else if(rc== FAILURE) { c->ping_outstanding = 1; } DeleteTimer(&timer); } }exit: return rc;}int cycle(MQTTClient* c, Timer* timer){ // read the socket, see what work is due unsigned short packet_type = readPacket(c, timer); int len = 0, rc = SUCCESS; switch (packet_type) { case CONNACK: case PUBACK: case SUBACK: break; case PUBLISH: { MQTTString topicName; MQTTMessage msg; int intQoS; if (MQTTDeserialize_publish(&msg.dup, &intQoS, &msg.retained, &msg.id, &topicName, (unsigned char**)&msg.payload, (int*)&msg.payloadlen, c->readbuf, c->readbuf_size) != 1) goto exit; msg.qos = (enum QoS)intQoS; deliverMessage(c, &topicName, &msg); if (msg.qos != QOS0) { if (msg.qos == QOS1) len = MQTTSerialize_ack(c->buf, c->buf_size, PUBACK, 0, msg.id); else if (msg.qos == QOS2) len = MQTTSerialize_ack(c->buf, c->buf_size, PUBREC, 0, msg.id); if (len <= 0) rc = FAILURE; else rc = sendPacket(c, len, timer); if (rc == FAILURE) goto exit; // there was a problem } break; } case PUBREC: { unsigned short mypacketid; unsigned char dup, type; if (MQTTDeserialize_ack(&type, &dup, &mypacketid, c->readbuf, c->readbuf_size) != 1) rc = FAILURE; else if ((len = MQTTSerialize_ack(c->buf, c->buf_size, PUBREL, 0, mypacketid)) <= 0) rc = FAILURE; else if ((rc = sendPacket(c, len, timer)) != SUCCESS) // send the PUBREL packet rc = FAILURE; // there was a problem if (rc == FAILURE) goto exit; // there was a problem break; } case PUBCOMP: break; case PINGRESP: c->ping_outstanding = 0; break; } keepalive(c); dprintf("\n"); exit: if (rc == SUCCESS) rc = packet_type; return rc;}int MQTTYield(MQTTClient* c, int timeout_ms){ int rc = SUCCESS; Timer timer; TimerInit(&timer); TimerCountdownMS(&timer, timeout_ms); //dprintf("c->readbuf=%p,c->readbuf_size=%u\n",c->readbuf,c->readbuf_size); do { if (cycle(c, &timer) == FAILURE) { rc = FAILURE; break; } } while (!TimerIsExpired(&timer)); DeleteTimer(&timer); return rc;}void MQTTRun(void* parm){ Timer timer; MQTTClient* c = (MQTTClient*)parm; TimerInit(&timer); while (1) {#if defined(MQTT_TASK) MutexLock(&c->mutex);#endif TimerCountdownMS(&timer, 500); /* Don't wait too long if no traffic is incoming */ cycle(c, &timer);#if defined(MQTT_TASK) MutexUnlock(&c->mutex);#endif } }#if defined(MQTT_TASK)int MQTTStartTask(MQTTClient* client){ return ThreadStart(&client->thread, &MQTTRun, client);}#endifint waitfor(MQTTClient* c, int packet_type, Timer* timer){ int rc = FAILURE; do { if (TimerIsExpired(timer)) break; // we timed out } while ((rc = cycle(c, timer)) != packet_type); return rc;}int MQTTConnect(MQTTClient* c, MQTTPacket_connectData* options){ Timer connect_timer; int rc = FAILURE; MQTTPacket_connectData default_options = MQTTPacket_connectData_initializer; int len = 0;#if defined(MQTT_TASK) MutexLock(&c->mutex);#endif if (c->isconnected) /* don't send connect packet again if we are already connected */ goto exit; TimerInit(&connect_timer); TimerCountdownMS(&connect_timer, c->command_timeout_ms); if (options == 0) options = &default_options; /* set default options if none were supplied */ c->keepAliveInterval = options->keepAliveInterval; TimerCountdown(&c->ping_timer, c->keepAliveInterval); if ((len = MQTTSerialize_connect(c->buf, c->buf_size, options)) <= 0) goto exit; if ((rc = sendPacket(c, len, &connect_timer)) != SUCCESS) // send the connect packet goto exit; // there was a problem // this will be a blocking call, wait for the connack if (waitfor(c, CONNACK, &connect_timer) == CONNACK) { unsigned char connack_rc = 255; unsigned char sessionPresent = 0; if (MQTTDeserialize_connack(&sessionPresent, &connack_rc, c->readbuf, c->readbuf_size) == 1) rc = connack_rc; else rc = FAILURE; } else rc = FAILURE;exit: if (rc == SUCCESS) c->isconnected = 1;#if defined(MQTT_TASK) MutexUnlock(&c->mutex);#endif DeleteTimer(&connect_timer); return rc;}int MQTTSubscribe(MQTTClient* c, const char* topicFilter, enum QoS qos, messageHandler messageHandler){ int rc = FAILURE; Timer timer; int len = 0; MQTTString topic = MQTTString_initializer; topic.cstring = (char *)topicFilter;#if defined(MQTT_TASK) MutexLock(&c->mutex);#endif if (!c->isconnected) goto exit; TimerInit(&timer); TimerCountdownMS(&timer, c->command_timeout_ms); len = MQTTSerialize_subscribe(c->buf, c->buf_size, 0, getNextPacketId(c), 1, &topic, (int*)&qos); if (len <= 0) goto exit; if ((rc = sendPacket(c, len, &timer)) != SUCCESS) // send the subscribe packet goto exit; // there was a problem if (waitfor(c, SUBACK, &timer) == SUBACK) // wait for suback { int count = 0, grantedQoS = -1; unsigned short mypacketid; if (MQTTDeserialize_suback(&mypacketid, 1, &count, &grantedQoS, c->readbuf, c->readbuf_size) == 1) rc = grantedQoS; // 0, 1, 2 or 0x80 if (rc != 0x80) { int i; for (i = 0; i < MAX_MESSAGE_HANDLERS; ++i) { if (c->messageHandlers[i].topicFilter == 0) { c->messageHandlers[i].topicFilter = topicFilter; c->messageHandlers[i].fp = messageHandler; rc = 0; break; } } } } else rc = FAILURE;exit:#if defined(MQTT_TASK) MutexUnlock(&c->mutex);#endif DeleteTimer(&timer); return rc;}int MQTTUnsubscribe(MQTTClient* c, const char* topicFilter){ int rc = FAILURE; Timer timer; int len = 0; MQTTString topic = MQTTString_initializer; topic.cstring = (char *)topicFilter;#if defined(MQTT_TASK) MutexLock(&c->mutex);#endif if (!c->isconnected) goto exit; TimerInit(&timer); TimerCountdownMS(&timer, c->command_timeout_ms); if ((len = MQTTSerialize_unsubscribe(c->buf, c->buf_size, 0, getNextPacketId(c), 1, &topic)) <= 0) goto exit; if ((rc = sendPacket(c, len, &timer)) != SUCCESS) // send the subscribe packet goto exit; // there was a problem if (waitfor(c, UNSUBACK, &timer) == UNSUBACK) { unsigned short mypacketid; // should be the same as the packetid above if (MQTTDeserialize_unsuback(&mypacketid, c->readbuf, c->readbuf_size) == 1) rc = 0; } else rc = FAILURE;exit:#if defined(MQTT_TASK) MutexUnlock(&c->mutex);#endif DeleteTimer(&timer); return rc;}int MQTTPublish(MQTTClient* c, const char* topicName, MQTTMessage* message){ int rc = FAILURE; Timer timer; int len = 0; MQTTString topic = MQTTString_initializer; topic.cstring = (char *)topicName;#if defined(MQTT_TASK) MutexLock(&c->mutex);#endif if (!c->isconnected) goto exit; TimerInit(&timer); TimerCountdownMS(&timer, c->command_timeout_ms); if (message->qos == QOS1 || message->qos == QOS2) message->id = getNextPacketId(c); len = MQTTSerialize_publish(c->buf, c->buf_size, 0, message->qos, message->retained, message->id, topic, (unsigned char*)message->payload, message->payloadlen); if (len <= 0) goto exit; if ((rc = sendPacket(c, len, &timer)) != SUCCESS) // send the subscribe packet goto exit; // there was a problem if (message->qos == QOS1) { if (waitfor(c, PUBACK, &timer) == PUBACK) { unsigned short mypacketid; unsigned char dup, type; if (MQTTDeserialize_ack(&type, &dup, &mypacketid, c->readbuf, c->readbuf_size) != 1) rc = FAILURE; } else rc = FAILURE; } else if (message->qos == QOS2) { if (waitfor(c, PUBCOMP, &timer) == PUBCOMP) { unsigned short mypacketid; unsigned char dup, type; if (MQTTDeserialize_ack(&type, &dup, &mypacketid, c->readbuf, c->readbuf_size) != 1) rc = FAILURE; } else rc = FAILURE; }exit:#if defined(MQTT_TASK) MutexUnlock(&c->mutex);#endif DeleteTimer(&timer); return rc;}int MQTTDisconnect(MQTTClient* c){ int rc = FAILURE; Timer timer; // we might wait for incomplete incoming publishes to complete int len = 0;#if defined(MQTT_TASK) MutexLock(&c->mutex);#endif TimerInit(&timer); TimerCountdownMS(&timer, c->command_timeout_ms); len = MQTTSerialize_disconnect(c->buf, c->buf_size); if (len > 0) rc = sendPacket(c, len, &timer); // send the disconnect packet c->isconnected = 0;#if defined(MQTT_TASK) MutexUnlock(&c->mutex);#endif DeleteTimer(&timer); return rc;}
3、DomoticzThread.h:
#ifndef __DOMOTICZ_THREAD_H__#define __DOMOTICZ_THREAD_H__extern void domoticz_thread_init(void);#endif
DomoticzThread.c:
#include <rtthread.h>#include "MQTTClient.h"//#include "led.h"#include "DomoticzMessageParser.h"//#define __DEBUG#ifdef __DEBUG#include <stdarg.h>#include <string.h>static const char *getCurrentFileName(){ const char *p = strrchr(__FILE__,'\\'); return ++p;}#define dprintf(fmt,...) rt_kprintf("%s,line:%d,"fmt,getCurrentFileName(),__LINE__,##__VA_ARGS__)#else#define dprintf(fmt,...)#endifstruct opts_struct{ char* clientid; int nodelimiter; char* delimiter; enum QoS qos; char* username; char* password; char* host; int port; int showtopics;} opts ={ (char*)"subscriber on STM32", 0, (char*)"\n", QOS2, NULL, NULL, (char*)"192.168.1.230", 1883, 0};int is_over;void quit_domoticz_thread(void){ is_over = 1;}void messageArrived(MessageData* md){ MQTTMessage* message = md->message;#if 0 /* Commented @ 2017-Apr-23 1:18:29 */ if (opts.showtopics) rt_kprintf("%.*s\t", md->topicName->lenstring.len, md->topicName->lenstring.data); if (opts.nodelimiter) rt_kprintf("%.*s", (int)message->payloadlen, (char*)message->payload); else rt_kprintf("%.*s%s", (int)message->payloadlen, (char*)message->payload, opts.delimiter);#endif /* Commented */ dprintf("payloadlen=%d,%s\n",(int)message->payloadlen,(char*)message->payload); dprintf("strlen(payload)=%d\n",strlen((char*)message->payload)); //fflush(stdout); ParseDomoticzMessage((char*)message->payload);}void set_host(char *host){ opts.host = host;}void domoticz_thread_entry(void* parameter){ int rc = 0; unsigned char buf[512]={0};//buf[100]; unsigned char readbuf[512]={0};//readbuf[100]; char* topic = "domoticz/out"; Network n; MQTTClient c; MQTTPacket_connectData data = MQTTPacket_connectData_initializer; is_over = 0; rt_kprintf("domoticz_thread_entry\n");//================== Added 2017-Apr-26 2:36:53 start ================== RegisterHardware(Create_LED0(),2); RegisterHardware(Create_LED1(),3); OpenHardwares(); SetupDomoitczMessageParser(); SetEnableParseItem(KEY_IDX); SetEnableParseItem(KEY_NVALUE); SetEnableParseItem(KEY_SWITCH_TYPE); initHardwareSettings();//================== Added 2017-Apr-26 2:36:53 end =================== NetworkInit(&n); NetworkConnect(&n, opts.host, opts.port); rt_kprintf("NetworkConnect ok!\n"); //MQTTClientInit(&c, &n, 1000, buf, 512, readbuf, 512); dprintf("readbuf=%p,readbuf size=%u\n",readbuf,sizeof(readbuf)); MQTTClientInit(&c, &n, 1000, buf, sizeof(buf), readbuf, sizeof(readbuf)); data.willFlag = 0; data.MQTTVersion = 3; data.clientID.cstring = opts.clientid; data.username.cstring = opts.username; data.password.cstring = opts.password; data.keepAliveInterval = 60; data.cleansession = 1; rt_kprintf("Connecting to %s %d\n", opts.host, opts.port); rc = MQTTConnect(&c, &data); dprintf("Connected %d\n", rc); if(rc==-1) { rt_kprintf("MQTTConnect err!\n"); return; } rt_kprintf("Subscribing to %s\n", topic); rc = MQTTSubscribe(&c, topic, opts.qos, messageArrived); dprintf("Subscribed %d\n", rc); while (!is_over) { //dprintf("\n"); MQTTYield(&c, 1000); } rt_kprintf("Stopping\n"); MQTTDisconnect(&c); NetworkDisconnect(&n);}void domoticz_thread_init(void){ rt_thread_t domoticz_thread; domoticz_thread = rt_thread_create("DomoticzThread", domoticz_thread_entry, RT_NULL, 0xf00, 28, 10); if (domoticz_thread != RT_NULL) rt_thread_startup(domoticz_thread);}#ifdef RT_USING_FINSH#include <finsh.h>FINSH_FUNCTION_EXPORT(set_host, set domoticz host ip addr);FINSH_FUNCTION_EXPORT(domoticz_thread_init,to run domoticz thread );FINSH_FUNCTION_EXPORT(quit_domoticz_thread,quit domoticz thread );#endif
二、domoticz消息解析和控制例程部分。
1、CommonTypes.h:
/*******************************************************************************filename: CommonTypes.h******************************************************************************/#ifndef COMMON_TYPES_H#define COMMON_TYPES_H#ifdef __cplusplusextern "C"{#endif//------------------------------------------------------------------------------//common defines#define KEY_IDX 0#define KEY_NAME 1#define KEY_ID 2#define KEY_UINT 3#define KEY_DTYPE 4#define KEY_STYPE 5#define KEY_NVALUE 6#define KEY_SVALUE1 7#define KEY_SVALUE2 8#define KEY_BATTERY 9#define KEY_RSSI 10#define KEY_SWITCH_TYPE 11#define MSG_MAX_LEN 128#define KEY_WORDS_NUM 12//------------------------------------------------------------------------------//common typestypedef enum { STRING=0, INT=1, UINT=2}ARG_TYPE_t;#ifdef __cplusplus}#endif#endif /* #ifndef COMMON_TYPES_H *//*-- File end --*/
HardwareInterface.h:
/*******************************************************************************filename: HardwareInterface.h******************************************************************************/#ifndef HARDWARE_INTERFACE_H#define HARDWARE_INTERFACE_H#ifdef __cplusplusextern "C"{#endif#include "CommonTypes.h"//------------------------------------------------------------------------------//common Hardware interfacetypedef struct{ //解析出来的消息参数类型(STRING、INT、UINT中的一种) ARG_TYPE_t type; //下面是解析出来的具体消息参数数据 union { char strArg[MSG_MAX_LEN]; int iVar; unsigned int uiVar; }param;}ParserArg;typedef struct{ //----------------respons parser interface---------------------------------- //int(*IDX_ParserCallback)(ParserArg* arg);//无需处理idx int(*NAME_ParserCallback)(ParserArg* arg); int(*ID_ParserCallback)(ParserArg* arg); int(*UINT_ParserCallback)(ParserArg* arg); int(*DTYPE_ParserCallback)(ParserArg* arg); int(*STYPE_ParserCallback)(ParserArg* arg); int(*NVALUE_ParserCallback)(ParserArg* arg); int(*SVALUE1_ParserCallback)(ParserArg* arg); int(*SVALUE2_ParserCallback)(ParserArg* arg); int(*BATTERY_ParserCallback)(ParserArg* arg); int(*RSSI_ParserCallback)(ParserArg* arg); int(*SWITCH_TYPE_ParserCallback)(ParserArg* arg); ParserArg parseArg; int RegisterIDX; //--------------device base operation--------------------------------------- //must be implement int (*Open)(); void (*Init)(); void (*Close)();}Hardware;typedef int(*ParserCallback)(ParserArg* arg);#ifdef __cplusplus}#endif#endif /* #ifndef HARDWARE_INTERFACE_H *//*-- File end --*/
2、HardwareControl.h:
/******************************************************************************* filename: HardwareControl.h******************************************************************************/#ifndef HARDWARE_CONTROL_H#define HARDWARE_CONTROL_H#ifdef __cplusplusextern "C"{#endif#include "HardwareInterface.h"#define HARDWARE_MAX_NUM 32#define REGISTER_SUCCESED 1#define REGISTER_ERR1 -1 //索引号已经被使用#define REGISTER_ERR2 -2 //容器已满,不能注册extern int OpenHardwares(void);extern void initHardwareSettings(void);extern void CloseHardwares(void);extern int RegisterHardware(Hardware *hardware,int idx);extern Hardware* GetHardware(int idx);extern int UnregisterHaraware(int idx);#ifdef __cplusplus}#endif#endif /* #ifndef HARDWARE_CONTROL_H *//*-- File end --*/
HardwareControl.c:
/******************************************************************************* filename: HardwareControl.c******************************************************************************/#include "HardwareControl.h"#include <stdio.h>#include <assert.h>//------------------------------------------------------------------------------//GetHardWare interfaceHardware* g_HardwareContainer[HARDWARE_MAX_NUM];/******************************************************************************* 函数名: RegisterHardware* 功能描述:向硬件容器注册一个索引号为idx的硬件* 参数1 :Hardware *hardware [I]:该硬件的指针* 参数2 :int idx [I]:要分配的索引号* 返回值: int ,成功则返回1,失败则返回错误号* 创建时间:2017-Apr-17 22:08:58* 修改时间:2017-Apr-17 22:08:58* 版本记录:* 其他说明:为了使用方便应该做一个配置文件以适配硬件信息******************************************************************************/int RegisterHardware(Hardware *hardware,int idx){ int i; assert(hardware); for(i=0;i<HARDWARE_MAX_NUM;i++) { if(g_HardwareContainer[i]) { if(g_HardwareContainer[i]->RegisterIDX==idx) return REGISTER_ERR1; else continue; } else { g_HardwareContainer[i] = hardware ; g_HardwareContainer[i]->RegisterIDX = idx; return 1; } } return REGISTER_ERR2; }/******************************************************************************* 函数名: GetHardWare* 功能描述: 根据索引号获取相应的硬件设备指针* 参数1 :int idx [I]:设备索引号* 返回值: 成功则返回对应硬件指针,失败返回0(NULL)* 创建时间:2017-Apr-16 18:52:10* 修改时间:2017-Apr-16 18:52:10* 版本记录:******************************************************************************/Hardware* GetHardware(int idx){ int i; for(i=0;i<HARDWARE_MAX_NUM;i++) { if(g_HardwareContainer[i] && g_HardwareContainer[i]->RegisterIDX==idx) return g_HardwareContainer[i]; } return 0;}/******************************************************************************* 函数名: UnregisterHaraware* 功能描述:取消索引号为idx的硬件注册* 参数1 :int idx [I]:要取消注册的硬件的idx号* 返回值: 成功则返回取消注册的位置,失败返回-1* 创建时间:2017-Apr-17 22:06:25* 修改时间:2017-Apr-17 22:06:25* 版本记录:******************************************************************************/int UnregisterHaraware(int idx){ int i; for(i=0;i<HARDWARE_MAX_NUM;i++) { if(g_HardwareContainer[i] && g_HardwareContainer[i]->RegisterIDX==idx) g_HardwareContainer[i] = 0; return i; } return -1;}//------------------------------------------------------------------------------//initionalizeint OpenHardwares(){ int i; int count=0; for(i=0;i<HARDWARE_MAX_NUM;i++) { if(g_HardwareContainer[i]) { if(!g_HardwareContainer[i]->Open) return -i;//如果该硬件接口没有实现Open,则返回它在容器中的位置的相反数(<=0) else { g_HardwareContainer[i]->Open(); count++; } } } return count;//如果成功返回执行Open的设备数量}void initHardwareSettings(){ int i; for(i=0;i<HARDWARE_MAX_NUM;i++) { if(g_HardwareContainer[i] && g_HardwareContainer[i]->Init) { g_HardwareContainer[i]->Init(); } }}void CloseHardwares(){ int i; for(i=0;i<HARDWARE_MAX_NUM;i++) { if(g_HardwareContainer[i] && g_HardwareContainer[i]->Close) g_HardwareContainer[i]->Close(); }}/*-- File end --*/
3、DomoticzMessageParser.h:
/******************************************************************************* filename: DomoticzMessageParser.h******************************************************************************/#ifndef DOMOTICZ_MESSAGE_PARSER_H#define DOMOTICZ_MESSAGE_PARSER_H#ifdef __cplusplusextern "C"{#endif#include "HardwareInterface.h"typedef struct{ char *str;//分割字符串后,消息存入buf中时所需对比的关键字 char *parseStr;//解析消息时所使用的匹配字符串 ARG_TYPE_t type;//该消息对应的类型(STRING、INT、UINT中的一种)}KeyWord_t;extern int GetKeywordIndex(const char* str);//------------------------------------------------------------------------------//common DomoitczMessageParser interfacetypedef struct DomoitczMessageParser DomoitczMessageParser;struct DomoitczMessageParser{ int(*IDX_Handler)(DomoitczMessageParser* pParser, const char* message); int(*NAME_Handler)(DomoitczMessageParser* pParser, const char* message); int(*ID_Handler)(DomoitczMessageParser* pParser, const char* message); int(*UINT_Handler)(DomoitczMessageParser* pParser, const char* message); int(*DTYPE_Handler)(DomoitczMessageParser* pParser, const char* message); int(*STYPE_Handler)(DomoitczMessageParser* pParser, const char* message); int(*NVALUE_Handler)(DomoitczMessageParser* pParser, const char* message); int(*SVALUE1_Handler)(DomoitczMessageParser* pParser, const char* message); int(*SVALUE2_Handler)(DomoitczMessageParser* pParser, const char* message); int(*BATTERY_Handler)(DomoitczMessageParser* pParser, const char* message); int(*RSSI_Handler)(DomoitczMessageParser* pParser, const char* message); int(*SWITCH_TYPE_Handler)(DomoitczMessageParser* pParser, const char* message); int (*FillArgStr)(DomoitczMessageParser* pParser,const char* value); char MsgBuf[KEY_WORDS_NUM][MSG_MAX_LEN]; Hardware* bindHardware;};typedef int(*DomoitczMessageParserHandler)(DomoitczMessageParser* pParser, const char* message);extern DomoitczMessageParser g_DMP;extern DomoitczMessageParser* g_pParser;extern void SetupDomoitczMessageParser(void);extern void SetEnableParseItem(int item);extern void SetDisableParseItem(int item);extern int ParseDomoticzMessage(char* str);//------------------------------------------------------------------------------//hardware settingsextern void initHardWareSettings(void);#ifdef __cplusplus}#endif#endif /* #ifndef DOMOTICZ_MESSAGE_PARSER_H *//*-- File end --*/
DomoticzMessageParser.c:
/******************************************************************************* filename: DomoticzMessageParser.c******************************************************************************//*-- #include --*/#include "DomoticzMessageParser.h"#include "HardwareControl.h"#include <stdio.h>#include <string.h>#include <assert.h>#include <stdarg.h>#ifdef _DEBUG#define dprintf(msg,...) printf("%s,line:%d,"msg,__FILE__,__LINE__,##__VA_ARGS__)#else#define dprintf(msg,...)#endifKeyWord_t KeyWords[KEY_WORDS_NUM+1]={ {"idx"," \"idx\" : %d,",INT}, {"name"," \"name\" : \"%s\",",STRING}, {"id"," \"id\" : \"%s\",",STRING}, {"unit"," \"unit\" : %u",UINT}, {"dtype"," \"dtype\" : \"%s\",",STRING}, {"stype"," \"stype\" : \"%s\",",STRING}, {"nvalue"," \"nvalue\" : %d,",INT}, {"svalue1"," \"svalue1\" : \"%s\",",STRING}, {"svalue2"," \"svalue2\" : \"%s\",",STRING}, {"Battery"," \"Battery\" : %u,",UINT}, {"RSSI"," \"RSSI\" : %d,",INT}, {"switchType"," \"switchType\" : \"%s\",",STRING}, {"unknown","unknown",STRING}//防止越界访问 };/******************************************************************************* 函数名: GetKeywordIndex* 功能描述: 根据关键字获取含该关键字的消息在KeyWords的位置索引号* 参数1 :const char* str [I]:要查询的具体关键字字符串* 返回值: 消息在KeyWords的位置索引号* 创建时间:2017-Apr-16 19:09:26* 修改时间:2017-Apr-16 19:09:26* 版本记录:******************************************************************************/int GetKeywordIndex(const char* str){ int i; for(i=0;i<KEY_WORDS_NUM;i++) { if(strstr(str,KeyWords[i].str)) { return i; } } return KEY_WORDS_NUM;}//------------------------------------------------------------------------------//DomoitczMessageParser interface implemention//#0int IDX_HandlerImpl(DomoitczMessageParser* pParser, const char* message){ int idx; if(!pParser) return 0; if(sscanf(message,KeyWords[KEY_IDX].parseStr,&idx)>0) { dprintf("idx=%d\n",idx); pParser->bindHardware = GetHardware(idx);//根据设备索引号搜索硬件设备 //pParser->bindHardware->IDX_ParserCallback(&(pParser->bindHardware->parseArg)); return pParser->bindHardware?1:0; } return 0;}//#1int NAME_HandlerImpl(DomoitczMessageParser* pParser,const char* message){ ParserCallback funcParseCallback; if(!pParser ||!pParser->bindHardware ||!pParser->bindHardware->NAME_ParserCallback) return 0; funcParseCallback = pParser->bindHardware->NAME_ParserCallback; if(sscanf(message,KeyWords[KEY_NAME].parseStr,pParser->bindHardware->parseArg.param.strArg)>0) { dprintf("name=%s\n",pParser->bindHardware->parseArg.param.strArg); pParser->bindHardware->parseArg.type = KeyWords[KEY_NAME].type; funcParseCallback(&(pParser->bindHardware->parseArg)); return 1; } return 0;}//#2int ID_HandlerImpl(DomoitczMessageParser* pParser,const char* message){ ParserCallback funcParseCallback; if(!pParser ||!pParser->bindHardware ||!pParser->bindHardware->ID_ParserCallback) return 0; funcParseCallback = pParser->bindHardware->ID_ParserCallback; if(sscanf(message,KeyWords[KEY_ID].parseStr,pParser->bindHardware->parseArg.param.strArg)>0) { dprintf("id=%s\n",pParser->bindHardware->parseArg.param.strArg); pParser->bindHardware->parseArg.type = KeyWords[KEY_ID].type; funcParseCallback(&(pParser->bindHardware->parseArg)); return 1; } return 0;}//#3int UINT_HandlerImpl(DomoitczMessageParser* pParser,const char* message){ ParserCallback funcParseCallback; if(!pParser ||!pParser->bindHardware ||!pParser->bindHardware->UINT_ParserCallback) return 0; funcParseCallback = pParser->bindHardware->UINT_ParserCallback; if(sscanf(message,KeyWords[KEY_UINT].parseStr,&(pParser->bindHardware->parseArg.param.uiVar))>0) { dprintf("uint=%u\n",pParser->bindHardware->parseArg.param.uiVar); pParser->bindHardware->parseArg.type = KeyWords[KEY_UINT].type; funcParseCallback(&(pParser->bindHardware->parseArg)); return 1; } return 0;}//#4int DTYPE_HandlerImpl(DomoitczMessageParser* pParser,const char* message){ ParserCallback funcParseCallback; if(!pParser ||!pParser->bindHardware ||!pParser->bindHardware->DTYPE_ParserCallback) return 0; funcParseCallback = pParser->bindHardware->DTYPE_ParserCallback; if(sscanf(message,KeyWords[KEY_DTYPE].parseStr,pParser->bindHardware->parseArg.param.strArg)>0) { dprintf("dtype=%s\n",pParser->bindHardware->parseArg.param.strArg); pParser->bindHardware->parseArg.type = KeyWords[KEY_DTYPE].type; funcParseCallback(&(pParser->bindHardware->parseArg)); return 1; } return 0;}//#5int STYPE_HandlerImpl(DomoitczMessageParser* pParser,const char* message){ ParserCallback funcParseCallback; if(!pParser ||!pParser->bindHardware ||!pParser->bindHardware->STYPE_ParserCallback) return 0; funcParseCallback = pParser->bindHardware->STYPE_ParserCallback; if(sscanf(message,KeyWords[KEY_STYPE].parseStr,pParser->bindHardware->parseArg.param.strArg)>0) { dprintf("name=%s\n",pParser->bindHardware->parseArg.param.strArg); pParser->bindHardware->parseArg.type = KeyWords[KEY_STYPE].type; funcParseCallback(&(pParser->bindHardware->parseArg)); return 1; } return 0;}//#6int NVALUE_HandlerImpl(DomoitczMessageParser* pParser,const char* message){ ParserCallback funcParseCallback; if(!pParser || !pParser->bindHardware || !pParser->bindHardware->NVALUE_ParserCallback) return 0; funcParseCallback = pParser->bindHardware->NVALUE_ParserCallback; if(sscanf(message,KeyWords[KEY_NVALUE].parseStr,&(pParser->bindHardware->parseArg.param.iVar))>0) { dprintf("nvalue=%d\n",pParser->bindHardware->parseArg.param.iVar); pParser->bindHardware->parseArg.type = KeyWords[KEY_NVALUE].type; funcParseCallback(&(pParser->bindHardware->parseArg)); return 1; } return 0;}//#7int SVALUE1_HandlerImpl(DomoitczMessageParser* pParser,const char* message){ ParserCallback funcParseCallback; if(!pParser ||!pParser->bindHardware ||!pParser->bindHardware->SVALUE1_ParserCallback) return 0; funcParseCallback = pParser->bindHardware->SVALUE1_ParserCallback; if(sscanf(message,KeyWords[KEY_SVALUE1].parseStr,pParser->bindHardware->parseArg.param.strArg)>0) { dprintf("svalue1=%s\n",pParser->bindHardware->parseArg.param.strArg); pParser->bindHardware->parseArg.type = KeyWords[KEY_SVALUE1].type; funcParseCallback(&(pParser->bindHardware->parseArg)); return 1; } return 0;}//#8int SVALUE2_HandlerImpl(DomoitczMessageParser* pParser,const char* message){ ParserCallback funcParseCallback; if(!pParser ||!pParser->bindHardware ||!pParser->bindHardware->SVALUE2_ParserCallback) return 0; funcParseCallback = pParser->bindHardware->SVALUE2_ParserCallback; if(sscanf(message,KeyWords[KEY_SVALUE2].parseStr,pParser->bindHardware->parseArg.param.strArg)>0) { dprintf("svalue2=%s\n",pParser->bindHardware->parseArg.param.strArg); pParser->bindHardware->parseArg.type = KeyWords[KEY_SVALUE2].type; funcParseCallback(&(pParser->bindHardware->parseArg)); return 1; } return 0;}//#9int BATTERY_HandlerImpl(DomoitczMessageParser* pParser,const char* message){ ParserCallback funcParseCallback; if(!pParser ||!pParser->bindHardware ||!pParser->bindHardware->BATTERY_ParserCallback) return 0; funcParseCallback = pParser->bindHardware->BATTERY_ParserCallback; if(sscanf(message,KeyWords[KEY_BATTERY].parseStr,&(pParser->bindHardware->parseArg.param.uiVar))>0) { dprintf("battery=%u\n",pParser->bindHardware->parseArg.param.uiVar); pParser->bindHardware->parseArg.type = KeyWords[KEY_BATTERY].type; funcParseCallback(&(pParser->bindHardware->parseArg)); return 1; } return 0;}//#10int RSSI_HandlerImpl(DomoitczMessageParser* pParser,const char* message){ ParserCallback funcParseCallback; if(!pParser ||!pParser->bindHardware ||!pParser->bindHardware->RSSI_ParserCallback) return 0; funcParseCallback = pParser->bindHardware->RSSI_ParserCallback; if(sscanf(message,KeyWords[KEY_RSSI].parseStr,&(pParser->bindHardware->parseArg.param.iVar))>0) { dprintf("RSSI=%d\n",pParser->bindHardware->parseArg.param.iVar); pParser->bindHardware->parseArg.type = KeyWords[KEY_RSSI].type; funcParseCallback(&(pParser->bindHardware->parseArg)); return 1; } return 0;}//#11int SWITCH_TYPE_HandlerImpl(DomoitczMessageParser* pParser,const char* message){ ParserCallback funcParseCallback; if(!pParser ||!pParser->bindHardware ||!pParser->bindHardware->SWITCH_TYPE_ParserCallback) return 0; funcParseCallback = pParser->bindHardware->SWITCH_TYPE_ParserCallback; if(sscanf(message,KeyWords[KEY_SWITCH_TYPE].parseStr,pParser->bindHardware->parseArg.param.strArg)>0) { dprintf("switchType=%s\n",pParser->bindHardware->parseArg.param.strArg); pParser->bindHardware->parseArg.type = KeyWords[KEY_SWITCH_TYPE].type; funcParseCallback(&(pParser->bindHardware->parseArg)); return 1; } return 0;}/******************************************************************************* 函数名: FillArgStrImpl* 功能描述: .* 参数1 :DomoitczMessageParser* pParser [I]:param description.* 参数2 :const char* value [I]:param description.* 返回值: int return variable description.* 创建时间:2017-Apr-16 19:16:58* 修改时间:2017-Apr-16 19:16:58* 版本记录:******************************************************************************/int FillArgStrImpl(DomoitczMessageParser* pParser,const char* value){ int key; if(!pParser) return -1; key = GetKeywordIndex(value); if(key>=KEY_WORDS_NUM) return -1; strcpy(pParser->MsgBuf[key],value); return key;}//------------------------------------------------------------------------------//Setup DomoitczMessageParserstatic int CALL_PARSER_FUNC_FLAG = 0;DomoitczMessageParser g_DMP;DomoitczMessageParser* g_pParser = &g_DMP;static DomoitczMessageParserHandler HandlerPool[KEY_WORDS_NUM];/******************************************************************************* 函数名: SetupDomoitczMessageParser* 功能描述: 构建消息解析器* 参数1 :DomoitczMessageParser* pDMP [I]:要构建的domoticz消息解析器指针* 参数2 :Hardware* bindHardware [I]:初始化消息解析器解析回调对象* 返回值: * 创建时间:2017-Apr-16 19:13:29* 修改时间:2017-Apr-16 19:13:29* 版本记录:******************************************************************************/void SetupDomoitczMessageParser(){ g_pParser->IDX_Handler = IDX_HandlerImpl; g_pParser->NAME_Handler = NAME_HandlerImpl; g_pParser->ID_Handler = ID_HandlerImpl; g_pParser->UINT_Handler = UINT_HandlerImpl; g_pParser->DTYPE_Handler = DTYPE_HandlerImpl; g_pParser->STYPE_Handler = STYPE_HandlerImpl; g_pParser->NVALUE_Handler = NVALUE_HandlerImpl; g_pParser->SVALUE1_Handler = SVALUE1_HandlerImpl; g_pParser->SVALUE2_Handler = SVALUE2_HandlerImpl; g_pParser->BATTERY_Handler = BATTERY_HandlerImpl; g_pParser->RSSI_Handler = RSSI_HandlerImpl; g_pParser->SWITCH_TYPE_Handler = SWITCH_TYPE_HandlerImpl; g_pParser->bindHardware = 0; g_pParser->FillArgStr = FillArgStrImpl; HandlerPool[KEY_IDX] = IDX_HandlerImpl; HandlerPool[KEY_NAME] = NAME_HandlerImpl; HandlerPool[KEY_ID] = ID_HandlerImpl; HandlerPool[KEY_UINT] = UINT_HandlerImpl; HandlerPool[KEY_DTYPE] = DTYPE_HandlerImpl; HandlerPool[KEY_STYPE] = STYPE_HandlerImpl; HandlerPool[KEY_NVALUE] = NVALUE_HandlerImpl; HandlerPool[KEY_SVALUE1] = SVALUE1_HandlerImpl; HandlerPool[KEY_SVALUE2] = SVALUE2_HandlerImpl ; HandlerPool[KEY_BATTERY] = BATTERY_HandlerImpl; HandlerPool[KEY_RSSI] = RSSI_HandlerImpl; HandlerPool[KEY_SWITCH_TYPE] = SWITCH_TYPE_HandlerImpl;}// 将str字符以spl分割,存于g_pParser->MsgBuf中,并返回子字符串数量int split(char* str, const char* delim){ int n = 0; char *result = NULL; assert(g_pParser); result = strtok(str, delim); while( result != NULL ) { g_pParser->FillArgStr(g_pParser,result); dprintf("result=%s\n",result); result = strtok(NULL, delim); } return n;}/******************************************************************************* 函数名: SetEnableParseItem* 功能描述: 设置CALLPARSER_FUNC_FLAG的item位置上的标志位为1* 参数1 :int item [I]:要置1的位置,由右往左数0,1,2,3,...。* 该参数不能超过KEY_WORDS_NUM,且不能超过31,否则视为无效。** 返回值: * 创建时间:2017-Apr-16 20:15:51* 修改时间:2017-Apr-16 20:15:51* 版本记录:******************************************************************************/void SetEnableParseItem(int item){ assert(item<32); if(item>=0 && item<KEY_WORDS_NUM) { CALL_PARSER_FUNC_FLAG |= 1<<item; }}/******************************************************************************* 函数名: SetEnableParseItem* 功能描述: 设置CALLPARSER_FUNC_FLAG的item位置上的标志位为0* 参数1 :int item [I]:要清零的位置,由右往左数0,1,2,3,...。* 该参数不能超过KEY_WORDS_NUM,且不能超过31,否则视为无效。** 返回值: * 创建时间:2017-Apr-16 20:15:51* 修改时间:2017-Apr-16 20:15:51* 版本记录:******************************************************************************/void SetDisableParseItem(int item){ assert(item<32); if(item>=0 && item<KEY_WORDS_NUM) { CALL_PARSER_FUNC_FLAG &= ~(1<<item); }}/******************************************************************************* 函数名: ParseDomoticzMessage* 功能描述: 解析消息,并回调与消息相应的硬件处理函数* 参数1 :char* str [I]:要解析的目标消息字符串* 返回值: int * 创建时间:2017-Apr-16 19:18:17* 修改时间:2017-Apr-16 19:18:17* 版本记录:******************************************************************************/int ParseDomoticzMessage(char* str){ int nCount ; //printf("---------------------------------------\n"); int i; int CallFlag ; nCount = split(str,"\n"); //SetDisableParseItem(KEY_SWITCH_TYPE); CallFlag = CALL_PARSER_FUNC_FLAG; //dprintf("CALL_PARSER_FUNC_FLAG=0x%X\n",CALL_PARSER_FUNC_FLAG); for(i=0;i<KEY_WORDS_NUM && i<32;i++) { if(CallFlag&0x1) { HandlerPool[i](g_pParser,g_pParser->MsgBuf[i]); //dprintf("i=%d\n",i); } CallFlag>>=1; } //g_pParser->IDX_Handler(g_pParser,g_pParser->MsgBuf[KEY_IDX]); //g_pParser->NVALUE_Handler(g_pParser,g_pParser->MsgBuf[KEY_NVALUE]); return 1;}/*-- File end --*/
4、例程部分。
1)led硬件驱动:
led.h
/* * File : led.h * This file is part of RT-Thread RTOS * COPYRIGHT (C) 2009, RT-Thread Development Team * * The license and distribution terms for this file may be * found in the file LICENSE in this distribution or at * http://www.rt-thread.org/license/LICENSE * * Change Logs: * Date Author Notes * 2009-01-05 Bernard the first version */#ifndef __LED_H__#define __LED_H__#include <rtthread.h>void rt_hw_led_init(void);void rt_hw_led_on(rt_uint32_t led);void rt_hw_led_off(rt_uint32_t led);#endif
led.c:
/** File : led.c* This file is part of RT-Thread RTOS* COPYRIGHT (C) 2009, RT-Thread Development Team** The license and distribution terms for this file may be* found in the file LICENSE in this distribution or at* http://www.rt-thread.org/license/LICENSE** Change Logs:* Date Author Notes* 2009-01-05 Bernard the first version*/#include <rtthread.h>#include <stm32f10x.h>#define led1_rcc RCC_APB2Periph_GPIOD#define led1_gpio GPIOD#define led1_pin (GPIO_Pin_2)#define led2_rcc RCC_APB2Periph_GPIOD#define led2_gpio GPIOD#define led2_pin (GPIO_Pin_3)void rt_hw_led_init(void){ GPIO_InitTypeDef GPIO_InitStructure; RCC_APB2PeriphClockCmd(led1_rcc|led2_rcc,ENABLE); GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_InitStructure.GPIO_Pin = led1_pin; GPIO_Init(led1_gpio, &GPIO_InitStructure); GPIO_InitStructure.GPIO_Pin = led2_pin; GPIO_Init(led2_gpio, &GPIO_InitStructure);}void rt_hw_led_on(rt_uint32_t n){ switch (n) { case 0: GPIO_SetBits(led1_gpio, led1_pin); break; case 1: GPIO_SetBits(led2_gpio, led2_pin); break; default: break; }}void rt_hw_led_off(rt_uint32_t n){ switch (n) { case 0: GPIO_ResetBits(led1_gpio, led1_pin); break; case 1: GPIO_ResetBits(led2_gpio, led2_pin); break; default: break; }}void led_toggle(rt_uint32_t n){ switch (n) { case 0: GPIO_WriteBit(led1_gpio, led1_pin, (BitAction)((GPIO_ReadOutputDataBit(led1_gpio,led1_pin))^1) ); break; case 1: GPIO_WriteBit(led2_gpio, led2_pin, (BitAction)((GPIO_ReadOutputDataBit(led2_gpio,led2_pin))^1) ); break; default: break; }}void rt_led_disp_thread_entry(void* parameter){ rt_uint32_t i=0; while(1) { i=0x50000; do{ i--; }while(i>0); led_toggle(0); i=0x50000; do{ i--; }while(i>0); led_toggle(1); }}void rt_led_disp_init(void){ rt_thread_t init_led_thread; init_led_thread = rt_thread_create("led_disp", rt_led_disp_thread_entry, RT_NULL, 128, 28, 10); if (init_led_thread != RT_NULL) rt_thread_startup(init_led_thread);}static rt_uint8_t led_inited = 0;void led(rt_uint32_t led, rt_uint32_t value){ /* init led configuration if it's not inited. */ if (!led_inited) { rt_hw_led_init(); led_inited = 1; } if ( led == 0 ) { /* set led status */ switch (value) { case 0: rt_hw_led_off(0); break; case 1: rt_hw_led_on(0); break; default: break; } } if ( led == 1 ) { /* set led status */ switch (value) { case 0: rt_hw_led_off(1); break; case 1: rt_hw_led_on(1); break; default: break; } }}#ifdef RT_USING_FINSH#include <finsh.h>FINSH_FUNCTION_EXPORT(led, set led[0 - 1] on[1] or off[0])FINSH_FUNCTION_EXPORT(led_toggle, toggle the leds)#endif
2)例程:
LED0.h
/******************************************************************************* filename: LED0.h******************************************************************************/#ifndef LED0_H#define LED0_H#include "HardwareInterface.h"extern Hardware* Create_LED0(void);extern int getLED0Status(void);#endif /* #ifndef LED0_H *//*-- File end --*/
LED0.c:
#include "led.h"#include "LED0.h"static Hardware LED0;static int led_no = 0;static int status=0;int LED0_Open(){ return 0;}void LED0_Init(){ rt_hw_led_off(led_no); status = 0;}void LED0_Close(){}/******************************************************************************* 函数名: LED0_NVALUE_ParserCallbackImpl* 功能描述: 在DomoiticzMessageParser进行解析"nvalue"消息参数后,* 被回调以执行相应功能** 参数1 :ParserArg* arg [I]:已经解析的消息参数* 返回值: 成功返回1,失败返回0* 创建时间:2017-Apr-16 18:50:27* 修改时间:2017-Apr-16 18:50:27* 版本记录:******************************************************************************/int LED0_NVALUE_ParserCallbackImpl(ParserArg* arg){ //printf("LED0_IDX_ParserCallbackImpl is called!\n"); if(arg && arg->type==INT) { status = arg->param.iVar; if(status==1) rt_hw_led_on(led_no); else if(status ==0) rt_hw_led_off(led_no); return 1; } return 0;}int LED0_SWITCH_TYPE_ParserCallbackImpl(ParserArg* arg){ //printf("LED0_SWITCH_TYPE_ParserCallbackImpl is called!\n"); if(arg && arg->type==STRING) { //dprintf("%s\n",arg->strArg); return 1; } return 0;}Hardware* Create_LED0(){ LED0.Open = LED0_Open; LED0.Init= LED0_Init; LED0.Close= LED0_Close; LED0.NVALUE_ParserCallback = LED0_NVALUE_ParserCallbackImpl; LED0.SWITCH_TYPE_ParserCallback = LED0_SWITCH_TYPE_ParserCallbackImpl; return &LED0;}int getLED0Status(){ return status;}
LED1.h:
/******************************************************************************* filename: LED1.h******************************************************************************/#ifndef LED1_H#define LED1_H#include "HardwareInterface.h"extern Hardware* Create_LED1(void);#endif /* #ifndef LED0_H *//*-- File end --*/
LED1.c
#include "led.h"#include "LED1.h"static Hardware LED1;static int led_no = 1;int LED1_Open(){ return 0;}void LED1_Init(){ rt_hw_led_off(led_no);}void LED1_Close(){}int LED1_NVALUE_ParserCallbackImpl(ParserArg* arg){ //dprintf("LED0_IDX_ParserCallbackImpl is called!\n"); if(arg && arg->type==INT) { if(arg->param.iVar==1) rt_hw_led_on(led_no); else if(arg->param.iVar ==0) rt_hw_led_off(led_no); return 1; } return 0;}Hardware* Create_LED1(){ LED1.Open = LED1_Open; LED1.Init= LED1_Init; LED1.Close= LED1_Close; LED1.NVALUE_ParserCallback = LED1_NVALUE_ParserCallbackImpl; return &LED1;}
5、在应用启动上加入我们的启动调用,位置在rt-thread\bsp\stm32f107\applications\application.c中。
改后的代码如下:
application.c:
/* * File : application.c * This file is part of RT-Thread RTOS * COPYRIGHT (C) 2006 - 2013, RT-Thread Development Team * * The license and distribution terms for this file may be * found in the file LICENSE in this distribution or at * http://www.rt-thread.org/license/LICENSE * * Change Logs: * Date Author Notes * 2009-01-05 Bernard the first version *//** * @addtogroup STM32 *//*@{*/#include <board.h>#include <rtthread.h>#ifdef RT_USING_DFS#include <dfs_fs.h>#include <dfs_init.h>#include <dfs_elm.h>#endif#ifdef RT_USING_LWIP#include <stm32_eth.h>#include <netif/ethernetif.h>extern int lwip_system_init(void);#endif#ifdef RT_USING_FINSH#include <shell.h>#include <finsh.h>#endif//================== Added 2017-Apr-20 5:01:52 start ==================#include "led.h"#include "../../../components/external/paho-mqtt/MQTTClient-C/samples/domoticz/DomoticzThread.h" //================== Added 2017-Apr-20 5:01:52 end ===================void rt_init_thread_entry(void* parameter){ { extern void rt_platform_init(void); rt_platform_init(); } /* Filesystem Initialization */#if defined(RT_USING_DFS) && defined(RT_USING_DFS_ELMFAT) /* initialize the device file system */ dfs_init(); /* initialize the elm chan FatFS file system*/ elm_init(); /* mount sd card fat partition 1 as root directory */ if (dfs_mount("sd0", "/", "elm", 0, 0) == 0) { rt_kprintf("File System initialized!\n"); } else { rt_kprintf("File System initialzation failed!\n"); }#endif /* RT_USING_DFS && RT_USING_DFS_ELMFAT */#ifdef RT_USING_LWIP /* initialize lwip stack */ /* register ethernetif device */ eth_system_device_init();//================== Added 2017-Apr-20 5:49:03 start ================== rt_hw_led_init(); rt_hw_led_off(0); rt_hw_led_off(1);//================== Added 2017-Apr-20 5:49:03 end =================== /* initialize lwip system */ lwip_system_init(); rt_kprintf("TCP/IP initialized!\n");//================== Added 2017-Apr-20 5:49:03 start ================== domoticz_thread_init();//================== Added 2017-Apr-20 5:49:03 end ===================#endif#ifdef RT_USING_FINSH /* initialize finsh */ finsh_system_init(); finsh_set_device(RT_CONSOLE_DEVICE_NAME);#endif}int rt_application_init(void){ rt_thread_t tid; tid = rt_thread_create("init", rt_init_thread_entry, RT_NULL, 2048, RT_THREAD_PRIORITY_MAX/3, 20); if (tid != RT_NULL) rt_thread_startup(tid); return 0;}/*@}*/
- 基于mini2440嵌入式linux上整合一套Domoticz智能家居系统(十)使用domoticz+mosquitto+Android客户端实现控制STM32板上的LED(一)
- 基于mini2440嵌入式linux上整合一套Domoticz智能家居系统(十一)使用domoticz+mosquitto+Android客户端实现控制STM32板上的LED(二)
- 基于mini2440嵌入式linux上整合一套Domoticz智能家居系统(八)使用domoticz+mosquitto+Android客户端实现控制mini2440上的LED(一)
- 基于mini2440嵌入式linux上整合一套Domoticz智能家居系统(九)使用domoticz+mosquitto+Android客户端实现控制mini2440上的LED(二)
- 基于mini2440嵌入式linux上整合一套Domoticz智能家居系统(六)使用domoticz联合arm上的mosquitto实现Android客户端远程控制
- 基于mini2440嵌入式linux上整合一套Domoticz智能家居系统(一)前言
- 基于mini2440嵌入式linux上整合一套Domoticz智能家居系统(四)交叉编译OpenSSL、c-ares、e2fsprogs和mosquitto
- 基于mini2440嵌入式linux上整合一套Domoticz智能家居系统(二)前期准备
- 基于mini2440嵌入式linux上整合一套Domoticz智能家居系统(五)交叉编译并安装zlib、curl、boost和domoticz
- 基于mini2440嵌入式linux上整合一套Domoticz智能家居系统(三)编译并安装cmake和git工具
- 基于mini2440嵌入式linux上整合一套Domoticz智能家居系统(七)交叉编译paho.mqtt.embedded-c库和嵌入式linux样例程序
- 通过网络控制mini2440上的LED
- 【Domoticz】【ESP】通过Domoticz控制esp的http命令格式——ESPEasy System Variables
- 基于Android系统的智能家居控制终端研究与实现
- 基于mini2440的按键中断控制LED(裸机代码)
- u-boot-2009.08在mini2440上的移植(一)---LED显示Uboot运行状况
- mini2440上程序不能控制led的解决办法
- mini2440上程序不能控制led的解决办法
- winamp整合贴(保持最新?)
- django搭建个人博客02,表设计(models)
- 数据库SQL优化
- Mybatis xml配置
- hdu 1000 A+B Problem
- 基于mini2440嵌入式linux上整合一套Domoticz智能家居系统(十)使用domoticz+mosquitto+Android客户端实现控制STM32板上的LED(一)
- CUDA-GPU programming introduction (2)
- SniperOJ pwn100-bof writeup
- tensorflow程序-最简单的CNN模型
- MySQL Cluster
- 网易复试(技术面)
- hdu 1001 Sum problem
- APP与服务端保持登录状态
- hdu 1002 A + B Problem II