Go to Post The key is to get the WHOLE TEAM to understand that you are designing and building a robot, which is a complete system - not just a collection of mechanical assemblies stuck together. - SteveGarward [more]
Home
Go Back   Chief Delphi > Technical > Programming
CD-Media   CD-Spy  
portal register members calendar search Today's Posts Mark Forums Read FAQ rules

 
Closed Thread
Thread Tools Rating: Thread Rating: 3 votes, 5.00 average. Display Modes
  #1   Spotlight this post!  
Unread 02-20-2015, 11:11 AM
Ether's Avatar
Ether Ether is offline
systems engineer (retired)
no team
 
Join Date: Nov 2009
Rookie Year: 1969
Location: US
Posts: 7,997
Ether has a reputation beyond reputeEther has a reputation beyond reputeEther has a reputation beyond reputeEther has a reputation beyond reputeEther has a reputation beyond reputeEther has a reputation beyond reputeEther has a reputation beyond reputeEther has a reputation beyond reputeEther has a reputation beyond reputeEther has a reputation beyond reputeEther has a reputation beyond repute
Motion Profiling


"Thread created automatically to discuss a document in CD-Media" does not allow attachments (I don't know the purpose for that limitation. Brandon: would it be beneficial to change that setting?)

Quote:
Originally Posted by JesseK View Post
I see that this is desired behavior, but how do we poke the robot so it does what we want?
Step1:
Determine how far you want the robot to move, how fast you want to allow it to accelerate at startup (and decelerate as it approaches the destination), and the max speed limit during that motion.

Step2:
Run the calculator to get your distance vs time equations.

Step3:
Each pass through TeleOp, chose the appropriate x(t) equation based on elapsed time "t" since start, and use that equation to compute a new position setpoint.

Step4:
Use the setpoint from Step3 as the position command to a closed-loop controller controlling whatever it is you want to control (e.g. wheels of a drivetrain, position of an elevator, etc)

Quote:
Seems like we would tune 'amax' until the robot drives the desired distance in the desired time.
You can always reduce amax and/or vmax if the computed time-to-destination is faster than required. And vice-versa.

For a given vmax, there will be an amax below which you can't get a trapezoidal motion profile (it will be triangular instead). What I've presented here presently doesn't handle that situation. I just uploaded a Maxima script and plot for triangular motion profile.

PS: The purpose of this thread is to raise awareness of and provide a focal point for conversation about the topic of motion profiling. I'm sure there are many motion profiling papers and apps out there in webland. If you have a favorite please feel free to posts links in this thread.



Last edited by Ether : 02-20-2015 at 12:17 PM.
  #2   Spotlight this post!  
Unread 02-20-2015, 12:29 PM
Ether's Avatar
Ether Ether is offline
systems engineer (retired)
no team
 
Join Date: Nov 2009
Rookie Year: 1969
Location: US
Posts: 7,997
Ether has a reputation beyond reputeEther has a reputation beyond reputeEther has a reputation beyond reputeEther has a reputation beyond reputeEther has a reputation beyond reputeEther has a reputation beyond reputeEther has a reputation beyond reputeEther has a reputation beyond reputeEther has a reputation beyond reputeEther has a reputation beyond reputeEther has a reputation beyond repute
Re: Motion Profiling

Quote:
Originally Posted by Ether View Post
For a given vmax, there will be an amax below which you can't get a trapezoidal motion profile (it will be triangular instead). What I've presented here presently doesn't handle that situation.
I just uploaded a Maxima script and plot for triangular motion profile.

User enters amax and distance-to-destination. There is no speed limit. amax can be varied to get desired time-to-destination.


  #3   Spotlight this post!  
Unread 02-20-2015, 01:10 PM
Ether's Avatar
Ether Ether is offline
systems engineer (retired)
no team
 
Join Date: Nov 2009
Rookie Year: 1969
Location: US
Posts: 7,997
Ether has a reputation beyond reputeEther has a reputation beyond reputeEther has a reputation beyond reputeEther has a reputation beyond reputeEther has a reputation beyond reputeEther has a reputation beyond reputeEther has a reputation beyond reputeEther has a reputation beyond reputeEther has a reputation beyond reputeEther has a reputation beyond reputeEther has a reputation beyond repute
Re: Motion Profiling

Paul Copioli wrote:
I am curious as to why you are proposing this equation based method when the filter based method posted from post #18 in this thread:http://www.chiefdelphi.com/forums/sh...motion+profile

The filter method is so much more real time friendly and flexible as you can determine the motion acceleration type just by manipulating the filter lengths.
Hi Paul,

Thanks for that link. Since that thread is closed, I invite interested parties to respond here if they wish.

Please note that I am not advocating any particular method. I'd simply like to discuss the pros and cons.


  #4   Spotlight this post!  
Unread 02-20-2015, 01:47 PM
Jared's Avatar
Jared Jared is offline
Registered User
no team
Team Role: Programmer
 
Join Date: Aug 2013
Rookie Year: 2012
Location: Connecticut
Posts: 602
Jared has a reputation beyond reputeJared has a reputation beyond reputeJared has a reputation beyond reputeJared has a reputation beyond reputeJared has a reputation beyond reputeJared has a reputation beyond reputeJared has a reputation beyond reputeJared has a reputation beyond reputeJared has a reputation beyond reputeJared has a reputation beyond reputeJared has a reputation beyond repute
Re: Motion Profiling

Quote:
Originally Posted by Ether View Post
[i]

Step1:
Determine how far you want the robot to move, how fast you want to allow it to accelerate at startup (and decelerate as it approaches the destination), and the max speed limit during that motion.

Step2:
Run the calculator to get your distance vs time equations.

Step3:
Each pass through TeleOp, chose the appropriate x(t) equation based on elapsed time "t" since start, and use that equation to compute a new position setpoint.

Step4:
Use the setpoint from Step3 as the position command to a closed-loop controller controlling whatever it is you want to control (e.g. wheels of a drivetrain, position of an elevator, etc)




We've done something very similar to what you have described here for motion profiling, but we've used a different method to follow the profile which works very well.

We started with the same profiling setup that you use, but we found that the wheels on the robot would slip during faster acceleration, so we switched to a jerk limited control - using trapezoids for acceleration instead of velocity. It just adds another degree of smoothness which helped for fast moves.

We are using something that was described to me by a member of team 254 at St. Louis last year.

We start by coming up with a big table of position, velocity, and acceleration for each .05 seconds that meet the desired position, max velocity, max acceleration, and max jerk goals. We have a thread that runs every .05 seconds and reads the next position, acceleration, and velocity values from the list. It takes the roboRIO about 100ms to generate all possible profiles that we would want to run and store them in memory.

The output for our motors is
Code:
output = k_a * acc + k_v * vel + k_p * (desired-actual)
This uses proportional feedback control like P in PID to correct for any misalignment, but most of the output comes from the velocity and acceleration feed forward. It's accurate to within 2" or so with no feedback at all using only well tuned feed forward constants.

Our method to calculate the motion profile isn't super efficient, super fast, and uses lots of memory, but it was written in a really short amount of time. We start by generating the acceleration ramp up part until we reach maximum acceleration. Next, we continue at maximum acceleration until we reach half of our maximum velocity. At that point, we mirror the acceleration curve so that the acceleration ramps down as we reach our maximum speed. The last step is to continue at maximum velocity until we reach half our distance setpoint, and then we mirror everything.

You can check out the code for making profiles here
https://github.com/dicarlo236/2015-R...Generator.java

Our follower is here
https://github.com/dicarlo236/2015-R...eFollower.java

You can see it running in this video
https://www.youtube.com/watch?v=sGQk0-u0HMI
  #5   Spotlight this post!  
Unread 02-20-2015, 02:09 PM
JesseK's Avatar
JesseK JesseK is offline
Expert Flybot Crasher
FRC #1885 (ILITE)
Team Role: Mentor
 
Join Date: Mar 2007
Rookie Year: 2005
Location: Reston, VA
Posts: 3,608
JesseK has a reputation beyond reputeJesseK has a reputation beyond reputeJesseK has a reputation beyond reputeJesseK has a reputation beyond reputeJesseK has a reputation beyond reputeJesseK has a reputation beyond reputeJesseK has a reputation beyond reputeJesseK has a reputation beyond reputeJesseK has a reputation beyond reputeJesseK has a reputation beyond reputeJesseK has a reputation beyond repute
Re: Motion Profiling

Jared, that's really interesting. We have a desire to do a trapezoidal profile on our tote lift in order to prevent sudden deceleration from jerking a tote off of the hooks. We're doing something primitive right now, but I wonder if our programming lead thought about a lookup table for it.
  #6   Spotlight this post!  
Unread 02-20-2015, 02:16 PM
Jared Russell's Avatar
Jared Russell Jared Russell is offline
Taking a year (mostly) off
FRC #0254 (The Cheesy Poofs), FRC #0341 (Miss Daisy)
Team Role: Engineer
 
Join Date: Nov 2002
Rookie Year: 2001
Location: San Francisco, CA
Posts: 3,069
Jared Russell has a reputation beyond reputeJared Russell has a reputation beyond reputeJared Russell has a reputation beyond reputeJared Russell has a reputation beyond reputeJared Russell has a reputation beyond reputeJared Russell has a reputation beyond reputeJared Russell has a reputation beyond reputeJared Russell has a reputation beyond reputeJared Russell has a reputation beyond reputeJared Russell has a reputation beyond reputeJared Russell has a reputation beyond repute
Re: Motion Profiling

Quote:
Originally Posted by Ether View Post
Paul Copioli wrote:
I am curious as to why you are proposing this equation based method when the filter based method posted from post #18 in this thread:http://www.chiefdelphi.com/forums/sh...motion+profile

The filter method is so much more real time friendly and flexible as you can determine the motion acceleration type just by manipulating the filter lengths.
Hi Paul,

Thanks for that link. Since that thread is closed, I invite interested parties to respond here if they wish.

Please note that I am not advocating any particular method. I'd simply like to discuss the pros and cons.


It's worth pointing out that the equation-based method is also real-time safe (deterministic runtime and does not allocate), although a microcontroller without an FPU would not be able to run it as quickly as a cascade of Boxcar filters.

The main advantage of polynomial-based motion profiling techniques is that you have a bit more flexibility in the constraints you can obey. For example, you could easily accommodate asymmetric forwards and backwards velocity/acceleration limits. It is also easy to change your constraints on the fly - as often as each control cycle. Doing this (for higher order constraints...velocity is easy) with FIR filters requires resizing the number of filter taps and pre-loading the new filter with the right initial conditions.

One other reason to favor the polynomial method is that relatively few high school students have had any exposure to linear filtering, but if they have taken a physics class they will know (and be able to apply) basic kinematics equations.

In general, though, either method works fine, and the filtering approach will be faster if you can get away with no asymmetries and constant constraints on higher order derivatives.

Last edited by Jared Russell : 02-20-2015 at 02:21 PM.
  #7   Spotlight this post!  
Unread 02-20-2015, 02:19 PM
Jared Russell's Avatar
Jared Russell Jared Russell is offline
Taking a year (mostly) off
FRC #0254 (The Cheesy Poofs), FRC #0341 (Miss Daisy)
Team Role: Engineer
 
Join Date: Nov 2002
Rookie Year: 2001
Location: San Francisco, CA
Posts: 3,069
Jared Russell has a reputation beyond reputeJared Russell has a reputation beyond reputeJared Russell has a reputation beyond reputeJared Russell has a reputation beyond reputeJared Russell has a reputation beyond reputeJared Russell has a reputation beyond reputeJared Russell has a reputation beyond reputeJared Russell has a reputation beyond reputeJared Russell has a reputation beyond reputeJared Russell has a reputation beyond reputeJared Russell has a reputation beyond repute
Re: Motion Profiling

Quote:
Originally Posted by JesseK View Post
Jared, that's really interesting. We have a desire to do a trapezoidal profile on our tote lift in order to prevent sudden deceleration from jerking a tote off of the hooks. We're doing something primitive right now, but I wonder if our programming lead thought about a lookup table for it.
The only reason we used a lookup table last season is that (a) we wanted to be able to visualize the x/y/theta quintic spline paths we were generating before deploying to the robot to make sure they looked reasonable, and (b) we needed to re-parameterize our splines by arc length, which we did with naive (and costly) numerical integration that took a little too long on the cRIO. If we had more time we would have made some quick optimizations and just done it on the fly.
  #8   Spotlight this post!  
Unread 02-20-2015, 03:09 PM
Jared's Avatar
Jared Jared is offline
Registered User
no team
Team Role: Programmer
 
Join Date: Aug 2013
Rookie Year: 2012
Location: Connecticut
Posts: 602
Jared has a reputation beyond reputeJared has a reputation beyond reputeJared has a reputation beyond reputeJared has a reputation beyond reputeJared has a reputation beyond reputeJared has a reputation beyond reputeJared has a reputation beyond reputeJared has a reputation beyond reputeJared has a reputation beyond reputeJared has a reputation beyond reputeJared has a reputation beyond repute
Re: Motion Profiling

The lookup table method was the best way for our team, as all of our autonomous action is based on distance triggers. We would generate the desired motion profile on the roboRIO, generate a .csv file from the data, copy it to the laptop, and open it in excel. This let us sit in front of graphs and data and decide when and where each action in autonomous would happen. It takes some amount of time for our elevator and container swatter to move, so knowing how fast the robot was going at each point was very helpful to determine where we should set the distance trigger for starting elevator or swatter movement.

The table method is also super easy to explain. The polynomial method would require separate polynomials for acceleration ramp up, constant positive acceleration, acceleration ramp down, constant velocity, deceleration ramp, constant deceleration, and final deceleration ramp. That seems harder to keep track of.

The table calculation method also gave me an easy way to work in motor equations. You get more torque, and therefore more acceleration, from your motor at lower speeds. If your profiling takes this into account, you can go much faster. Without taking this into account, you must limit your max acceleration to less than the robot can actually achieve so that as it accelerates, it can still achieve the same acceleration.

The solution to the dc motor equations for a pulley driven lift is

Code:
v = (r * w_n * (a_g * r * m - T_s)/T_s) * (1 - e^(-(t * T_s)/(r^2 * w_n * m))))
where v is velocity, r is pulley radius, w_n is no load speed (rad/sec), a_g is acceleration of gravity, m is mass carried, T_s is stall torque, and t is time.

