Lua Example Programs

Scene Tasks

Calling a scene with the scene interface

-- Scene call
-- Use the Lua API `scene` interface to call scene B from scene A (in this example, scene B's ID is assumed to be 10216)
-- `scene` can be understood as a wrapper function. It blocks until the logic has finished executing, so the call is blocking.

-- Content of scene A
print(scene(10216,{'{1, 2}', 3, 4}))-- Print scene return values 4, 5, 6. Note that the parameters of the scene interface {'{1, 2}', 3, 4} must be strings or numbers
print(var_test) -- Print the global variable from scene B


--Content of scene B
local params = ...  --The passed parameters {'{1, 2}', 3, 4} are assigned to params, type table, where the first parameter '{1, 2}' is a string type
print("params: ", params) 
print('print parameter type',type(params[1]))
 
-- Parse the string into a form usable in Lua
if params and  next(params) then
  for i=1,#params do
    param =  params[i]
    if i ==1 then
      print('print parameter 1 type',type(params[1])) --the first parameter is a string type
      table_data = load("return " .. param)() -- convert to table type
      print('print parameter 1 type',type(table_data)) 
      print(table_data)
    else
      print(param)
    end
  end
end
-- Set a global variable, printed in scene A, and it can be printed out
var_test = {
    name = "Xiao Wu",
    age = 16
}

return 4, 5, 6

Calling a task with the start_task interface

-- Task call
--1. In scene A, use the Lua API start_task to call scene B (scene ID 10216);
--2. Note that in a Lua scene, if scene A is invoked as a serial task, when calling the start_task interface, the fourth parameter must be true (parallel execution),
--    because scene A itself is a serial task when executed serially, and serial tasks are queued. At most one serial task can run at a time,
--    and subsequent tasks only run after the preceding task completes, fails or is terminated; therefore the serial scene A task is logically stuck here and cannot complete the logic below.
--3. If scene A is invoked as a parallel task by another scene task, when start_task calls a serial task, if there is no serial task running at the moment, it returns the task ID without blocking; if a serial task is running, it blocks;
--4. When start_task calls a parallel task, the parallel task starts immediately. Multiple parallel tasks can run at the same time, and their environment variables are independent;
--5. If scene A runs as a serial task and calls scene B to run as a parallel task, note that if both scenes contain motion logic, pay attention to the logic control to avoid confusing concurrent motion execution

--Difference between start_task and scene interfaces
--1. Both start_task and scene interfaces can call a scene. The difference is that start_task starts a separate task (parallel or serial), while the scene call is like calling a function, blocking execution
--2. In addition, start_task does not return scene B's return value like scene does; it returns the ID of the executed task

--Content of scene A
task_id = start_task(10216,{'{1, 2}', 3, 4}, "", true, 1) -- Print scene return values 4, 5, 6. Note the parameters {'{1, 2}', 3, 4} must be strings or numbers
print(var_test) -- Print the global variable

--Content of scene B
local params = ...  --Assign the passed parameters {'{1, 2}', 3, 4} to params, type table, where the first parameter '{1, 2}' is a string type
print("params: ", params) 
print('print parameter type',type(params[1]))
 
-- Parse the string, see the parameter parsing example for scenes for details
if params and  next(params) then
  for i=1,#params do
    param =  params[i]
    if i ==1 then
      print('print parameter type',type(params[1])) --the first parameter is a string type
      table_data = load("return " .. param)() -- convert to table type
      print('print parameter type',type(table_data)) 
      print(table_data)
    else
      print(param)
    end
  end
end
-- Set a global variable, printed in scene A, and it can be printed out
var_test = {
    name = "Xiao Wu",
    age = 16
}
return 4, 5, 6

The executed scene being called

--The executed scene being called usually contains a series of motion logic, combined with gripper opening/closing and DO output control
-- Execute motion
current_tcp_pose = get_target_tcp_pose()  --current end coordinate
print('current_tcp_pose',current_tcp_pose)
offset_position = {0, 0, 0.1, 0, 0, 0} --offset relative to the end TCP position, moving forward 0.1m along the z-axis of the end TCP coordinate system
calculate_location = pose_times(current_tcp_pose, offset_position) --calculated new position
print('calculate_location',calculate_location)
movej(calculate_location, 1, 0.5, 0, 0)
--insert multiple points to implement motion
--Gripper opening/closing
set_claw(100,100)
sync() --wait for the gripper to finish
--Check the gripper opening
claw_state = get_claw_aio('Amplitude')
if claw_state > 90 then
  print('Gripper opened')
end
set_claw(100,0)

--Turn on DO
set_do(1,1)

Parameter parsing in scene tasks

C_POSE = {}
J_POSE = {}
--[[
Use the Lua API: scene(scene_id, params, dir) or start_task(scene_id, params, dir, is_parallel, loop_to) to call the executed scene 
Or use the SDK  start_task(scene_id, params, dir, is_parallel, loop_to) to call the scene.
If params (string array) is passed in, since the parameters may be strings in the form of a dictionary or an array, the parameters need to be parsed and referenced in the Lua context.
Then, based on the passed parameter content and the agreed protocol, perform logic judgment to execute the corresponding logic.
]]--

local params =...  --parameters, such as {"{'x': 0.297070,'y': 0.019994,'z': 0.385210,'rx': -1.694321,'ry': -0.116908,'rz': -0.303152} ",'{1,2}',1}
                        -- such as {"[0.297070, 0.019994,0.385210,-0.303152, -0.116908,-1.694321] ",'{1,2}',1}
print("params: ", params)

print('print the number of parameters',#params)   --print the number of parameters
print('print the type of the first parameter',type(params[1]))

local json = require("json")
-- The python sdk passes a dictionary-form Cartesian coordinate and an array-form joint angle coordinate. Convert them to a Lua-form position, parse the string into a form usable in Lua 
if params and  next(params) then
  for i=1,#params do
    if i==1 then
      ----If the passed position is in dictionary style {'x': 0.297070,'y': 0.019994,'z': 0.385210,'rx': -1.694321,'ry': -0.116908,'rz': -0.303152}, handle it as follows
      -- cpose = json.decode(params[i])
      -- print(cpose)
      -- lua_cpose={cpose.x,cpose.y,cpose.z,cpose.rz,cpose.ry,cpose.rx}
      -- print(lua_cpose)
      -- C_POSE[i]= lua_cpose

      -- --If the position is in list style [0.297070, 0.019994,0.385210,-0.303152, -0.116908,-1.694321], the order represents
      jpose = json.decode(params[i])
      lua_jpose = {j1=jpose[1],j2=jpose[2],j3=jpose[3],j4=jpose[4],j5=jpose[5],j6=jpose[6]}
      J_POSE[i]= lua_jpose
    end

    if i==2 then
      str = params[i]
      table_result = load("return " .. str)()
      for key, value in pairs(table_result) do
        print(key, value)
      end
    end
  end
end

-- Besides JSON parsing, there are other ways to parse strings
local str = "{x = 1}"
local func, err = load("return " .. str)
if not func then
    error("Failed to load string: " .. err)
end
local tbl = func()
print(tbl.x)
  

-- Convert the string "{1,2}" into a Lua table
local str = "{1,2}"
local table_result = load("return " .. str)()
for key, value in pairs(table_result) do
    print(key, value)
end
 

Calling a task scene on a schedule

J_POSES = {}
C_POSES = {}
local date = os.date("*t", os.time());
--The result is date = {year = 2017, month = 8, day = 22, yday = 234, wday = 3,hour = 10, min = 46, sec = 52, isdst = false}
 
execution_scene_flag=false  --whether to execute the specified scene A  8:00-21:00

execution_exscene_flag=false  --whether to execute the extended scene B 22:00-8:00
exscene_executed_tags=0       --mark whether the extended scene B has been executed. Mark that it has been executed once

while true do
    wait(100)
    local date = os.date("*t", os.time())
    if date['hour']>=8 and date['hour']<=21 then
        execution_scene_flag=true
        exscene_executed_tags=0
    elseif (string.find(os.date(), " 22:")) then -- end at 22:00 every day
        execution_scene_flag=false
        if exscene_executed_tags == 0 then
            execution_exscene_flag=true 
        else
            execution_exscene_flag=false 
        end 
    end

    if execution_scene_flag==true then
        -- Execute the scene
        print('Running scene')
        scene(10103)  --Modify the scene number as needed. It blocks here for serial execution
    end

    if execution_exscene_flag==true then
        print('Running scene')
        scene(10103)  -- Execute the scene. It blocks here for serial execution
        exscene_executed_tags=1
    end
end

Auto-start task on boot

-- Purpose
-- Auto-start a scene at boot. Start a main scene that loops, detecting input signals in real time: it can be 485/232 TTL serial data,
-- or DI input (the DI inputs include those on the control box and the flange, as well as the signal input when the shoulder light panel and the flange button are pressed)
-- Upon detecting the corresponding signal, execute the corresponding action

-- The specific implementation is as follows
--Create a new scene A and set it as the default program (auto-start on boot), with the following content:
start_task('10026', nil, nil, true, 1) --Modify the main scene number 10026 according to the specific situation. Call the main scene as a parallel task
-- Note: '10026' called here is the main scene, which has an infinite loop logic that continuously detects input signals and makes corresponding actions
--       The fourth parameter is true, meaning parallel execution. This main task only performs logic judgment and does not execute specific action commands. It does not block. Action logic and tasks are implemented by calling sub-tasks again

-- The main task scene 10026 content is as follows:
--Redefine the underlying function on_robot_stop. When the emergency stop is pressed, only cancel the task, do not power down
function on_robot_stop(is_estop)
    print("estop current TASK id:", task_id)
    cancel_task(task_id)
end

--Physical serial port communication reading
local com = serial.open("/dev/ttyS1") ---Use the RS485 serial port of the control box
com:set_timeout(200)
com:set_baud_rate(9600)
function Com_read_data()
    local success, rst = pcall(function() return com:read(1) end)
    if success then
        if rst[1] == 0xAA then
            --Read according to the serial protocol. Here a simple handling is done: after finding the byte header, read a certain length
            local success, rst = pcall(function() return com:read(11) end)
            if success then
                table.insert(rst, 1,0xAA ) 
                return success, rst
            end
        end
    end
    return false,nil
end
-- CRC16 calculation (Modbus)
local function calc_crc(data)
    local crc = 0xFFFF
    for i = 1, #data do
        crc = crc ~ data[i]
        for _ = 1, 8 do
            local j = crc & 1
            crc = crc >> 1
            if j == 1 then
                crc = crc ~ 0xA001
            end
        end
    end
    return crc
end
-- Serial data parsing request
function parse_request(request)
    if #request < 12 then return nil end
    
    -- Check the protocol header
    if request[1] ~= 0xAA or request[2] ~= 0x55 then return nil end
    
    -- Check the slave ID
    if request[3] ~= 0x01 then return nil end
    
    -- Check the command word
    if request[5] ~= 0x37 and request[5] ~= 0x38 then return nil end
    -- print('check command word')
    
    -- Verify the CRC
    local crc_low = request[11]
    local crc_high = request[12]

    local crc_data = {}
    for i=1,10 do
        crc_data[i] = request[i]
    end
    local calc_crc_val = calc_crc(crc_data)
    
    if crc_low ~= (calc_crc_val & 0xFF) or crc_high ~= ((calc_crc_val >> 8) & 0xFF) then
        return nil
    end    

    -- Return the basket number
    return request[5], request[6] --command word, basket number(0-3)
end


--Infinite loop logic
while true do
    -- Handle communication requests and feedback status
    local success, request = Com_read_data()
    if success then
        if request and #request == 12 then
            local cmd_id, basket_id = parse_request(request)
            print("com received :",request)
            if basket_id ~= nil then 
                response = {0x55, 0xAA}
                print("com sent<:", response)   
                local response_success, response_rst = pcall(function() return com:write(response) end)
                if response_success then
                    print("com sent successfully")
                    -- Execute the corresponding logic according to the protocol content
                    task_id = start_task('10001', nil, nil, false, 1) -- the serially executed task
                else
                    print("com sending failed")
                end   
            end
        end
    end

    if get_di(0) == 1 then --received a di signal from the control box
        while true do
            wait(10) --wait 100ms
            if get_di(0) == 0 then --check whether the di signal has become 0
                print("Received a di signal from the control box")
                break
            end
        end
        local robot_state = lebai:get_robot_state()
        if robot_state == 'ESTOP' or robot_state == 'IDLE'  or  robot_state == 'TEACHING'  then
            if robot_state == 'ESTOP' then
                start_sys()
                sleep(2000) -- wait for the system startup to complete
            end
 
            state = get_task_state(task_id) --get the task status. The task has finished executing and is no longer running
            if state ~= 'WAIT' and state ~= 'RUNNING' and state ~= 'PAUSE'  
            then
                if robot_state == 'TEACHING' then
                    end_teach_mode()
                end
                    
                -- Start a task. Modify scene 10024 as needed
                task_id = start_task('10024', nil, nil, false, 1) -- the serially executed task
                print(string.format('Robot start task, task_id: %s', task_id))       
            end
        end
    end

    local button_type = "SHOULDER"  --"FLANGE_BTN" is the flange flat button   "SHOULDER" is the shoulder light button control
    if lebai:get_di(button_type, 0)==1 then 
        while lebai:get_di(button_type, 0)==1 do
            wait(20)
        end
        task_id = get_main_task_id()
        cancel_task(task_id) -- stop the task
    end

    sleep(10) -- wait for a while in each loop to reduce CPU usage

end

Serial Communication

Serial communication interaction control

--[[
Start a Lua scene that reads serial information in a loop,
When different data is read, parse the data that conforms to the protocol,
According to the protocol agreement, execute different motion logic or other tasks upon receiving different data,
And reply with data according to the protocol.
--]]

-- Serial communication data interaction control example
com1 = serial.open("/dev/ttyS1")
com1:set_timeout(200)
com1:set_baud_rate(9600)

-- Convert a byte stream to hexadecimal representation
function table2Hex(s)
    rst = ''
    for i = 1, #s do
        rst = rst .. string.format('0x%02X  ', s[i])
        -- print(s[i])
    end
    return rst
end
-- Convert a string to hexadecimal representation
function String2Hex(s)
	local rst = ""
	for i = 1, #s do
	  local byte = string.byte(s, i)
	  rst = rst .. string.format('0x%02X', byte)
	  if i < #s then
		rst = rst .. ' '  --only add spaces between characters
	  end
	end
	return rst
  end

-- Convert a single-byte unsigned integer to a signed integer
function u2s_8(num)
    if num > 127 then
        num = -(256 - num)
    end
    return num
end
-- Convert a single-byte signed integer to an unsigned integer
function s2u_8(num)
    if num < 0 then
        num = 256 + num
    end
    return num
end
-- Convert a two-byte unsigned integer to a signed integer
function usign2sign(num)
	if num > 32767 then
		num = -(65536 - num)
	end
	return num
end
-- Convert a two-byte signed integer to an unsigned integer
function sign2usign(num)
	if num < 0 then
		num = 65536 + num
	end
	return num
end

--- crc16_modbus  checksum calculation
--  data data to be checked (string or byte array)
--  Returns the checksum value
function crc16_modbus(data)
    local crc = 0xFFFF  -- initial value
    local poly = 0xA001 -- polynomial (bit-reversed form of 0x8005)

    -- Check the input type (string or table)
    local byte
    for i = 1, #data do
        if type(data) == "string" then
            byte = string.byte(data, i)  -- string mode: take the ASCII code byte by byte
        else
            byte = data[i]  -- table mode: take the value directly
        end
		crc=((crc ~ byte) & 0xFFFF) -- XOR the current byte
        -- Process 8 bits
        for _ = 1, 8 do
            if (crc & 1) ~= 0 then
                crc = ((crc >> 1) ~ poly)& 0xFFFF -- shift right and XOR the polynomial
            else
                crc = crc >> 1  -- shift right only
            end
        end
    end

    -- Return the low byte and the high byte (little-endian mode)
    local crc_low = crc & 0xFF
    local crc_high = (crc >> 8) & 0xFF
	--ret = ((crc_low << 8) | crc_high)
	-- return ret
    return crc_low, crc_high
end

-- Calculate and verify the sum
function calculateChecksum(data)
    local sum = 0
    for i = 1, #data do
        sum = sum + data[i]
    end
    -- Take the low 8 bits as the checksum
    return sum % 256
end


-- Calculate the XOR checksum
--  data input data (string or table, such as "123" or {0x01, 0x02})
--  xor_checksum XOR checksum (1 byte, 0x00~0xFF)
function xor_checksum(data)
    local checksum = 0  -- initial value

    -- Iterate over each byte
    for i = 1, #data do
        local byte
        if type(data) == "string" then
            byte = string.byte(data, i)  -- string mode: take the ASCII code
        else
            byte = data[i]  -- table mode: take the value directly
        end
        checksum = checksum ~ byte  -- XOR operation
    end

    -- Return the 1-byte checksum value (0x00~0xFF)
    return checksum & 0xFF
end
 
function recv_data()
	-- Read the serial port, use pcall to catch errors
	success, result = pcall(function() return com1:read() end)
	if success  and #result>2 then
		--Add checksum logic here, using CRC check as an example
		local subTable = {}
		for i = 1, #result-2 do
			subTable[i] = result[i]
		end
		local crc_low, crc_high = crc16_modbus(subTable)
		if result[#result-1] == crc_low  and  result[#result] ==crc_high  then
			print(table2Hex(subTable))  -- print HEX
			local data = string.char(table.unpack(subTable))
			return data
		else
			return nil
		end
	else
	  	print("Error: ", result)
	end
end

while true
do
	data = recv_data()
	if data == "123456" 
	then
		movej({j1 = 0, j2 = 0, j3 = 0, j4 = 0, j5 = 0, j6 = 0}, 0.1, 0.1, 0, 0)
		sync() -- wait for the motion to finish
        set_claw(100,100)
		local str = "finish1"
		local crc_low, crc_high = crc16_modbus(str)
		com1:write({string.byte(str, 1, #str),crc_low,crc_high}) -- send the string
    elseif data == "234567" 
	then
		scene(10001) -- execute the scene, blocking logic, need to wait until the scene task completes
        set_claw(100,0)
		local str = "finish2" 
		local crc_low, crc_high = crc16_modbus(str)
		com1:write({string.byte(str, 1, #str),crc_low,crc_high}) -- send the string
	else
		--com1:write({0xAA,0x55, 0x01, 0x08, 0xFF ,0x00, 0x00 ,0x00, 0x00, 0x00 ,0xC9 ,0xF5 })
	    print("unknown data:", data)
    end
end

Serial control of the Xiaofei coffee machine

-- Serial communication ice maker protocol example
--CRC check function, the parameter is the data string
function Crc16(buf)
    local init = 0xFFFF
    local poly = 0xA001
    local ret = init
    local byte=0
    for j=1,#buf,1 do
        byte = string.byte(buf,j)
        ret=((ret ~ byte) & 0xFFFF)
        for i=1,8,1 do
            if((ret & 0x0001)>0) then
                ret = (ret >> 1)
                ret = ((ret ~ poly) & 0xFFFF)
            else
                ret= (ret >> 1)
            end
        end
    end
    local hi = ((ret >> 8) & 0xFF)
    local lo = (ret & 0xFF)
    ret = ((lo << 8) | hi)
    return ret
end

local com1 = serial.open("/dev/ttyS0")  ---select the 232 port for serial communication
com1:set_timeout(300)   --read timeout
com1:set_baud_rate(9600)   --set the baud rate
com1:set_parity("None")   --None: no parity check

--Function for sending commands to the serial port
function Send_Cmd_to_com(cmd)
    -- print('cmd :',cmd)
    com1:write(cmd) -- send the corresponding command
    --Loop reading until data is read and verified and returned, or terminate and return false if no data is read after ten attempts
    for i = 1,3 do
        wait(100)
        -- Read the serial port, use pcall to catch errors
        success, result = pcall(function() return com1:read() end)
        -- print(success)
        if success==true and  result~=nil then
            -- print('receive:',result) -- print HEX
            crc_response = Crc16(string.char(table.unpack(result,1,#result-2)))  
            -- print('crc_response:',crc_response) 
            -- print( crc_response>>8 ,crc_response&0xFF) 
            if  result[#result-1]==crc_response>>8 and result[#result]==crc_response&0xFF then
                return result
            end
        end
    end
    return false
end

--Ice dispensing function. The parameter is the ice dispensing time, in units of 0.1 seconds. For example, input 40 means dispensing ice for 4 seconds. Returns true if ice dispensing succeeds, returns false if it fails, and returns other values for command sending or communication hardware problems
function Make_ice(out_ice_time)  
    if out_ice_time==nil then
        out_ice_time=10
    end
    makeice_cmd = {0xA5,0x5A,0x01,0x02} 
    out_time=  math.ceil(out_ice_time) --round the time
    table.insert(makeice_cmd, out_time&0xff00)
    table.insert(makeice_cmd, out_time&0xff)
    crc_result = Crc16(string.char(table.unpack(makeice_cmd))) --get the checksum  
    table.insert(makeice_cmd, crc_result>>8)
    table.insert(makeice_cmd, crc_result&0xFF)
    --Send the ice dispensing command
    make_ice_result= Send_Cmd_to_com(makeice_cmd)
    if make_ice_result ~=false then
        --Normal return result of the ice dispensing command
        if #make_ice_result==8 then
            status_code=table.unpack(make_ice_result,5,5)
            if status_code==0 then  --ice dispensing succeeded
                return true 
            else                  --ice dispensing failed
                return false
            end
        --Return result caused by an erroneous command
        elseif #make_ice_result==7 then
            abnormal_code= table.unpack(make_ice_result,5,5)
            if abnormal_code==0 then  --position error causing ice dispensing failure
                return 'undefined error'
            elseif abnormal_code==2 then  --command sending problem causing ice dispensing failure
                return 'unrecognizable command'
            elseif abnormal_code==3  then  --command checksum error causing ice dispensing failure
                return 'CRC check error'
            elseif abnormal_code==4  then  --system busy causing ice dispensing failure
                return 'system busy'
            end
        end
    else
        --communication failure
        print('Anomaly in communication with the ice maker when sending the ice dispensing command')
        return 'communication error'  --return false, the command has no return value
    end
end

--Read the ice maker status
function Read_machine_status()  
    read_status_cmd = {0xA5,0x5A,0x01,0x01,0x00,0x00,0x10,0xDF}  
    --Send the status reading command
    read_status_result= Send_Cmd_to_com(read_status_cmd)
    --Return the status for a normal command
    if read_status_result ~=false then
        if #read_status_result==8 then                                  
            machine_status_code= table.unpack(read_status_result,5,5)
            error_code = table.unpack(read_status_result,6,6)
            print('Ice maker status code:', machine_status_code , ' error code:',error_code )
            if machine_status_code==2 then
                return 'idle standby'
            elseif machine_status_code==3 then
                return 'dispensing ice or water'
            elseif machine_status_code==4 then
                return 'discharging ice'
            elseif machine_status_code==5 then
                if error_code==1  then
                    return 'insufficient water supply'
                elseif error_code==2 then
                    return 'ice maker internal temperature too low'
                elseif error_code==3  then
                    return 'ice maker internal temperature too high'
                elseif error_code==4  then
                    return 'compressor temperature too high'
                elseif error_code==5  then
                    return 'exhaust vent temperature too low'
                elseif error_code==6 then
                    return 'motor current too high'
                elseif error_code==7  then
                    return 'communication error'
                elseif error_code==8  then
                    return 'data storage error'
                elseif error_code==9  then
                    return 'motor stall'
                elseif error_code==10  then
                    return 'motor warning'
                end   
            elseif machine_status_code==6 then
                return  'cleaning'
            elseif machine_status_code==1 or machine_status_code==0 then
                return 'initializing on power-on'
            elseif machine_status_code==7 then
                return 'waiting to take ice' 
            end
        --Return the status for an abnormal command
        elseif #read_status_result==7 then
            abnormal_code= table.unpack(read_status_result,5,5)
            if abnormal_code==0 then 
                return 'undefined error'
            elseif abnormal_code==2 then
                return 'unrecognizable command'
            elseif abnormal_code==3  then
                return 'CRC check error'
            elseif abnormal_code==4  then
                return 'system busy'
            end
        end
    --communication failure, no return value
    else
        print('Anomaly in communication with the ice maker when reading the ice maker status' )
        return 'communication error'
    end
    return false
end

--Check the ice bucket status
function Read_icebucket_status()  
    read_icebucket_cmd = {0xA5,0x5A,0x01,0x03,0x00,0x00,0xB1,0x1F}  
    --Send the status reading command
    read_icebucket_result= Send_Cmd_to_com(read_icebucket_cmd)
    --Return the status for a normal command
    if read_icebucket_result~=false then
        if #read_icebucket_result==8 then                                  
            icebucket_status_code= table.unpack(read_icebucket_result,5,5)
            if icebucket_status_code==1 then
                return 'ice bucket full'
            else
                return 'ice bucket not full'
            end
        --Return the status for an abnormal command
        elseif #read_icebucket_result==7 then
            icebucket_abnormal_code= table.unpack(read_icebucket_result,5,5)
            if icebucket_abnormal_code==0 then 
                return 'undefined error'
            elseif icebucket_abnormal_code==2 then
                return 'unrecognizable command'
            elseif icebucket_abnormal_code==3  then
                return 'CRC check error'
            elseif icebucket_abnormal_code==4  then
                return 'system busy'
            end
        end
    --communication failure, no return value
    else
        print('Anomaly in communication with the ice maker when reading the ice bucket status' )
        return 'communication error'
    end
    return false
end
print(Make_ice(10))
print(Read_machine_status())
print(Read_icebucket_status())

Control box Modbus RTU control

-- Control box Modbus example
-- Use the Modbus/RTU of the control box
local com1 = serial.open("/dev/ttyS1")
com1:set_timeout(200)
com1:set_baud_rate(9600)
local mb = modbus.new_rtu(com1)
-- Configure Modbus
mb:set_timeout(500)
mb:set_slave(0x01)

-- Write a single coil
success, result = pcall(function() mb:write_single_coil(0x0000, true) end)
if not success then
  print("Error: "..tostring(result))
else
    print("Write successful")
end
-- Write multiple coils
success, result = pcall(function()
  mb:write_multiple_coils(0x0000, {true,true,true,true,true})
end)
if not success then
  print("Error: ", result)
else
    print("Write successful")
end
 -- Read multiple coils
success, result = pcall(function() return mb:read_coils(0x0000, 5) end)
if not success then
    print("Error: ", result)
else
    print(result)
end

-- Write a single holding register
mb:write_single_register(0x0090, 0x0088)
-- Write multiple holding registers
mb:write_multiple_registers(0x0090, {0x0088, 0x0088})
-- Read multiple holding registers
mb:read_holding_registers(0x0090, 2)
-- Read multiple input registers
mb:read_input_registers(0x0080, 2)

Modbus control of the Sanqi servo motor

-- Sanqi servo motor control example
 
C_POSE = {}
J_POSE = {}

disable_auto_sync()  -- disable auto sync
 
--Convert the read bytes into hexadecimal number representation
function String2Hex(s)
    rst = ''
    for i = 1, #s do
        rst = rst .. string.format('0x%02X  ', s[i])
    end
    return rst
end

function get_sign32(vx)
    if (not vx) or (vx < 0x80000000) then
        return vx
    end
    return vx - 0x100000000
end
--Convert an unsigned 2-byte integer into a signed one
function get_sign16(vx)
    if (not vx) or (vx < 0x8000) then
        return vx
    end
    return vx - 0x10000
end 
--Convert a signed 4-byte integer into an unsigned one
function signed32_to_unsigned32(vx)
    if not (vx<0) then
        return vx
    end
    return vx + 0x100000000
end

--Define the serial port
com1 = serial.open("/dev/ttyS1")
com1:set_baud_rate(38400)
com1:set_parity("Even")
com1:set_timeout(200)
 
-- Configure Modbus
mb = modbus.new_rtu(com1)
mb:set_slave(0x01)

EI_status = 0
EI_address = 0x0000  --photoelectric address
function Servo_ON()
    mb:write_multiple_registers(0x4230, {0, 1})
    wait(20)
end

function Servo_OFF()
    mb:write_multiple_registers(0x4230, {0, 0})
    wait(20)
end

function Clear_EI()
    mb:write_multiple_registers(EI_address, {0, 0})
    wait(20)
end
--Position preset command when starting the servo (clear faults)
function Set_zero_EI()
    Clear_EI()
    mb:write_multiple_registers(EI_address, {0x0100>>16, 0x0100&0x0000FFFF})
    wait(20)
end
--Home return
function Find_zero_EI()
    Clear_EI()
    mb:write_multiple_registers(EI_address, {0x0400>>16, 0x0400&0x0000FFFF})
    wait(20)
end
--Move command
function Go_position_EI()
    Clear_EI()
    mb:write_multiple_registers(EI_address, {0x1>>16, 0x1&0x0000FFFF})
    wait(20)
end

--EI2 = -OT, triggers when 0, EI4 = +OT, triggers when 0, EI5 = home LS detection, triggers when 1
-- Read the limit and home photoelectric sensor status
function Read_EI()
    EI_status = mb:read_coils(0x0400, 5)
    print(EI_status)
    if EI_status ~= false then
        local rst = {}
        rst['home'] = not EI_status[5]
        rst['OT-'] = not EI_status[2]
        rst['OT+'] = not EI_status[4] 
        return rst
    else
        return false
    end
end

--Set position parameters. The input is the converted pulse count
function Set_position( position_value )
    position_value = signed32_to_unsigned32(position_value)
    print(position_value)
    print({position_value>>16, position_value&0x0000FFFF})
    mb:write_multiple_registers(0x5102 , {position_value>>16, position_value&0x0000FFFF})
    wait(20)
end
--Read position. The returned position unit is millimeters
function Read_position()
    local rst_position = mb:read_holding_registers(0x100c, 2)
    wait(20)
    if rst_position == false then
        return false
    end
    local current_position = get_sign32((rst_position[1]<<16) + rst_position[2]) * 32 / 4000
    print('Current position:' ,current_position)
    return current_position
end

--Set speed parameters
function Set_position_speed(speed_value)
    mb:write_multiple_registers(0x5104 , {speed_value>>16, speed_value&0x0000FFFF})
    wait(20)
end

--Read the instantaneous speed value
function Read_position_speed()
    speed_rst = mb:read_holding_registers(0x5104, 2)
    wait(20)
    current_speed = get_sign32((speed_rst[1]<<16) + speed_rst[2] )
    print('Current speed:' ,current_speed)
    return current_speed
end

--Read the current speed
function Read_vel()
    print(444)
    local rst = mb:read_holding_registers(0x1000, 2)
    wait(20)
    print(777)
    if rst == false then
        return false
    end
    local current_vel = get_sign32((rst[1]<<16) + rst[2]) 
    print('Current velocity:' , math.modf(current_vel))
    return current_vel
end

--Set absolute position mode
function Set_abs_mode()
    mb:write_multiple_registers(0x5100, {0x00FF0000>> 16,0x00FF0000&0x0000FFFF})
    wait(20)
end

--Initialize and return home
function Init_servo()
    if Read_vel() ~= 0 then
        print('The seventh axis is moving and cannot be reset.')
        return false
    end
    Set_zero_EI()
    Find_zero_EI()
    Set_abs_mode()
   
    while true do
        wait(500)
        if Read_vel() == 0 and math.abs(Read_position()) < 5 then
            break
        end
    end
    return true
end

-- pos unit: millimeters
function Move(pos,move_speed)
    if move_speed==nil then
        move_speed=80000
    end
    -- if pos > 1540 or pos < -350 then
    --     print('Exceeds the linear axis motion range')
    --     return false
    -- end
    if Read_vel() ~= 0 then
        print('The seventh axis is moving and cannot accept move commands.')
        return false
    end
    pos_driver = math.modf(pos / 32 * 4000)  --#set the position immediate value, 4000unit = 9mm, correction: 4000unit = 32mm
    print(pos_driver)
    Set_position(pos_driver)
    Set_position_speed(move_speed)
    Go_position_EI()
 
    while true do
        if math.abs(Read_position() - pos) < 4 then
            return true
        else
            wait(500)
        end
    end
end

Flange Modbus RTU gripper control

-- Flange Modbus example 
-- Generally, the gripper is controlled directly by calling the Lua API or the SDK. Here we take the flange Modbus control method as an example.
-- This helps to understand that when adding other communication modules at the end, if the 485 wire is paralleled on the gripper control wire, similar interfaces can be used to control the added communication module

local mb = modbus.new_flange() --Since the serial port address of the flange is fixed, there is no need to configure the serial port as when instantiating the control box Modbus
-- Configure Modbus
mb:set_timeout(600)
mb:set_slave(0x01)

mb:write_single_register(0x9c40, 100) --amplitude control
wait(100)
mb:write_single_register(0x9c41, 10) --force control

amplitude = mb:read_holding_registers(0x9c45, 1) --read the amplitude
print(amplitude)
force = mb:read_holding_registers(0x9c46, 1) --read the force
print(force)

--To prevent errors, use pcall to catch errors
success, result = pcall(function() return mb:read_holding_registers(0x9c46, 1) end)
if not success then
  print("Error: ", result)
else:
    print(result)
end

Modbus TCP example

-- Through Modbus TCP communication interaction, query the value of a fixed register, call a certain scene action according to the protocol, and query the action completion status and cancel the task according to the protocol

-- The specific implementation is as follows
--Create a new scene A and set it as the default program (auto-start on boot), with the following content:
start_task('10026', nil, nil, true, 1)  --Modify the main scene number according to the specific situation. Call the main scene as a parallel task
-- Note: '10026' called here is the main scene, which has an infinite loop logic that continuously reads a fixed address and performs corresponding actions according to the protocol.
--       The fourth parameter is true, meaning parallel execution. This main task only performs logic judgment and does not execute specific action commands. It does not block. Action logic and tasks are implemented by calling sub-tasks again

--The main task scene content is as follows:
--Redefine the underlying function on_robot_stop. When the emergency stop is pressed, only cancel the task, do not power down
function on_robot_stop(is_estop)
    print("estop current TASK id:", task_id)
    cancel_task(task_id)
end
mb = modbus.new_tcp('192.168.4.85', 22)  --the control box acts as a client master station and sends requests
-- Configure Modbus
mb:set_timeout(500)
mb:set_slave(0x01)
 
function recv_data()
	-- read
	success, result = pcall(function() return mb:read_holding_registers(0x0000, 2) end)
	if success then
	   print('recieve data ',result)
	   local data = string.char(table.unpack(result))
	   return data
	else
	   print("Error: ", result)
	end
end

function send_data(address,data)
	--after receiving the task, execute it and give feedback
	success, result = pcall(function() mb:write_multiple_registers(address, data) end)
	if success then
	   return true
	else
	  print("Error: ", result)
	  return false
	end
end

--Note:
--1. In start_task("10001", {}, "", false, 1), the parameter false starts a serial task
--2. Note that if scene 10001 is started upon receiving 1, and then scene 10002 is started upon receiving 2, since serial tasks are queued,
--    the logic start_task("10002", {}, "", false, 1) will block until the previous serial task finishes
--3. Because the arm cannot execute the actions of scene A and the actions of scene B at the same time, scenes with motion logic generally execute serially

while true
do
	wait(50) --read once every 50ms
	data = recv_data()
	if(data == "1")
	then
		task_id = start_task("10001", {}, "", false, 1)
		local str = 1
		local address = 0x0000
		send_data(address, {1}) --execution feedback
		 
	elseif(data == "2")
	then
		task_id = start_task("10002", {}, "", false, 1) 
		local str = 2
		local address = 0x0000
		send_data(address, {2}) --execution feedback
    elseif (data == "check") --check the task status
	then
		state = get_task_state(task_id)
		--state task status: "NONE" no task; "WAIT" queuing; "RUNNING" running; "PAUSE" paused; "SUCCESS" ran successfully; "INTERRUPTING" stopping; "INTERRUPT" stopped; "FAIL" ran with failure

		local str = state
		local address = 0x0002
		send_data(address, {string.byte(str, 1, #str)}) --feedback
		if state=="NONE" or state=="SUCCESS" or state=="INTERRUPTING" or state=="INTERRUPT" or state=="FAIL":
			local address = 0x0000
			send_data(address, {0}) --feedback

	elseif (data == "cancel") --cancel the task
	then
		task_id = get_main_task_id()  --get the serial task ID
		cancel_task(task_id)
		local str = 0
		local address = 0x0000
		send_data(address, {0}) --feedback
    end
end

Spatial Position Calculation

Relative point calculation

-- Relative position point calculation
-- Find the position relative to the end TCP coordinate system in the base coordinate system, and perform motion relative to the end
current_tcp_pose = get_target_tcp_pose()  --current end coordinate
print('current_tcp_pose',current_tcp_pose)
offset_position = {0, 0, 0.1, 0, 0, 0} --offset relative to the end TCP position, moving forward 0.1m along the z-axis of the end TCP coordinate system
calculate_location = pose_times(current_tcp_pose, offset_position) --calculated new position
print('calculate_location',calculate_location)
movej(calculate_location, 1, 0.5, 0, 0)


--If you want the current position and orientation to undergo a pose transformation in a certain coordinate system.
----For example, relative to the base coordinate system, increase by 0.1 along the base z-axis
base = get_target_tcp_pose()  --current end coordinate
print('current_tcp_pose',base)
delta = {0, 0, 0.1,0,0, 0} --move 0.1m along the z-axis of the frame coordinate system
frame  ={0, 0, 0, 0, 0, 0}  -- base coordinate system. Pose offset direction, only the orientation part is effective.
pose = pose_add(base, delta, frame)
print('pose',pose)
movej(pose, 1, 0.5, 0, 0)

----For example, relative to a new coordinate system rotated 45° about the y-axis so that the z-axis points diagonally upward, increase by 0.1 along the base z-axis
base = get_target_tcp_pose()  --current end coordinate
print('current_tcp_pose',base)
delta = {0, 0, 0.1, 0, 0, 0}  -- move 0.1m along the z-axis of the frame coordinate system
frame  = {0, 0, 0, 0 , 0.78, 0}  -- new coordinate system rotated 45° about the y-axis, with the z-axis pointing diagonally upward
pose =  pose_add(base, delta, frame)
print('pose',pose)
movej(pose, 1, 0.5, 0, 0)

Motion Control

Trajectory reproduction

--Trajectory reproduction
name = "test" --name this dragged trajectory "test"
function on_robot_state(state)
    if state == 11
    then
        -- Enter teach mode, start trajectory recording
        start_record_trajectory(0.01)
    end
    if last_state == 11
    then
        -- Exit teach mode, finish trajectory recording
        end_record_trajectory(name)
        -- Reproduce the trajectory
        move_trajectory(name)
    end

    last_state = state
end

while true
do
    sleep(1000)
end

--While the above scene is running, press the teach button to start recording, drag to form a trajectory, and release the teach button to finish recording
--Create a new scene and call move_trajectory("test") to reproduce the trajectory

Drawing a five-pointed star

-- Draw a five-pointed star
function clone(x) --clone the point
  return {x[1],x[2],x[3],x[4],x[5],x[6]}
end

local p = math.rad(36)/2
local vel = 0.06
local acc = 0.1

function draw_star(a, r)
  local x = r * math.sin(p)
  local y = r * math.cos(p)
  print('x', x, 'y', y)

  print('a', a)
  movej(a, 0.4, 1, 0, 1)
  wait(1000)

  b = clone(a)
  b[1] = a[1] - x
  b[2] = a[2] - y
  print('b', b)
  movel(b, vel, acc, 0, 0)

  c = clone(a)
  c[1] = a[1] + r / 2
  c[2] = a[2] - r / 2 * math.cos(math.rad(36))
  print('c', c)
  movel(c, vel, acc, 0, 0)

  d = clone(a)
  d[1] = a[1] - r / 2
  d[2] = c[2]
  print('d', d)
  movel(d, vel, acc, 0, 0)

  e = clone(a)
  e[1] = a[1] + x
  e[2] = a[2] - y
  print('e', e)
  movel(e, vel, acc, 0, 0)

  movel(a, vel, acc, 0, 0)

end
-- a = get_actual_tcp_pose()
a = {-0.34, -0.12, 0.4, -1.57, 0, 0.25}
for i=0,2 do
  a[3] = a[3] - 0.08
  draw_star(a, 0.1+i*0.02)
end

a[2] = a[2] + 0.2
a[3] = a[3] - 0.04
for i=0,2 do
  a[3] = a[3] + 0.08
  draw_star(a, 0.1+i*0.02)
end

Two-robot synchronization

-- Two-robot synchronization: make one arm follow another arm to perform the same action
disable_auto_sync()
lebai = lebai_sdk.connect("127.0.0.1", false) -- local robot
target = lebai_sdk.connect("192.168.4.100", false) -- the controlled robot that performs the follow action
target:start_sys() -- enable the controlled robot
wait(3000)
-- Move the controlled robot to the current position of the local robot
pose = (lebai:get_kin_data())["actual_joint_pose"]
target:movej(pose, 0.1, 0.1, 0, 0) 
target:wait_move()

print("start")
last_time = timestamp() --the millisecond timestamp of the last synchronization
start_flag =0 -- the local robot calls an action scene, start flag
while true
do
    wait(10)
    now = timestamp()
    used_time = now-last_time
    last_time = now

    -- Synchronize the arm position
    status = lebai:get_kin_data()
    target:move_pvat(status["actual_joint_pose"], status["actual_joint_speed"], status["actual_joint_acc"], used_time/1000) --synchronous motion
    -- Detect whether the motion has started
    if start_flag == 0 then
        start_flag = 1
        scene_id = 10289  -- action scene ID
        start_task(scene_id, nil, '', true, 1) -- the local arm starts moving according to the scene setting, and the other arm starts to follow
    end
    -- This is an infinite loop logic. After the local robot finishes the scene, manually teach the local robot, and the other arm will follow the motion too
    -- To stop, manually stop the scene or press the emergency stop, or stop via a third-party script
end

Synchronized execution of one scene across multiple robots

-- Implement multiple robots executing one scene at the same time (all devices have the scene ID to execute)
C_POSE = {}
J_POSE = {}
--Disable auto sync
disable_auto_sync()
-- IP address list. Add if there are new robots, delete if there are none
local robot_ips = {
    "127.0.0.1",    -- master
    "192.168.6.109" ,
    "192.168.4.57", -- controlled robot 1
    "192.168.4.109" 
}
-- Table storing connection objects
local robots = {}
-- Create connections in batch
for i, ip in ipairs(robot_ips) do
    success, result = pcall(function() return lebai_sdk.connect(ip, false) end)
    if success then
        robots[i] = result
        print("Connection successful: " .. ip)
        robots[i]:start_sys()
    else
        print("Connection failed: " .. ip) 
    end
end
wait(3000)
-- Move the controlled robots to the current position of the local robot
local pose = (robots[1]:get_kin_data())["actual_joint_pose"]
for i =1,#robot_ips do
    if robots[i] then
        robots[i]:movej(pose, 0.5, 0.5, 0, 0) 
    end
end
for i =1,#robot_ips do
    if robots[i] then
        robots[i]:wait_move()
    end
end
print("Start action")
for i =1,#robot_ips do
    if robots[i] then
        robots[i]:start_task(10010, nil, '', true, 2)
    end
end
print("All started")

IO Control

DI interaction control

-- By controlling the DI input signal, call the corresponding scene action
-- And control task cancellation through the shoulder button

tasks = {}--store the started task IDs
-- Cancel all tasks
function cancel_all_tasks()
    for k,v in pairs(tasks) do
        print(k,v)
        cancel_task(v)
        tasks[k] = nil
    end
end

--Note:
-- 1. In start_task("10160", {}, "", true, 1), the parameter true starts a parallel task
-- 2. Note that if DI0 is pressed to start scene 10160, and then DI1 is pressed to start scene 10161, since tasks execute in parallel,
--     if both scenes have motion logic, executing the motion of two scenes at the same time may cause motion confusion. Therefore, call cancel_all_tasks() before a new task is executed

button_type = "SHOULDER"  --"FLANGE_BTN" is the flange flat button   "SHOULDER" is the shoulder light button control
while true do
    wait(100) --check once every 0.1s

    if get_di(0) == 1 then
        wait_di(0, 0, "=")   -- wait for the button to be released
        cancel_all_tasks()
        tasks["0"] = start_task("10160", {}, "", true, 1)
    end
    if get_di(1) == 1 then
        wait_di(1, 0, "=")  -- wait for the button to be released
        cancel_all_tasks()
        tasks["1"] = start_task("10160", {}, "", true, 1)
    end
    if lebai:get_di(button_type, 0)==1 then
        while lebai:get_di(button_type, 0)==1 do
            wait(20)
        end
        cancel_all_tasks()
        tasks["0"] = start_task("10160", {}, "", true, 1)
    end
end

DI interaction control of the gripper

-- Implement pressing the shoulder button 3 times consecutively to control the opening/closing of the gripper

set_auto('ARM_POWER' , true)    -- set auto power-on after boot
set_auto('ENABLE_JOINT' , true)   --set auto-start to idle state after power-on
init_claw(true)  --initialize the gripper
sync()

claw_status ='close'
set_claw(100,0)
sync()
button_type = "SHOULDER"  --"FLANGE_BTN" is the flange flat button   "SHOULDER" is the shoulder light button control
button_times = 0  --number of button presses. Press 3 times consecutively to open or close the gripper
interval_flag = timestamp()

while true do
    wait(20) --reduce CPU usage
    if timestamp()-interval_flag >2000 then
        button_times = 0
    end

    if lebai:get_di(button_type, 0)==1 then
        while lebai:get_di(button_type, 0)==1 do
            wait(20)
        end

        button_times = button_times + 1

        if  button_times ==1 then
            interval_flag = timestamp()
        elseif  button_times == 3 then
            button_times = 0
            if claw_status =='close' then
                claw_status='open'
                print(claw_status)
                set_claw(100,100)
                sync()
            elseif claw_status=='open' then
                claw_status='close'
                set_claw(100,0)
                print(claw_status)
                sync()
            end
        end
    end
    
end

DO control of the pump

-- DO control of the suction pump
-- During motion, when the suction pump sensor detects contact with an object, the suction pump starts to suck the corresponding object, places it in a suitable position, and then the suction pump releases it
-- For the specific wiring and assembly method, see the official website

--First move to above the target position of the object to be sucked
object_pose = {j1=-1.9267757433836,j2=-1.9655087582777,j3=2.2828510337716,j4=-0.34313232749017,j5=-3.9459738292373,j6=0}	
object_pose_top = pose_add(object_pose, {0,0,0.15,0,0,0})  --based on the object_pose point, move 0.15m along the z-axis of the base coordinate system to form a new coordinate
movej(object_pose_top, 0.5, 0.5, 0, 0)

--Move gradually downward from above the object. When the end DI is triggered, the pump has contacted the object. Stop moving and turn on the pump
movel_until(object_pose, 0.2, 0.08, 0, function() return get_flange_di(0) == 1 end)
--Turn on the pump to suck
set_do(0, 1) -- turn on the pump
set_do(1, 0) -- close the valve

-- Move to above the target position of the object
movej(object_pose_top, 0.5, 0.5, 0, 0)
put_pose = {j1=0,j2=-1.9655087582777,j3=2.2828510337716,j4=-0.34313232749017,j5=-3.9459738292373,j6= 0}	
movej(put_pose, 0.5, 0.5, 0, 0)
--Turn off the pump to release
set_do(0, 0) -- turn off the pump
set_do(1, 1) -- open the valve

wait(500)
set_do(1, 0) -- close the valve