Skip to main content

How to use Lua scripts

First use

Setting Up Lua Scripting with Visual Studio Code

To make Lua scripting easier, follow these steps to set up Visual Studio Code with the Lua extension:

  1. Install Visual Studio Code

    • If you don't have it yet, download and install Visual Studio Code.
  2. Add the Lua Extension

    • Open Visual Studio Code.
    • Go to the Extensions view (click on the square icon on the sidebar or press Ctrl+Shift+X).
    • Search for "Lua" and install the Lua extension by sumneko.
      image.png
  3. Create a Folder for Libraries

    • Make a new folder on your computer to store the emdrive library. For example, you can name it D:\LuaLibraries.
  4. Configure Developer Mode and Add Library Path

    • Open Visual Studio Code.
    • Press Ctrl+P to open the search bar.
    • Type >settings and select "Open User Settings (JSON)" from the list.
      image.png


  5. Edit User Settings (JSON)

    • In the opened settings.json file, add the following code.
      "Lua.misc.parameters": [
          "--develop=true"
      ],
      "Lua.workspace.checkThirdParty": true,
      "Lua.workspace.userThirdParty": ["path_to_our_library"]
    • Make sure to keep any existing code intact. Add a comma at the end of the last line of the existing code, then paste the new code below it. Update the path to match your folder (D:/LuaLibraries). Use forward slashes instead of backslashes.
  6. Save and Close

    • Save the changes to settings.json.
    • Close Visual Studio Code.


Example of "settings.json":

image.png


Getting Started with Lua Scripting

  1. Create a New Folder

    • Make a new folder for your project.
  2. Open the Folder with Visual Studio Code

    • Right-click the folder and select "Open with Code."
      image.png
    • This will open Visual Studio Code with your new folder as the workspace.
  3. Create a New Lua Script

    • In the Explorer window of Visual Studio Code, right-click and select "New File."
      image.png
    • Name the file firstscript.lua.

Using the emdrive Library

Option 1: Automatic Setup

  1. Add Code to Script

    • In firstscript.lua, add the following line:

      require('emdrive')
  2. Apply Path Modification

    • A pop-up window will appear in the bottom right corner. Click "Apply and modify."
    • After applying, delete the require('emdrive') line.
      image.png
  3. Check .vscode Folder

    • A new folder named .vscode will be created.
    • Inside, there will be a settings.json file containing the path to the library.

Option 2: Manual Setup

  1. Create .vscode Folder

    • Manually create a .vscode folder in your workspace.
  2. Create settings.json File

    • Inside the .vscode folder, create a settings.json file.
  3. Add Library Path to settings.json

    • Add the following code to settings.json:


      {
          "lua.workspace.library": [
              "D:/LuaLibraries"
          ]
      }

Examples

Example 1: "Hello World"

Description:

Print "Hello World" every 100ms to IO_Output.

To make the script work, you need to have two functions: Initialize and Loop.

  1. Copy the Code

    Copy the following code to an example.lua file:

    function Initialize()	
    	LoopPeriodMs = 100
    end
    
    function Loop()
        IO.Write("Hello World ")
    end
  2. Load the Script on the Inverter

    • Go to 0x2040 0x05 - Script.
    • Select "Write from file" and click "...".
    • A pop-up window will appear. Locate the saved script and click "Open".
    • Click "Write".

  3. Check if the Script is Valid

    • Go to 0x2040 0x02 - Status.
    • The value "3" indicates the downloaded file is valid.
  4. Run the Script

    • Set 0x2040 0x03 - Control to 2.
    • If the script is running, the status value should be "5".
  5. View the Output

    • Go to 0x2040 0x07 - IO_output.
    • Click "Read" to see the "Hello World" string in the Description window.

Example 2: Data types & variables

Example 2.1: Data types

