<?xml version="1.0"?>
<feed xmlns="http://www.w3.org/2005/Atom" xml:lang="en">
	<id>https://content.epicefi.com/wiki/api.php?action=feedcontributions&amp;feedformat=atom&amp;user=Joesphan</id>
	<title>epicEFI Wiki - User contributions [en]</title>
	<link rel="self" type="application/atom+xml" href="https://content.epicefi.com/wiki/api.php?action=feedcontributions&amp;feedformat=atom&amp;user=Joesphan"/>
	<link rel="alternate" type="text/html" href="https://content.epicefi.com/wiki/index.php/Special:Contributions/Joesphan"/>
	<updated>2026-08-29T07:02:33Z</updated>
	<subtitle>User contributions</subtitle>
	<generator>MediaWiki 1.45.1</generator>
	<entry>
		<id>https://content.epicefi.com/wiki/index.php?title=Lua&amp;diff=501</id>
		<title>Lua</title>
		<link rel="alternate" type="text/html" href="https://content.epicefi.com/wiki/index.php?title=Lua&amp;diff=501"/>
		<updated>2026-08-18T04:08:53Z</updated>

		<summary type="html">&lt;p&gt;Joesphan: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;= Lua Scripting =&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;TL;DR:&#039;&#039;&#039; Connect with TunerStudio, open the &#039;&#039;&#039;Lua&#039;&#039;&#039; 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.&lt;br /&gt;
