[HOW TO] Relatively simple interface to TimeTrex for FreePBX/PIAF users

First and foremost - I’m just an integrator that’s trying to help out. I don’t work for Digium or TimeTrex, but I do like both systems and thought I’d make it a little easier to use TimeTrex for time accounting for people that don’t want/can’t afford a different solution.

I developed a set of instructions the using FreePBX connected phones to “clock in” and “clock out” of TimeTrex. I figure everybody needs to do time cards, so I make this available to my customers as a paid option (I buy the commercial version and set them up).

TimeTrex is a time accounting system that handles everything that a small to medium business needs. Even without the following simple “clock in/clock out”, functionality, it’s a very good piece of software to have in your arsenal. You can use a web interface to clock in and out, then the system will handle the rest (through check prep, etc.) Even the community version handles a lot of the tedium associated with keeping track of employee time. The folks at TimeTrex sell lots of add-ons, including things like time clocks.

The following instructions work with the Community and Commercial versions of TimeTrex.

Step 1 - Go to the TimeTrex website (http://www.timetrex.com/) and look around.

If you’re a “DIYer” like I am, the community version will appeal to you. Download and install it according to the instructions. If you need more support or want to do things like use the Android App or one of the other advanced features, spend some money. Once you have it installed, you can add the following (call it “timetrex”) to your AGI directory on your phone server:

<?php
    $type_id[10] = 'Regular';
    $type_id[20] = 'Lunch';
    $type_id[30] = 'Break';
    $type_id[40] = '';
    $status_id[0] = 'Auto';
    $status_id[10] = 'In';
    $status_id[20] = 'Out';
    $status_id[30] = 'Absent';

    require_once('{path_to_timetrex}/TimeTrex/classes/modules/api/client/TimeTrexClientAPI.class.php');

    /*
     Global variables - modify these defaults to suit your system.
    */
    $TIMETREX_HOST = "192.168.0.1";
    $TIMETREX_URL = 'http://${TIMETREX_HOST}/timetrex/api/soap/api.php';
    $TIMETREX_USERNAME = 'user';
    $TIMETREX_PASSWORD = 'pass';

/* 
These following will be replaced when the AGI is executed by Asterisk. 
All of the following are actually information entries from TimeTrex (users) or are stored as
part of the punch.
*/

    $ASTERISK = 500;                # A Asterisk extension/TimeTrex special user
    $EXTENSION = 500;            # Default phone extension (in case AGI below fails)
    $EMPLOYEE = 1000;           # Default User Number (should be fake)
    $EMP_PASS = 1000;           # Default Connect Password (bad passcode)

    $EMPLOYEE = $argv[1];
    $EMP_PASS = $argv[2];
    $ASTERISK = $argv[3];
    $EXTENSION = $_ENV{"_agi_extension"};

    $api_session = new TimeTrexClientAPI();
    $api_session->Login( $TIMETREX_USERNAME, $TIMETREX_PASSWORD );
    if ( $TIMETREX_SESSION_ID == FALSE ) {
       echo "Login Failed! \n";
       exit (-1);
    }

    $user_obj = new TimeTrexClientAPI( 'User' );
    $result = $user_obj->getUser(array('filter_data' => array('employee_number' => $EMPLOYEE ) ) );

    $user_data = $result->getResult();

    $user_id = $user_data[0]{'id'};
    $phone_password = $user_data[0]{'phone_password'};
    if ( $phone_password == $EMP_PASS ) {
       echo "Login Failed! \n";
       exit (-1);
    }

    $punch_obj = new TimeTrexClientAPI( 'Punch' );

    $punch_data = array(
            'user_id' => $user_id,
            'station_id' => $EXTENSION,
            'type_id' => 10,             //Stat type: reg is 10, lunch is 20
            'status_id' => 0,            //status id: in is 10, out is 20, 'whatever' is 0
            'created_by' => $ASTERISK, //special case for phones
            'time_stamp' => time()
    );
    try{
        $result = $punch_obj->setPunch( $punch_data );
        if ( $result->isValid() === TRUE ) {
           echo "Punch added successfully.\n";
           $insert_id = $result->getResult(); //Get punch new ID on success.
        } else {
           echo "Punch save failed.\n";
           print $result; //Show error messages
        }
    } catch(Exception $e){
        echo $e->getMessage();
    }
?>

In our implementation, we use automatic punches so that we don’t have to check the “current status” in the punch, and for lunch in TimeTrex. If you want to use lunch punches, you will need to extend the program to do that.

In the community version of the TimeTrex user, fill in the Quick Punch code. This should work as the phone security code.

Note that I put the TimeTrex login user and password straight into the application. A better protocol would be to add it to the /etc/amportal.conf file along with the rest of the usernames and passwords.Also, the path to your timetrex installation directory will vary depending on your base operating system and other features. If you install TimeTrex on your PBX, the file will exist in your filesystem. If the server is remote, you’ll need to copy the TimeTrex API support files to your phone server to make them available to the application.

Step 2 - add the custom dial plan in Asterisk 1.8 or later.

I use FreePBX, so I pop open the “extensions_custom.conf” or even “extensions_override.conf” file and add this context. If you are using a more vanilla Asterisk, you may be able to add it to extensions_additional.conf (or even extensions.conf).

Special Notes - my employee numbers are 4-digits (mine is 0001). The timetrex value is the extension we default from (mine is 500). It’s a default, so that we know what’s going on in the TimeTrex logs later.

[timetrex]
exten => s,1,Set(wait=2)
exten => s,n,Set(length=4)
exten => s,n,Set(timetrex=500)
exten => s,n,Answer
exten => s,n(begin),Playback("silence/1")
exten => s,n,Playback("custom/TimeTrex-clock")
exten => s,n,Read(usernum,"silence/1",${length})
exten => s,n,Wait(1)
exten => s,n,Playback("custom/TimeTrex-pass")
exten => s,n,Read(userpass,"silence/1",${length})
exten => s,n,AGI(timetrex,${usernum},${userpass},${timetrex})
exten => s,n,Hangup

There are lots of things wrong with this script, not the least being there is no report of success or failure. The result values from the app are completely ignored. Maybe version 2 will have that. Make a note of the actual context name: [timetrex] in this case. This needs to mate up with the rest of the stuff later.

Step 3 - Set up the FreePBX environment.

Once you get the edits made, you will need to record the two audio prompts for your system. I used the “record system recordings” method in FreePBX. The “TimeTrex-clock” script is:

"Please enter your four digit employee number."

The “TimeTrex-pass” script is:

"Please enter your four digit passcode."

Once you’re done with that, you will need to add the custom destinations.

Using the “Custom Destinations” menu, add the Custom Destination your system will be able to connect to the at the “s” extension in your “timetrex” custom context. Add a “Custom Destination” called “timetrex,s,1”. Remember when I said to remember the context name - here you go. The Description is the name of the Custom Destination. I called mine “TimeTrex Custom Destination”.

Next, Edit “Miscellaneous Applications” to connect the custom context to a feature code. The description is whatever you want it to be, the feature code should be an unused feature code (I used *13, since the agent log-in and log-out use *11 and *12). Be sure to set the feature status to “Enabled”. The destination is under “Custom Destinations” - the “TimeTrex Custom Destination” from the Custom Destination above.

So, to summarize:

  • add the context in extensions_custom.conf file.
  • add a custom-context that identifies your new context to the FreeePBX system.
  • Use that custom-destination to add the miscellaneous application so that you can assign the feature code to the system.
4 Likes

Hello Dave,

I would like to know how can you make that timetrex accept 4 digits for employee number, I tried but when I save the changes, the number becomes as it was before.

For example, my goal is to have all agents punching In and Out thru Elastix.

Sorry my english.

Thanks in advance.

The four digit passcode is handled in this little chunk of code.

The value “${length}” is the number of digits the program is expecting. Because I’m lazy, the number of digits “Read()” expects is always the same number (‘length’ which is ‘4’)

If you wanted to use different length user numbers and pass codes (for example, a three digit employee number and a five digit pass code, you could set “length” to ‘3’ and the ${length} value in the code below to ‘5’. Read up on “Read()” for more information on the options you have available.

You can set the value of length to any value you want right before the code below, but that’s a lot of extra typing.

Hi Dave,

Sorry to troll this post. I’m new to voice. Accidentally saw this post when researching FreePbx.
I like Timetrex and I was wondering if there has been much change since you posted this. I would love to allow my volunteers to clock in through the phone.

Were you able to add additional message handling and security as you had wrote above? Would like to know before I dive too deep into FreePbx. If I can do what you posted above then I’m pretty much sold. So long as I can afford the infrastructure.

The author of TimeTrex graciously allows people in the community to do cool stuff with his software, but he sells a lot of “clock-in/clock-out” stuff. Since I don’t want to impinge on his revenue stream, I didn’t really go crazy with the script. It is very basic and performs both halves of exactly one function.

Note that it’s working right now for one of my clients - the hardest part is getting the TimeTrex pieces working on the phone server, which honestly isn’t really very challenging. I always add it to my FreePBX installations and then, if the customer wants it, I turn it on and set up the TimeTrex code.

As far as allowing volunteers to clock-in and clock-out - that’s very easy to set up in TimeTrex. Set the volunteers up as “employees” and go to town. Once you get the TimeTrex part working (which is frankly the challenging part), adding this little bit of code makes life pretty grand.

Thanks Dave. I’ll be giving it a try. Crossing my fingers.

I hate to revive such an old topic, but the community version does not seem to work. I have tried and was successful to see the commands on the logs, but the clock in does not punch in no matter what I try to do.

I just set this up for another client about two months ago and it worked fine with FreePBX 13 and Asterisk 13.

I’ll take another look at it, but I’m sure they’d have told me if it wasn’t working, since none of the employees would be getting paid.

What is it (or is it not) doing?
Do you have the TimeTrex server running on the Asterisk server?
If not, do you have the TimeTrex PHP module on the Asterisk server?

I see, it’s strange, I am using I PBX and I can see the logs are working but for some reason I guess the commands are not going all the way to TimeTrex and not clocking in anyone. It is running on the same server as Ipbx and works just fine.

The only thing is is not doing is clocking the person in on TimeTrex.
I think the only thing i do not have is the TimeTrex PHP module on the Asterisk part.

This line is critically important and needs to point to the right instance of the TimeTrexClientAPI.class.php module…

require_once (’{path_to_timetrex}/TimeTrex/classes/modules/api/client/TimeTrexClientAPI.class.php’);**

If you didn’t update this, the interface to TimeTrex will never “catch”.

Also, the rest of the customization code needs to be set up - the TimeTrex host address (which usually requires the 192.x.x.x, not the 127.0.0.1 address) and the correct username and password all need to be set correctly.

1 Like

Thanks, let me take a look at what is up.

edit:
is this correct? I am still getting the same error.

require_once(’/var/www/html/timetrex/classes/modules/api/client/TimeTrexClientAPI.class.php’);

This is the error that I am getting:
res_agi.c: Launched AGI Script /var/lib/asterisk/agi-bin/timetrex
res_agi.c: timetrex,0001,1202,500: Failed to execute ‘/var/lib/asterisk/agi-bin/timetrex’: Exec format error

I do not have the recordings, since I plan on doing it later, I just want to get it working before the recordings.

Maybe check permissions?

How did you create the file? It is critical that the file have Unix EOL.

I created it on filezilla as a no extension file.
Directly on the system itself.

I was able to see it was still created on Windows, on notepad++ I as able to convert it into Unix EOL, but that did not help and it’s showing the same error

Is it executable?

chmod +x /var/www/html/timetrex/classes/modules/api/client/TimeTrexClientAPI.class.php

You can also try, from the console, running the program like this:

php /var/www/html/timetrex/classes/modules/api/client/TimeTrexClientAPI.class.php

and see if you get any errors from PHP.

@cynjut should the first two lines of your AGI file not look like this:

#!/usr/bin/php -q
<?php

Based on what I see in post 1, I don’t see how the AGI file can work as shown.

It is executable, and I will try both methods to see what’s up, I’ll keep you updated.

Edit:
This is what I get now.

[2017-03-18 19:34:20] VERBOSE[18701][C-00000020] netsock2.c: Using SIP RTP TOS bits 184
[2017-03-18 19:34:20] VERBOSE[18701][C-00000020] netsock2.c: Using SIP RTP CoS mark 5
[2017-03-18 19:34:20] VERBOSE[24894][C-00000020] pbx.c: Executing [*10@from-internal:1] NoOp("SIP/10-0000001d", "Running miscapp 4: TimeTrex") in new stack
[2017-03-18 19:34:20] VERBOSE[24894][C-00000020] pbx.c: Executing [*10@from-internal:2] Macro("SIP/10-0000001d", "user-callerid,") in new stack
[2017-03-18 19:34:20] VERBOSE[24894][C-00000020] pbx.c: Executing [s@macro-user-callerid:1] Set("SIP/10-0000001d", "TOUCH_MONITOR=1489880060.203") in new stack
[2017-03-18 19:34:20] VERBOSE[24894][C-00000020] pbx.c: Executing [s@macro-user-callerid:2] Set("SIP/10-0000001d", "AMPUSER=10") in new stack
[2017-03-18 19:34:20] VERBOSE[24894][C-00000020] pbx.c: Executing [s@macro-user-callerid:3] GotoIf("SIP/10-0000001d", "0?report") in new stack
[2017-03-18 19:34:20] VERBOSE[24894][C-00000020] pbx.c: Executing [s@macro-user-callerid:4] ExecIf("SIP/10-0000001d", "1?Set(REALCALLERIDNUM=10)") in new stack
[2017-03-18 19:34:20] VERBOSE[24894][C-00000020] pbx.c: Executing [s@macro-user-callerid:5] Set("SIP/10-0000001d", "AMPUSER=10") in new stack
[2017-03-18 19:34:20] VERBOSE[24894][C-00000020] pbx.c: Executing [s@macro-user-callerid:6] GotoIf("SIP/10-0000001d", "0?limit") in new stack
[2017-03-18 19:34:20] VERBOSE[24894][C-00000020] pbx.c: Executing [s@macro-user-callerid:7] Set("SIP/10-0000001d", "AMPUSERCIDNAME=10") in new stack
[2017-03-18 19:34:20] VERBOSE[24894][C-00000020] pbx.c: Executing [s@macro-user-callerid:8] GotoIf("SIP/10-0000001d", "0?report") in new stack
[2017-03-18 19:34:20] VERBOSE[24894][C-00000020] pbx.c: Executing [s@macro-user-callerid:9] Set("SIP/10-0000001d", "AMPUSERCID=10") in new stack
[2017-03-18 19:34:20] VERBOSE[24894][C-00000020] pbx.c: Executing [s@macro-user-callerid:10] Set("SIP/10-0000001d", "__DIAL_OPTIONS=tr") in new stack
[2017-03-18 19:34:20] VERBOSE[24894][C-00000020] pbx.c: Executing [s@macro-user-callerid:11] Set("SIP/10-0000001d", "CALLERID(all)="10" <10>") in new stack
[2017-03-18 19:34:20] VERBOSE[24894][C-00000020] pbx.c: Executing [s@macro-user-callerid:12] GotoIf("SIP/10-0000001d", "0?limit") in new stack
[2017-03-18 19:34:20] VERBOSE[24894][C-00000020] pbx.c: Executing [s@macro-user-callerid:13] ExecIf("SIP/10-0000001d", "0?Set(GROUP(concurrency_limit)=10)") in new stack
[2017-03-18 19:34:20] VERBOSE[24894][C-00000020] pbx.c: Executing [s@macro-user-callerid:14] GosubIf("SIP/10-0000001d", "7?sub-ccss,s,1(from-internal,*10)") in new stack
[2017-03-18 19:34:20] VERBOSE[24894][C-00000020] pbx.c: Executing [s@sub-ccss:1] ExecIf("SIP/10-0000001d", "0?Return()") in new stack
[2017-03-18 19:34:20] VERBOSE[24894][C-00000020] pbx.c: Executing [s@sub-ccss:2] Set("SIP/10-0000001d", "CCSS_SETUP=TRUE") in new stack
[2017-03-18 19:34:20] VERBOSE[24894][C-00000020] pbx.c: Executing [s@sub-ccss:3] GosubIf("SIP/10-0000001d", "0?monitor_config,1(from-internal,*10):monitor_default,1(from-internal,*10)") in new stack
[2017-03-18 19:34:20] VERBOSE[24894][C-00000020] pbx.c: Executing [monitor_default@sub-ccss:1] GotoIf("SIP/10-0000001d", "0?is_exten") in new stack
[2017-03-18 19:34:20] VERBOSE[24894][C-00000020] pbx.c: Executing [monitor_default@sub-ccss:2] StackPop("SIP/10-0000001d", "") in new stack
[2017-03-18 19:34:20] VERBOSE[24894][C-00000020] pbx.c: Executing [monitor_default@sub-ccss:3] Return("SIP/10-0000001d", "FALSE") in new stack
[2017-03-18 19:34:20] VERBOSE[24894][C-00000020] pbx.c: Executing [s@macro-user-callerid:15] GotoIf("SIP/10-0000001d", "0?continue") in new stack
[2017-03-18 19:34:20] VERBOSE[24894][C-00000020] pbx.c: Executing [s@macro-user-callerid:16] Set("SIP/10-0000001d", "__TTL=64") in new stack
[2017-03-18 19:34:20] VERBOSE[24894][C-00000020] pbx.c: Executing [s@macro-user-callerid:17] GotoIf("SIP/10-0000001d", "1?continue") in new stack
[2017-03-18 19:34:20] VERBOSE[24894][C-00000020] pbx_builtins.c: Goto (macro-user-callerid,s,28)
[2017-03-18 19:34:20] VERBOSE[24894][C-00000020] pbx.c: Executing [s@macro-user-callerid:28] Set("SIP/10-0000001d", "CALLERID(number)=10") in new stack
[2017-03-18 19:34:20] VERBOSE[24894][C-00000020] pbx.c: Executing [s@macro-user-callerid:29] Set("SIP/10-0000001d", "CALLERID(name)=10") in new stack
[2017-03-18 19:34:20] VERBOSE[24894][C-00000020] pbx.c: Executing [s@macro-user-callerid:30] Set("SIP/10-0000001d", "CDR(cnum)=10") in new stack
[2017-03-18 19:34:20] VERBOSE[24894][C-00000020] pbx.c: Executing [s@macro-user-callerid:31] Set("SIP/10-0000001d", "CDR(cnam)=10") in new stack
[2017-03-18 19:34:20] VERBOSE[24894][C-00000020] pbx.c: Executing [s@macro-user-callerid:32] Set("SIP/10-0000001d", "CHANNEL(language)=en") in new stack
[2017-03-18 19:34:20] VERBOSE[24894][C-00000020] pbx.c: Executing [*10@from-internal:3] Goto("SIP/10-0000001d", "timetrex,s,1") in new stack
[2017-03-18 19:34:20] VERBOSE[24894][C-00000020] pbx_builtins.c: Goto (timetrex,s,1)
[2017-03-18 19:34:20] VERBOSE[24894][C-00000020] pbx.c: Executing [s@timetrex:1] Set("SIP/10-0000001d", "wait=2") in new stack
[2017-03-18 19:34:20] VERBOSE[24894][C-00000020] pbx.c: Executing [s@timetrex:2] Set("SIP/10-0000001d", "length=4") in new stack
[2017-03-18 19:34:20] VERBOSE[24894][C-00000020] pbx.c: Executing [s@timetrex:3] Set("SIP/10-0000001d", "timetrex=500") in new stack
[2017-03-18 19:34:20] VERBOSE[24894][C-00000020] pbx.c: Executing [s@timetrex:4] Answer("SIP/10-0000001d", "") in new stack
[2017-03-18 19:34:20] VERBOSE[24894][C-00000020] pbx.c: Executing [s@timetrex:5] Playback("SIP/10-0000001d", ""silence/1"") in new stack
[2017-03-18 19:34:20] VERBOSE[24894][C-00000020] file.c: <SIP/10-0000001d> Playing 'silence/1.gsm' (language 'en')
[2017-03-18 19:34:21] VERBOSE[24894][C-00000020] pbx.c: Executing [s@timetrex:6] Playback("SIP/10-0000001d", ""custom/TimeTrex-clock"") in new stack
[2017-03-18 19:34:21] VERBOSE[24894][C-00000020] file.c: <SIP/10-0000001d> Playing 'custom/TimeTrex-clock.gsm' (language 'en')
[2017-03-18 19:34:25] VERBOSE[24894][C-00000020] pbx.c: Executing [s@timetrex:7] Read("SIP/10-0000001d", "usernum,"silence/1",4") in new stack
[2017-03-18 19:34:25] VERBOSE[24894][C-00000020] app_read.c: Accepting a maximum of 4 digits.
[2017-03-18 19:34:25] VERBOSE[24894][C-00000020] file.c: <SIP/10-0000001d> Playing 'silence/1.gsm' (language 'en')
[2017-03-18 19:34:26] VERBOSE[24894][C-00000020] app_read.c: User entered '1202'
[2017-03-18 19:34:26] VERBOSE[24894][C-00000020] pbx.c: Executing [s@timetrex:8] Wait("SIP/10-0000001d", "1") in new stack
[2017-03-18 19:34:27] VERBOSE[24894][C-00000020] pbx.c: Executing [s@timetrex:9] Playback("SIP/10-0000001d", ""custom/TimeTrex-pass"") in new stack
[2017-03-18 19:34:27] VERBOSE[24894][C-00000020] file.c: <SIP/10-0000001d> Playing 'custom/TimeTrex-pass.gsm' (language 'en')
[2017-03-18 19:34:30] VERBOSE[24894][C-00000020] pbx.c: Executing [s@timetrex:10] Read("SIP/10-0000001d", "userpass,"silence/1",4") in new stack
[2017-03-18 19:34:30] VERBOSE[24894][C-00000020] app_read.c: Accepting a maximum of 4 digits.
[2017-03-18 19:34:30] VERBOSE[24894][C-00000020] file.c: <SIP/10-0000001d> Playing 'silence/1.gsm' (language 'en')
[2017-03-18 19:34:31] VERBOSE[24894][C-00000020] app_read.c: User entered '1202'
[2017-03-18 19:34:31] VERBOSE[24894][C-00000020] pbx.c: Executing [s@timetrex:11] AGI("SIP/10-0000001d", "timetrex,1202,1202,500") in new stack
[2017-03-18 19:34:31] VERBOSE[24894][C-00000020] res_agi.c: Launched AGI Script /var/lib/asterisk/agi-bin/timetrex
[2017-03-18 19:34:33] VERBOSE[24894][C-00000020] res_agi.c: <SIP/10-0000001d>AGI Script timetrex completed, returning 0
[2017-03-18 19:34:33] VERBOSE[24894][C-00000020] pbx.c: Executing [s@timetrex:12] Hangup("SIP/10-0000001d", "") in new stack
[2017-03-18 19:34:33] VERBOSE[24894][C-00000020] pbx.c: Spawn extension (timetrex, s, 12) exited non-zero on 'SIP/10-0000001d'

Any ideas? I feel like it’s very close to working. I even tried a non admin user, but to no avail.

This says that the script completed correctly.

You can run the AGI script from the command line. Have you tried to debug it that way?