Lua is a lightweight, high-level programming language known for its simplicity and flexibility. It has several basic data types, each serving a different purpose. Here are the main data types in Lua along with examples for each:

  1. Nil
    Represents the absence of a value.
  2. Boolean
    Represents a boolean value, either true or false.
  3. Number
    Represents both integer and floating-point numbers.
  4. String
    Represents a sequence of characters.
  5. Table
    Represents associative arrays, which can be used as arrays, dictionaries, or other data structures.
  6. Function
    Represents a callable function.
function Initialize()	
	LoopPeriodMs = 100
end

function Loop()
    
    -- 1. Nil
    local myVariable = nil
    IO.Print(myVariable)  -- Output: nil

    -- 2. Boolean
    local isTrue = true
    local isFalse = false
    IO.Print(isTrue)  -- Output: true
    IO.Print(isFalse)  -- Output: false

    -- 3. Number
    local integerNumber = 42
    local floatingNumber = 3.14
    IO.Print(integerNumber)  -- Output: 42
    IO.Print(floatingNumber)  -- Output: 3.14

    -- 4. String
    local myString = "Hello, Lua!"
    IO.Print(myString)  -- Output: Hello, Lua!

    -- 5. Table
    local myTable = { key1 = "value1", key2 = "value2" }
    IO.Print(myTable.key1)  -- Output: value1
    IO.Print(myTable.key2)  -- Output: value2

    local arrayTable = { "apple", "banana", "cherry" }
    IO.Print(arrayTable[1])  -- Output: apple

    -- 6. Function
    local function myFunction(a, b)
      return a + b
    end
    IO.Print(myFunction(2, 3))  -- Output: 5
 
    -- 6. Anonymous function
    local anonFunction = function(x, y)
      return x * y
    end
    IO.Print(anonFunction(4, 5))  -- Output: 20

end

Example 2.2: Variables

  1. Global variables
    Global variables are accessible from anywhere in the program unless shadowed by a local variable of the same name. By default, any variable declared without the local keyword is global.
    myGlobalVariable = 10  -- Global variable
    
    function printGlobal()
      print(myGlobalVariable)
    end
    
    printGlobal()  -- Output: 10
  2. Local Variables
    Local variables are only accessible within the block or function where they are declared. They help avoid polluting the global namespace and can be used to manage scope more effectively.
    local myLocalVariable = 20  -- Local variable
    
    function printLocal()
      local myLocalVariable = 30  -- Local to this function
      print(myLocalVariable)
    end
    
    printLocal()  -- Output: 30
    print(myLocalVariable)  -- Output: 20
  3. Table fields
    Variables can also be fields of tables, allowing for the creation of more complex data structures like arrays, dictionaries, and objects.
    local myTable = {
      field1 = "Hello",
      field2 = "World"
    }
    
    print(myTable.field1)  -- Output: Hello
    print(myTable.field2)  -- Output: World

Variable scope

  • Global Scope: Variables declared outside of any function or block are global by default.
  • Local Scope: Variables declared with the local keyword within a function, block, or loop are local to that block.
  1. Global vs. Local
    myGlobal = "I am global"
    
    function testScope()
      local myLocal = "I am local"
      print(myGlobal)  -- Accessible
      print(myLocal)   -- Accessible
    end
    
    testScope()
    
    print(myGlobal)  -- Accessible
    -- print(myLocal) -- Error: myLocal is not accessible here

  2. Local Variable Shadowing
    local x = 5  -- Local variable in the main chunk
    
    function shadowTest()
      local x = 10  -- Local variable in the function
      print(x)  -- Output: 10
    end
    
    shadowTest()
    print(x)  -- Output: 5

Global variables and tables use a lot of memory, especially in embedded systems. To save memory, it's best to use local variables whenever possible. 
If you use a global variable at the start of your code and don't need it later, clear the global variable to free up memory. You can do this by adding the following code:

myGlobalVariable = nil

Example 3: Inputs/Outputs

Example 3.1: Use of digital inputs & outputs

Turn low side 1 - ON when switch on digital pin 1 is switched ON.

