--[[ Latest Lua version tested - 5.3.6 E-bike Calculator - Print estimated speed and range of an ebike. Written in 2017 by Luna Rose rg19@sdf.org To the extent possible under law, the author(s) have dedicated all copyright and related and neighboring rights to this software to the public domain worldwide. This software is distributed without any warranty. You should have received a copy of the CC0 Public Domain Dedication along with this software. If not, see --]] --[[ ############ User Data ############ Modify the variables in this section to match your ebike configuration. If the priority is loaded touring with lots of hills then a 52V system should be more than sufficient with the right gearing. If maximum speed is the main priority then 72V would be best, offering a 50% increas in speed over 48V. --]] -- Motor specifications local kv = 73 -- Motor RPM constant (kV, aka "RPM per Volt" local reduction = 21.9 -- Internal gear reduction local efficiency = .85 -- Motor efficiency in percent (from 0 to 1) local amp_max = 50 -- Maximum rated current capacity local watt_max = 3000 -- Maximum rated power capacity -- Battery and controller local volt_nom = 50.4 -- Nominal voltage of the battery local amp_hours = 20.0 -- Nominal amp-hour capacity of the battery local amp_out_max = 20 -- Maximum current draw for battery/controller -- Drivetrain local crank_length = 172.5 -- Length of pedal cranks in millimeters local radius = 347 -- Radius of tires in millimeters local chainring = 36 -- Tooth count of the chainring local cadence = 60 -- Average typical cadence of the rider local cassette = { -- Tooth counts for each gear on the cassette 11, 13, 16, 20, 24, 28, 32, 36, 40, 46, 52, } -- ############ End of User Data ############ -- Colorized output local red = "\027[31;1m" local green = "\027[32;1m" local yellow = "\027[33;1m" local white = "\027[37;1m" local plain = "\027[0m" local cyan = "\027[36;1m" -- System stats local power = volt_nom * amp_out_max local watt_hr = volt_nom * amp_hours local rpm = (kv * volt_nom / reduction) * .7 -- Magic number for loaded weight os.execute("clear") ; io.write(white, "Volts\tAmps\tWatts\tWh\tRPM\n") local disp_a, disp_w, disp_wh, disp_rpm disp_a = (amp_out_max >= amp_max) and red .. amp_out_max or green .. amp_out_max disp_w = (power >= watt_max) and red .. power or green .. power disp_wh = (power >= watt_hr) and red .. watt_hr or green .. watt_hr disp_rpm = (rpm >= 100) and red .. rpm or green .. rpm io.write(yellow, volt_nom, "\t", disp_a, "\t", disp_w, "\t", disp_wh, "\t", disp_rpm, '\n') -- Drivetrain display io.write(white, "\nRPM\tTeeth\tCranks\n", plain, cadence, '\t', chainring, '\t', crank_length) --[[ Gain ratios and speed estimates Estimated speed uses the calculated motor RPM on all gears with a loaded RPM reduction factor of 0.7 to account for overall weight, though actual RPM may be higher. Above 28MPH the real speed is likely much lower since this is not a physically based model. The required power is based on the square of the speed which for the most part seems to match real world values up to about 28MPH. At higher speeds the real values would be higher than calculated due to additional resistive forces. Since anything above 30 isn't ideal for a bicycle, no attempt will be made to refine the calculator. --]] local ratio = radius / crank_length local cc = 2 * 3.14 * crank_length / 1000 -- Circumfrence of pedal travel io.write("\n\n", cyan, "Gear\tGain\tMotor\tPedal\tWatts\n", plain) for i = 1, #cassette do local gain = ("%.4g"):format(ratio * chainring / cassette[i]) local motor = ("%.3g"):format(gain * cc * rpm * 60 / 1000 * .6214) local pedal = ("%.3g"):format(gain * cc * cadence * 60 / 1000 * .6214) local watts = ("%.4g"):format(motor * motor) io.write(cassette[i], "\t", gain, "\t", motor, "\t", pedal, "\t", watts, '\n') end --[[ Range estimates Real world range should be greater than what is calculated here since actual continuous power will vary with pedaling, hence these are minimums. --]] local range_400 = ("%.3g"):format(watt_hr / 20) local range_750 = ("%.3g"):format(watt_hr / 28) local range_1000 = ("%.3g"):format(watt_hr / 31.7) io.write(green, "\nRange @ 400W: ", plain, range_400, " miles\n", yellow, "Range @ 750W: ", plain, range_750, " miles\n", red, "Range @ 1000W: ", plain, range_1000, " miles\n")