Jump to content

Lua: Difference between revisions

From epicEFI Wiki
No edit summary
 
Line 43: Line 43:
Simple startup example:
Simple startup example:


<syntaxhighlight lang="lua">
<pre>
print('Hello Lua startup!')
print('Hello Lua startup!')


Line 49: Line 49:
     print('Hello onTick()')
     print('Hello onTick()')
end
end
</syntaxhighlight>
</pre>


=== Controlling the tick rate ===
=== Controlling the tick rate ===
Line 55: Line 55:
<code>setTickRate(hz)</code> sets how often epicEFI calls <code>onTick</code>. If your script does heavy work, it may run slower than the requested rate. The Lua VM runs at low priority relative to engine control, so you cannot starve critical ECU functions — set the rate to whatever your script needs. <code>onCanRx</code> runs at the same rate as <code>onTick</code>.
<code>setTickRate(hz)</code> sets how often epicEFI calls <code>onTick</code>. If your script does heavy work, it may run slower than the requested rate. The Lua VM runs at low priority relative to engine control, so you cannot starve critical ECU functions — set the rate to whatever your script needs. <code>onCanRx</code> runs at the same rate as <code>onTick</code>.


<syntaxhighlight lang="lua">
<pre>
n = 0
n = 0
setTickRate(5) -- 5 Hz
setTickRate(5) -- 5 Hz
Line 62: Line 62:
     n = n + 1
     n = n + 1
end
end
</syntaxhighlight>
</pre>


=== Editing scripts ===
=== Editing scripts ===
Line 248: Line 248:
CAN RX callback shape:
CAN RX callback shape:


<syntaxhighlight lang="lua">
<pre>
function onCanRx(bus, id, dlc, data)
function onCanRx(bus, id, dlc, data)
     -- handle frame
     -- handle frame
end
end
</syntaxhighlight>
</pre>


For high-throughput RX, see <code>enableCanRxWorkaround()</code> in the firmware examples.
For high-throughput RX, see <code>enableCanRxWorkaround()</code> in the firmware examples.
Line 266: Line 266:
<code>deltaTime</code> is measured automatically between successive <code>pid:get</code> calls.
<code>deltaTime</code> is measured automatically between successive <code>pid:get</code> calls.


<syntaxhighlight lang="lua">
<pre>
-- p, i, d, min, max
-- p, i, d, min, max
pid = Pid.new(2, 0, 0, -100, 100)
pid = Pid.new(2, 0, 0, -100, 100)
Line 279: Line 279:
industrialPid:get(target, input)
industrialPid:get(target, input)
industrialPid:reset()
industrialPid:reset()
</syntaxhighlight>
</pre>


=== Utility ===
=== Utility ===
Line 296: Line 296:
'''Example:'''
'''Example:'''


<syntaxhighlight lang="lua">
<pre>
n = 5.5
n = 5.5
print('Hello Lua, number is: ' .. n)
print('Hello Lua, number is: ' .. n)
</syntaxhighlight>
</pre>


Output: <code>Hello Lua, number is: 5.5</code>
Output: <code>Hello Lua, number is: 5.5</code>
Line 456: Line 456:
=== Timer example ===
=== Timer example ===


<syntaxhighlight lang="lua">
<pre>
t = Timer.new()
t = Timer.new()
timingAdd = 0
timingAdd = 0
Line 479: Line 479:
   print('Hello analog ' .. auxV .. " " .. val)
   print('Hello analog ' .. auxV .. " " .. val)
end
end
</syntaxhighlight>
</pre>


=== PWM ===
=== PWM ===


<syntaxhighlight lang="lua">
<pre>
startPwm(0, 100, 0)
startPwm(0, 100, 0)


Line 490: Line 490:
  setPwmDuty(0, enable_pump and 1 or 0)
  setPwmDuty(0, enable_pump and 1 or 0)
end
end
</syntaxhighlight>
</pre>


=== CAN transmit ===
=== CAN transmit ===


<syntaxhighlight lang="lua">
<pre>
function onTick()
function onTick()
   clt = getSensor("CLT")
   clt = getSensor("CLT")
Line 506: Line 506:
   txCan(1, 0x600, 1, txPayload)
   txCan(1, 0x600, 1, txPayload)
end
end
</syntaxhighlight>
</pre>


=== Set sensor value ===
=== Set sensor value ===
Line 512: Line 512:
Use standard sensor names from TunerStudio Live Data.
Use standard sensor names from TunerStudio Live Data.


<syntaxhighlight lang="lua">
<pre>
-- do not configure the same physical input elsewhere
-- do not configure the same physical input elsewhere
vssSensor = Sensor.new("VehicleSpeed")
vssSensor = Sensor.new("VehicleSpeed")
Line 522: Line 522:
  print("VSS " .. valFromSensor)
  print("VSS " .. valFromSensor)
end
end
</syntaxhighlight>
</pre>


=== CAN receive ===
=== CAN receive ===


<syntaxhighlight lang="lua">
<pre>
canRxAdd(0x500)
canRxAdd(0x500)
canRxAdd(0x570)
canRxAdd(0x570)
Line 538: Line 538:
  end
  end
end
end
</syntaxhighlight>
</pre>


=== Table / curve ===
=== Table / curve ===


<syntaxhighlight lang="lua">
<pre>
tableIndex = findTableIndex("duty")
tableIndex = findTableIndex("duty")


Line 552: Line 552:
sparkCutCurve = findCurveIndex("sparkcut")
sparkCutCurve = findCurveIndex("sparkcut")
sparkCutByTorque = curve(sparkCutCurve, torquex)
sparkCutByTorque = curve(sparkCutCurve, torquex)
</syntaxhighlight>
</pre>


== See also ==
== See also ==

Latest revision as of 04:08, 18 August 2026

Lua Scripting

TL;DR: Connect with TunerStudio, open the Lua tab (or use epicEFI Console), and edit your script there. Lua runs on the ECU for live scripting. The most popular use case is CAN bus integration.

Introduction

epicEFI gives you a lot of flexibility — enough to build user-defined control strategies for primary and auxiliary actuators. Boards with an F7 or H7 MCU (EpicECU, M144H7, M144F7RED, etc.) have the most headroom for Lua. F4 boards (MEGA100F4, UAEFI) also support Lua but with tighter memory limits.

Variable names and hashes

Calibration names, output channel names, variable types (CONFIG GETTER/SETTER, OUTPC GETTER/SETTER), and their djb2 hashes depend on your exact firmware build. Do not guess — look them up here:

Variables Lookup

Use this tool when calling getCalibration() / setCalibration(), getOutput(), configuring user-table axes, or reading/writing variables over EPIC CAN.

Basics

epicEFI provides hooks to interface with the firmware, manipulate its state, and read/write configuration:

Some example uses are in Examples.

Conventions

  • The Lua interpreter reports errors when something is wrong; check epicEFI Console / TunerStudio Lua output for errors and print() output.
  • Unless otherwise noted, all index parameters are zero-based (first element is index 0).

Writing Your Script

The entire Lua script is loaded at startup. After that, a function named onTick is called periodically by epicEFI.

Simple startup example:

print('Hello Lua startup!')

function onTick()
    print('Hello onTick()')
end

Controlling the tick rate

setTickRate(hz) sets how often epicEFI calls onTick. If your script does heavy work, it may run slower than the requested rate. The Lua VM runs at low priority relative to engine control, so you cannot starve critical ECU functions — set the rate to whatever your script needs. onCanRx runs at the same rate as onTick.

n = 0
setTickRate(5) -- 5 Hz
function onTick()
    print('Hello Lua: ' .. n)
    n = n + 1
end

Editing scripts

An editor with Lua Language Server (LSP) support makes writing scripts much easier.

Hooks / function reference

User settings

getOutput(name)

Example: getOutput("clutchUpState") or getOutput("brakePedalState").

Valid output names for your firmware: Variable Status Selector.

setClutchUpState(value)

setBrakePedalState(value)

Use setBrakePedalState to report a CAN-based brake pedal to epicEFI.

setAcRequestState(value)

Use setAcRequestState to report a CAN-based A/C request.

setEtbDisabled(value)

setIgnDisabled(value)

Use setIgnDisabled for cranking safety and other ignition-cut strategies.

setAcDisabled(value)

Disable/suppress A/C regardless of how it would otherwise be enabled.

getTimeSinceAcToggleMs()

getCalibration(name)

Returns the current value of a scalar calibration setting. Example: getCalibration("cranking.rpm").

Full list of valid names: Variable Status Selector.

setCalibration(name, value, needEvent)

Sets a calibration value. Optionally fires a calibration change event depending on needEvent.

Example: setCalibration("cranking.rpm", 900, false)

burnconfig

Schedules a write of current calibration to flash once the engine is stopped.

findSetting(name, defaultValue)

Finds a user setting by name and returns its numeric value. Useful when the script author and the tuner are different people, or when editing is done only through TunerStudio fields rather than the Lua editor.

  • Parameters
    • name: Variable name (matches the configuration field name)
    • defaultValue: Returned if the setting is not found

isFirmwareError

Returns true if the ECU is in a critical/fatal error state.

Engine control

startCrankingEngine()

Starts cranking as if the physical start button were pressed.

stopEngine()

isEngineStopRequested()

Returns true if an engine stop was requested (by Lua or the start/stop button) within the last five seconds.

setLaunchTrigger