function Initialize()	
	LoopPeriodMs = 10
end

function Loop()

    local switch = Digital.Get(DIGITAL_IN_1)

    if switch == 1.0 then
        Digital.Set(LOW_SIDE_1, 1)
    else
        Digital.Set(LOW_SIDE_1, 0)
    end
end

Example 3.2: Use of analog inputs

Print analog value of analog input 1 when switch on digital pin 1 is switched ON.

function Initialize()	
	LoopPeriodMs = 10
end

function Loop()

    local switch = Digital.Get(DIGITAL_IN_1)

    if switch == 1.0 then
        local rawVal = Analog.Get(ANALOG_IN_1)
        IO.Print(rawVal)    
    end
end

Loop period is set to 1000ms
LED is connected on low side 1.
Blink LED every 0.5s with Time.WaitMs().

function Initialize()	
	LoopPeriodMs = 1000
end

function Loop()

    local ledState = Digital.Get(LOW_SIDE_1)

    if(ledState == 0.0) then
        Digital.Set(LOW_SIDE_1, 1.0)
    else
        Digital.Set(LOW_SIDE_1, 0.0)
    end

    Time.WaitMs(500)

end

If we set the LoopPeriodMs to 100ms, the script stops running and shows a status of "6", meaning "Script timed out and was stopped."

Here are the key points to understand:

  • Time.WaitMs() Function: This function pauses the script for a specified time. In our example, it pauses for 0.5 seconds.
  • LoopPeriodMs Setting: We set the script to loop every 100ms.
  • Error Explanation: Since the script must loop every 100ms but pauses for 0.5 seconds, it causes an error.

Important Note: Be very careful when using the Time.WaitMs() function to avoid such errors.

We want the LED to blink every 0.5 seconds, while the main loop runs every 10ms. Here's how we do it:


  1. Count Loops: We count each loop from 1 to 50.
  2. Multiply Counter: Multiply the counter by the loop period (10ms).
  3. Check Reminder: Divide the result by 500.
  4. Toggle Output: If the remainder is 0, we toggle the LED.
Main_loop_period = 10
Counter = 1


function Initialize()	
	LoopPeriodMs = Main_loop_period
end

function Loop()

    if ((Counter)*Main_loop_period)%500==0 then
    
        local ledState = Digital.Get(LOW_SIDE_1)

        if(ledState == 0.0) then
            Digital.Set(LOW_SIDE_1, 1.0)
        else
            Digital.Set(LOW_SIDE_1, 0.0)
        end

    end
 
    if Counter<50 then
        Counter=Counter+1
    else
        Counter=1
    end

end


The script initializes a start time variable and defines two functions. In the Loop function, the script checks if the StartTime is nil and if so, sets it to the current time in milliseconds.
The Loop function then continuously gets the current time in milliseconds. If 500 milliseconds have passed since the StartTime, it resets the StartTime to the current time.
It then gets the state of a digital output LOW_SIDE_1.
If LOW_SIDE_1 is off (state is 0.0), it turns it on (state to 1.0).
If LOW_SIDE_1 is on (state is 1.0), it turns it off (state to 0.0).

This process repeats every 500 milliseconds, toggling the state of LOW_SIDE_1 each time.

StartTime = nil

function Initialize()	
	LoopPeriodMs = 10
end

function Loop()

    --Execute only once at the start of the loop
    if(StartTime == nil) then
        StartTime = Time.GetMs()
    end

    --Chechk every loop what is the time
    local CurrentTime = Time.GetMs() 

    if (CurrentTime - StartTime >= 500) then
        StartTime = CurrentTime

        local ledState = Digital.Get(LOW_SIDE_1)

        if(ledState == 0.0) then
            Digital.Set(LOW_SIDE_1, 1.0)
        else
            Digital.Set(LOW_SIDE_1, 0.0)
        end

    end
end

Example 5: CAN send

Example 5.1: (CAN send simple message)

