收藏官网首页
查看: 14324|回复: 0

【一起来玩RTOS系列】之RT-Thread 事件用于线程同步

321

主题

1054

帖子

4513

积分

论坛元老

Rank: 8Rank: 8

积分
4513
QQ
跳转到指定楼层
楼主
发表于 2017-12-9 22:09:36 | 只看该作者 |只看大图 回帖奖励 |倒序浏览 |阅读模式
校园创客福利
事件

事件主要用于线程间的同步,与信号量不同,它的特点是可以实现一对多,多对多的同步。即一个线程可等待多个事件的触发:可以是其中任意一个事件唤醒线程进行事件处理的操作;也可以是几个事件都到达后才唤醒线程进行后续的处理;同样,事件也可以是多个线程同步多个事件,这种多个事件的集合可以用一个32位无符号整型变量来表示,变量的每一位代表一个事件,线程通过“逻辑与”或“逻辑或”与一个或多个事件建立关联,形成一个事件集。事件的“逻辑或”也称为是独立型同步,指的是线程与任何事件之一发生同步;事件“逻辑与”也称为是关联型同步,指的是线程与若干事件都发生同步。

RT-Thread定义的事件有以下特点:

事件只与线程相关,事件间相互独立:每个线程拥有32个事件标志,采用一个32 bit无符号整型数进行记录,每一个bit代表一个事件。若干个事件构成一个事件集;
事件仅用于同步,不提供数据传输功能;
事件无排队性,即多次向线程发送同一事件(如果线程还未来得及读走),其效果等同于只发送一次。
在RT-Thread实现中,每个线程都拥有一个事件信息标记,它有三个属性,分别是RT_EVENT_FLAG_AND(逻辑与),RT_EVENT_FLAG_OR(逻辑或)以及RT_EVENT_FLAG_CLEAR (清除标记)。当线程等待事件同步时,可以通过32个事件标志和这个事件信息标记来判断当前接收的事件是否满足同步条件。


如图所示,线程#1的事件标志中第2位和第29位被置位,如果事件信息标记位设为逻辑与,则表示线程#1只有在事件1和事件29都发生以后才会被触发唤醒,如果事件信息标记位设为逻辑或,则事件1或事件29中的任意一个发生都会触发唤醒线程#1。如果信息标记同时设置了清除标记位,则当线程#1唤醒后将主动把事件1和事件29清为零,否则事件标志将依然存在(即置1)。

事件控制块

  1. struct rt_event
  2. {
  3.     struct rt_ipc_object parent; /* 继承自ipc_object类 */
  4.     rt_uint32_t set;                /* 事件集合           */
  5. };
  6. /* rt_event_t是指向事件结构体的指针 */
  7. typedef struct rt_event* rt_event_t;
复制代码

rt_event对象从rt_ipc_object 中派生,由IPC容器管理。

事件相关接口

创建事件

当创建一个事件时,内核首先创建一个事件控制块,然后对该事件控制块进行基本的初始化,创建事件使用下面的函数接口:

  1. rt_event_t rt_event_create (const char* name, rt_uint8_t flag);
复制代码

调用该函数接口时,系统会从动态内存堆中分配事件对象,然后进行对象的初始化,IPC对象初始化,并把set设置成0。

函数参数


      参数  描述

      name  事件的名称;

      flag  事件的标志,可以使用如下的数值:

  1. #define RT_IPC_FLAG_FIFO 0x00 /* IPC参数采用FIFO方式*/
  2. #define RT_IPC_FLAG_PRIO 0x01 /* IPC参数采用优先级方式*/
复制代码

函数返回

创建成功返回事件对象的句柄;创建失败返回RT_NULL。

删除事件

系统不再使用事件对象时,通过删除事件对象控制块来释放系统资源。删除事件可以使用下面的函数接口:

  1. rt_err_t rt_event_delete (rt_event_t event);
复制代码

在调用rt_event_delete函数删除一个事件对象时,应该确保该事件不再被使用。在删除前会唤醒所有挂起在该事件上的线程(线程的返回值是-RT_ERROR),然后释放事件对象占用的内存块。

