#!/usr/bin/php Current global state as string * */ function get_gstate(){ /* (1) Export global file descriptor */ global $f_gstate; /* (2) Read first line */ $f_gstate->seek(0); $state = $f_gstate->fgets(); $state = preg_replace('@^\s+@', '', $state); $state = preg_replace('@\s+$@', '', $state); /* (3) Check data */ if( strlen($state) < 1 ) return false; /* (4) 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 * * @return success If success (action performed) | FALSE * */ function act($user){ /* [1] Export global caches + variables =========================================================*/ /* (1) Variables */ global $timeout; /* (2) Caches */ global $actions; /* (3) Log history file descriptor */ global $f_accesslog; /* (5) Update global state */ $state = get_gstate(); /* [2] Manage timeout =========================================================*/ /* (1) If no action for this @timeout -> reset to 0 */ if( !isset($actions[$timeout]) || !is_array($actions[$timeout]) ) $timeout = 0; /* (2) fetch actions for the current @timeout */ $actionlist = $actions[$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=>$acttion){ /* (3) If have permission -> add to list */ if( in_array($id_action, $user['can']) ) $grantedFor[] = $id_action; } /* (4) If no action found -> abort */ if( count($grantedFor) == 0 ){ slog('user not granted to any action', 'mfrc522: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', 'mfrc522:loop'); return false; } /* (7) Extract corresponding action */ $action = $grantedFor[$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', 'mfrc522: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, $f_gstate; /* (2) Caches */ global $actions; /* [1] Open file descriptors on useful files =========================================================*/ /* (1) Read accesses */ $f_auth = new SplFileObject(AUTH_CONF, 'r'); $f_actions = new SplFileObject(ACTIONS_CONF, 'r'); $f_gstate = new SplFileObject(STATE_CONF, 'r'); /* (2) Append accesses (logs) */ $f_accesslog = new SplFileObject(DEFAULT_DATA, '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 = $f_gstate->current(); if( !$gstate ) return 127; /* (3) Remove surrounding spaces */ $gstate = preg_replace('@^\s+@', '', $gstate); $gstate = preg_replace('@\s+$@', '', $gstate); /* (4) Manage error */ if( strlen($gstate) < 1 ) return 127; return 0; } function mfrc522_loop(){ /* [1] Load global variables =========================================================*/ /* (1) Persistent variabes */ global $last_user; global $timeout; /* (2) Caches */ global $actions; /* [1] Wait for rfid card =========================================================*/ /* (1) Read card */ $code = syscall(SOURCE_DIR.'/lib/mfrc522/read'); /* (2) If no card read -> reset @last_user / @timeout + abort */ if( $code === false ){ $last_user = null; $timeout = 0; return false; } /* (3) If code -> format it */ $code = strtoupper($code); slog("card '$code' read", 'mfrc522:read'); /* [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 (not authenticated)", "mfrc522:loop"); $last_user = null; $timeout = 0; return false; } /* [3] Manage @timeout incrementation + @last_user =========================================================*/ /* (1) If same as last -> increment @timeout */ if( $last_user == $user['id'] ) $timeout++; /* (2) If different -> reset @timeout to 0 */ else $timeout = 0; /* [4] Manage action =========================================================*/ /* (1) Try to process action */ $performed = act($user); /* (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 '".get_gstate()."'", 'mfrc522:loop'); // {3} If updated -> success // else slog("Chips updated to ".get_gstate()."'", 'mfrc522:loop'); /* (3) If not performed -> log error */ }else slog("Cannot perform action by '$code' at timeout $timeout", 'mfrc522: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', 'mfrc522:loop'); echo $exec; die($exec); } /* (3) Success message */ slog('daemon started (loop)', 'mfrc522:loop'); /* [2] Daemon loop =========================================================*/ while( true ){ $start_ts = microtime(true); mfrc522_loop(); while( microtime(true) - $start_ts < 0.5 ); } ?>