DIY Stand Alone Barcode Punch Station

Discussion for TimeTrex open source community developers.
Locked
EricM
Posts: 30
Joined: Wed Jan 16, 2013 2:22 pm

DIY Stand Alone Barcode Punch Station

Post by EricM »

I wanted a standalone punch station for the free version of Timetrex but it didn't look like anything was available, so I made one myself from a Raspberry Pi ($35) and a Metrologic MS3580 QuantumT Orbit Scanner ($50).

The plan is to give each employee a barcode with their employee number on it. If they just walk up and scan their barcode, it will look up their next punch, set the punch, and then print off a time card for them.

We also plan on having a few tokens with barcodes on them next to the timeclock. Scan a token and then your card to preform special punches. You can scan a "REPORT", "IN", "OUT", "BREAK", "LUNCH" to try to force a certain type of punch.

To set it up, I started with a clean Timetrex installation, and made the first user the account from which the barcode station will log in from.

The server then needed some minor adjustments to work since the free version's API doesn't have a working implemention of getUserPunch();

In APIAuthentication I made it so that user_id one (the administrator account created on first setup) can switchusers by changing

Code: Select all

if ( $this->getPermissionObject()->Check('company','view') AND $this->getPermissionObject()->Check('company','login_other_user') ) {
to

Code: Select all

if ( $this->getCurrentUserObject()->getId() == '1') {  
Then in APIPunch I changed

Code: Select all

if ( is_object($current_station) AND $current_station->checkAllowed( $user_id, $station_id, $station_type ) == TRUE ) {
to

Code: Select all

if ( '1'  == '1' ) {
to allow all stations

And then I commented out the 3 instances of:

Code: Select all

//'station_id' => $current_station->getId(),
That's it for the server. For the client, I put a 2GB SD card in my Raspberry Pi and installed ArchLinux. I set the resolution to 640x480 in config.txt, installed php (I had to enable soap and adjust open_basedir in php.ini), copied the timetrex install folder in /opt, and made an locked down user account. Then I set that account to automatically log into the command line, with these instructions:
https://wiki.archlinux.org/index.php/Au ... al_console

Finally, I set .bash_profile to automatically run my punch card script on boot.

Code: Select all

<?php


require_once('../../classes/modules/api/client/TimeTrexClientAPI.class.php');

//admin login info
$TIMETREX_URL = 'http://SERVERIPHERE:8085/api/soap/api.php';
$TIMETREX_USERNAME = 'ADMINNAMEHERE';
$TIMETREX_PASSWORD = 'ADMINPASSWORDHERE';

$colors = new Colors();
	
reconnect:
 
    echo "\n" . $colors->getColoredString("Reconnecting to server.....", "white", "red"). "\n";
    $api_session = new TimeTrexClientAPI();
	$api_session->Login( $TIMETREX_USERNAME, $TIMETREX_PASSWORD );
	
	if ( $TIMETREX_SESSION_ID == FALSE ) {
		goto reconnect;
	}
	
newscan:
	$punch_obj = new TimeTrexClientAPI( 'Punch' );
	$punchtime = time();
    $punchtype = "Normal";
	$punchstatus = "Auto";
    $punchid = 0;
	$firsttime = TRUE;
 while ($punchid == 0){
    
    if (time() - $punchtime > 10){
    	    $punchtype = "Normal";
	        $punchstatus = "Auto";
	}

    //get time from server and print it
    $result = $punch_obj->getUserPunch();
	if($result == false){
	 echo $colors->getColoredString("CONNECTION ERROR\n", "white", "red");
	goto reconnect;
	}
	$punch_data = $result->getResult();
	if($punch_data == false){
	 echo $colors->getColoredString("CONNECTION ERROR\n", "white", "red");
	goto reconnect;
	}
	echo "\n\n";
 	echo $punch_data['time_stamp'];
	 echo "\n\nScanner Ready\n\n";

	 
	 
	 if ($punchtype != "Normal"){
	echo $colors->getColoredString("Punch Type: " . $punchtype , "white", "red");
	} else{
	echo "Punch Type: ". $punchtype ;
	}
	echo "\n";
	 if ($punchstatus != "Auto"){
	echo $colors->getColoredString("Punch Direction: " . $punchstatus , "white", "red");
	} else{
    echo "Punch Direction: ". $punchstatus;
	}
	
	
	echo "\n\n";
	
	//skip the padding to leave the last report on the screen
	if ($firsttime == FALSE){
	 echo "\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n"; 
	 }
	 
	//get scanner input on a timer
    $input = fgets_u(STDIN);
   
   if (strpos($input,"IN") !== FALSE){
    $punchstatus = "In";
	$punchtime = time();
   }
   if (strpos($input,"OUT") !== FALSE){
    $punchstatus = "Out";
	$punchtime = time();
   }
   
   if (strpos($input,"BREAK") !== FALSE){
    $punchtype = "Break";
	$punchtime = time();
   }
   if (strpos($input,"LUNCH") !== FALSE){
    $punchtype = "Lunch";
	$punchtime = time();
   }
   
   if (strpos($input,"NORMAL") !== FALSE || strpos($input,"RESET") !== FALSE ||  strpos($input,"CLEAR") !== FALSE){
        $punchtype = "Normal";
	    $punchstatus = "Auto";
   }
    
   if (strpos($input,"REPORT") !== FALSE){
    $punchtype = "Report";
	$punchtime = time();
   }
      
   if (is_numeric($input)){
	$punchid = (int)$input;
   }   
   
   $firsttime = FALSE;
}
  
$user_obj = new TimeTrexClientAPI( 'User' );
if($user_obj  == false){
	 echo $colors->getColoredString("CONNECTION ERROR - user_obj\n", "white", "red");
	goto reconnect;
}

$result = $user_obj->getUser(array('filter_data' => array('employee_number' => $punchid)));
if($result == false){
     echo $colors->getColoredString("CONNECTION ERROR - getUser\n", "white", "red");
	goto reconnect;
}
$user_data = $result->getResult();
if($user_data  == false){
     echo $colors->getColoredString("CONNECTION ERROR- getResult -> getUser\n", "white", "red");
	goto reconnect;
}
if (is_bool($user_data) == TRUE) {
	echo $colors->getColoredString("\n\nUSER NOT FOUND IN DATABASE!\n\n", "white", "red");
	goto newscan;
}

echo "\nUser found.  Working...\n";

if ($punchtype != "Report"){
  
  
//Switch to the User we scanned in
$auth_obj = new TimeTrexClientAPI( 'Authentication' );
if($auth_obj  == false){
	 echo $colors->getColoredString("CONNECTION ERROR - auth_obj\n", "white", "red");
	goto reconnect;
}
$auth_obj->switchUser( $user_data[0]['id'] );
if($auth_obj  == false){
	 echo $colors->getColoredString("CONNECTION ERROR - switchUser\n", "white", "red");
	goto reconnect;
}

$punch_obj = new TimeTrexClientAPI( 'Punch' );
if($punch_obj  == false){
	 echo $colors->getColoredString("CONNECTION ERROR - punch_obj\n", "white", "red");
	goto reconnect;
}

$result = $punch_obj->getUserPunch();
if($result  == false){
	 echo $colors->getColoredString("CONNECTION ERROR - getUserPunch\n", "white", "red");
	goto reconnect;
}

$punch_data = $result->getResult();
if($punch_data  == false){
     echo $colors->getColoredString("CONNECTION ERROR - getResult -> getUserPunch\n", "white", "red");
	goto reconnect;
}

//override the automaticlly generated punch
if ($punchstatus == "In"){
 $punch_data['status_id']=10;
}
if ($punchstatus == "Out"){
$punch_data['status_id']=20;
}

if ($punchtype == "Break"){
 $punch_data['type_id']=30;
}
if ($punchtype == "Lunch"){
$punch_data['type_id']=20;
}

$result = $punch_obj->setUserPunch($punch_data);
if($result  == false){
     echo $colors->getColoredString("CONNECTION ERROR - setUserPunch\n", "white", "red");
	goto reconnect;
}

$result = $result->getResult();

if ($result == false) {
    echo $colors->getColoredString("\nPLEASE WAIT AT LEAST ONE MINUTE BETWEEN PUNCHES!\n", "white", "red");
	goto reconnect;
}else {
echo "#{$user_data[0]['id']} Punch Complete\n";
}

$api_session->Logout();
}

//Generate the punch report
//log back in as admin

$api_session->Login( $TIMETREX_USERNAME, $TIMETREX_PASSWORD );
if ( $TIMETREX_SESSION_ID == FALSE ) {
	 echo $colors->getColoredString("CONNECTION ERROR - REPORT LOGIN FAILED\n", "white", "red");
	goto reconnect;
}

//get report
echo "____________________\n\n";
echo "Punch Summary Report\n";
echo "____________________\n\n";
$report_obj = new TimeTrexClientAPI( 'PunchSummaryReport' );
if($report_obj  == false){
     echo $colors->getColoredString("CONNECTION ERROR - report_obj\n", "white", "red");
	goto reconnect;
}

$config = $report_obj->getTemplate('by_employee+punch_summary+total_time+include_user_id')->getResult();
if($config  == false){
     echo $colors->getColoredString("CONNECTION ERROR - getTemplate\n", "white", "red");
	goto reconnect;
}

//adjust template
$config['-1010-time_period']['time_period'] = 'this_pay_period';
$config['include_user_id'] = $user_data[0]['id'];

//let the server generate the report
$result = $report_obj->getPunchSummaryReport($config , 'csv' );
if($result  == false){
     echo $colors->getColoredString("CONNECTION ERROR - getPunchSummaryReport\n", "white", "red");
	goto reconnect;
}

$result = $result->getResult();
if($result  == false){
     echo $colors->getColoredString("CONNECTION ERROR - getResult -> getPunchSummaryReport\n", "white", "red");
	goto reconnect;
}

//parse and display the CSV file

//decode report
$input = base64_decode($result['data']);
//split the line breaks
$csvData = str_getcsv($input, "\n"); 
//split at the commas
foreach ($csvData as &$value) {
     $value = str_getcsv($value, ','); 
}

$total = 0;  // tally of total hours worked

//print table headers
echo "#{$user_data[0]['id']}: {$user_data[0]['last_name']}, {$user_data[0]['first_name']}\n\n";

//echo each line of data
foreach ($csvData as &$value) {
     echo str_pad ($value[2], 10);
	 echo str_pad ($value[3], 20);
	 echo str_pad ($value[4], 10);
	 echo str_pad ($value[5], 20);
     if (is_numeric($value[6]) ) {
	   $total = $total + (float)$value[6];
	   if ((float)$value[6] != 0 ){
	   printf( "% 6.3f", $value[6]); 
	   }
	 }else{
	 echo str_pad($value[6], 10);
	 }
	 echo "\n";
}	

//print total hours worked
echo str_pad (" ", 60);
printf( "% 6.3f",  $total); 

//done
goto newscan;
  
			
function fgets_u($pStdn) {
        $pArr = array($pStdn);
        if (false === ($num_changed_streams = stream_select($pArr, $write = NULL, $except = NULL, 10 ))) {
            print("\$ 001 Socket Error : UNABLE TO WATCH STDIN.\n");
            return FALSE;
        } elseif ($num_changed_streams > 0) {
                return trim(fgets($pStdn, 1024));
        }
    }
	
	
?>

<?php
 
	class Colors {
		private $foreground_colors = array();
		private $background_colors = array();
 
		public function __construct() {
			// Set up shell colors
			$this->foreground_colors['black'] = '0;30';
			$this->foreground_colors['dark_gray'] = '1;30';
			$this->foreground_colors['blue'] = '0;34';
			$this->foreground_colors['light_blue'] = '1;34';
			$this->foreground_colors['green'] = '0;32';
			$this->foreground_colors['light_green'] = '1;32';
			$this->foreground_colors['cyan'] = '0;36';
			$this->foreground_colors['light_cyan'] = '1;36';
			$this->foreground_colors['red'] = '0;31';
			$this->foreground_colors['light_red'] = '1;31';
			$this->foreground_colors['purple'] = '0;35';
			$this->foreground_colors['light_purple'] = '1;35';
			$this->foreground_colors['brown'] = '0;33';
			$this->foreground_colors['yellow'] = '1;33';
			$this->foreground_colors['light_gray'] = '0;37';
			$this->foreground_colors['white'] = '1;37';
 
			$this->background_colors['black'] = '40';
			$this->background_colors['red'] = '41';
			$this->background_colors['green'] = '42';
			$this->background_colors['yellow'] = '43';
			$this->background_colors['blue'] = '44';
			$this->background_colors['magenta'] = '45';
			$this->background_colors['cyan'] = '46';
			$this->background_colors['light_gray'] = '47';
		}
 
		// Returns colored string
		public function getColoredString($string, $foreground_color = null, $background_color = null) {
			$colored_string = "";
 
			// Check if given foreground color found
			if (isset($this->foreground_colors[$foreground_color])) {
				$colored_string .= "\033[" . $this->foreground_colors[$foreground_color] . "m";
			}
			// Check if given background color found
			if (isset($this->background_colors[$background_color])) {
				$colored_string .= "\033[" . $this->background_colors[$background_color] . "m";
			}
 
			// Add string and end coloring
			$colored_string .=  $string . "\033[0m";
 
			return $colored_string;
		}
 
		// Returns all foreground color names
		public function getForegroundColors() {
			return array_keys($this->foreground_colors);
		}
 
		// Returns all background color names
		public function getBackgroundColors() {
			return array_keys($this->background_colors);
		}
	}
 
?>
I need to do a bit more testing, but after it is put into production I will post a SD card image of my Raspberry Pi installation.
johnsmith
Posts: 1
Joined: Thu Feb 21, 2013 1:55 pm

Re: DIY Stand Alone Barcode Punch Station

Post by johnsmith »

That is seriously impressive, and I would love to try the image you have if you would be so kind !

Very well done and fingers crossed I can have a play myself :)
EricM
Posts: 30
Joined: Wed Jan 16, 2013 2:22 pm

Re: DIY Stand Alone Barcode Punch Station

Post by EricM »

I looked into releasing an image, but I can't be sure that I've sanitized all of my personal information off of the image...

The directions posted above are very complete, I would suggest just following those. Feel free to ask if you need any clarifications.

Here is the most updated version of my punch station script:

Code: Select all

<?php


require_once('../../classes/modules/api/client/TimeTrexClientAPI.class.php');

//admin login info
$TIMETREX_URL = 'http://DOMAIN:8085/api/soap/api.php';
$TIMETREX_USERNAME = 'admin';
$TIMETREX_PASSWORD = 'PASSWORD';

$colors = new Colors();

reconnect:

$TIMETREX_SESSION_ID= FALSE;
echo "\n" . $colors->getColoredString("Reconnecting to server.....", "white", "red"). "\n";
$api_session = new TimeTrexClientAPI();
$api_session->Login( $TIMETREX_USERNAME, $TIMETREX_PASSWORD );

if ( $TIMETREX_SESSION_ID == FALSE ) {
    sleep(10);
    goto reconnect;
}


newscan:
$punch_obj = new TimeTrexClientAPI( 'Punch' );
$punchtime = time();
$punchtype = "Normal";
$punchstatus = "Auto";
$punchid = 0;
$firsttime = TRUE;
while ($punchid == 0) {

    if (time() - $punchtime > 10) {
        $punchtype = "Normal";
        $punchstatus = "Auto";
    }

    //get time from server and print it
    $result = $punch_obj->getUserPunch();
    if ($result == false) {
        echo $colors->getColoredString("CONNECTION ERROR", "white", "red");
        echo "\n";
        goto reconnect;
    }
    $punch_data = $result->getResult();
    if ($punch_data == false) {
        echo $colors->getColoredString("CONNECTION ERROR", "white", "red");
        echo "\n";
        goto reconnect;
    }
    echo "\n\n";
    echo $punch_data['time_stamp'];
    echo "\n\nScanner Ready\n\n";

    if ($punchtype != "Normal") {
        echo $colors->getColoredString("Punch Type: " . $punchtype , "white", "red");
    } else {
        echo "Punch Type: ". $punchtype ;
    }
    echo "\n";
    if ($punchstatus != "Auto") {
        echo $colors->getColoredString("Punch Direction: " . $punchstatus , "white", "red");
    } else {
        echo "Punch Direction: ". $punchstatus;
    }

    echo "\n\n";

    //skip the padding to leave the last report on the screen
    if ($firsttime == FALSE) {
        echo "\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n";
    }

    //get scanner input if over 10 seconds, refresh screen
    $input = fgets_u(STDIN,10);

    if (strpos($input,"IN") !== FALSE) {
        $punchstatus = "In";
        $punchtime = time();
    }
    if (strpos($input,"OUT") !== FALSE) {
        $punchstatus = "Out";
        $punchtime = time();
    }

    if (strpos($input,"BREAK") !== FALSE) {
        $punchtype = "Break";
        $punchstatus = "Out";
        $punchtime = time();
    }
    if (strpos($input,"LUNCH") !== FALSE) {
        $punchtype = "Lunch";
        $punchstatus = "Out";
        $punchtime = time();
    }

    if (strpos($input,"NORMAL") !== FALSE || strpos($input,"RESET") !== FALSE ||  strpos($input,"CLEAR") !== FALSE) {
        $punchtype = "Normal";
        $punchstatus = "Auto";
    }

    if (strpos($input,"REPORT") !== FALSE) {
        $punchtype = "Report";
        $punchtime = time();
    }

    if (is_numeric($input)) {
        $punchid = (int)$input;
    }

    $firsttime = FALSE;
}

$user_obj = new TimeTrexClientAPI( 'User' );
if ($user_obj  == false) {
    echo $colors->getColoredString("CONNECTION ERROR - user_obj\n", "white", "red");
    goto reconnect;
}

$result = $user_obj->getUser(array('filter_data' => array('employee_number' => $punchid)));
if ($result == false) {
    echo $colors->getColoredString("CONNECTION ERROR - getUser", "white", "red");
    echo "\n";
    goto reconnect;
}
$user_data = $result->getResult();
if ($user_data  == false) {
    echo $colors->getColoredString("CONNECTION ERROR- getResult -> getUser", "white", "red");
    echo "\n";
    goto reconnect;
}
if (is_bool($user_data) == TRUE) {
    echo "\n\n";
    echo $colors->getColoredString("USER NOT FOUND IN DATABASE!", "white", "red");
    echo "\n\n";
    goto newscan;
}

echo "\nUser found.  Working...\n";

if ($punchtype != "Report") {


	//Switch to the User we scanned in
    $auth_obj = new TimeTrexClientAPI( 'Authentication' );
    if ($auth_obj  == false) {
        echo $colors->getColoredString("CONNECTION ERROR - auth_obj", "white", "red");
        echo "\n";
        goto reconnect;
    }
    $auth_obj->switchUser( $user_data[0]['id'] );
    if ($auth_obj  == false) {
        echo $colors->getColoredString("CONNECTION ERROR - switchUser", "white", "red");
        echo "\n";
        goto reconnect;
    }

    $punch_obj = new TimeTrexClientAPI( 'Punch' );
    if ($punch_obj  == false) {
        echo $colors->getColoredString("CONNECTION ERROR - punch_obj", "white", "red");
        echo "\n";
        goto reconnect;
    }

    $result = $punch_obj->getUserPunch();
    if ($result  == false) {
        echo $colors->getColoredString("CONNECTION ERROR - getUserPunch", "white", "red");
        echo "\n";
        goto reconnect;
    }

    $punch_data = $result->getResult();
    if ($punch_data  == false) {
        echo $colors->getColoredString("CONNECTION ERROR - getResult -> getUserPunch", "white", "red");
        echo "\n";
        goto reconnect;
    }

	//override the automaticlly generated punch
    if ($punchstatus == "In") {
        $punch_data['status_id']=10;
    }
    if ($punchstatus == "Out") {
        $punch_data['status_id']=20;
    }

    if ($punchtype == "Break") {
        $punch_data['type_id']=30;
    }
    if ($punchtype == "Lunch") {
        $punch_data['type_id']=20;
    }

    if ($punchtype != "Report") {

        $result = $punch_obj->setUserPunch($punch_data);
        if ($result  == false) {
            echo $colors->getColoredString("CONNECTION ERROR - setUserPunch", "white", "red");
            echo "\n";
            goto reconnect;
        }

        $result = $result->getResult();

        if ($result == false) {
            echo $colors->getColoredString("\nPLEASE WAIT AT LEAST ONE MINUTE BETWEEN PUNCHES!", "white", "red");
            echo "\n";
            goto reconnect;
        } else {
            echo "#{$user_data[0]['employee_number']} Punch Complete\n";
        }

    }

    $api_session->Logout();
}

//Generate the punch report
//log back in as admin

$api_session->Login( $TIMETREX_USERNAME, $TIMETREX_PASSWORD );
if ( $TIMETREX_SESSION_ID == FALSE ) {
    echo $colors->getColoredString("CONNECTION ERROR - REPORT LOGIN FAILED", "white", "red");
    echo "\n";
    goto reconnect;
}

//get report
echo "____________________\n\n";
echo "Punch Summary Report\n";
echo "____________________\n\n";
$report_obj = new TimeTrexClientAPI( 'PunchSummaryReport' );
if ($report_obj  == false) {
    echo $colors->getColoredString("CONNECTION ERROR - report_obj", "white", "red");
    echo "\n";
    goto reconnect;
}

$config = $report_obj->getTemplate('by_employee+punch_summary+total_time')->getResult();
if ($config  == false) {
    echo $colors->getColoredString("CONNECTION ERROR - getTemplate", "white", "red");
    echo "\n";
    goto reconnect;
}

//adjust template
$config['-1010-time_period']['time_period'] = 'this_pay_period';
$config['employee_number'] = $user_data[0]['employee_number'];


//let the server generate the report
$result = $report_obj->getPunchSummaryReport($config , 'csv' );
if ($result  == false) {
    echo $colors->getColoredString("CONNECTION ERROR - getPunchSummaryReport", "white", "red");
    echo "\n";
    goto reconnect;
}

$result = $result->getResult();
if ($result  == false) {
    echo $colors->getColoredString("CONNECTION ERROR - getResult -> getPunchSummaryReport", "white", "red");
    echo "\n";
    goto reconnect;
}

//parse and display the CSV file

//decode report
$input = base64_decode($result['data']);
//split the line breaks
$input = str_replace("\"", "", $input);
$csvData = explode( "\n", $input);
//split at the commas
foreach ($csvData as &$value) {
    $value = explode(',', $value);
}

$total = 0;  // tally of total hours worked

//print table headers
echo "#{$user_data[0]['employee_number']}: {$user_data[0]['last_name']}, {$user_data[0]['first_name']}\n\n";

//echo each line of data
foreach ($csvData as &$value) {
    if (isset($value[2]) && isset($value[3]) &&isset($value[4]) && isset($value[5]) && isset($value[6])) {
        echo str_pad ($value[2], 10);
        echo str_pad ($value[3], 20);
        echo str_pad ($value[4], 10);
        echo str_pad ($value[5], 20);
        if (is_numeric($value[6]) ) {
            $total = $total + (float)$value[6];
            if ((float)$value[6] != 0 ) {
                printf( "% 6.3f", $value[6]);
            }
        } else {
            echo str_pad($value[6], 10);
        }
        echo "\n";
    }
}
//print total hours worked
echo str_pad (" ", 60);
echo $colors->getColoredString(sprintf( "% 6.3f",  $total), "white", "green");
//done
goto newscan;


function fgets_u($pStdn,$delay) {
    $pArr = array($pStdn);
    if (false === ($num_changed_streams = stream_select($pArr, $write = NULL, $except = NULL,$delay ))) {
        print("\$ 001 Socket Error : UNABLE TO WATCH STDIN.\n");
        return FALSE;
    }
    elseif ($num_changed_streams > 0) {
        return trim(fgets($pStdn, 1024));
    }
}
?>

<?php

class Colors {
    private $foreground_colors = array();
    private $background_colors = array();

    public function __construct() {
        // Set up shell colors
        $this->foreground_colors['black'] = '0;30';
        $this->foreground_colors['dark_gray'] = '1;30';
        $this->foreground_colors['blue'] = '0;34';
        $this->foreground_colors['light_blue'] = '1;34';
        $this->foreground_colors['green'] = '0;32';
        $this->foreground_colors['light_green'] = '1;32';
        $this->foreground_colors['cyan'] = '0;36';
        $this->foreground_colors['light_cyan'] = '1;36';
        $this->foreground_colors['red'] = '0;31';
        $this->foreground_colors['light_red'] = '1;31';
        $this->foreground_colors['purple'] = '0;35';
        $this->foreground_colors['light_purple'] = '1;35';
        $this->foreground_colors['brown'] = '0;33';
        $this->foreground_colors['yellow'] = '1;33';
        $this->foreground_colors['light_gray'] = '0;37';
        $this->foreground_colors['white'] = '1;37';

        $this->background_colors['black'] = '40';
        $this->background_colors['red'] = '41';
        $this->background_colors['green'] = '42';
        $this->background_colors['yellow'] = '43';
        $this->background_colors['blue'] = '44';
        $this->background_colors['magenta'] = '45';
        $this->background_colors['cyan'] = '46';
        $this->background_colors['light_gray'] = '47';
    }

    // Returns colored string
    public function getColoredString($string, $foreground_color = null, $background_color = null) {
        $colored_string = "";

        // Check if given foreground color found
        if (isset($this->foreground_colors[$foreground_color])) {
            $colored_string .= "\033[" . $this->foreground_colors[$foreground_color] . "m";
        }
        // Check if given background color found
        if (isset($this->background_colors[$background_color])) {
            $colored_string .= "\033[" . $this->background_colors[$background_color] . "m";
        }

        // Add string and end coloring
        $colored_string .=  $string . "\033[0m";

        return $colored_string;
    }

    // Returns all foreground color names
    public function getForegroundColors() {
        return array_keys($this->foreground_colors);
    }

    // Returns all background color names
    public function getBackgroundColors() {
        return array_keys($this->background_colors);
    }
}


?>
Attachments
IMAG0597.jpg
IMAG0594.jpg
EricM
Posts: 30
Joined: Wed Jan 16, 2013 2:22 pm

Re: DIY Stand Alone Barcode Punch Station

Post by EricM »

I recently upgraded from a 5.x version of TimeTrex (on an old Windows XP install) to 7.3.1 (on an Ubuntu 12.04 LTS VM in Proxmox) without any problems.

I would like to note that this modification still works in TimeTrex 7.3.1, but there are a few minor changes.

In APIAuthentication, change two instances of (one is in newSession, the other in switchUser)

Code: Select all

if ( $this->getPermissionObject()->Check('company','view') AND $this->getPermissionObject()->Check('company','login_other_user') ) {
to

Code: Select all

if ( $this->getCurrentUserObject()->getId() == '1') {  
In APIPunch, change once instance of

Code: Select all

if ( is_object($current_station) AND $current_station->checkAllowed( $user_id, $station_id, $station_type ) == TRUE ) {
to

Code: Select all

if ( '1'  == '1' ) {
and comment out one line

Code: Select all

$data['station_id'] = $current_station->getId();
to

Code: Select all

//$data['station_id'] = $current_station->getId();
TimeTrex has been working great for us, with over 60,000 punches processed through our timeclocks so far.
systemadmin
Posts: 7
Joined: Tue Jun 10, 2014 12:49 pm

Re: DIY Stand Alone Barcode Punch Station

Post by systemadmin »

Hi,

thanks for sharing the great idea of the standalone punch station. I just set up a prototype with some changes to your original setup

- replaced the bar code scanner by a cheap (10 EUR) 125kHz RFID USB reader
- added a small USB keypad (8 EUR) to enter codes for IN/OUT/BREAK/REPORT
- changed the output to german :)

[img][/http://wiki.system-admin.info/DSC_0027.JPG]

I added a user "punch" that is automatically loged in via /etc/inittab. The user has /home/punch/punch.php as his shell setup via /etc/passwd. This makes sure the application is startet automatically and the user can not reach a regular command prompt.

changes to /etc/inittab:

Code: Select all

# /sbin/getty invocations for the runlevels.
#
# The "id" field MUST be the same as the last
# characters of the device (after "tty").
#
# Format:
#  <id>:<runlevels>:<action>:<process>
#
# Note that on most Debian systems tty7 is used by the X Window System,
# so if you want to add more getty's go ahead but skip tty7 if you run X.
#
1:2345:respawn:/bin/login -f punch tty1 </dev/tty1 >/dev/tty1 2>&1
2:23:respawn:/sbin/getty 38400 tty2
3:23:respawn:/sbin/getty 38400 tty3
4:23:respawn:/sbin/getty 38400 tty4
5:23:respawn:/sbin/getty 38400 tty5
6:23:respawn:/sbin/getty 38400 tty6

from /etc/passwd:

Code: Select all

punch:x:1001:1004:,,,:/home/punch:/home/punch/punch.php



punch.php

Code: Select all

#!/usr/bin/php
<?php


require_once('./TimeTrexClientAPI.class.php');

//admin login info
$TIMETREX_URL = 'http://127.0.0.1/timetrex/api/soap/api.php';
$TIMETREX_USERNAME = 'admin';
$TIMETREX_PASSWORD = 'Wr$$dNuW';

$colors = new Colors();

exec("/usr/bin/clear");

reconnect:

$TIMETREX_SESSION_ID= FALSE;
echo "\n" . $colors->getColoredString("Verbindung wird aufgebaut.....", "white", "red"). "\n";
$api_session = new TimeTrexClientAPI();
$api_session->Login( $TIMETREX_USERNAME, $TIMETREX_PASSWORD );

if ( $TIMETREX_SESSION_ID == FALSE ) {
    sleep(10);
    goto reconnect;
}


newscan:
$punch_obj = new TimeTrexClientAPI( 'Punch' );
$punchtime = time();
$punchtype = "Normal";
$punchstatus = "Auto";
$punchid = 0;
$firsttime = TRUE;
while ($punchid == 0) {

    if (time() - $punchtime > 10) {
        $punchtype = "Normal";
        $punchstatus = "Auto";
    }

    //get time from server and print it
    $result = $punch_obj->getUserPunch();
    if ($result == false) {
        echo $colors->getColoredString("VERBINDUNGSFEHLER", "white", "red");
        echo "\n";
        goto reconnect;
    }
    $punch_data = $result->getResult();
    if ($punch_data == false) {
        echo $colors->getColoredString("VERBINDUNGSFEHLER", "white", "red");
        echo "\n";
        goto reconnect;
    }
    echo "\n\n";
    echo $punch_data['time_stamp'];
    echo "\n\nScanner bereit\n\n";

    if ($punchtype != "Normal") {
        echo $colors->getColoredString("Stempel Art: " . $punchtype , "white", "red");
    } else {
        echo "Stempel Modus: ". $punchtype ;
    }
    echo "\n";
    if ($punchstatus != "Auto") {
        echo $colors->getColoredString("Stempel Richtung: " . $punchstatus , "white", "red");
    } else {
        echo "Stempel Richtung: ". $punchstatus;
    }

    echo "\n\n";

    //skip the padding to leave the last report on the screen
    if ($firsttime == FALSE) {
        echo "\n\n\n";
		echo "Tastaturkürzel (falls notwendig)\n\n";
		echo "<1>  <ENTER>    : Kommt (In)\n";
		echo "<2>  <ENTER>    : Geht (Out)\n";
		echo "<3>  <ENTER>    : Pause (Break)\n";
		echo "<4>  <ENTER>    : Essen (Lunch)\n";
		echo "<5>  <ENTER>    : Bericht (Report)\n";
		echo "<00> <ENTER>    : Automatik\n";
		echo "\n\n";
		echo "Bitte Transponder vor das Lesegerät halten\n";
		echo "\n\n\n\n\n\n\n";
        //echo "\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n";
    }

    //get scanner input if over 10 seconds, refresh screen
	$mode_change_flag = "FALSE";
	// make sure NUMLOCK is on
	exec('/usr/bin/setleds -D +num');
    $input = fgets_u(STDIN,10);

    if (strcmp($input,"1") == 0) {
        $punchtype = "Normal";
        $punchstatus = "In";
        $punchtime = time();
		$mode_change_flag = "TRUE";
		$input = "";
    }
    if (strcmp($input,"2") == 0) {
        $punchstatus = "Out";
        $punchtime = time();
		$mode_change_flag = "TRUE";
		$input = "";
    }

    if (strcmp($input,"3") == 0) {
        $punchtype = "Break";
        $punchstatus = "Out";
        $punchtime = time();
		$mode_change_flag = "TRUE";
		$input = "";
    }
    if (strcmp($input,"4") == 0) {
        $punchtype = "Lunch";
        $punchstatus = "Out";
        $punchtime = time();
		$mode_change_flag = "TRUE";
		$input = "";
    }

    if (strcmp($input,"00") == 0) {
        $punchtype = "Normal";
        $punchstatus = "Auto";
		$mode_change_flag = "TRUE";
		$input = "";
    }

    if (strcmp($input,"5") == 0) {
        $punchtype = "Report";
        $punchtime = time();
		$mode_change_flag = "TRUE";
		$input = "";
    }


	if ($mode_change_flag !== FALSE) {
   	 	if (is_numeric($input)) {
   		    $punchid = (int)$input;
   		}
	}

    $firsttime = FALSE;
}

$user_obj = new TimeTrexClientAPI( 'User' );
if ($user_obj  == false) {
    echo $colors->getColoredString("VERBINDUNGSFEHLER - user_obj\n", "white", "red");
    goto reconnect;
}

$result = $user_obj->getUser(array('filter_data' => array('employee_number' => $punchid)));
if ($result == false) {
    echo $colors->getColoredString("VERBINDUNGSFEHLER - getUser", "white", "red");
    echo "\n";
    goto reconnect;
}
$user_data = $result->getResult();
if ($user_data  == false) {
    echo $colors->getColoredString("VERBINDUNGSFEHLER- getResult -> getUser", "white", "red");
    echo "\n";
    goto reconnect;
}
if (is_bool($user_data) == TRUE) {
    echo "\n\n";
    echo $colors->getColoredString("MITARBEITER NICHT GEFUNDEN!", "white", "red");
    echo "\n\n";
    goto newscan;
}

echo "\nMitarbeiterIn gefunden.  Moment...\n";

if ($punchtype != "Report") {


   //Switch to the User we scanned in
    $auth_obj = new TimeTrexClientAPI( 'Authentication' );
    if ($auth_obj  == false) {
        echo $colors->getColoredString("VERBINDUNGSFEHLER - auth_obj", "white", "red");
        echo "\n";
        goto reconnect;
    }
    $auth_obj->switchUser( $user_data[0]['id'] );
    if ($auth_obj  == false) {
        echo $colors->getColoredString("VERBINDUNGSFEHLER - switchUser", "white", "red");
        echo "\n";
        goto reconnect;
    }

    $punch_obj = new TimeTrexClientAPI( 'Punch' );
    if ($punch_obj  == false) {
        echo $colors->getColoredString("VERBINDUNGSFEHLER - punch_obj", "white", "red");
        echo "\n";
        goto reconnect;
    }

    $result = $punch_obj->getUserPunch();
    if ($result  == false) {
        echo $colors->getColoredString("VERBINDUNGSFEHLER - getUserPunch", "white", "red");
        echo "\n";
        goto reconnect;
    }

    $punch_data = $result->getResult();
    if ($punch_data  == false) {
        echo $colors->getColoredString("VERBINDUNGSFEHLER - getResult -> getUserPunch", "white", "red");
        echo "\n";
        goto reconnect;
    }

   //override the automaticlly generated punch
    if ($punchstatus == "In") {
        $punch_data['status_id']=10;
    }
    if ($punchstatus == "Out") {
        $punch_data['status_id']=20;
    }

    if ($punchtype == "Break") {
        $punch_data['type_id']=30;
    }
    if ($punchtype == "Lunch") {
        $punch_data['type_id']=20;
    }

    if ($punchtype != "Report") {

        $result = $punch_obj->setUserPunch($punch_data);
        if ($result  == false) {
            echo $colors->getColoredString("VERBINDUNGSFEHLER - setUserPunch", "white", "red");
            echo "\n";
            goto reconnect;
        }

        $result = $result->getResult();

        if ($result == false) {
            echo $colors->getColoredString("\nBITTE MIND. EINE MINUTE ZWISCHEN DEN BUCHUNGEN!", "white", "red");
            echo "\n";
            goto reconnect;
        } else {
            echo "#{$user_data[0]['employee_number']} Punch Complete\n";
        }

    }

    $api_session->Logout();
}

//Generate the punch report
//log back in as admin

$api_session->Login( $TIMETREX_USERNAME, $TIMETREX_PASSWORD );
if ( $TIMETREX_SESSION_ID == FALSE ) {
    echo $colors->getColoredString("VERBINDUNGSFEHLER - BERICHT NICHT VERFÜGBAR", "white", "red");
    echo "\n";
    goto reconnect;
}

//get report
echo "____________________\n\n";
echo "Bericht\n";
echo "____________________\n\n";
$report_obj = new TimeTrexClientAPI( 'PunchSummaryReport' );
if ($report_obj  == false) {
    echo $colors->getColoredString("VERBINDUNGSFEHLER - report_obj", "white", "red");
    echo "\n";
    goto reconnect;
}

$config = $report_obj->getTemplate('by_employee+punch_summary+total_time')->getResult();
if ($config  == false) {
    echo $colors->getColoredString("VERBINDUNGSFEHLER - getTemplate", "white", "red");
    echo "\n";
    goto reconnect;
}

//adjust template
$config['-1010-time_period']['time_period'] = 'this_pay_period';
$config['employee_number'] = $user_data[0]['employee_number'];


//let the server generate the report
$result = $report_obj->getPunchSummaryReport($config , 'csv' );
if ($result  == false) {
    echo $colors->getColoredString("VERBINDUNGSFEHLER - getPunchSummaryReport", "white", "red");
    echo "\n";
    goto reconnect;
}

$result = $result->getResult();
if ($result  == false) {
    echo $colors->getColoredString("VERBINDUNGSFEHLER - getResult -> getPunchSummaryReport", "white", "red");
    echo "\n";
    goto reconnect;
}

//parse and display the CSV file

//decode report
$input = base64_decode($result['data']);
//split the line breaks
$input = str_replace("\"", "", $input);
$csvData = explode( "\n", $input);
//split at the commas
foreach ($csvData as &$value) {
    $value = explode(',', $value);
}

$total = 0;  // tally of total hours worked

//print table headers
echo "#{$user_data[0]['employee_number']}: {$user_data[0]['last_name']}, {$user_data[0]['first_name']}\n\n";

//echo each line of data
foreach ($csvData as &$value) {
    if (isset($value[2]) && isset($value[3]) &&isset($value[4]) && isset($value[5]) && isset($value[6])) {
        echo str_pad ($value[2], 12);
        echo str_pad ($value[3], 20);
        echo str_pad ($value[4], 12);
        echo str_pad ($value[5], 20);
        if (is_numeric($value[6]) ) {
            $total = $total + (float)$value[6];
            if ((float)$value[6] != 0 ) {
                printf( "% 6.3f", $value[6]);
            }
        } else {
            echo str_pad($value[6], 10);
        }
        echo "\n";
    }
}
//print total hours worked
echo str_pad (" ", 60);
echo $colors->getColoredString(sprintf( "% 6.3f",  $total), "white", "green");
//done
goto newscan;


function fgets_u($pStdn,$delay) {
    $pArr = array($pStdn);
    if (false === ($num_changed_streams = stream_select($pArr, $write = NULL, $except = NULL,$delay ))) {
        print("\$ 001 Socket Error : UNABLE TO WATCH STDIN.\n");
        return FALSE;
    }
    elseif ($num_changed_streams > 0) {
        return trim(fgets($pStdn, 1024));
    }
}
?>

<?php

class Colors {
    private $foreground_colors = array();
    private $background_colors = array();

    public function __construct() {
        // Set up shell colors
        $this->foreground_colors['black'] = '0;30';
        $this->foreground_colors['dark_gray'] = '1;30';
        $this->foreground_colors['blue'] = '0;34';
        $this->foreground_colors['light_blue'] = '1;34';
        $this->foreground_colors['green'] = '0;32';
        $this->foreground_colors['light_green'] = '1;32';
        $this->foreground_colors['cyan'] = '0;36';
        $this->foreground_colors['light_cyan'] = '1;36';
        $this->foreground_colors['red'] = '0;31';
        $this->foreground_colors['light_red'] = '1;31';
        $this->foreground_colors['purple'] = '0;35';
        $this->foreground_colors['light_purple'] = '1;35';
        $this->foreground_colors['brown'] = '0;33';
        $this->foreground_colors['yellow'] = '1;33';
        $this->foreground_colors['light_gray'] = '0;37';
        $this->foreground_colors['white'] = '1;37';

        $this->background_colors['black'] = '40';
        $this->background_colors['red'] = '41';
        $this->background_colors['green'] = '42';
        $this->background_colors['yellow'] = '43';
        $this->background_colors['blue'] = '44';
        $this->background_colors['magenta'] = '45';
        $this->background_colors['cyan'] = '46';
        $this->background_colors['light_gray'] = '47';
    }

    // Returns colored string
    public function getColoredString($string, $foreground_color = null, $background_color = null) {
        $colored_string = "";

        // Check if given foreground color found
        if (isset($this->foreground_colors[$foreground_color])) {
            $colored_string .= "\033[" . $this->foreground_colors[$foreground_color] . "m";
        }
        // Check if given background color found
        if (isset($this->background_colors[$background_color])) {
            $colored_string .= "\033[" . $this->background_colors[$background_color] . "m";
        }

        // Add string and end coloring
        $colored_string .=  $string . "\033[0m";

        return $colored_string;
    }

    // Returns all foreground color names
    public function getForegroundColors() {
        return array_keys($this->foreground_colors);
    }

    // Returns all background color names
    public function getBackgroundColors() {
        return array_keys($this->background_colors);
    }
}
?>
cu

Carsten
Kendash
Posts: 12
Joined: Fri Aug 01, 2014 10:31 pm

Re: DIY Stand Alone Barcode Punch Station

Post by Kendash »

I have followed this and got it to work. However after the initial clock in, when a user clocks out it is clocking them out and then immediately clocking them back in. Any ideas on what could be causing this?
Kendash
Posts: 12
Joined: Fri Aug 01, 2014 10:31 pm

Re: DIY Stand Alone Barcode Punch Station

Post by Kendash »

Does this still work with most current version? I have tried to get it working ... and it does except it will not switch users (I found a workaround for that) and it double punches on clock out. It is immediately clocking back in after clock out. (I have not found a fix for this yet)
systemadmin
Posts: 7
Joined: Tue Jun 10, 2014 12:49 pm

Re: DIY Stand Alone Barcode Punch Station

Post by systemadmin »

Hi Kendash,


sorry, I didn't look into this forum for some time.

What version of timetrex you are actually running now? Did you try to punch IN/OUT via a connected keyboard or with barcode / RFID etc?


cu


Carsten
Kendash
Posts: 12
Joined: Fri Aug 01, 2014 10:31 pm

Re: DIY Stand Alone Barcode Punch Station

Post by Kendash »

I would love to get it to switch users but I never could get it to do that. I have since found a work around for my needs but If I was to ever figure out how to get the userswitch working that would simplify my process.

I am using the latest 7.4.4
mgreen
Posts: 1
Joined: Fri Oct 31, 2014 7:59 am

Re: DIY Stand Alone Barcode Punch Station

Post by mgreen »

So did this double punch issue get resolved? This project is highly relevant to my interests. I read your Samba thread as well Kendash, looks like you've put quite a bit of effort into this.
Locked