setSparkSkipRatio(ratio)

  • setSparkSkipRatio(0) — skip 0% of ignition events (no skip)
  • setSparkSkipRatio(0.5) — skip half of ignition events (never two consecutive skips)

Useful for torque reduction.

setSparkHardSkipRatio(ratio)

  • setSparkHardSkipRatio(0) — no skip
  • setSparkHardSkipRatio(0.75) — skip 75% of ignition events

setIdleAdd(percent)

Percent added to idle (including open loop).

setFuelAdd(amount)

Currently not functional.

Fuel mass to add to injection, scaled by setFuelMult(); starts at 0.

setFuelMult(coeff)

Currently not functional.

Multiplier for added fuel mass; starts at 1.0.

setBoostTargetAdd(amount)

Additive offset for closed-loop boost target.

setBoostTargetMult(coeff)

Multiplier for closed-loop boost target.

setBoostDutyAdd(amount)

Additive offset for open-loop boost duty.

setTimingAdd(angle)

Use negative values to retard timing.

setTimingMult(coeff)

Useful for torque reduction.

setEtbAdd(percent)

ETB adder as a percent of wide-open: e.g. 10 adds +10%. Static offset on top of the computed position (TPS 5% + 10 → 15% ETB command).

Useful for torque reduction.

Timer

yourTimer = Timer.new() creates a timer object.

reset

yourTimer:reset() resets the timer.

getElapsedSeconds

yourTimer:getElapsedSeconds() returns seconds since last reset.

getTsButtonCount

getTsButtonCount(X) returns how many times Lua button X was pressed in TunerStudio. See ts-button-example.lua in the firmware examples.

CAN bus

enableCanTx(isEnabled)

Enabled by default. Use enableCanTx(false) to suppress CAN transmit from Lua.

txCan(bus, ID, isExt, payload)

  • Parameters
    • bus: Hardware CAN bus index — typically 1 on single-CAN boards; 1 or 2 on dual-CAN boards (EpicECU, M144H7, etc.)
    • isExt: 0 for 11-bit standard ID

canRxAdd(id)

canRxAdd(bus, id)

canRxAdd(id, callback)

canRxAdd(bus, id, callback)

canRxAddMask(id, mask)

canRxAddMask(bus, id, mask)

canRxAddMask(id, mask, callback)

canRxAddMask(bus, id, mask, callback)

  • Parameters
    • id: CAN ID to listen for
    • mask: Applied to the received ID before comparison. Example: id 3 and mask 0xFF matches any frame whose low 8 bits are 3. Omit the mask to match exactly one ID.
    • bus: Hardware CAN bus index. If omitted, frames from any bus are received.
    • callback: Function called when a matching frame arrives. If omitted, onCanRx is used.

CAN RX callback shape:

function onCanRx(bus, id, dlc, data)
    -- handle frame
end

For high-throughput RX, see enableCanRxWorkaround() in the firmware examples.

SENT protocol

getSentValue(index)

getSentValues(index)

PID

deltaTime is measured automatically between successive pid:get calls.

-- p, i, d, min, max
pid = Pid.new(2, 0, 0, -100, 100)
pid:setOffset(0.3)
pid:get(target, input)
pid:reset()

industrialPid = IndustrialPid.new(2, 0, 0, -100, 100)
industrialPid:setOffset(0.3)
industrialPid:setDerivativeFilterLoss(0.3)
industrialPid:setAntiwindupFreq(0.3)
industrialPid:get(target, input)
industrialPid:reset()

Utility

print(msg)

Prints a line to the ECU log.

  • Parameters: msg — string or number
  • Returns: none

vin(index)

Returns the VIN character at the given zero-based index.

Example:

n = 5.5
print('Hello Lua, number is: ' .. n)

Output: Hello Lua, number is: 5.5

setTickRate(hz)

Sets how often epicEFI calls onTick and onCanRx, in Hz. Default after reset is 10 Hz.

  • Parameters: hz — clamped to 1–200 Hz
  • Returns: none

mcu_standby()

Puts the MCU into standby (low current). Use with care.

interpolate(x1, y1, x2, y2, x)

Linear interpolation of x on the line through (x1, y1) and (x2, y2).

findTableIndex(name)

Returns the index of a script table by human-readable name.

table3d(tableIdx, x, y)

Looks up a value from a script 3D table.

  • Parameters
    • tableIdx: Table index (1–4)
    • x: X-axis value (often RPM)
    • y: Y-axis value (often load)
  • Returns: table output value

findCurveIndex(name)

Returns the index of a script curve by name.

curve(curveIdx, x)

Looks up a value from a script curve.

  • Parameters
    • curveIdx: Curve index (1-based)
    • x: Axis value

setDebug(index, value)

