Dynam Labs VCU+ Plugin Reference Manual
June 18, 2025
Overview
Plugins enable users to add custom functionality to the VCU+ while keeping its core firmware lightweight and efficient. By allowing customization without modifying the core, plugins ensure the system remains maintainable and adaptable to a wide range of application-specific needs.
With plugins, users can implement tailored features, such as custom control algorithms or data processing workflows, without the overhead of embedding these directly into the core firmware. This flexibility empowers users to quickly adapt the VCU to their unique requirements.
This reference manual provides a comprehensive guide to using plugins, including an overview of available functions, configuration options, and instructions for writing, testing, and deploying custom Lua-based logic.
The plugin language is Lua, chosen for its simplicity, low learning curve, and lightweight nature. Lua is widely regarded for its ease of integration into embedded systems and its ability to execute scripts efficiently, making it ideal for the resource-constrained environment of the VCU+. Its straightforward syntax and minimal setup allow both novice and experienced developers to quickly write and test custom logic.
For more information about Lua, refer to the official documentation:
This reference manual provides a comprehensive guide to using plugins, including an overview of available functions, configuration options, and instructions for writing, testing, and deploying custom Lua-based logic.
Core Loop
The core logic of a plugin is implemented within the onTick()
function. This function is automatically called by the VCU at a fixed interval, serving as the main loop for your script. All continuous tasks, such as reading sensors, making decisions, and controlling outputs, should be placed within this function.
onTick()
This function is called repeatedly by the VCU firmware. Implement your main control logic inside this function.
Example Use Case:Continuously monitoring battery temperature and activating a cooling fan if it exceeds a threshold.
-- This function will be called on every tick of the plugin execution cycle
function onTick()
local temp = getSensor(BatteryTemp)
if temp > 80 then
setOut(HS1) -- Turn on cooling fan
else
clearOut(HS1) -- Turn off cooling fan
end
end
Functions
The following is a list of all currently supported functions available for use in plugins. These functions form the building blocks for creating powerful and highly customized logic within your plugins, giving you full control over VCU outputs, sensor data, communication, and control algorithms. The list is continuously growing as more functions are added.
setOut(pin)
This function sets the specified output pin to its active state:
- High-Side Outputs: The pin transitions to a high state (voltage applied).
- Low-Side Outputs: The pin transitions to a low state (connected to ground).
Activating a high-side output pin to power a relay or a low-side output pin for devices requiring a ground-side switch.
-- Activate a high-side output 1
setOut(HS1)
-- Activate a low-side output 2
setOut(LS2)
clearOut(pin)
This function clears the specified output pin, setting it to its inactive state:
- High-Side Outputs: The pin transitions to a low state (voltage removed).
- Low-Side Outputs: The pin transitions to a high state (disconnected from ground).
Deactivating a relay or turning off a light connected to the VCU.
-- Deactivate the high-side output 1
clearOut(HS1)
-- Deactivate the low-side output 2
clearOut(LS2)
getSensor(sensorID)
Retrieves the current value of the specified sensor. This allows the plugin to access real-time data, such as temperature, a speed, or pressure. Returns nil if the sensor state is invalid. Always check the return value before performing arithmetic operations. The return value is always a floating point number, so it must be rounded or converted to an integer before using it in bitwise operations.
Example Use Case:Reading a temperature sensor value for dynamic control logic.
List of available Sensors:
AcceleratorPedal
AcceleratorPedalPrimary
AcceleratorPedalSecondary
BatteryCurrent
BatteryVoltage
BatteryTemp
BatteryCharge
InverterCurr
DI_inverterTemp
DI_statorTemp
DI_heatSinkTemp
DI_inletTemp
DIS_inverterTemp
DIS_statorTemp
DIS_heatSinkTemp
DIS_inletTemp
Temp1
Temp2
Temp3
Temp4
RPM
Din1
Din2
Din3
Din4
Din5
Din6
Din7
Din8
Din9
Din10
Din11
Din12
Din13
Din14
Din15
DinNone
LVBatt
CAN_KEYPAD_B1
CAN_KEYPAD_B2
CAN_KEYPAD_B3
CAN_KEYPAD_B4
CAN_KEYPAD_B5
CAN_KEYPAD_B6
CAN_KEYPAD_B7
CAN_KEYPAD_B8
Ignition
Ain1
Ain2
Ain3
Ain4
Ain5
Ain6
Ain7
Ain8
Gear
-- Retrieve the value from Temperature sensor 1
local temperature = getSensor(Temp1)
-- Retrieve Accelerator pedal value and use it in logic
local pedal = getSensor(AcceleratorPedal)
sendCAN(canBus, canID, data)
Sends a CAN message with the specified ID and data payload. Enables communication with other devices on the CAN bus.
canBus
: The physical bus to send the message on. (CAN1, CAN2 or CAN3)canID
: The identifier of the CAN message.data
: The payload, typically a byte array.
Sending commands to external controllers or reporting custom data.
-- Send a CAN message on CAN2 with ID 0x123 and data payload {0x01, 0x02, 0x03}
sendCAN(CAN2, 0x123, {0x01, 0x02, 0x03})
-- Example of sending a command to an external device on the CAN2 bus
sendCAN(CAN2, 0x200, {0xFF, 0xAA, 0x55})
receiveCAN(canBus, canID, handler)
Registers a CAN handler that gets called when a CAN message is received. Only gets called when a
message on the bus with correct canID is received. This function only needs to be called once,
from global scope. The message handler gets called at the loop rate - prior to the onTick()
function, not at the exact moment the CAN message is received.
canBus
: The physical bus to send the message on. (CAN1/CAN2/CAN3)canID
: The identifier of the CAN message.handler
: function that gets called when CAN message is received
function handleMessage(data)
local temp = (data[2] << 8) | data[1]
print("Temp DLC: " .. #data .. " Temp: " .. temp)
end
receiveCAN(CAN2, 0x123, handleMessage)
newPID(p, i, d)
Creates a new PID controller instance with the specified tuning parameters.
p
: Proportional gaini
: Integral gaind
: Derivative gain
Initializing a PID controller to maintain target speeds or stabilize temperatures.
-- Create a PID controller with P=1.0, I=0.5, D=0.1
local pid = newPID(1.0, 0.5, 0.1)
pid:compute(target, actual)
Computes the control output of an existing PID controller based on a target value and the actual measured value. This function is supposed to be called in a loop.
target
: The desired setpoint value.actual
: The current measured value.
-- Create a PID controller
local pid = newPID(1.0, 0.5, 0.1)
-- Compute the control output
local targetTemp = 100 -- Target temperature
local actualTemp = getSensor(Temp1) -- Assume Temp1 provides actual temperature
local controlOutput = pid:compute(targetTemp, actualTemp)
setPWMFreq(channel, frequency)
Sets the frequency of the specified PWM (Pulse Width Modulation) channel in Hertz (Hz). This determines how many PWM cycles are generated per second.
channel
: The PWM channel to configure (e.g., PWM1, PWM2).frequency
: The desired frequency in Hertz.
setPWMFreq(PWM1, 750)
setPWMDuty(channel, duty)
Sets the duty cycle of the specified PWM (Pulse Width Modulation) a channel in percent (%).
channel
: The PWM channel to configure (e.g., PWM1, PWM2).duty
: The desired duty cycle in percent.
setPWMDuty(PWM1, 25)
print(string)
Prints a message to the plugin log inside of Bolt
.
string
: String to print
local ain3_val = getSensor(Ain3)
print("Ain3 Value: " .. ain_val)
printStatistics()
Prints detailed memory usage and system statistics to the plugin log.
Example Use Case:Useful for debugging memory leaks and monitoring performance.
-- Print memory and performance stats to the log
printStatistics()
getTime()
Returns elapsed time in ms since the VCU booted. Example Use Case: Handle timing operations
if getTime() > 2500 then
someFunction()
end
requestGear(gear)
(Requires firmware v1.1.6
or later)
Requests a new gear.
gear
: Requested Gear (GEAR_P, GEAR_R, GEAR_N, GEAR_D)
if someVar == 2500 then
requestGear(GEAR_D)
end
setDrivemode(mode)
(Requires firmware v1.1.7
or later)
Sets a new drive-mode.
mode
: New drive mode (CHILL, NORMAL, SPORT, VALET)
if msg[1] == 100 then
setDrivemode(SPORT)
end
setChargeLimit(limit)
(Requires firmware v1.1.7
or later)
Sets a new charge(= regen) limit.
limit
: New charge limit in Amperes.
if id == BMS_LIMIT then
setChargeLimit(msg[2])
end
setDischargeLimit(limit)
(Requires firmware v1.1.7
or later)
Sets a new discharge limit.
limit
: New discharge limit in Amperes.
if id == BMS_LIMIT then
setDischargeLimit(msg[3])
end
GPSFixAcquired()
(Requires firmware v1.1.7
or later)
Checks if a valid GPS fix has been acquired. Example Use Case: Custom CAN message implementations. Returns: True if a valid GPS fix has been acquired, false otherwise.
if GPSFixAcquired() then
send_GPS_data()
end
getGPSData(data)
(Requires firmware v1.1.7
or later)
Fetches GPS data.
data
: GPS data requested. Supported types:- LATITUDE (deg)
- LONGITUDE (deg)
- ALTITUDE (m)
- GROUNDSPEED (km/h)
- HEADING (deg)
- TIME (unix timestamp, seconds since epoch)
Example Use Case: Custom CAN message implementations. Returns: Value of the data requested, nil if no valid GPS fix has been acquired.
local heading = getGPSData(HEADING)
getPrechargeState()
(Requires firmware v1.1.8
or later)
Retrieves the current state of the pre-charge sequence.
Example Use Case:Check if the pre-charge sequence is complete before closing contactors.
Returns: A number representing the pre-charge state (e.g., 0 for inactive, 1 for in-progress, 2 for complete).
if getPrechargeState() == 2 then
-- Precharge is complete, ready to close contactors
setOut(HS1)
end
setPrechargeDone()
(Requires firmware v1.1.8
or later)
Manually marks the pre-charge sequence as complete.
Example Use Case:Used in custom pre-charge logic where the completion criteria is determined by the plugin.
-- If custom conditions are met, mark precharge as done
if getSensor(BatteryVoltage) > 300 then
setPrechargeDone()
end
Constants
The plugin environment provides a set of predefined constants for easy access to pins, channels, and system states.
Output Pins
- High-Side Outputs:
HS1
,HS2
,HS3
,HS4
,HS5
,HS6
,HS7
,HS8
- Low-Side Outputs:
LS1
,LS2
,LS3
,LS4
,LS5
,LS6
,LS7
,LS8
,LS9
,LS10
,LS11
,LS12
- CAN Keypad LEDs:
CAN-KEY1
,CAN-KEY2
,CAN-KEY3
,CAN-KEY4
,CAN-KEY5
,CAN-KEY6
,CAN-KEY7
,CAN-KEY8
PWM Channels
PWM1
,PWM2
,PWM3
,PWM4
,PWM5
,PWM6
,PWM7
- Hardware revision v1 only:
PWM8
,PWM9
,PWM10
CAN Buses
CAN1
,CAN2
,CAN3
Gear Selection
GEAR_P
(Park)GEAR_R
(Reverse)GEAR_N
(Neutral)GEAR_D
(Drive)
Drive Modes
CHILL
NORMAL
SPORT
VALET
GPS Data Types
LATITUDE
LONGITUDE
ALTITUDE
GROUNDSPEED
HEADING
TIME