To send a CAN message every 100ms with an ID of 0x205 and data value 500 using the first 2 bytes, follow these steps: 

image.png


  1. Use Template:

    • Start with example 4.3 as a template.
  2. Add Variables and Functions:

    • Define a CanID variable.
    • Initialize CAN with the CAN.Initialize() function.
  3. Sending Parameters:

    • Use the CAN_TX_ONLY parameter for sending.
    • Messages will not be extended.
    • Set filters to 0 since we are only sending.
  4. Modify the Code:

    • Delete the code for toggling the output.
  5. Create Custom Function:

    • Create a function that is called every 100ms.
    • Name the argument Data_raw.
    • Split Data_raw into 2 bytes.
    • Send the data using the CAN.Send() function.
CanID = 0x205

StartTime = nil

function Initialize()

    CAN.Initialize(CAN_TX_ONLY,false,0,0)

	LoopPeriodMs = 10
end

function Loop()

    --Execute only once at the start of the loop
    if(StartTime == nil) then
        StartTime = Time.GetMs()
    end

    --Chechk every loop what is the time
    local CurrentTime = Time.GetMs() 

    if (CurrentTime - StartTime >= 100) then
        StartTime = CurrentTime
        SendData(500)
    end
end

function SendData(Data_raw)
    local val = Data_raw
    local byte0 = math.floor(val) & 0xFF
    local byte1 = (math.floor(val) & 0xFF00) >> 8
    CAN.Send(CanID,{byte0,byte1,0,0,0,0,0,0})
end

Example 5.2 : CAN send extended message (J1939)

We will do the same as in example 5.1 except we will send an extended message (It will be a J1939 message PH3 - the data order might be different in the standard).

 
ID = 0x18FF8203. 

The only thing we need to change is 2 lines, we need to change the CanID and in the CAN.Initialize() function, the boolean value to true. If we change only the CanID then the message will still be sent out but the ID will be 0x199 (last 3 digits of - ID 0x18FF8199).

CanID = 0x18FF8199
CAN.Initialize(CAN_TX_ONLY,true,0,0)

Example 6: CAN receive

Example 6.1 : CAN receive

In this example we will turn ON and OFF a LED that is connected on low side 1 with a received can message.
The Can ID has to be 0x123 and we will send only one byte of data. If the value of data is 1 then the LED will be turned ON, otherwise it will be turned OFF. 
 
To use the CAN receive function we need to change the CAN.Initialite() and add a function called "CAN.Received(message)". With the following code we always go into the CAN.Received() function when a message is received and then we check if the ID is correct.

function Initialize()
    CAN.Initialize(CAN_RX_TX,false,0,0)
	LoopPeriodMs = 10
end

function Loop()


end

function CAN.Received(message)
    if (message.ID == 0x123) then
        IO.Print(" Data1: ", message.Data[1])
        if(message.Data[1] == 1) then
            Digital.Set(LOW_SIDE_1, 1.0)
        else
            Digital.Set(LOW_SIDE_1, 0.0)
        end
    end
end

We can also add a filter so we only go into the CAN.Received() when the message has a proper ID. We achieve this with the following code.

function Initialize()
    CAN.Initialize(CAN_RX_TX,false,0x123,0x123)
	LoopPeriodMs = 10
end

function Loop()


end

function CAN.Received(message)
    --if (message.ID == 0x123) then
    IO.Print(" Data1: ", message.Data[1])
    if(message.Data[1] == 1) then
        Digital.Set(LOW_SIDE_1, 1.0)
    else
        Digital.Set(LOW_SIDE_1, 0.0)
    end
    --end
end

Example 7: Read & Set CANopen objects


In this example, we'll use an analog input (HW AIN1) as a throttle to set the motor velocity reference (object 0x3010 0x05). We will also read this object and print its value to the Lua output. Additionally, we'll limit the maximum RPM using the math library to prevent the motor from running away if there's a problem with the analog reading.

