terminal.c 11.3 KB
Newer Older
Bc. Petr Elexa's avatar
Bc. Petr Elexa committed
1 2 3 4 5 6 7
/**
 *  @file
 *  @brief Terminal client for access control system (ACS).
 *
 *  @author Petr Elexa
 *  @see LICENSE
 *
8 9 10
 */

#include "terminal.h"
Bc. Petr Elexa's avatar
Bc. Petr Elexa committed
11
#include "board.h"
12
#include "weigand.h"
Bc. Petr Elexa's avatar
Bc. Petr Elexa committed
13
#include "static_cache.h"
14 15 16
#include "FreeRTOS.h"
#include "task.h"
#include "stream_buffer.h"
Bc. Petr Elexa's avatar
Bc. Petr Elexa committed
17
#include "can/can_term_driver.h"
18
#include "acs_can_protocol.h"
Bc. Petr Elexa's avatar
Bc. Petr Elexa committed
19
#include <stdio.h>
Bc. Petr Elexa's avatar
Bc. Petr Elexa committed
20
#include <string.h>
21

Bc. Petr Elexa's avatar
Bc. Petr Elexa committed
22 23 24 25 26 27 28
/*****************************************************************************
 * Private types/enumerations/variables
 ****************************************************************************/

// How long to wait for user request.
// This also controls intensity of door status messages.
static const uint16_t USER_REQUEST_WAIT_MS = 1500;
29

30 31
static const uint16_t USER_REQUEST_MIN_PERIOD_MS = 1000;

Bc. Petr Elexa's avatar
Bc. Petr Elexa committed
32 33
// Cache entry type mapping to our type.
typedef cache_item_t term_cache_item_t;  // 4 bytes
34

Bc. Petr Elexa's avatar
Bc. Petr Elexa committed
35
// Available cache values.
36
enum term_cache_reader
Bc. Petr Elexa's avatar
Bc. Petr Elexa committed
37
{
38 39 40 41
  cache_reader_none = 0,
  cache_reader_A = 1,
  cache_reader_B = 2,
  cache_reader_all = 3
Bc. Petr Elexa's avatar
Bc. Petr Elexa committed
42 43
};

Bc. Petr Elexa's avatar
Bc. Petr Elexa committed
44
// Address for currently active master.
45
static uint16_t _act_master = ACS_RESERVED_ADDR;
Bc. Petr Elexa's avatar
Bc. Petr Elexa committed
46

Bc. Petr Elexa's avatar
Bc. Petr Elexa committed
47
// Flag for master alive broadcast timeout.
48
static bool _master_timeout = true;
49

Bc. Petr Elexa's avatar
Bc. Petr Elexa committed
50
// Timer handle for master timeout.
51
static TimerHandle_t _act_timer = NULL;
52

Bc. Petr Elexa's avatar
Bc. Petr Elexa committed
53 54
// Timer ID for master timeout.
static const uint32_t _act_timer_id = TERMINAL_TIMER_ID;
55

56
// Last door open(true) / close(false) status
Bc. Petr Elexa's avatar
Bc. Petr Elexa committed
57 58 59 60 61
static bool _last_door_state[ACS_READER_COUNT] = {false, false};

/*****************************************************************************
 * Private functions
 ****************************************************************************/
62

Bc. Petr Elexa's avatar
Bc. Petr Elexa committed
63
// Map reader index to correct cache value.
64
static inline uint8_t map_reader_idx_to_cache(uint8_t reader_idx)
Bc. Petr Elexa's avatar
Bc. Petr Elexa committed
65
{
66
  return (reader_idx == ACS_READER_A_IDX ? cache_reader_A : cache_reader_B);
Bc. Petr Elexa's avatar
Bc. Petr Elexa committed
67 68
}

Bc. Petr Elexa's avatar
Bc. Petr Elexa committed
69
static inline void _terminal_user_authorized(uint8_t reader_idx)
Bc. Petr Elexa's avatar
Bc. Petr Elexa committed
70 71
{
  DEBUGSTR("auth OK\n");
72
  reader_unlock(reader_idx, BEEP_ON_SUCCESS, OK_LED_ON_SUCCESS);
Bc. Petr Elexa's avatar
Bc. Petr Elexa committed
73 74
}

Bc. Petr Elexa's avatar
Bc. Petr Elexa committed
75
static inline void __terminal_user_not_authorized(uint8_t reader_idx)
Bc. Petr Elexa's avatar
Bc. Petr Elexa committed
76
{
Bc. Petr Elexa's avatar
Bc. Petr Elexa committed
77
  (void)reader_idx;
Bc. Petr Elexa's avatar
Bc. Petr Elexa committed
78 79
  DEBUGSTR("auth FAIL\n");
}
80

Bc. Petr Elexa's avatar
Bc. Petr Elexa committed
81
// Callback for timer dedicated to master master activity.
82 83 84 85
static void _timer_callback(TimerHandle_t pxTimer)
{
  configASSERT(pxTimer);

Bc. Petr Elexa's avatar
Bc. Petr Elexa committed
86
  // Which timer expired.
87 88 89 90
  uint32_t id = (uint32_t) pvTimerGetTimerID(pxTimer);

  if (id == _act_timer_id)
  {
Bc. Petr Elexa's avatar
Bc. Petr Elexa committed
91
    // Active master timeout after T = 2 * MASTER_ALIVE_TIMEOUT.
92 93 94 95
    portENTER_CRITICAL();
    if (_master_timeout == true)
    {
      _act_master = ACS_RESERVED_ADDR;
96
      Board_LED_Set(BOARD_LED_STATUS, false);
97 98 99 100 101 102
    }
    _master_timeout = true;
    portEXIT_CRITICAL();
  }
}

Bc. Petr Elexa's avatar
Bc. Petr Elexa committed
103 104 105 106
/*****************************************************************************
 * Public functions
 ****************************************************************************/

Bc. Petr Elexa's avatar
Bc. Petr Elexa committed
107
void term_can_error(uint32_t error_info)
Bc. Petr Elexa's avatar
Bc. Petr Elexa committed
108
{
Bc. Petr Elexa's avatar
Bc. Petr Elexa committed
109
  (void)error_info;
Bc. Petr Elexa's avatar
Bc. Petr Elexa committed
110 111 112 113 114 115
  /* TODO Process CAN bus errors. */
}

void term_can_send(uint8_t msg_obj_num)
{
  (void)msg_obj_num;
Bc. Petr Elexa's avatar
Bc. Petr Elexa committed
116 117
}

Bc. Petr Elexa's avatar
Bc. Petr Elexa committed
118
// This is called from interrupt. We must not block.
119
void term_can_recv(uint8_t msg_obj_num)
120
{
121
  CCAN_MSG_OBJ_T msg_obj;
Bc. Petr Elexa's avatar
Bc. Petr Elexa committed
122
  // Determine which CAN message has been received.
123
  msg_obj.msgobj = msg_obj_num;
Bc. Petr Elexa's avatar
Bc. Petr Elexa committed
124
  // Now load up the msg_obj structure with the CAN message.
125 126
  LPC_CCAN_API->can_receive(&msg_obj);

127
  acs_msg_head_t head;
Bc. Petr Elexa's avatar
Bc. Petr Elexa committed
128
  head.scalar = msg_obj.mode_id;
129

130
  uint8_t reader_idx;
131

Bc. Petr Elexa's avatar
Bc. Petr Elexa committed
132
  // Get target door if message is for us.
133
  if (msg_obj.msgobj == ACS_MSGOBJ_RECV_DOOR_A)
134
  {
135
    reader_idx = ACS_READER_A_IDX;
136 137 138 139
    DEBUGSTR("for door A\n");
  }
  else if (msg_obj.msgobj == ACS_MSGOBJ_RECV_DOOR_B)
  {
140
    reader_idx = ACS_READER_B_IDX;
141 142 143 144
    DEBUGSTR("for door B\n");
  }
  else if (msg_obj.msgobj == ACS_MSGOBJ_RECV_BCAST)
  {
Bc. Petr Elexa's avatar
Bc. Petr Elexa committed
145
    // Broadcast message.
146 147 148
    if (head.fc == FC_ALIVE &&
        head.src >= ACS_MSTR_FIRST_ADDR &&
        head.src <= ACS_MSTR_LAST_ADDR)
149
    {
Bc. Petr Elexa's avatar
Bc. Petr Elexa committed
150
      // Update master address if timeout occurred.
151
      portENTER_CRITICAL();
152 153 154 155 156
      if (_master_timeout == true)
      {
        _act_master = head.src;
        Board_LED_Set(BOARD_LED_STATUS, true);
      }
157 158 159
      _master_timeout = false;
      portEXIT_CRITICAL();
      DEBUGSTR("master alive\n");
160
    }
161
    return;
162 163
  }
  else return;
Bc. Petr Elexa's avatar
Bc. Petr Elexa committed
164

Bc. Petr Elexa's avatar
Bc. Petr Elexa committed
165
  // Stop processing if card reader not configured.
166
  if (reader_idx >= ACS_READER_COUNT) return;
167

Bc. Petr Elexa's avatar
Bc. Petr Elexa committed
168
  // Continue deducing action and execute it.
169
  if (head.fc == FC_USER_AUTH_RESP)
170
  {
Bc. Petr Elexa's avatar
Bc. Petr Elexa committed
171
    _terminal_user_authorized(reader_idx);
172

Bc. Petr Elexa's avatar
Bc. Petr Elexa committed
173
    #if CACHING_ENABLED
Bc. Petr Elexa's avatar
Bc. Petr Elexa committed
174 175 176 177 178
      uint32_t user_id;
      uint8_t len = msg_obj.dlc > sizeof(user_id) ? sizeof(user_id) : msg_obj.dlc;
      memcpy(&user_id, msg_obj.data, len);
      term_cache_item_t user;
      user.key = user_id;
179
      user.value = map_reader_idx_to_cache(reader_idx);
Bc. Petr Elexa's avatar
Bc. Petr Elexa committed
180 181
      static_cache_insert(user);
    #endif
182
  }
183
  else if (head.fc == FC_USER_NOT_AUTH_RESP)
Bc. Petr Elexa's avatar
Bc. Petr Elexa committed
184
  {
Bc. Petr Elexa's avatar
Bc. Petr Elexa committed
185
    __terminal_user_not_authorized(reader_idx);
186

Bc. Petr Elexa's avatar
Bc. Petr Elexa committed
187
    #if CACHING_ENABLED
Bc. Petr Elexa's avatar
Bc. Petr Elexa committed
188 189 190 191 192
      uint32_t user_id;
      uint8_t len = msg_obj.dlc >= sizeof(user_id) ? sizeof(user_id) : msg_obj.dlc;
      memcpy(&user_id, msg_obj.data, len);
      term_cache_item_t user;
      user.key = user_id;
193
      user.value = cache_reader_none;
194 195
      static_cache_insert(user);
    #endif
Bc. Petr Elexa's avatar
Bc. Petr Elexa committed
196
  }
197
  else if (head.fc == FC_DOOR_CTRL)
198 199 200
  {
    switch (msg_obj.data[0])
    {
201
      case DATA_DOOR_CTRL_REMOTE_UNLCK:
202
        DEBUGSTR("cmd UNLOCK\n");
203
        reader_unlock(reader_idx, BEEP_ON_SUCCESS, OK_LED_ON_SUCCESS);
204
        break;
205
      case DATA_DOOR_CTRL_CLR_CACHE:
206
        DEBUGSTR("cmd CLR CACHE\n");
Bc. Petr Elexa's avatar
Bc. Petr Elexa committed
207
#if CACHING_ENABLED
208 209
        static_cache_reset();
#endif
210 211 212 213 214 215
        break;
      default:
        break;
    }
  }
  else return;
Bc. Petr Elexa's avatar
Bc. Petr Elexa committed
216 217
}

218
// Send door status update to server
219
static void terminal_send_door_status(uint8_t reader_idx, bool is_open)
Bc. Petr Elexa's avatar
Bc. Petr Elexa committed
220
{
221 222 223 224 225 226 227
  //check if master online
  if (_act_master == ACS_RESERVED_ADDR)
  {
    DEBUGSTR("master offline\n");
    return;
  }

228
  // Prepare msg head to send request on CAN
229
  acs_msg_head_t head;
Bc. Petr Elexa's avatar
Bc. Petr Elexa committed
230
  head.scalar = CAN_MSGOBJ_EXT;
231 232
  head.prio = PRIO_DOOR_STATUS;
  head.fc = FC_DOOR_STATUS;
233
  head.dst = _act_master;
234

235
  uint8_t status = (is_open ? DATA_DOOR_STATUS_OPEN : DATA_DOOR_STATUS_CLOSED);
236

237
  if (reader_idx == ACS_READER_A_IDX)
238
  {
239
    head.src = get_reader_a_addr();
240
    CAN_send_once(ACS_MSGOBJ_SEND_DOOR_A, head.scalar, (void *)&status, sizeof(status));
241
  }
242
  else if (reader_idx == ACS_READER_B_IDX)
243
  {
244
    head.src = get_reader_b_addr();
245
    CAN_send_once(ACS_MSGOBJ_SEND_DOOR_B, head.scalar, (void *)&status, sizeof(status));
246
  }
Bc. Petr Elexa's avatar
Bc. Petr Elexa committed
247 248
}

Bc. Petr Elexa's avatar
Bc. Petr Elexa committed
249
// Send command to server that request authorization of user for given reader.
250
static void terminal_request_auth(uint32_t user_id, uint8_t reader_idx)
Bc. Petr Elexa's avatar
Bc. Petr Elexa committed
251
{
Bc. Petr Elexa's avatar
Bc. Petr Elexa committed
252
  // Check if master online.
253 254
  if (_act_master == ACS_RESERVED_ADDR)
  {
255
    DEBUGSTR("master off-line\n");
256 257 258
    return;
  }

Bc. Petr Elexa's avatar
Bc. Petr Elexa committed
259
  // Prepare message head to send request on CAN.
260
  acs_msg_head_t head;
Bc. Petr Elexa's avatar
Bc. Petr Elexa committed
261
  head.scalar = CAN_MSGOBJ_EXT;
262 263
  head.prio = PRIO_USER_AUTH_REQ;
  head.fc = FC_USER_AUTH_REQ;
264
  head.dst = _act_master;
265

266
  if (reader_idx == ACS_READER_A_IDX)
267
  {
268
    head.src = get_reader_a_addr();
Bc. Petr Elexa's avatar
Bc. Petr Elexa committed
269
    CAN_send_once(ACS_MSGOBJ_SEND_DOOR_A, head.scalar, (void *)&user_id, sizeof(user_id));
270
  }
271
  else if (reader_idx == ACS_READER_B_IDX)
272
  {
273
    head.src = get_reader_b_addr();
Bc. Petr Elexa's avatar
Bc. Petr Elexa committed
274
    CAN_send_once(ACS_MSGOBJ_SEND_DOOR_B, head.scalar, (void *)&user_id, sizeof(user_id));
275
  }
Bc. Petr Elexa's avatar
Bc. Petr Elexa committed
276 277
}

Bc. Petr Elexa's avatar
Bc. Petr Elexa committed
278
// Process user identification on a reader.
279
static void terminal_user_identified(uint32_t user_id, uint8_t reader_idx)
Bc. Petr Elexa's avatar
Bc. Petr Elexa committed
280
{
Bc. Petr Elexa's avatar
Bc. Petr Elexa committed
281 282 283
#if CACHING_ENABLED
  term_cache_item_t user = {.key = user_id};
#endif
Bc. Petr Elexa's avatar
Bc. Petr Elexa committed
284

285
  if (reader_idx < ACS_READER_COUNT)
Bc. Petr Elexa's avatar
Bc. Petr Elexa committed
286
  {
Bc. Petr Elexa's avatar
Bc. Petr Elexa committed
287
#if CACHING_ENABLED
288
    if (static_cache_get(&user))
Bc. Petr Elexa's avatar
Bc. Petr Elexa committed
289
    {
290
      if (map_reader_idx_to_cache(reader_idx) & user.value)
Bc. Petr Elexa's avatar
Bc. Petr Elexa committed
291
      {
Bc. Petr Elexa's avatar
Bc. Petr Elexa committed
292
        _terminal_user_authorized(reader_idx);
Bc. Petr Elexa's avatar
Bc. Petr Elexa committed
293
      }
Bc. Petr Elexa's avatar
Bc. Petr Elexa committed
294
    }
Bc. Petr Elexa's avatar
Bc. Petr Elexa committed
295
    else
296
#else
Bc. Petr Elexa's avatar
Bc. Petr Elexa committed
297
    {
298
      terminal_request_auth(user_id, reader_idx);
Bc. Petr Elexa's avatar
Bc. Petr Elexa committed
299
    }
300
#endif
Bc. Petr Elexa's avatar
Bc. Petr Elexa committed
301
  }
302 303
}

Bc. Petr Elexa's avatar
Bc. Petr Elexa committed
304
// Main loop in terminal processing task.
Bc. Petr Elexa's avatar
Bc. Petr Elexa committed
305
//
Bc. Petr Elexa's avatar
Bc. Petr Elexa committed
306
// Waked only when request is in the reader buffer or after timeout.
Bc. Petr Elexa's avatar
Bc. Petr Elexa committed
307
static void terminal_task(void *pvParameters)
308
{
Bc. Petr Elexa's avatar
Bc. Petr Elexa committed
309 310
  (void)pvParameters;

Bc. Petr Elexa's avatar
Bc. Petr Elexa committed
311
  // start timer for detecting master timeout
312 313
  configASSERT(xTimerStart(_act_timer, 0));

Bc. Petr Elexa's avatar
Bc. Petr Elexa committed
314 315
  while (true)
  {
Bc. Petr Elexa's avatar
Bc. Petr Elexa committed
316
    // Get pending user request
317
    uint32_t user_id;
318 319 320

    TickType_t begin_time = xTaskGetTickCount();

321
    uint8_t reader_idx = reader_get_request_from_buffer(&user_id, USER_REQUEST_WAIT_MS);
322 323 324 325 326 327 328 329 330 331

    // Calculate actual wait time.
    TickType_t wait_time = xTaskGetTickCount() - begin_time;

    // Prevent brute-force attack by limiting requests frequency.
    if (wait_time < pdMS_TO_TICKS(USER_REQUEST_MIN_PERIOD_MS))
		{
      vTaskDelay(pdMS_TO_TICKS(USER_REQUEST_MIN_PERIOD_MS) - wait_time);
		}

332
    if (reader_idx < ACS_READER_COUNT)
Bc. Petr Elexa's avatar
Bc. Petr Elexa committed
333
    {
Bc. Petr Elexa's avatar
Bc. Petr Elexa committed
334
      DEBUGSTR("user identified\n");
335
      terminal_user_identified(user_id, reader_idx);
Bc. Petr Elexa's avatar
Bc. Petr Elexa committed
336
    }
337 338 339
    // Check if door open/close state changed
    for (size_t idx = 0; idx < ACS_READER_COUNT; ++idx)
    {
Bc. Petr Elexa's avatar
Bc. Petr Elexa committed
340
      if (reader_is_door_open(idx) != _last_door_state[idx])
341 342
      {
        DEBUGSTR("new door state\n");
Bc. Petr Elexa's avatar
Bc. Petr Elexa committed
343 344
        _last_door_state[idx] = !_last_door_state[idx];
        terminal_send_door_status(idx, _last_door_state[idx]);
345 346
      }
    }
Bc. Petr Elexa's avatar
Bc. Petr Elexa committed
347 348 349 350 351
  }
}

