gundami

Untitled

Jul 26th, 2026 (edited)
16
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 7.13 KB | None | 0 0
  1. -- ============================================================================
  2. -- DFPWM EGPWS/TAWS Warning System for CC: Sable
  3. -- Featuring Attitude-Aware Logic & External Monitor Auto-Detection
  4. -- ============================================================================
  5.  
  6. if not sublevel then
  7. error("Error: 'sublevel' API missing! Must be placed on a Sable Sub-Level.")
  8. end
  9.  
  10. local dfpwm = require("cc.audio.dfpwm")
  11. local speaker = peripheral.find("speaker")
  12.  
  13. if not speaker then
  14. error("Error: No CC Speaker found attached to the computer!")
  15. end
  16.  
  17. -- ----------------------------------------------------------------------------
  18. -- Monitor Auto-Detection & Redirection
  19. -- ----------------------------------------------------------------------------
  20. local mon = peripheral.find("monitor")
  21. if mon then
  22. -- Scale text to fit. Adjust this value (0.5 to 5) based on your monitor size.
  23. mon.setTextScale(1)
  24. term.redirect(mon)
  25. end
  26.  
  27. -- ----------------------------------------------------------------------------
  28. -- Audio Management & Non-blocking DFPWM Player
  29. -- ----------------------------------------------------------------------------
  30. local currentAlert = nil
  31. local isPlaying = false
  32.  
  33. -- Ensure these files exist in your computer's directory
  34. local SOUND_FILES = {
  35. ["pull_up"] = "sounds/pullup.dfpwm",
  36. ["terrain_pull_up"] = "sounds/terrain.dfpwm",
  37. ["dont_sink"] = "sounds/dontsink.dfpwm",
  38. ["bank_angle"] = "sounds/bankangle.dfpwm"
  39. }
  40.  
  41. local function playDFPWM(filepath)
  42. if not fs.exists(filepath) then return end
  43.  
  44. local decoder = dfpwm.make_decoder()
  45. local file = fs.open(filepath, "rb")
  46. if not file then return end
  47.  
  48. isPlaying = true
  49. local chunkSize = 16 * 1024
  50.  
  51. while true do
  52. local chunk = file.read(chunkSize)
  53. if not chunk then break end
  54.  
  55. local buffer = decoder(chunk)
  56. -- Wait until speaker has capacity for new audio buffer
  57. while not speaker.playAudio(buffer) do
  58. os.pullEvent("speaker_audio_empty")
  59. end
  60. end
  61.  
  62. file.close()
  63. isPlaying = false
  64. end
  65.  
  66. -- Audio Worker Coroutine Loop
  67. local function audioThread()
  68. while true do
  69. if currentAlert and not isPlaying then
  70. local file = SOUND_FILES[currentAlert]
  71. if file then
  72. playDFPWM(file)
  73. sleep(0.15) -- Delay between repeating voice loops
  74. else
  75. sleep(0.1)
  76. end
  77. else
  78. sleep(0.1)
  79. end
  80. end
  81. end
  82.  
  83. -- ----------------------------------------------------------------------------
  84. -- Telemetry & Quaternion Math Helpers
  85. -- ----------------------------------------------------------------------------
  86. local function quaternionToPitchRoll(q)
  87. local x, y, z, w = q.x or q[1] or 0, q.y or q[2] or 0, q.z or q[3] or 0, q.w or q[4] or 1
  88.  
  89. local sinp = 2 * (w * x - y * z)
  90. local pitch = (math.abs(sinp) >= 1) and (math.pi / 2 * (sinp > 0 and 1 or -1)) or math.asin(sinp)
  91.  
  92. local sinr_cosp = 2 * (w * z + x * y)
  93. local cosr_cosp = 1 - 2 * (x * x + z * z)
  94. local roll = math.atan2(sinr_cosp, cosr_cosp)
  95.  
  96. return math.deg(pitch), math.deg(roll)
  97. end
  98.  
  99. local function getTelemetry()
  100. local pos = (sublevel.getPosition and sublevel.getPosition()) or {x=0, y=0, z=0}
  101. local altitude = pos.y or pos[2] or 0
  102.  
  103. local getVel = sublevel.getLinearVelocity or sublevel.getVelocity
  104. local vel = getVel and getVel() or {x=0, y=0, z=0}
  105. local vx, vy, vz = vel.x or vel[1] or 0, vel.y or vel[2] or 0, vel.z or vel[3] or 0
  106. local speed = math.sqrt(vx^2 + vy^2 + vz^2)
  107.  
  108. local getRot = sublevel.getRotation or sublevel.getOrientation or sublevel.getQuaternion
  109. local pitch, roll = 0, 0
  110. if getRot then
  111. local rot = getRot()
  112. if rot.w or rot[4] then
  113. pitch, roll = quaternionToPitchRoll(rot)
  114. else
  115. pitch = rot.pitch or rot.x or rot[1] or 0
  116. roll = rot.roll or rot.z or rot[3] or 0
  117. end
  118. end
  119.  
  120. return {
  121. alt = altitude,
  122. speed = speed,
  123. verticalSpeed = vy, -- Negative = Descending
  124. pitch = pitch, -- Positive = Nose Up, Negative = Nose Down
  125. roll = roll -- Positive/Negative = Left/Right bank
  126. }
  127. end
  128.  
  129. -- ----------------------------------------------------------------------------
  130. -- Main Logic & UI Refresh Loop
  131. -- ----------------------------------------------------------------------------
  132. local function mainThread()
  133. term.clear()
  134. term.setCursorPos(1, 1)
  135. print("========================================")
  136. print(" EGPWS ACTIVE (ATTITUDE AWARE) ")
  137. print("========================================")
  138.  
  139. while true do
  140. local tel = getTelemetry()
  141. local newAlert = nil
  142.  
  143. -- Real-time Flight Data Display
  144. term.setCursorPos(1, 4)
  145. term.clearLine()
  146. print(string.format("ALT: %6.1fm | VS: %6.1fm/s | SPD: %5.1fm/s", tel.alt, tel.verticalSpeed, tel.speed))
  147. term.clearLine()
  148. print(string.format("PITCH: %5.1fdeg | ROLL: %5.1fdeg", tel.pitch, tel.roll))
  149.  
  150. -- ====================================================================
  151. -- ALERT EVALUATION (Evaluated by Priority Order)
  152. -- ====================================================================
  153.  
  154. -- 1. PULL UP (Altitude + Sink Rate + Pitch Angle)
  155. local pullUpThreshold = -12
  156. if tel.pitch < -15 then
  157. pullUpThreshold = -8 -- Nose pitched down: trigger earlier
  158. elseif tel.pitch > 5 then
  159. pullUpThreshold = -18 -- Nose pitched up (recovering): relax threshold
  160. end
  161.  
  162. if tel.alt < 80 and tel.verticalSpeed < pullUpThreshold then
  163. newAlert = "pull_up"
  164.  
  165. -- 2. TERRAIN PULL UP (Extreme sink rate OR low altitude steep dive)
  166. elseif tel.alt < 150 and (tel.verticalSpeed < -20 or (tel.alt < 60 and tel.pitch < -25)) then
  167. newAlert = "terrain_pull_up"
  168.  
  169. -- 3. BANK ANGLE (Dynamic bank limit based on pitch)
  170. -- Default 45 deg limit, tightens to 30 deg if diving
  171. elseif math.abs(tel.roll) > ((tel.pitch < -10) and 30 or 45) then
  172. newAlert = "bank_angle"
  173.  
  174. -- 4. DON'T SINK (Nose is pitched up > 2 deg, but altitude is dropping)
  175. -- Detects low altitude stalls or windshear during takeoff/climb
  176. elseif tel.alt < 100 and tel.pitch > 2 and tel.verticalSpeed < -1.5 and tel.speed > 5 then
  177. newAlert = "dont_sink"
  178. end
  179.  
  180. -- Update global active alert
  181. currentAlert = newAlert
  182.  
  183. -- UI Alert Bar
  184. term.setCursorPos(1, 8)
  185. term.clearLine()
  186. if currentAlert then
  187. term.blit(" WARN: " .. string.format("%-25s", currentAlert:upper()) .. " ", "fffffffffffffffffffffffffffffffff", "e44444444444444444444444444444444")
  188. else
  189. term.write(" STATUS: NORMAL FLIGHT")
  190. end
  191.  
  192. sleep(0.05) -- 20Hz update tick
  193. end
  194. end
  195.  
  196. -- Run UI/Telemetry logic and DFPWM Audio Driver concurrently
  197. parallel.waitForAny(mainThread, audioThread)
Advertisement
Add Comment
Please, Sign In to add comment