0V = 0RPM
5V = 200RPM

For this example to work you need an inverter that is configured to work with the connected motor. You need to be first able to spin it in velocity mode using the configurator.
When you start the script go to operational and turn on PWMs manually.

If the analog throttle is damaged (either a short circuit or a broken circuit), there is no safety system in place. Here’s what can happen:

If the circuit is broken, no voltage will be applied, and the RPM will be 0.
If there is a short circuit, the inverter will receive the full 5V on the analog input, causing the RPM to go to the maximum.

To prevent these issues, we need to add safety features. These will be demonstrated in Example 8.

VelocityRef = {0x3010, 0x05}

function Initialize()
	LoopPeriodMs = 10
end

function Loop()
   

    local rpm = Analog.Get(ANALOG_IN_1) * 40
    rpm = math.min(rpm,200)

    CANopen.SetObjectValue(VelocityRef, rpm)
    
    local VelRef_from_CANopen = CANopen.GetObjectValue(VelocityRef)

    IO.Print(VelRef_from_CANopen)
end

Example 8: Demo application

Example 8.1 : Demo application using CANopen objects.

In this example, we will demonstrate how to use the HW AIN1 input for a simple throttle control with a potentiometer (0-5V).

  • Throttle Input: HW AIN1 (0-5V Potentiometer)
    • Minimum RPM: 0 (corresponds to 0.5V)
    • Maximum RPM: 200 (corresponds to 4.5V)
    • Motor Stop Conditions:
      • If the throttle voltage is below 0.2V or above 4.8V, the motor will stop to protect against short and break issues.
    • PWM Enable Condition:
      • If the throttle voltage is below 0.5V, the PWM signals will be enabled
function Initialize()

    CANopen.SetObjectValue(VelocityRef, 0) -- Set Velocity ref to 0
	CANopen.SetObjectValue(ControlMode, 1) -- Set velocity mode
	CANopen.SetNMTState(CO_OPERATIONAL)    -- Go into operational mode

	LoopPeriodMs = 100
end

function Loop()
    local throttleVoltage = Analog.Get(ANALOG_IN_1)
    -- Decide whether to enable or disable the motor
    if Digital.Get(DIGITAL_IN_1) == 1 then
        -- only enable if voltage on analog in 1 is < 0.5 V so the motor does not start ang goes to high RPM
        if (throttleVoltage < 0.5) then
            CANopen.SetObjectValue(PwmControl, 1)
        end
    else
        CANopen.SetObjectValue(PwmControl, 0)
    end

    if (throttleVoltage > 0.2 and throttleVoltage < 4.8) == true then
           -- Map values 0.5 - 4.5 V to 0 - 200 RPM and
            local rpm = (throttleVoltage - 0.5) / 4 * 200
            rpm = math.min(rpm, 200)
            rpm = math.max(rpm, 0)
            CANopen.SetObjectValue(VelocityRef, rpm)
    else
        CANopen.SetObjectValue(VelocityRef, 0)
        CANopen.SetObjectValue(PwmControl, 0)
    end
end

Example 8.2 : Demo application using dedicated motor library.

In this example we will have the same functionality of the code as in example 8.1. but with the use of motor library
With the use of the motor library the code is easier to read and write than example 8.1.

function Initialize()

    Motor.SetReferenceVelocity(0)       -- Set Velocity ref to 0
	Motor.SetControlMode(VELOCITY_MODE) -- Set velocity mode
	CANopen.SetNMTState(CO_OPERATIONAL) -- Go into operational mode

	LoopPeriodMs = 100
end

function Loop()
    local throttleVoltage = Analog.Get(ANALOG_IN_1)
    -- Decide whether to enable or disable the motor
    if Digital.Get(DIGITAL_IN_1) == 1 then
        -- only enable if voltage on analog in 1 is < 0.5 V so the motor does not start ang goes to high RPM
        if (throttleVoltage < 0.5) then
            Motor.Enable()
        end
    else
        Motor.Disable()
    end

    if (throttleVoltage > 0.2 and throttleVoltage < 4.8) == true then
           -- Map values 0.5 - 4.5 V to 0 - 200 RPM and
            local rpm = (throttleVoltage - 0.5) / 4 * 200
            rpm = math.min(rpm, 200)
            rpm = math.max(rpm, 0)
            Motor.SetReferenceVelocity(rpm)
    else
        Motor.SetReferenceVelocity(0)
        Motor.Disable()
    end
end

Example 9: Throttle script (state - machine)

Throttle Control

  • Adjusting the Throttle: Use the potentiometer connected to Analog_IN_1.
  • Protection: The potentiometer is protected against short and break circuits.
  • Deadband: A deadband of 0.3V is set for the potentiometer.
  • Speed Limits:
    • Minimum speed: 0 RPM
    • Maximum speed: 300 RPM

Motor Operation

  • Activation:
    • The motor runs only when digital pin 1 (ON/OFF switch) is connected.
    • Change motor direction using a switch on digital pin 3.
  • Speed Setting:
    • Forward max speed: Object 0x3020 0x0D
    • Reverse max speed: Object 0x3020 0x0E
    • Switching direction at max speed changes to the corresponding reverse speed.

LED Diagnostics

  • LED Indicator (HS1):
    • On: System working normally.
    • Blinking: System in error mode.
  • Error Blink Codes:
    • Error 2: Blinks twice, then pauses.
    • Error 3: Blinks three times, then pauses.

Voltage and RPM Details

  • Working Voltage Range:
    • Minimum RPM: at 0.5V
    • Maximum RPM: at 4.5V
    • Deadband: 0.3V
  • Error Conditions:
    • ERROR1: Voltage out of bounds
    • ERROR2: Analog_IN_1 < (Min_Volt - Deadband)
    • ERROR3: Analog_IN_1 > (Max_Volt + Deadband)

CANopen Error Messages

  • Error Message (0x80 + nodeID):
    • Sent every 100ms in error state.
    • Data length: 8 bytes.
    • Byte 0 Mapping:
      • Bit 0: Throttle potentiometer error out of bounds
      • Bit 1: Throttle potentiometer break circuit error
      • Bit 2: Throttle potentiometer short circuit error
  • Warning Message (0x180 + nodeID):
    • Sent every 100ms in start state.
    • Data length: 8 bytes.
    • Byte 0: Warning code 0x01 (Motor disabled, potentiometer not in min position)

If the motor spins at Forward max speed and you switch the direction with the switch then it will spin at Reverse max speed

-- Limits
MAX_RPM = 300
MIN_RPM = -300

-- Objects
FORWARD_MAX_SPEED_ID = {0x3020, 0xD}
REVERSE_MAX_SPEED_ID = {0x3020, 0xE}
LED_ID 				 = {0x30A4, 0x02}


-- Inputs
ENABLE_SW 	 = DIGITAL_IN_1
DIRECTION_SW = DIGITAL_IN_2
THROTTLE_IN  = ANALOG_IN_1
DIRECTION_IN = DIGITAL_IN_3


-- Voltage thresholds
THROTTLE_MIN_V = 0.5
THROTTLE_MAX_V = 4.5
DEADBAND 	 = 0.3


ERROR	  = 0
ERROR_reg = 0
WARNINGS  = 0


StartTime1	= nil
StartTIme2	= nil
StartTime3 	= nil
StartTime4 	= nil
FlagCounter = 0


DIN		  = {ON = 1, OFF = 0}
LED 	  = {OFF = 0x0, ON = 0x1}
DIRECTION = {FORWARD = 1, REVERSE = 0}

-- Application FSM
Application = {}


function Initialize()
	CAN.Initialize(CAN_TX_ONLY,false,0,0)

	Application.NextState = "Idle"

    Motor.SetReferenceVelocity(0)
	Motor.SetControlMode(VELOCITY_MODE)

	LoopPeriodMs = 10
end


function Loop()
	Application[Application.NextState]()
end


Application.Idle = function ()
	CANopen.SetObjectValue(LED_ID, LED.ON)
	CANopen.SetNMTState(CO_OPERATIONAL)
	Application.NextState = "Start"
end


Application.Start = function ()


    -- IF enable switch is off go to stop state
    if (Digital.Get(ENABLE_SW) == DIN.OFF ) then
		Application.NextState = "Stop"
		return
	end


    -- Get voltage
	local throttleVoltage = Analog.Get(THROTTLE_IN)


    -- Go into error state if throttle voltage is out of bounds
    if OutOfBounds(throttleVoltage, (THROTTLE_MIN_V - DEADBAND), (THROTTLE_MAX_V + DEADBAND)) == true then
		ERROR = (ERROR or 0) | 1
		Application.NextState = "Error"
		return
	end


    -- Check that voltage is bellow minimal throttle voltage - so that the motor does not start at high speed
	if Motor.GetState() == MOTOR_OFF then
		if throttleVoltage < (THROTTLE_MIN_V) then
            Motor.Enable()
			WARNINGS = WARNINGS & Negate_Xbit(1, 8)
            return
		else
			WARNINGS = (WARNINGS or 0) | 1
        end
	end

	-- Only set velocity reference when pwms are enabled
	if ((Motor.GetState() == MOTOR_RUN) and (throttleVoltage >= 0.5)) then
		-- Calculate Voltage
    	--local rpm = ((throttleVoltage - THROTTLE_MIN_V) * MAX_RPM) / (THROTTLE_MAX_V - THROTTLE_MIN_V)
		local rpm = ((throttleVoltage - THROTTLE_MIN_V) * GetMaxSpeed()) / (THROTTLE_MAX_V - THROTTLE_MIN_V)
    	-- Limit RPM
    	rpm = math.min(rpm, MAX_RPM)
    	rpm = math.max(rpm, MIN_RPM)
		IO.Print("REF: ", rpm)
    	-- Set ref
		Motor.SetReferenceVelocity(rpm)
	end

	-- Send warnings every 100ms
	local CurrentTime = Time.GetMs()
	if((StartTIme2 == nil) or (CurrentTime-StartTIme2) >= 100) then
		SendWarnings()
	end
end


Application.Stop = function ()
	Motor.Disable()
    --When eneble sw is activated go to start state
	if (Digital.Get(ENABLE_SW) == DIN.ON) then
		Application.NextState = "Start"
	end
end

Application.Error = function ()

    Motor.Disable()
	Motor.SetReferenceVelocity(0)

	--local ledValue = CANopen.GetObjectValue(LED_ID)
    local throttleVoltage = Analog.Get(THROTTLE_IN)
	local ErrorBlinkCounter = 0
	local CurrentTime

	--If bit 0 = 1 then OutOfBounds is detected
	if (ERROR & 1) == 1 then
		if throttleVoltage < (THROTTLE_MIN_V - DEADBAND) then
			ERROR = ERROR | 2
			ERROR_reg = ERROR_reg | 1 -- set the generic error register
		elseif throttleVoltage > (THROTTLE_MAX_V + DEADBAND) then
			ERROR = ERROR | 4
			ERROR_reg = ERROR_reg | 1 -- set the generic error register
		else
			-- Clear ERROR bit 0, 1, 2
			ERROR = ERROR & Negate_Xbit(7, 16)
			-- Clear the generic error register
			ERROR_reg = ERROR_reg | Negate_Xbit(1, 8)
		end
	end

	-- Send Errors every 100ms
	CurrentTime = Time.GetMs()
	if ((StartTime1 == nil) or (CurrentTime - StartTime1) >= 100) then
		StartTime1 = CurrentTime
		SendError()
	end

	-- Determine how many times we want to blink the led
    if ((ERROR & 2) >> 1) == 1 then
        ErrorBlinkCounter = 2
    elseif ((ERROR & 4) >> 2) == 1 then
        ErrorBlinkCounter = 3
    end

    -- Start the blinking cycle if enough time has passed
	CurrentTime = Time.GetMs()
    if StartTime3 == nil or (CurrentTime - StartTime3) >= 3000 then
        StartTime3 = CurrentTime
        FlagCounter = 0
    end

    -- Handle the blinking logic
    if FlagCounter < 2 * ErrorBlinkCounter then
        if StartTime4 == nil or (CurrentTime - StartTime4) >= 300 then
            StartTime4 = CurrentTime
            FlagCounter = FlagCounter + 1

            -- Toggle LED
            --if ledValue == 0  then
            if FlagCounter % 2 == 1 then
				CANopen.SetObjectValue(LED_ID, LED.ON)
            else
                CANopen.SetObjectValue(LED_ID, LED.OFF)
            end
        end
	else
		-- Ensure the LED is off during the pause period
        CANopen.SetObjectValue(LED_ID, LED.OFF)
    end

    if ERROR == 0 then
		FlagCounter = 0
		Application.NextState = "Idle"
		return
	end
