# 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: - [Official Lua Documentation](https://www.lua.org/docs.html) 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. **Sample Code:** ```lua -- 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 ``` --- ### `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). **Example Use Case:** Activating a high-side output pin to power a relay or a low-side output pin for devices requiring a ground-side switch. **Sample Code:** ```lua -- 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). **Example Use Case:** Deactivating a relay or turning off a light connected to the VCU. **Sample Code:** ```lua -- 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` **Sample Code:** ```lua -- 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. **Arguments:** * `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. **Example Use Case:** Sending commands to external controllers or reporting custom data. **Sample Code:** ```lua -- 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. **Arguments:** * `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 **Example Use Case:** Receiving data from external controllers. **Sample Code:** ```lua function handleMessage(data) local temp = (data[2] 2500 then someFunction() end ``` --- ### `requestGear(gear)` **Firmware:** 1.1.6 Requests a new gear. **Arguments:** * `gear`: Requested Gear (GEAR_P, GEAR_R, GEAR_N, GEAR_D) **Example Use Case:** Custom shifter implementations **Sample Code:** ```lua if someVar == 2500 then requestGear(GEAR_D) end ``` --- ### `setDrivemode(mode)` **Firmware:** 1.1.7 Sets a new drive-mode. **Arguments:** * `mode`: New drive mode (CHILL, NORMAL, SPORT, VALET) **Example Use Case:** Conditional drive mode changes **Sample Code:** ```lua if msg[1] == 100 then setDrivemode(SPORT) end ``` --- ### `setChargeLimit(limit)` **Firmware:** 1.1.7 Sets a new charge(= regen) limit. **Arguments:** * `limit`: New charge limit in Amperes. **Example Use Case:** Custom BMS implementations **Sample Code:** ```lua if id == BMS_LIMIT then setChargeLimit(msg[2]) end ``` --- ### `setDischargeLimit(limit)` **Firmware:** 1.1.7 Sets a new discharge limit. **Arguments:** * `limit`: New discharge limit in Amperes. **Example Use Case:** Custom BMS implementations **Sample Code:** ```lua if id == BMS_LIMIT then setDischargeLimit(msg[3]) end ``` --- ### `GPSFixAcquired()` **Firmware:** 1.1.7 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. **Sample Code:** ```lua if GPSFixAcquired() then send_GPS_data() end ``` --- ### `getGPSData(data)` **Firmware:** 1.1.7 Fetches GPS data. **Arguments:** * `data`: <> GPS data requested. Supported types: LATITUDE (deg **Example Use Case:** Custom CAN message implementations. **Returns:** Value of the data requested, nil if no valid GPS fix has been acquired. **Sample Code:** ```lua local heading = getGPSData(HEADING) ``` --- ### `getPrechargeState()` **Firmware:** 1.1.8 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). **Sample Code:** ```lua if getPrechargeState() == 2 then -- Precharge is complete, ready to close contactors setOut(HS1) end ``` --- ### `setPrechargeDone()` **Firmware:** 1.1.8 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. **Sample Code:** ```lua -- 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`