函数参数


      参数  描述

     event  事件对象的句柄。

函数返回

RT_EOK

初始化事件

静态事件对象的内存是在系统编译时由编译器分配的,一般放于数据段或ZI段中。在使用静态事件对象前,需要先行对它进行初始化操作。初始化事件使用下面的函数接口:

  1. rt_err_t rt_event_init(rt_event_t event, const char* name, rt_uint8_t flag);
复制代码

调用该接口时,需指定静态事件对象的句柄(即指向事件控制块的指针),然后系统会初始化事件对象,并加入到系统对象容器中进行管理。

函数参数


      参数  描述

     event  事件对象的句柄。

      name  事件名称;

      flag  事件的标志,可以使用如下的数值:

  1. #define RT_IPC_FLAG_FIFO 0x00 /* IPC参数采用FIFO方式*/
  2. #define RT_IPC_FLAG_PRIO 0x01 /* IPC参数采用优先级方式*/
复制代码

函数返回

RT_EOK

脱离事件

脱离事件是将事件对象从内核对象管理器中删除。脱离事件使用下面的函数接口:

  1. rt_err_t rt_event_detach(rt_event_t event);
复制代码

用户调用这个函数时,系统首先唤醒所有挂在该事件等待队列上的线程(线程的返回值是- RT_ERROR ),然后将该事件从内核对象管理器中删除。

函数参数


      参数  描述

     event  事件对象的句柄。

函数返回

RT_EOK

接收事件

内核使用32位的无符号整型数来标识事件,它的每一位代表一个事件,因此一个事件对象可同时等待接收32个事件,内核可以通过指定选择参数“逻辑与”或“逻辑或”来选择如何激活线程,使用“逻辑与”参数表示只有当所有等待的事件都发生时才激活线程,而使用“逻辑或”参数则表示只要有一个等待的事件发生就激活线程。接收事件使用下面的函数接口:

  1. rt_err_t rt_event_recv(rt_event_t event, rt_uint32_t set, rt_uint8_t option,
  2.                        rt_int32_t timeout, rt_uint32_t* recved);
复制代码

当用户调用这个接口时,系统首先根据set参数和接收选项来判断它要接收的事件是否发生,如果已经发生,则根据参数option上是否设置有RT_EVENT_FLAG_CLEAR来决定是否重置事件的相应标志位,然后返回(其中recved参数返回收到的事件); 如果没有发生,则把等待的set和option参数填入线程本身的结构中,然后把线程挂起在此事件对象上,直到其等待的事件满足条件或等待时间超过指定的超时时间。如果超时时间设置为零,则表示当线程要接受的事件没有满足其要求时就不等待,而直接返回-RT_TIMEOUT。

函数参数


      参数  描述

     event  事件对象的句柄。

       set  接收线程感兴趣的事件;

    option  接收选项;

   timeout  指定超时时间;

    recved  指向收到的事件;

函数返回

正确接收返回RT_EOK,超时返回-RT_TIMEOUT,其他返回-RT_ERROR。

发送事件

通过发送事件服务,可以发送一个或多个事件。发送事件可以使用下面的函数接口:

  1. rt_err_t rt_event_send(rt_event_t event, rt_uint32_t set);
复制代码

使用该函数接口时,通过参数set指定的事件标志来设定event对象的事件标志值,然后遍历等待在event事件对象上的等待线程链表,判断是否有线程的事件激活要求与当前event对象事件标志值匹配,如果有,则唤醒该线程。

函数参数


      参数  描述

     event  事件对象的句柄。

       set  发送的事件集;

函数返回

RT_EOK
使用场合

事件可使用于多种场合,它能够在一定程度上替代信号量,用于线程间同步。一个线程或中断服务例程发送一个事件给事件对象,而后等待的线程被唤醒并对相应的事件进行处理。但是它与信号量不同的是,事件的发送操作在事件未清除前,是不可累计的,而信号量的释放动作是累计的。 事件另外一个特性是,接收线程可等待多种事件,即多个事件对应一个线程或多个线程。同时按照线程等待的参数,可选择是“逻辑或”触发还是“逻辑与”触发。这个特性也是信号量等所不具备的,信号量只能识别单一的释放动作,而不能同时等待多种类型的释放。 如图所示:


