Управління офісним освітленням по 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 Test Client 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



Джерело: Хабрахабр

0 коментарів

Тільки зареєстровані та авторизовані користувачі можуть залишати коментарі.