void terminal_init(void)
{
Bc. Petr Elexa's avatar
Bc. Petr Elexa committed
352
  // Initialize configuration for terminal.
353
  configASSERT(terminal_config_init());
354

Bc. Petr Elexa's avatar
Bc. Petr Elexa committed
355
  // Assign CAN callback functions of on-chip drivers.
Bc. Petr Elexa's avatar
Bc. Petr Elexa committed
356 357
  CCAN_CALLBACKS_T term_can_callbacks =
  {
Bc. Petr Elexa's avatar
Bc. Petr Elexa committed
358 359 360 361 362 363 364 365 366
    term_can_recv,  // Callback for any message received CAN frame which ID
                    // matches with any of the message objects' masks.
    term_can_send,  // Callback for every transmitted CAN frame.
    term_can_error, // Callback for CAN errors.
    NULL,           // Not used.
    NULL,           // Not used.
    NULL,           // Not used.
    NULL,           // Not used.
    NULL,           // Not used.
Bc. Petr Elexa's avatar
Bc. Petr Elexa committed
367 368
  };

Bc. Petr Elexa's avatar
Bc. Petr Elexa committed
369
  // Init CAN driver.
Bc. Petr Elexa's avatar
Bc. Petr Elexa committed
370
  CAN_init(&term_can_callbacks, CAN_BAUD_RATE);
Bc. Petr Elexa's avatar
Bc. Petr Elexa committed
371

Bc. Petr Elexa's avatar
Bc. Petr Elexa committed
372
  // CAN msg filter for door A.
373
  CAN_recv_filter(ACS_MSGOBJ_RECV_DOOR_A,
374
                  get_reader_a_addr() << ACS_DST_ADDR_OFFSET,
375
                  ACS_DST_ADDR_MASK, true);
Bc. Petr Elexa's avatar
Bc. Petr Elexa committed
376
  // CAN msg filter for door B.
377
  CAN_recv_filter(ACS_MSGOBJ_RECV_DOOR_B,
378
                  get_reader_b_addr() << ACS_DST_ADDR_OFFSET,
379
                  ACS_DST_ADDR_MASK, true);
Bc. Petr Elexa's avatar
Bc. Petr Elexa committed
380
  // CAN msg filter for broadcast.
381 382 383 384
  CAN_recv_filter(ACS_MSGOBJ_RECV_BCAST,
                  ACS_BROADCAST_ADDR << ACS_DST_ADDR_OFFSET,
                  ACS_DST_ADDR_MASK, true);

Bc. Petr Elexa's avatar
Bc. Petr Elexa committed
385
  // Initialize card readers.
386
  for (size_t id = 0; id < ACS_READER_COUNT; ++id)
Bc. Petr Elexa's avatar
Bc. Petr Elexa committed
387
  {
388
    if (reader_conf[id].enabled) terminal_reconfigure(NULL, id);
Bc. Petr Elexa's avatar
Bc. Petr Elexa committed
389
  }
390

Bc. Petr Elexa's avatar
Bc. Petr Elexa committed
391
  // Create timer for master alive status timeout.
392
  _act_timer = xTimerCreate("MAT", (ACS_MASTER_ALIVE_TIMEOUT_MS / portTICK_PERIOD_MS),
393 394
               pdTRUE, (void *)_act_timer_id, _timer_callback);
  configASSERT(_act_timer);
Bc. Petr Elexa's avatar
Bc. Petr Elexa committed
395

Bc. Petr Elexa's avatar
Bc. Petr Elexa committed
396
  // Create task for terminal loop.
397
  xTaskCreate(terminal_task, "term_tsk", configMINIMAL_STACK_SIZE + 128, NULL, (tskIDLE_PRIORITY + 1UL), NULL);
398 399
}

400
void terminal_reconfigure(reader_conf_t * reader_cfg, uint8_t reader_idx)
401
{
402
  if (reader_idx >= ACS_READER_COUNT) return;
403

Bc. Petr Elexa's avatar
Bc. Petr Elexa committed
404
  portENTER_CRITICAL(); // Effectively disables interrupts.
Bc. Petr Elexa's avatar
Bc. Petr Elexa committed
405

406
  if (reader_cfg != NULL)
Bc. Petr Elexa's avatar
Bc. Petr Elexa committed
407
  {
408
    memcpy(&reader_conf[reader_idx], reader_cfg, sizeof(reader_conf_t));
Bc. Petr Elexa's avatar
Bc. Petr Elexa committed
409

Bc. Petr Elexa's avatar
Bc. Petr Elexa committed
410
    // Reconfigure the reader instance.
411
    if (reader_conf[reader_idx].enabled)
412
    {
413 414
      reader_init(reader_idx);
      DEBUGSTR("reader enabled\n");
415 416 417
    }
    else
    {
Bc. Petr Elexa's avatar
Bc. Petr Elexa committed
418
      // Disable interface.
419 420
      reader_deinit(reader_idx);
      DEBUGSTR("reader disabled\n");
421
    }
Bc. Petr Elexa's avatar
Bc. Petr Elexa committed
422
  }
423
  else
Bc. Petr Elexa's avatar
Bc. Petr Elexa committed
424
  {
Bc. Petr Elexa's avatar
Bc. Petr Elexa committed
425
    // No new configuration given -- just init.
426
    reader_init(reader_idx);
Bc. Petr Elexa's avatar
Bc. Petr Elexa committed
427
  }
428 429

  portEXIT_CRITICAL();
430
}