If you have lots of time, you could integrate this again and find the position equation, and use that to determine your motion profile.

However, that's difficult, and you can come up with a simpler equation, v * a = k, where k is some constant specific to your setup. This means you either get a large acceleration or a large velocity, but not both at the same time. This equation is only valid when a is less than the acceleration achieved by stall torque and v is less than the no load speed. This is really easy to add into your calculations for the constant acceleration portion with
Code:
a = k/v_prev
If you have a large velocity, you can't accelerate as fast.

This would let you achieve the most aggressive and fastest profile possible for when speed really matters.

Last edited by Jared : 02-21-2015 at 02:03 PM. Reason: Missed some close parenthesis - Thanks Ether
  #9   Spotlight this post!  
Unread 02-20-2015, 05:12 PM
Ether's Avatar
Ether Ether is offline
systems engineer (retired)
no team
 
Join Date: Nov 2009
Rookie Year: 1969
Location: US
Posts: 7,997
Ether has a reputation beyond reputeEther has a reputation beyond reputeEther has a reputation beyond reputeEther has a reputation beyond reputeEther has a reputation beyond reputeEther has a reputation beyond reputeEther has a reputation beyond reputeEther has a reputation beyond reputeEther has a reputation beyond reputeEther has a reputation beyond reputeEther has a reputation beyond repute
Re: Motion Profiling


