#!/usr/bin/php Current global state as string * */ function get_gstate(){ /* (1) Initialize file descriptor */ $f_gstate = new SplFileObject(STATE_CONF, 'r'); /* (2) Read first line */ $f_gstate->seek(0); $state = $f_gstate->current(); $state = preg_replace('@^\s+@', '', $state); $state = preg_replace('@\s+$@', '', $state); /* (3) Free file descriptor */ /* (4) Check data */ if( strlen($state) < 1 ) return false; /* (5) Return global state */ return $state; } /* RETURNS THE PERMISSIONS FOR A SPECIFIC CODE AND AN ACTION * * @code Some RFID code * * @return user User info if found ; else NULL * */ function auth($code){ global $f_auth; /* (1) Goto first line */ $f_auth->seek(0); /* (2) Parse each line */ while( !$f_auth->eof() ){ /* (3) Try to parse line */ $parsed = json_decode($f_auth->fgets(), true); /* (3) Ignore if parse error */ if( is_null($parsed) ) continue; /* (4) If wrong code -> go to next */ if( $parsed[0] != $code ) continue; /* (5) return user info */ return [ 'id' => $parsed[1], 'can' => $parsed[2] ]; } /* (6) Return FALSE if not found */ return null; } /* PROCESS ACTIONS IF ACTION CAN BE PERFORMED BY USER (DEPENDING ON TIMEOUT/STATE) * * @user Current user information * @cur_timeout The real timeout (diff in sec) * * @return success If success (action performed) | FALSE * */ function act($user, $cur_timeout){ /* [1] Export global caches + variables =========================================================*/ /* (1) Variables */ global $last_to; /* (2) Caches */ global $actions; /* (3) Log history file descriptor */ global $f_accesslog; /* (5) Update global state */ $state = get_gstate(); // manage error if( $state === false ) return false; /* [2] Manage timeout =========================================================*/ /* (1) Store @cur_timeout not to repeat it */ $last_to = $cur_timeout; /* (2) If no action for this @cur_timeout -> reset to 0 */ if( !isset($actions[$cur_timeout]) || !is_array($actions[$cur_timeout]) ) return false; /* (3) fetch actions for the current @cur_timeout */ $actionlist = $actions[floor($cur_timeout)]; /* [3] Manage permissions (action that can be performed) =========================================================*/ /* (1) Will contain available @id_action-s granted for the user */ $grantedFor = []; /* (2) Search an action the user can perform in the list */ foreach($actionlist as $id_action=>$action){ /* (3) If have permission -> add to list */ if( in_array($id_action, $user['can']) ) $grantedFor[] = $id_action; } /* (3) If no action found -> abort */ if( count($grantedFor) == 0 ){ slog('user not granted to any action', 'rfid-read:loop'); return false; } /* [4] Manage action depending on the actual STATE =========================================================*/ /* (1) Will contain the first available action depending on STATE */ $toPerform = null; /* (2) For each granted action, check the STATE */ foreach($grantedFor as $id_action){ $failed = false; /* (3) Fetch action data */ $action = $actionlist[$id_action]; /* (4) Check if the state allows the action to be performed */ for( $c = 0 ; $c < strlen($state) && $c < strlen($action['prev']) ; $c++ ){ // // {1} 'X' = any state -> so ignore // if( $action['prev'][$c] == 'X' ) continue; // {2} Other state : if not equal -> set as FAILED // else if( $action['prev'][$c] != $state[$c] ){ $failed = true; break; } } /* (5) If not failed -> save action in @toPerform */ if( !$failed ){ $toPerform = $id_action; break; } } /* (6) If no action cant be performed -> abort */ if( is_null($toPerform) ){ slog('global state allows no action to be performed', 'rfid-read:loop'); return false; } /* (7) Extract corresponding action */ $action = $actionlist[$toPerform]; /* [5] Process the action on the STATE =========================================================*/ /* (1) Update the state with the found action */ for( $c = 0 ; $c < strlen($state) && $c < strlen($action['next']) ; $c++ ){ // {1} If 'x' -> let the current state // if( $action['next'][$c] == 'X' ) continue; // {2} If other state -> update it // else $state[$c] = $action['next'][$c]; } /* (2) Update the state file */ $written = @file_put_contents(STATE_CONF, $state); /* (3) Manage error */ if( $written === false ) slog('cannot update STATE file', 'rfid-read:loop'); /* [6] Log action =========================================================*/ /* (1) Log action to default log file */ $f_accesslog->fwrite( json_encode([ time(), $user['id'], $toPerform ]).PHP_EOL ); /* (2) Return status */ return true; } function mfrc522_setup(){ /* [0] Initialize global variables =========================================================*/ /* (1) File descriptiors */ global $f_auth, $f_accesslog; /* (2) Caches */ global $actions; /* (3) Useful data */ global $FEATURE; /* [1] Open file descriptors on useful files =========================================================*/ /* (1) Read accesses */ $f_auth = new SplFileObject(AUTH_CONF, 'r'); $f_actions = new SplFileObject(ACTIONS_CONF, 'r'); /* (2) Append accesses (logs) */ if( !file_exists(DATA_DIR."/$FEATURE") ) file_put_contents(DATA_DIR."/$FEATURE", ''); $f_accesslog = new SplFileObject(DATA_DIR."/$FEATURE", 'a'); /* [2] Parse ACTIONS and cache them =========================================================*/ /* (1) Parse each line */ while( !$f_actions->eof() ){ /* (2) Try to parse line */ $parsed = json_decode($f_actions->fgets(), true); /* (2) Ignore if parse error */ if( is_null($parsed) ) continue; /* (3) Add key (timeout) to cache */ if( !isset($actions[$parsed[0]]) ) $actions[$parsed[0]] = []; /* (4) Add entry to cache */ $actions[$parsed[0]][$parsed[1]] = [ 'prev' => $parsed[2], 'next' => $parsed[3] ]; } /* (5) Free file descriptor */ $f_actions = null; /* [3] Check global state =========================================================*/ /* (1) Check file */ $gstate = get_gstate(); /* (2) Manage error */ if( $gstate === false ) return 127; /* [4] Update global state =========================================================*/ /* (1) Do it once to unlock gpio boot lag */ syscall(SOURCE_DIR.'/lib/global-state/update'); /* (2) Do it again to apply global state */ syscall(SOURCE_DIR.'/lib/global-state/update'); return 0; } function mfrc522_loop(){ /* [1] Load global variables =========================================================*/ /* (1) Persistent variabes */ global $last_code; global $last_user; global $timeout, $last_to; /* (2) Caches */ global $actions; global $FEATURE; /* (3) Get global state */ $gstate = get_gstate(); // Manage error if( $gstate === false ) return false; /* [1] Wait for rfid card =========================================================*/ /* (1) Read card */ $code = syscall(SOURCE_DIR."/feature/$FEATURE/read"); /* (2) If no card read -> reset @last_user / @timeout + abort */ if( $code === false ){ $last_user = null; $timeout = 0; $last_to = -1; return false; } /* (3) If code -> format it */ $code = strtoupper($code); if( $code != $last_code ) slog("card '$code' read", 'rfid-read:read'); $last_code = $code; /* [2] Check for user in auth list =========================================================*/ /* (1) Check user for this code */ $user = auth($code); /* (2) If not found -> reset @last_user / @timeout + abort */ if( is_null($user) ){ slog("Unknown user", "rfid-read:loop"); $last_user = null; $timeout = 0; $last_to = -1; return false; } /* [3] Manage @timeout incrementation + @last_user =========================================================*/ /* (1) If new user or different one -> Set timeout to current time */ if( $last_user != $user['id'] ) $timeout = microtime(true); /* [4] Manage action =========================================================*/ /* (1) Calc real timeout (diff in sec. between now and first time card passed) */ $cur_timeout = floor(microtime(true) - $timeout); /* (2) If already acted for this timeout -> abort */ if( $last_to == $cur_timeout ) return false; /* (3) Try to process action */ $performed = act($user, $cur_timeout); /* (2) If performed -> update chip according to new state */ if( $performed ){ // {1} Try to update state // $updateds = syscall(SOURCE_DIR.'/lib/global-state/update'); // {2} If not updated -> error // if( !$updateds ) slog("Cannot update chips to '{$gstate}'", 'rfid-read:loop'); // {3} If updated -> success // else slog("Chips updated to '{$gstate}'", 'rfid-read:loop'); /* (3) If not performed -> log error */ }else slog("Cannot perform action by '$code' at timeout $cur_timeout", 'rfid-read:loop'); /* (4) Store user for next loop */ $last_user = $user['id']; } /* [1] Set up daemon =========================================================*/ /* (1) Set up */ $exec = mfrc522_setup(); /* (2) Manage error */ if( $exec != 0 ){ slog('cannot set up the daemon', 'rfid-read:loop'); echo $exec; die($exec); } /* (3) Success message */ slog('daemon started (loop)', 'rfid-read:loop'); /* [2] Daemon loop =========================================================*/ while( true ){ /* (1) Store time */ $loop_ts = microtime(true); /* (2) Execute loop code */ mfrc522_loop(); /* (3) Wait for @LOOP_FREQ seconds */ while( microtime(true) - $loop_ts < ($LOOP_FREQ/1000) ); } ?>