This is a read-only snapshot of the ComputerCraft forums, taken in April 2020.
DarkMorford's profile picture

Porting a PID controller to Lua

Started by DarkMorford, 19 October 2016 - 02:47 PM
DarkMorford #1
Posted 19 October 2016 - 04:47 PM
I'm trying to port Brett Beauregard's Arduino PID library to Lua/ComputerCraft for controlling reactors/turbines/etc. The code appears pretty straightforward, and I have a mostly-complete implementation over at gist, but I'm having trouble figuring out how to connect the input, output, and setpoint variables.

The original library uses C++ pointers to allow Compute() and other methods to directly access the input, output, and setpoint; this saves the overhead of having additional methods such as GetOutput() or SetInput(). Of course, Lua doesn't have the concept of a pointer, and so a different approach is required. The obvious/naive solution would be to simply add SetInput() and its ilk, but I have a gut feeling that there's a more "Lua-ish" way to go about it.

As the experts, how would you recommend setting up these operating parameters, and is there anything I should change in my implementation to make it more ComputerCraft-friendly (as opposed to the vanilla Lua interpreter)?
Lupus590 #2
Posted 19 October 2016 - 06:59 PM
tables are copied like pointers
Bomb Bloke #3
Posted 20 October 2016 - 04:02 AM
The original library uses C++ pointers to allow Compute() and other methods to directly access the input, output, and setpoint; this saves the overhead of having additional methods such as GetOutput() or SetInput(). Of course, Lua doesn't have the concept of a pointer, and so a different approach is required. The obvious/naive solution would be to simply add SetInput() and its ilk, but I have a gut feeling that there's a more "Lua-ish" way to go about it.

Depends where you do and don't want access to these values? Your current implementation obviously allows the likes of Compute() easy access, but it also allows this sort of thing:

local myPID = PID:new(<someValues>)

-- This...
print( myPID:GetKp() )

-- ... gives the same result as this:
print( myPID.dispKp )

-- Catch is that you can also do this:
myPID.dispKp = <newValue>

If that's not a problem to you (and I don't see why it would be), then stick with the way you've done things, just remove the redundant functions such as PID:GetKp().

If you wanted to prevent that sort of direct access from outside of the internal API functions, then you'd need to ditch the metatables and instead define duplicate functions every time a new object was requested. Eg:

function PID:new(Input, Output, Setpoint, Kp, Ki, Kd, ControllerDirection)  -- All these parameters (Input, Output, etc) are defined as local to this function.
	local inAuto = false
	local outMin = 0
	local outMax = 255
	local SampleTime = 100
	local lastTime = 0
	-- yadda yadda...

	local obj = {}
	
	obj.Compute = function()
		if not inAuto then return false end   -- We can access the above locals as upvalues to this new function.
			local now = millis()
			local timeChange = now - lastTime
			if timeChange >= SampleTime then
			local input = Input
			-- yadda yadda...
	end
	
	-- Define all the other functions here...
	
	return obj  -- All the functions in obj will still be able to access the locals initialised when "new" was called.
end

This way, obj.Compute() can directly access "Output" for eg (still without wasting an extra function call to go off and fetch it), but if you want external access to that value (from outside of your API functions) then you'd need to specifically provide a eg "getOutput" function to return it - and if you don't provide it, then that access simply isn't there.