Just for fun and educational purposes, I wrote a Maxima script for sinusoidal accel profile and added it to the paper.

The user enters max allowable acceleration and time-to-destination, and the script computes the equations for distance, speed, acceleration, and jerk versus time.. and plots them.


Attached is a sample graph generated with the script for max accel = 6.3 ft/sec2 and time-to-distance = 3 seconds.

The vehicle goes from x=0, v=0, a=0, and jerk=13.2 at t=0
to x=9, v=0, a=0, and jerk=13.2 at t=3 seconds.


Attached Thumbnails
Click image for larger version

Name:	Sinusoidal Accel Profile.png
Views:	112
Size:	45.0 KB
ID:	18442  
  #10   Spotlight this post!  
Unread 02-20-2015, 08:51 PM
Ether's Avatar
Ether Ether is offline
systems engineer (retired)
no team
 
Join Date: Nov 2009
Rookie Year: 1969
Location: US
Posts: 7,997
Ether has a reputation beyond reputeEther has a reputation beyond reputeEther has a reputation beyond reputeEther has a reputation beyond reputeEther has a reputation beyond reputeEther has a reputation beyond reputeEther has a reputation beyond reputeEther has a reputation beyond reputeEther has a reputation beyond reputeEther has a reputation beyond reputeEther has a reputation beyond repute
Re: Motion Profiling

Quote:
Originally Posted by Ether View Post
Attached is a sample graph generated with the script for max accel = 6.3 ft/sec2 and time-to-distance = 3 seconds.

The vehicle goes from x=0, v=0, a=0, and jerk=13.2 at t=0
to x=9, v=0, a=0, and jerk=13.2 at t=3 seconds.
For comparison, here's a plot using trapezoidal accel, with the same max accel, jerk, and time-to-distance.

The vehicle goes from x=0, v=0, a=0, and jerk=13.2 at t=0
to x=9.7, v=0, a=0, and jerk=13.2 at t=3 seconds.
Attached Thumbnails
Click image for larger version

Name:	Trapezoidal Accel Profile.png
Views:	88
Size:	27.8 KB
ID:	18445  
  #11   Spotlight this post!  
Unread 02-21-2015, 01:43 PM
Ether's Avatar
Ether Ether is offline
systems engineer (retired)
no team
 
Join Date: Nov 2009
Rookie Year: 1969
Location: US
Posts: 7,997
Ether has a reputation beyond reputeEther has a reputation beyond reputeEther has a reputation beyond reputeEther has a reputation beyond reputeEther has a reputation beyond reputeEther has a reputation beyond reputeEther has a reputation beyond reputeEther has a reputation beyond reputeEther has a reputation beyond reputeEther has a reputation beyond reputeEther has a reputation beyond repute
Re: Motion Profiling

Quote:
Originally Posted by Jared View Post
However, that's difficult, and you can come up with a simpler equation, v * a = k, where k is some constant specific to your setup. This means you either get a large acceleration or a large velocity, but not both at the same time. This equation is only valid when a is less than the acceleration achieved by stall torque and v is less than the no load speed. This is really easy to add into your calculations for the constant acceleration portion with
Code:
a = k/v_prev
If you have a large velocity, you can't accelerate as fast.

This would let you achieve the most aggressive and fastest profile possible for when speed really matters.
If you can experimentally (or analytically) determine the maximum speed vmax and the maximum acceleration amax, you could try using a linear relation:

acceleration = amax*(1-speed/vmax)

... and that would be easy to numerically integrate in a spreadsheet.

When speed=vmax the acceleration is zero, and when speed=0 the acceleration is amax.

EDIT: added screenshot and XLS for user-selectable accel & speed limiting

Attached Thumbnails
Click image for larger version

Name:	accel limited profile.png
Views:	58
Size:	12.4 KB
ID:	18450  Click image for larger version

Name:	accel & speed limited profile.png
Views:	48
Size:	10.5 KB
ID:	18453  
Attached Files
File Type: xls accel limited profile.xls (34.0 KB, 15 views)
File Type: xls accel & speed limited profile.xls (34.5 KB, 29 views)

Last edited by Ether : 02-21-2015 at 03:37 PM. Reason: added more attachments
  #12   Spotlight this post!  
Unread 02-21-2015, 02:48 PM
Jared's Avatar
Jared Jared is offline
Registered User
no team
Team Role: Programmer
 
Join Date: Aug 2013
Rookie Year: 2012
Location: Connecticut
Posts: 602
Jared has a reputation beyond reputeJared has a reputation beyond reputeJared has a reputation beyond reputeJared has a reputation beyond reputeJared has a reputation beyond reputeJared has a reputation beyond reputeJared has a reputation beyond reputeJared has a reputation beyond reputeJared has a reputation beyond reputeJared has a reputation beyond reputeJared has a reputation beyond repute
Re: Motion Profiling

I've attached an excel spreadsheet with a few of the motion equations I've come up with so far. For inputs, they take motor constants. If you have a gear reduction before the motor constants, you need to come up with a new set of motor constants that match the characteristics of the output of the gearbox.

The first sheet is for some mechanism which is always fighting gravity in the same direction it is trying to move. The speed under load after a few seconds matches the loaded speed values calculated by the JVN design calculator.

The velocity is determined exactly, not by numerical integration. I don't know how to integrate that velocity function (though I'm sure it's possible), so I did what you suggested in your previous post.