Sets a debug channel when ECU debug mode is Lua.

  • Parameters: index 1–7, value — channel value
  • Returns: none

Input

getSensor(name)

Reads a sensor by name, e.g. getSensor("AcceleratorPedal").

  • Parameters: name — sensor name (same names as TunerStudio Live Data, e.g. TPS, CLT, RPM, MAP)
  • Returns: reading, or nil if the sensor is invalid or not configured

getSensorByIndex(index)

Reads a sensor by enum index.

  • Returns: reading, or nil on failure

getSensorRaw(index)

Raw sensor value (usually pin voltage before scaling).

  • Returns: raw value, or 0 if unsupported / not configured / failed

getAuxAnalog(index)

Like getSensorRaw but for aux analog inputs — always voltage.

  • Parameters: index 0–3
  • Returns: voltage, or nil if not configured

hasSensor(index)

Whether a sensor slot is configured (valid or not).

  • Returns: boolean

getDigital(index)

Reads a built-in digital input.

Index Channel
0 Clutch down
1 Clutch up
2 Brake switch
3 AC switch

getAuxDigital(index)

Reads a user-configured digital input (index 0–7). Configure pins under Lua Digital Aux Inputs in TunerStudio.

readPin(pinName)

Reads an MCU pin directly (e.g. "PD15"). Emergency/debug only — prefer Lua aux inputs for real logic.

Output

Not the same as Live Data “outputs” or GPPWM.

selfStimulateRPM(rpm)

Positive RPM starts injector clicking at that speed; 0 stops self-stimulation.

startPwm(index, frequency, duty)

Starts PWM on a Lua PWM output. Pin assignment: Lua PWM Outputs in TunerStudio.

  • Parameters
    • index: 0–7
    • frequency: 1–1000 Hz
    • duty: 0.0 = off, 1.0 = full on

setPwmDuty(index, duty)

setPwmFreq(index, frequency)

getGpPwm(index)

Current GPPWM output percent (index 0–3).

setLuaGauge(index, value)

Writes a Lua gauge (indices 1–8) for Live Data / logging.

setDacVoltage(index, value)

Only on boards with DAC hardware enabled.

Console commands

  • luamemory — Lua memory usage
  • luareset — reset Lua VM

Examples

Example scripts ship with the firmware under firmware/controllers/lua/examples/ (browse on GitHub).

Notable examples: honda-bcm.txt (VSS from CAN / gear detection), ford-focus-ii-pps.txt, bmw-idrive.txt.

Timer example

t = Timer.new()
timingAdd = 0

function onTick()
   auxV = getAuxAnalog(0)
   tps = getSensor("TPS")
   -- check for nil if aux input is not assigned
   if auxV > 2 then
     t:reset()
   end

   val = t:getElapsedSeconds()

   if t:getElapsedSeconds() < 3 then
     timingAdd = 10
   else
     timingAdd = 0
   end
   setTimingAdd(timingAdd)

   print('Hello analog ' .. auxV .. " " .. val)
end

PWM

startPwm(0, 100, 0)

function onTick()
 enable_pump = getSensor("RPM") > 700 and getSensor("BatteryVoltage") > 13 and getSensor("VehicleSpeed") < 60
 setPwmDuty(0, enable_pump and 1 or 0)
end

CAN transmit

function onTick()
  clt = getSensor("CLT")
  print('CLT ' .. clt)
  voltage0 = getSensor("aux0")

  txPayload = {}
  txPayload[1] = math.floor(((voltage0/256) - math.floor(voltage0/256))*256)
  txPayload[2] = math.floor(voltage0 / 256)

  txCan(1, 0x600, 1, txPayload)
end

Set sensor value

Use standard sensor names from TunerStudio Live Data.

-- do not configure the same physical input elsewhere
vssSensor = Sensor.new("VehicleSpeed")
vssSensor:setTimeout(3000)
function onTick()
 injectedVssValue = 123.4
 vssSensor:set(injectedVssValue)
 valFromSensor = getSensor("VehicleSpeed")
 print("VSS " .. valFromSensor)
end

CAN receive

canRxAdd(0x500)
canRxAdd(0x570)
function onCanRx(bus, id, dlc, data)
 print('got CAN id=' .. id .. ' dlc=' .. dlc)
 if id == 0x500 then
  canState = data[1]
 end
 if id == 0x570 then
  mcu_standby()
 end
end

Table / curve

tableIndex = findTableIndex("duty")

TurbochargerSpeed = getSensor("TurbochargerSpeed")
tps = getSensor("Tps1")

dutyCycle = table3d(tableIndex, TurbochargerSpeed, tps)

sparkCutCurve = findCurveIndex("sparkcut")
sparkCutByTorque = curve(sparkCutCurve, torquex)

See also