Управление офисным освещением по Wi-Fi. Часть 3: Управляем светом

от автора


Сегодня мы, наконец, завершим данный цикл, соединив знания и наработки, полученные в первой и второй частях: будем управлять офисными светильники с помощью сенсорных кнопок по Wi-Fi, используя протокол ModBus TCP через шлюз ModBus-DALI.

Краткое содержание предыдущих серий

Мы научились, используя Wi-Fi модуль WINC1500 от Atmel, коннектится к сторонней точке доступа, а так же подключаться в качестве клиента к TCP-серверу и передавать в него данные. Так же была освоена работа с сенсорными кнопками и слайдерами, о чем было заснято соответствующее видео.

Вспомним общую концепцию. К отладке с микроконтроллером Atmel SAMD21 подключена плата расширения Q-touch, на которой расположены три кнопки, слайдер и ротор. Мы будем использовать одну кнопку для включения освещения, другую для выключения и слайдер для плавной регулировки яркости. Ко второму порту подключена плата с модулем WINC1500. Наше устройство (вернее, прототип) будет выступать в качестве TCP-клиента. Сервером будет выступать DALIGW1 — ModBus TCP устройство, преобразующее ModBus команды в команды протокола DALI (Digital Addressable Lighting Interface) — специальный протокол для цифрового управления освещением. Таким образом, мы будем получать значения положения сенсоров и формировать запросы в соответствии с протоколом Modbus и отправлять по Wi-fi в шлюз.

TCP клиент

Для простоты не будем создавать точку доступа, сканировать доступные сети и просить пользователя подключиться к одной из них, а зададим нужные настройки для нашей офисной локальной сети прямо в коде.
В начале работы можно открыть один из созданных ранее проектов (по предыдущим частям статьи) или проект-пример для Qtouch или WINC1500, и добавить нужные модули с помощью Wizard’а, как описывалось в одной из наших предыдущих статей.

Настраиваем в файле main.h параметры сети:

#define MAIN_WLAN_SSID                   "Habrahabr"    // имя сети #define MAIN_WLAN_AUTH                    M2M_WIFI_SEC_WPA_PSK    // тип шифрования #define MAIN_WLAN_PSK                     "123456789" // пароль к сети #define MAIN_WIFI_M2M_PRODUCT_NAME        "Hello world!\n\r" #define MAIN_WIFI_M2M_SERVER_IP			   0xc0a81490   // IP адрес сервера (шлюза)  #define MAIN_WIFI_M2M_SERVER_PORT         (502)   // порт сервера. обычно 502 для ModBus TCP 

В main.c настраиваем библиотеку для работы с winc1500 в режиме tcp клиент.

     //Initialize the BSP.  	nm_bsp_init();  	// Initialize socket address structure. 	addr.sin_family = AF_INET; 	addr.sin_port = _htons(MAIN_WIFI_M2M_SERVER_PORT); 	addr.sin_addr.s_addr = _htonl(MAIN_WIFI_M2M_SERVER_IP);  	// Initialize Wi-Fi parameters structure.  	memset((uint8_t *)¶m, 0, sizeof(tstrWifiInitParam));  	// Initialize Wi-Fi driver with data and status callbacks. 	param.pfAppWifiCb = wifi_cb; 	ret = m2m_wifi_init(¶m); 	if (M2M_SUCCESS != ret) 	 { 		printf("main: m2m_wifi_init call error!(%d)\r\n", ret); 		while (1); 	}  	// Initialize socket module  	socketInit(); 	registerSocketCallback(socket_cb, NULL);  	// Connect to router.  	m2m_wifi_connect((char *)MAIN_WLAN_SSID, sizeof(MAIN_WLAN_SSID), MAIN_WLAN_AUTH, (char *)MAIN_WLAN_PSK, M2M_WIFI_CH_ALL); 

Не забываем про наши любимые callback’и. Прописываем функции обработки события по сети.

Код callback'ов

static void socket_cb(SOCKET sock, uint8_t u8Msg, void *pvMsg) { 	switch (u8Msg) 	{ 	   // Socket connected  	   case SOCKET_MSG_CONNECT: 	   { 	      tstrSocketConnectMsg *pstrConnect = (tstrSocketConnectMsg *)pvMsg; 		  if (pstrConnect && pstrConnect->s8Error >= 0) 		  { 		     printf("socket_cb: connect success!\r\n");		 		     if (button_pressed!=0) 		     { 		        send(tcp_client_socket, &data_to_send, 12, 0); 			    printf("socket_number after connection: %d\r\n", tcp_client_socket); 		     } 		     close(tcp_client_socket); 		     delay_ms(50); 		  }  		  else 		  { 		  	  printf("socket_cb: connect error!\r\n"); 			  close(tcp_client_socket); 			  tcp_client_socket = -1; 		  } 	   } 	   break;  	   // Message send  	   case SOCKET_MSG_SEND: 	   { 		  printf("socket_cb: send success!\r\n"); 		  recv(tcp_client_socket, gau8SocketTestBuffer, sizeof(gau8SocketTestBuffer), 0); 	   } 	   break;  	   // Message receive  	   case SOCKET_MSG_RECV: 	   { 		  tstrSocketRecvMsg *pstrRecv = (tstrSocketRecvMsg *)pvMsg; 		  if (pstrRecv && pstrRecv->s16BufferSize > 0)  		  { 			  printf("socket_cb: recv success!\r\n"); 			  printf("TCP Client Test Complete!\r\n"); 		  } 		  else 		  { 			  printf("socket_cb: recv error!\r\n"); 			  close(tcp_client_socket); 			  tcp_client_socket = -1; 		  } 	    } 	    break;  	default: 		break; 	} }  static void wifi_cb(uint8_t u8MsgType, void *pvMsg) { 	 	printf("wifi_cb: u8MsgType= %d\n",u8MsgType); 	switch (u8MsgType) { 	case M2M_WIFI_RESP_CON_STATE_CHANGED: 	{ 		tstrM2mWifiStateChanged *pstrWifiState = (tstrM2mWifiStateChanged *)pvMsg; 		if (pstrWifiState->u8CurrState == M2M_WIFI_CONNECTED)  		{ 			printf("wifi_cb: M2M_WIFI_RESP_CON_STATE_CHANGED: CONNECTED\r\n"); 			m2m_wifi_request_dhcp_client(); 		} 		else 		{ 			if (pstrWifiState->u8CurrState == M2M_WIFI_DISCONNECTED)  			{ 			   printf("wifi_cb: M2M_WIFI_RESP_CON_STATE_CHANGED: DISCONNECTED\r\n"); 			   wifi_connected = 0; 			   m2m_wifi_connect((char *)MAIN_WLAN_SSID, sizeof(MAIN_WLAN_SSID), MAIN_WLAN_AUTH, (char *)MAIN_WLAN_PSK, M2M_WIFI_CH_ALL); 			} 		} 		break; 	}  	case M2M_WIFI_REQ_DHCP_CONF: 	{ 		uint8_t *pu8IPAddress = (uint8_t *)pvMsg; 		wifi_connected = 1; 		printf("wifi_cb: M2M_WIFI_REQ_DHCP_CONF: IP is %u.%u.%u.%u\r\n", pu8IPAddress[0], pu8IPAddress[1], pu8IPAddress[2], pu8IPAddress[3]); 		printf("wifi_cb: M2M_WIFI_REQ_DHCP_CONF: getaway IP is %u.%u.%u.%u\r\n", pu8IPAddress[4], pu8IPAddress[5], pu8IPAddress[6], pu8IPAddress[7]); 		printf("wifi_cb: M2M_WIFI_REQ_DHCP_CONF: DNS IP is %u.%u.%u.%u\r\n", pu8IPAddress[8], pu8IPAddress[9], pu8IPAddress[10], pu8IPAddress[11]); 		printf("wifi_cb: M2M_WIFI_REQ_DHCP_CONF: mask is %u.%u.%u.%u\r\n", pu8IPAddress[12], pu8IPAddress[13], pu8IPAddress[14], pu8IPAddress[15]); 		break; 	}  	default: 		break; 	} } 

Практически это и есть весь код, необходимый для работы с winc в качестве tcp клиента.

Qtouch

Весь код, описанный во второй части нашей статьи для обработки кнопок и слайдера, переносится полностью, нужно лишь добавить формирование и отправку посылок по tcp.

Собираем все вместе

Протокол ModBus прост, открыт и горячо любим в системах автоматики, потому информации о нем в интернете можно найти массу. Вот что говорит вики. Мы, как уже упоминалось, будем использовать его модификацицию TCP, рассчитанную на работу в локальных сетях.
Так как задача у нас учебно-тренировочная, то реализовывать красивую библиотеку ModBus мы не станем (через отдельные функции, с указанием регистра, данных и пр.), а просто будем формировать соответствующий набор байтиков и отправлять их в открытый сокет. Пакет Modbus в нашем случае будет меняться только в части данных, записываемых в регистр (в соответствии с протоколом работы с нашим шлюзом). Используем команду записи в один регистр — код команды 0x06.
Поля пакета:

  • Transaction ID, Protocol ID в соответствии со спецификацией Modbus
  • длина = 6
  • Unit ID в соответствии со спецификацией Modbus
  • код команды 0x06
  • номер регистра 25 (0x0019)
  • значение регистра: код команды DALI, адрес светильника/группы светильников и данные для команды (в зависимости от типа команды).

Погружать читателя в специфику протокола DALI целью настоящей статьи не ставилось, потому просто перечислю используемые команды с кратким пояснением:

  • OFF — немедленно выключить указанный светильник, повесим на левую сенсорную кнопку
  • RECALL MAX LEVEL — плавно установить максимальную яркость светильника, правая кнопка
  • ON AND STEP UP — если светильник выключен, установить минимальную яркость. будем вызывать перед RECALL MAX LEVEL
  • DIRECT ARC POWER X- плавно установить указанную яркость X для светильника, повесим на слайдер

Примечание: «ученые спорят» о том как правильнее ModBus мастеру (в роли которого выступает наша отладка) общаться со слейвами — закрывать сокет после завершения текущего сеанса связи или держать его открытым. Мы в нашем примере пошли первым путем, исходя из того что на шине могут оказаться и другие мастера, желающие подключиться к нашему шлюзу. Но тут правильного подхода, скорее всего нет, и все зависит от мировоззрения разработчика.

На этом всё. Заключительная часть вышла не особенно объемной, но весь основной фарш был в первых двух частях. Надеюсь было интересно.

Итоговый код main

int main(void) { 	uint8_t button1_state; 	uint8_t button2_state; 	uint8_t slider_state; 	uint8_t slider_position; 	tstrWifiInitParam param; 	int8_t ret; 	struct sockaddr_in addr; 	// Initialize and configure system and generic clocks. 	// Use conf_clocks.h to configure system and generic clocks. 	system_init(); 	// Enable global interrupts.	  	system_interrupt_enable_global(); 	//Initialize delay service. 	delay_init();     //Initialize timer. (RTC actually) 	timer_init(); 	//Initialize QTouch library and configure touch sensors. 	touch_sensors_init(); 	// Configure port pins 	configure_port_pins(); 	// Turn off all extension board LEDs 	port_pin_set_output_level(LED_0_PIN, 1); 	port_pin_set_output_level(LED_1_PIN, 1); 	port_pin_set_output_level(LED_2_PIN, 1); 	port_pin_set_output_level(LED_3_PIN, 1); 	port_pin_set_output_level(LED_4_PIN, 1); 	port_pin_set_output_level(LED_5_PIN, 1); 	port_pin_set_output_level(LED_6_PIN, 1); 	port_pin_set_output_level(LED_7_PIN, 1); 	port_pin_set_output_level(LED_8_PIN, 1); 	port_pin_set_output_level(LED_9_PIN, 1); 	port_pin_set_output_level(LED_R_PIN, 1); 	port_pin_set_output_level(LED_G_PIN, 1); 	port_pin_set_output_level(LED_B_PIN, 1);  	PWM_Count = 0; 	NVMCTRL->CTRLB.bit.SLEEPPRM = 3; 	system_set_sleepmode(SYSTEM_SLEEPMODE_STANDBY); 	// Initialize the UART console.  	configure_console(); 	printf(STRING_HEADER);  	//Initialize the BSP.  	nm_bsp_init();  	// Initialize socket address structure. 	addr.sin_family = AF_INET; 	addr.sin_port = _htons(MAIN_WIFI_M2M_SERVER_PORT); 	addr.sin_addr.s_addr = _htonl(MAIN_WIFI_M2M_SERVER_IP);  	// Initialize Wi-Fi parameters structure.  	memset((uint8_t *)¶m, 0, sizeof(tstrWifiInitParam));  	// Initialize Wi-Fi driver with data and status callbacks. 	param.pfAppWifiCb = wifi_cb; 	ret = m2m_wifi_init(¶m); 	if (M2M_SUCCESS != ret) 	 { 		printf("main: m2m_wifi_init call error!(%d)\r\n", ret); 		while (1); 	}  	// Initialize socket module  	socketInit(); 	registerSocketCallback(socket_cb, NULL);  	// Connect to router.  	m2m_wifi_connect((char *)MAIN_WLAN_SSID, sizeof(MAIN_WLAN_SSID), MAIN_WLAN_AUTH, (char *)MAIN_WLAN_PSK, M2M_WIFI_CH_ALL);  	while (1) 	{ 		// Goto STANDBY sleep mode, unless woken by timer or PTC interrupt. 		system_sleep(); 		// Start touch sensor measurement, if touch_time.time_to_measure_touch flag is set by timer. 		touch_sensors_measure(); 		if (measure_tick<INACTIVITY_DELAY) 		{ 			measure_tick++; 		} 		if ((p_mutlcap_measure_data->measurement_done_touch == 1u))  		{ 			p_mutlcap_measure_data->measurement_done_touch = 0u;  			// Get touch sensor states 			button1_state = GET_MUTLCAP_SENSOR_STATE(0); 			button2_state = GET_MUTLCAP_SENSOR_STATE(1); 			rotor_state = GET_MUTLCAP_SENSOR_STATE(2); 			slider_state = GET_MUTLCAP_SENSOR_STATE(3);  			if (button1_state)  			{ 				if(button_pressed!=1) 				{ 					port_pin_set_output_level(LED_8_PIN, 0); 					button_pressed=1;                                         form_modbus_packet(0x05,DALI_OFF ); 					tcp_client_socket = socket(AF_INET, SOCK_STREAM, 0); 					ret=connect(tcp_client_socket, (struct sockaddr *)&addr, sizeof(struct sockaddr_in)); 				} 			} 			else  			{ 				port_pin_set_output_level(LED_8_PIN, 1); 				if (button_pressed==1) 				{ 					button_pressed=0; 				} 			}  			if (button2_state) 			{ 				if(button_pressed!=2) 				{ 					port_pin_set_output_level(LED_9_PIN, 0); 					button_pressed=2;                                         form_modbus_packet(0x05,DALI_RECALL_MAX_LEVEL); 					tcp_client_socket = socket(AF_INET, SOCK_STREAM, 0); 					ret=connect(tcp_client_socket, (struct sockaddr *)&addr, sizeof(struct sockaddr_in)); 				} 			}  			else 			{ 				port_pin_set_output_level(LED_9_PIN, 1); 				if (button_pressed==2) 				{ 				   button_pressed=0; 				} 			}  			// Clear all slider controlled LEDs 			port_pin_set_output_level(LED_0_PIN, 1); 			port_pin_set_output_level(LED_1_PIN, 1); 			port_pin_set_output_level(LED_2_PIN, 1); 			port_pin_set_output_level(LED_3_PIN, 1); 			port_pin_set_output_level(LED_4_PIN, 1); 			port_pin_set_output_level(LED_5_PIN, 1); 			port_pin_set_output_level(LED_6_PIN, 1); 			port_pin_set_output_level(LED_7_PIN, 1);  			// If slider is activated 			if(slider_state) 			{ 				 // Parse slider position 				slider_position = GET_MUTLCAP_ROTOR_SLIDER_POSITION(1); 				 			//	slider_position = slider_position >> 5u; 				printf("slider_position= %d\n",slider_position); 				if (measure_tick==INACTIVITY_DELAY) 				{ 					button_pressed=4;                                         form_modbus_packet(0x05,DALI_ON_AND_STEP_UP); 					tcp_client_socket = socket(AF_INET, SOCK_STREAM, 0); 					ret=connect(tcp_client_socket, (struct sockaddr *)&addr, sizeof(struct sockaddr_in)); 					delay_ms(500);					 				} 				else 				{ 				   if (button_pressed==4) 				   { 						button_pressed=0; 				   } 				   if ((button_pressed!=3)&&(previous_slider_position!=slider_position)) 				   { 						button_pressed=3; 						brightness=slider_position;//<<5u; 						if (brightness==255) 						{ 							brightness=254; 						} 						printf("brightness= %d \n",brightness);                                                 form_modbus_packet(0x04,brightness); 						tcp_client_socket = socket(AF_INET, SOCK_STREAM, 0); 						ret=connect(tcp_client_socket, (struct sockaddr *)&addr, sizeof(struct sockaddr_in)); 						previous_slider_position=slider_position; 				   } 				   else 				   { 				      if (button_pressed==3) 					  { 						 button_pressed=0; 					  } 				   } 				}  				measure_tick=0;				 				switch(slider_position) 				{ 					case 0: 						port_pin_set_output_level(LED_0_PIN, 0); 						break; 					case 1: 						port_pin_set_output_level(LED_0_PIN, 0); 						port_pin_set_output_level(LED_1_PIN, 0); 						break; 					case 2: 						port_pin_set_output_level(LED_0_PIN, 0); 						port_pin_set_output_level(LED_1_PIN, 0); 						port_pin_set_output_level(LED_2_PIN, 0); 						break; 					case 3: 						port_pin_set_output_level(LED_0_PIN, 0); 						port_pin_set_output_level(LED_1_PIN, 0); 						port_pin_set_output_level(LED_2_PIN, 0); 						port_pin_set_output_level(LED_3_PIN, 0); 						break; 					case 4: 						port_pin_set_output_level(LED_0_PIN, 0); 						port_pin_set_output_level(LED_1_PIN, 0); 						port_pin_set_output_level(LED_2_PIN, 0); 						port_pin_set_output_level(LED_3_PIN, 0); 						port_pin_set_output_level(LED_4_PIN, 0); 						break; 					case 5: 						port_pin_set_output_level(LED_0_PIN, 0); 						port_pin_set_output_level(LED_1_PIN, 0); 						port_pin_set_output_level(LED_2_PIN, 0); 						port_pin_set_output_level(LED_3_PIN, 0); 						port_pin_set_output_level(LED_4_PIN, 0); 						port_pin_set_output_level(LED_5_PIN, 0); 						break; 					case 6: 						port_pin_set_output_level(LED_0_PIN, 0); 						port_pin_set_output_level(LED_1_PIN, 0); 						port_pin_set_output_level(LED_2_PIN, 0); 						port_pin_set_output_level(LED_3_PIN, 0); 						port_pin_set_output_level(LED_4_PIN, 0); 						port_pin_set_output_level(LED_5_PIN, 0); 						port_pin_set_output_level(LED_6_PIN, 0); 						break; 					case 7: 						port_pin_set_output_level(LED_0_PIN, 0); 						port_pin_set_output_level(LED_1_PIN, 0); 						port_pin_set_output_level(LED_2_PIN, 0); 						port_pin_set_output_level(LED_3_PIN, 0); 						port_pin_set_output_level(LED_4_PIN, 0); 						port_pin_set_output_level(LED_5_PIN, 0); 						port_pin_set_output_level(LED_6_PIN, 0); 						port_pin_set_output_level(LED_7_PIN, 0); 						break; 					default: 						port_pin_set_output_level(LED_0_PIN, 1); 						port_pin_set_output_level(LED_1_PIN, 1); 						port_pin_set_output_level(LED_2_PIN, 1); 						port_pin_set_output_level(LED_3_PIN, 1); 						port_pin_set_output_level(LED_4_PIN, 1); 						port_pin_set_output_level(LED_5_PIN, 1); 						port_pin_set_output_level(LED_6_PIN, 1); 						port_pin_set_output_level(LED_7_PIN, 1); 						break; 				} 			} 		}//measurement done flag 		m2m_wifi_handle_events(NULL);  		if (wifi_connected == M2M_WIFI_CONNECTED) 		{ 			// Open client socket. 			if (tcp_client_socket < 0) 			{ 				if ((tcp_client_socket = socket(AF_INET, SOCK_STREAM, 0)) < 0) 				{ 					printf("main: failed to create TCP client socket error!\r\n"); 					continue; 				} 				// Connect server 				 				printf("socket_number new connection: %d\r\n", tcp_client_socket); 				ret=connect(tcp_client_socket, (struct sockaddr *)&addr, sizeof(struct sockaddr_in)); 				printf("ret value: %d\r\n", ret); 				if (ret < 0) 				{ 					close(tcp_client_socket); 					tcp_client_socket = -1; 				} 			} 	   }     }//while(1) }//main 

ссылка на оригинал статьи https://habrahabr.ru/post/276007/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *