Compare commits

...

80 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
d9c29c89be refactored thruster init to follow new thruster schema; removed redundant UpdateStabilization function; removed some implimentation from the UpdateGlobalThrust functions in preparation for refactor 2026-06-28 11:02:53 -05:00
583c291af7 i switched up angular and lateral in the last commit. fixed 2026-06-28 10:55:58 -05:00
38e2e36e25 update thruster table for better thrust direction support 2026-06-28 10:53:15 -05:00
0c42fdaa8e removed debug statements from previous commit and (hopefully) fixed the array not empty check, since the check was designed for tables and not arrays 2026-06-27 18:04:28 -05:00
c724e848f7 more debug statements for troubleshooting 2026-06-27 18:02:05 -05:00
67c6916632 random test 1 for velocity sensors not found error 2026-06-27 18:00:08 -05:00
14ba0ba5ab re-order PollSensors to verify that the most important sensors have their debug statements display last 2026-06-27 17:58:52 -05:00
02b7e64301 accidentally forgot to re-define thrustDirections in UpdateGlobalAngularThrust 2026-06-27 17:55:02 -05:00
286e7287aa broke out UpdateGlobalThrust into UpdateGlobalLateralThrust and UpdateGlobalAngularThrust to check if the sensor issue is isolated 2026-06-27 17:53:49 -05:00
f229d407f8 refine nil check in last commit 2026-06-27 17:50:08 -05:00
621c27e551 nil verification in UpdateGlobalThrust for required SensorData fields 2026-06-27 17:48:30 -05:00
633e2c11d5 fix accidental function references instead of function value references 2026-06-27 17:41:09 -05:00
ae0e728d3e first implementation of the UpdateGlobalThrust function 2026-06-27 17:34:10 -05:00
ba5888bf1a added a WIP schema for all data structures under the ### Data Structures heading 2026-06-27 17:33:48 -05:00
ae5091c31e fix typo 2026-06-27 16:46:28 -05:00
3 changed files with 815 additions and 183 deletions

221
docs.md
View File

@@ -119,7 +119,7 @@ Relevant peripherals:
> note: returns 0 if the magnitude is below 0.05 m/s
- `getAxis()`: returns the body-frame axis the sensor measures along
- returns string "x", "y", and "z"
- returns string "x", "y", or "z"
### Analog Transmission
@@ -193,3 +193,222 @@ Every cycle:
- Altitude Sensor `getHeight()`, `getAirPressure()`, `getVerticalSpeed()`
- 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
The member variables of the Config table is as follows:
```lua
Config = {
ConfigPath = "/path/to/config.txt",
thrusterConfigPath = "path/to/thrusters.txt",
Debug = false, -- controls various debug print statements
Monitors = {
InstrumentPanelMonitor = {
name = "peripheralName",
peripheral = {} -- stores the table returned by running peripheral.wrap(name)
},
AutopilotControlMonitor = {
name = "peripheralName",
peripheral = {}
}
},
Autopilot = {
AutopilotEngaged = false, -- global shutoff for all autopilot functions
AutoForeAft = false, -- automatic forward/rearward throttle
AutopilotDesiredSpeed = nil, -- keep a certain velocity
AutopilotDesiredHeading = nil -- maintain a certain heading
},
SensorCorrection = {
Heading = 0 -- this is directly added to the heading value, in case the vessel was built with the heading not being direct north
},
Throttles = {
fore = {
name = "peripheralName",
side = "redstoneRelaySide" -- it is assumed that a user will use a redstone relay for their throttles
},
aft = {
name = "peripheralName",
side = "redstoneRelaySide"
},
down = {
name = "peripheralName",
side = "redstoneRelaySide"
}
}
}
```
#### SensorData
The member variables of the sensor table is as follows:
```lua
SensorData = {
Velocity = {
Raw = {
x = nil,
y = nil,
z = nil
}
},
Altitude = {
Altitude = nil,
AirPressure = nil,
VerticalSpeed = nil
},
NavTable = {
Heading = nil,
HasTarget = nil,
-- values past this are only populated if HasTarget == true
TargetBearing = nil,
TargetClosureRate = nil,
TargetVerticalOffset = nil,
TargetRelativeAngle = nil
},
Gimbal = {
Angles = {
xAngle = nil,
zAngle = nil
},
AngularRates = {
wx = nil,
wy = nil,
wz = nil
},
LinearAcceleration = {
ax = nil,
ay = nil,
az = nil
}
}
```
#### 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,
transmission = nil,
affectVectors = {
angular = {
yaw = nil,
pitch = "up",
roll = "star",
},
lateral = {
x = nil,
y = "up",
z = nil
}
},
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,
transmission = nil,
affectVectors = {
angular = {
yaw = nil,
pitch = "up",
roll = "port",
},
lateral = {
x = nil,
y = "up",
z = nil
}
},
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,
transmission = nil,
affectVectors = {
angular = {
yaw = nil,
pitch = "down",
roll = "port",
},
lateral = {
x = nil,
y = "up",
z = nil
}
},
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,
transmission = nil,
affectVectors = {
angular = {
yaw = nil,
pitch = "down",
roll = "star",
},
lateral = {
x = nil,
y = "up",
z = nil
}
},
power = nil
}
}
```

693
main.lua
View File

@@ -1,37 +1,31 @@
Thrusters = {
thruster0 = {
type = "rotator",
name = nil,
thruster = nil,
transmission = nil,
-- NOTE:
-- affectVectors will have values depending on how the thruster's
-- thrust vector interacts with the vessel's attitude
-- possible directions for yaw: "port"|"star"
-- possible directions for roll: "port"|"star"
-- possible directions for pitch: "up"|"down"
-- possible directions for lateral: "port"|"star"|"fore"|"aft"|"up"|"down"
affectVectors = {
yaw = nil,
pitch = nil,
roll = nil,
lateral = nil
Thrusters = {} -- Table of thruster tables; see docs.md####Thrusters for the details of the thruster table structure
ThrustDirections = {
Angular = {
Negative = {
x = "pitchdown",
y = "yawport",
z = "rollport"
},
power = 0
Positive = {
x = "pitchup",
y = "yawstar",
z = "rollstar"
}
},
thruster1 = {
type = "thruster",
name = nil,
thruster = nil,
affectVectors = {
yaw = nil,
pitch = nil,
roll = nil,
lateral = nil
Lateral = {
Negative = {
x = "star",
y = "down",
z = "aft"
},
power = 0
Positive = {
x = "port",
y = "up",
z = "fore"
}
}
} -- Table of thruster tables
}
SensorData = {}
@@ -82,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
@@ -159,6 +153,7 @@ end
Config.Autopilot = {
AutopilotEngaged = false,
AutoForeAft = false,
AutopilotDesiredAltitude = nil,
AutopilotDesiredSpeed = nil,
AutopilotDesiredHeading = nil
}
@@ -246,49 +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
-- possible directions for yaw: "port"|"star"
-- possible directions for roll: "port"|"star"
-- possible directions for pitch: "up"|"down"
-- possible directions for lateral: "port"|"star"|"fore"|"aft"|"up"|"down"
affectVectors = {
yaw = nil,
pitch = nil,
roll = nil,
lateral = 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"
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
}
},
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
@@ -297,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
@@ -306,37 +315,117 @@ function ThrusterInit()
thruster = tv,
transmission = nil,
type = "thruster",
-- NOTE:
-- affectVectors will have values depending on how the thruster's
-- thrust vector interacts with the vessel's attitude
-- possible directions for yaw: "port"|"star"
-- possible directions for roll: "port"|"star"
-- possible directions for pitch: "up"|"down"
-- possible directions for lateral: "port"|"star"|"fore"|"aft"|"up"|"down"
--[[
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 = {
yaw = nil,
pitch = nil,
roll = nil,
lateral = nil
angular = {
yaw = nil,
pitch = nil,
roll = nil
},
lateral = {
x = nil,
y = nil,
z = nil
}
},
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
@@ -395,10 +484,16 @@ local function partiallyUpdateThrusters(thrusterList)
transmission = nil,
type = "thruster",
affectVectors = {
yaw = nil,
roll = nil,
pitch = nil,
lateral = nil
angular = {
yaw = nil,
roll = nil,
pitch = nil
},
lateral = {
x = nil,
y = nil,
z = nil
}
},
power = tv.getPower()
}
@@ -412,10 +507,16 @@ local function partiallyUpdateThrusters(thrusterList)
transmission = nil,
type = "rotator",
affectVectors = {
yaw = nil,
roll = nil,
pitch = nil,
lateral = nil
angular = {
yaw = nil,
roll = nil,
pitch = nil
},
lateral = {
x = nil,
y = nil,
z = nil
}
},
power = tv.getPower()
}
@@ -423,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
@@ -435,14 +536,6 @@ local function partiallyUpdateThrusters(thrusterList)
end
end
function UpdateStabilization()
-- This function needs full implementation according to requirements in docs.md
-- It should adjust thrust based on sensor data to achieve target angles/velocities
-- For now, implement placeholder functionality that calls the existing thrust methods
print("UpdateStabilization called")
end
function PollThrottle()
for _, v in pairs(Config.Throttles) do
if v.name and v.side then
@@ -452,10 +545,10 @@ function PollThrottle()
end
function PollSensors()
PollVelocity()
PollAltitude()
PollNavTable()
PollAltitude()
PollGimbal()
PollVelocity()
end
function PollVelocity()
@@ -464,8 +557,7 @@ function PollVelocity()
-- Velocity Sensors
VelocityVectors = {}
local next = next
if next(velSensors) == nil then -- verify table is not empty
if velSensors[1] ~= nil then -- verify array is not empty
SensorData.Velocity = {}
@@ -473,15 +565,32 @@ 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.")
end
end
end
@@ -563,35 +672,134 @@ function PollGimbal()
SensorData.Gimbal = {}
Angles = gimbalSensor.getAngles()
AngularRates = gimbalSensor.getAngularRates()
LinearAcceleration = gimbalSensor.getLinearAcceleration()
Angles = {
xAngle = 0, -- pitch angle
zAngle = 0 -- roll angle
}
for i, v in ipairs(gimbalSensor.getAngles()) do
if i == 1 then
Angles.xAngle = v
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
wz = 0 -- roll rate
}
for i, v in ipairs(gimbalSensor.getAngularRates()) do
if i == 0 then
AngularRates.wx = v
elseif i == 1 then
AngularRates.wy = v
elseif i == 2 then
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
az = 0 -- fore-aft axis
}
for i, v in ipairs(gimbalSensor.getLinearAcceleration()) do
if i == 0 then
LinearAcceleration.ax = v
elseif i == 1 then
LinearAcceleration.ay = v
elseif i == 2 then
LinearAcceleration.az = v
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
-- the sigmoid is probably the best function to use for determining how much thrust to apply based on the current velocity or angular rate
function Sigmoid(x)
local e = 2.718281828459045
return 1 / (1 + e^(-x))
end
-- this sigmoid is modified to return a value between -1 and 1, and inverted so that the output is negative when the input is positive, and vice versa
-- the idea is to take the velocity or angular rate, and return a value that can be used to determine how much thrust to apply in the opposite direction to counteract it
-- then, the output can be given to the update thrust function which will propogate the value to the thrusters
function CustomSigmoid(x)
return ((2 * Sigmoid(x)) - 1) * -1
return ((-2 * Sigmoid(x)) + 1)
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
@@ -601,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
@@ -611,58 +819,197 @@ 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
end
function GetThrusterPower(thruster)
if thruster.type == "rotator" then
return thruster.power
end
if thruster.type == "thruster" then
return thruster.thruster.getPower()
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()
-- lateral thrust
local desiredLateralThrustVectors = {}
-- 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
for f, v in pairs(VelocityVectors) do
desiredLateralThrustVectors[f] = CustomSigmoid(v)
local dt = GetDeltaTime() / 1000
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
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
end
if f == "y" then
if v > 0 then table.insert(thrustDirections, { down = v }) else table.insert(thrustDirections, { up = math.abs(v) }) end
end
if f == "z" then
if v > 0 then table.insert(thrustDirections, { fore = v }) else table.insert(thrustDirections, { aft = math.abs(v) }) end
end
end
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
for _, d in pairs(thrustDirections) do
for _, t in pairs(Thrusters) do
if t.affectVectors.lateral == d then
SetThrusterPower(t, d)
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
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)
for _, thruster in pairs(Thrusters) do
local desiredThrust = 0
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
-- angular thrust
local desiredAngluarThrustVectors = {}
for f, v in pairs(AngularRates) do
desiredAngluarThrustVectors[f] = CustomSigmoid(v)
end
-- Need to complete this function implementation based on docs.md requirements
end
function Init()
@@ -712,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
@@ -732,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)
@@ -813,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))
@@ -827,10 +1183,15 @@ 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()
UpdateGlobalThrust()
-- Update monitor displays
if Config.Monitors.InstrumentPanelMonitor then
displayInstrumentPanel(Config.Monitors.InstrumentPanelMonitor, SensorData)
@@ -840,21 +1201,10 @@ function Main()
displayAutopilotControls(Config.Monitors.AutopilotControlMonitor, SensorData)
end
local unindexedThrusters = checkIfThrusterIsIndexed()
if unindexedThrusters ~= nil then
partiallyUpdateThrusters(unindexedThrusters)
end
PollSensors()
UpdateGlobalThrust()
UpdateStabilization()
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
@@ -866,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
}