&lt;br /&gt;
== Introduction ==&lt;br /&gt;
&lt;br /&gt;
epicEFI gives you a lot of flexibility — enough to build user-defined control strategies for primary and auxiliary actuators. Boards with an &#039;&#039;&#039;F7&#039;&#039;&#039; or &#039;&#039;&#039;H7&#039;&#039;&#039; MCU (EpicECU, M144H7, M144F7RED, etc.) have the most headroom for Lua. F4 boards (MEGA100F4, UAEFI) also support Lua but with tighter memory limits.&lt;br /&gt;
&lt;br /&gt;
== Variable names and hashes ==&lt;br /&gt;
&lt;br /&gt;
Calibration names, output channel names, variable types (CONFIG GETTER/SETTER, OUTPC GETTER/SETTER), and their &#039;&#039;&#039;djb2 hashes&#039;&#039;&#039; depend on your exact firmware build. Do not guess — look them up here:&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;[https://content.epicefi.com/docs/variable_status_selector.html Variables Lookup]&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Use this tool when calling &amp;lt;code&amp;gt;getCalibration()&amp;lt;/code&amp;gt; / &amp;lt;code&amp;gt;setCalibration()&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;getOutput()&amp;lt;/code&amp;gt;, configuring user-table axes, or reading/writing variables over EPIC CAN.&lt;br /&gt;
&lt;br /&gt;
== Basics ==&lt;br /&gt;
&lt;br /&gt;
epicEFI provides hooks to interface with the firmware, manipulate its state, and read/write configuration:&lt;br /&gt;
&lt;br /&gt;
* [[#CAN bus|Hooks for CAN bus communications]]&lt;br /&gt;
* [[#Input|Inputs from sensors can be read directly]]. [[#Set sensor value|You can also produce sensor values]] with Lua.&lt;br /&gt;
* [[#Output|ECU general purpose outputs]]&lt;br /&gt;
* [[#Engine control|Aspects of the engine can be controlled directly]]&lt;br /&gt;
* ECU configuration can be accessed (read/write) via [[#getCalibration(name)|&amp;lt;code&amp;gt;getCalibration()&amp;lt;/code&amp;gt;]] and [[#setCalibration(name, value, needEvent)|&amp;lt;code&amp;gt;setCalibration()&amp;lt;/code&amp;gt;]].&lt;br /&gt;
** Valid calibration names for your firmware are listed in the [https://content.epicefi.com/docs/variable_status_selector.html Variable Status Selector].&lt;br /&gt;
* ECU internal state (logic outputs / Live Data values) can be read via [[#getOutput(name)|&amp;lt;code&amp;gt;getOutput()&amp;lt;/code&amp;gt;]], and some can be changed via dedicated setters (e.g. [[#setClutchUpState(value)|&amp;lt;code&amp;gt;setClutchUpState()&amp;lt;/code&amp;gt;]]). See [[#Output|Output]].&lt;br /&gt;
** Valid output names for your firmware are listed in the [https://content.epicefi.com/docs/variable_status_selector.html Variable Status Selector].&lt;br /&gt;
* Hooks to read values from SENT sensors; see [[#SENT protocol|SENT protocol]].&lt;br /&gt;
* Useful helper routines; see [[#Utility|Utility]].&lt;br /&gt;
&lt;br /&gt;
Some example uses are in [[#Examples|Examples]].&lt;br /&gt;
&lt;br /&gt;
== Conventions ==&lt;br /&gt;
&lt;br /&gt;
* The Lua interpreter reports errors when something is wrong; check epicEFI Console / TunerStudio Lua output for errors and &amp;lt;code&amp;gt;print()&amp;lt;/code&amp;gt; output.&lt;br /&gt;
* Unless otherwise noted, all &amp;lt;code&amp;gt;index&amp;lt;/code&amp;gt; parameters are &#039;&#039;&#039;zero-based&#039;&#039;&#039; (first element is index &amp;lt;code&amp;gt;0&amp;lt;/code&amp;gt;).&lt;br /&gt;
&lt;br /&gt;
== Writing Your Script ==&lt;br /&gt;
&lt;br /&gt;
The entire Lua script is loaded at startup. After that, a function named &amp;lt;code&amp;gt;onTick&amp;lt;/code&amp;gt; is called periodically by epicEFI.&lt;br /&gt;
&lt;br /&gt;
Simple startup example:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
print(&#039;Hello Lua startup!&#039;)&lt;br /&gt;
&lt;br /&gt;
function onTick()&lt;br /&gt;
    print(&#039;Hello onTick()&#039;)&lt;br /&gt;
end&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Controlling the tick rate ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;setTickRate(hz)&amp;lt;/code&amp;gt; sets how often epicEFI calls &amp;lt;code&amp;gt;onTick&amp;lt;/code&amp;gt;. 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. &amp;lt;code&amp;gt;onCanRx&amp;lt;/code&amp;gt; runs at the same rate as &amp;lt;code&amp;gt;onTick&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
n = 0&lt;br /&gt;
setTickRate(5) -- 5 Hz&lt;br /&gt;
function onTick()&lt;br /&gt;
    print(&#039;Hello Lua: &#039; .. n)&lt;br /&gt;
    n = n + 1&lt;br /&gt;
end&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Editing scripts ===&lt;br /&gt;
&lt;br /&gt;
An editor with [https://github.com/LuaLS/lua-language-server#install Lua Language Server] (LSP) support makes writing scripts much easier.&lt;br /&gt;
&lt;br /&gt;
== Hooks / function reference ==&lt;br /&gt;
&lt;br /&gt;
=== User settings ===&lt;br /&gt;
&lt;br /&gt;
==== &amp;lt;code&amp;gt;getOutput(name)&amp;lt;/code&amp;gt; ====&lt;br /&gt;
&lt;br /&gt;
Example: &amp;lt;code&amp;gt;getOutput(&amp;quot;clutchUpState&amp;quot;)&amp;lt;/code&amp;gt; or &amp;lt;code&amp;gt;getOutput(&amp;quot;brakePedalState&amp;quot;)&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Valid output names for your firmware: [https://content.epicefi.com/docs/variable_status_selector.html Variable Status Selector].&lt;br /&gt;
&lt;br /&gt;
==== &amp;lt;code&amp;gt;setClutchUpState(value)&amp;lt;/code&amp;gt; ====&lt;br /&gt;
&lt;br /&gt;
==== &amp;lt;code&amp;gt;setBrakePedalState(value)&amp;lt;/code&amp;gt; ====&lt;br /&gt;
&lt;br /&gt;
Use &amp;lt;code&amp;gt;setBrakePedalState&amp;lt;/code&amp;gt; to report a CAN-based brake pedal to epicEFI.&lt;br /&gt;
&lt;br /&gt;
==== &amp;lt;code&amp;gt;setAcRequestState(value)&amp;lt;/code&amp;gt; ====&lt;br /&gt;
&lt;br /&gt;
Use &amp;lt;code&amp;gt;setAcRequestState&amp;lt;/code&amp;gt; to report a CAN-based A/C request.&lt;br /&gt;
&lt;br /&gt;
==== &amp;lt;code&amp;gt;setEtbDisabled(value)&amp;lt;/code&amp;gt; ====&lt;br /&gt;
&lt;br /&gt;
==== &amp;lt;code&amp;gt;setIgnDisabled(value)&amp;lt;/code&amp;gt; ====&lt;br /&gt;
&lt;br /&gt;
Use &amp;lt;code&amp;gt;setIgnDisabled&amp;lt;/code&amp;gt; for cranking safety and other ignition-cut strategies.&lt;br /&gt;
&lt;br /&gt;
==== &amp;lt;code&amp;gt;setAcDisabled(value)&amp;lt;/code&amp;gt; ====&lt;br /&gt;
&lt;br /&gt;
Disable/suppress A/C regardless of how it would otherwise be enabled.&lt;br /&gt;
&lt;br /&gt;
==== &amp;lt;code&amp;gt;getTimeSinceAcToggleMs()&amp;lt;/code&amp;gt; ====&lt;br /&gt;
&lt;br /&gt;
==== &amp;lt;code&amp;gt;getCalibration(name)&amp;lt;/code&amp;gt; ====&lt;br /&gt;
&lt;br /&gt;
Returns the current value of a scalar calibration setting. Example: &amp;lt;code&amp;gt;getCalibration(&amp;quot;cranking.rpm&amp;quot;)&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Full list of valid names: [https://content.epicefi.com/docs/variable_status_selector.html Variable Status Selector].&lt;br /&gt;
&lt;br /&gt;
==== &amp;lt;code&amp;gt;setCalibration(name, value, needEvent)&amp;lt;/code&amp;gt; ====&lt;br /&gt;
&lt;br /&gt;
Sets a calibration value. Optionally fires a calibration change event depending on &amp;lt;code&amp;gt;needEvent&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Example: &amp;lt;code&amp;gt;setCalibration(&amp;quot;cranking.rpm&amp;quot;, 900, false)&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== &amp;lt;code&amp;gt;burnconfig&amp;lt;/code&amp;gt; ====&lt;br /&gt;
&lt;br /&gt;
Schedules a write of current calibration to flash once the engine is stopped.&lt;br /&gt;
&lt;br /&gt;
==== &amp;lt;code&amp;gt;findSetting(name, defaultValue)&amp;lt;/code&amp;gt; ====&lt;br /&gt;
&lt;br /&gt;
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.&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;Parameters&#039;&#039;&#039;&lt;br /&gt;
** &amp;lt;code&amp;gt;name&amp;lt;/code&amp;gt;: Variable name (matches the configuration field name)&lt;br /&gt;
** &amp;lt;code&amp;gt;defaultValue&amp;lt;/code&amp;gt;: Returned if the setting is not found&lt;br /&gt;
&lt;br /&gt;
=== &amp;lt;code&amp;gt;isFirmwareError&amp;lt;/code&amp;gt; ===&lt;br /&gt;
&lt;br /&gt;
Returns &amp;lt;code&amp;gt;true&amp;lt;/code&amp;gt; if the ECU is in a critical/fatal error state.&lt;br /&gt;
&lt;br /&gt;
=== Engine control ===&lt;br /&gt;
&lt;br /&gt;
==== &amp;lt;code&amp;gt;startCrankingEngine()&amp;lt;/code&amp;gt; ====&lt;br /&gt;
&lt;br /&gt;
Starts cranking as if the physical start button were pressed.&lt;br /&gt;
&lt;br /&gt;
==== &amp;lt;code&amp;gt;stopEngine()&amp;lt;/code&amp;gt; ====&lt;br /&gt;
&lt;br /&gt;
==== &amp;lt;code&amp;gt;isEngineStopRequested()&amp;lt;/code&amp;gt; ====&lt;br /&gt;
&lt;br /&gt;
Returns &amp;lt;code&amp;gt;true&amp;lt;/code&amp;gt; if an engine stop was requested (by Lua or the start/stop button) within the last five seconds.&lt;br /&gt;
&lt;br /&gt;
==== &amp;lt;code&amp;gt;setLaunchTrigger&amp;lt;/code&amp;gt; ====&lt;br /&gt;
&lt;br /&gt;
==== &amp;lt;code&amp;gt;setSparkSkipRatio(ratio)&amp;lt;/code&amp;gt; ====&lt;br /&gt;
&lt;br /&gt;
* &amp;lt;code&amp;gt;setSparkSkipRatio(0)&amp;lt;/code&amp;gt; — skip 0% of ignition events (no skip)&lt;br /&gt;
* &amp;lt;code&amp;gt;setSparkSkipRatio(0.5)&amp;lt;/code&amp;gt; — skip half of ignition events (never two consecutive skips)&lt;br /&gt;
&lt;br /&gt;
Useful for torque reduction.&lt;br /&gt;
&lt;br /&gt;
==== &amp;lt;code&amp;gt;setSparkHardSkipRatio(ratio)&amp;lt;/code&amp;gt; ====&lt;br /&gt;
&lt;br /&gt;
* &amp;lt;code&amp;gt;setSparkHardSkipRatio(0)&amp;lt;/code&amp;gt; — no skip&lt;br /&gt;
* &amp;lt;code&amp;gt;setSparkHardSkipRatio(0.75)&amp;lt;/code&amp;gt; — skip 75% of ignition events&lt;br /&gt;
&lt;br /&gt;
==== &amp;lt;code&amp;gt;setIdleAdd(percent)&amp;lt;/code&amp;gt; ====&lt;br /&gt;
&lt;br /&gt;
Percent added to idle (including open loop).&lt;br /&gt;
&lt;br /&gt;
==== &amp;lt;code&amp;gt;setFuelAdd(amount)&amp;lt;/code&amp;gt; ====&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;Currently not functional.&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Fuel mass to add to injection, scaled by [[#setFuelMult(coeff)|&amp;lt;code&amp;gt;setFuelMult()&amp;lt;/code&amp;gt;]]; starts at 0.&lt;br /&gt;
&lt;br /&gt;
==== &amp;lt;code&amp;gt;setFuelMult(coeff)&amp;lt;/code&amp;gt; ====&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;Currently not functional.&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Multiplier for added fuel mass; starts at 1.0.&lt;br /&gt;
&lt;br /&gt;
==== &amp;lt;code&amp;gt;setBoostTargetAdd(amount)&amp;lt;/code&amp;gt; ====&lt;br /&gt;
&lt;br /&gt;
Additive offset for closed-loop boost target.&lt;br /&gt;
&lt;br /&gt;
==== &amp;lt;code&amp;gt;setBoostTargetMult(coeff)&amp;lt;/code&amp;gt; ====&lt;br /&gt;
&lt;br /&gt;
Multiplier for closed-loop boost target.&lt;br /&gt;
&lt;br /&gt;
==== &amp;lt;code&amp;gt;setBoostDutyAdd(amount)&amp;lt;/code&amp;gt; ====&lt;br /&gt;
&lt;br /&gt;
Additive offset for open-loop boost duty.&lt;br /&gt;
&lt;br /&gt;
==== &amp;lt;code&amp;gt;setTimingAdd(angle)&amp;lt;/code&amp;gt; ====&lt;br /&gt;
&lt;br /&gt;
Use negative values to retard timing.&lt;br /&gt;
&lt;br /&gt;
==== &amp;lt;code&amp;gt;setTimingMult(coeff)&amp;lt;/code&amp;gt; ====&lt;br /&gt;
&lt;br /&gt;
Useful for torque reduction.&lt;br /&gt;
&lt;br /&gt;
==== &amp;lt;code&amp;gt;setEtbAdd(percent)&amp;lt;/code&amp;gt; ====&lt;br /&gt;
&lt;br /&gt;
ETB adder as a percent of wide-open: e.g. &amp;lt;code&amp;gt;10&amp;lt;/code&amp;gt; adds +10%. Static offset on top of the computed position (TPS 5% + &amp;lt;code&amp;gt;10&amp;lt;/code&amp;gt; → 15% ETB command).&lt;br /&gt;
&lt;br /&gt;
Useful for torque reduction.&lt;br /&gt;
&lt;br /&gt;
=== Timer ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;yourTimer = Timer.new()&amp;lt;/code&amp;gt; creates a timer object.&lt;br /&gt;
&lt;br /&gt;
==== &amp;lt;code&amp;gt;reset&amp;lt;/code&amp;gt; ====&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;yourTimer:reset()&amp;lt;/code&amp;gt; resets the timer.&lt;br /&gt;
&lt;br /&gt;
==== &amp;lt;code&amp;gt;getElapsedSeconds&amp;lt;/code&amp;gt; ====&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;yourTimer:getElapsedSeconds()&amp;lt;/code&amp;gt; returns seconds since last reset.&lt;br /&gt;
&lt;br /&gt;
==== &amp;lt;code&amp;gt;getTsButtonCount&amp;lt;/code&amp;gt; ====&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;getTsButtonCount(X)&amp;lt;/code&amp;gt; returns how many times Lua button &#039;&#039;&#039;X&#039;&#039;&#039; was pressed in TunerStudio. See &amp;lt;code&amp;gt;ts-button-example.lua&amp;lt;/code&amp;gt; in the firmware examples.&lt;br /&gt;
&lt;br /&gt;
=== CAN bus ===&lt;br /&gt;
&lt;br /&gt;
==== &amp;lt;code&amp;gt;enableCanTx(isEnabled)&amp;lt;/code&amp;gt; ====&lt;br /&gt;
&lt;br /&gt;
Enabled by default. Use &amp;lt;code&amp;gt;enableCanTx(false)&amp;lt;/code&amp;gt; to suppress CAN transmit from Lua.&lt;br /&gt;
&lt;br /&gt;
==== &amp;lt;code&amp;gt;txCan(bus, ID, isExt, payload)&amp;lt;/code&amp;gt; ====&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;Parameters&#039;&#039;&#039;&lt;br /&gt;
** &amp;lt;code&amp;gt;bus&amp;lt;/code&amp;gt;: Hardware CAN bus index — typically &amp;lt;code&amp;gt;1&amp;lt;/code&amp;gt; on single-CAN boards; &amp;lt;code&amp;gt;1&amp;lt;/code&amp;gt; or &amp;lt;code&amp;gt;2&amp;lt;/code&amp;gt; on dual-CAN boards (EpicECU, M144H7, etc.)&lt;br /&gt;
** &amp;lt;code&amp;gt;isExt&amp;lt;/code&amp;gt;: &amp;lt;code&amp;gt;0&amp;lt;/code&amp;gt; for 11-bit standard ID&lt;br /&gt;
&lt;br /&gt;
==== &amp;lt;code&amp;gt;canRxAdd(id)&amp;lt;/code&amp;gt; ====&lt;br /&gt;
&lt;br /&gt;
==== &amp;lt;code&amp;gt;canRxAdd(bus, id)&amp;lt;/code&amp;gt; ====&lt;br /&gt;
&lt;br /&gt;
==== &amp;lt;code&amp;gt;canRxAdd(id, callback)&amp;lt;/code&amp;gt; ====&lt;br /&gt;
&lt;br /&gt;
==== &amp;lt;code&amp;gt;canRxAdd(bus, id, callback)&amp;lt;/code&amp;gt; ====&lt;br /&gt;
&lt;br /&gt;
==== &amp;lt;code&amp;gt;canRxAddMask(id, mask)&amp;lt;/code&amp;gt; ====&lt;br /&gt;
&lt;br /&gt;
==== &amp;lt;code&amp;gt;canRxAddMask(bus, id, mask)&amp;lt;/code&amp;gt; ====&lt;br /&gt;
&lt;br /&gt;
==== &amp;lt;code&amp;gt;canRxAddMask(id, mask, callback)&amp;lt;/code&amp;gt; ====&lt;br /&gt;
&lt;br /&gt;
==== &amp;lt;code&amp;gt;canRxAddMask(bus, id, mask, callback)&amp;lt;/code&amp;gt; ====&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;Parameters&#039;&#039;&#039;&lt;br /&gt;
** &amp;lt;code&amp;gt;id&amp;lt;/code&amp;gt;: CAN ID to listen for&lt;br /&gt;
** &amp;lt;code&amp;gt;mask&amp;lt;/code&amp;gt;: Applied to the received ID before comparison. Example: id &amp;lt;code&amp;gt;3&amp;lt;/code&amp;gt; and mask &amp;lt;code&amp;gt;0xFF&amp;lt;/code&amp;gt; matches any frame whose low 8 bits are &amp;lt;code&amp;gt;3&amp;lt;/code&amp;gt;. Omit the mask to match exactly one ID.&lt;br /&gt;
** &amp;lt;code&amp;gt;bus&amp;lt;/code&amp;gt;: Hardware CAN bus index. If omitted, frames from any bus are received.&lt;br /&gt;
** &amp;lt;code&amp;gt;callback&amp;lt;/code&amp;gt;: Function called when a matching frame arrives. If omitted, &amp;lt;code&amp;gt;onCanRx&amp;lt;/code&amp;gt; is used.&lt;br /&gt;
&lt;br /&gt;
CAN RX callback shape:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
function onCanRx(bus, id, dlc, data)&lt;br /&gt;
    -- handle frame&lt;br /&gt;
end&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
For high-throughput RX, see &amp;lt;code&amp;gt;enableCanRxWorkaround()&amp;lt;/code&amp;gt; in the firmware examples.&lt;br /&gt;
&lt;br /&gt;
=== SENT protocol ===&lt;br /&gt;
&lt;br /&gt;
==== &amp;lt;code&amp;gt;getSentValue(index)&amp;lt;/code&amp;gt; ====&lt;br /&gt;
&lt;br /&gt;
==== &amp;lt;code&amp;gt;getSentValues(index)&amp;lt;/code&amp;gt; ====&lt;br /&gt;
&lt;br /&gt;
=== PID ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;deltaTime&amp;lt;/code&amp;gt; is measured automatically between successive &amp;lt;code&amp;gt;pid:get&amp;lt;/code&amp;gt; calls.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
-- p, i, d, min, max&lt;br /&gt;
pid = Pid.new(2, 0, 0, -100, 100)&lt;br /&gt;
pid:setOffset(0.3)&lt;br /&gt;
pid:get(target, input)&lt;br /&gt;
pid:reset()&lt;br /&gt;
&lt;br /&gt;
industrialPid = IndustrialPid.new(2, 0, 0, -100, 100)&lt;br /&gt;
industrialPid:setOffset(0.3)&lt;br /&gt;
industrialPid:setDerivativeFilterLoss(0.3)&lt;br /&gt;
industrialPid:setAntiwindupFreq(0.3)&lt;br /&gt;
industrialPid:get(target, input)&lt;br /&gt;
industrialPid:reset()&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Utility ===&lt;br /&gt;
&lt;br /&gt;
==== &amp;lt;code&amp;gt;print(msg)&amp;lt;/code&amp;gt; ====&lt;br /&gt;
&lt;br /&gt;
Prints a line to the ECU log.&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;Parameters:&#039;&#039;&#039; &amp;lt;code&amp;gt;msg&amp;lt;/code&amp;gt; — string or number&lt;br /&gt;
* &#039;&#039;&#039;Returns:&#039;&#039;&#039; none&lt;br /&gt;
&lt;br /&gt;
==== &amp;lt;code&amp;gt;vin(index)&amp;lt;/code&amp;gt; ====&lt;br /&gt;
&lt;br /&gt;
Returns the VIN character at the given zero-based index.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Example:&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
n = 5.5&lt;br /&gt;
print(&#039;Hello Lua, number is: &#039; .. n)&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Output: &amp;lt;code&amp;gt;Hello Lua, number is: 5.5&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== &amp;lt;code&amp;gt;setTickRate(hz)&amp;lt;/code&amp;gt; ====&lt;br /&gt;
&lt;br /&gt;
Sets how often epicEFI calls &amp;lt;code&amp;gt;onTick&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;onCanRx&amp;lt;/code&amp;gt;, in Hz. Default after reset is 10 Hz.&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;Parameters:&#039;&#039;&#039; &amp;lt;code&amp;gt;hz&amp;lt;/code&amp;gt; — clamped to 1–200 Hz&lt;br /&gt;
* &#039;&#039;&#039;Returns:&#039;&#039;&#039; none&lt;br /&gt;
&lt;br /&gt;
==== &amp;lt;code&amp;gt;mcu_standby()&amp;lt;/code&amp;gt; ====&lt;br /&gt;
&lt;br /&gt;
Puts the MCU into standby (low current). Use with care.&lt;br /&gt;
&lt;br /&gt;
==== &amp;lt;code&amp;gt;interpolate(x1, y1, x2, y2, x)&amp;lt;/code&amp;gt; ====&lt;br /&gt;
&lt;br /&gt;
Linear interpolation of &amp;lt;code&amp;gt;x&amp;lt;/code&amp;gt; on the line through &amp;lt;code&amp;gt;(x1, y1)&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;(x2, y2)&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
==== &amp;lt;code&amp;gt;findTableIndex(name)&amp;lt;/code&amp;gt; ====&lt;br /&gt;
&lt;br /&gt;
Returns the index of a script table by human-readable name.&lt;br /&gt;
&lt;br /&gt;
==== &amp;lt;code&amp;gt;table3d(tableIdx, x, y)&amp;lt;/code&amp;gt; ====&lt;br /&gt;
&lt;br /&gt;
Looks up a value from a script 3D table.&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;Parameters&#039;&#039;&#039;&lt;br /&gt;
** &amp;lt;code&amp;gt;tableIdx&amp;lt;/code&amp;gt;: Table index (1–4)&lt;br /&gt;
** &amp;lt;code&amp;gt;x&amp;lt;/code&amp;gt;: X-axis value (often RPM)&lt;br /&gt;
** &amp;lt;code&amp;gt;y&amp;lt;/code&amp;gt;: Y-axis value (often load)&lt;br /&gt;
* &#039;&#039;&#039;Returns:&#039;&#039;&#039; table output value&lt;br /&gt;
&lt;br /&gt;
==== &amp;lt;code&amp;gt;findCurveIndex(name)&amp;lt;/code&amp;gt; ====&lt;br /&gt;
&lt;br /&gt;
Returns the index of a script curve by name.&lt;br /&gt;
&lt;br /&gt;
==== &amp;lt;code&amp;gt;curve(curveIdx, x)&amp;lt;/code&amp;gt; ====&lt;br /&gt;
&lt;br /&gt;
Looks up a value from a script curve.&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;Parameters&#039;&#039;&#039;&lt;br /&gt;
** &amp;lt;code&amp;gt;curveIdx&amp;lt;/code&amp;gt;: Curve index (1-based)&lt;br /&gt;
** &amp;lt;code&amp;gt;x&amp;lt;/code&amp;gt;: Axis value&lt;br /&gt;
&lt;br /&gt;
==== &amp;lt;code&amp;gt;setDebug(index, value)&amp;lt;/code&amp;gt; ====&lt;br /&gt;
&lt;br /&gt;
Sets a debug channel when ECU debug mode is &#039;&#039;&#039;Lua&#039;&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;Parameters:&#039;&#039;&#039; &amp;lt;code&amp;gt;index&amp;lt;/code&amp;gt; 1–7, &amp;lt;code&amp;gt;value&amp;lt;/code&amp;gt; — channel value&lt;br /&gt;
* &#039;&#039;&#039;Returns:&#039;&#039;&#039; none&lt;br /&gt;
&lt;br /&gt;
=== Input ===&lt;br /&gt;
&lt;br /&gt;
==== &amp;lt;code&amp;gt;getSensor(name)&amp;lt;/code&amp;gt; ====&lt;br /&gt;
&lt;br /&gt;
Reads a sensor by name, e.g. &amp;lt;code&amp;gt;getSensor(&amp;quot;AcceleratorPedal&amp;quot;)&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;Parameters:&#039;&#039;&#039; &amp;lt;code&amp;gt;name&amp;lt;/code&amp;gt; — sensor name (same names as TunerStudio Live Data, e.g. &amp;lt;code&amp;gt;TPS&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;CLT&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;RPM&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;MAP&amp;lt;/code&amp;gt;)&lt;br /&gt;
* &#039;&#039;&#039;Returns:&#039;&#039;&#039; reading, or &amp;lt;code&amp;gt;nil&amp;lt;/code&amp;gt; if the sensor is invalid or not configured&lt;br /&gt;
&lt;br /&gt;
==== &amp;lt;code&amp;gt;getSensorByIndex(index)&amp;lt;/code&amp;gt; ====&lt;br /&gt;
&lt;br /&gt;
Reads a sensor by enum index.&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;Returns:&#039;&#039;&#039; reading, or &amp;lt;code&amp;gt;nil&amp;lt;/code&amp;gt; on failure&lt;br /&gt;
&lt;br /&gt;
==== &amp;lt;code&amp;gt;getSensorRaw(index)&amp;lt;/code&amp;gt; ====&lt;br /&gt;
&lt;br /&gt;
Raw sensor value (usually pin voltage before scaling).&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;Returns:&#039;&#039;&#039; raw value, or &amp;lt;code&amp;gt;0&amp;lt;/code&amp;gt; if unsupported / not configured / failed&lt;br /&gt;
&lt;br /&gt;
==== &amp;lt;code&amp;gt;getAuxAnalog(index)&amp;lt;/code&amp;gt; ====&lt;br /&gt;
&lt;br /&gt;
Like &amp;lt;code&amp;gt;getSensorRaw&amp;lt;/code&amp;gt; but for aux analog inputs — always voltage.&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;Parameters:&#039;&#039;&#039; &amp;lt;code&amp;gt;index&amp;lt;/code&amp;gt; 0–3&lt;br /&gt;
* &#039;&#039;&#039;Returns:&#039;&#039;&#039; voltage, or &amp;lt;code&amp;gt;nil&amp;lt;/code&amp;gt; if not configured&lt;br /&gt;
&lt;br /&gt;
==== &amp;lt;code&amp;gt;hasSensor(index)&amp;lt;/code&amp;gt; ====&lt;br /&gt;
&lt;br /&gt;
Whether a sensor slot is configured (valid or not).&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;Returns:&#039;&#039;&#039; boolean&lt;br /&gt;
&lt;br /&gt;
==== &amp;lt;code&amp;gt;getDigital(index)&amp;lt;/code&amp;gt; ====&lt;br /&gt;
&lt;br /&gt;
Reads a built-in digital input.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! Index !! Channel&lt;br /&gt;
|-&lt;br /&gt;
| 0 || Clutch down&lt;br /&gt;
|-&lt;br /&gt;
| 1 || Clutch up&lt;br /&gt;
|-&lt;br /&gt;
| 2 || Brake switch&lt;br /&gt;
|-&lt;br /&gt;
| 3 || AC switch&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
==== &amp;lt;code&amp;gt;getAuxDigital(index)&amp;lt;/code&amp;gt; ====&lt;br /&gt;
&lt;br /&gt;
Reads a user-configured digital input (index 0–7). Configure pins under &#039;&#039;&#039;Lua Digital Aux Inputs&#039;&#039;&#039; in TunerStudio.&lt;br /&gt;
&lt;br /&gt;
==== &amp;lt;code&amp;gt;readPin(pinName)&amp;lt;/code&amp;gt; ====&lt;br /&gt;
&lt;br /&gt;
Reads an MCU pin directly (e.g. &amp;lt;code&amp;gt;&amp;quot;PD15&amp;quot;&amp;lt;/code&amp;gt;). Emergency/debug only — prefer Lua aux inputs for real logic.&lt;br /&gt;
&lt;br /&gt;
=== Output ===&lt;br /&gt;
&lt;br /&gt;
Not the same as Live Data “outputs” or GPPWM.&lt;br /&gt;
&lt;br /&gt;
==== &amp;lt;code&amp;gt;selfStimulateRPM(rpm)&amp;lt;/code&amp;gt; ====&lt;br /&gt;
&lt;br /&gt;
Positive RPM starts injector clicking at that speed; &amp;lt;code&amp;gt;0&amp;lt;/code&amp;gt; stops self-stimulation.&lt;br /&gt;
&lt;br /&gt;
==== &amp;lt;code&amp;gt;startPwm(index, frequency, duty)&amp;lt;/code&amp;gt; ====&lt;br /&gt;
&lt;br /&gt;
Starts PWM on a Lua PWM output. Pin assignment: &#039;&#039;&#039;Lua PWM Outputs&#039;&#039;&#039; in TunerStudio.&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;Parameters&#039;&#039;&#039;&lt;br /&gt;
** &amp;lt;code&amp;gt;index&amp;lt;/code&amp;gt;: 0–7&lt;br /&gt;
** &amp;lt;code&amp;gt;frequency&amp;lt;/code&amp;gt;: 1–1000 Hz&lt;br /&gt;
** &amp;lt;code&amp;gt;duty&amp;lt;/code&amp;gt;: 0.0 = off, 1.0 = full on&lt;br /&gt;
&lt;br /&gt;
==== &amp;lt;code&amp;gt;setPwmDuty(index, duty)&amp;lt;/code&amp;gt; ====&lt;br /&gt;
&lt;br /&gt;
==== &amp;lt;code&amp;gt;setPwmFreq(index, frequency)&amp;lt;/code&amp;gt; ====&lt;br /&gt;
&lt;br /&gt;
==== &amp;lt;code&amp;gt;getGpPwm(index)&amp;lt;/code&amp;gt; ====&lt;br /&gt;
&lt;br /&gt;
Current GPPWM output percent (index 0–3).&lt;br /&gt;
&lt;br /&gt;
==== &amp;lt;code&amp;gt;setLuaGauge(index, value)&amp;lt;/code&amp;gt; ====&lt;br /&gt;
&lt;br /&gt;
Writes a Lua gauge (indices 1–8) for Live Data / logging.&lt;br /&gt;
&lt;br /&gt;
==== &amp;lt;code&amp;gt;setDacVoltage(index, value)&amp;lt;/code&amp;gt; ====&lt;br /&gt;
&lt;br /&gt;
Only on boards with DAC hardware enabled.&lt;br /&gt;
&lt;br /&gt;
== Console commands ==&lt;br /&gt;
&lt;br /&gt;
* &amp;lt;code&amp;gt;luamemory&amp;lt;/code&amp;gt; — Lua memory usage&lt;br /&gt;
* &amp;lt;code&amp;gt;luareset&amp;lt;/code&amp;gt; — reset Lua VM&lt;br /&gt;
&lt;br /&gt;
== Examples ==&lt;br /&gt;
&lt;br /&gt;
Example scripts ship with the firmware under &amp;lt;code&amp;gt;firmware/controllers/lua/examples/&amp;lt;/code&amp;gt; ([https://github.com/epicEFI/epicefi_fw/tree/master/firmware/controllers/lua/examples browse on GitHub]).&lt;br /&gt;
&lt;br /&gt;
Notable examples: &amp;lt;code&amp;gt;honda-bcm.txt&amp;lt;/code&amp;gt; (VSS from CAN / gear detection), &amp;lt;code&amp;gt;ford-focus-ii-pps.txt&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;bmw-idrive.txt&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
=== Timer example ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
t = Timer.new()&lt;br /&gt;
timingAdd = 0&lt;br /&gt;
&lt;br /&gt;
function onTick()&lt;br /&gt;
   auxV = getAuxAnalog(0)&lt;br /&gt;
   tps = getSensor(&amp;quot;TPS&amp;quot;)&lt;br /&gt;
   -- check for nil if aux input is not assigned&lt;br /&gt;
   if auxV &amp;gt; 2 then&lt;br /&gt;
     t:reset()&lt;br /&gt;
   end&lt;br /&gt;
&lt;br /&gt;
   val = t:getElapsedSeconds()&lt;br /&gt;
&lt;br /&gt;
   if t:getElapsedSeconds() &amp;lt; 3 then&lt;br /&gt;
     timingAdd = 10&lt;br /&gt;
   else&lt;br /&gt;
     timingAdd = 0&lt;br /&gt;
   end&lt;br /&gt;
   setTimingAdd(timingAdd)&lt;br /&gt;
&lt;br /&gt;
   print(&#039;Hello analog &#039; .. auxV .. &amp;quot; &amp;quot; .. val)&lt;br /&gt;
end&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== PWM ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
startPwm(0, 100, 0)&lt;br /&gt;
&lt;br /&gt;
function onTick()&lt;br /&gt;
 enable_pump = getSensor(&amp;quot;RPM&amp;quot;) &amp;gt; 700 and getSensor(&amp;quot;BatteryVoltage&amp;quot;) &amp;gt; 13 and getSensor(&amp;quot;VehicleSpeed&amp;quot;) &amp;lt; 60&lt;br /&gt;
 setPwmDuty(0, enable_pump and 1 or 0)&lt;br /&gt;
end&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== CAN transmit ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
function onTick()&lt;br /&gt;
  clt = getSensor(&amp;quot;CLT&amp;quot;)&lt;br /&gt;
  print(&#039;CLT &#039; .. clt)&lt;br /&gt;
  voltage0 = getSensor(&amp;quot;aux0&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
  txPayload = {}&lt;br /&gt;
  txPayload[1] = math.floor(((voltage0/256) - math.floor(voltage0/256))*256)&lt;br /&gt;
  txPayload[2] = math.floor(voltage0 / 256)&lt;br /&gt;
&lt;br /&gt;
  txCan(1, 0x600, 1, txPayload)&lt;br /&gt;
end&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Set sensor value ===&lt;br /&gt;
&lt;br /&gt;
Use standard sensor names from TunerStudio Live Data.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
-- do not configure the same physical input elsewhere&lt;br /&gt;
vssSensor = Sensor.new(&amp;quot;VehicleSpeed&amp;quot;)&lt;br /&gt;
vssSensor:setTimeout(3000)&lt;br /&gt;
function onTick()&lt;br /&gt;
 injectedVssValue = 123.4&lt;br /&gt;
 vssSensor:set(injectedVssValue)&lt;br /&gt;
 valFromSensor = getSensor(&amp;quot;VehicleSpeed&amp;quot;)&lt;br /&gt;
 print(&amp;quot;VSS &amp;quot; .. valFromSensor)&lt;br /&gt;
end&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== CAN receive ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
canRxAdd(0x500)&lt;br /&gt;
canRxAdd(0x570)&lt;br /&gt;
function onCanRx(bus, id, dlc, data)&lt;br /&gt;
 print(&#039;got CAN id=&#039; .. id .. &#039; dlc=&#039; .. dlc)&lt;br /&gt;
 if id == 0x500 then&lt;br /&gt;
  canState = data[1]&lt;br /&gt;
 end&lt;br /&gt;
 if id == 0x570 then&lt;br /&gt;
  mcu_standby()&lt;br /&gt;
 end&lt;br /&gt;
end&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Table / curve ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
tableIndex = findTableIndex(&amp;quot;duty&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
TurbochargerSpeed = getSensor(&amp;quot;TurbochargerSpeed&amp;quot;)&lt;br /&gt;
tps = getSensor(&amp;quot;Tps1&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
dutyCycle = table3d(tableIndex, TurbochargerSpeed, tps)&lt;br /&gt;
&lt;br /&gt;
sparkCutCurve = findCurveIndex(&amp;quot;sparkcut&amp;quot;)&lt;br /&gt;
sparkCutByTorque = curve(sparkCutCurve, torquex)&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== See also ==&lt;br /&gt;
&lt;br /&gt;
* [https://content.epicefi.com/docs/variable_status_selector.html Variable Status Selector] — names, types, and hashes for your firmware&lt;br /&gt;
* [https://github.com/epicEFI/epicefi_fw/tree/master/firmware/controllers/lua/examples Lua examples] — vehicle-specific scripts&lt;br /&gt;
* [http://lua-users.org/wiki/TernaryOperator Lua ternary operator] — Lua has no &amp;lt;code&amp;gt;?:&amp;lt;/code&amp;gt;; use &amp;lt;code&amp;gt;and&amp;lt;/code&amp;gt; / &amp;lt;code&amp;gt;or&amp;lt;/code&amp;gt; patterns&lt;/div&gt;</summary>
		<author><name>Joesphan</name></author>
	</entry>
	<entry>
		<id>https://content.epicefi.com/wiki/index.php?title=Lua&amp;diff=500</id>
		<title>Lua</title>
		<link rel="alternate" type="text/html" href="https://content.epicefi.com/wiki/index.php?title=Lua&amp;diff=500"/>
		<updated>2026-08-18T03:19:08Z</updated>

		<summary type="html">&lt;p&gt;Joesphan: /* Variable names and hashes */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;= Lua Scripting =&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;TL;DR:&#039;&#039;&#039; Connect with TunerStudio, open the &#039;&#039;&#039;Lua&#039;&#039;&#039; 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.&lt;br /&gt;
&lt;br /&gt;
== Introduction ==&lt;br /&gt;
&lt;br /&gt;
epicEFI gives you a lot of flexibility — enough to build user-defined control strategies for primary and auxiliary actuators. Boards with an &#039;&#039;&#039;F7&#039;&#039;&#039; or &#039;&#039;&#039;H7&#039;&#039;&#039; MCU (EpicECU, M144H7, M144F7RED, etc.) have the most headroom for Lua. F4 boards (MEGA100F4, UAEFI) also support Lua but with tighter memory limits.&lt;br /&gt;
&lt;br /&gt;
== Variable names and hashes ==&lt;br /&gt;
&lt;br /&gt;
Calibration names, output channel names, variable types (CONFIG GETTER/SETTER, OUTPC GETTER/SETTER), and their &#039;&#039;&#039;djb2 hashes&#039;&#039;&#039; depend on your exact firmware build. Do not guess — look them up here:&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;[https://content.epicefi.com/docs/variable_status_selector.html Variables Lookup]&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Use this tool when calling &amp;lt;code&amp;gt;getCalibration()&amp;lt;/code&amp;gt; / &amp;lt;code&amp;gt;setCalibration()&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;getOutput()&amp;lt;/code&amp;gt;, configuring user-table axes, or reading/writing variables over EPIC CAN.&lt;br /&gt;
&lt;br /&gt;
== Basics ==&lt;br /&gt;
&lt;br /&gt;
epicEFI provides hooks to interface with the firmware, manipulate its state, and read/write configuration:&lt;br /&gt;
&lt;br /&gt;
* [[#CAN bus|Hooks for CAN bus communications]]&lt;br /&gt;
* [[#Input|Inputs from sensors can be read directly]]. [[#Set sensor value|You can also produce sensor values]] with Lua.&lt;br /&gt;
* [[#Output|ECU general purpose outputs]]&lt;br /&gt;
* [[#Engine control|Aspects of the engine can be controlled directly]]&lt;br /&gt;
* ECU configuration can be accessed (read/write) via [[#getCalibration(name)|&amp;lt;code&amp;gt;getCalibration()&amp;lt;/code&amp;gt;]] and [[#setCalibration(name, value, needEvent)|&amp;lt;code&amp;gt;setCalibration()&amp;lt;/code&amp;gt;]].&lt;br /&gt;
** Valid calibration names for your firmware are listed in the [https://content.epicefi.com/docs/variable_status_selector.html Variable Status Selector].&lt;br /&gt;
* ECU internal state (logic outputs / Live Data values) can be read via [[#getOutput(name)|&amp;lt;code&amp;gt;getOutput()&amp;lt;/code&amp;gt;]], and some can be changed via dedicated setters (e.g. [[#setClutchUpState(value)|&amp;lt;code&amp;gt;setClutchUpState()&amp;lt;/code&amp;gt;]]). See [[#Output|Output]].&lt;br /&gt;
** Valid output names for your firmware are listed in the [https://content.epicefi.com/docs/variable_status_selector.html Variable Status Selector].&lt;br /&gt;
* Hooks to read values from SENT sensors; see [[#SENT protocol|SENT protocol]].&lt;br /&gt;
* Useful helper routines; see [[#Utility|Utility]].&lt;br /&gt;
&lt;br /&gt;
Some example uses are in [[#Examples|Examples]].&lt;br /&gt;
&lt;br /&gt;
== Conventions ==&lt;br /&gt;
&lt;br /&gt;
* The Lua interpreter reports errors when something is wrong; check epicEFI Console / TunerStudio Lua output for errors and &amp;lt;code&amp;gt;print()&amp;lt;/code&amp;gt; output.&lt;br /&gt;
* Unless otherwise noted, all &amp;lt;code&amp;gt;index&amp;lt;/code&amp;gt; parameters are &#039;&#039;&#039;zero-based&#039;&#039;&#039; (first element is index &amp;lt;code&amp;gt;0&amp;lt;/code&amp;gt;).&lt;br /&gt;
&lt;br /&gt;
== Writing Your Script ==&lt;br /&gt;
&lt;br /&gt;
The entire Lua script is loaded at startup. After that, a function named &amp;lt;code&amp;gt;onTick&amp;lt;/code&amp;gt; is called periodically by epicEFI.&lt;br /&gt;
&lt;br /&gt;
Simple startup example:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;lua&amp;quot;&amp;gt;&lt;br /&gt;
print(&#039;Hello Lua startup!&#039;)&lt;br /&gt;
&lt;br /&gt;
function onTick()&lt;br /&gt;
    print(&#039;Hello onTick()&#039;)&lt;br /&gt;
end&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Controlling the tick rate ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;setTickRate(hz)&amp;lt;/code&amp;gt; sets how often epicEFI calls &amp;lt;code&amp;gt;onTick&amp;lt;/code&amp;gt;. 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. &amp;lt;code&amp;gt;onCanRx&amp;lt;/code&amp;gt; runs at the same rate as &amp;lt;code&amp;gt;onTick&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;lua&amp;quot;&amp;gt;&lt;br /&gt;
n = 0&lt;br /&gt;
setTickRate(5) -- 5 Hz&lt;br /&gt;
function onTick()&lt;br /&gt;
    print(&#039;Hello Lua: &#039; .. n)&lt;br /&gt;
    n = n + 1&lt;br /&gt;
end&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Editing scripts ===&lt;br /&gt;
&lt;br /&gt;
An editor with [https://github.com/LuaLS/lua-language-server#install Lua Language Server] (LSP) support makes writing scripts much easier.&lt;br /&gt;
&lt;br /&gt;
== Hooks / function reference ==&lt;br /&gt;
&lt;br /&gt;
=== User settings ===&lt;br /&gt;
&lt;br /&gt;
==== &amp;lt;code&amp;gt;getOutput(name)&amp;lt;/code&amp;gt; ====&lt;br /&gt;
&lt;br /&gt;
Example: &amp;lt;code&amp;gt;getOutput(&amp;quot;clutchUpState&amp;quot;)&amp;lt;/code&amp;gt; or &amp;lt;code&amp;gt;getOutput(&amp;quot;brakePedalState&amp;quot;)&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Valid output names for your firmware: [https://content.epicefi.com/docs/variable_status_selector.html Variable Status Selector].&lt;br /&gt;
&lt;br /&gt;
==== &amp;lt;code&amp;gt;setClutchUpState(value)&amp;lt;/code&amp;gt; ====&lt;br /&gt;
&lt;br /&gt;
==== &amp;lt;code&amp;gt;setBrakePedalState(value)&amp;lt;/code&amp;gt; ====&lt;br /&gt;
&lt;br /&gt;
Use &amp;lt;code&amp;gt;setBrakePedalState&amp;lt;/code&amp;gt; to report a CAN-based brake pedal to epicEFI.&lt;br /&gt;
&lt;br /&gt;
==== &amp;lt;code&amp;gt;setAcRequestState(value)&amp;lt;/code&amp;gt; ====&lt;br /&gt;
&lt;br /&gt;
Use &amp;lt;code&amp;gt;setAcRequestState&amp;lt;/code&amp;gt; to report a CAN-based A/C request.&lt;br /&gt;
&lt;br /&gt;
==== &amp;lt;code&amp;gt;setEtbDisabled(value)&amp;lt;/code&amp;gt; ====&lt;br /&gt;
&lt;br /&gt;
==== &amp;lt;code&amp;gt;setIgnDisabled(value)&amp;lt;/code&amp;gt; ====&lt;br /&gt;
&lt;br /&gt;
Use &amp;lt;code&amp;gt;setIgnDisabled&amp;lt;/code&amp;gt; for cranking safety and other ignition-cut strategies.&lt;br /&gt;
&lt;br /&gt;
==== &amp;lt;code&amp;gt;setAcDisabled(value)&amp;lt;/code&amp;gt; ====&lt;br /&gt;
&lt;br /&gt;
Disable/suppress A/C regardless of how it would otherwise be enabled.&lt;br /&gt;
&lt;br /&gt;
==== &amp;lt;code&amp;gt;getTimeSinceAcToggleMs()&amp;lt;/code&amp;gt; ====&lt;br /&gt;
&lt;br /&gt;
==== &amp;lt;code&amp;gt;getCalibration(name)&amp;lt;/code&amp;gt; ====&lt;br /&gt;
&lt;br /&gt;
Returns the current value of a scalar calibration setting. Example: &amp;lt;code&amp;gt;getCalibration(&amp;quot;cranking.rpm&amp;quot;)&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Full list of valid names: [https://content.epicefi.com/docs/variable_status_selector.html Variable Status Selector].&lt;br /&gt;
&lt;br /&gt;
==== &amp;lt;code&amp;gt;setCalibration(name, value, needEvent)&amp;lt;/code&amp;gt; ====&lt;br /&gt;
&lt;br /&gt;
Sets a calibration value. Optionally fires a calibration change event depending on &amp;lt;code&amp;gt;needEvent&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Example: &amp;lt;code&amp;gt;setCalibration(&amp;quot;cranking.rpm&amp;quot;, 900, false)&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== &amp;lt;code&amp;gt;burnconfig&amp;lt;/code&amp;gt; ====&lt;br /&gt;
&lt;br /&gt;
Schedules a write of current calibration to flash once the engine is stopped.&lt;br /&gt;
&lt;br /&gt;
==== &amp;lt;code&amp;gt;findSetting(name, defaultValue)&amp;lt;/code&amp;gt; ====&lt;br /&gt;
&lt;br /&gt;
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.&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;Parameters&#039;&#039;&#039;&lt;br /&gt;
** &amp;lt;code&amp;gt;name&amp;lt;/code&amp;gt;: Variable name (matches the configuration field name)&lt;br /&gt;
** &amp;lt;code&amp;gt;defaultValue&amp;lt;/code&amp;gt;: Returned if the setting is not found&lt;br /&gt;
&lt;br /&gt;
=== &amp;lt;code&amp;gt;isFirmwareError&amp;lt;/code&amp;gt; ===&lt;br /&gt;
&lt;br /&gt;
Returns &amp;lt;code&amp;gt;true&amp;lt;/code&amp;gt; if the ECU is in a critical/fatal error state.&lt;br /&gt;
&lt;br /&gt;
=== Engine control ===&lt;br /&gt;
&lt;br /&gt;
==== &amp;lt;code&amp;gt;startCrankingEngine()&amp;lt;/code&amp;gt; ====&lt;br /&gt;
&lt;br /&gt;
Starts cranking as if the physical start button were pressed.&lt;br /&gt;
&lt;br /&gt;
==== &amp;lt;code&amp;gt;stopEngine()&amp;lt;/code&amp;gt; ====&lt;br /&gt;
&lt;br /&gt;
==== &amp;lt;code&amp;gt;isEngineStopRequested()&amp;lt;/code&amp;gt; ====&lt;br /&gt;
&lt;br /&gt;
Returns &amp;lt;code&amp;gt;true&amp;lt;/code&amp;gt; if an engine stop was requested (by Lua or the start/stop button) within the last five seconds.&lt;br /&gt;
&lt;br /&gt;
==== &amp;lt;code&amp;gt;setLaunchTrigger&amp;lt;/code&amp;gt; ====&lt;br /&gt;
&lt;br /&gt;
==== &amp;lt;code&amp;gt;setSparkSkipRatio(ratio)&amp;lt;/code&amp;gt; ====&lt;br /&gt;
&lt;br /&gt;
* &amp;lt;code&amp;gt;setSparkSkipRatio(0)&amp;lt;/code&amp;gt; — skip 0% of ignition events (no skip)&lt;br /&gt;
* &amp;lt;code&amp;gt;setSparkSkipRatio(0.5)&amp;lt;/code&amp;gt; — skip half of ignition events (never two consecutive skips)&lt;br /&gt;
&lt;br /&gt;
Useful for torque reduction.&lt;br /&gt;
&lt;br /&gt;
==== &amp;lt;code&amp;gt;setSparkHardSkipRatio(ratio)&amp;lt;/code&amp;gt; ====&lt;br /&gt;
&lt;br /&gt;
* &amp;lt;code&amp;gt;setSparkHardSkipRatio(0)&amp;lt;/code&amp;gt; — no skip&lt;br /&gt;
* &amp;lt;code&amp;gt;setSparkHardSkipRatio(0.75)&amp;lt;/code&amp;gt; — skip 75% of ignition events&lt;br /&gt;
&lt;br /&gt;
==== &amp;lt;code&amp;gt;setIdleAdd(percent)&amp;lt;/code&amp;gt; ====&lt;br /&gt;
&lt;br /&gt;
Percent added to idle (including open loop).&lt;br /&gt;
&lt;br /&gt;
==== &amp;lt;code&amp;gt;setFuelAdd(amount)&amp;lt;/code&amp;gt; ====&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;Currently not functional.&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Fuel mass to add to injection, scaled by [[#setFuelMult(coeff)|&amp;lt;code&amp;gt;setFuelMult()&amp;lt;/code&amp;gt;]]; starts at 0.&lt;br /&gt;
&lt;br /&gt;
==== &amp;lt;code&amp;gt;setFuelMult(coeff)&amp;lt;/code&amp;gt; ====&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;Currently not functional.&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Multiplier for added fuel mass; starts at 1.0.&lt;br /&gt;
&lt;br /&gt;
==== &amp;lt;code&amp;gt;setBoostTargetAdd(amount)&amp;lt;/code&amp;gt; ====&lt;br /&gt;
&lt;br /&gt;
Additive offset for closed-loop boost target.&lt;br /&gt;
&lt;br /&gt;
==== &amp;lt;code&amp;gt;setBoostTargetMult(coeff)&amp;lt;/code&amp;gt; ====&lt;br /&gt;
&lt;br /&gt;
Multiplier for closed-loop boost target.&lt;br /&gt;
&lt;br /&gt;
==== &amp;lt;code&amp;gt;setBoostDutyAdd(amount)&amp;lt;/code&amp;gt; ====&lt;br /&gt;
&lt;br /&gt;
Additive offset for open-loop boost duty.&lt;br /&gt;
&lt;br /&gt;
==== &amp;lt;code&amp;gt;setTimingAdd(angle)&amp;lt;/code&amp;gt; ====&lt;br /&gt;
&lt;br /&gt;
Use negative values to retard timing.&lt;br /&gt;
&lt;br /&gt;
==== &amp;lt;code&amp;gt;setTimingMult(coeff)&amp;lt;/code&amp;gt; ====&lt;br /&gt;
&lt;br /&gt;
Useful for torque reduction.&lt;br /&gt;
&lt;br /&gt;
==== &amp;lt;code&amp;gt;setEtbAdd(percent)&amp;lt;/code&amp;gt; ====&lt;br /&gt;
&lt;br /&gt;
ETB adder as a percent of wide-open: e.g. &amp;lt;code&amp;gt;10&amp;lt;/code&amp;gt; adds +10%. Static offset on top of the computed position (TPS 5% + &amp;lt;code&amp;gt;10&amp;lt;/code&amp;gt; → 15% ETB command).&lt;br /&gt;
&lt;br /&gt;
Useful for torque reduction.&lt;br /&gt;
&lt;br /&gt;
=== Timer ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;yourTimer = Timer.new()&amp;lt;/code&amp;gt; creates a timer object.&lt;br /&gt;
&lt;br /&gt;
==== &amp;lt;code&amp;gt;reset&amp;lt;/code&amp;gt; ====&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;yourTimer:reset()&amp;lt;/code&amp;gt; resets the timer.&lt;br /&gt;
&lt;br /&gt;
==== &amp;lt;code&amp;gt;getElapsedSeconds&amp;lt;/code&amp;gt; ====&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;yourTimer:getElapsedSeconds()&amp;lt;/code&amp;gt; returns seconds since last reset.&lt;br /&gt;
&lt;br /&gt;
==== &amp;lt;code&amp;gt;getTsButtonCount&amp;lt;/code&amp;gt; ====&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;getTsButtonCount(X)&amp;lt;/code&amp;gt; returns how many times Lua button &#039;&#039;&#039;X&#039;&#039;&#039; was pressed in TunerStudio. See &amp;lt;code&amp;gt;ts-button-example.lua&amp;lt;/code&amp;gt; in the firmware examples.&lt;br /&gt;
&lt;br /&gt;
=== CAN bus ===&lt;br /&gt;
&lt;br /&gt;
==== &amp;lt;code&amp;gt;enableCanTx(isEnabled)&amp;lt;/code&amp;gt; ====&lt;br /&gt;
&lt;br /&gt;
Enabled by default. Use &amp;lt;code&amp;gt;enableCanTx(false)&amp;lt;/code&amp;gt; to suppress CAN transmit from Lua.&lt;br /&gt;
&lt;br /&gt;
==== &amp;lt;code&amp;gt;txCan(bus, ID, isExt, payload)&amp;lt;/code&amp;gt; ====&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;Parameters&#039;&#039;&#039;&lt;br /&gt;
** &amp;lt;code&amp;gt;bus&amp;lt;/code&amp;gt;: Hardware CAN bus index — typically &amp;lt;code&amp;gt;1&amp;lt;/code&amp;gt; on single-CAN boards; &amp;lt;code&amp;gt;1&amp;lt;/code&amp;gt; or &amp;lt;code&amp;gt;2&amp;lt;/code&amp;gt; on dual-CAN boards (EpicECU, M144H7, etc.)&lt;br /&gt;
** &amp;lt;code&amp;gt;isExt&amp;lt;/code&amp;gt;: &amp;lt;code&amp;gt;0&amp;lt;/code&amp;gt; for 11-bit standard ID&lt;br /&gt;
&lt;br /&gt;
==== &amp;lt;code&amp;gt;canRxAdd(id)&amp;lt;/code&amp;gt; ====&lt;br /&gt;
&lt;br /&gt;
==== &amp;lt;code&amp;gt;canRxAdd(bus, id)&amp;lt;/code&amp;gt; ====&lt;br /&gt;
&lt;br /&gt;
==== &amp;lt;code&amp;gt;canRxAdd(id, callback)&amp;lt;/code&amp;gt; ====&lt;br /&gt;
&lt;br /&gt;
==== &amp;lt;code&amp;gt;canRxAdd(bus, id, callback)&amp;lt;/code&amp;gt; ====&lt;br /&gt;
&lt;br /&gt;
==== &amp;lt;code&amp;gt;canRxAddMask(id, mask)&amp;lt;/code&amp;gt; ====&lt;br /&gt;
&lt;br /&gt;
==== &amp;lt;code&amp;gt;canRxAddMask(bus, id, mask)&amp;lt;/code&amp;gt; ====&lt;br /&gt;
&lt;br /&gt;
==== &amp;lt;code&amp;gt;canRxAddMask(id, mask, callback)&amp;lt;/code&amp;gt; ====&lt;br /&gt;
&lt;br /&gt;
==== &amp;lt;code&amp;gt;canRxAddMask(bus, id, mask, callback)&amp;lt;/code&amp;gt; ====&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;Parameters&#039;&#039;&#039;&lt;br /&gt;
** &amp;lt;code&amp;gt;id&amp;lt;/code&amp;gt;: CAN ID to listen for&lt;br /&gt;
** &amp;lt;code&amp;gt;mask&amp;lt;/code&amp;gt;: Applied to the received ID before comparison. Example: id &amp;lt;code&amp;gt;3&amp;lt;/code&amp;gt; and mask &amp;lt;code&amp;gt;0xFF&amp;lt;/code&amp;gt; matches any frame whose low 8 bits are &amp;lt;code&amp;gt;3&amp;lt;/code&amp;gt;. Omit the mask to match exactly one ID.&lt;br /&gt;
** &amp;lt;code&amp;gt;bus&amp;lt;/code&amp;gt;: Hardware CAN bus index. If omitted, frames from any bus are received.&lt;br /&gt;
** &amp;lt;code&amp;gt;callback&amp;lt;/code&amp;gt;: Function called when a matching frame arrives. If omitted, &amp;lt;code&amp;gt;onCanRx&amp;lt;/code&amp;gt; is used.&lt;br /&gt;
&lt;br /&gt;
CAN RX callback shape:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;lua&amp;quot;&amp;gt;&lt;br /&gt;
function onCanRx(bus, id, dlc, data)&lt;br /&gt;
    -- handle frame&lt;br /&gt;
end&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
For high-throughput RX, see &amp;lt;code&amp;gt;enableCanRxWorkaround()&amp;lt;/code&amp;gt; in the firmware examples.&lt;br /&gt;
&lt;br /&gt;
=== SENT protocol ===&lt;br /&gt;
&lt;br /&gt;
==== &amp;lt;code&amp;gt;getSentValue(index)&amp;lt;/code&amp;gt; ====&lt;br /&gt;
&lt;br /&gt;
==== &amp;lt;code&amp;gt;getSentValues(index)&amp;lt;/code&amp;gt; ====&lt;br /&gt;
&lt;br /&gt;
=== PID ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;deltaTime&amp;lt;/code&amp;gt; is measured automatically between successive &amp;lt;code&amp;gt;pid:get&amp;lt;/code&amp;gt; calls.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;lua&amp;quot;&amp;gt;&lt;br /&gt;
-- p, i, d, min, max&lt;br /&gt;
pid = Pid.new(2, 0, 0, -100, 100)&lt;br /&gt;
pid:setOffset(0.3)&lt;br /&gt;
pid:get(target, input)&lt;br /&gt;
pid:reset()&lt;br /&gt;
&lt;br /&gt;
industrialPid = IndustrialPid.new(2, 0, 0, -100, 100)&lt;br /&gt;
industrialPid:setOffset(0.3)&lt;br /&gt;
industrialPid:setDerivativeFilterLoss(0.3)&lt;br /&gt;
industrialPid:setAntiwindupFreq(0.3)&lt;br /&gt;
industrialPid:get(target, input)&lt;br /&gt;
industrialPid:reset()&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Utility ===&lt;br /&gt;
&lt;br /&gt;
==== &amp;lt;code&amp;gt;print(msg)&amp;lt;/code&amp;gt; ====&lt;br /&gt;
&lt;br /&gt;
Prints a line to the ECU log.&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;Parameters:&#039;&#039;&#039; &amp;lt;code&amp;gt;msg&amp;lt;/code&amp;gt; — string or number&lt;br /&gt;
* &#039;&#039;&#039;Returns:&#039;&#039;&#039; none&lt;br /&gt;
&lt;br /&gt;
==== &amp;lt;code&amp;gt;vin(index)&amp;lt;/code&amp;gt; ====&lt;br /&gt;
&lt;br /&gt;
Returns the VIN character at the given zero-based index.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Example:&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;lua&amp;quot;&amp;gt;&lt;br /&gt;
n = 5.5&lt;br /&gt;
print(&#039;Hello Lua, number is: &#039; .. n)&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Output: &amp;lt;code&amp;gt;Hello Lua, number is: 5.5&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== &amp;lt;code&amp;gt;setTickRate(hz)&amp;lt;/code&amp;gt; ====&lt;br /&gt;
&lt;br /&gt;
Sets how often epicEFI calls &amp;lt;code&amp;gt;onTick&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;onCanRx&amp;lt;/code&amp;gt;, in Hz. Default after reset is 10 Hz.&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;Parameters:&#039;&#039;&#039; &amp;lt;code&amp;gt;hz&amp;lt;/code&amp;gt; — clamped to 1–200 Hz&lt;br /&gt;
* &#039;&#039;&#039;Returns:&#039;&#039;&#039; none&lt;br /&gt;
&lt;br /&gt;
==== &amp;lt;code&amp;gt;mcu_standby()&amp;lt;/code&amp;gt; ====&lt;br /&gt;
&lt;br /&gt;
Puts the MCU into standby (low current). Use with care.&lt;br /&gt;
&lt;br /&gt;
==== &amp;lt;code&amp;gt;interpolate(x1, y1, x2, y2, x)&amp;lt;/code&amp;gt; ====&lt;br /&gt;
&lt;br /&gt;
Linear interpolation of &amp;lt;code&amp;gt;x&amp;lt;/code&amp;gt; on the line through &amp;lt;code&amp;gt;(x1, y1)&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;(x2, y2)&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
==== &amp;lt;code&amp;gt;findTableIndex(name)&amp;lt;/code&amp;gt; ====&lt;br /&gt;
&lt;br /&gt;
Returns the index of a script table by human-readable name.&lt;br /&gt;
&lt;br /&gt;
==== &amp;lt;code&amp;gt;table3d(tableIdx, x, y)&amp;lt;/code&amp;gt; ====&lt;br /&gt;
&lt;br /&gt;
Looks up a value from a script 3D table.&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;Parameters&#039;&#039;&#039;&lt;br /&gt;
** &amp;lt;code&amp;gt;tableIdx&amp;lt;/code&amp;gt;: Table index (1–4)&lt;br /&gt;
** &amp;lt;code&amp;gt;x&amp;lt;/code&amp;gt;: X-axis value (often RPM)&lt;br /&gt;
** &amp;lt;code&amp;gt;y&amp;lt;/code&amp;gt;: Y-axis value (often load)&lt;br /&gt;
* &#039;&#039;&#039;Returns:&#039;&#039;&#039; table output value&lt;br /&gt;
&lt;br /&gt;
==== &amp;lt;code&amp;gt;findCurveIndex(name)&amp;lt;/code&amp;gt; ====&lt;br /&gt;
&lt;br /&gt;
Returns the index of a script curve by name.&lt;br /&gt;
&lt;br /&gt;
==== &amp;lt;code&amp;gt;curve(curveIdx, x)&amp;lt;/code&amp;gt; ====&lt;br /&gt;
&lt;br /&gt;
Looks up a value from a script curve.&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;Parameters&#039;&#039;&#039;&lt;br /&gt;
** &amp;lt;code&amp;gt;curveIdx&amp;lt;/code&amp;gt;: Curve index (1-based)&lt;br /&gt;
** &amp;lt;code&amp;gt;x&amp;lt;/code&amp;gt;: Axis value&lt;br /&gt;
&lt;br /&gt;
==== &amp;lt;code&amp;gt;setDebug(index, value)&amp;lt;/code&amp;gt; ====&lt;br /&gt;
&lt;br /&gt;
Sets a debug channel when ECU debug mode is &#039;&#039;&#039;Lua&#039;&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;Parameters:&#039;&#039;&#039; &amp;lt;code&amp;gt;index&amp;lt;/code&amp;gt; 1–7, &amp;lt;code&amp;gt;value&amp;lt;/code&amp;gt; — channel value&lt;br /&gt;
* &#039;&#039;&#039;Returns:&#039;&#039;&#039; none&lt;br /&gt;
&lt;br /&gt;
=== Input ===&lt;br /&gt;
&lt;br /&gt;
==== &amp;lt;code&amp;gt;getSensor(name)&amp;lt;/code&amp;gt; ====&lt;br /&gt;
&lt;br /&gt;
Reads a sensor by name, e.g. &amp;lt;code&amp;gt;getSensor(&amp;quot;AcceleratorPedal&amp;quot;)&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;Parameters:&#039;&#039;&#039; &amp;lt;code&amp;gt;name&amp;lt;/code&amp;gt; — sensor name (same names as TunerStudio Live Data, e.g. &amp;lt;code&amp;gt;TPS&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;CLT&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;RPM&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;MAP&amp;lt;/code&amp;gt;)&lt;br /&gt;
* &#039;&#039;&#039;Returns:&#039;&#039;&#039; reading, or &amp;lt;code&amp;gt;nil&amp;lt;/code&amp;gt; if the sensor is invalid or not configured&lt;br /&gt;
&lt;br /&gt;
==== &amp;lt;code&amp;gt;getSensorByIndex(index)&amp;lt;/code&amp;gt; ====&lt;br /&gt;
&lt;br /&gt;
Reads a sensor by enum index.&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;Returns:&#039;&#039;&#039; reading, or &amp;lt;code&amp;gt;nil&amp;lt;/code&amp;gt; on failure&lt;br /&gt;
&lt;br /&gt;
==== &amp;lt;code&amp;gt;getSensorRaw(index)&amp;lt;/code&amp;gt; ====&lt;br /&gt;
&lt;br /&gt;
Raw sensor value (usually pin voltage before scaling).&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;Returns:&#039;&#039;&#039; raw value, or &amp;lt;code&amp;gt;0&amp;lt;/code&amp;gt; if unsupported / not configured / failed&lt;br /&gt;
&lt;br /&gt;
==== &amp;lt;code&amp;gt;getAuxAnalog(index)&amp;lt;/code&amp;gt; ====&lt;br /&gt;
&lt;br /&gt;
Like &amp;lt;code&amp;gt;getSensorRaw&amp;lt;/code&amp;gt; but for aux analog inputs — always voltage.&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;Parameters:&#039;&#039;&#039; &amp;lt;code&amp;gt;index&amp;lt;/code&amp;gt; 0–3&lt;br /&gt;
* &#039;&#039;&#039;Returns:&#039;&#039;&#039; voltage, or &amp;lt;code&amp;gt;nil&amp;lt;/code&amp;gt; if not configured&lt;br /&gt;
&lt;br /&gt;
==== &amp;lt;code&amp;gt;hasSensor(index)&amp;lt;/code&amp;gt; ====&lt;br /&gt;
&lt;br /&gt;
Whether a sensor slot is configured (valid or not).&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;Returns:&#039;&#039;&#039; boolean&lt;br /&gt;
&lt;br /&gt;
==== &amp;lt;code&amp;gt;getDigital(index)&amp;lt;/code&amp;gt; ====&lt;br /&gt;
&lt;br /&gt;
Reads a built-in digital input.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! Index !! Channel&lt;br /&gt;
|-&lt;br /&gt;
| 0 || Clutch down&lt;br /&gt;
|-&lt;br /&gt;
| 1 || Clutch up&lt;br /&gt;
|-&lt;br /&gt;
| 2 || Brake switch&lt;br /&gt;
|-&lt;br /&gt;
| 3 || AC switch&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
==== &amp;lt;code&amp;gt;getAuxDigital(index)&amp;lt;/code&amp;gt; ====&lt;br /&gt;
&lt;br /&gt;
Reads a user-configured digital input (index 0–7). Configure pins under &#039;&#039;&#039;Lua Digital Aux Inputs&#039;&#039;&#039; in TunerStudio.&lt;br /&gt;
&lt;br /&gt;
==== &amp;lt;code&amp;gt;readPin(pinName)&amp;lt;/code&amp;gt; ====&lt;br /&gt;
&lt;br /&gt;
Reads an MCU pin directly (e.g. &amp;lt;code&amp;gt;&amp;quot;PD15&amp;quot;&amp;lt;/code&amp;gt;). Emergency/debug only — prefer Lua aux inputs for real logic.&lt;br /&gt;
&lt;br /&gt;
=== Output ===&lt;br /&gt;
&lt;br /&gt;
Not the same as Live Data “outputs” or GPPWM.&lt;br /&gt;
&lt;br /&gt;
==== &amp;lt;code&amp;gt;selfStimulateRPM(rpm)&amp;lt;/code&amp;gt; ====&lt;br /&gt;
&lt;br /&gt;
Positive RPM starts injector clicking at that speed; &amp;lt;code&amp;gt;0&amp;lt;/code&amp;gt; stops self-stimulation.&lt;br /&gt;
&lt;br /&gt;
==== &amp;lt;code&amp;gt;startPwm(index, frequency, duty)&amp;lt;/code&amp;gt; ====&lt;br /&gt;
&lt;br /&gt;
Starts PWM on a Lua PWM output. Pin assignment: &#039;&#039;&#039;Lua PWM Outputs&#039;&#039;&#039; in TunerStudio.&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;Parameters&#039;&#039;&#039;&lt;br /&gt;
** &amp;lt;code&amp;gt;index&amp;lt;/code&amp;gt;: 0–7&lt;br /&gt;
** &amp;lt;code&amp;gt;frequency&amp;lt;/code&amp;gt;: 1–1000 Hz&lt;br /&gt;
** &amp;lt;code&amp;gt;duty&amp;lt;/code&amp;gt;: 0.0 = off, 1.0 = full on&lt;br /&gt;
&lt;br /&gt;
==== &amp;lt;code&amp;gt;setPwmDuty(index, duty)&amp;lt;/code&amp;gt; ====&lt;br /&gt;
&lt;br /&gt;
==== &amp;lt;code&amp;gt;setPwmFreq(index, frequency)&amp;lt;/code&amp;gt; ====&lt;br /&gt;
&lt;br /&gt;
==== &amp;lt;code&amp;gt;getGpPwm(index)&amp;lt;/code&amp;gt; ====&lt;br /&gt;
&lt;br /&gt;
Current GPPWM output percent (index 0–3).&lt;br /&gt;
&lt;br /&gt;
==== &amp;lt;code&amp;gt;setLuaGauge(index, value)&amp;lt;/code&amp;gt; ====&lt;br /&gt;
&lt;br /&gt;
Writes a Lua gauge (indices 1–8) for Live Data / logging.&lt;br /&gt;
&lt;br /&gt;
==== &amp;lt;code&amp;gt;setDacVoltage(index, value)&amp;lt;/code&amp;gt; ====&lt;br /&gt;
&lt;br /&gt;
Only on boards with DAC hardware enabled.&lt;br /&gt;
&lt;br /&gt;
== Console commands ==&lt;br /&gt;
&lt;br /&gt;
* &amp;lt;code&amp;gt;luamemory&amp;lt;/code&amp;gt; — Lua memory usage&lt;br /&gt;
* &amp;lt;code&amp;gt;luareset&amp;lt;/code&amp;gt; — reset Lua VM&lt;br /&gt;
&lt;br /&gt;
== Examples ==&lt;br /&gt;
&lt;br /&gt;
Example scripts ship with the firmware under &amp;lt;code&amp;gt;firmware/controllers/lua/examples/&amp;lt;/code&amp;gt; ([https://github.com/epicEFI/epicefi_fw/tree/master/firmware/controllers/lua/examples browse on GitHub]).&lt;br /&gt;
&lt;br /&gt;
Notable examples: &amp;lt;code&amp;gt;honda-bcm.txt&amp;lt;/code&amp;gt; (VSS from CAN / gear detection), &amp;lt;code&amp;gt;ford-focus-ii-pps.txt&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;bmw-idrive.txt&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
=== Timer example ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;lua&amp;quot;&amp;gt;&lt;br /&gt;
t = Timer.new()&lt;br /&gt;
timingAdd = 0&lt;br /&gt;
&lt;br /&gt;
function onTick()&lt;br /&gt;
   auxV = getAuxAnalog(0)&lt;br /&gt;
   tps = getSensor(&amp;quot;TPS&amp;quot;)&lt;br /&gt;
   -- check for nil if aux input is not assigned&lt;br /&gt;
   if auxV &amp;gt; 2 then&lt;br /&gt;
     t:reset()&lt;br /&gt;
   end&lt;br /&gt;
&lt;br /&gt;
   val = t:getElapsedSeconds()&lt;br /&gt;
&lt;br /&gt;
   if t:getElapsedSeconds() &amp;lt; 3 then&lt;br /&gt;
     timingAdd = 10&lt;br /&gt;
   else&lt;br /&gt;
     timingAdd = 0&lt;br /&gt;
   end&lt;br /&gt;
   setTimingAdd(timingAdd)&lt;br /&gt;
&lt;br /&gt;
   print(&#039;Hello analog &#039; .. auxV .. &amp;quot; &amp;quot; .. val)&lt;br /&gt;
end&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== PWM ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;lua&amp;quot;&amp;gt;&lt;br /&gt;
startPwm(0, 100, 0)&lt;br /&gt;
&lt;br /&gt;
function onTick()&lt;br /&gt;
 enable_pump = getSensor(&amp;quot;RPM&amp;quot;) &amp;gt; 700 and getSensor(&amp;quot;BatteryVoltage&amp;quot;) &amp;gt; 13 and getSensor(&amp;quot;VehicleSpeed&amp;quot;) &amp;lt; 60&lt;br /&gt;
 setPwmDuty(0, enable_pump and 1 or 0)&lt;br /&gt;
end&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== CAN transmit ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;lua&amp;quot;&amp;gt;&lt;br /&gt;
function onTick()&lt;br /&gt;
  clt = getSensor(&amp;quot;CLT&amp;quot;)&lt;br /&gt;
  print(&#039;CLT &#039; .. clt)&lt;br /&gt;
  voltage0 = getSensor(&amp;quot;aux0&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
  txPayload = {}&lt;br /&gt;
  txPayload[1] = math.floor(((voltage0/256) - math.floor(voltage0/256))*256)&lt;br /&gt;
  txPayload[2] = math.floor(voltage0 / 256)&lt;br /&gt;
&lt;br /&gt;
  txCan(1, 0x600, 1, txPayload)&lt;br /&gt;
end&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Set sensor value ===&lt;br /&gt;
&lt;br /&gt;
Use standard sensor names from TunerStudio Live Data.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;lua&amp;quot;&amp;gt;&lt;br /&gt;
-- do not configure the same physical input elsewhere&lt;br /&gt;
vssSensor = Sensor.new(&amp;quot;VehicleSpeed&amp;quot;)&lt;br /&gt;
vssSensor:setTimeout(3000)&lt;br /&gt;
function onTick()&lt;br /&gt;
 injectedVssValue = 123.4&lt;br /&gt;
 vssSensor:set(injectedVssValue)&lt;br /&gt;
 valFromSensor = getSensor(&amp;quot;VehicleSpeed&amp;quot;)&lt;br /&gt;
 print(&amp;quot;VSS &amp;quot; .. valFromSensor)&lt;br /&gt;
end&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== CAN receive ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;lua&amp;quot;&amp;gt;&lt;br /&gt;
canRxAdd(0x500)&lt;br /&gt;
canRxAdd(0x570)&lt;br /&gt;
function onCanRx(bus, id, dlc, data)&lt;br /&gt;
 print(&#039;got CAN id=&#039; .. id .. &#039; dlc=&#039; .. dlc)&lt;br /&gt;
 if id == 0x500 then&lt;br /&gt;
  canState = data[1]&lt;br /&gt;
 end&lt;br /&gt;
 if id == 0x570 then&lt;br /&gt;
  mcu_standby()&lt;br /&gt;
 end&lt;br /&gt;
end&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Table / curve ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;lua&amp;quot;&amp;gt;&lt;br /&gt;
tableIndex = findTableIndex(&amp;quot;duty&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
TurbochargerSpeed = getSensor(&amp;quot;TurbochargerSpeed&amp;quot;)&lt;br /&gt;
tps = getSensor(&amp;quot;Tps1&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
dutyCycle = table3d(tableIndex, TurbochargerSpeed, tps)&lt;br /&gt;
&lt;br /&gt;
sparkCutCurve = findCurveIndex(&amp;quot;sparkcut&amp;quot;)&lt;br /&gt;
sparkCutByTorque = curve(sparkCutCurve, torquex)&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== See also ==&lt;br /&gt;
&lt;br /&gt;
* [https://content.epicefi.com/docs/variable_status_selector.html Variable Status Selector] — names, types, and hashes for your firmware&lt;br /&gt;
* [https://github.com/epicEFI/epicefi_fw/tree/master/firmware/controllers/lua/examples Lua examples] — vehicle-specific scripts&lt;br /&gt;
* [http://lua-users.org/wiki/TernaryOperator Lua ternary operator] — Lua has no &amp;lt;code&amp;gt;?:&amp;lt;/code&amp;gt;; use &amp;lt;code&amp;gt;and&amp;lt;/code&amp;gt; / &amp;lt;code&amp;gt;or&amp;lt;/code&amp;gt; patterns&lt;/div&gt;</summary>
		<author><name>Joesphan</name></author>
	</entry>
	<entry>
		<id>https://content.epicefi.com/wiki/index.php?title=Lua&amp;diff=499</id>
		<title>Lua</title>
		<link rel="alternate" type="text/html" href="https://content.epicefi.com/wiki/index.php?title=Lua&amp;diff=499"/>
		<updated>2026-08-18T03:17:15Z</updated>

		<summary type="html">&lt;p&gt;Joesphan: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;= Lua Scripting =&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;TL;DR:&#039;&#039;&#039; Connect with TunerStudio, open the &#039;&#039;&#039;Lua&#039;&#039;&#039; 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.&lt;br /&gt;
&lt;br /&gt;
== Introduction ==&lt;br /&gt;
&lt;br /&gt;
epicEFI gives you a lot of flexibility — enough to build user-defined control strategies for primary and auxiliary actuators. Boards with an &#039;&#039;&#039;F7&#039;&#039;&#039; or &#039;&#039;&#039;H7&#039;&#039;&#039; MCU (EpicECU, M144H7, M144F7RED, etc.) have the most headroom for Lua. F4 boards (MEGA100F4, UAEFI) also support Lua but with tighter memory limits.&lt;br /&gt;
&lt;br /&gt;
== Variable names and hashes ==&lt;br /&gt;
&lt;br /&gt;
Calibration names, output channel names, variable types (CONFIG GETTER/SETTER, OUTPC GETTER/SETTER), and their &#039;&#039;&#039;djb2 hashes&#039;&#039;&#039; depend on your exact firmware build. Do not guess — look them up here:&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;[https://content.epicefi.com/docs/variable_status_selector.html Variable Status Selector]&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Use this tool when calling &amp;lt;code&amp;gt;getCalibration()&amp;lt;/code&amp;gt; / &amp;lt;code&amp;gt;setCalibration()&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;getOutput()&amp;lt;/code&amp;gt;, configuring user-table axes, or reading/writing variables over EPIC CAN.&lt;br /&gt;
&lt;br /&gt;
== Basics ==&lt;br /&gt;
&lt;br /&gt;
epicEFI provides hooks to interface with the firmware, manipulate its state, and read/write configuration:&lt;br /&gt;
&lt;br /&gt;
* [[#CAN bus|Hooks for CAN bus communications]]&lt;br /&gt;
* [[#Input|Inputs from sensors can be read directly]]. [[#Set sensor value|You can also produce sensor values]] with Lua.&lt;br /&gt;
* [[#Output|ECU general purpose outputs]]&lt;br /&gt;
* [[#Engine control|Aspects of the engine can be controlled directly]]&lt;br /&gt;
* ECU configuration can be accessed (read/write) via [[#getCalibration(name)|&amp;lt;code&amp;gt;getCalibration()&amp;lt;/code&amp;gt;]] and [[#setCalibration(name, value, needEvent)|&amp;lt;code&amp;gt;setCalibration()&amp;lt;/code&amp;gt;]].&lt;br /&gt;
** Valid calibration names for your firmware are listed in the [https://content.epicefi.com/docs/variable_status_selector.html Variable Status Selector].&lt;br /&gt;
* ECU internal state (logic outputs / Live Data values) can be read via [[#getOutput(name)|&amp;lt;code&amp;gt;getOutput()&amp;lt;/code&amp;gt;]], and some can be changed via dedicated setters (e.g. [[#setClutchUpState(value)|&amp;lt;code&amp;gt;setClutchUpState()&amp;lt;/code&amp;gt;]]). See [[#Output|Output]].&lt;br /&gt;
** Valid output names for your firmware are listed in the [https://content.epicefi.com/docs/variable_status_selector.html Variable Status Selector].&lt;br /&gt;
* Hooks to read values from SENT sensors; see [[#SENT protocol|SENT protocol]].&lt;br /&gt;
* Useful helper routines; see [[#Utility|Utility]].&lt;br /&gt;
&lt;br /&gt;
Some example uses are in [[#Examples|Examples]].&lt;br /&gt;
&lt;br /&gt;
== Conventions ==&lt;br /&gt;
&lt;br /&gt;
* The Lua interpreter reports errors when something is wrong; check epicEFI Console / TunerStudio Lua output for errors and &amp;lt;code&amp;gt;print()&amp;lt;/code&amp;gt; output.&lt;br /&gt;
* Unless otherwise noted, all &amp;lt;code&amp;gt;index&amp;lt;/code&amp;gt; parameters are &#039;&#039;&#039;zero-based&#039;&#039;&#039; (first element is index &amp;lt;code&amp;gt;0&amp;lt;/code&amp;gt;).&lt;br /&gt;
&lt;br /&gt;
== Writing Your Script ==&lt;br /&gt;
&lt;br /&gt;
The entire Lua script is loaded at startup. After that, a function named &amp;lt;code&amp;gt;onTick&amp;lt;/code&amp;gt; is called periodically by epicEFI.&lt;br /&gt;
&lt;br /&gt;
Simple startup example:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;lua&amp;quot;&amp;gt;&lt;br /&gt;
print(&#039;Hello Lua startup!&#039;)&lt;br /&gt;
&lt;br /&gt;
function onTick()&lt;br /&gt;
    print(&#039;Hello onTick()&#039;)&lt;br /&gt;
end&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Controlling the tick rate ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;setTickRate(hz)&amp;lt;/code&amp;gt; sets how often epicEFI calls &amp;lt;code&amp;gt;onTick&amp;lt;/code&amp;gt;. 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. &amp;lt;code&amp;gt;onCanRx&amp;lt;/code&amp;gt; runs at the same rate as &amp;lt;code&amp;gt;onTick&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;lua&amp;quot;&amp;gt;&lt;br /&gt;
n = 0&lt;br /&gt;
setTickRate(5) -- 5 Hz&lt;br /&gt;
function onTick()&lt;br /&gt;
    print(&#039;Hello Lua: &#039; .. n)&lt;br /&gt;
    n = n + 1&lt;br /&gt;
end&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Editing scripts ===&lt;br /&gt;
&lt;br /&gt;
An editor with [https://github.com/LuaLS/lua-language-server#install Lua Language Server] (LSP) support makes writing scripts much easier.&lt;br /&gt;
&lt;br /&gt;
== Hooks / function reference ==&lt;br /&gt;
&lt;br /&gt;
=== User settings ===&lt;br /&gt;
&lt;br /&gt;
==== &amp;lt;code&amp;gt;getOutput(name)&amp;lt;/code&amp;gt; ====&lt;br /&gt;
&lt;br /&gt;
Example: &amp;lt;code&amp;gt;getOutput(&amp;quot;clutchUpState&amp;quot;)&amp;lt;/code&amp;gt; or &amp;lt;code&amp;gt;getOutput(&amp;quot;brakePedalState&amp;quot;)&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Valid output names for your firmware: [https://content.epicefi.com/docs/variable_status_selector.html Variable Status Selector].&lt;br /&gt;
&lt;br /&gt;
==== &amp;lt;code&amp;gt;setClutchUpState(value)&amp;lt;/code&amp;gt; ====&lt;br /&gt;
&lt;br /&gt;
==== &amp;lt;code&amp;gt;setBrakePedalState(value)&amp;lt;/code&amp;gt; ====&lt;br /&gt;
&lt;br /&gt;
Use &amp;lt;code&amp;gt;setBrakePedalState&amp;lt;/code&amp;gt; to report a CAN-based brake pedal to epicEFI.&lt;br /&gt;
&lt;br /&gt;
==== &amp;lt;code&amp;gt;setAcRequestState(value)&amp;lt;/code&amp;gt; ====&lt;br /&gt;
&lt;br /&gt;
Use &amp;lt;code&amp;gt;setAcRequestState&amp;lt;/code&amp;gt; to report a CAN-based A/C request.&lt;br /&gt;
&lt;br /&gt;
==== &amp;lt;code&amp;gt;setEtbDisabled(value)&amp;lt;/code&amp;gt; ====&lt;br /&gt;
&lt;br /&gt;
==== &amp;lt;code&amp;gt;setIgnDisabled(value)&amp;lt;/code&amp;gt; ====&lt;br /&gt;
&lt;br /&gt;
Use &amp;lt;code&amp;gt;setIgnDisabled&amp;lt;/code&amp;gt; for cranking safety and other ignition-cut strategies.&lt;br /&gt;
&lt;br /&gt;
==== &amp;lt;code&amp;gt;setAcDisabled(value)&amp;lt;/code&amp;gt; ====&lt;br /&gt;
&lt;br /&gt;
Disable/suppress A/C regardless of how it would otherwise be enabled.&lt;br /&gt;
&lt;br /&gt;
==== &amp;lt;code&amp;gt;getTimeSinceAcToggleMs()&amp;lt;/code&amp;gt; ====&lt;br /&gt;
&lt;br /&gt;
==== &amp;lt;code&amp;gt;getCalibration(name)&amp;lt;/code&amp;gt; ====&lt;br /&gt;
&lt;br /&gt;
Returns the current value of a scalar calibration setting. Example: &amp;lt;code&amp;gt;getCalibration(&amp;quot;cranking.rpm&amp;quot;)&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Full list of valid names: [https://content.epicefi.com/docs/variable_status_selector.html Variable Status Selector].&lt;br /&gt;
&lt;br /&gt;
==== &amp;lt;code&amp;gt;setCalibration(name, value, needEvent)&amp;lt;/code&amp;gt; ====&lt;br /&gt;
&lt;br /&gt;
Sets a calibration value. Optionally fires a calibration change event depending on &amp;lt;code&amp;gt;needEvent&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Example: &amp;lt;code&amp;gt;setCalibration(&amp;quot;cranking.rpm&amp;quot;, 900, false)&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== &amp;lt;code&amp;gt;burnconfig&amp;lt;/code&amp;gt; ====&lt;br /&gt;
&lt;br /&gt;
Schedules a write of current calibration to flash once the engine is stopped.&lt;br /&gt;
&lt;br /&gt;
==== &amp;lt;code&amp;gt;findSetting(name, defaultValue)&amp;lt;/code&amp;gt; ====&lt;br /&gt;
&lt;br /&gt;
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.&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;Parameters&#039;&#039;&#039;&lt;br /&gt;
** &amp;lt;code&amp;gt;name&amp;lt;/code&amp;gt;: Variable name (matches the configuration field name)&lt;br /&gt;
** &amp;lt;code&amp;gt;defaultValue&amp;lt;/code&amp;gt;: Returned if the setting is not found&lt;br /&gt;
&lt;br /&gt;
=== &amp;lt;code&amp;gt;isFirmwareError&amp;lt;/code&amp;gt; ===&lt;br /&gt;
&lt;br /&gt;
Returns &amp;lt;code&amp;gt;true&amp;lt;/code&amp;gt; if the ECU is in a critical/fatal error state.&lt;br /&gt;
&lt;br /&gt;
=== Engine control ===&lt;br /&gt;
&lt;br /&gt;
==== &amp;lt;code&amp;gt;startCrankingEngine()&amp;lt;/code&amp;gt; ====&lt;br /&gt;
&lt;br /&gt;
Starts cranking as if the physical start button were pressed.&lt;br /&gt;
&lt;br /&gt;
==== &amp;lt;code&amp;gt;stopEngine()&amp;lt;/code&amp;gt; ====&lt;br /&gt;
&lt;br /&gt;
==== &amp;lt;code&amp;gt;isEngineStopRequested()&amp;lt;/code&amp;gt; ====&lt;br /&gt;
&lt;br /&gt;
Returns &amp;lt;code&amp;gt;true&amp;lt;/code&amp;gt; if an engine stop was requested (by Lua or the start/stop button) within the last five seconds.&lt;br /&gt;
&lt;br /&gt;
==== &amp;lt;code&amp;gt;setLaunchTrigger&amp;lt;/code&amp;gt; ====&lt;br /&gt;
&lt;br /&gt;
==== &amp;lt;code&amp;gt;setSparkSkipRatio(ratio)&amp;lt;/code&amp;gt; ====&lt;br /&gt;
&lt;br /&gt;
* &amp;lt;code&amp;gt;setSparkSkipRatio(0)&amp;lt;/code&amp;gt; — skip 0% of ignition events (no skip)&lt;br /&gt;
* &amp;lt;code&amp;gt;setSparkSkipRatio(0.5)&amp;lt;/code&amp;gt; — skip half of ignition events (never two consecutive skips)&lt;br /&gt;
&lt;br /&gt;
Useful for torque reduction.&lt;br /&gt;
&lt;br /&gt;
==== &amp;lt;code&amp;gt;setSparkHardSkipRatio(ratio)&amp;lt;/code&amp;gt; ====&lt;br /&gt;
&lt;br /&gt;
* &amp;lt;code&amp;gt;setSparkHardSkipRatio(0)&amp;lt;/code&amp;gt; — no skip&lt;br /&gt;
* &amp;lt;code&amp;gt;setSparkHardSkipRatio(0.75)&amp;lt;/code&amp;gt; — skip 75% of ignition events&lt;br /&gt;
&lt;br /&gt;
==== &amp;lt;code&amp;gt;setIdleAdd(percent)&amp;lt;/code&amp;gt; ====&lt;br /&gt;
&lt;br /&gt;
Percent added to idle (including open loop).&lt;br /&gt;
&lt;br /&gt;
==== &amp;lt;code&amp;gt;setFuelAdd(amount)&amp;lt;/code&amp;gt; ====&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;Currently not functional.&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Fuel mass to add to injection, scaled by [[#setFuelMult(coeff)|&amp;lt;code&amp;gt;setFuelMult()&amp;lt;/code&amp;gt;]]; starts at 0.&lt;br /&gt;
&lt;br /&gt;
==== &amp;lt;code&amp;gt;setFuelMult(coeff)&amp;lt;/code&amp;gt; ====&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;Currently not functional.&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Multiplier for added fuel mass; starts at 1.0.&lt;br /&gt;
&lt;br /&gt;
==== &amp;lt;code&amp;gt;setBoostTargetAdd(amount)&amp;lt;/code&amp;gt; ====&lt;br /&gt;
&lt;br /&gt;
Additive offset for closed-loop boost target.&lt;br /&gt;
&lt;br /&gt;
==== &amp;lt;code&amp;gt;setBoostTargetMult(coeff)&amp;lt;/code&amp;gt; ====&lt;br /&gt;
&lt;br /&gt;
Multiplier for closed-loop boost target.&lt;br /&gt;
&lt;br /&gt;
==== &amp;lt;code&amp;gt;setBoostDutyAdd(amount)&amp;lt;/code&amp;gt; ====&lt;br /&gt;
&lt;br /&gt;
Additive offset for open-loop boost duty.&lt;br /&gt;
&lt;br /&gt;
==== &amp;lt;code&amp;gt;setTimingAdd(angle)&amp;lt;/code&amp;gt; ====&lt;br /&gt;
&lt;br /&gt;
Use negative values to retard timing.&lt;br /&gt;
&lt;br /&gt;
==== &amp;lt;code&amp;gt;setTimingMult(coeff)&amp;lt;/code&amp;gt; ====&lt;br /&gt;
&lt;br /&gt;
Useful for torque reduction.&lt;br /&gt;
&lt;br /&gt;
==== &amp;lt;code&amp;gt;setEtbAdd(percent)&amp;lt;/code&amp;gt; ====&lt;br /&gt;
&lt;br /&gt;
ETB adder as a percent of wide-open: e.g. &amp;lt;code&amp;gt;10&amp;lt;/code&amp;gt; adds +10%. Static offset on top of the computed position (TPS 5% + &amp;lt;code&amp;gt;10&amp;lt;/code&amp;gt; → 15% ETB command).&lt;br /&gt;
&lt;br /&gt;
Useful for torque reduction.&lt;br /&gt;
&lt;br /&gt;
=== Timer ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;yourTimer = Timer.new()&amp;lt;/code&amp;gt; creates a timer object.&lt;br /&gt;
&lt;br /&gt;
==== &amp;lt;code&amp;gt;reset&amp;lt;/code&amp;gt; ====&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;yourTimer:reset()&amp;lt;/code&amp;gt; resets the timer.&lt;br /&gt;
&lt;br /&gt;
==== &amp;lt;code&amp;gt;getElapsedSeconds&amp;lt;/code&amp;gt; ====&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;yourTimer:getElapsedSeconds()&amp;lt;/code&amp;gt; returns seconds since last reset.&lt;br /&gt;
&lt;br /&gt;
==== &amp;lt;code&amp;gt;getTsButtonCount&amp;lt;/code&amp;gt; ====&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;getTsButtonCount(X)&amp;lt;/code&amp;gt; returns how many times Lua button &#039;&#039;&#039;X&#039;&#039;&#039; was pressed in TunerStudio. See &amp;lt;code&amp;gt;ts-button-example.lua&amp;lt;/code&amp;gt; in the firmware examples.&lt;br /&gt;
&lt;br /&gt;
=== CAN bus ===&lt;br /&gt;
&lt;br /&gt;
==== &amp;lt;code&amp;gt;enableCanTx(isEnabled)&amp;lt;/code&amp;gt; ====&lt;br /&gt;
&lt;br /&gt;
Enabled by default. Use &amp;lt;code&amp;gt;enableCanTx(false)&amp;lt;/code&amp;gt; to suppress CAN transmit from Lua.&lt;br /&gt;
&lt;br /&gt;
==== &amp;lt;code&amp;gt;txCan(bus, ID, isExt, payload)&amp;lt;/code&amp;gt; ====&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;Parameters&#039;&#039;&#039;&lt;br /&gt;
** &amp;lt;code&amp;gt;bus&amp;lt;/code&amp;gt;: Hardware CAN bus index — typically &amp;lt;code&amp;gt;1&amp;lt;/code&amp;gt; on single-CAN boards; &amp;lt;code&amp;gt;1&amp;lt;/code&amp;gt; or &amp;lt;code&amp;gt;2&amp;lt;/code&amp;gt; on dual-CAN boards (EpicECU, M144H7, etc.)&lt;br /&gt;
** &amp;lt;code&amp;gt;isExt&amp;lt;/code&amp;gt;: &amp;lt;code&amp;gt;0&amp;lt;/code&amp;gt; for 11-bit standard ID&lt;br /&gt;
&lt;br /&gt;
==== &amp;lt;code&amp;gt;canRxAdd(id)&amp;lt;/code&amp;gt; ====&lt;br /&gt;
&lt;br /&gt;
==== &amp;lt;code&amp;gt;canRxAdd(bus, id)&amp;lt;/code&amp;gt; ====&lt;br /&gt;
&lt;br /&gt;
==== &amp;lt;code&amp;gt;canRxAdd(id, callback)&amp;lt;/code&amp;gt; ====&lt;br /&gt;
&lt;br /&gt;
==== &amp;lt;code&amp;gt;canRxAdd(bus, id, callback)&amp;lt;/code&amp;gt; ====&lt;br /&gt;
&lt;br /&gt;
==== &amp;lt;code&amp;gt;canRxAddMask(id, mask)&amp;lt;/code&amp;gt; ====&lt;br /&gt;
&lt;br /&gt;
==== &amp;lt;code&amp;gt;canRxAddMask(bus, id, mask)&amp;lt;/code&amp;gt; ====&lt;br /&gt;
&lt;br /&gt;
==== &amp;lt;code&amp;gt;canRxAddMask(id, mask, callback)&amp;lt;/code&amp;gt; ====&lt;br /&gt;
&lt;br /&gt;
==== &amp;lt;code&amp;gt;canRxAddMask(bus, id, mask, callback)&amp;lt;/code&amp;gt; ====&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;Parameters&#039;&#039;&#039;&lt;br /&gt;
** &amp;lt;code&amp;gt;id&amp;lt;/code&amp;gt;: CAN ID to listen for&lt;br /&gt;
** &amp;lt;code&amp;gt;mask&amp;lt;/code&amp;gt;: Applied to the received ID before comparison. Example: id &amp;lt;code&amp;gt;3&amp;lt;/code&amp;gt; and mask &amp;lt;code&amp;gt;0xFF&amp;lt;/code&amp;gt; matches any frame whose low 8 bits are &amp;lt;code&amp;gt;3&amp;lt;/code&amp;gt;. Omit the mask to match exactly one ID.&lt;br /&gt;
** &amp;lt;code&amp;gt;bus&amp;lt;/code&amp;gt;: Hardware CAN bus index. If omitted, frames from any bus are received.&lt;br /&gt;
** &amp;lt;code&amp;gt;callback&amp;lt;/code&amp;gt;: Function called when a matching frame arrives. If omitted, &amp;lt;code&amp;gt;onCanRx&amp;lt;/code&amp;gt; is used.&lt;br /&gt;
&lt;br /&gt;
CAN RX callback shape:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;lua&amp;quot;&amp;gt;&lt;br /&gt;
function onCanRx(bus, id, dlc, data)&lt;br /&gt;
    -- handle frame&lt;br /&gt;
end&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
For high-throughput RX, see &amp;lt;code&amp;gt;enableCanRxWorkaround()&amp;lt;/code&amp;gt; in the firmware examples.&lt;br /&gt;
&lt;br /&gt;
=== SENT protocol ===&lt;br /&gt;
&lt;br /&gt;
==== &amp;lt;code&amp;gt;getSentValue(index)&amp;lt;/code&amp;gt; ====&lt;br /&gt;
&lt;br /&gt;
==== &amp;lt;code&amp;gt;getSentValues(index)&amp;lt;/code&amp;gt; ====&lt;br /&gt;
&lt;br /&gt;
=== PID ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;deltaTime&amp;lt;/code&amp;gt; is measured automatically between successive &amp;lt;code&amp;gt;pid:get&amp;lt;/code&amp;gt; calls.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;lua&amp;quot;&amp;gt;&lt;br /&gt;
-- p, i, d, min, max&lt;br /&gt;
pid = Pid.new(2, 0, 0, -100, 100)&lt;br /&gt;
pid:setOffset(0.3)&lt;br /&gt;
pid:get(target, input)&lt;br /&gt;
pid:reset()&lt;br /&gt;
&lt;br /&gt;
industrialPid = IndustrialPid.new(2, 0, 0, -100, 100)&lt;br /&gt;
industrialPid:setOffset(0.3)&lt;br /&gt;
industrialPid:setDerivativeFilterLoss(0.3)&lt;br /&gt;
industrialPid:setAntiwindupFreq(0.3)&lt;br /&gt;
industrialPid:get(target, input)&lt;br /&gt;
industrialPid:reset()&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Utility ===&lt;br /&gt;
&lt;br /&gt;
==== &amp;lt;code&amp;gt;print(msg)&amp;lt;/code&amp;gt; ====&lt;br /&gt;
&lt;br /&gt;
Prints a line to the ECU log.&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;Parameters:&#039;&#039;&#039; &amp;lt;code&amp;gt;msg&amp;lt;/code&amp;gt; — string or number&lt;br /&gt;
* &#039;&#039;&#039;Returns:&#039;&#039;&#039; none&lt;br /&gt;
&lt;br /&gt;
==== &amp;lt;code&amp;gt;vin(index)&amp;lt;/code&amp;gt; ====&lt;br /&gt;
&lt;br /&gt;
Returns the VIN character at the given zero-based index.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Example:&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;lua&amp;quot;&amp;gt;&lt;br /&gt;
n = 5.5&lt;br /&gt;
print(&#039;Hello Lua, number is: &#039; .. n)&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Output: &amp;lt;code&amp;gt;Hello Lua, number is: 5.5&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== &amp;lt;code&amp;gt;setTickRate(hz)&amp;lt;/code&amp;gt; ====&lt;br /&gt;
&lt;br /&gt;
Sets how often epicEFI calls &amp;lt;code&amp;gt;onTick&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;onCanRx&amp;lt;/code&amp;gt;, in Hz. Default after reset is 10 Hz.&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;Parameters:&#039;&#039;&#039; &amp;lt;code&amp;gt;hz&amp;lt;/code&amp;gt; — clamped to 1–200 Hz&lt;br /&gt;
* &#039;&#039;&#039;Returns:&#039;&#039;&#039; none&lt;br /&gt;
&lt;br /&gt;
==== &amp;lt;code&amp;gt;mcu_standby()&amp;lt;/code&amp;gt; ====&lt;br /&gt;
&lt;br /&gt;
Puts the MCU into standby (low current). Use with care.&lt;br /&gt;
&lt;br /&gt;
==== &amp;lt;code&amp;gt;interpolate(x1, y1, x2, y2, x)&amp;lt;/code&amp;gt; ====&lt;br /&gt;
&lt;br /&gt;
Linear interpolation of &amp;lt;code&amp;gt;x&amp;lt;/code&amp;gt; on the line through &amp;lt;code&amp;gt;(x1, y1)&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;(x2, y2)&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
==== &amp;lt;code&amp;gt;findTableIndex(name)&amp;lt;/code&amp;gt; ====&lt;br /&gt;
&lt;br /&gt;
Returns the index of a script table by human-readable name.&lt;br /&gt;
&lt;br /&gt;
==== &amp;lt;code&amp;gt;table3d(tableIdx, x, y)&amp;lt;/code&amp;gt; ====&lt;br /&gt;
&lt;br /&gt;
Looks up a value from a script 3D table.&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;Parameters&#039;&#039;&#039;&lt;br /&gt;
** &amp;lt;code&amp;gt;tableIdx&amp;lt;/code&amp;gt;: Table index (1–4)&lt;br /&gt;
** &amp;lt;code&amp;gt;x&amp;lt;/code&amp;gt;: X-axis value (often RPM)&lt;br /&gt;
** &amp;lt;code&amp;gt;y&amp;lt;/code&amp;gt;: Y-axis value (often load)&lt;br /&gt;
* &#039;&#039;&#039;Returns:&#039;&#039;&#039; table output value&lt;br /&gt;
&lt;br /&gt;
==== &amp;lt;code&amp;gt;findCurveIndex(name)&amp;lt;/code&amp;gt; ====&lt;br /&gt;
&lt;br /&gt;
Returns the index of a script curve by name.&lt;br /&gt;
&lt;br /&gt;
==== &amp;lt;code&amp;gt;curve(curveIdx, x)&amp;lt;/code&amp;gt; ====&lt;br /&gt;
&lt;br /&gt;
Looks up a value from a script curve.&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;Parameters&#039;&#039;&#039;&lt;br /&gt;
** &amp;lt;code&amp;gt;curveIdx&amp;lt;/code&amp;gt;: Curve index (1-based)&lt;br /&gt;
** &amp;lt;code&amp;gt;x&amp;lt;/code&amp;gt;: Axis value&lt;br /&gt;
&lt;br /&gt;
==== &amp;lt;code&amp;gt;setDebug(index, value)&amp;lt;/code&amp;gt; ====&lt;br /&gt;
&lt;br /&gt;
Sets a debug channel when ECU debug mode is &#039;&#039;&#039;Lua&#039;&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;Parameters:&#039;&#039;&#039; &amp;lt;code&amp;gt;index&amp;lt;/code&amp;gt; 1–7, &amp;lt;code&amp;gt;value&amp;lt;/code&amp;gt; — channel value&lt;br /&gt;
* &#039;&#039;&#039;Returns:&#039;&#039;&#039; none&lt;br /&gt;
&lt;br /&gt;
=== Input ===&lt;br /&gt;
&lt;br /&gt;
==== &amp;lt;code&amp;gt;getSensor(name)&amp;lt;/code&amp;gt; ====&lt;br /&gt;
&lt;br /&gt;
Reads a sensor by name, e.g. &amp;lt;code&amp;gt;getSensor(&amp;quot;AcceleratorPedal&amp;quot;)&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;Parameters:&#039;&#039;&#039; &amp;lt;code&amp;gt;name&amp;lt;/code&amp;gt; — sensor name (same names as TunerStudio Live Data, e.g. &amp;lt;code&amp;gt;TPS&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;CLT&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;RPM&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;MAP&amp;lt;/code&amp;gt;)&lt;br /&gt;
* &#039;&#039;&#039;Returns:&#039;&#039;&#039; reading, or &amp;lt;code&amp;gt;nil&amp;lt;/code&amp;gt; if the sensor is invalid or not configured&lt;br /&gt;
&lt;br /&gt;
==== &amp;lt;code&amp;gt;getSensorByIndex(index)&amp;lt;/code&amp;gt; ====&lt;br /&gt;
&lt;br /&gt;
Reads a sensor by enum index.&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;Returns:&#039;&#039;&#039; reading, or &amp;lt;code&amp;gt;nil&amp;lt;/code&amp;gt; on failure&lt;br /&gt;
&lt;br /&gt;
==== &amp;lt;code&amp;gt;getSensorRaw(index)&amp;lt;/code&amp;gt; ====&lt;br /&gt;
&lt;br /&gt;
Raw sensor value (usually pin voltage before scaling).&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;Returns:&#039;&#039;&#039; raw value, or &amp;lt;code&amp;gt;0&amp;lt;/code&amp;gt; if unsupported / not configured / failed&lt;br /&gt;
&lt;br /&gt;
==== &amp;lt;code&amp;gt;getAuxAnalog(index)&amp;lt;/code&amp;gt; ====&lt;br /&gt;
&lt;br /&gt;
Like &amp;lt;code&amp;gt;getSensorRaw&amp;lt;/code&amp;gt; but for aux analog inputs — always voltage.&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;Parameters:&#039;&#039;&#039; &amp;lt;code&amp;gt;index&amp;lt;/code&amp;gt; 0–3&lt;br /&gt;
* &#039;&#039;&#039;Returns:&#039;&#039;&#039; voltage, or &amp;lt;code&amp;gt;nil&amp;lt;/code&amp;gt; if not configured&lt;br /&gt;
&lt;br /&gt;
==== &amp;lt;code&amp;gt;hasSensor(index)&amp;lt;/code&amp;gt; ====&lt;br /&gt;
&lt;br /&gt;
Whether a sensor slot is configured (valid or not).&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;Returns:&#039;&#039;&#039; boolean&lt;br /&gt;
&lt;br /&gt;
==== &amp;lt;code&amp;gt;getDigital(index)&amp;lt;/code&amp;gt; ====&lt;br /&gt;
&lt;br /&gt;
Reads a built-in digital input.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! Index !! Channel&lt;br /&gt;
|-&lt;br /&gt;
| 0 || Clutch down&lt;br /&gt;
|-&lt;br /&gt;
| 1 || Clutch up&lt;br /&gt;
|-&lt;br /&gt;
| 2 || Brake switch&lt;br /&gt;
|-&lt;br /&gt;
| 3 || AC switch&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
==== &amp;lt;code&amp;gt;getAuxDigital(index)&amp;lt;/code&amp;gt; ====&lt;br /&gt;
&lt;br /&gt;
Reads a user-configured digital input (index 0–7). Configure pins under &#039;&#039;&#039;Lua Digital Aux Inputs&#039;&#039;&#039; in TunerStudio.&lt;br /&gt;
&lt;br /&gt;
==== &amp;lt;code&amp;gt;readPin(pinName)&amp;lt;/code&amp;gt; ====&lt;br /&gt;
&lt;br /&gt;
Reads an MCU pin directly (e.g. &amp;lt;code&amp;gt;&amp;quot;PD15&amp;quot;&amp;lt;/code&amp;gt;). Emergency/debug only — prefer Lua aux inputs for real logic.&lt;br /&gt;
&lt;br /&gt;
=== Output ===&lt;br /&gt;
&lt;br /&gt;
Not the same as Live Data “outputs” or GPPWM.&lt;br /&gt;
&lt;br /&gt;
==== &amp;lt;code&amp;gt;selfStimulateRPM(rpm)&amp;lt;/code&amp;gt; ====&lt;br /&gt;
&lt;br /&gt;
Positive RPM starts injector clicking at that speed; &amp;lt;code&amp;gt;0&amp;lt;/code&amp;gt; stops self-stimulation.&lt;br /&gt;
&lt;br /&gt;
==== &amp;lt;code&amp;gt;startPwm(index, frequency, duty)&amp;lt;/code&amp;gt; ====&lt;br /&gt;
&lt;br /&gt;
Starts PWM on a Lua PWM output. Pin assignment: &#039;&#039;&#039;Lua PWM Outputs&#039;&#039;&#039; in TunerStudio.&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;Parameters&#039;&#039;&#039;&lt;br /&gt;
** &amp;lt;code&amp;gt;index&amp;lt;/code&amp;gt;: 0–7&lt;br /&gt;
** &amp;lt;code&amp;gt;frequency&amp;lt;/code&amp;gt;: 1–1000 Hz&lt;br /&gt;
** &amp;lt;code&amp;gt;duty&amp;lt;/code&amp;gt;: 0.0 = off, 1.0 = full on&lt;br /&gt;
&lt;br /&gt;
==== &amp;lt;code&amp;gt;setPwmDuty(index, duty)&amp;lt;/code&amp;gt; ====&lt;br /&gt;
&lt;br /&gt;
==== &amp;lt;code&amp;gt;setPwmFreq(index, frequency)&amp;lt;/code&amp;gt; ====&lt;br /&gt;
&lt;br /&gt;
==== &amp;lt;code&amp;gt;getGpPwm(index)&amp;lt;/code&amp;gt; ====&lt;br /&gt;
&lt;br /&gt;
Current GPPWM output percent (index 0–3).&lt;br /&gt;
&lt;br /&gt;
==== &amp;lt;code&amp;gt;setLuaGauge(index, value)&amp;lt;/code&amp;gt; ====&lt;br /&gt;
&lt;br /&gt;
Writes a Lua gauge (indices 1–8) for Live Data / logging.&lt;br /&gt;
&lt;br /&gt;
==== &amp;lt;code&amp;gt;setDacVoltage(index, value)&amp;lt;/code&amp;gt; ====&lt;br /&gt;
&lt;br /&gt;
Only on boards with DAC hardware enabled.&lt;br /&gt;
&lt;br /&gt;
== Console commands ==&lt;br /&gt;
&lt;br /&gt;
* &amp;lt;code&amp;gt;luamemory&amp;lt;/code&amp;gt; — Lua memory usage&lt;br /&gt;
* &amp;lt;code&amp;gt;luareset&amp;lt;/code&amp;gt; — reset Lua VM&lt;br /&gt;
&lt;br /&gt;
== Examples ==&lt;br /&gt;
&lt;br /&gt;
Example scripts ship with the firmware under &amp;lt;code&amp;gt;firmware/controllers/lua/examples/&amp;lt;/code&amp;gt; ([https://github.com/epicEFI/epicefi_fw/tree/master/firmware/controllers/lua/examples browse on GitHub]).&lt;br /&gt;
&lt;br /&gt;
Notable examples: &amp;lt;code&amp;gt;honda-bcm.txt&amp;lt;/code&amp;gt; (VSS from CAN / gear detection), &amp;lt;code&amp;gt;ford-focus-ii-pps.txt&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;bmw-idrive.txt&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
=== Timer example ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;lua&amp;quot;&amp;gt;&lt;br /&gt;
t = Timer.new()&lt;br /&gt;
timingAdd = 0&lt;br /&gt;
&lt;br /&gt;
function onTick()&lt;br /&gt;
   auxV = getAuxAnalog(0)&lt;br /&gt;
   tps = getSensor(&amp;quot;TPS&amp;quot;)&lt;br /&gt;
   -- check for nil if aux input is not assigned&lt;br /&gt;
   if auxV &amp;gt; 2 then&lt;br /&gt;
     t:reset()&lt;br /&gt;
   end&lt;br /&gt;
&lt;br /&gt;
   val = t:getElapsedSeconds()&lt;br /&gt;
&lt;br /&gt;
   if t:getElapsedSeconds() &amp;lt; 3 then&lt;br /&gt;
     timingAdd = 10&lt;br /&gt;
   else&lt;br /&gt;
     timingAdd = 0&lt;br /&gt;
   end&lt;br /&gt;
   setTimingAdd(timingAdd)&lt;br /&gt;
&lt;br /&gt;
   print(&#039;Hello analog &#039; .. auxV .. &amp;quot; &amp;quot; .. val)&lt;br /&gt;
end&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== PWM ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;lua&amp;quot;&amp;gt;&lt;br /&gt;
startPwm(0, 100, 0)&lt;br /&gt;
&lt;br /&gt;
function onTick()&lt;br /&gt;
 enable_pump = getSensor(&amp;quot;RPM&amp;quot;) &amp;gt; 700 and getSensor(&amp;quot;BatteryVoltage&amp;quot;) &amp;gt; 13 and getSensor(&amp;quot;VehicleSpeed&amp;quot;) &amp;lt; 60&lt;br /&gt;
 setPwmDuty(0, enable_pump and 1 or 0)&lt;br /&gt;
end&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== CAN transmit ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;lua&amp;quot;&amp;gt;&lt;br /&gt;
function onTick()&lt;br /&gt;
  clt = getSensor(&amp;quot;CLT&amp;quot;)&lt;br /&gt;
  print(&#039;CLT &#039; .. clt)&lt;br /&gt;
  voltage0 = getSensor(&amp;quot;aux0&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
  txPayload = {}&lt;br /&gt;
  txPayload[1] = math.floor(((voltage0/256) - math.floor(voltage0/256))*256)&lt;br /&gt;
  txPayload[2] = math.floor(voltage0 / 256)&lt;br /&gt;
&lt;br /&gt;
  txCan(1, 0x600, 1, txPayload)&lt;br /&gt;
end&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Set sensor value ===&lt;br /&gt;
&lt;br /&gt;
Use standard sensor names from TunerStudio Live Data.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;lua&amp;quot;&amp;gt;&lt;br /&gt;
-- do not configure the same physical input elsewhere&lt;br /&gt;
vssSensor = Sensor.new(&amp;quot;VehicleSpeed&amp;quot;)&lt;br /&gt;
vssSensor:setTimeout(3000)&lt;br /&gt;
function onTick()&lt;br /&gt;
 injectedVssValue = 123.4&lt;br /&gt;
 vssSensor:set(injectedVssValue)&lt;br /&gt;
 valFromSensor = getSensor(&amp;quot;VehicleSpeed&amp;quot;)&lt;br /&gt;
 print(&amp;quot;VSS &amp;quot; .. valFromSensor)&lt;br /&gt;
end&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== CAN receive ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;lua&amp;quot;&amp;gt;&lt;br /&gt;
canRxAdd(0x500)&lt;br /&gt;
canRxAdd(0x570)&lt;br /&gt;
function onCanRx(bus, id, dlc, data)&lt;br /&gt;
 print(&#039;got CAN id=&#039; .. id .. &#039; dlc=&#039; .. dlc)&lt;br /&gt;
 if id == 0x500 then&lt;br /&gt;
  canState = data[1]&lt;br /&gt;
 end&lt;br /&gt;
 if id == 0x570 then&lt;br /&gt;
  mcu_standby()&lt;br /&gt;
 end&lt;br /&gt;
end&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Table / curve ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;lua&amp;quot;&amp;gt;&lt;br /&gt;
tableIndex = findTableIndex(&amp;quot;duty&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
TurbochargerSpeed = getSensor(&amp;quot;TurbochargerSpeed&amp;quot;)&lt;br /&gt;
tps = getSensor(&amp;quot;Tps1&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
dutyCycle = table3d(tableIndex, TurbochargerSpeed, tps)&lt;br /&gt;
&lt;br /&gt;
sparkCutCurve = findCurveIndex(&amp;quot;sparkcut&amp;quot;)&lt;br /&gt;
sparkCutByTorque = curve(sparkCutCurve, torquex)&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== See also ==&lt;br /&gt;
&lt;br /&gt;
* [https://content.epicefi.com/docs/variable_status_selector.html Variable Status Selector] — names, types, and hashes for your firmware&lt;br /&gt;
* [https://github.com/epicEFI/epicefi_fw/tree/master/firmware/controllers/lua/examples Lua examples] — vehicle-specific scripts&lt;br /&gt;
* [http://lua-users.org/wiki/TernaryOperator Lua ternary operator] — Lua has no &amp;lt;code&amp;gt;?:&amp;lt;/code&amp;gt;; use &amp;lt;code&amp;gt;and&amp;lt;/code&amp;gt; / &amp;lt;code&amp;gt;or&amp;lt;/code&amp;gt; patterns&lt;/div&gt;</summary>
		<author><name>Joesphan</name></author>
	</entry>
	<entry>
		<id>https://content.epicefi.com/wiki/index.php?title=Lua&amp;diff=498</id>
		<title>Lua</title>
		<link rel="alternate" type="text/html" href="https://content.epicefi.com/wiki/index.php?title=Lua&amp;diff=498"/>
		<updated>2026-08-18T03:15:51Z</updated>

		<summary type="html">&lt;p&gt;Joesphan: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;# Lua Scripting&lt;br /&gt;
&lt;br /&gt;
**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.&lt;br /&gt;
&lt;br /&gt;
## Introduction&lt;br /&gt;
&lt;br /&gt;
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.&lt;br /&gt;
&lt;br /&gt;
## Variable names and hashes&lt;br /&gt;
&lt;br /&gt;
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:&lt;br /&gt;
&lt;br /&gt;
**[Variable Status Selector](https://content.epicefi.com/docs/variable_status_selector.html)**&lt;br /&gt;
&lt;br /&gt;
Use this tool when calling `getCalibration()` / `setCalibration()`, `getOutput()`, configuring user-table axes, or reading/writing variables over EPIC CAN.&lt;br /&gt;
&lt;br /&gt;
## Basics&lt;br /&gt;
&lt;br /&gt;
epicEFI provides hooks to interface with the firmware, manipulate its state, and read/write configuration:&lt;br /&gt;
&lt;br /&gt;
- [Hooks for CAN bus communications](#can-bus)&lt;br /&gt;
- [Inputs from sensors can be read directly](#input). [You can also produce sensor values](#set-sensor-value) with Lua.&lt;br /&gt;
- [ECU general purpose outputs](#output)&lt;br /&gt;
- [Aspects of the engine can be controlled directly](#engine-control)&lt;br /&gt;
- ECU configuration can be accessed (read/write) via [`getCalibration()`](#getcalibrationname) and [`setCalibration()`](#setcalibrationname-value-needevent).&lt;br /&gt;
  - Valid calibration names for your firmware are listed in the [Variable Status Selector](https://content.epicefi.com/docs/variable_status_selector.html).&lt;br /&gt;
- ECU internal state (logic outputs / Live Data values) can be read via [`getOutput()`](#getoutputname), and some can be changed via dedicated setters (e.g. [`setClutchUpState()`](#setclutchupstatevalue)). See [Output](#output).&lt;br /&gt;
  - Valid output names for your firmware are listed in the [Variable Status Selector](https://content.epicefi.com/docs/variable_status_selector.html).&lt;br /&gt;
- Hooks to read values from SENT sensors; see [SENT protocol](#sent-protocol).&lt;br /&gt;
- Useful helper routines; see [Utility](#utility).&lt;br /&gt;
&lt;br /&gt;
Some example uses are in [Examples](#examples).&lt;br /&gt;
&lt;br /&gt;
## Conventions&lt;br /&gt;
&lt;br /&gt;
- The Lua interpreter reports errors when something is wrong; check epicEFI Console / TunerStudio Lua output for errors and `print()` output.&lt;br /&gt;
- Unless otherwise noted, all `index` parameters are **zero-based** (first element is index `0`).&lt;br /&gt;
&lt;br /&gt;
## Writing Your Script&lt;br /&gt;
&lt;br /&gt;
The entire Lua script is loaded at startup. After that, a function named `onTick` is called periodically by epicEFI.&lt;br /&gt;
&lt;br /&gt;
Simple startup example:&lt;br /&gt;
&lt;br /&gt;
```lua&lt;br /&gt;
print(&#039;Hello Lua startup!&#039;)&lt;br /&gt;
&lt;br /&gt;
function onTick()&lt;br /&gt;
    print(&#039;Hello onTick()&#039;)&lt;br /&gt;
end&lt;br /&gt;
```&lt;br /&gt;
&lt;br /&gt;
### Controlling the tick rate&lt;br /&gt;
&lt;br /&gt;
`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`.&lt;br /&gt;
&lt;br /&gt;
```lua&lt;br /&gt;
n = 0&lt;br /&gt;
setTickRate(5) -- 5 Hz&lt;br /&gt;
function onTick()&lt;br /&gt;
    print(&#039;Hello Lua: &#039; .. n)&lt;br /&gt;
    n = n + 1&lt;br /&gt;
end&lt;br /&gt;
```&lt;br /&gt;
&lt;br /&gt;
### Editing scripts&lt;br /&gt;
&lt;br /&gt;
An editor with [Lua Language Server](https://github.com/LuaLS/lua-language-server#install) (LSP) support makes writing scripts much easier.&lt;br /&gt;
&lt;br /&gt;
## Hooks / function reference&lt;br /&gt;
&lt;br /&gt;
### User settings&lt;br /&gt;
&lt;br /&gt;
#### `getOutput(name)`&lt;br /&gt;
&lt;br /&gt;
Example: `getOutput(&amp;quot;clutchUpState&amp;quot;)` or `getOutput(&amp;quot;brakePedalState&amp;quot;)`.&lt;br /&gt;
&lt;br /&gt;
Valid output names for your firmware: [Variable Status Selector](https://content.epicefi.com/docs/variable_status_selector.html).&lt;br /&gt;
&lt;br /&gt;
#### `setClutchUpState(value)`&lt;br /&gt;
&lt;br /&gt;
#### `setBrakePedalState(value)`&lt;br /&gt;
&lt;br /&gt;
Use `setBrakePedalState` to report a CAN-based brake pedal to epicEFI.&lt;br /&gt;
&lt;br /&gt;
#### `setAcRequestState(value)`&lt;br /&gt;
&lt;br /&gt;
Use `setAcRequestState` to report a CAN-based A/C request.&lt;br /&gt;
&lt;br /&gt;
#### `setEtbDisabled(value)`&lt;br /&gt;
&lt;br /&gt;
#### `setIgnDisabled(value)`&lt;br /&gt;
&lt;br /&gt;
Use `setIgnDisabled` for cranking safety and other ignition-cut strategies.&lt;br /&gt;
&lt;br /&gt;
#### `setAcDisabled(value)`&lt;br /&gt;
&lt;br /&gt;
Disable/suppress A/C regardless of how it would otherwise be enabled.&lt;br /&gt;
&lt;br /&gt;
#### `getTimeSinceAcToggleMs()`&lt;br /&gt;
&lt;br /&gt;
#### `getCalibration(name)`&lt;br /&gt;
&lt;br /&gt;
Returns the current value of a scalar calibration setting. Example: `getCalibration(&amp;quot;cranking.rpm&amp;quot;)`.&lt;br /&gt;
&lt;br /&gt;
Full list of valid names: [Variable Status Selector](https://content.epicefi.com/docs/variable_status_selector.html).&lt;br /&gt;
&lt;br /&gt;
#### `setCalibration(name, value, needEvent)`&lt;br /&gt;
&lt;br /&gt;
Sets a calibration value. Optionally fires a calibration change event depending on `needEvent`.&lt;br /&gt;
&lt;br /&gt;
Example: `setCalibration(&amp;quot;cranking.rpm&amp;quot;, 900, false)`&lt;br /&gt;
&lt;br /&gt;
#### `burnconfig`&lt;br /&gt;
&lt;br /&gt;
Schedules a write of current calibration to flash once the engine is stopped.&lt;br /&gt;
&lt;br /&gt;
#### `findSetting(name, defaultValue)`&lt;br /&gt;
&lt;br /&gt;
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.&lt;br /&gt;
&lt;br /&gt;
- **Parameters**&lt;br /&gt;
  - `name`: Variable name (matches the configuration field name)&lt;br /&gt;
  - `defaultValue`: Returned if the setting is not found&lt;br /&gt;
&lt;br /&gt;
### `isFirmwareError`&lt;br /&gt;
&lt;br /&gt;
Returns `true` if the ECU is in a critical/fatal error state.&lt;br /&gt;
&lt;br /&gt;
### Engine control&lt;br /&gt;
&lt;br /&gt;
#### `startCrankingEngine()`&lt;br /&gt;
&lt;br /&gt;
Starts cranking as if the physical start button were pressed.&lt;br /&gt;
&lt;br /&gt;
#### `stopEngine()`&lt;br /&gt;
&lt;br /&gt;
#### `isEngineStopRequested()`&lt;br /&gt;
&lt;br /&gt;
Returns `true` if an engine stop was requested (by Lua or the start/stop button) within the last five seconds.&lt;br /&gt;
&lt;br /&gt;
#### `setLaunchTrigger`&lt;br /&gt;
&lt;br /&gt;
#### `setSparkSkipRatio(ratio)`&lt;br /&gt;
&lt;br /&gt;
- `setSparkSkipRatio(0)` — skip 0% of ignition events (no skip)&lt;br /&gt;
- `setSparkSkipRatio(0.5)` — skip half of ignition events (never two consecutive skips)&lt;br /&gt;
&lt;br /&gt;
Useful for torque reduction.&lt;br /&gt;
&lt;br /&gt;
#### `setSparkHardSkipRatio(ratio)`&lt;br /&gt;
&lt;br /&gt;
- `setSparkHardSkipRatio(0)` — no skip&lt;br /&gt;
- `setSparkHardSkipRatio(0.75)` — skip 75% of ignition events&lt;br /&gt;
&lt;br /&gt;
#### `setIdleAdd(percent)`&lt;br /&gt;
&lt;br /&gt;
Percent added to idle (including open loop).&lt;br /&gt;
&lt;br /&gt;
#### `setFuelAdd(amount)`&lt;br /&gt;
&lt;br /&gt;
*Currently not functional.*&lt;br /&gt;
&lt;br /&gt;
Fuel mass to add to injection, scaled by [`setFuelMult()`](#setfuelmultcoeff); starts at 0.&lt;br /&gt;
&lt;br /&gt;
#### `setFuelMult(coeff)`&lt;br /&gt;
&lt;br /&gt;
*Currently not functional.*&lt;br /&gt;
&lt;br /&gt;
Multiplier for added fuel mass; starts at 1.0.&lt;br /&gt;
&lt;br /&gt;
#### `setBoostTargetAdd(amount)`&lt;br /&gt;
&lt;br /&gt;
Additive offset for closed-loop boost target.&lt;br /&gt;
&lt;br /&gt;
#### `setBoostTargetMult(coeff)`&lt;br /&gt;
&lt;br /&gt;
Multiplier for closed-loop boost target.&lt;br /&gt;
&lt;br /&gt;
#### `setBoostDutyAdd(amount)`&lt;br /&gt;
&lt;br /&gt;
Additive offset for open-loop boost duty.&lt;br /&gt;
&lt;br /&gt;
#### `setTimingAdd(angle)`&lt;br /&gt;
&lt;br /&gt;
Use negative values to retard timing.&lt;br /&gt;
&lt;br /&gt;
#### `setTimingMult(coeff)`&lt;br /&gt;
&lt;br /&gt;
Useful for torque reduction.&lt;br /&gt;
&lt;br /&gt;
#### `setEtbAdd(percent)`&lt;br /&gt;
&lt;br /&gt;
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).&lt;br /&gt;
&lt;br /&gt;
Useful for torque reduction.&lt;br /&gt;
&lt;br /&gt;
### Timer&lt;br /&gt;
&lt;br /&gt;
`yourTimer = Timer.new()` creates a timer object.&lt;br /&gt;
&lt;br /&gt;
#### `reset`&lt;br /&gt;
&lt;br /&gt;
`yourTimer:reset()` resets the timer.&lt;br /&gt;
&lt;br /&gt;
#### `getElapsedSeconds`&lt;br /&gt;
&lt;br /&gt;
`yourTimer:getElapsedSeconds()` returns seconds since last reset.&lt;br /&gt;
&lt;br /&gt;
#### `getTsButtonCount`&lt;br /&gt;
&lt;br /&gt;
`getTsButtonCount(X)` returns how many times Lua button **X** was pressed in TunerStudio. See `ts-button-example.lua` in the firmware examples.&lt;br /&gt;
&lt;br /&gt;
### CAN bus&lt;br /&gt;
&lt;br /&gt;
#### `enableCanTx(isEnabled)`&lt;br /&gt;
&lt;br /&gt;
Enabled by default. Use `enableCanTx(false)` to suppress CAN transmit from Lua.&lt;br /&gt;
&lt;br /&gt;
#### `txCan(bus, ID, isExt, payload)`&lt;br /&gt;
&lt;br /&gt;
- **Parameters**&lt;br /&gt;
  - `bus`: Hardware CAN bus index — typically `1` on single-CAN boards; `1` or `2` on dual-CAN boards (EpicECU, M144H7, etc.)&lt;br /&gt;
  - `isExt`: `0` for 11-bit standard ID&lt;br /&gt;
&lt;br /&gt;
#### `canRxAdd(id)`&lt;br /&gt;
&lt;br /&gt;
#### `canRxAdd(bus, id)`&lt;br /&gt;
&lt;br /&gt;
#### `canRxAdd(id, callback)`&lt;br /&gt;
&lt;br /&gt;
#### `canRxAdd(bus, id, callback)`&lt;br /&gt;
&lt;br /&gt;
#### `canRxAddMask(id, mask)`&lt;br /&gt;
&lt;br /&gt;
#### `canRxAddMask(bus, id, mask)`&lt;br /&gt;
&lt;br /&gt;
#### `canRxAddMask(id, mask, callback)`&lt;br /&gt;
&lt;br /&gt;
#### `canRxAddMask(bus, id, mask, callback)`&lt;br /&gt;
&lt;br /&gt;
- **Parameters**&lt;br /&gt;
  - `id`: CAN ID to listen for&lt;br /&gt;
  - `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.&lt;br /&gt;
  - `bus`: Hardware CAN bus index. If omitted, frames from any bus are received.&lt;br /&gt;
  - `callback`: Function called when a matching frame arrives. If omitted, `onCanRx` is used.&lt;br /&gt;
&lt;br /&gt;
CAN RX callback shape:&lt;br /&gt;
&lt;br /&gt;
```lua&lt;br /&gt;
function onCanRx(bus, id, dlc, data)&lt;br /&gt;
    -- handle frame&lt;br /&gt;
end&lt;br /&gt;
```&lt;br /&gt;
&lt;br /&gt;
For high-throughput RX, see `enableCanRxWorkaround()` in the firmware examples.&lt;br /&gt;
&lt;br /&gt;
### SENT protocol&lt;br /&gt;
&lt;br /&gt;
#### `getSentValue(index)`&lt;br /&gt;
&lt;br /&gt;
#### `getSentValues(index)`&lt;br /&gt;
&lt;br /&gt;
### PID&lt;br /&gt;
&lt;br /&gt;
`deltaTime` is measured automatically between successive `pid:get` calls.&lt;br /&gt;
&lt;br /&gt;
```lua&lt;br /&gt;
-- p, i, d, min, max&lt;br /&gt;
pid = Pid.new(2, 0, 0, -100, 100)&lt;br /&gt;
pid:setOffset(0.3)&lt;br /&gt;
pid:get(target, input)&lt;br /&gt;
pid:reset()&lt;br /&gt;
&lt;br /&gt;
industrialPid = IndustrialPid.new(2, 0, 0, -100, 100)&lt;br /&gt;
industrialPid:setOffset(0.3)&lt;br /&gt;
industrialPid:setDerivativeFilterLoss(0.3)&lt;br /&gt;
industrialPid:setAntiwindupFreq(0.3)&lt;br /&gt;
industrialPid:get(target, input)&lt;br /&gt;
industrialPid:reset()&lt;br /&gt;
```&lt;br /&gt;
&lt;br /&gt;
### Utility&lt;br /&gt;
&lt;br /&gt;
#### `print(msg)`&lt;br /&gt;
&lt;br /&gt;
Prints a line to the ECU log.&lt;br /&gt;
&lt;br /&gt;
- **Parameters:** `msg` — string or number&lt;br /&gt;
- **Returns:** none&lt;br /&gt;
&lt;br /&gt;
#### `vin(index)`&lt;br /&gt;
&lt;br /&gt;
Returns the VIN character at the given zero-based index.&lt;br /&gt;
&lt;br /&gt;
**Example:**&lt;br /&gt;
&lt;br /&gt;
```lua&lt;br /&gt;
n = 5.5&lt;br /&gt;
print(&#039;Hello Lua, number is: &#039; .. n)&lt;br /&gt;
```&lt;br /&gt;
&lt;br /&gt;
Output: `Hello Lua, number is: 5.5`&lt;br /&gt;
&lt;br /&gt;
#### `setTickRate(hz)`&lt;br /&gt;
&lt;br /&gt;
Sets how often epicEFI calls `onTick` and `onCanRx`, in Hz. Default after reset is 10 Hz.&lt;br /&gt;
&lt;br /&gt;
- **Parameters:** `hz` — clamped to 1–200 Hz&lt;br /&gt;
- **Returns:** none&lt;br /&gt;
&lt;br /&gt;
#### `mcu_standby()`&lt;br /&gt;
&lt;br /&gt;
Puts the MCU into standby (low current). Use with care.&lt;br /&gt;
&lt;br /&gt;
#### `interpolate(x1, y1, x2, y2, x)`&lt;br /&gt;
&lt;br /&gt;
Linear interpolation of `x` on the line through `(x1, y1)` and `(x2, y2)`.&lt;br /&gt;
&lt;br /&gt;
#### `findTableIndex(name)`&lt;br /&gt;
&lt;br /&gt;
Returns the index of a script table by human-readable name.&lt;br /&gt;
&lt;br /&gt;
#### `table3d(tableIdx, x, y)`&lt;br /&gt;
&lt;br /&gt;
Looks up a value from a script 3D table.&lt;br /&gt;
&lt;br /&gt;
- **Parameters**&lt;br /&gt;
  - `tableIdx`: Table index (1–4)&lt;br /&gt;
  - `x`: X-axis value (often RPM)&lt;br /&gt;
  - `y`: Y-axis value (often load)&lt;br /&gt;
- **Returns:** table output value&lt;br /&gt;
&lt;br /&gt;
#### `findCurveIndex(name)`&lt;br /&gt;
&lt;br /&gt;
Returns the index of a script curve by name.&lt;br /&gt;
&lt;br /&gt;
#### `curve(curveIdx, x)`&lt;br /&gt;
&lt;br /&gt;
Looks up a value from a script curve.&lt;br /&gt;
&lt;br /&gt;
- **Parameters**&lt;br /&gt;
  - `curveIdx`: Curve index (1-based)&lt;br /&gt;
  - `x`: Axis value&lt;br /&gt;
&lt;br /&gt;
#### `setDebug(index, value)`&lt;br /&gt;
&lt;br /&gt;
Sets a debug channel when ECU debug mode is **Lua**.&lt;br /&gt;
&lt;br /&gt;
- **Parameters:** `index` 1–7, `value` — channel value&lt;br /&gt;
- **Returns:** none&lt;br /&gt;
&lt;br /&gt;
### Input&lt;br /&gt;
&lt;br /&gt;
#### `getSensor(name)`&lt;br /&gt;
&lt;br /&gt;
Reads a sensor by name, e.g. `getSensor(&amp;quot;AcceleratorPedal&amp;quot;)`.&lt;br /&gt;
&lt;br /&gt;
- **Parameters:** `name` — sensor name (same names as TunerStudio Live Data, e.g. `TPS`, `CLT`, `RPM`, `MAP`)&lt;br /&gt;
- **Returns:** reading, or `nil` if the sensor is invalid or not configured&lt;br /&gt;
&lt;br /&gt;
#### `getSensorByIndex(index)`&lt;br /&gt;
&lt;br /&gt;
Reads a sensor by enum index.&lt;br /&gt;
&lt;br /&gt;
- **Returns:** reading, or `nil` on failure&lt;br /&gt;
&lt;br /&gt;
#### `getSensorRaw(index)`&lt;br /&gt;
&lt;br /&gt;
Raw sensor value (usually pin voltage before scaling).&lt;br /&gt;
&lt;br /&gt;
- **Returns:** raw value, or `0` if unsupported / not configured / failed&lt;br /&gt;
&lt;br /&gt;
#### `getAuxAnalog(index)`&lt;br /&gt;
&lt;br /&gt;
Like `getSensorRaw` but for aux analog inputs — always voltage.&lt;br /&gt;
&lt;br /&gt;
- **Parameters:** `index` 0–3&lt;br /&gt;
- **Returns:** voltage, or `nil` if not configured&lt;br /&gt;
&lt;br /&gt;
#### `hasSensor(index)`&lt;br /&gt;
&lt;br /&gt;
Whether a sensor slot is configured (valid or not).&lt;br /&gt;
&lt;br /&gt;
- **Returns:** boolean&lt;br /&gt;
&lt;br /&gt;
#### `getDigital(index)`&lt;br /&gt;
&lt;br /&gt;
Reads a built-in digital input.&lt;br /&gt;
&lt;br /&gt;
| Index | Channel        |&lt;br /&gt;
|------:|----------------|&lt;br /&gt;
| 0     | Clutch down    |&lt;br /&gt;
| 1     | Clutch up      |&lt;br /&gt;
| 2     | Brake switch   |&lt;br /&gt;
| 3     | AC switch      |&lt;br /&gt;
&lt;br /&gt;
#### `getAuxDigital(index)`&lt;br /&gt;
&lt;br /&gt;
Reads a user-configured digital input (index 0–7). Configure pins under **Lua Digital Aux Inputs** in TunerStudio.&lt;br /&gt;
&lt;br /&gt;
#### `readPin(pinName)`&lt;br /&gt;
&lt;br /&gt;
Reads an MCU pin directly (e.g. `&amp;quot;PD15&amp;quot;`). Emergency/debug only — prefer Lua aux inputs for real logic.&lt;br /&gt;
&lt;br /&gt;
### Output&lt;br /&gt;
&lt;br /&gt;
Not the same as Live Data “outputs” or GPPWM.&lt;br /&gt;
&lt;br /&gt;
#### `selfStimulateRPM(rpm)`&lt;br /&gt;
&lt;br /&gt;
Positive RPM starts injector clicking at that speed; `0` stops self-stimulation.&lt;br /&gt;
&lt;br /&gt;
#### `startPwm(index, frequency, duty)`&lt;br /&gt;
&lt;br /&gt;
Starts PWM on a Lua PWM output. Pin assignment: **Lua PWM Outputs** in TunerStudio.&lt;br /&gt;
&lt;br /&gt;
- **Parameters**&lt;br /&gt;
  - `index`: 0–7&lt;br /&gt;
  - `frequency`: 1–1000 Hz&lt;br /&gt;
  - `duty`: 0.0 = off, 1.0 = full on&lt;br /&gt;
&lt;br /&gt;
#### `setPwmDuty(index, duty)`&lt;br /&gt;
&lt;br /&gt;
#### `setPwmFreq(index, frequency)`&lt;br /&gt;
&lt;br /&gt;
#### `getGpPwm(index)`&lt;br /&gt;
&lt;br /&gt;
Current GPPWM output percent (index 0–3).&lt;br /&gt;
&lt;br /&gt;
#### `setLuaGauge(index, value)`&lt;br /&gt;
&lt;br /&gt;
Writes a Lua gauge (indices 1–8) for Live Data / logging.&lt;br /&gt;
&lt;br /&gt;
#### `setDacVoltage(index, value)`&lt;br /&gt;
&lt;br /&gt;
Only on boards with DAC hardware enabled.&lt;br /&gt;
&lt;br /&gt;
## Console commands&lt;br /&gt;
&lt;br /&gt;
- `luamemory` — Lua memory usage&lt;br /&gt;
- `luareset` — reset Lua VM&lt;br /&gt;
&lt;br /&gt;
## Examples&lt;br /&gt;
&lt;br /&gt;
Example scripts ship with the firmware under `firmware/controllers/lua/examples/` ([browse on GitHub](https://github.com/epicEFI/epicefi_fw/tree/master/firmware/controllers/lua/examples)).&lt;br /&gt;
&lt;br /&gt;
Notable examples: `honda-bcm.txt` (VSS from CAN / gear detection), `ford-focus-ii-pps.txt`, `bmw-idrive.txt`.&lt;br /&gt;
&lt;br /&gt;
### Timer example&lt;br /&gt;
&lt;br /&gt;
```lua&lt;br /&gt;
t = Timer.new()&lt;br /&gt;
timingAdd = 0&lt;br /&gt;
&lt;br /&gt;
function onTick()&lt;br /&gt;
   auxV = getAuxAnalog(0)&lt;br /&gt;
   tps = getSensor(&amp;quot;TPS&amp;quot;)&lt;br /&gt;
   -- check for nil if aux input is not assigned&lt;br /&gt;
   if auxV &amp;gt; 2 then&lt;br /&gt;
     t:reset()&lt;br /&gt;
   end&lt;br /&gt;
&lt;br /&gt;
   val = t:getElapsedSeconds()&lt;br /&gt;
&lt;br /&gt;
   if t:getElapsedSeconds() &amp;lt; 3 then&lt;br /&gt;
     timingAdd = 10&lt;br /&gt;
   else&lt;br /&gt;
     timingAdd = 0&lt;br /&gt;
   end&lt;br /&gt;
   setTimingAdd(timingAdd)&lt;br /&gt;
&lt;br /&gt;
   print(&#039;Hello analog &#039; .. auxV .. &amp;quot; &amp;quot; .. val)&lt;br /&gt;
end&lt;br /&gt;
```&lt;br /&gt;
&lt;br /&gt;
### PWM&lt;br /&gt;
&lt;br /&gt;
```lua&lt;br /&gt;
startPwm(0, 100, 0)&lt;br /&gt;
&lt;br /&gt;
function onTick()&lt;br /&gt;
 enable_pump = getSensor(&amp;quot;RPM&amp;quot;) &amp;gt; 700 and getSensor(&amp;quot;BatteryVoltage&amp;quot;) &amp;gt; 13 and getSensor(&amp;quot;VehicleSpeed&amp;quot;) &amp;lt; 60&lt;br /&gt;
 setPwmDuty(0, enable_pump and 1 or 0)&lt;br /&gt;
end&lt;br /&gt;
```&lt;br /&gt;
&lt;br /&gt;
### CAN transmit&lt;br /&gt;
&lt;br /&gt;
```lua&lt;br /&gt;
function onTick()&lt;br /&gt;
  clt = getSensor(&amp;quot;CLT&amp;quot;)&lt;br /&gt;
  print(&#039;CLT &#039; .. clt)&lt;br /&gt;
  voltage0 = getSensor(&amp;quot;aux0&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
  txPayload = {}&lt;br /&gt;
  txPayload[1] = math.floor(((voltage0/256) - math.floor(voltage0/256))*256)&lt;br /&gt;
  txPayload[2] = math.floor(voltage0 / 256)&lt;br /&gt;
&lt;br /&gt;
  txCan(1, 0x600, 1, txPayload)&lt;br /&gt;
end&lt;br /&gt;
```&lt;br /&gt;
&lt;br /&gt;
### Set sensor value&lt;br /&gt;
&lt;br /&gt;
Use standard sensor names from TunerStudio Live Data.&lt;br /&gt;
&lt;br /&gt;
```lua&lt;br /&gt;
-- do not configure the same physical input elsewhere&lt;br /&gt;
vssSensor = Sensor.new(&amp;quot;VehicleSpeed&amp;quot;)&lt;br /&gt;
vssSensor:setTimeout(3000)&lt;br /&gt;
function onTick()&lt;br /&gt;
 injectedVssValue = 123.4&lt;br /&gt;
 vssSensor:set(injectedVssValue)&lt;br /&gt;
 valFromSensor = getSensor(&amp;quot;VehicleSpeed&amp;quot;)&lt;br /&gt;
 print(&amp;quot;VSS &amp;quot; .. valFromSensor)&lt;br /&gt;
end&lt;br /&gt;
```&lt;br /&gt;
&lt;br /&gt;
### CAN receive&lt;br /&gt;
&lt;br /&gt;
```lua&lt;br /&gt;
canRxAdd(0x500)&lt;br /&gt;
canRxAdd(0x570)&lt;br /&gt;
function onCanRx(bus, id, dlc, data)&lt;br /&gt;
 print(&#039;got CAN id=&#039; .. id .. &#039; dlc=&#039; .. dlc)&lt;br /&gt;
 if id == 0x500 then&lt;br /&gt;
  canState = data[1]&lt;br /&gt;
 end&lt;br /&gt;
 if id == 0x570 then&lt;br /&gt;
  mcu_standby()&lt;br /&gt;
 end&lt;br /&gt;
end&lt;br /&gt;
```&lt;br /&gt;
&lt;br /&gt;
### Table / curve&lt;br /&gt;
&lt;br /&gt;
```lua&lt;br /&gt;
tableIndex = findTableIndex(&amp;quot;duty&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
TurbochargerSpeed = getSensor(&amp;quot;TurbochargerSpeed&amp;quot;)&lt;br /&gt;
tps = getSensor(&amp;quot;Tps1&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
dutyCycle = table3d(tableIndex, TurbochargerSpeed, tps)&lt;br /&gt;
&lt;br /&gt;
sparkCutCurve = findCurveIndex(&amp;quot;sparkcut&amp;quot;)&lt;br /&gt;
sparkCutByTorque = curve(sparkCutCurve, torquex)&lt;br /&gt;
```&lt;br /&gt;
&lt;br /&gt;
## See also&lt;br /&gt;
&lt;br /&gt;
- [Variable Status Selector](https://content.epicefi.com/docs/variable_status_selector.html) — names, types, and hashes for your firmware&lt;br /&gt;
- [Lua examples](https://github.com/epicEFI/epicefi_fw/tree/master/firmware/controllers/lua/examples) — vehicle-specific scripts&lt;br /&gt;
- [Lua ternary operator](http://lua-users.org/wiki/TernaryOperator) — Lua has no `?:`; use `and` / `or` patterns&lt;/div&gt;</summary>
		<author><name>Joesphan</name></author>
	</entry>
	<entry>
		<id>https://content.epicefi.com/wiki/index.php?title=Lua&amp;diff=497</id>
		<title>Lua</title>
		<link rel="alternate" type="text/html" href="https://content.epicefi.com/wiki/index.php?title=Lua&amp;diff=497"/>
		<updated>2026-08-18T03:15:33Z</updated>

		<summary type="html">&lt;p&gt;Joesphan: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&amp;lt;nowiki&amp;gt;#&amp;lt;/nowiki&amp;gt; Lua Scripting&lt;br /&gt;
&lt;br /&gt;
&amp;lt;nowiki&amp;gt;**&amp;lt;/nowiki&amp;gt;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.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;nowiki&amp;gt;##&amp;lt;/nowiki&amp;gt; Introduction&lt;br /&gt;
&lt;br /&gt;
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.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;nowiki&amp;gt;##&amp;lt;/nowiki&amp;gt; Variable names and hashes&lt;br /&gt;
&lt;br /&gt;
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:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;nowiki&amp;gt;**&amp;lt;/nowiki&amp;gt;[Variable Status Selector](&amp;lt;nowiki&amp;gt;https://content.epicefi.com/docs/variable_status_selector.html)**&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Use this tool when calling `getCalibration()` / `setCalibration()`, `getOutput()`, configuring user-table axes, or reading/writing variables over EPIC CAN.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;nowiki&amp;gt;##&amp;lt;/nowiki&amp;gt; Basics&lt;br /&gt;
&lt;br /&gt;
epicEFI provides hooks to interface with the firmware, manipulate its state, and read/write configuration:&lt;br /&gt;
&lt;br /&gt;
- [Hooks for CAN bus communications](#can-bus)&lt;br /&gt;
&lt;br /&gt;
- [Inputs from sensors can be read directly](#input). [You can also produce sensor values](#set-sensor-value) with Lua.&lt;br /&gt;
&lt;br /&gt;
- [ECU general purpose outputs](#output)&lt;br /&gt;
&lt;br /&gt;
- [Aspects of the engine can be controlled directly](#engine-control)&lt;br /&gt;
&lt;br /&gt;
- ECU configuration can be accessed (read/write) via [`getCalibration()`](#getcalibrationname) and [`setCalibration()`](#setcalibrationname-value-needevent).&lt;br /&gt;
&lt;br /&gt;
  - Valid calibration names for your firmware are listed in the [Variable Status Selector](&amp;lt;nowiki&amp;gt;https://content.epicefi.com/docs/variable_status_selector.html&amp;lt;/nowiki&amp;gt;).&lt;br /&gt;
&lt;br /&gt;
- ECU internal state (logic outputs / Live Data values) can be read via [`getOutput()`](#getoutputname), and some can be changed via dedicated setters (e.g. [`setClutchUpState()`](#setclutchupstatevalue)). See [Output](#output).&lt;br /&gt;
&lt;br /&gt;
  - Valid output names for your firmware are listed in the [Variable Status Selector](&amp;lt;nowiki&amp;gt;https://content.epicefi.com/docs/variable_status_selector.html&amp;lt;/nowiki&amp;gt;).&lt;br /&gt;
&lt;br /&gt;
- Hooks to read values from SENT sensors; see [SENT protocol](#sent-protocol).&lt;br /&gt;
&lt;br /&gt;
- Useful helper routines; see [Utility](#utility).&lt;br /&gt;
&lt;br /&gt;
Some example uses are in [Examples](#examples).&lt;br /&gt;
&lt;br /&gt;
&amp;lt;nowiki&amp;gt;##&amp;lt;/nowiki&amp;gt; Conventions&lt;br /&gt;
&lt;br /&gt;
- The Lua interpreter reports errors when something is wrong; check epicEFI Console / TunerStudio Lua output for errors and `print()` output.&lt;br /&gt;
&lt;br /&gt;
- Unless otherwise noted, all `index` parameters are **zero-based** (first element is index `0`).&lt;br /&gt;
&lt;br /&gt;
&amp;lt;nowiki&amp;gt;##&amp;lt;/nowiki&amp;gt; Writing Your Script&lt;br /&gt;
&lt;br /&gt;
The entire Lua script is loaded at startup. After that, a function named `onTick` is called periodically by epicEFI.&lt;br /&gt;
&lt;br /&gt;
Simple startup example:&lt;br /&gt;
&lt;br /&gt;
```lua&lt;br /&gt;
&lt;br /&gt;
print(&#039;Hello Lua startup!&#039;)&lt;br /&gt;
&lt;br /&gt;
function onTick()&lt;br /&gt;
&lt;br /&gt;
    print(&#039;Hello onTick()&#039;)&lt;br /&gt;
&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
```&lt;br /&gt;
&lt;br /&gt;
&amp;lt;nowiki&amp;gt;###&amp;lt;/nowiki&amp;gt; Controlling the tick rate&lt;br /&gt;
&lt;br /&gt;
`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`.&lt;br /&gt;
&lt;br /&gt;
```lua&lt;br /&gt;
&lt;br /&gt;
n = 0&lt;br /&gt;
&lt;br /&gt;
setTickRate(5) -- 5 Hz&lt;br /&gt;
&lt;br /&gt;
function onTick()&lt;br /&gt;
&lt;br /&gt;
    print(&#039;Hello Lua: &#039; .. n)&lt;br /&gt;
&lt;br /&gt;
    n = n + 1&lt;br /&gt;
&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
```&lt;br /&gt;
&lt;br /&gt;
&amp;lt;nowiki&amp;gt;###&amp;lt;/nowiki&amp;gt; Editing scripts&lt;br /&gt;
&lt;br /&gt;
An editor with [Lua Language Server](&amp;lt;nowiki&amp;gt;https://github.com/LuaLS/lua-language-server#install&amp;lt;/nowiki&amp;gt;) (LSP) support makes writing scripts much easier.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;nowiki&amp;gt;##&amp;lt;/nowiki&amp;gt; Hooks / function reference&lt;br /&gt;
&lt;br /&gt;
&amp;lt;nowiki&amp;gt;###&amp;lt;/nowiki&amp;gt; User settings&lt;br /&gt;
&lt;br /&gt;
&amp;lt;nowiki&amp;gt;####&amp;lt;/nowiki&amp;gt; `getOutput(name)`&lt;br /&gt;
&lt;br /&gt;
Example: `getOutput(&amp;quot;clutchUpState&amp;quot;)` or `getOutput(&amp;quot;brakePedalState&amp;quot;)`.&lt;br /&gt;
&lt;br /&gt;
Valid output names for your firmware: [Variable Status Selector](&amp;lt;nowiki&amp;gt;https://content.epicefi.com/docs/variable_status_selector.html&amp;lt;/nowiki&amp;gt;).&lt;br /&gt;
&lt;br /&gt;
&amp;lt;nowiki&amp;gt;####&amp;lt;/nowiki&amp;gt; `setClutchUpState(value)`&lt;br /&gt;
&lt;br /&gt;
&amp;lt;nowiki&amp;gt;####&amp;lt;/nowiki&amp;gt; `setBrakePedalState(value)`&lt;br /&gt;
&lt;br /&gt;
Use `setBrakePedalState` to report a CAN-based brake pedal to epicEFI.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;nowiki&amp;gt;####&amp;lt;/nowiki&amp;gt; `setAcRequestState(value)`&lt;br /&gt;
&lt;br /&gt;
Use `setAcRequestState` to report a CAN-based A/C request.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;nowiki&amp;gt;####&amp;lt;/nowiki&amp;gt; `setEtbDisabled(value)`&lt;br /&gt;
&lt;br /&gt;
&amp;lt;nowiki&amp;gt;####&amp;lt;/nowiki&amp;gt; `setIgnDisabled(value)`&lt;br /&gt;
&lt;br /&gt;
Use `setIgnDisabled` for cranking safety and other ignition-cut strategies.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;nowiki&amp;gt;####&amp;lt;/nowiki&amp;gt; `setAcDisabled(value)`&lt;br /&gt;
&lt;br /&gt;
Disable/suppress A/C regardless of how it would otherwise be enabled.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;nowiki&amp;gt;####&amp;lt;/nowiki&amp;gt; `getTimeSinceAcToggleMs()`&lt;br /&gt;
&lt;br /&gt;
&amp;lt;nowiki&amp;gt;####&amp;lt;/nowiki&amp;gt; `getCalibration(name)`&lt;br /&gt;
&lt;br /&gt;
Returns the current value of a scalar calibration setting. Example: `getCalibration(&amp;quot;cranking.rpm&amp;quot;)`.&lt;br /&gt;
&lt;br /&gt;
Full list of valid names: [Variable Status Selector](&amp;lt;nowiki&amp;gt;https://content.epicefi.com/docs/variable_status_selector.html&amp;lt;/nowiki&amp;gt;).&lt;br /&gt;
&lt;br /&gt;
&amp;lt;nowiki&amp;gt;####&amp;lt;/nowiki&amp;gt; `setCalibration(name, value, needEvent)`&lt;br /&gt;
&lt;br /&gt;
Sets a calibration value. Optionally fires a calibration change event depending on `needEvent`.&lt;br /&gt;
&lt;br /&gt;
Example: `setCalibration(&amp;quot;cranking.rpm&amp;quot;, 900, false)`&lt;br /&gt;
&lt;br /&gt;
&amp;lt;nowiki&amp;gt;####&amp;lt;/nowiki&amp;gt; `burnconfig`&lt;br /&gt;
&lt;br /&gt;
Schedules a write of current calibration to flash once the engine is stopped.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;nowiki&amp;gt;####&amp;lt;/nowiki&amp;gt; `findSetting(name, defaultValue)`&lt;br /&gt;
&lt;br /&gt;
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.&lt;br /&gt;
&lt;br /&gt;
- **Parameters**&lt;br /&gt;
&lt;br /&gt;
  - `name`: Variable name (matches the configuration field name)&lt;br /&gt;
&lt;br /&gt;
  - `defaultValue`: Returned if the setting is not found&lt;br /&gt;
&lt;br /&gt;
&amp;lt;nowiki&amp;gt;###&amp;lt;/nowiki&amp;gt; `isFirmwareError`&lt;br /&gt;
&lt;br /&gt;
Returns `true` if the ECU is in a critical/fatal error state.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;nowiki&amp;gt;###&amp;lt;/nowiki&amp;gt; Engine control&lt;br /&gt;
&lt;br /&gt;
&amp;lt;nowiki&amp;gt;####&amp;lt;/nowiki&amp;gt; `startCrankingEngine()`&lt;br /&gt;
&lt;br /&gt;
Starts cranking as if the physical start button were pressed.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;nowiki&amp;gt;####&amp;lt;/nowiki&amp;gt; `stopEngine()`&lt;br /&gt;
&lt;br /&gt;
&amp;lt;nowiki&amp;gt;####&amp;lt;/nowiki&amp;gt; `isEngineStopRequested()`&lt;br /&gt;
&lt;br /&gt;
Returns `true` if an engine stop was requested (by Lua or the start/stop button) within the last five seconds.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;nowiki&amp;gt;####&amp;lt;/nowiki&amp;gt; `setLaunchTrigger`&lt;br /&gt;
&lt;br /&gt;
&amp;lt;nowiki&amp;gt;####&amp;lt;/nowiki&amp;gt; `setSparkSkipRatio(ratio)`&lt;br /&gt;
&lt;br /&gt;
- `setSparkSkipRatio(0)` — skip 0% of ignition events (no skip)&lt;br /&gt;
&lt;br /&gt;
- `setSparkSkipRatio(0.5)` — skip half of ignition events (never two consecutive skips)&lt;br /&gt;
&lt;br /&gt;
Useful for torque reduction.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;nowiki&amp;gt;####&amp;lt;/nowiki&amp;gt; `setSparkHardSkipRatio(ratio)`&lt;br /&gt;
&lt;br /&gt;
- `setSparkHardSkipRatio(0)` — no skip&lt;br /&gt;
&lt;br /&gt;
- `setSparkHardSkipRatio(0.75)` — skip 75% of ignition events&lt;br /&gt;
&lt;br /&gt;
&amp;lt;nowiki&amp;gt;####&amp;lt;/nowiki&amp;gt; `setIdleAdd(percent)`&lt;br /&gt;
&lt;br /&gt;
Percent added to idle (including open loop).&lt;br /&gt;
&lt;br /&gt;
&amp;lt;nowiki&amp;gt;####&amp;lt;/nowiki&amp;gt; `setFuelAdd(amount)`&lt;br /&gt;
&lt;br /&gt;
&amp;lt;nowiki&amp;gt;*&amp;lt;/nowiki&amp;gt;Currently not functional.*&lt;br /&gt;
&lt;br /&gt;
Fuel mass to add to injection, scaled by [`setFuelMult()`](#setfuelmultcoeff); starts at 0.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;nowiki&amp;gt;####&amp;lt;/nowiki&amp;gt; `setFuelMult(coeff)`&lt;br /&gt;
&lt;br /&gt;
&amp;lt;nowiki&amp;gt;*&amp;lt;/nowiki&amp;gt;Currently not functional.*&lt;br /&gt;
&lt;br /&gt;
Multiplier for added fuel mass; starts at 1.0.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;nowiki&amp;gt;####&amp;lt;/nowiki&amp;gt; `setBoostTargetAdd(amount)`&lt;br /&gt;
&lt;br /&gt;
Additive offset for closed-loop boost target.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;nowiki&amp;gt;####&amp;lt;/nowiki&amp;gt; `setBoostTargetMult(coeff)`&lt;br /&gt;
&lt;br /&gt;
Multiplier for closed-loop boost target.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;nowiki&amp;gt;####&amp;lt;/nowiki&amp;gt; `setBoostDutyAdd(amount)`&lt;br /&gt;
&lt;br /&gt;
Additive offset for open-loop boost duty.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;nowiki&amp;gt;####&amp;lt;/nowiki&amp;gt; `setTimingAdd(angle)`&lt;br /&gt;
&lt;br /&gt;
Use negative values to retard timing.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;nowiki&amp;gt;####&amp;lt;/nowiki&amp;gt; `setTimingMult(coeff)`&lt;br /&gt;
&lt;br /&gt;
Useful for torque reduction.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;nowiki&amp;gt;####&amp;lt;/nowiki&amp;gt; `setEtbAdd(percent)`&lt;br /&gt;
&lt;br /&gt;
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).&lt;br /&gt;
&lt;br /&gt;
Useful for torque reduction.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;nowiki&amp;gt;###&amp;lt;/nowiki&amp;gt; Timer&lt;br /&gt;
&lt;br /&gt;
`yourTimer = Timer.new()` creates a timer object.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;nowiki&amp;gt;####&amp;lt;/nowiki&amp;gt; `reset`&lt;br /&gt;
&lt;br /&gt;
`yourTimer:reset()` resets the timer.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;nowiki&amp;gt;####&amp;lt;/nowiki&amp;gt; `getElapsedSeconds`&lt;br /&gt;
&lt;br /&gt;
`yourTimer:getElapsedSeconds()` returns seconds since last reset.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;nowiki&amp;gt;####&amp;lt;/nowiki&amp;gt; `getTsButtonCount`&lt;br /&gt;
&lt;br /&gt;
`getTsButtonCount(X)` returns how many times Lua button **X** was pressed in TunerStudio. See `ts-button-example.lua` in the firmware examples.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;nowiki&amp;gt;###&amp;lt;/nowiki&amp;gt; CAN bus&lt;br /&gt;
&lt;br /&gt;
&amp;lt;nowiki&amp;gt;####&amp;lt;/nowiki&amp;gt; `enableCanTx(isEnabled)`&lt;br /&gt;
&lt;br /&gt;
Enabled by default. Use `enableCanTx(false)` to suppress CAN transmit from Lua.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;nowiki&amp;gt;####&amp;lt;/nowiki&amp;gt; `txCan(bus, ID, isExt, payload)`&lt;br /&gt;
&lt;br /&gt;
- **Parameters**&lt;br /&gt;
&lt;br /&gt;
  - `bus`: Hardware CAN bus index — typically `1` on single-CAN boards; `1` or `2` on dual-CAN boards (EpicECU, M144H7, etc.)&lt;br /&gt;
&lt;br /&gt;
  - `isExt`: `0` for 11-bit standard ID&lt;br /&gt;
&lt;br /&gt;
&amp;lt;nowiki&amp;gt;####&amp;lt;/nowiki&amp;gt; `canRxAdd(id)`&lt;br /&gt;
&lt;br /&gt;
&amp;lt;nowiki&amp;gt;####&amp;lt;/nowiki&amp;gt; `canRxAdd(bus, id)`&lt;br /&gt;
&lt;br /&gt;
&amp;lt;nowiki&amp;gt;####&amp;lt;/nowiki&amp;gt; `canRxAdd(id, callback)`&lt;br /&gt;
&lt;br /&gt;
&amp;lt;nowiki&amp;gt;####&amp;lt;/nowiki&amp;gt; `canRxAdd(bus, id, callback)`&lt;br /&gt;
&lt;br /&gt;
&amp;lt;nowiki&amp;gt;####&amp;lt;/nowiki&amp;gt; `canRxAddMask(id, mask)`&lt;br /&gt;
&lt;br /&gt;
&amp;lt;nowiki&amp;gt;####&amp;lt;/nowiki&amp;gt; `canRxAddMask(bus, id, mask)`&lt;br /&gt;
&lt;br /&gt;
&amp;lt;nowiki&amp;gt;####&amp;lt;/nowiki&amp;gt; `canRxAddMask(id, mask, callback)`&lt;br /&gt;
&lt;br /&gt;
&amp;lt;nowiki&amp;gt;####&amp;lt;/nowiki&amp;gt; `canRxAddMask(bus, id, mask, callback)`&lt;br /&gt;
&lt;br /&gt;
- **Parameters**&lt;br /&gt;
&lt;br /&gt;
  - `id`: CAN ID to listen for&lt;br /&gt;
&lt;br /&gt;
  - `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.&lt;br /&gt;
&lt;br /&gt;
  - `bus`: Hardware CAN bus index. If omitted, frames from any bus are received.&lt;br /&gt;
&lt;br /&gt;
  - `callback`: Function called when a matching frame arrives. If omitted, `onCanRx` is used.&lt;br /&gt;
&lt;br /&gt;
CAN RX callback shape:&lt;br /&gt;
&lt;br /&gt;
```lua&lt;br /&gt;
&lt;br /&gt;
function onCanRx(bus, id, dlc, data)&lt;br /&gt;
&lt;br /&gt;
    -- handle frame&lt;br /&gt;
&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
```&lt;br /&gt;
&lt;br /&gt;
For high-throughput RX, see `enableCanRxWorkaround()` in the firmware examples.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;nowiki&amp;gt;###&amp;lt;/nowiki&amp;gt; SENT protocol&lt;br /&gt;
&lt;br /&gt;
&amp;lt;nowiki&amp;gt;####&amp;lt;/nowiki&amp;gt; `getSentValue(index)`&lt;br /&gt;
&lt;br /&gt;
&amp;lt;nowiki&amp;gt;####&amp;lt;/nowiki&amp;gt; `getSentValues(index)`&lt;br /&gt;
&lt;br /&gt;
&amp;lt;nowiki&amp;gt;###&amp;lt;/nowiki&amp;gt; PID&lt;br /&gt;
&lt;br /&gt;
`deltaTime` is measured automatically between successive `pid:get` calls.&lt;br /&gt;
&lt;br /&gt;
```lua&lt;br /&gt;
&lt;br /&gt;
-- p, i, d, min, max&lt;br /&gt;
&lt;br /&gt;
pid = Pid.new(2, 0, 0, -100, 100)&lt;br /&gt;
&lt;br /&gt;
pid:setOffset(0.3)&lt;br /&gt;
&lt;br /&gt;
pid:get(target, input)&lt;br /&gt;
&lt;br /&gt;
pid:reset()&lt;br /&gt;
&lt;br /&gt;
industrialPid = IndustrialPid.new(2, 0, 0, -100, 100)&lt;br /&gt;
&lt;br /&gt;
industrialPid:setOffset(0.3)&lt;br /&gt;
&lt;br /&gt;
industrialPid:setDerivativeFilterLoss(0.3)&lt;br /&gt;
&lt;br /&gt;
industrialPid:setAntiwindupFreq(0.3)&lt;br /&gt;
&lt;br /&gt;
industrialPid:get(target, input)&lt;br /&gt;
&lt;br /&gt;
industrialPid:reset()&lt;br /&gt;
&lt;br /&gt;
```&lt;br /&gt;
&lt;br /&gt;
&amp;lt;nowiki&amp;gt;###&amp;lt;/nowiki&amp;gt; Utility&lt;br /&gt;
&lt;br /&gt;
&amp;lt;nowiki&amp;gt;####&amp;lt;/nowiki&amp;gt; `print(msg)`&lt;br /&gt;
&lt;br /&gt;
Prints a line to the ECU log.&lt;br /&gt;
&lt;br /&gt;
- **Parameters:** `msg` — string or number&lt;br /&gt;
&lt;br /&gt;
- **Returns:** none&lt;br /&gt;
&lt;br /&gt;
&amp;lt;nowiki&amp;gt;####&amp;lt;/nowiki&amp;gt; `vin(index)`&lt;br /&gt;
&lt;br /&gt;
Returns the VIN character at the given zero-based index.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;nowiki&amp;gt;**&amp;lt;/nowiki&amp;gt;Example:**&lt;br /&gt;
&lt;br /&gt;
```lua&lt;br /&gt;
&lt;br /&gt;
n = 5.5&lt;br /&gt;
&lt;br /&gt;
print(&#039;Hello Lua, number is: &#039; .. n)&lt;br /&gt;
&lt;br /&gt;
```&lt;br /&gt;
&lt;br /&gt;
Output: `Hello Lua, number is: 5.5`&lt;br /&gt;
&lt;br /&gt;
&amp;lt;nowiki&amp;gt;####&amp;lt;/nowiki&amp;gt; `setTickRate(hz)`&lt;br /&gt;
&lt;br /&gt;
Sets how often epicEFI calls `onTick` and `onCanRx`, in Hz. Default after reset is 10 Hz.&lt;br /&gt;
&lt;br /&gt;
- **Parameters:** `hz` — clamped to 1–200 Hz&lt;br /&gt;
&lt;br /&gt;
- **Returns:** none&lt;br /&gt;
&lt;br /&gt;
&amp;lt;nowiki&amp;gt;####&amp;lt;/nowiki&amp;gt; `mcu_standby()`&lt;br /&gt;
&lt;br /&gt;
Puts the MCU into standby (low current). Use with care.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;nowiki&amp;gt;####&amp;lt;/nowiki&amp;gt; `interpolate(x1, y1, x2, y2, x)`&lt;br /&gt;
&lt;br /&gt;
Linear interpolation of `x` on the line through `(x1, y1)` and `(x2, y2)`.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;nowiki&amp;gt;####&amp;lt;/nowiki&amp;gt; `findTableIndex(name)`&lt;br /&gt;
&lt;br /&gt;
Returns the index of a script table by human-readable name.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;nowiki&amp;gt;####&amp;lt;/nowiki&amp;gt; `table3d(tableIdx, x, y)`&lt;br /&gt;
&lt;br /&gt;
Looks up a value from a script 3D table.&lt;br /&gt;
&lt;br /&gt;
- **Parameters**&lt;br /&gt;
&lt;br /&gt;
  - `tableIdx`: Table index (1–4)&lt;br /&gt;
&lt;br /&gt;
  - `x`: X-axis value (often RPM)&lt;br /&gt;
&lt;br /&gt;
  - `y`: Y-axis value (often load)&lt;br /&gt;
&lt;br /&gt;
- **Returns:** table output value&lt;br /&gt;
&lt;br /&gt;
&amp;lt;nowiki&amp;gt;####&amp;lt;/nowiki&amp;gt; `findCurveIndex(name)`&lt;br /&gt;
&lt;br /&gt;
Returns the index of a script curve by name.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;nowiki&amp;gt;####&amp;lt;/nowiki&amp;gt; `curve(curveIdx, x)`&lt;br /&gt;
&lt;br /&gt;
Looks up a value from a script curve.&lt;br /&gt;
&lt;br /&gt;
- **Parameters**&lt;br /&gt;
&lt;br /&gt;
  - `curveIdx`: Curve index (1-based)&lt;br /&gt;
&lt;br /&gt;
  - `x`: Axis value&lt;br /&gt;
&lt;br /&gt;
&amp;lt;nowiki&amp;gt;####&amp;lt;/nowiki&amp;gt; `setDebug(index, value)`&lt;br /&gt;
&lt;br /&gt;
Sets a debug channel when ECU debug mode is **Lua**.&lt;br /&gt;
&lt;br /&gt;
- **Parameters:** `index` 1–7, `value` — channel value&lt;br /&gt;
&lt;br /&gt;
- **Returns:** none&lt;br /&gt;
&lt;br /&gt;
&amp;lt;nowiki&amp;gt;###&amp;lt;/nowiki&amp;gt; Input&lt;br /&gt;
&lt;br /&gt;
&amp;lt;nowiki&amp;gt;####&amp;lt;/nowiki&amp;gt; `getSensor(name)`&lt;br /&gt;
&lt;br /&gt;
Reads a sensor by name, e.g. `getSensor(&amp;quot;AcceleratorPedal&amp;quot;)`.&lt;br /&gt;
&lt;br /&gt;
- **Parameters:** `name` — sensor name (same names as TunerStudio Live Data, e.g. `TPS`, `CLT`, `RPM`, `MAP`)&lt;br /&gt;
&lt;br /&gt;
- **Returns:** reading, or `nil` if the sensor is invalid or not configured&lt;br /&gt;
&lt;br /&gt;
&amp;lt;nowiki&amp;gt;####&amp;lt;/nowiki&amp;gt; `getSensorByIndex(index)`&lt;br /&gt;
&lt;br /&gt;
Reads a sensor by enum index.&lt;br /&gt;
&lt;br /&gt;
- **Returns:** reading, or `nil` on failure&lt;br /&gt;
&lt;br /&gt;
&amp;lt;nowiki&amp;gt;####&amp;lt;/nowiki&amp;gt; `getSensorRaw(index)`&lt;br /&gt;
&lt;br /&gt;
Raw sensor value (usually pin voltage before scaling).&lt;br /&gt;
&lt;br /&gt;
- **Returns:** raw value, or `0` if unsupported / not configured / failed&lt;br /&gt;
&lt;br /&gt;
&amp;lt;nowiki&amp;gt;####&amp;lt;/nowiki&amp;gt; `getAuxAnalog(index)`&lt;br /&gt;
&lt;br /&gt;
Like `getSensorRaw` but for aux analog inputs — always voltage.&lt;br /&gt;
&lt;br /&gt;
- **Parameters:** `index` 0–3&lt;br /&gt;
&lt;br /&gt;
- **Returns:** voltage, or `nil` if not configured&lt;br /&gt;
&lt;br /&gt;
&amp;lt;nowiki&amp;gt;####&amp;lt;/nowiki&amp;gt; `hasSensor(index)`&lt;br /&gt;
&lt;br /&gt;
Whether a sensor slot is configured (valid or not).&lt;br /&gt;
&lt;br /&gt;
- **Returns:** boolean&lt;br /&gt;
&lt;br /&gt;
&amp;lt;nowiki&amp;gt;####&amp;lt;/nowiki&amp;gt; `getDigital(index)`&lt;br /&gt;
&lt;br /&gt;
Reads a built-in digital input.&lt;br /&gt;
&lt;br /&gt;
| Index | Channel        |&lt;br /&gt;
&lt;br /&gt;
|------:|----------------|&lt;br /&gt;
&lt;br /&gt;
| 0     | Clutch down    |&lt;br /&gt;
&lt;br /&gt;
| 1     | Clutch up      |&lt;br /&gt;
&lt;br /&gt;
| 2     | Brake switch   |&lt;br /&gt;
&lt;br /&gt;
| 3     | AC switch      |&lt;br /&gt;
&lt;br /&gt;
&amp;lt;nowiki&amp;gt;####&amp;lt;/nowiki&amp;gt; `getAuxDigital(index)`&lt;br /&gt;
&lt;br /&gt;
Reads a user-configured digital input (index 0–7). Configure pins under **Lua Digital Aux Inputs** in TunerStudio.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;nowiki&amp;gt;####&amp;lt;/nowiki&amp;gt; `readPin(pinName)`&lt;br /&gt;
&lt;br /&gt;
Reads an MCU pin directly (e.g. `&amp;quot;PD15&amp;quot;`). Emergency/debug only — prefer Lua aux inputs for real logic.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;nowiki&amp;gt;###&amp;lt;/nowiki&amp;gt; Output&lt;br /&gt;
&lt;br /&gt;
Not the same as Live Data “outputs” or GPPWM.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;nowiki&amp;gt;####&amp;lt;/nowiki&amp;gt; `selfStimulateRPM(rpm)`&lt;br /&gt;
&lt;br /&gt;
Positive RPM starts injector clicking at that speed; `0` stops self-stimulation.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;nowiki&amp;gt;####&amp;lt;/nowiki&amp;gt; `startPwm(index, frequency, duty)`&lt;br /&gt;
&lt;br /&gt;
Starts PWM on a Lua PWM output. Pin assignment: **Lua PWM Outputs** in TunerStudio.&lt;br /&gt;
&lt;br /&gt;
- **Parameters**&lt;br /&gt;
&lt;br /&gt;
  - `index`: 0–7&lt;br /&gt;
&lt;br /&gt;
  - `frequency`: 1–1000 Hz&lt;br /&gt;
&lt;br /&gt;
  - `duty`: 0.0 = off, 1.0 = full on&lt;br /&gt;
&lt;br /&gt;
&amp;lt;nowiki&amp;gt;####&amp;lt;/nowiki&amp;gt; `setPwmDuty(index, duty)`&lt;br /&gt;
&lt;br /&gt;
&amp;lt;nowiki&amp;gt;####&amp;lt;/nowiki&amp;gt; `setPwmFreq(index, frequency)`&lt;br /&gt;
&lt;br /&gt;
&amp;lt;nowiki&amp;gt;####&amp;lt;/nowiki&amp;gt; `getGpPwm(index)`&lt;br /&gt;
&lt;br /&gt;
Current GPPWM output percent (index 0–3).&lt;br /&gt;
&lt;br /&gt;
&amp;lt;nowiki&amp;gt;####&amp;lt;/nowiki&amp;gt; `setLuaGauge(index, value)`&lt;br /&gt;
&lt;br /&gt;
Writes a Lua gauge (indices 1–8) for Live Data / logging.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;nowiki&amp;gt;####&amp;lt;/nowiki&amp;gt; `setDacVoltage(index, value)`&lt;br /&gt;
&lt;br /&gt;
Only on boards with DAC hardware enabled.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;nowiki&amp;gt;##&amp;lt;/nowiki&amp;gt; Console commands&lt;br /&gt;
&lt;br /&gt;
- `luamemory` — Lua memory usage&lt;br /&gt;
&lt;br /&gt;
- `luareset` — reset Lua VM&lt;br /&gt;
&lt;br /&gt;
&amp;lt;nowiki&amp;gt;##&amp;lt;/nowiki&amp;gt; Examples&lt;br /&gt;
&lt;br /&gt;
Example scripts ship with the firmware under `firmware/controllers/lua/examples/` ([browse on GitHub](&amp;lt;nowiki&amp;gt;https://github.com/epicEFI/epicefi_fw/tree/master/firmware/controllers/lua/examples&amp;lt;/nowiki&amp;gt;)).&lt;br /&gt;
&lt;br /&gt;
Notable examples: `honda-bcm.txt` (VSS from CAN / gear detection), `ford-focus-ii-pps.txt`, `bmw-idrive.txt`.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;nowiki&amp;gt;###&amp;lt;/nowiki&amp;gt; Timer example&lt;br /&gt;
&lt;br /&gt;
```lua&lt;br /&gt;
&lt;br /&gt;
t = Timer.new()&lt;br /&gt;
&lt;br /&gt;
timingAdd = 0&lt;br /&gt;
&lt;br /&gt;
function onTick()&lt;br /&gt;
&lt;br /&gt;
   auxV = getAuxAnalog(0)&lt;br /&gt;
&lt;br /&gt;
   tps = getSensor(&amp;quot;TPS&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
   -- check for nil if aux input is not assigned&lt;br /&gt;
&lt;br /&gt;
   if auxV &amp;gt; 2 then&lt;br /&gt;
&lt;br /&gt;
     t:reset()&lt;br /&gt;
&lt;br /&gt;
   end&lt;br /&gt;
&lt;br /&gt;
   val = t:getElapsedSeconds()&lt;br /&gt;
&lt;br /&gt;
   if t:getElapsedSeconds() &amp;lt; 3 then&lt;br /&gt;
&lt;br /&gt;
     timingAdd = 10&lt;br /&gt;
&lt;br /&gt;
   else&lt;br /&gt;
&lt;br /&gt;
     timingAdd = 0&lt;br /&gt;
&lt;br /&gt;
   end&lt;br /&gt;
&lt;br /&gt;
   setTimingAdd(timingAdd)&lt;br /&gt;
&lt;br /&gt;
   print(&#039;Hello analog &#039; .. auxV .. &amp;quot; &amp;quot; .. val)&lt;br /&gt;
&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
```&lt;br /&gt;
&lt;br /&gt;
&amp;lt;nowiki&amp;gt;###&amp;lt;/nowiki&amp;gt; PWM&lt;br /&gt;
&lt;br /&gt;
```lua&lt;br /&gt;
&lt;br /&gt;
startPwm(0, 100, 0)&lt;br /&gt;
&lt;br /&gt;
function onTick()&lt;br /&gt;
&lt;br /&gt;
enable_pump = getSensor(&amp;quot;RPM&amp;quot;) &amp;gt; 700 and getSensor(&amp;quot;BatteryVoltage&amp;quot;) &amp;gt; 13 and getSensor(&amp;quot;VehicleSpeed&amp;quot;) &amp;lt; 60&lt;br /&gt;
&lt;br /&gt;
setPwmDuty(0, enable_pump and 1 or 0)&lt;br /&gt;
&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
```&lt;br /&gt;
&lt;br /&gt;
&amp;lt;nowiki&amp;gt;###&amp;lt;/nowiki&amp;gt; CAN transmit&lt;br /&gt;
&lt;br /&gt;
```lua&lt;br /&gt;
&lt;br /&gt;
function onTick()&lt;br /&gt;
&lt;br /&gt;
  clt = getSensor(&amp;quot;CLT&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
  print(&#039;CLT &#039; .. clt)&lt;br /&gt;
&lt;br /&gt;
  voltage0 = getSensor(&amp;quot;aux0&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
  txPayload = {}&lt;br /&gt;
&lt;br /&gt;
  txPayload[1] = math.floor(((voltage0/256) - math.floor(voltage0/256))*256)&lt;br /&gt;
&lt;br /&gt;
  txPayload[2] = math.floor(voltage0 / 256)&lt;br /&gt;
&lt;br /&gt;
  txCan(1, 0x600, 1, txPayload)&lt;br /&gt;
&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
```&lt;br /&gt;
&lt;br /&gt;
&amp;lt;nowiki&amp;gt;###&amp;lt;/nowiki&amp;gt; Set sensor value&lt;br /&gt;
&lt;br /&gt;
Use standard sensor names from TunerStudio Live Data.&lt;br /&gt;
&lt;br /&gt;
```lua&lt;br /&gt;
&lt;br /&gt;
-- do not configure the same physical input elsewhere&lt;br /&gt;
&lt;br /&gt;
vssSensor = Sensor.new(&amp;quot;VehicleSpeed&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
vssSensor:setTimeout(3000)&lt;br /&gt;
&lt;br /&gt;
function onTick()&lt;br /&gt;
&lt;br /&gt;
injectedVssValue = 123.4&lt;br /&gt;
&lt;br /&gt;
vssSensor:set(injectedVssValue)&lt;br /&gt;
&lt;br /&gt;
valFromSensor = getSensor(&amp;quot;VehicleSpeed&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
print(&amp;quot;VSS &amp;quot; .. valFromSensor)&lt;br /&gt;
&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
```&lt;br /&gt;
&lt;br /&gt;
&amp;lt;nowiki&amp;gt;###&amp;lt;/nowiki&amp;gt; CAN receive&lt;br /&gt;
&lt;br /&gt;
```lua&lt;br /&gt;
&lt;br /&gt;
canRxAdd(0x500)&lt;br /&gt;
&lt;br /&gt;
canRxAdd(0x570)&lt;br /&gt;
&lt;br /&gt;
function onCanRx(bus, id, dlc, data)&lt;br /&gt;
&lt;br /&gt;
print(&#039;got CAN id=&#039; .. id .. &#039; dlc=&#039; .. dlc)&lt;br /&gt;
&lt;br /&gt;
if id == 0x500 then&lt;br /&gt;
&lt;br /&gt;
  canState = data[1]&lt;br /&gt;
&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
if id == 0x570 then&lt;br /&gt;
&lt;br /&gt;
  mcu_standby()&lt;br /&gt;
&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
```&lt;br /&gt;
&lt;br /&gt;
&amp;lt;nowiki&amp;gt;###&amp;lt;/nowiki&amp;gt; Table / curve&lt;br /&gt;
&lt;br /&gt;
```lua&lt;br /&gt;
&lt;br /&gt;
tableIndex = findTableIndex(&amp;quot;duty&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
TurbochargerSpeed = getSensor(&amp;quot;TurbochargerSpeed&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
tps = getSensor(&amp;quot;Tps1&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
dutyCycle = table3d(tableIndex, TurbochargerSpeed, tps)&lt;br /&gt;
&lt;br /&gt;
sparkCutCurve = findCurveIndex(&amp;quot;sparkcut&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
sparkCutByTorque = curve(sparkCutCurve, torquex)&lt;br /&gt;
&lt;br /&gt;
```&lt;br /&gt;
&lt;br /&gt;
&amp;lt;nowiki&amp;gt;##&amp;lt;/nowiki&amp;gt; See also&lt;br /&gt;
&lt;br /&gt;
- [Variable Status Selector](&amp;lt;nowiki&amp;gt;https://content.epicefi.com/docs/variable_status_selector.html&amp;lt;/nowiki&amp;gt;) — names, types, and hashes for your firmware&lt;br /&gt;
&lt;br /&gt;
- [Lua examples](&amp;lt;nowiki&amp;gt;https://github.com/epicEFI/epicefi_fw/tree/master/firmware/controllers/lua/examples&amp;lt;/nowiki&amp;gt;) — vehicle-specific scripts&lt;br /&gt;
&lt;br /&gt;
- [Lua ternary operator](&amp;lt;nowiki&amp;gt;http://lua-users.org/wiki/TernaryOperator&amp;lt;/nowiki&amp;gt;) — Lua has no `?:`; use `and` / `or` patterns&lt;/div&gt;</summary>
		<author><name>Joesphan</name></author>
	</entry>
	<entry>
		<id>https://content.epicefi.com/wiki/index.php?title=Lua&amp;diff=496</id>
		<title>Lua</title>
		<link rel="alternate" type="text/html" href="https://content.epicefi.com/wiki/index.php?title=Lua&amp;diff=496"/>
		<updated>2026-08-18T02:49:32Z</updated>

		<summary type="html">&lt;p&gt;Joesphan: Created page with &amp;quot;safsefwefewfewfwef&amp;quot;&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;safsefwefewfewfwef&lt;/div&gt;</summary>
		<author><name>Joesphan</name></author>
	</entry>
	<entry>
		<id>https://content.epicefi.com/wiki/index.php?title=Welcome&amp;diff=495</id>
		<title>Welcome</title>
		<link rel="alternate" type="text/html" href="https://content.epicefi.com/wiki/index.php?title=Welcome&amp;diff=495"/>
		<updated>2026-08-18T02:49:24Z</updated>

		<summary type="html">&lt;p&gt;Joesphan: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;[[File:Epicefi.png|center|320px|link=|alt=epicEFI logo]]&lt;br /&gt;
&lt;br /&gt;
= epicEFI Wiki =&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;Firmware, tuning tools, docs, and community support for epicEFI.&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; style=&amp;quot;width:100%; text-align:left; margin-top:0.6em;&amp;quot;&lt;br /&gt;
! style=&amp;quot;width:33%;&amp;quot; | Firmware&lt;br /&gt;
! style=&amp;quot;width:33%;&amp;quot; | EpicTuner&lt;br /&gt;
! style=&amp;quot;width:34%;&amp;quot; | Community&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;[https://content.epicefi.com/firmware/ Download firmware]&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;br/&amp;gt;&amp;lt;span style=&amp;quot;font-size:90%;&amp;quot;&amp;gt;Stable builds and release files&amp;lt;/span&amp;gt;&lt;br /&gt;
| &#039;&#039;&#039;[https://github.com/AKCore/epictuner_public/releases/latest Get EpicTuner]&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;br/&amp;gt;&amp;lt;span style=&amp;quot;font-size:90%;&amp;quot;&amp;gt;Latest release on GitHub&amp;lt;/span&amp;gt;&lt;br /&gt;
| &#039;&#039;&#039;[https://discord.gg/TwVxa2wRZS Join Discord]&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;br/&amp;gt;&amp;lt;span style=&amp;quot;font-size:90%;&amp;quot;&amp;gt;Support + announcements + chat&amp;lt;/span&amp;gt;&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== Start here ==&lt;br /&gt;
* &#039;&#039;&#039;[[Quickstart]]&#039;&#039;&#039; — fastest way to get your ECU running&lt;br /&gt;
* &#039;&#039;&#039;[[Configuration]]&#039;&#039;&#039; — TunerStudio options and setup&lt;br /&gt;
&lt;br /&gt;
== Firmware ==&lt;br /&gt;
* &#039;&#039;&#039;[[Firmware#Updating the Firmware|Update firmware]]&#039;&#039;&#039; — step-by-step update guide&lt;br /&gt;
&lt;br /&gt;
== Hardware and compatibility ==&lt;br /&gt;
* &#039;&#039;&#039;[[Hardware]]&#039;&#039;&#039; — supported epicEFI hardware&lt;br /&gt;
* &#039;&#039;&#039;[[Triggers]]&#039;&#039;&#039; — check trigger pattern support&lt;br /&gt;
&lt;br /&gt;
== Advanced ==&lt;br /&gt;
* [[Lua]] — scripting documentation&lt;br /&gt;
&lt;br /&gt;
== Project ==&lt;br /&gt;
* &#039;&#039;&#039;[[About]]&#039;&#039;&#039; — what epicEFI is and how it works&lt;br /&gt;
* &#039;&#039;&#039;[[Contributing]]&#039;&#039;&#039; — how to help improve the project&lt;br /&gt;
&lt;br /&gt;
== AI Section ==&lt;br /&gt;
* &#039;&#039;&#039;[[Claude Discord AI Docs|Claude Discord AI Docs]]&#039;&#039;&#039; — attempts by claude to parse chat history&lt;br /&gt;
&lt;br /&gt;
== Need help? ==&lt;br /&gt;
If you’re stuck, join &#039;&#039;&#039;[https://discord.gg/TwVxa2wRZS Discord]&#039;&#039;&#039; for community support.&lt;/div&gt;</summary>
		<author><name>Joesphan</name></author>
	</entry>
	<entry>
		<id>https://content.epicefi.com/wiki/index.php?title=Welcome&amp;diff=494</id>
		<title>Welcome</title>
		<link rel="alternate" type="text/html" href="https://content.epicefi.com/wiki/index.php?title=Welcome&amp;diff=494"/>
		<updated>2026-08-18T02:37:46Z</updated>

		<summary type="html">&lt;p&gt;Joesphan: /* epicEFI Wiki */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;[[File:Epicefi.png|center|320px|link=|alt=epicEFI logo]]&lt;br /&gt;
&lt;br /&gt;
= epicEFI Wiki =&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;Firmware, tuning tools, docs, and community support for epicEFI.&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; style=&amp;quot;width:100%; text-align:left; margin-top:0.6em;&amp;quot;&lt;br /&gt;
! style=&amp;quot;width:33%;&amp;quot; | Firmware&lt;br /&gt;
! style=&amp;quot;width:33%;&amp;quot; | EpicTuner&lt;br /&gt;
! style=&amp;quot;width:34%;&amp;quot; | Community&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;[https://content.epicefi.com/firmware/ Download firmware]&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;br/&amp;gt;&amp;lt;span style=&amp;quot;font-size:90%;&amp;quot;&amp;gt;Stable builds and release files&amp;lt;/span&amp;gt;&lt;br /&gt;
| &#039;&#039;&#039;[https://github.com/AKCore/epictuner_public/releases/latest Get EpicTuner]&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;br/&amp;gt;&amp;lt;span style=&amp;quot;font-size:90%;&amp;quot;&amp;gt;Latest release on GitHub&amp;lt;/span&amp;gt;&lt;br /&gt;
| &#039;&#039;&#039;[https://discord.gg/TwVxa2wRZS Join Discord]&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;br/&amp;gt;&amp;lt;span style=&amp;quot;font-size:90%;&amp;quot;&amp;gt;Support + announcements + chat&amp;lt;/span&amp;gt;&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== Start here ==&lt;br /&gt;
* &#039;&#039;&#039;[[Quickstart]]&#039;&#039;&#039; — fastest way to get your ECU running&lt;br /&gt;
* &#039;&#039;&#039;[[Configuration]]&#039;&#039;&#039; — TunerStudio options and setup&lt;br /&gt;
&lt;br /&gt;
== Firmware ==&lt;br /&gt;
* &#039;&#039;&#039;[[Firmware#Updating the Firmware|Update firmware]]&#039;&#039;&#039; — step-by-step update guide&lt;br /&gt;
&lt;br /&gt;
== Hardware and compatibility ==&lt;br /&gt;
* &#039;&#039;&#039;[[Hardware]]&#039;&#039;&#039; — supported epicEFI hardware&lt;br /&gt;
* &#039;&#039;&#039;[[Triggers]]&#039;&#039;&#039; — check trigger pattern support&lt;br /&gt;
&lt;br /&gt;
== Advanced ==&lt;br /&gt;
* &#039;&#039;&#039;[https://www.google.com Lua]&#039;&#039;&#039; — scripting documentation&lt;br /&gt;
&lt;br /&gt;
== Project ==&lt;br /&gt;
* &#039;&#039;&#039;[[About]]&#039;&#039;&#039; — what epicEFI is and how it works&lt;br /&gt;
* &#039;&#039;&#039;[[Contributing]]&#039;&#039;&#039; — how to help improve the project&lt;br /&gt;
&lt;br /&gt;
== AI Section ==&lt;br /&gt;
* &#039;&#039;&#039;[[Claude Discord AI Docs|Claude Discord AI Docs]]&#039;&#039;&#039; — attempts by claude to parse chat history&lt;br /&gt;
&lt;br /&gt;
== Need help? ==&lt;br /&gt;
If you’re stuck, join &#039;&#039;&#039;[https://discord.gg/TwVxa2wRZS Discord]&#039;&#039;&#039; for community support.&lt;/div&gt;</summary>
		<author><name>Joesphan</name></author>
	</entry>
	<entry>
		<id>https://content.epicefi.com/wiki/index.php?title=Welcome&amp;diff=436</id>
		<title>Welcome</title>
		<link rel="alternate" type="text/html" href="https://content.epicefi.com/wiki/index.php?title=Welcome&amp;diff=436"/>
		<updated>2026-03-08T07:21:05Z</updated>

		<summary type="html">&lt;p&gt;Joesphan: /* AI Section */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;[[File:Epicefi.png|center|320px|link=|alt=epicEFI logo]]&lt;br /&gt;
&lt;br /&gt;
= epicEFI Wiki =&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;Firmware, tuning tools, docs, and community support for epicEFI.&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; style=&amp;quot;width:100%; text-align:left; margin-top:0.6em;&amp;quot;&lt;br /&gt;
! style=&amp;quot;width:33%;&amp;quot; | Firmware&lt;br /&gt;
! style=&amp;quot;width:33%;&amp;quot; | EpicTuner&lt;br /&gt;
! style=&amp;quot;width:34%;&amp;quot; | Community&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;[https://content.epicefi.com/firmware/ Download firmware]&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;br/&amp;gt;&amp;lt;span style=&amp;quot;font-size:90%;&amp;quot;&amp;gt;Stable builds and release files&amp;lt;/span&amp;gt;&lt;br /&gt;
| &#039;&#039;&#039;[https://github.com/AKCore/epictuner_public/releases/latest Get EpicTuner]&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;br/&amp;gt;&amp;lt;span style=&amp;quot;font-size:90%;&amp;quot;&amp;gt;Latest release on GitHub&amp;lt;/span&amp;gt;&lt;br /&gt;
| &#039;&#039;&#039;[https://discord.gg/TwVxa2wRZS Join Discord]&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;br/&amp;gt;&amp;lt;span style=&amp;quot;font-size:90%;&amp;quot;&amp;gt;Support + announcements + chat&amp;lt;/span&amp;gt;&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== Start here ==&lt;br /&gt;
* &#039;&#039;&#039;[[Quickstart]]&#039;&#039;&#039; — fastest way to get your ECU running&lt;br /&gt;
* &#039;&#039;&#039;[[Configuration]]&#039;&#039;&#039; — TunerStudio options and setup&lt;br /&gt;
&lt;br /&gt;
== Firmware ==&lt;br /&gt;
* &#039;&#039;&#039;[[Firmware#Updating the Firmware|Update firmware]]&#039;&#039;&#039; — step-by-step update guide&lt;br /&gt;
&lt;br /&gt;
== Hardware and compatibility ==&lt;br /&gt;
* &#039;&#039;&#039;[[Hardware]]&#039;&#039;&#039; — supported epicEFI hardware&lt;br /&gt;
* &#039;&#039;&#039;[[Triggers]]&#039;&#039;&#039; — check trigger pattern support&lt;br /&gt;
&lt;br /&gt;
== Project ==&lt;br /&gt;
* &#039;&#039;&#039;[[About|About]]&#039;&#039;&#039; — what epicEFI is and how it works&lt;br /&gt;
* &#039;&#039;&#039;[[Contributing]]&#039;&#039;&#039; — how to help improve the project&lt;br /&gt;
&lt;br /&gt;
== AI Section ==&lt;br /&gt;
* &#039;&#039;&#039;[[Claude Discord AI Docs|Claude Discord AI Docs]]&#039;&#039;&#039; — attempts by claude to parse chat history&lt;br /&gt;
&lt;br /&gt;
== Need help? ==&lt;br /&gt;
If you’re stuck, join &#039;&#039;&#039;[https://discord.gg/TwVxa2wRZS Discord]&#039;&#039;&#039; for community support.&lt;/div&gt;</summary>
		<author><name>Joesphan</name></author>
	</entry>
	<entry>
		<id>https://content.epicefi.com/wiki/index.php?title=Welcome&amp;diff=435</id>
		<title>Welcome</title>
		<link rel="alternate" type="text/html" href="https://content.epicefi.com/wiki/index.php?title=Welcome&amp;diff=435"/>
		<updated>2026-03-08T07:20:42Z</updated>

		<summary type="html">&lt;p&gt;Joesphan: /* Start here */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;[[File:Epicefi.png|center|320px|link=|alt=epicEFI logo]]&lt;br /&gt;
&lt;br /&gt;
= epicEFI Wiki =&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;Firmware, tuning tools, docs, and community support for epicEFI.&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; style=&amp;quot;width:100%; text-align:left; margin-top:0.6em;&amp;quot;&lt;br /&gt;
! style=&amp;quot;width:33%;&amp;quot; | Firmware&lt;br /&gt;
! style=&amp;quot;width:33%;&amp;quot; | EpicTuner&lt;br /&gt;
! style=&amp;quot;width:34%;&amp;quot; | Community&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;[https://content.epicefi.com/firmware/ Download firmware]&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;br/&amp;gt;&amp;lt;span style=&amp;quot;font-size:90%;&amp;quot;&amp;gt;Stable builds and release files&amp;lt;/span&amp;gt;&lt;br /&gt;
| &#039;&#039;&#039;[https://github.com/AKCore/epictuner_public/releases/latest Get EpicTuner]&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;br/&amp;gt;&amp;lt;span style=&amp;quot;font-size:90%;&amp;quot;&amp;gt;Latest release on GitHub&amp;lt;/span&amp;gt;&lt;br /&gt;
| &#039;&#039;&#039;[https://discord.gg/TwVxa2wRZS Join Discord]&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;br/&amp;gt;&amp;lt;span style=&amp;quot;font-size:90%;&amp;quot;&amp;gt;Support + announcements + chat&amp;lt;/span&amp;gt;&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== Start here ==&lt;br /&gt;
* &#039;&#039;&#039;[[Quickstart]]&#039;&#039;&#039; — fastest way to get your ECU running&lt;br /&gt;
* &#039;&#039;&#039;[[Configuration]]&#039;&#039;&#039; — TunerStudio options and setup&lt;br /&gt;
&lt;br /&gt;
== Firmware ==&lt;br /&gt;
* &#039;&#039;&#039;[[Firmware#Updating the Firmware|Update firmware]]&#039;&#039;&#039; — step-by-step update guide&lt;br /&gt;
&lt;br /&gt;
== Hardware and compatibility ==&lt;br /&gt;
* &#039;&#039;&#039;[[Hardware]]&#039;&#039;&#039; — supported epicEFI hardware&lt;br /&gt;
* &#039;&#039;&#039;[[Triggers]]&#039;&#039;&#039; — check trigger pattern support&lt;br /&gt;
&lt;br /&gt;
== Project ==&lt;br /&gt;
* &#039;&#039;&#039;[[About|About]]&#039;&#039;&#039; — what epicEFI is and how it works&lt;br /&gt;
* &#039;&#039;&#039;[[Contributing]]&#039;&#039;&#039; — how to help improve the project&lt;br /&gt;
&lt;br /&gt;
== AI Section ==&lt;br /&gt;
* &#039;&#039;&#039;[[Claude Discord AI Docs|Claude Discord AI Docs]]&#039;&#039;&#039; — attempts by claude to parse chat history&lt;br /&gt;
&lt;br /&gt;
chicken&lt;br /&gt;
&lt;br /&gt;
== Need help? ==&lt;br /&gt;
If you’re stuck, join &#039;&#039;&#039;[https://discord.gg/TwVxa2wRZS Discord]&#039;&#039;&#039; for community support.&lt;/div&gt;</summary>
		<author><name>Joesphan</name></author>
	</entry>
</feed>