Compare commits

..

65 Commits

Author SHA1 Message Date
eb8faa9c10 adjust PID kp values 2026-06-30 00:21:25 -05:00
165c0cefa9 adjust PID maxOutput 2026-06-30 00:19:05 -05:00
131bbd9f0a adjust PID update function return value 2026-06-30 00:16:39 -05:00
dd70e5ddb2 comment out SetThrusterPower debug print 2026-06-30 00:10:43 -05:00
a65299be26 fix an issue with populateThrusterValues? idk how this regression happened... 2026-06-30 00:09:40 -05:00
7174c16628 add debug print to new section 2026-06-30 00:04:59 -05:00
fa96b2f59f add aformentioned simplified stabilization for vessels with thrusters with the field populated correctly 2026-06-30 00:03:07 -05:00
f816761e0f add extra field in Thrusters for simplifying quadricopter-like stabilization systems 2026-06-29 23:57:13 -05:00
056c10c346 add a debug print to the PID update function 2026-06-29 23:47:45 -05:00
908a2153fa comment out primary UpdateGlobalThrust debug print for testing 2026-06-29 23:43:58 -05:00
7bcabcbbe6 edit debug prints and add new ones 2026-06-29 23:42:58 -05:00
1aa391f56c adjust PID max output 2026-06-29 23:40:09 -05:00
e7831845a8 edit UpdateGlobalThrust debug print 2026-06-29 23:39:40 -05:00
d228666c87 remove normalized thrust reference in UpdateGlobalThrust debug print 2026-06-29 23:38:07 -05:00
b8b22f29b1 fix number boolean comparison in PIDs 2026-06-29 23:37:14 -05:00
46c04bf646 fix out-of-scope issue with the D variable in PIDs 2026-06-29 23:35:43 -05:00
61c8f88d40 edit PIDs to output normalized data and changed how UpdatGlobalThrust works 2026-06-29 23:34:18 -05:00
cb7b9490bf make max thrust for normalization 50 2026-06-29 23:11:04 -05:00
600a683a12 make minimum thrust for normalization 0 2026-06-29 23:08:50 -05:00
373089d374 try to fix normalization again 2026-06-29 23:05:23 -05:00
23644f7072 switch signs around for correction values in poll functions 2026-06-29 22:50:29 -05:00
e11c6483f9 change thrust nomalization 2026-06-29 22:36:06 -05:00
7c0cd566b1 change the UpdateGlobalThrust thrust nomalization to actually have a contiguous range of values 2026-06-29 22:15:01 -05:00
aab62f51cc remove actualpower print statement from SetThrusterPower 2026-06-29 22:11:28 -05:00
12cad05ed6 convert ms to seconds in UpdateGlobalThrust 2026-06-29 22:09:03 -05:00
92988ac4f5 chage altitude PIDs 2026-06-29 22:02:52 -05:00
68a3f4589b continued from last commit 2026-06-29 21:58:00 -05:00
ed25934cb7 hopefully fix SetThrusterPower for rotators with analog transmissions 2026-06-29 21:56:04 -05:00
d2bacd30bb add another debug print and change position of debug print in SetThrusterPower 2026-06-29 21:52:47 -05:00
f38c765c40 add debug print to UpdateGlobalThrust 2026-06-29 21:49:07 -05:00
91c1723a9e add debug statement to SetThrusterPower 2026-06-29 21:46:22 -05:00
406cc966ec adjust kp values 2026-06-29 21:44:21 -05:00
d07b952d66 add heading correction to other sensor poll functions 2026-06-29 21:42:26 -05:00
85fd1d0d85 correct errors and slight code cleanup 2026-06-29 21:23:10 -05:00
0766e4ce5a maybe fix UpdateGlobalThrust? 2026-06-29 21:19:45 -05:00
a408dd9f34 adjust kp values for currently used PIDs (testing) 2026-06-29 21:08:48 -05:00
c80855dc2f continued from last commit 2026-06-29 21:02:07 -05:00
1ab0510826 have UpdateGlobalThrust attempt to counteract the actual pitch/roll instead of the pitch/roll rates 2026-06-29 21:01:46 -05:00
485c2eac37 fix displayInstrumentPanel pitch/roll incorrect references 2026-06-29 20:54:32 -05:00
996c49f5a3 moved PollVelocity debug print outside of loop to avoid redundant terminal output 2026-06-29 20:52:34 -05:00
1de102e132 move post-quit code outside of mainloop 2026-06-29 20:49:41 -05:00
06a10ff0f9 lower pause back to normal value 2026-06-29 20:48:24 -05:00
9b397337da i'm stupd 2026-06-29 20:47:53 -05:00
39a8b0ba88 set thruster power to 0 (and release control of transmission if applicable) on quit 2026-06-29 20:46:52 -05:00
3ab9bc411f attempt a different approach at finding transmissions 2026-06-29 20:41:46 -05:00
a508fbad58 added a break in populateThrusterValues to break out of loop when transmission is found 2026-06-29 20:31:33 -05:00
b431e1a0a6 added even more debug prints 2026-06-29 20:26:46 -05:00
13f970af76 added some more debug prints to populateThrusterValues 2026-06-29 20:17:52 -05:00
1f27dc0cda attempt another fix at transmission nil value population 2026-06-29 20:07:03 -05:00
b712a38006 increased pause between init and mainloop so that i can actually read debug prints 2026-06-29 20:04:31 -05:00
ce11e77d7c change some ordering and add some debug prints to thruster init functions 2026-06-29 20:03:05 -05:00
19c423ecfa added short pause before mainloop to display initialization changes 2026-06-29 19:55:14 -05:00
2d6e142eb6 remove unindexed thruster checking from mainloop for now 2026-06-29 19:53:51 -05:00
aff1a45a58 add nil checking for thruster init functions 2026-06-29 19:51:53 -05:00
ab8d65f477 added nil check for transmission list in populateThrusterValues 2026-06-29 19:48:22 -05:00
539fdd92fe hopefully fix some issues regarding thruster initialization 2026-06-29 19:46:03 -05:00
4976c5b227 fix PollVelocity empty array check 2026-06-29 16:19:31 -05:00
e27c899c17 add nil check for main SensorData subtables (but not values in those subtables) 2026-06-29 16:13:43 -05:00
e69c370c97 assorted bug fixes 2026-06-29 16:11:54 -05:00
9c22f72d4e add nil check for rotator thrusters with nil transmissions 2026-06-29 14:12:07 -05:00
dd4bc11c43 added error print for SetThrusterPower in order to catch nil thruster references 2026-06-29 14:10:17 -05:00
e13ae84aae hopefully fix nil reference error related to propeller transmissions 2026-06-29 14:02:58 -05:00
a9f483a05d remove missed UpdateStabilization reference in mainloop 2026-06-29 13:57:10 -05:00
d57bcf0ad4 begin reimplementation of UpdateGlobalThrust based on PIDs instead of the old system 2026-06-29 13:52:38 -05:00
de7decceaa modified thruster table to include all axis movement vectors 2026-06-29 13:51:57 -05:00
3 changed files with 504 additions and 159 deletions