各个事件类型可分别发送或一起发送给事件对象,而事件对象可以等待多个线程,它们仅对它们感兴趣的事件进行关注。当有它们感兴趣的事件发生时,线程就将被唤醒并进行后续的处理动作。

接下来在机智云gokit智能硬件开发板上演示如何创建并使用事件进行线程的同步。程序创建3个相同优先级的线程1、2、3,并初始化一个静态事件对象, 线程2定时发送事件 (事件3),线程3定时发送事件 (事件5),线程1等待在事件对象上以接收事件,演示了采用逻辑与和逻辑或的方式。

  1. /**
  2.   ************************
  3.   * File Name          : main.c
  4.   * Description        : Main program body
  5.   ************************
  6.   ** This notice applies to any and all portions of this file
  7.   * that are not between comment pairs USER CODE BEGIN and
  8.   * USER CODE END. Other portions of this file, whether
  9.   * inserted by the user or by software development tools
  10.   * are owned by their respective copyright owners.
  11.   *
  12.   * COPYRIGHT(c) 2017 STMicroelectronics
  13.   *
  14.   * Redistribution and use in source and binary forms, with or without modification,
  15.   * are permitted provided that the following conditions are met:
  16.   *   1. Redistributions of source code must retain the above copyright notice,
  17.   *      this list of conditions and the following disclaimer.
  18.   *   2. Redistributions in binary form must reproduce the above copyright notice,
  19.   *      this list of conditions and the following disclaimer in the documentation
  20.   *      and/or other materials provided with the distribution.
  21.   *   3. Neither the name of STMicroelectronics nor the names of its contributors
  22.   *      may be used to endorse or promote products derived from this software
  23.   *      without specific prior written permission.
  24.   *
  25.   * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
  26.   * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
  27.   * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
  28.   * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
  29.   * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
  30.   * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
  31.   * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
  32.   * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
  33.   * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
  34.   * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  35.   *
  36.   ************************
  37.   */
  38. /* Includes ------------------------------------------------------------------*/
  39. #include "main.h"
  40. #include "STM32f1xx_hal.h"
  41. #include "usart.h"
  42. #include "gpio.h"

  43. /* USER CODE BEGIN Includes */
  44. #include "rtthread.h"
  45. #include "string.h"
  46. /* USER CODE END Includes */

  47. /* Private variables ---------------------------------------------------------*/

  48. /* USER CODE BEGIN PV */
  49. /* Private variables ---------------------------------------------------------*/

  50. /* USER CODE END PV */

  51. /* Private function prototypes -----------------------------------------------*/
  52. void SystemClock_Config(void);

  53. /* USER CODE BEGIN PFP */
  54. /* Private function prototypes -----------------------------------------------*/

  55. /* USER CODE END PFP */

  56. /* USER CODE BEGIN 0 */
  57. //重映射串口1到rt_kprintf
  58. void rt_hw_console_output(const char *str)
  59. {
  60.     /* empty console output */
  61.         char aa='\r';
  62.           rt_enter_critical();

  63.                 while(*str!='\0')
  64.                 {
  65.                         if(*str=='\n')
  66.                         {
  67.                                 HAL_UART_Transmit(&huart1, (uint8_t *)&aa, 1, 10);
  68.                         }
  69.                                 HAL_UART_Transmit(&huart1, (uint8_t *)(str++), 1, 10);
  70.                 }
  71.                
  72.                 rt_exit_critical();
  73. }

  74. void rt_hw_us_delay(int us)
  75. {
  76.     rt_uint32_t delta;

  77.     /* 获得延时经过的tick数 */
  78.     us = us * (SysTick->LOAD/(1000000/RT_TICK_PER_SECOND));

  79.     /* 获得当前时间 */
  80.     delta = SysTick->VAL;

  81.     /* 循环获得当前时间,直到达到指定的时间后退出循环 */
  82.     while (delta - SysTick->VAL< us);
  83. }

  84. void rt_hw_ms_delay(int ms)
  85. {
  86.         int i=0,j=0;
  87.         for(j=0;j<ms;j++)
  88.         {
  89.                 for (i=0;i<2;i++)
  90.                 rt_hw_us_delay(500);
  91.         }
  92. }
  93. uint32_t rt_hw_delay_Init(void)
  94. {
  95. #if !defined(STM32F0xx)
  96.         uint32_t c;
  97.         
  98.     /* Enable TRC */
  99.     CoreDebug->DEMCR &= ~0x01000000;
  100.     CoreDebug->DEMCR |=  0x01000000;
  101.         
  102.     /* Enable counter */
  103.     DWT->CTRL &= ~0x00000001;
  104.     DWT->CTRL |=  0x00000001;
  105.         
  106.     /* Reset counter */
  107.     DWT->CYCCNT = 0;
  108.         
  109.         /* Check if DWT has started */
  110.         c = DWT->CYCCNT;
  111.         
  112.         /* 2 dummys */
  113.         __ASM volatile ("NOP");
  114.         __ASM volatile ("NOP");
  115.         
  116.         /* Return difference, if result is zero, DWT has not started */
  117.         return (DWT->CYCCNT - c);
  118. #else
  119.         /* Return OK */
  120.         return 1;
  121. #endif
  122. }
  123. void rt_hw_delay_us(__IO uint32_t micros)
  124. {
  125. #if !defined(STM32F0xx)
  126.         uint32_t start = DWT->CYCCNT;
  127.         
  128.         /* Go to number of cycles for system */
  129.         micros *= (HAL_RCC_GetHCLKFreq() / 1000000);
  130.         
  131.         /* Delay till end */
  132.         while ((DWT->CYCCNT - start) < micros);
  133. #else
  134.         /* Go to clock cycles */
  135.         micros *= (SystemCoreClock / 1000000) / 5;
  136.         
  137.         /* Wait till done */
  138.         while (micros--);
  139. #endif
  140. }
  141. void rt_hw_delay_ms(__IO uint32_t mills)
  142. {
  143.         rt_hw_delay_us(1000*mills);
  144. }
  145. /*
  146. * 程序清单:事件
  147. *
  148. * 这个程序会创建3个动态线程及初始化一个静态事件对象
  149. * 一个线程1等待在事件对象上以接收事件;
  150. * 一个线程2定时发送事件 (事件3)
  151. * 一个线程3定时发送事件 (事件5)
  152. */

  153. /* 指向线程控制块的指针 */
  154. static rt_thread_t tid1 = RT_NULL;
  155. static rt_thread_t tid2 = RT_NULL;
  156. static rt_thread_t tid3 = RT_NULL;

  157. /* 事件控制块 */
  158. static struct rt_event event;

  159. /* 线程1入口函数 */
  160. static void thread1_entry(void *param)
  161. {
  162.     rt_uint32_t e;

  163.     while (1)
  164.     {
  165.         /* 以逻辑与的方式接收事件 */
  166.         if (rt_event_recv(&event, ((1 << 3) | (1 << 5)),
  167.             RT_EVENT_FLAG_AND | RT_EVENT_FLAG_CLEAR,
  168.             RT_WAITING_FOREVER, &e) == RT_EOK)
  169.         {
  170.             rt_kprintf("thread1: AND recv event 0x%x\n", e);
  171.         }

  172.         rt_kprintf("thread1: delay 1s to prepare second event\n");
  173.         rt_thread_delay(1000);

  174.         /* 以逻辑或的方式接收事件 */
  175.         if (rt_event_recv(&event, ((1 << 3) | (1 << 5)),
  176.             RT_EVENT_FLAG_OR | RT_EVENT_FLAG_CLEAR,
  177.             RT_WAITING_FOREVER, &e) == RT_EOK)
  178.         {
  179.             rt_kprintf("thread1: OR recv event 0x%x\n", e);
  180.         }

  181.         rt_thread_delay(5);
  182.     }
  183. }

  184. /* 线程2入口函数 */
  185. static void thread2_entry(void *param)
  186. {
  187.     /* 线程2持续地发送事件#3 */
  188.     while (1)
  189.     {
  190.         rt_kprintf("thread2: send event1\n");
  191.         rt_event_send(&event, (1 << 3));

  192.         rt_thread_delay(10);
  193.     }
  194. }

  195. /* 线程3入口函数 */
  196. static void thread3_entry(void *param)
  197. {
  198.     /* 线程3持续地发送事件#5 */
  199.     while (1)
  200.     {
  201.         rt_kprintf("thread3: send event2\n");
  202.         rt_event_send(&event, (1 << 5));

  203.         rt_thread_delay(20);
  204.     }
  205. }

  206. /* USER CODE END 0 */

  207. int main(void)
  208. {

  209.   /* USER CODE BEGIN 1 */

  210.   /* USER CODE END 1 */

  211.   /* MCU Configuration----------------------------------------------------------*/

  212. //  /* Reset of all peripherals, Initializes the Flash interface and the Systick. */
  213. //  HAL_Init();

  214. //  /* USER CODE BEGIN Init */

  215. //  /* USER CODE END Init */

  216. //  /* Configure the system clock */
  217. //  SystemClock_Config();

  218. //  /* USER CODE BEGIN SysInit */

  219. //  /* USER CODE END SysInit */

  220. //  /* Initialize all configured peripherals */
  221. //  MX_GPIO_Init();
  222. //  MX_USART1_UART_Init();

  223.   /* USER CODE BEGIN 2 */
  224.         
  225. /* 初始化事件对象 */
  226.     rt_event_init(&event, "event", RT_IPC_FLAG_FIFO);

  227.     /* 创建线程1 */
  228.     tid1 = rt_thread_create("t1",
  229.         thread1_entry, /* 线程入口是thread1_entry */
  230.         RT_NULL, /* 入口参数是RT_NULL */
  231.         512,
  232.                                 2,
  233.                                 20);
  234.     if (tid1 != RT_NULL)
  235.         rt_thread_startup(tid1);

  236.     /* 创建线程2 */
  237.     tid2 = rt_thread_create("t2",
  238.         thread2_entry, /* 线程入口是thread2_entry */
  239.         RT_NULL, /* 入口参数是RT_NULL */
  240.         512,
  241.                                 2,
  242.                                 20);
  243.     if (tid2 != RT_NULL)
  244.         rt_thread_startup(tid2);

  245.     /* 创建线程3 */
  246.     tid3 = rt_thread_create("t3",
  247.         thread3_entry, /* 线程入口是thread3_entry */
  248.         RT_NULL, /* 入口参数是RT_NULL */
  249.                                 512,
  250.                                 2,
  251.                                 20);
  252.     if (tid3 != RT_NULL)
  253.         rt_thread_startup(tid3);
  254.         
  255.                 rt_hw_delay_Init();
  256.                         
  257.                 printf("\r\n机智云  只为智能硬件而生\r\n");
  258.                 printf("Gizwits Smart Cloud for Smart Products\r\n");
  259.                 printf("链接|增值|开放|中立|安全|自有|自由|生态\r\n");
  260.                 printf("www.gizwits.com\r\n");
  261.                 printf("\r\nGokit RT-Thread Demo\r\n\r\n");
  262.                
  263.                 return 0;
  264.         
  265.   /* USER CODE END 2 */

  266.   /* Infinite loop */
  267.   /* USER CODE BEGIN WHILE */
  268. //  while (1)
  269. //  {
  270.   /* USER CODE END WHILE */

  271.   /* USER CODE BEGIN 3 */
  272. //               
  273. //  }
  274.   /* USER CODE END 3 */

  275. }

  276. /** System Clock Configuration
  277. */
  278. void SystemClock_Config(void)
  279. {

  280.   RCC_OscInitTypeDef RCC_OscInitStruct;
  281.   RCC_ClkInitTypeDef RCC_ClkInitStruct;

  282.     /**Initializes the CPU, AHB and APB busses clocks
  283.     */
  284.   RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE;
  285.   RCC_OscInitStruct.HSEState = RCC_HSE_ON;
  286.   RCC_OscInitStruct.HSEPredivValue = RCC_HSE_PREDIV_DIV1;
  287.   RCC_OscInitStruct.HSIState = RCC_HSI_ON;
  288.   RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;
  289.   RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE;
  290.   RCC_OscInitStruct.PLL.PLLMUL = RCC_PLL_MUL9;
  291.   if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK)
  292.   {
  293.     _Error_Handler(__FILE__, __LINE__);
  294.   }

  295.     /**Initializes the CPU, AHB and APB busses clocks
  296.     */
  297.   RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK|RCC_CLOCKTYPE_SYSCLK
  298.                               |RCC_CLOCKTYPE_PCLK1|RCC_CLOCKTYPE_PCLK2;
  299.   RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK;
  300.   RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;
  301.   RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV2;
  302.   RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1;

  303.   if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_2) != HAL_OK)
  304.   {
  305.     _Error_Handler(__FILE__, __LINE__);
  306.   }

  307.     /**Configure the Systick interrupt time
  308.     */
  309.   HAL_SYSTICK_Config(HAL_RCC_GetHCLKFreq()/1000);

  310.     /**Configure the Systick
  311.     */
  312.   HAL_SYSTICK_CLKSourceConfig(SYSTICK_CLKSOURCE_HCLK);

  313.   /* SysTick_IRQn interrupt configuration */
  314.   HAL_NVIC_SetPriority(SysTick_IRQn, 0, 0);
  315. }

  316. /* USER CODE BEGIN 4 */

  317. /* USER CODE END 4 */

  318. /**
  319.   * @brief  This function is executed in case of error occurrence.
  320.   * @param  None
  321.   * @retval None
  322.   */
  323. void _Error_Handler(char * file, int line)
  324. {
  325.   /* USER CODE BEGIN Error_Handler_Debug */
  326.   /* User can add his own implementation to report the HAL error return state */
  327.   while(1)
  328.   {
  329.   }
  330.   /* USER CODE END Error_Handler_Debug */
  331. }

  332. #ifdef USE_FULL_ASSERT

  333. /**
  334.    * @brief Reports the name of the source file and the source line number
  335.    * where the assert_param error has occurred.
  336.    * @param file: pointer to the source file name
  337.    * @param line: assert_param error line source number
  338.    * @retval None
  339.    */
  340. void assert_failed(uint8_t* file, uint32_t line)
  341. {
  342.   /* USER CODE BEGIN 6 */
  343.   /* User can add his own implementation to report the file name and line number,
  344.     ex: printf("Wrong parameters value: file %s on line %d\r\n", file, line) */
  345.   /* USER CODE END 6 */

  346. }

  347. #endif

  348. /**
  349.   * @}
  350.   */

  351. /**
  352.   * @}
  353. */

  354. /******** (C) COPYRIGHT STMicroelectronics **END OF FILE**/
复制代码
串口信息如下:


源码下载:
Gokit_RT-Thread.zip (8.67 MB, 下载次数: 1, 售价: 1 金钱)


iot开发平台
1、机智云QQ群:G1群:104975951 G2群:491509598 G3群:287087942
机智云爱好者-APP开发群: 599735135
QQ群目前非常活跃,欢迎大家参与进来,交流,讨论,答疑,解惑~~
2、机智云微信公众号: 机智云 gizwits、 机智云智能宠物屋go-iot
关注机智云Gizwits官方公众号随时掌握最新资讯和活动信息
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

加入Q群 返回顶部

版权与免责声明 © 2006-2024 Gizwits IoT Technology Co., Ltd. ( 粤ICP备11090211号 )

快速回复 返回顶部 返回列表