The second sheet calculates velocity and position with explicit functions for each, but does not take into account gravity. The velocity function had a less complicated integral. This would work for a drive system, but not for an elevator. I like this better because the robot could simply plug in the time elapsed into the position function to determine its setpoint.

If you're interested in seeing my work, I could take a picture of it.


Our 2015 elevator has these values
stall torque 25 nm
mass 35 kg
free speed 32 rad/sec
sprocket radius .022 meters

It's way overpowered, and does achieve full speed in less than .25 seconds under load.
Attached Files
File Type: xlsx Elevator Motion Profile.xlsx (34.3 KB, 60 views)

Last edited by Jared : 02-21-2015 at 02:52 PM. Reason: Added example data
  #13   Spotlight this post!  
Unread 03-06-2015, 06:07 PM
Ether's Avatar
Ether Ether is offline
systems engineer (retired)
no team
 
Join Date: Nov 2009
Rookie Year: 1969
Location: US
Posts: 7,997
Ether has a reputation beyond reputeEther has a reputation beyond reputeEther has a reputation beyond reputeEther has a reputation beyond reputeEther has a reputation beyond reputeEther has a reputation beyond reputeEther has a reputation beyond reputeEther has a reputation beyond reputeEther has a reputation beyond reputeEther has a reputation beyond reputeEther has a reputation beyond repute
Re: Motion Profiling


I've encountered what appears to be a bug in the version of vBulletin® that CD is using (Version 3.6.4).

When I edited the text boxes for this whitepaper and added "center" tags (to center the text), it "locked" the text boxes so that I can no longer edit them: normally to edit a text box you just left-click on the box... but that no longer works (nothing happens).

Has anyone else encountered this? Better yet, does anyone know of a way to "unlock" the text boxes?

So, please ignore the hyperling in the text box; it is incorrect.

BTW: I just uploaded another short attachment providing details how to compute a sinusoidal accel motion profile from a given desired max accel and a given distance to target (starting from zero speed). Currently, it's the one at the bottom of the list.

http://www.chiefdelphi.com/media/papers/download/4313


  #14   Spotlight this post!  
Unread 03-11-2015, 05:23 PM
Ether's Avatar
Ether Ether is offline
systems engineer (retired)
no team
 
Join Date: Nov 2009
Rookie Year: 1969
Location: US
Posts: 7,997
Ether has a reputation beyond reputeEther has a reputation beyond reputeEther has a reputation beyond reputeEther has a reputation beyond reputeEther has a reputation beyond reputeEther has a reputation beyond reputeEther has a reputation beyond reputeEther has a reputation beyond reputeEther has a reputation beyond reputeEther has a reputation beyond reputeEther has a reputation beyond repute
Re: Motion Profiling


Oops.

Someone just found a nasty typo in attachment "how to calculate sinusoidal acceleration motion profile revA"

The function x(t) should be K2∙(t-K3∙sin(K1∙t))

I will make that correction and post revB

.
  #15   Spotlight this post!  
Unread 03-17-2015, 04:42 PM
Ether's Avatar
Ether Ether is offline
systems engineer (retired)
no team
 
Join Date: Nov 2009
Rookie Year: 1969
Location: US
Posts: 7,997
Ether has a reputation beyond reputeEther has a reputation beyond reputeEther has a reputation beyond reputeEther has a reputation beyond reputeEther has a reputation beyond reputeEther has a reputation beyond reputeEther has a reputation beyond reputeEther has a reputation beyond reputeEther has a reputation beyond reputeEther has a reputation beyond reputeEther has a reputation beyond repute
Re: Motion Profiling


Has anyone ever tried a controller like this* ?

How about with the velocity loop in the SRX and the position in the RIO ?

* the diagram should have included separate gains for the velocity and acceleration feedforward.


Last edited by Ether : 03-17-2015 at 07:18 PM.
Closed Thread


Thread Tools
Display Modes Rate This Thread
Rate This Thread:

Posting Rules
You may not post new threads
You may not post replies
You may not post attachments
You may not edit your posts

vB code is On
Smilies are On
[IMG] code is On
HTML code is Off
Forum Jump


All times are GMT -5. The time now is 05:47 AM.

The Chief Delphi Forums are sponsored by Innovation First International, Inc.


Powered by vBulletin® Version 3.6.4
Copyright ©2000 - 2017, Jelsoft Enterprises Ltd.
Copyright © Chief Delphi