end


function GetMaxSpeed()
	if Digital.Get(DIRECTION_IN) == DIRECTION.FORWARD then
		return CANopen.GetObjectValue(FORWARD_MAX_SPEED_ID)
	end
	return CANopen.GetObjectValue(REVERSE_MAX_SPEED_ID)
end


function SendError()
	-- Send on ID + nodeID; we get the NodeId from CANopen off the inverter
	local CanID = 0x80 + CANopen.GetObjectValue({0x100B, 0x00})
	local byte0 = ERROR & 0xFF
	local byte1 = (ERROR & 0xFF00) >> 8
	local byte2 = ERROR_reg & 0xFF
	CAN.Send(CanID,{byte0, byte1, byte2, 0, 0, 0, 0, 0})
end

function SendWarnings()
	-- Send on PDO + nodeID;
	local CanID = 0x180 + CANopen.GetObjectValue({0x100B, 0x00})
	local byte0 = WARNINGS & 0xFF
	CAN.Send(CanID,{byte0, 0, 0, 0, 0, 0, 0, 0})
end


function OutOfBounds(throttleVoltage, minVal, maxVal)
    if (throttleVoltage < minVal) or (throttleVoltage > maxVal) then
        return true
    else
        return false
    end
end


--- Fuctino to negate bits
---@param value number Value to negate
---@param xBit number Number of bites e.g 16bit => xBit = 16
function Negate_Xbit(value, xBit)
    local negated = 0
    for i = 0, (xBit - 1) do
        if (value & (1 << i)) == 0 then
            negated = negated + (1 << i)
        end
    end
    return negated
end

Error 0x06070010 when loading a script may indicate Lua object 0x2040 0x01 isn't set to 1. To resolve, ensure proper access level in Configurator to unlock this feature. If you do not have the proper access level contact us.

image.png

image.png


Best practices

For further insights and practical tips, I recommend exploring "Programming in Lua," available at: Programming in Lua. Authored by experts in the field, this comprehensive resource offers invaluable guidance for Lua developers. It's worth noting that the entire topic is accessible free of charge as of April 24, 2024. Additionally, some sections of the topic include examples to further illustrate key concepts and techniques.

Minimizing Global Variables

In Lua scripting, it's imperative to exercise caution with global variables to prevent memory exhaustion and script failures, especially on embbeded systems. Emphasizing the utilization of local variables whenever possible is paramount. By minimizing global variable usage, we mitigate the risk of memory overflow and enhance script performance.

Example:

myGlobalVaribale = 10  --Global variable
local myLocalVaribale = 10  --Local variable

Issues

If IO.Print() isn't functioning, it may signal an outdated firmware version. Firmware 0x10C01 lacks support, while 0x10D02 enables the function.