46
docs.md
View File

@@ -194,6 +194,12 @@ Every cycle:
- Velocity Sensor `getAxis()`, `getVelocity`
- Poll input signal strengths for global downwards thrust, and fore and aft thrust
### Math
#### Calculate Counteractive Thrust
TODO: fill in
### Data Structures
#### Config
@@ -241,6 +247,8 @@ Config = {
}
```
#### SensorData
The member variables of the sensor table is as follows:
```lua
@@ -284,11 +292,22 @@ SensorData = {
}
```
#### Thrusters
The member variables of the Thruster table are as follows (using some example thrusters):
```lua
Thrusters = {
gyroscopic_propeller_bearing_0 = {
-- primary attitude thruster?
primary_pitch_thruster = true,
primary_roll_thruster = true,
primary_yaw_thruster = false,
-- primary lateral thruster?
primary_altitude_thruster = true,
primary_fore_thruster = false,
primary_aft_thruster = false,
x_config_equivalent = "1", -- if you're running a standard x configuration, what prop is this equivalent to?
type = "rotator",
name = "gyroscopic_propeller_bearing_0",
thruster = nil,
@@ -308,6 +327,15 @@ Thrusters = {
power = nil
},
gyroscopic_propeller_bearing_1 = {
-- primary attitude thruster?
primary_pitch_thruster = true,
primary_roll_thruster = true,
primary_yaw_thruster = false,
-- primary lateral thruster?
primary_altitude_thruster = true,
primary_fore_thruster = false,
primary_aft_thruster = false,
x_config_equivalent = "2", -- if you're running a standard x configuration, what prop is this equivalent to?
type = "rotator",
name = "gyroscopic_propeller_bearing_1",
thruster = nil,
@@ -327,6 +355,15 @@ Thrusters = {
power = nil
},
gyroscopic_propeller_bearing_2 = {
-- primary attitude thruster?
primary_pitch_thruster = true,
primary_roll_thruster = true,
primary_yaw_thruster = false,
-- primary lateral thruster?
primary_altitude_thruster = true,
primary_fore_thruster = false,
primary_aft_thruster = false,
x_config_equivalent = "4", -- if you're running a standard x configuration, what prop is this equivalent to?
type = "rotator",
name = "gyroscopic_propeller_bearing_2",
thruster = nil,
@@ -346,6 +383,15 @@ Thrusters = {
power = nil
},
gyroscopic_propeller_bearing_3 = {
-- primary attitude thruster?
primary_pitch_thruster = true,
primary_roll_thruster = true,
primary_yaw_thruster = false,
-- primary lateral thruster?
primary_altitude_thruster = true,
primary_fore_thruster = false,
primary_aft_thruster = false,
x_config_equivalent = "3", -- if you're running a standard x configuration, what prop is this equivalent to?
type = "rotator",
name = "gyroscopic_propeller_bearing_3",
thruster = nil,

533
main.lua
View File

@@ -76,7 +76,7 @@ function serializeTable(val, name, skipnewlines, depth)
local tmp = string.rep(" ", depth)
if name then
if not string.match(name, '^[a-zA-z_][a-zA-Z0-9_]*$') then
if not string.match(name, '^[a-zA-Z_][a-zA-Z0-9_]*$') then
name = string.gsub(name, "'", "\\'")
name = "['".. name .. "']"
end
@@ -153,6 +153,7 @@ end
Config.Autopilot = {
AutopilotEngaged = false,
AutoForeAft = false,
AutopilotDesiredAltitude = nil,
AutopilotDesiredSpeed = nil,
AutopilotDesiredHeading = nil
}
@@ -240,61 +241,60 @@ function PropellerInit()
local propellers = {}
for _, v in ipairs(TransmissionTypes) do
table.insert(transmissions, peripheral.find(v))
local vlist = { peripheral.find(v) }
if vlist ~= nil and vlist[1] ~= nil then
table.insert(transmissions, vlist)
end
end
for _, v in ipairs(PropellerTypes) do
table.insert(propellers, peripheral.find(v))
local vlist = peripheral.find(v)
if vlist ~= nil and vlist[1] ~= nil then
table.insert(propellers, vlist)
end
end
for pi, pv in ipairs(propellers) do
for ti, tv in ipairs(transmissions) do
if pv.getSubnetworkAnchorId() == tv.getSelfId() then
if Thrusters[peripheral.getName(pv)] == nil then
Thrusters[peripheral.getName(pv)] = {
type = "rotator",
thruster = pv,
transmission = tv,
--[[
NOTE:
affectVectors will have values depending on how the thruster's
thrust vector interacts with the vessel's attitude
angular:
possible directions for angular.yaw: "port"|"star"
possible directions for angular.roll: "port"|"star"
possible directions for angular.pitch: "up"|"down"
lateral:
possible directions for lateral.x: "port"|"star"
possible directions for lateral.y: "up"|"down"
possible directions for lateral.z: "fore"|"aft"
]]
affectVectors = {
angular = {
yaw = nil,
pitch = nil,
roll = nil
if Thrusters[peripheral.getName(pv)] == nil then
for _, tv in pairs(transmissions) do
if pv.getSourceId() == tv.getSelfId() then
if Thrusters[peripheral.getName(pv)] == nil then
Thrusters[peripheral.getName(pv)] = {
type = "rotator",
thruster = pv,
transmission = peripheral.wrap(tv),
--[[
NOTE:
affectVectors will have values depending on how the thruster's
thrust vector interacts with the vessel's attitude
angular:
possible directions for angular.yaw: "port"|"star"
possible directions for angular.roll: "port"|"star"
possible directions for angular.pitch: "up"|"down"
lateral:
possible directions for lateral.x: "port"|"star"
possible directions for lateral.y: "up"|"down"
possible directions for lateral.z: "fore"|"aft"
]]
affectVectors = {
angular = {
yaw = nil,
pitch = nil,
roll = nil
},
lateral = {
x = nil,
y = nil,
z = nil
}
},
lateral = {
x = nil,
y = nil,
z = nil
}
},
power = (tv.getSignal())/15
}
end
if Thrusters[peripheral.getName(pv)] ~= nil then
if Thrusters[peripheral.getName(pv)].type == nil then
Thrusters[peripheral.getName(pv)].type = "rotator"
power = (tv.getSignal())/15
}
end
Thrusters[peripheral.getName(pv)].thruster = pv
Thrusters[peripheral.getName(pv)].transmission = tv
if Thrusters[peripheral.getName(pv)].affectVectors == nil then
Thrusters[peripheral.getName(pv)].affectVectors = {}
end
Thrusters[peripheral.getName(pv)].power = (tv.getSignal())/15
end
end
elseif Config.Debug then
print("DEBUG: thruster of name "..peripheral.getName(pv).." already indexed, skipping (called by PropellerInit)")
end
end
end
@@ -303,7 +303,10 @@ function ThrusterInit()
local thrusters = {}
for _, v in ipairs(ThrusterTypes) do
table.insert(thrusters, peripheral.find(v))
local vlist = peripheral.find(v)
if vlist ~= nil and vlist[1] ~= nil then
table.insert(thrusters, vlist)
end
end
for ti, tv in ipairs(thrusters) do
@@ -339,22 +342,90 @@ function ThrusterInit()
},
power = tv.getPower()
}
elseif Config.Debug then
print("DEBUG: thruster of name "..peripheral.getName(tv).." already indexed, skipping (called by ThrusterInit)")
end
if Thrusters[peripheral.getName(tv)] ~= nil then
if Thrusters[peripheral.getName(tv)].type == nil then
Thrusters[peripheral.getName(tv)].type = "thruster"
end
end
-- this is for populating nil values from thruster config files
local function populateThrusterValues()
for _, thruster in pairs(Thrusters) do
if thruster.name == nil and thruster.thruster == nil then -- as long as we have one of thruster or name, we can fetch other data
print("ERROR: Thruster ".._.." has no name or thruster object. Removing entry in thruster list")
Thrusters[_] = nil
return
end
if thruster.name == nil and thruster.thruster ~= nil then
thruster.name = peripheral.getName(thruster.thruster)
end
if thruster.thruster == nil and thruster.name ~= nil then
thruster.thruster = peripheral.wrap(thruster.name)
end
if thruster.type == nil then
if Config.Debug then print("DEBUG: Thruster "..thruster.name.." does not have a type.") end
for _, tt in ipairs(ThrusterTypes) do
if peripheral.getType(thruster.thruster) == tt then
if Config.Debug then print("DEBUG: Detected thruster "..thruster.name.." is of type 'thruster'.") end
thruster.type = "thruster"
break
end
end
Thrusters[peripheral.getName(tv)].thruster = tv
Thrusters[peripheral.getName(tv)].transmission = nil
if Thrusters[peripheral.getName(tv)].affectVectors == nil then
Thrusters[peripheral.getName(tv)].affectVectors = {}
for _, pt in ipairs(PropellerTypes) do
if peripheral.getType(thruster.thruster) == pt then
if Config.Debug then print("DEBUG: Detected thruster "..thruster.name.." is of type 'rotator'.") end
thruster.type = "rotator"
break
end
end
end
if thruster.type == "rotator" and thruster.transmission == nil then
if Config.Debug then print("DEBUG: Thruster "..thruster.name.." does not have an associated transmission.") end
local subnetworkId = peripheral.wrap(thruster.name).getSourceId()
for _, transmissionType in ipairs(TransmissionTypes) do
local transmissions = { peripheral.find(transmissionType) }
if transmissions ~= nil and transmissions[1] ~= nil then
if Config.Debug then print("DEBUG: transmission of type "..transmissionType.." found.") end
for _, transmission in ipairs(transmissions) do
local thisTransmissionId = transmission.getSelfId()
if Config.Debug then print("DEBUG: Comparing subnetwork IDs values: "..thisTransmissionId.." == "..subnetworkId) end
if transmission.getSelfId() == subnetworkId then
if Config.Debug then print("DEBUG: Detected that thruster "..thruster.name.." is connected to transmission "..peripheral.getName(transmission)) end
thruster.transmission = peripheral.getName(transmission)
break
elseif Config.Debug then
print("DEBUG: Comparison inequal")
end
end
elseif Config.Debug then
print("DEBUG: No transmissions of type "..transmissionType.." (print from populateThrusterValues)")
end
end
end
if thruster.power == nil then
if Config.Debug then print("DEBUG: Thruster "..thruster.name..": thruster power is not populated") end
if thruster.type == "thruster" then
thruster.power = thruster.thruster.getPower()
elseif thruster.type == "rotator" then
if thruster.transmission ~= nil then
local transmissionType = peripheral.getType(peripheral.wrap(thruster.transmission))
if transmissionType == "analog_transmission" then
thruster.power = (peripheral.wrap(thruster.transmission).getSignal())/15
elseif transmissionType == "Create_RotationSpeedController" then
thruster.power = (peripheral.wrap(thruster.transmission).getTargetSpeed())/256
end
else
print("ERROR: Thruster "..peripheral.getName(thruster.thruster).." has no transmission.")
end
end
Thrusters[peripheral.getName(tv)].power = tv.getPower()
end
end
end
function Update()
populateThrusterValues()
PropellerInit()
ThrusterInit()
end
@@ -453,8 +524,8 @@ local function partiallyUpdateThrusters(thrusterList)
for _, tt in ipairs(TransmissionTypes) do
local transmissions = peripheral.find(tt)
for _, t in ipairs(transmissions) do
if tv.getSubnetworkAnchorId() == t.getSelfId() then
thisThruster.transmission = t
if tv.getSourceId() == t.getSelfId() then
thisThruster.transmission = peripheral.getName(t)
end
end
end
@@ -486,7 +557,7 @@ function PollVelocity()
-- Velocity Sensors
VelocityVectors = {}
if velSensors[0] == nil then -- verify array is not empty
if velSensors[1] ~= nil then -- verify array is not empty
SensorData.Velocity = {}
@@ -494,15 +565,28 @@ function PollVelocity()
local vsAxis = velsensor.getAxis()
local vsVelocity = velsensor.getVelocity()
-- correct for heading correction value
if Config.SensorCorrection.Heading == -90 or Config.SensorCorrection.Heading == 270 then
if vsAxis == "x" then vsAxis = "z" end
if vsAxis == "z" then vsAxis = "x" end
elseif Config.SensorCorrection.Heading == 180 or Config.SensorCorrection.Heading == -180 then
vsVelocity = -vsVelocity
elseif Config.SensorCorrection.Heading == 90 or Config.SensorCorrection.Heading == -270 then
if vsAxis == "x" then vsAxis = "z" end
if vsAxis == "z" then vsAxis = "x" end
vsVelocity = -vsVelocity
end
VelocityVectors[vsAxis] = vsVelocity
SensorData.Velocity.Raw = VelocityVectors
if Config.Debug then
print("DEBUG: PollVelocity fetched sensor data: "..tableToString(SensorData.Velocity))
end
end
if Config.Debug then
print("DEBUG: PollVelocity fetched sensor data: "..tableToString(SensorData.Velocity))
end
else
if Config.Debug then
print("DEBUG: No velocity sensors found.")
@@ -593,13 +677,27 @@ function PollGimbal()
zAngle = 0 -- roll angle
}
for i, v in ipairs(gimbalSensor.getAngles()) do
if i == 0 then
if i == 1 then
Angles.xAngle = v
elseif i == 1 then
elseif i == 2 then
Angles.zAngle = v
end
end
-- correct for heading correction value
if Config.SensorCorrection.Heading == -90 or Config.SensorCorrection.Heading == 270 then
local temp = Angles.xAngle
Angles.xAngle = Angles.zAngle
Angles.zAngle = temp
elseif Config.SensorCorrection.Heading == 180 or Config.SensorCorrection.Heading == -180 then
Angles.xAngle = -Angles.xAngle
Angles.zAngle = -Angles.zAngle
elseif Config.SensorCorrection.Heading == 90 or Config.SensorCorrection.Heading == -270 then
local temp = Angles.xAngle
Angles.xAngle = -Angles.zAngle
Angles.zAngle = -temp
end
AngularRates = {
wx = 0, -- pitch rate
wy = 0, -- yaw rate
@@ -614,6 +712,21 @@ function PollGimbal()
AngularRates.wz = v
end
end
-- correct for heading correction value
if Config.SensorCorrection.Heading == -90 or Config.SensorCorrection.Heading == 270 then
local temp = AngularRates.wx
AngularRates.wx = AngularRates.wz
AngularRates.wz = temp
elseif Config.SensorCorrection.Heading == 180 or Config.SensorCorrection.Heading == -180 then
AngularRates.wx = -AngularRates.wx
AngularRates.wz = -AngularRates.wz
elseif Config.SensorCorrection.Heading == 90 or Config.SensorCorrection.Heading == -270 then
local temp = AngularRates.wx
AngularRates.wx = -AngularRates.wz
AngularRates.wz = -temp
end
LinearAcceleration = {
ax = 0, -- port-starboard axis
ay = 0, -- up-down axis
@@ -629,12 +742,26 @@ function PollGimbal()
end
end
-- correct for heading correction value
if Config.SensorCorrection.Heading == -90 or Config.SensorCorrection.Heading == 270 then
local temp = LinearAcceleration.ax
LinearAcceleration.ax = LinearAcceleration.az
LinearAcceleration.az = temp
elseif Config.SensorCorrection.Heading == 180 or Config.SensorCorrection.Heading == -180 then
LinearAcceleration.ax = -LinearAcceleration.ax
LinearAcceleration.az = -LinearAcceleration.az
elseif Config.SensorCorrection.Heading == 90 or Config.SensorCorrection.Heading == -270 then
local temp = LinearAcceleration.ax
LinearAcceleration.ax = -LinearAcceleration.az
LinearAcceleration.az = -temp
end
SensorData.Gimbal.Angles = Angles
SensorData.Gimbal.AngularRates = AngularRates
SensorData.Gimbal.LinearAcceleration = LinearAcceleration
if Config.Debug then
print("DEBUG: PollGimbaal fetched sensor data: "..tableToString(SensorData.Gimbal))
print("DEBUG: PollGimbal fetched sensor data: "..tableToString(SensorData.Gimbal))
end
end
end
@@ -655,10 +782,24 @@ end
-- thruster is a thruster type, power is a vector from 0.0 to 1.0
function SetThrusterPower(thruster, power)
if thruster == nil then
print("ERROR: SetThrusterPower called with nil thruster.")
return
end
if power > 1.0 then power = 1.0 elseif power < 0.0 then power = 0.0 end
--if Config.Debug then print("DEBUG: thruster "..thruster.name..": power "..power) end
if thruster.type == "rotator" then
thruster.power = power
local actualPower = 0
if peripheral.getType(thruster.transmission) == "Create_RotationSpeedController" then
if thruster.transmission == nil then
print("ERROR: SetThrusterPower called with nil transmission for rotator thruster.")
print("Thruster: "..thruster.name)
return
end
if peripheral.getType(peripheral.wrap(thruster.transmission)) == "Create_RotationSpeedController" then
if (math.ceil(power*256) - power*256 <= 0.5) then
actualPower = math.ceil(power*256)
end
@@ -668,7 +809,7 @@ function SetThrusterPower(thruster, power)
thruster.transmission.setTargetSpeed(actualPower)
end
if peripheral.getType(thruster.transmission) == "analog_transmission" then
if peripheral.getType(peripheral.wrap(thruster.transmission)) == "analog_transmission" then
if (math.ceil(power*15) - power*15) <= 0.5 then
actualPower = math.ceil(power*15)
end
@@ -678,16 +819,16 @@ function SetThrusterPower(thruster, power)
if actualPower < 1 then
actualPower = 15 -- signal 15 will decouple the speed and stop the motion
end
if actualPower <= 15 then
elseif actualPower <= 15 then
actualPower = actualPower - 1
end
thruster.transmission.setSignal(actualPower)
peripheral.wrap(thruster.transmission).setSignal(actualPower)
-- if Config.Debug then print("DEBUG: actualPower: "..actualPower) end
end
end
if thruster.type == "thruster" then
elseif thruster.type == "thruster" then
thruster.power = power
thruster.thruster.setPowerNormalized(power)
end
@@ -702,80 +843,173 @@ function GetThrusterPower(thruster)
end
end
-- PID systems for stabilization and autopilot
LastTime = os.epoch("utc")
function GetDeltaTime()
local currentTime = os.epoch("utc")
local dt = (currentTime - LastTime) / 1000 -- convert milliseconds to seconds
LastTime = currentTime
if dt <= 0 then dt = 0.001 end -- prevent division by zero
return dt
end
function CreatePID(kp, ki, kd)
return {
kp = kp,
ki = ki,
kd = kd,
integral = 0, lastError = 0,
minOutput = 0.0,
maxOutput = 32.0,
update = function(self, setpoint, pv, dt)
if dt <= 0 then return 0.0 end
local error = setpoint - pv
local P = self.kp * error
local D = self.kd * ((error - self.lastError) / dt)
local potential_i = self.integral + (self.ki * error * dt)
local raw_output = P + potential_i + D
if Config.Debug then print("DEBUG: PID raw output: "..raw_output) end
local clampedOutput = 0
if raw_output >= self.minOutput and raw_output <= self.maxOutput then
self.integral = potential_i
clampedOutput = raw_output
else
clampedOutput = math.max(self.minOutput, math.min(raw_output, self.maxOutput))
end
self.lastError = error
return clampedOutput
end
}
end
-- tune these for the best values for each vessel
PIDs = {
-- angular
-- attitude PIDs
RollAttitudePID = CreatePID(3.0, 0.01, 0.05),
PitchAttitudePID = CreatePID(3.0, 0.01, 0.05),
YawAttitudePID = CreatePID(0.1, 0.01, 0.05),
-- rate PIDs
RollRatePID = CreatePID(0.8, 0.01, 0.05),
PitchRatePID = CreatePID(0.8, 0.01, 0.05),
YawRatePID = CreatePID(0.5, 0.01, 0.05),
-- lateral
-- value PIDs
AltitudePID = CreatePID(0.6, 0.01, 0.05),
PortStarPID = CreatePID(0.1, 0.01, 0.05),
ForeAftPID = CreatePID(0.1, 0.01, 0.05),
-- rate PIDs
VerticalRatePID = CreatePID(0.8, 0.01, 0.05),
PortStarRatePID = CreatePID(0.1, 0.01, 0.05),
ForeAftRatePID = CreatePID(0.1, 0.01, 0.05)
}
-- min and max thrust for thrust normalization
local minThrust = math.huge
local maxThrust = -math.huge
local function normalize(val, min, max)
if min == max then return 0.5 end
return (val - min) / (max - min)
end
function UpdateGlobalThrust()
-- for each thruster, there is a table of affectVectors that determine in what directions the thruster can apply thrust
-- each thruster has a power value from 0 to 1, which is the amount of thrust the thruster is currently applying
-- depending on the desired thrust vectors, some counteractive desired thrust vectors will be ignored
-- lateral thrust
UpdateGlobalLateralThrust()
local dt = GetDeltaTime() / 1000
-- angular thrust
UpdateGlobalAngularThrust()
end
function UpdateGlobalLateralThrust()
-- lateral thrust
-- verify that SensorData.Velocity.Raw is not nil
if SensorData.Velocity == nil or SensorData.Velocity.Raw == nil then
if Config.Debug then
print("DEBUG: SensorData.Velocity.Raw or SensorData.Velocity is nil, skipping UpdateGlobalLateralThrust")
end
if SensorData.Gimbal == nil or SensorData.Altitude == nil or SensorData.Velocity == nil then
print("ERROR: One or more of SensorData is nil. Cannot update thrust.")
return
end
local AltitudeRateOutput = PIDs.VerticalRatePID:update(Config.Autopilot.AutopilotDesiredAltitude, SensorData.Altitude.Altitude, dt)
local throttleOutput = PIDs.AltitudePID:update(AltitudeRateOutput, SensorData.Altitude.VerticalSpeed, dt)
if Config.Debug then print("DEBUG: Throttle Output: "..throttleOutput) end
-- TODO: Refactor this
local pitchOutput = PIDs.PitchAttitudePID:update(0, SensorData.Gimbal.Angles.xAngle, dt)
local pitchRateOutput = PIDs.PitchRatePID:update(pitchOutput, SensorData.Gimbal.AngularRates.wx or 0, dt)
if Config.Debug then print("DEBUG: Pitch Output: "..pitchRateOutput) end
local desiredLateralThrustVectors = {}
local rollOutput = PIDs.RollAttitudePID:update(0, SensorData.Gimbal.Angles.zAngle, dt)
local rollRateOutput = PIDs.RollRatePID:update(rollOutput, SensorData.Gimbal.AngularRates.wz or 0, dt)
if Config.Debug then print("DEBUG: Roll Output: "..rollRateOutput) end
for f, v in pairs(SensorData.Velocity.Raw) do
desiredLateralThrustVectors[f] = CustomSigmoid(v)
end
local yawOutput = 5
if Config.Autopilot.AutopilotDesiredHeading ~= nil then yawOutput = PIDs.YawAttitudePID:update(Config.Autopilot.AutopilotDesiredHeading, SensorData.NavTable.Heading, dt) end
local yawRateOutput = PIDs.YawRatePID:update(Config.Autopilot.AutopilotDesiredYawRate or 0, SensorData.Gimbal.AngularRates.wy or 0, dt)
--[[
desiredLateralThrustVectors is a table that is similar to SensorData.Velocity.Raw, but with values between -1 and 1.
essentially, it reverses the velocity vector and then clamps the range between -1 and 1, so that the value can be used for thrust power
]]
for _, thruster in pairs(Thrusters) do
local desiredThrust = 0
local thrustDirections = {}
for f, v in pairs(desiredLateralThrustVectors) do
if f == "x" then
if v > 0 then table.insert(thrustDirections, { port = v }) else table.insert(thrustDirections, { star = math.abs(v) }) end
elseif f == "y" then
if v > 0 then table.insert(thrustDirections, { down = v }) else table.insert(thrustDirections, { up = math.abs(v) }) end
elseif f == "z" then
if v > 0 then table.insert(thrustDirections, { fore = v }) else table.insert(thrustDirections, { aft = math.abs(v) }) end
if thruster.x_config_equivalent ~= nil then
-- Calculate desired thrust based on affectVectors and PID outputs
if thruster.affectVectors.angular.pitch ~= nil and thruster.primary_pitch_thruster then
if thruster.affectVectors.angular.pitch == "up" then
desiredThrust = desiredThrust + pitchRateOutput
else
desiredThrust = desiredThrust - pitchRateOutput
end
end
if thruster.affectVectors.angular.roll ~= nil and thruster.primary_roll_thruster then
if thruster.affectVectors.angular.roll == "port" then
desiredThrust = desiredThrust + rollRateOutput
else
desiredThrust = desiredThrust - rollRateOutput
end
end
if thruster.affectVectors.angular.yaw ~= nil and thruster.primary_yaw_thruster then
if thruster.affectVectors.angular.yaw == "port" then
desiredThrust = desiredThrust + yawRateOutput
else
desiredThrust = desiredThrust - yawRateOutput
end
end
if thruster.affectVectors.lateral.y == "up" then
desiredThrust = desiredThrust + throttleOutput
end
--if Config.Debug then print("DEBUG: "..thruster.name..": desiredThrust "..desiredThrust.." (from UpdateGlobalThrust)") end
SetThrusterPower(thruster, desiredThrust)
else
if Config.Debug then print("DEBUG: Thruster has x_config_equivalent set") end
local prop1 = throttleOutput + pitchRateOutput + rollRateOutput -- front-left
local prop2 = throttleOutput + pitchRateOutput - rollRateOutput -- front-right
local prop3 = throttleOutput - pitchRateOutput - rollRateOutput -- back-right
local prop4 = throttleOutput - pitchRateOutput + rollRateOutput -- back-left
if thruster.x_config_equivalent == "1" then SetThrusterPower(thruster, prop1)
elseif thruster.x_config_equivalent == "2" then SetThrusterPower(thruster, prop2)
elseif thruster.x_config_equivalent == "3" then SetThrusterPower(thruster, prop3)
elseif thruster.x_config_equivalent == "4" then SetThrusterPower(thruster, prop4)
else print("ERROR: Thruster "..thruster.name.." has invalid x_config_equivalent value")
end
end
end
--[[
thrustDirections is an array of tables, each table has a key that is the direction of the desired thrust, and a value that is the magnitude of the desired thrust
like so:
thrustDirections = {
{ port = 0.5 },
{ down = 0.1 },
{ aft = 0.6 }
}
there will only ever be three tables in thrustDirections, as there are only three axes of movement, and each axis can only have one desired thrust direction at a time
(angular thrust is the same, but with different keys for the tables, like pitchup/pitchdown, yawport/yawstar, rollport/rollstar)
]]
end
function UpdateGlobalAngularThrust()
-- angular thrust
-- verify that SensorData.Gimbal.AngularRates is not nil
if SensorData.Gimbal == nil or SensorData.Gimbal.AngularRates == nil then
if Config.Debug then
print("DEBUG: SensorData.Gimbal.AngularRates or SensorData.Gimbal is nil, skipping UpdateGlobalAngularThrust")
end
return
end
-- TODO: Implement this
end
function Init()
@@ -825,7 +1059,10 @@ function Init()
end
end
-- If we're here, the config loaded successfully and the initialization is done.
-- If we're here, the config loaded successfully.
populateThrusterValues()
Update()
end
-- Display functions for monitors
@@ -845,9 +1082,9 @@ function displayInstrumentPanel(monitor, sensorData)
if next(sensorData) ~= nil then
if sensorData then
m.setCursorPos(1, 3)
m.write("Pitch: " .. string.format("%.2f", SensorData.Gimbal.Angles[0] or 0))
m.write("Pitch: " .. string.format("%.2f", SensorData.Gimbal.Angles.xAngle or 0))
m.setCursorPos(1, 4)
m.write("Roll: " .. string.format("%.2f", SensorData.Gimbal.Angles[1] or 0))
m.write("Roll: " .. string.format("%.2f", SensorData.Gimbal.Angles.zAngle or 0))
m.setCursorPos(1, 5)
m.write("Yaw: " .. string.format("%.2f", SensorData.NavTable.Heading or 0))
m.setCursorPos(1, 6)
@@ -926,6 +1163,12 @@ function collectSensorData()
end
function WriteConfigFiles()
-- since the thruster object will change every time, set each thruster's thruster object to nil before writing to the config file, so that the config file only contains the thruster names and not the thruster objects
for _, thruster in pairs(Thrusters) do
thruster.thruster = nil
thruster.transmission = nil
end
print("Writing to thruster config file.")
local thrusterConfigFile = fs.open(Config.thrusterConfigPath, "w+")
thrusterConfigFile.write(tableToString(Thrusters))
@@ -940,13 +1183,13 @@ end
function Main()
Init()
os.sleep(5) -- pause for any initialization errors to display
local sentinel = true
print("Mainloop starting. Press 'q' to stop the loop and save configuration changes.")
while sentinel do
PollSensors()
UpdateStabilization()
UpdateGlobalThrust()
-- Update monitor displays
@@ -958,17 +1201,10 @@ function Main()
displayAutopilotControls(Config.Monitors.AutopilotControlMonitor, SensorData)
end
local unindexedThrusters = checkIfThrusterIsIndexed()
if unindexedThrusters ~= nil then
partiallyUpdateThrusters(unindexedThrusters)
end
local function getTerminateEvent()
local event, key, is_held = os.pullEventRaw("key")
if keys.getName(key) == "q" then
print("Quit Input Received")
WriteConfigFiles()
sentinel = false
else
@@ -980,6 +1216,17 @@ function Main()
parallel.waitForAny(do_sleep, getTerminateEvent)
end
for _, t in pairs(Thrusters) do
if t.type == "rotator" then
SetThrusterPower(t, 0.0)
peripheral.wrap(t.transmission).releaseSignal() -- release control of transmissions on quit
else
SetThrusterPower(t, 0.0)
end
end
WriteConfigFiles()
end
-- Run the main function when the script starts

View File

@@ -1,53 +1,105 @@
return {
gyroscopic_propeller_bearing_0 = {
primary_pitch_thruster = true,
primary_roll_thruster = true,
primary_yaw_thruster = false,
primary_altitude_thruster = true,
primary_fore_thruster = false,
primary_aft_thruster = false,
x_config_equivalent = "1",
type = "rotator",
name = "gyroscopic_propeller_bearing_0",
thruster = nil,
transmission = nil,
affectVectors = {
yaw = nil,
pitch = "up",
roll = "star",
lateral = nil
angular = {
yaw = nil,
pitch = "up",
roll = "star",
},
lateral = {
x = nil,
y = "up",
z = nil
}
},
power = nil
},
gyroscopic_propeller_bearing_1 = {
primary_pitch_thruster = true,
primary_roll_thruster = true,
primary_yaw_thruster = false,
primary_altitude_thruster = true,
primary_fore_thruster = false,
primary_aft_thruster = false,
x_config_equivalent = "2",
type = "rotator",
name = "gyroscopic_propeller_bearing_1",
thruster = nil,
transmission = nil,
affectVectors = {
yaw = nil,
pitch = "up",
roll = "port",
lateral = nil
angular = {
yaw = nil,
pitch = "up",
roll = "port",
},
lateral = {
x = nil,
y = "up",
z = nil
}
},
power = nil
},
gyroscopic_propeller_bearing_2 = {
primary_pitch_thruster = true,
primary_roll_thruster = true,
primary_yaw_thruster = false,
primary_altitude_thruster = true,
primary_fore_thruster = false,
primary_aft_thruster = false,
x_config_equivalent = "4",
type = "rotator",
name = "gyroscopic_propeller_bearing_2",
thruster = nil,
transmission = nil,
affectVectors = {
yaw = nil,
pitch = "down",
roll = "port",
lateral = nil
angular = {
yaw = nil,
pitch = "down",
roll = "port",
},
lateral = {
x = nil,
y = "up",
z = nil
}
},
power = nil
},
gyroscopic_propeller_bearing_3 = {
primary_pitch_thruster = true,
primary_roll_thruster = true,
primary_yaw_thruster = false,
primary_altitude_thruster = true,
primary_fore_thruster = false,
primary_aft_thruster = false,
x_config_equivalent = "3",
type = "rotator",
name = "gyroscopic_propeller_bearing_3",
thruster = nil,
transmission = nil,
affectVectors = {
yaw = nil,
pitch = "down",
roll = "star",
lateral = nil
angular = {
yaw = nil,
pitch = "down",
roll = "star",
},
lateral = {
x = nil,
y = "up",
z = nil
}
},
power = nil
}