Commit 20599dfb authored by Bc. Petr Elexa's avatar Bc. Petr Elexa

add support to switch learn mode by card

parent 006b924a
FLUSHALL
SELECT 1
SADD __all 0
SADD __learn 0
SADD __void 0
SADD door_A 4
SADD door_B 5
......@@ -17,7 +18,7 @@ SET 7577396 door_B
SET 814190 door_C
SET 814185 door_C
SET 814204 door_D
SET 814199 door_D
SET 814199 __learn
SET 1000 __void
SET 1001 __void
SET 1002 __void
......
......@@ -5,7 +5,7 @@ This is readme for Access control system server (ACS server/master).
********************************************************************
Its main job is to respond to authentication requests from RFID readers connected on CAN bus.
The server is designed to be not accessible from ethernet network and be administred through ssh from outside
The server is designed to be not accessible from public network and be administred through ssh from outside
or from administrative application over secured channel.
Redis server should be on the same machine or network as it is designed to be accesed only from trusted clients
inside trusted environments.
......
......@@ -131,6 +131,12 @@ class acs_can_proto(object):
FC_DOOR_STATUS = 5
# m->s
FC_ALIVE = 6
# m<-s
FC_LEARN_USER = 7
# m->s
FC_LEARN_USER_OK = 8
# m->s
FC_LEARN_USER_FAIL = 9
# priorities
PRIO_RESERVED = 0
......@@ -140,6 +146,9 @@ class acs_can_proto(object):
PRIO_DOOR_CTRL = 3
PRIO_DOOR_STATUS = 4
PRIO_ALIVE = 1
PRIO_LEARN_USER = 2
PRIO_LEARN_USER_FAIL = 2
PRIO_LEARN_USER_OK = 2
MASTER_ALIVE_PERIOD = 5 # seconds
MASTER_ALIVE_TIMEOUT = 12
......@@ -147,6 +156,8 @@ class acs_can_proto(object):
# Data for FC_DOOR_CTRL
DATA_DOOR_CTRL_REMOTE_UNLCK = b'\x01'
DATA_DOOR_CTRL_CLR_CACHE = b'\x02'
DATA_DOOR_CTRL_LEARN_MODE = b'\x03'
DATA_DOOR_CTRL_NORMAL_MODE = b'\x04'
# Data for FC_DOOR_STATUS
DATA_DOOR_STATUS_CLOSED = b'\x01'
......@@ -184,13 +195,14 @@ class acs_can_proto(object):
CAN_ID_MASK = 0xFFFFFFFF
def __init__(self, master_addr:int, cb_user_auth_req, cb_door_status_update):
def __init__(self, master_addr:int, cb_user_auth_req, cb_door_status_update, cb_learn_user):
# create socket
self.can_sock = can_raw_sock()
# register message callbacks
self.cb_user_auth_req = cb_user_auth_req
self.cb_door_status_update = cb_door_status_update
self.cb_learn_user = cb_learn_user
if self.ACS_MSTR_LAST_ADDR >= master_addr >= self.ACS_MSTR_FIRST_ADDR:
self.addr = master_addr
......@@ -239,6 +251,22 @@ class acs_can_proto(object):
return (self.__msg(self.PRIO_ALIVE, self.FC_ALIVE, self.ACS_BROADCAST_ADDR),
0, b'\x00')
def msg_reader_normal_mode(self, reader_addr):
return (self.__msg(self.PRIO_DOOR_CTRL, self.FC_DOOR_CTRL, reader_addr),
1, self.DATA_DOOR_CTRL_NORMAL_MODE)
def msg_reader_learn_mode(self, reader_addr):
return (self.__msg(self.PRIO_DOOR_CTRL, self.FC_DOOR_CTRL, reader_addr),
1, self.DATA_DOOR_CTRL_LEARN_MODE)
def msg_learn_user_ok(self, reader_addr, user_id:int):
return (self.__msg(self.PRIO_LEARN_USER_OK, self.FC_LEARN_USER_OK, reader_addr),
4, user_id.to_bytes(4, "little", signed=True))
def msg_learn_user_fail(self, reader_addr, user_id:int):
return (self.__msg(self.PRIO_LEARN_USER_FAIL, self.FC_LEARN_USER_FAIL, reader_addr),
4, user_id.to_bytes(4, "little", signed=True))
# Parse arbitration ID
def __parse_msg_head(self, msg_head):
prio = (msg_head & self.ACS_PRIO_MASK) >> self.ACS_PRIO_OFFSET
......@@ -258,10 +286,22 @@ class acs_can_proto(object):
if fc == self.FC_USER_AUTH_REQ:
if self.cb_user_auth_req is not None:
user_id = int.from_bytes(msg_data[:4], "little", signed=True)
if self.cb_user_auth_req(src, user_id):
ok = self.cb_user_auth_req(src, user_id)
if ok is None:
return self.NO_MESSAGE
elif ok:
return self.msg_auth_ok(src, user_id)
else:
return self.msg_auth_fail(src, user_id)
elif fc == self.FC_LEARN_USER:
if self.cb_learn_user is not None:
ok = self.cb_learn_user(src, int.from_bytes(msg_data[:4], "little", signed=True))
if ok is None:
return self.NO_MESSAGE
elif ok:
return self.msg_learn_user_ok(src, user_id)
else:
return self.msg_learn_user_fail(src, user_id)
elif fc == self.FC_DOOR_STATUS:
if self.cb_door_status_update is not None:
self.cb_door_status_update(src, msg_data[:1] == self.DATA_DOOR_STATUS_OPEN)
......
......@@ -24,6 +24,7 @@ class acs_database(object):
__ALL_GRP = b"__all" # Special group representing all doors.
__EMPTY_GRP = b"__void" # Special empty group.
__LEARN_GRP = b"__learn" # Special group for switching between on and learn.
__NONAME_GRP_PREFIX = "__nng_"
__RESERVED_ADDR = 0
__DEFAULT_HOST = "localhost"
......@@ -31,6 +32,12 @@ class acs_database(object):
__DOOR_MODE_IDX = 0
__DOOR_STATUS_IDX = 1
# user auth types
USER_AUTH_FAIL = 0
USER_AUTH_OK = 1
USER_NOT_EXIST = 2
USER_AUTH_LEARN = 3
# Doors can be disabled to prevent access. Or switched to learn mode where authorization request
# is interpreted that door is unlocked and user is added to database and given access to source door.
DOOR_MODE_DISABLE = b"off"
......@@ -51,6 +58,7 @@ class acs_database(object):
# create basic groups (if not present)
self.add_doors_to_group(self.__ALL_GRP, self.__RESERVED_ADDR)
self.add_doors_to_group(self.__EMPTY_GRP, self.__RESERVED_ADDR)
self.add_doors_to_group(self.__LEARN_GRP, self.__RESERVED_ADDR)
# Return user's group name or None if user does not exist.
def get_user_group(self, user_id:int) -> str:
......@@ -118,15 +126,21 @@ class acs_database(object):
def is_door_registered(self, door_addr:int) -> bool:
self.__rclient_door.llen(door_addr) == 2
# Return True if user is authorized for given door (door) number.
def is_user_authorized(self, user_id:int, door_addr:int) -> bool:
# Return user authorization for given door address.
def user_authorization(self, user_id:int, door_addr:int) -> bool:
group = self.get_user_group(user_id)
if group is None or user_id == self.__RESERVED_ADDR or group == self.__EMPTY_GRP:
return False
if group is None:
return self.USER_NOT_EXIST
elif user_id == self.__RESERVED_ADDR or group == self.__EMPTY_GRP:
return self.USER_AUTH_FAIL
elif group == self.__ALL_GRP:
return True
return self.USER_AUTH_OK
elif group == self.__LEARN_GRP:
return self.USER_AUTH_LEARN
elif self.__rclient_group.sismember(group, door_addr):
return self.USER_AUTH_OK
else:
return self.__rclient_group.sismember(group, door_addr)
return self.USER_AUTH_FAIL
# Log that user accessed a door/door.
def log_user_access(self, user_id, door_addr):
......
......@@ -26,8 +26,9 @@ class acs_server(object):
self.can_if = can_if
self.addr = addr
try:
self.proto = acs_can_proto(addr, cb_user_auth_req=self.resp_to_auth_req,
cb_door_status_update=self.door_status_update)
self.proto = acs_can_proto(addr, cb_user_auth_req=self._resp_to_auth_req,
cb_door_status_update=self._door_status_update,
cb_learn_user=self._learn_user)
self.proto.bind(can_if)
except Exception as e:
logging.exception("Unable to start the server: %s", e)
......@@ -54,6 +55,18 @@ class acs_server(object):
logging.warning("Forcing shutdown...")
sys.exit(0)
# server command to unlock door
def change_door_mode(self, reader_addr, mode):
if self.debug:
logging.debug("change_door_mode: reader={}, mode={}".format(reader_addr, mode))
self.db.set_door_mode(reader_addr, self.db.DOOR_MODE_ENABLED)
if mode == self.db.DOOR_MODE_LEARN:
can_id, dlc, data = self.proto.msg_reader_learn_mode(reader_addr)
self.proto.can_sock.send(can_id, dlc, data)
else:
can_id, dlc, data = self.proto.msg_reader_normal_mode(reader_addr)
self.proto.can_sock.send(can_id, dlc, data)
# server command to unlock door
def remote_unlock_door(self, reader_addr):
if self.debug:
......@@ -62,44 +75,70 @@ class acs_server(object):
self.proto.can_sock.send(can_id, dlc, data)
# add a new user to database
def add_new_user(self, user_id, reader_addr):
def add_new_user(self, user_id, reader_addr, extend_group):
if self.debug:
logging.debug("add_new_user: reader={}, user={}".format(reader_addr, user_id))
# do not add existing users
if self.db.get_user_group(user_id) is not None:
return
# create special group
group = self.db.create_group_for_door(reader_addr)
group = self.db.get_user_group(user_id)
if group is not None:
if self.db.add_user(user_id, group):
return
if extend_group:
# add door to user's group
return (self.db.add_doors_to_group(group, reader_addr) > 0)
else:
self.db.remove_group(group)
return
return
return False
else:
# create special group
group = self.db.create_group_for_door(reader_addr)
if group is not None:
if self.db.add_user(user_id, group):
return True
else:
# remove group is adding failed
self.db.remove_group(group)
return False
# callback to learn user request
def _learn_user(self, user_id, reader_addr):
if self.debug:
logging.debug("learn_user: reader={}, user={}".format(reader_addr, user_id))
mode = self.db.get_door_mode(reader_addr)
if mode is None:
return False # treat as invalid request (door does not exist)
if mode == self.db.DOOR_MODE_LEARN:
user_auth_type = self.db.user_authorization(user_id, reader_addr)
if user_auth_type == self.db.USER_AUTH_LEARN:
self.change_door_mode(reader_addr, self.db.DOOR_MODE_ENABLED)
return None
elif user_auth_type == self.db.USER_NOT_EXIST:
return self.add_new_user(user_id, reader_addr, False)
elif user_auth_type == self.db.USER_AUTH_OK:
return self.add_new_user(user_id, reader_addr, True)
else:
return False
# callback to authorization request
# return True if authorized to open door False otherwise
def resp_to_auth_req(self, reader_addr, user_id):
def _resp_to_auth_req(self, reader_addr, user_id):
if self.debug:
logging.debug("resp_to_auth_req: reader={}, user={}".format(reader_addr, user_id))
mode = self.db.get_door_mode(reader_addr)
if mode is None:
return False # treat as invalid request (door does not exist)
if mode == self.db.DOOR_MODE_ENABLED:
if self.db.is_user_authorized(user_id, reader_addr):
user_auth_type = self.db.user_authorization(user_id, reader_addr)
if user_auth_type == self.db.USER_AUTH_OK:
self.db.log_user_access(user_id, reader_addr)
return True
elif user_auth_type == self.db.USER_AUTH_LEARN:
self.change_door_mode(reader_addr, self.db.DOOR_MODE_LEARN)
return None
else:
return False
elif mode == self.db.DOOR_MODE_LEARN:
self.add_new_user(user_id, reader_addr)
return True
else:
return False
# callback for door status update
def door_status_update(self, reader_addr, is_open:bool):
def _door_status_update(self, reader_addr, is_open:bool):
if self.debug:
logging.debug("door_status_update: reader={} open={}".format(reader_addr, is_open))
self.db.set_door_is_open(reader_addr, is_open)
......
......@@ -53,6 +53,9 @@
#define FC_DOOR_CTRL 0x4 // M -> S
#define FC_DOOR_STATUS 0x5 // S -> M
#define FC_ALIVE 0x6 // M -> S
#define FC_LEARN_USER 0x7 // S -> M
#define FC_LEARN_USER_OK 0x8 // M -> S
#define FC_LEARN_USER_FAIL 0x9 // M -> S
// Priority range.
#define ACS_MAX_PRIO 0
......@@ -66,10 +69,15 @@
#define PRIO_DOOR_CTRL 0x3
#define PRIO_DOOR_STATUS 0x4
#define PRIO_ALIVE 0x1
#define PRIO_LEARN_USER 0x2
#define PRIO_LEARN_USER_FAIL 0x2
#define PRIO_LEARN_USER_OK 0x2
// Data for FC_DOOR_CTRL.
#define DATA_DOOR_CTRL_REMOTE_UNLCK 0x01
#define DATA_DOOR_CTRL_CLR_CACHE 0x02
#define DATA_DOOR_CTRL_LEARN_MODE 0x03
#define DATA_DOOR_CTRL_NORMAL_MODE 0x04
// Data for FC_DOOR_STATUS.
#define DATA_DOOR_STATUS_CLOSED 0x01
......
......@@ -204,6 +204,10 @@ void term_can_recv(uint8_t msg_obj_num)
static_cache_insert(user);
#endif
}
else if (head.fc == FC_LEARN_USER_OK)
{
reader_signal_to_user(reader_idx, BEEP_ON_SUCCESS);
}
else if (head.fc == FC_DOOR_CTRL)
{
switch (msg_obj.data[0])
......@@ -218,6 +222,16 @@ void term_can_recv(uint8_t msg_obj_num)
_cache_clear_req = true;
#endif
break;
case DATA_DOOR_CTRL_NORMAL_MODE:
DEBUGSTR("cmd NORMAL MODE\n");
reader_conf[reader_idx].learn_mode = false;
reader_signal_to_user(reader_idx, BEEP_ON_SUCCESS);
break;
case DATA_DOOR_CTRL_LEARN_MODE:
DEBUGSTR("cmd LEARN MODE\n");
reader_conf[reader_idx].learn_mode = true;
reader_signal_to_user(reader_idx, BEEP_ON_SUCCESS);
break;
default:
break;
}
......@@ -285,6 +299,35 @@ static void terminal_request_auth(uint32_t user_id, uint8_t reader_idx)
}
}
// Send command to server that request to learn a user for given reader.
static void terminal_request_user_learn(uint32_t user_id, uint8_t reader_idx)
{
// Check if master online.
if (_act_master == ACS_RESERVED_ADDR)
{
DEBUGSTR("master off-line\n");
return;
}
// Prepare message head to send request on CAN.
acs_msg_head_t head;
head.scalar = CAN_MSGOBJ_EXT;
head.prio = PRIO_LEARN_USER;
head.fc = FC_LEARN_USER;
head.dst = _act_master;
if (reader_idx == ACS_READER_A_IDX)
{
head.src = get_reader_a_addr();
CAN_send_once(ACS_MSGOBJ_SEND_DOOR_A, head.scalar, (void *)&user_id, sizeof(user_id));
}
else if (reader_idx == ACS_READER_B_IDX)
{
head.src = get_reader_b_addr();
CAN_send_once(ACS_MSGOBJ_SEND_DOOR_B, head.scalar, (void *)&user_id, sizeof(user_id));
}
}
// Process user identification on a reader.
static void terminal_user_identified(uint32_t user_id, uint8_t reader_idx)
{
......@@ -294,17 +337,21 @@ static void terminal_user_identified(uint32_t user_id, uint8_t reader_idx)
if (reader_idx < ACS_READER_MAXCOUNT && reader_conf[reader_idx].enabled)
{
if (reader_conf[reader_idx].learn_mode)
{
terminal_request_user_learn(user_id, reader_idx);
}
#if CACHING_ENABLED
// Read from cache online if master is offline.
if ((_act_master == ACS_RESERVED_ADDR) && static_cache_get(&user))
else if ((_act_master == ACS_RESERVED_ADDR) && static_cache_get(&user))
{
if (map_reader_idx_to_cache(reader_idx) & user.value)
{
_terminal_user_authorized(reader_idx);
}
}
else
#else
else
{
terminal_request_auth(user_id, reader_idx);
}
......
......@@ -15,7 +15,7 @@
//#define DEVEL_BOARD // LPCXpresso11C24 development board
#define FW_VERSION_STR "1.0.1"
#define FW_VERSION_STR "1.1.0"
//-------------------------------------------------------------
// General settings.
......
......@@ -66,6 +66,7 @@ reader_conf_t reader_conf[ACS_READER_MAXCOUNT] =
.open_time_sec = ACS_READER_A_OPEN_TIME_MS,
.gled_time_sec = ACS_READER_A_OK_GLED_TIME_MS,
.enabled = ACS_READER_A_ENABLED,
.learn_mode = false,
.door_open = DOOR_CLOSED
},
{
......@@ -74,6 +75,7 @@ reader_conf_t reader_conf[ACS_READER_MAXCOUNT] =
.open_time_sec = ACS_READER_B_OPEN_TIME_MS,
.gled_time_sec = ACS_READER_B_OK_GLED_TIME_MS,
.enabled = ACS_READER_B_ENABLED,
.learn_mode = false,
.door_open = DOOR_CLOSED
}
};
......@@ -202,6 +204,10 @@ uint8_t reader_get_request_from_buffer(uint32_t * user_id, uint16_t time_to_wait
void reader_unlock(uint8_t idx, bool with_beep, bool with_ok_led)
{
configASSERT(xTimerStart(reader_conf[idx].timer_ok, 0));
Chip_GPIO_SetPinState(LPC_GPIO, _reader_wiring[idx].rled_port, _reader_wiring[idx].rled_pin, LOG_LOW);
configASSERT(xTimerStart(reader_conf[idx].timer_open, 0));
Chip_GPIO_SetPinState(LPC_GPIO, _reader_wiring[idx].relay_port, _reader_wiring[idx].relay_pin, LOG_LOW);
// Unlock state
if (with_beep)
{
......@@ -211,11 +217,17 @@ void reader_unlock(uint8_t idx, bool with_beep, bool with_ok_led)
{
Chip_GPIO_SetPinState(LPC_GPIO, _reader_wiring[idx].gled_port, _reader_wiring[idx].gled_pin, LOG_HIGH);
}
Chip_GPIO_SetPinState(LPC_GPIO, _reader_wiring[idx].rled_port, _reader_wiring[idx].rled_pin, LOG_LOW);
configASSERT(xTimerStart(reader_conf[idx].timer_open, 0));
}
Chip_GPIO_SetPinState(LPC_GPIO, _reader_wiring[idx].relay_port, _reader_wiring[idx].relay_pin, LOG_LOW);
void reader_signal_to_user(uint8_t idx, bool with_beep)
{
configASSERT(xTimerStart(reader_conf[idx].timer_ok, 0));
Chip_GPIO_SetPinState(LPC_GPIO, _reader_wiring[idx].rled_port, _reader_wiring[idx].rled_pin, LOG_HIGH);
Chip_GPIO_SetPinState(LPC_GPIO, _reader_wiring[idx].gled_port, _reader_wiring[idx].gled_pin, LOG_HIGH);
if (with_beep)
{
Chip_GPIO_SetPinState(LPC_GPIO, _reader_wiring[idx].beep_port, _reader_wiring[idx].beep_pin, LOG_HIGH);
}
}
bool reader_is_door_open(uint8_t reader_idx)
......
......@@ -18,13 +18,6 @@
#include "timers.h"
#include "weigand.h"
typedef enum
{
READER_MODE_DEF = 0,
READER_MODE_LOCKED,
READER_MODE_LEARN
} reader_mode_t;
typedef struct
{
TimerHandle_t timer_open;
......@@ -32,6 +25,7 @@ typedef struct
uint16_t open_time_sec;
uint16_t gled_time_sec;
uint8_t enabled;
uint8_t learn_mode;
uint8_t door_open;
} reader_conf_t;
......@@ -89,6 +83,14 @@ uint8_t reader_get_request_from_buffer(uint32_t * user_id, uint16_t time_to_wait
*/
void reader_unlock(uint8_t idx, bool with_beep, bool with_ok_led);
/**
* @brief Activate sound and light on reader.
*
* @param idx ... reader index
* @param with_beep ... true for sound signal
*/
void reader_signal_to_user(uint8_t idx, bool with_beep);
/**
* @brief Check door status.
*
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment