From d83053d250095aeb065281b478b6fa5dd72e366d Mon Sep 17 00:00:00 2001 From: Ricardo Borenstein Date: Thu, 10 Oct 2024 10:59:36 +0200 Subject: [PATCH 01/11] Fixes and adding new features --- xa-cabin.lua | 35 ++-- xa-cabin/GUI.lua | 52 +++--- xa-cabin/announcement.lua | 26 +-- xa-cabin/globals.lua | 40 +++-- xa-cabin/helpers.lua | 24 +-- xa-cabin/state.lua | 350 ++++++++++++++++++-------------------- 6 files changed, 271 insertions(+), 256 deletions(-) diff --git a/xa-cabin.lua b/xa-cabin.lua index fc26ae9..6725efe 100644 --- a/xa-cabin.lua +++ b/xa-cabin.lua @@ -1,8 +1,8 @@ LIP = dofile(SCRIPT_DIRECTORY .. "/xa-cabin/LIP.lua") -XA_CABIN_LOGGER = dofile(SCRIPT_DIRECTORY .. "/xa-cabin/logging.lua") +LOGGER = dofile(SCRIPT_DIRECTORY .. "/xa-cabin/logging.lua") dofile(SCRIPT_DIRECTORY .. "/xa-cabin/globals.lua") HELPERS = dofile(SCRIPT_DIRECTORY .. "/xa-cabin/helpers.lua") -local STATE = dofile(SCRIPT_DIRECTORY .. "/xa-cabin/state.lua") +STATE = dofile(SCRIPT_DIRECTORY .. "/xa-cabin/state.lua") local GUI = dofile(SCRIPT_DIRECTORY .. "/xa-cabin/GUI.lua") ANNOUNCEMENTS = dofile(SCRIPT_DIRECTORY .. "/xa-cabin/announcement.lua") @@ -28,7 +28,7 @@ local plane_config_file_path = AIRCRAFT_PATH .. "/xa-cabin.ini" local plane_config_file = io.open(plane_config_file_path, "r") if plane_config_file == nil then - XA_CABIN_LOGGER.write_log("Creating new plane config file") + LOGGER.write_log("Creating new plane config file") LIP.save(plane_config_file_path, XA_CABIN_PLANE_CONFIG) end XA_CABIN_PLANE_CONFIG = LIP.load(AIRCRAFT_PATH .. "/xa-cabin.ini") @@ -40,8 +40,8 @@ if XA_CABIN_PLANE_CONFIG.RWY_LIGHTS ~= nil then XA_CABIN_PLANE_CONFIG = LIP.load(AIRCRAFT_PATH .. "/xa-cabin.ini") end -XA_CABIN_LOGGER.dumpTable(XA_CABIN_PLANE_CONFIG) -XA_CABIN_LOGGER.write_log("Loaded plane config file") +LOGGER.dumpTable(XA_CABIN_PLANE_CONFIG) +LOGGER.write_log("Loaded plane config file") SIMBRIEF = dofile(SCRIPT_DIRECTORY .. "/xa-cabin/simbrief.lua") @@ -144,17 +144,20 @@ footnotes: If changing color using PushStyleColor, here are common color codes: MAGENTA = 0xFFFF00FF; ]] -function xa_cabin_update_state() - local status, err = pcall(STATE.update_flight_state) - if not status then - XA_CABIN_LOGGER.write_log("Error in update flight state: " .. err) - end +-- function update_state() +-- local status, err = pcall(STATE.update_flight_state) +-- if not status then +-- LOGGER.write_log("Error in update flight state: " .. err) +-- end - local status2, err2 = pcall(STATE.update_cabin_state) - if not status2 then - XA_CABIN_LOGGER.write_log("Error in update cabin state: " .. err2) - end -end +-- local status2, err2 = pcall(STATE.update_cabin_state) +-- if not status2 then +-- LOGGER.write_log("Error in update cabin state: " .. err2) +-- end +-- end ANNOUNCEMENTS.loadSounds() -do_often("xa_cabin_update_state()") +-- Register update functions +do_often("STATE.update_flight_state_every_minute()") +do_often("STATE.update_cabin_state_every_minute()") +-- do_often("update_state()") diff --git a/xa-cabin/GUI.lua b/xa-cabin/GUI.lua index 85d553d..52b0633 100644 --- a/xa-cabin/GUI.lua +++ b/xa-cabin/GUI.lua @@ -57,14 +57,14 @@ function GUI.SimbriefInfo(win_width, win_height) imgui.TextUnformatted("Flight State: ") imgui.SameLine() imgui.PushStyleColor(imgui.constant.Col.Text, 0xFF00FF00) - imgui.TextUnformatted(" " .. XA_CABIN_STATES.flight_state.current_state) + imgui.TextUnformatted(" " .. STATE.flight_phase) -- Use the dynamically updated flight phase imgui.PopStyleColor() imgui.Spacing() imgui.TextUnformatted("Cabin State: ") imgui.SameLine() imgui.PushStyleColor(imgui.constant.Col.Text, 0xFF00FF00) - imgui.TextUnformatted(" " .. XA_CABIN_STATES.cabin_state.current_state) + imgui.TextUnformatted(" " .. STATE.cabin_state) -- Use the dynamically updated cabin state imgui.PopStyleColor() imgui.Spacing() end @@ -164,37 +164,45 @@ function GUI.Announcements(win_width, win_height) if imgui.BeginChild("Announcements", win_width - 32, win_height * SECOND_ROW_HEIGHT_PERCENT) then imgui.SetWindowFontScale(1.2) if imgui.BeginTable("XA Cabin", 3) then - for i = 2, #XA_CABIN_CABIN_XA_CABIN_STATES, 3 - do - imgui.Spacing() - imgui.Spacing() - imgui.Spacing() - imgui.Spacing() + local total_announcements = #ANNOUNCEMENT_STATES + for i = 1, total_announcements, 3 do imgui.TableNextRow() + + -- First Column imgui.TableNextColumn() - if imgui.Button(XA_CABIN_CABIN_XA_CABIN_STATES[i], win_width * 0.3 - 16, 50) then -- Bigger than normal sized button - ANNOUNCEMENTS.play_sound(XA_CABIN_CABIN_XA_CABIN_STATES[i]) + local state1 = ANNOUNCEMENT_STATES[i] + if state1 then + local displayName1 = DISPLAY_NAME_TO_STATE[state1] or state1 + if imgui.Button(displayName1, win_width * 0.3 - 16, 50) then + STATE.change_cabin_state(state1) + end end + + -- Second Column imgui.TableNextColumn() - if XA_CABIN_CABIN_XA_CABIN_STATES[i + 1] == nil then - break + local state2 = ANNOUNCEMENT_STATES[i + 1] + if state2 then + local displayName2 = DISPLAY_NAME_TO_STATE[state2] or state2 + if imgui.Button(displayName2, win_width * 0.3 - 16, 50) then + STATE.change_cabin_state(state2) + end end - if imgui.Button(XA_CABIN_CABIN_XA_CABIN_STATES[i + 1], win_width * 0.3 - 16, 50) then -- Bigger than normal sized button - ANNOUNCEMENTS.play_sound(XA_CABIN_CABIN_XA_CABIN_STATES[i + 1]) - end - imgui.TableNextColumn() - if XA_CABIN_CABIN_XA_CABIN_STATES[i + 2] == nil then - break - end - if imgui.Button(XA_CABIN_CABIN_XA_CABIN_STATES[i + 2], win_width * 0.3 - 16, 50) then -- Bigger than normal sized button - ANNOUNCEMENTS.play_sound(XA_CABIN_CABIN_XA_CABIN_STATES[i + 2]) + -- Third Column + imgui.TableNextColumn() + local state3 = ANNOUNCEMENT_STATES[i + 2] + if state3 then + local displayName3 = DISPLAY_NAME_TO_STATE[state3] or state3 + if imgui.Button(displayName3, win_width * 0.3 - 16, 50) then + STATE.change_cabin_state(state3) + end end end + imgui.EndTable() end - imgui.EndTable() end imgui.EndChild() end + return GUI; diff --git a/xa-cabin/announcement.lua b/xa-cabin/announcement.lua index 0e046ed..088d129 100644 --- a/xa-cabin/announcement.lua +++ b/xa-cabin/announcement.lua @@ -4,15 +4,19 @@ local ANNOUNCEMENTS = { } function ANNOUNCEMENTS.play_sound(cabin_state) ANNOUNCEMENTS.stopSounds() - play_sound(ANNOUNCEMENTS.sounds[cabin_state]) - XA_CABIN_LOGGER.write_log("Playing announcement for " .. ANNOUNCEMENTS.files[cabin_state]) + if ANNOUNCEMENTS.sounds[cabin_state] then + play_sound(ANNOUNCEMENTS.sounds[cabin_state]) + XA_CABIN_LOGGER.write_log("Playing announcement for cabin state: " .. cabin_state) + else + XA_CABIN_LOGGER.write_log("Error: No sound loaded for cabin state: " .. tostring(cabin_state)) + end end function ANNOUNCEMENTS.stopSounds() if ANNOUNCEMENTS.sounds then - for i = 2, #XA_CABIN_CABIN_XA_CABIN_STATES do - if ANNOUNCEMENTS.sounds[XA_CABIN_CABIN_XA_CABIN_STATES[i]] then - stop_sound(ANNOUNCEMENTS.sounds[XA_CABIN_CABIN_XA_CABIN_STATES[i]]) + for i = 2, #ANNOUNCEMENT_STATES do + if ANNOUNCEMENTS.sounds[ANNOUNCEMENT_STATES[i]] then + stop_sound(ANNOUNCEMENTS.sounds[ANNOUNCEMENT_STATES[i]]) end end end @@ -29,22 +33,22 @@ end function ANNOUNCEMENTS.loadSounds() ANNOUNCEMENTS.stopSounds() - -- ANNOUNCEMENTS.unloadAllSounds() local language = XA_CABIN_SETTINGS.announcement.language local accent = XA_CABIN_SETTINGS.announcement.accent local speaker = XA_CABIN_SETTINGS.announcement.speaker - XA_CABIN_LOGGER.dumpTable(XA_CABIN_CABIN_XA_CABIN_STATES) - for i = 2, #XA_CABIN_CABIN_XA_CABIN_STATES do + for i = 1, #ANNOUNCEMENT_STATES do + local cabin_state = ANNOUNCEMENT_STATES[i] local wav_file_path = SCRIPT_DIRECTORY .. "xa-cabin/announcements/" .. - XA_CABIN_CABIN_XA_CABIN_STATES[i] .. "/" .. language .. "-" .. accent .. "-" .. speaker .. ".wav" + cabin_state .. "/" .. language .. "-" .. accent .. "-" .. speaker .. ".wav" local tmp = io.open(wav_file_path, "r") if tmp == nil then XA_CABIN_LOGGER.write_log("File not found: " .. wav_file_path) else + tmp:close() local index = load_WAV_file(wav_file_path) - ANNOUNCEMENTS.sounds[XA_CABIN_CABIN_XA_CABIN_STATES[i]] = index - ANNOUNCEMENTS.files[XA_CABIN_CABIN_XA_CABIN_STATES[i]] = wav_file_path + ANNOUNCEMENTS.sounds[cabin_state] = index + ANNOUNCEMENTS.files[cabin_state] = wav_file_path end end XA_CABIN_LOGGER.dumpTable(ANNOUNCEMENTS.sounds) diff --git a/xa-cabin/globals.lua b/xa-cabin/globals.lua index 3e0921a..d8fb88e 100644 --- a/xa-cabin/globals.lua +++ b/xa-cabin/globals.lua @@ -1,16 +1,16 @@ -XA_CABIN_VERSION = "v0.0.1" -XA_CABIN_CABIN_XA_CABIN_STATES = { - "Pre-Boarding", - "Boarding", - "Boarding Complete", - "Safety Demonstration", - "Takeoff", - "Climb", - "Cruise", - "Prepare for Landing", - "Final Approach", - "Post Landing", - "Emergency" +XA_CABIN_VERSION = "v0.1.0" +ANNOUNCEMENT_STATES = { + "pre_boarding", + "boarding", + "boarding_complete", + "safety_demonstration", + "takeoff", + "climb", + "cruise", + "prepare_for_landing", + "final_approach", + "post_landing", + "emergency" } XA_CABIN_SETTINGS = { @@ -74,3 +74,17 @@ XA_CABIN_PLANE_CONFIG = { threshold = 1 }, } + +DISPLAY_NAME_TO_STATE = { + ["Pre-Boarding"] = "pre_boarding", + ["Boarding"] = "boarding", + ["Boarding Complete"] = "boarding_complete", + ["Safety Demonstration"] = "safety_demonstration", + ["Takeoff"] = "takeoff", + ["Climb"] = "climb", + ["Cruise"] = "cruise", + ["Prepare for Landing"] = "prepare_for_landing", + ["Final Approach"] = "final_approach", + ["Post Landing"] = "post_landing", + ["Emergency"] = "emergency" -- Assuming you have this state defined +} \ No newline at end of file diff --git a/xa-cabin/helpers.lua b/xa-cabin/helpers.lua index 66e4b92..21acf43 100644 --- a/xa-cabin/helpers.lua +++ b/xa-cabin/helpers.lua @@ -1,19 +1,21 @@ local HELPERS = {} function HELPERS.is_door_open() + if XA_CABIN_SETTINGS.bypass_door_check then + XA_CABIN_LOGGER.write_log("Bypassing door check") + return true -- Always return true if bypassing the door check + end + + XA_CABIN_LOGGER.write_log("Checking door status") if XA_CABIN_DATAREFS.DOOR == nil then XA_CABIN_DATAREFS.DOOR = dataref_table(XA_CABIN_PLANE_CONFIG.DOOR.dataref_str) - local funcCode = [[ - return function(x, debug) - if debug then - XA_CABIN_LOGGER.write_log('Dataref: ' .. x) - XA_CABIN_LOGGER.write_log('Debug: ' .. tostring(debug)) - end - return x]] .. XA_CABIN_PLANE_CONFIG.DOOR.operator .. XA_CABIN_PLANE_CONFIG.DOOR.threshold .. [[ - end - ]] - XA_CABIN_PLANE_CONFIG.DOOR["Func"] = load(funcCode)() + XA_CABIN_LOGGER.write_log("Door dataref initialized: " .. tostring(XA_CABIN_PLANE_CONFIG.DOOR.dataref_str)) end - return XA_CABIN_PLANE_CONFIG.DOOR["Func"](XA_CABIN_DATAREFS.DOOR[0], false) + + local door_status = XA_CABIN_DATAREFS.DOOR[0] > 0 + XA_CABIN_LOGGER.write_log("Door status value: " .. tostring(XA_CABIN_DATAREFS.DOOR[0])) + XA_CABIN_LOGGER.write_log("Door comparison result: " .. tostring(door_status)) + + return door_status end function HELPERS.is_landing_ligths_on() diff --git a/xa-cabin/state.lua b/xa-cabin/state.lua index 1a7c824..dc14267 100644 --- a/xa-cabin/state.lua +++ b/xa-cabin/state.lua @@ -1,89 +1,146 @@ -local STATE = { +STATE = { climb_counter = 0, cruise_counter = 0, descend_counter = 0, - bording_delay_counter = 0, + boarding_delay_counter = 0, + last_update_time = os.clock(), + last_flight_update_time = os.clock(), + last_cabin_update_time = os.clock(), } +-- Initialize the cabin and flight state variables +STATE.flight_phase = "parked" -- Initial state of the flight +STATE.cabin_state = "pre_boarding" -- Initial state of the cabin + +function STATE.should_update(last_update_time) + local current_time = os.clock() + if current_time - last_update_time >= 10 then -- or 60 for one minute + return true, current_time + else + return false, last_update_time + end +end + +function STATE.update_flight_state_every_minute() + LOGGER.write_log("Updating flight state. Current state: " .. STATE.flight_phase) + local shouldUpdate, newTime = STATE.should_update(STATE.last_flight_update_time) + if shouldUpdate then + STATE.last_flight_update_time = newTime + STATE.update_flight_state() + end +end + +function STATE.update_cabin_state_every_minute() + LOGGER.write_log("Updating cabin state. Current state: " .. STATE.cabin_state) + local shouldUpdate, newTime = STATE.should_update(STATE.last_cabin_update_time) + if shouldUpdate then + STATE.last_cabin_update_time = newTime + sync_cabin_state_with_flight_state(STATE.flight_phase) + end +end + +-- Change flight state with checks to avoid redundant changes +function STATE.change_flight_state(new_state) + if XA_CABIN_STATES.flight_state.current_state == new_state then + LOGGER.write_log("Flight state is already: " .. new_state) + return -- Exit if the flight state is already the new_state + end -function change_flight_state(new_state) if XA_CABIN_STATES.flight_state[new_state] == nil then - logMsg("Invalid flight state: " .. new_state) + LOGGER.write_log("Invalid flight state: " .. new_state) return end + + -- Update flight state + LOGGER.write_log("Changing flight state from: " .. XA_CABIN_STATES.flight_state.current_state .. " to: " .. new_state) XA_CABIN_STATES.flight_state[XA_CABIN_STATES.flight_state.current_state] = false XA_CABIN_STATES.flight_state[new_state] = true XA_CABIN_STATES.flight_state.current_state = new_state - XA_CABIN_LOGGER.write_log("Flight state changed to: " .. new_state) + STATE.flight_phase = new_state -- Update the global flight phase + + -- Sync the cabin state with the new flight state + sync_cabin_state_with_flight_state(new_state) + + -- Log the change + LOGGER.write_log("Flight state changed to: " .. new_state) +end + +-- Sync the cabin state based on the flight state +function sync_cabin_state_with_flight_state(flight_state) + LOGGER.write_log("Syncing cabin state with flight state: " .. flight_state) + if flight_state == "taxi_out" then + STATE.change_cabin_state("safety_demonstration") + elseif flight_state == "takeoff" then + STATE.change_cabin_state("takeoff") + elseif flight_state == "climb" then + STATE.change_cabin_state("climb") + elseif flight_state == "cruise" then + STATE.change_cabin_state("cruise") + elseif flight_state == "descent" then + STATE.change_cabin_state("prepare_for_landing") + elseif flight_state == "approach" then + STATE.change_cabin_state("final_approach") + elseif flight_state == "taxi_in" then + STATE.change_cabin_state("post_landing") + elseif flight_state == "parked" then + STATE.change_cabin_state("pre_boarding") + end end function STATE.update_flight_state() + local KNOTS_TO_MPS = 0.514444 + local TAXI_SPEED_THRESHOLD_KNOTS = 10 -- Knots + local TAXI_SPEED_THRESHOLD = TAXI_SPEED_THRESHOLD_KNOTS * KNOTS_TO_MPS + local APPROACH_SPEED_THRESHOLD_KNOTS = 50 -- Knots + local APPROACH_SPEED_THRESHOLD = APPROACH_SPEED_THRESHOLD_KNOTS * KNOTS_TO_MPS + local TAXI_IN_SPEED_THRESHOLD_KNOTS = 1 -- Knots + local TAXI_IN_SPEED_THRESHOLD = TAXI_IN_SPEED_THRESHOLD_KNOTS * KNOTS_TO_MPS + -- Vertical speed thresholds in feet per minute + local LEVEL_FLIGHT_VS_THRESHOLD = 500 -- Feet per minute -- process PARKED state if XA_CABIN_STATES.flight_state.current_state == "parked" then - if XA_CABIN_DATAREFS.GS == nil then - XA_CABIN_DATAREFS.GS = dataref_table('sim/flightmodel/position/groundspeed') - end - if XA_CABIN_DATAREFS.GEAR_FORCE == nil then - XA_CABIN_DATAREFS.GEAR_FORCE = dataref_table('sim/flightmodel/forces/fnrml_gear') - end - - if XA_CABIN_DATAREFS.GS[0] > 5 / 1.9 and XA_CABIN_DATAREFS.GEAR_FORCE[0] > 1 then - change_flight_state("taxi_out") + if XA_CABIN_DATAREFS.GS[0] > TAXI_SPEED_THRESHOLD and XA_CABIN_DATAREFS.GEAR_FORCE[0] > 1 then + STATE.change_flight_state("taxi_out") end return end -- process TAXI_OUT state if XA_CABIN_STATES.flight_state.current_state == "taxi_out" then - if XA_CABIN_DATAREFS.N1 == nil then - XA_CABIN_DATAREFS.N1 = dataref_table('sim/cockpit2/engine/indicators/N1_percent') - end - if XA_CABIN_DATAREFS.N1[0] > 75 then - change_flight_state("takeoff") + STATE.change_flight_state("takeoff") end return end -- process TAKEOFF state if XA_CABIN_STATES.flight_state.current_state == "takeoff" then - if XA_CABIN_DATAREFS.VS == nil then - XA_CABIN_DATAREFS.VS = dataref_table('sim/flightmodel/position/vh_ind_fpm') - end - if XA_CABIN_DATAREFS.GEAR_FORCE == nil then - XA_CABIN_DATAREFS.GEAR_FORCE = dataref_table('sim/flightmodel/forces/fnrml_gear') - end - if XA_CABIN_DATAREFS.VS[0] > 200 and XA_CABIN_DATAREFS.GEAR_FORCE[0] < 1 then - change_flight_state("climb") + STATE.change_flight_state("climb") end return end -- process CLIMB state if XA_CABIN_STATES.flight_state.current_state == "climb" then - if XA_CABIN_DATAREFS.VS == nil then - XA_CABIN_DATAREFS.VS = dataref_table('sim/flightmodel/position/vh_ind_fpm') - end - - if XA_CABIN_DATAREFS.VS[0] > -500 and XA_CABIN_DATAREFS.VS[0] < 500 then + if XA_CABIN_DATAREFS.VS[0] > -LEVEL_FLIGHT_VS_THRESHOLD and XA_CABIN_DATAREFS.VS[0] < LEVEL_FLIGHT_VS_THRESHOLD then STATE.cruise_counter = STATE.cruise_counter + 1 else STATE.cruise_counter = 0 end - if XA_CABIN_DATAREFS.VS[0] < -500 then + if XA_CABIN_DATAREFS.VS[0] < -LEVEL_FLIGHT_VS_THRESHOLD then STATE.descend_counter = STATE.descend_counter + 1 else STATE.descend_counter = 0 end if STATE.cruise_counter > 15 then - change_flight_state("cruise") + STATE.change_flight_state("cruise") return end if STATE.descend_counter > 15 then - change_flight_state("descent") + STATE.change_flight_state("descent") return end return @@ -91,29 +148,30 @@ function STATE.update_flight_state() -- process CRUISE state if XA_CABIN_STATES.flight_state.current_state == "cruise" then - if XA_CABIN_DATAREFS.VS == nil then - XA_CABIN_DATAREFS.VS = dataref_table('sim/flightmodel/position/vh_ind_fpm') - end - - if XA_CABIN_DATAREFS.VS[0] > 500 then + if XA_CABIN_DATAREFS.VS[0] > LEVEL_FLIGHT_VS_THRESHOLD then + -- Climbing STATE.climb_counter = STATE.climb_counter + 1 - else - STATE.climb_counter = 0 - end - - if XA_CABIN_DATAREFS.VS[0] < -500 then + STATE.cruise_counter = 0 + STATE.descend_counter = 0 + elseif XA_CABIN_DATAREFS.VS[0] < -LEVEL_FLIGHT_VS_THRESHOLD then + -- Descending STATE.descend_counter = STATE.descend_counter + 1 + STATE.climb_counter = 0 + STATE.cruise_counter = 0 else + -- Level flight + STATE.cruise_counter = STATE.cruise_counter + 1 + STATE.climb_counter = 0 STATE.descend_counter = 0 end if STATE.climb_counter > 30 then - change_flight_state("climb") + STATE.change_flight_state("climb") return end if STATE.descend_counter > 30 then - change_flight_state("descent") + STATE.change_flight_state("descent") return end return @@ -121,40 +179,33 @@ function STATE.update_flight_state() -- process DESCENT state if XA_CABIN_STATES.flight_state.current_state == "descent" then - if XA_CABIN_DATAREFS.VS == nil then - XA_CABIN_DATAREFS.VS = dataref_table('sim/flightmodel/position/vh_ind_fpm') - end - if XA_CABIN_DATAREFS.AGL == nil then - XA_CABIN_DATAREFS.AGL = dataref_table('sim/flightmodel/position/y_agl') - end - if XA_CABIN_DATAREFS.GEAR_FORCE == nil then - XA_CABIN_DATAREFS.GEAR_FORCE = dataref_table('sim/flightmodel/forces/fnrml_gear') - end - - if XA_CABIN_DATAREFS.VS[0] > 500 then + if XA_CABIN_DATAREFS.VS[0] > LEVEL_FLIGHT_VS_THRESHOLD then STATE.climb_counter = STATE.climb_counter + 1 else STATE.climb_counter = 0 end - if XA_CABIN_DATAREFS.VS[0] < 500 and XA_CABIN_DATAREFS.VS[0] > -500 then + if XA_CABIN_DATAREFS.VS[0] > -LEVEL_FLIGHT_VS_THRESHOLD and XA_CABIN_DATAREFS.VS[0] < LEVEL_FLIGHT_VS_THRESHOLD then + -- Level flight STATE.cruise_counter = STATE.cruise_counter + 1 + STATE.climb_counter = 0 + STATE.descend_counter = 0 else STATE.cruise_counter = 0 end if STATE.climb_counter > 15 then - change_flight_state("climb") + STATE.change_flight_state("climb") return end if STATE.cruise_counter > 15 then - change_flight_state("cruise") + STATE.change_flight_state("cruise") return end if XA_CABIN_DATAREFS.AGL[0] < 800 and XA_CABIN_DATAREFS.GEAR_FORCE[0] < 5 and XA_CABIN_DATAREFS.VS[0] < -200 then - change_flight_state("approach") + STATE.change_flight_state("approach") return end return @@ -162,150 +213,83 @@ function STATE.update_flight_state() -- process APPROACH state if XA_CABIN_STATES.flight_state.current_state == "approach" then - if XA_CABIN_DATAREFS.GS == nil then - XA_CABIN_DATAREFS.GS = dataref_table('sim/flightmodel/position/groundspeed') - end - if XA_CABIN_DATAREFS.GEAR_FORCE == nil then - XA_CABIN_DATAREFS.GEAR_FORCE = dataref_table('sim/flightmodel/forces/fnrml_gear') - end - - if XA_CABIN_DATAREFS.GS[0] < 50 / 1.9 and XA_CABIN_DATAREFS.GEAR_FORCE[0] > 10 then - change_flight_state("taxi_in") + if XA_CABIN_DATAREFS.GS[0] < APPROACH_SPEED_THRESHOLD and XA_CABIN_DATAREFS.GEAR_FORCE[0] > 10 then + STATE.change_flight_state("taxi_in") end return end -- process TAXI_IN state - if XA_CABIN_STATES.flight_state.current_state == "approach" then - if XA_CABIN_DATAREFS.GS == nil then - XA_CABIN_DATAREFS.GS = dataref_table('sim/flightmodel/position/groundspeed') - end - if XA_CABIN_DATAREFS.N1 == nil then - XA_CABIN_DATAREFS.N1 = dataref_table('sim/cockpit2/engine/indicators/N1_percent') - end - - if XA_CABIN_DATAREFS.GS[0] < 1 / 1.9 and XA_CABIN_DATAREFS.N1[0] > 15 then - change_flight_state("parked") + if XA_CABIN_STATES.flight_state.current_state == "taxi_in" then + if XA_CABIN_DATAREFS.GS[0] < TAXI_IN_SPEED_THRESHOLD and XA_CABIN_DATAREFS.N1[0] > 15 then + STATE.change_flight_state("parked") end return end end -function change_cabin_state(new_state) +-- Change cabin state safely +function STATE.change_cabin_state(new_state) + if new_state == XA_CABIN_STATES.cabin_state.current_state then + return -- Prevent unnecessary state change and potential loop + end + if XA_CABIN_STATES.cabin_state[new_state] == nil then - logMsg("Invalid flight state: " .. new_state) + LOGGER.write_log("Invalid cabin state: " .. new_state) return end + XA_CABIN_STATES.cabin_state[XA_CABIN_STATES.cabin_state.current_state] = false XA_CABIN_STATES.cabin_state[new_state] = true XA_CABIN_STATES.cabin_state.current_state = new_state - if XA_CABIN_SETTINGS.mode.automated then - ANNOUNCEMENTS.play_sound(cabin_state_to_CANBIN_XA_CABIN_STATES(new_state)) - end - XA_CABIN_LOGGER.write_log("Flight state changed to: " .. new_state) -end - -function STATE.update_cabin_state() - if XA_CABIN_STATES.cabin_state.current_state == "pre_boarding" then - if HELPERS.is_door_open() and XA_CABIN_STATES.flight_state.parked then - STATE.bording_delay_counter = STATE.bording_delay_counter + 1 - -- random delay 45-60 - if STATE.bording_delay_counter > math.random(90, 120) then - change_cabin_state("boarding") - end - end - return - end - - if XA_CABIN_STATES.cabin_state.current_state == "boarding" then - if not HELPERS.is_door_open() and not XA_CABIN_STATES.flight_state.taxi_out then - change_cabin_state("boarding_complete") - end - return - end - - if XA_CABIN_STATES.cabin_state.current_state == "boarding_complete" then - if XA_CABIN_STATES.flight_state.taxi_out then - change_cabin_state("safety_demonstration") - end - return - end - - if XA_CABIN_STATES.cabin_state.current_state == "safety_demonstration" then - if HELPERS.is_landing_ligths_on() and XA_CABIN_STATES.flight_state.taxi_out then - change_cabin_state("takeoff") - end - return - end + STATE.cabin_state = new_state -- Ensure this updates the global variable - if XA_CABIN_STATES.cabin_state.current_state == "takeoff" then - if XA_CABIN_DATAREFS.AGL == nil then - XA_CABIN_DATAREFS.AGL = dataref_table('sim/flightmodel/position/y_agl') - end - if XA_CABIN_STATES.flight_state.climb and XA_CABIN_DATAREFS.AGL[0] > 1000 then - change_cabin_state("climb") - end - return - end - - if XA_CABIN_STATES.cabin_state.current_state == "climb" then - if XA_CABIN_STATES.flight_state.cruise then - change_cabin_state("cruise") - end - return - end - - if XA_CABIN_STATES.cabin_state.current_state == "cruise" then - if XA_CABIN_STATES.flight_state.descent then - change_cabin_state("prepare_for_landing") - end - return + if XA_CABIN_SETTINGS.mode.automated then + ANNOUNCEMENTS.play_sound(new_state) + LOGGER.write_log("Playing announcement for cabin state: " .. new_state) end - if XA_CABIN_STATES.cabin_state.current_state == "prepare_for_landing" then - if XA_CABIN_STATES.flight_state.approach then - change_cabin_state("final_approach") - end - return - end + LOGGER.write_log("Cabin state changed to: " .. new_state) +end - if XA_CABIN_STATES.cabin_state.current_state == "final_approach" then - if XA_CABIN_STATES.flight_state.taxi_in then - change_cabin_state("post_landing") - end - return - end +-- Define the mapping table at the beginning of your script +CABIN_STATE_MAPPING = { + ["pre_boarding"] = "mapped_state_1", + ["boarding"] = "mapped_state_2", + ["boarding_complete"] = "mapped_state_3", + ["safety_demonstration"] = "mapped_state_4", + ["takeoff"] = "mapped_state_5", + ["climb"] = "mapped_state_6", + ["cruise"] = "mapped_state_7", + ["prepare_for_landing"] = "mapped_state_8", + ["final_approach"] = "mapped_state_9", + ["post_landing"] = "mapped_state_10", +} - if XA_CABIN_STATES.cabin_state.current_state == "post_landing" then - if XA_CABIN_STATES.flight_state.parked then - change_cabin_state("pre_boarding") - end - return +function cabin_state_to_CABIN_XA_CABIN_STATES(cabin_state) + LOGGER.write_log("Mapping cabin state to CABIN: " .. cabin_state) + local mapped_state = CABIN_STATE_MAPPING[cabin_state] + if mapped_state then + return mapped_state + else + LOGGER.write_log("Invalid cabin state: " .. tostring(cabin_state)) + return nil end end -function cabin_state_to_CANBIN_XA_CABIN_STATES(cabin_state) - if cabin_state == "pre_boarding" then - return XA_CABIN_CABIN_XA_CABIN_STATES[1] - elseif cabin_state == "boarding" then - return XA_CABIN_CABIN_XA_CABIN_STATES[2] - elseif cabin_state == "boarding_complete" then - return XA_CABIN_CABIN_XA_CABIN_STATES[3] - elseif cabin_state == "safety_demonstration" then - return XA_CABIN_CABIN_XA_CABIN_STATES[4] - elseif cabin_state == "takeoff" then - return XA_CABIN_CABIN_XA_CABIN_STATES[5] - elseif cabin_state == "climb" then - return XA_CABIN_CABIN_XA_CABIN_STATES[6] - elseif cabin_state == "cruise" then - return XA_CABIN_CABIN_XA_CABIN_STATES[7] - elseif cabin_state == "prepare_for_landing" then - return XA_CABIN_CABIN_XA_CABIN_STATES[8] - elseif cabin_state == "final_approach" then - return XA_CABIN_CABIN_XA_CABIN_STATES[9] - elseif cabin_state == "post_landing" then - return XA_CABIN_CABIN_XA_CABIN_STATES[10] - end +function STATE.initialize_datarefs() + XA_CABIN_DATAREFS.GS = dataref_table('sim/flightmodel/position/groundspeed') + XA_CABIN_DATAREFS.GEAR_FORCE = dataref_table('sim/flightmodel/forces/fnrml_gear') + XA_CABIN_DATAREFS.N1 = dataref_table('sim/cockpit2/engine/indicators/N1_percent') + XA_CABIN_DATAREFS.VS = dataref_table('sim/flightmodel/position/vh_ind_fpm') + XA_CABIN_DATAREFS.AGL = dataref_table('sim/flightmodel/position/y_agl') + -- Initialize other required datarefs here end -return STATE +-- Call this function at the start of your script +STATE.initialize_datarefs() +-- Register update functions +do_every_frame("STATE.update_flight_state_every_minute()") +do_every_frame("STATE.update_cabin_state_every_minute()") + +return STATE \ No newline at end of file From 87e5c13ebfeae2533756e880ca5699d6bfd2e31c Mon Sep 17 00:00:00 2001 From: Ricardo Borenstein Date: Thu, 10 Oct 2024 13:54:36 +0200 Subject: [PATCH 02/11] using code from master and adding dynamically identify if starts from runway. Buttons are updating cabin state as well. --- xa-cabin.lua | 35 ++- xa-cabin/GUI.lua | 55 +++-- xa-cabin/announcement.lua | 34 ++- xa-cabin/globals.lua | 40 ++-- xa-cabin/helpers.lua | 24 +-- xa-cabin/state.lua | 439 ++++++++++++++++++++++++-------------- 6 files changed, 361 insertions(+), 266 deletions(-) diff --git a/xa-cabin.lua b/xa-cabin.lua index 6725efe..fc26ae9 100644 --- a/xa-cabin.lua +++ b/xa-cabin.lua @@ -1,8 +1,8 @@ LIP = dofile(SCRIPT_DIRECTORY .. "/xa-cabin/LIP.lua") -LOGGER = dofile(SCRIPT_DIRECTORY .. "/xa-cabin/logging.lua") +XA_CABIN_LOGGER = dofile(SCRIPT_DIRECTORY .. "/xa-cabin/logging.lua") dofile(SCRIPT_DIRECTORY .. "/xa-cabin/globals.lua") HELPERS = dofile(SCRIPT_DIRECTORY .. "/xa-cabin/helpers.lua") -STATE = dofile(SCRIPT_DIRECTORY .. "/xa-cabin/state.lua") +local STATE = dofile(SCRIPT_DIRECTORY .. "/xa-cabin/state.lua") local GUI = dofile(SCRIPT_DIRECTORY .. "/xa-cabin/GUI.lua") ANNOUNCEMENTS = dofile(SCRIPT_DIRECTORY .. "/xa-cabin/announcement.lua") @@ -28,7 +28,7 @@ local plane_config_file_path = AIRCRAFT_PATH .. "/xa-cabin.ini" local plane_config_file = io.open(plane_config_file_path, "r") if plane_config_file == nil then - LOGGER.write_log("Creating new plane config file") + XA_CABIN_LOGGER.write_log("Creating new plane config file") LIP.save(plane_config_file_path, XA_CABIN_PLANE_CONFIG) end XA_CABIN_PLANE_CONFIG = LIP.load(AIRCRAFT_PATH .. "/xa-cabin.ini") @@ -40,8 +40,8 @@ if XA_CABIN_PLANE_CONFIG.RWY_LIGHTS ~= nil then XA_CABIN_PLANE_CONFIG = LIP.load(AIRCRAFT_PATH .. "/xa-cabin.ini") end -LOGGER.dumpTable(XA_CABIN_PLANE_CONFIG) -LOGGER.write_log("Loaded plane config file") +XA_CABIN_LOGGER.dumpTable(XA_CABIN_PLANE_CONFIG) +XA_CABIN_LOGGER.write_log("Loaded plane config file") SIMBRIEF = dofile(SCRIPT_DIRECTORY .. "/xa-cabin/simbrief.lua") @@ -144,20 +144,17 @@ footnotes: If changing color using PushStyleColor, here are common color codes: MAGENTA = 0xFFFF00FF; ]] --- function update_state() --- local status, err = pcall(STATE.update_flight_state) --- if not status then --- LOGGER.write_log("Error in update flight state: " .. err) --- end +function xa_cabin_update_state() + local status, err = pcall(STATE.update_flight_state) + if not status then + XA_CABIN_LOGGER.write_log("Error in update flight state: " .. err) + end --- local status2, err2 = pcall(STATE.update_cabin_state) --- if not status2 then --- LOGGER.write_log("Error in update cabin state: " .. err2) --- end --- end + local status2, err2 = pcall(STATE.update_cabin_state) + if not status2 then + XA_CABIN_LOGGER.write_log("Error in update cabin state: " .. err2) + end +end ANNOUNCEMENTS.loadSounds() --- Register update functions -do_often("STATE.update_flight_state_every_minute()") -do_often("STATE.update_cabin_state_every_minute()") --- do_often("update_state()") +do_often("xa_cabin_update_state()") diff --git a/xa-cabin/GUI.lua b/xa-cabin/GUI.lua index 52b0633..77cd1ac 100644 --- a/xa-cabin/GUI.lua +++ b/xa-cabin/GUI.lua @@ -57,14 +57,14 @@ function GUI.SimbriefInfo(win_width, win_height) imgui.TextUnformatted("Flight State: ") imgui.SameLine() imgui.PushStyleColor(imgui.constant.Col.Text, 0xFF00FF00) - imgui.TextUnformatted(" " .. STATE.flight_phase) -- Use the dynamically updated flight phase + imgui.TextUnformatted(" " .. XA_CABIN_STATES.flight_state.current_state) imgui.PopStyleColor() imgui.Spacing() imgui.TextUnformatted("Cabin State: ") imgui.SameLine() imgui.PushStyleColor(imgui.constant.Col.Text, 0xFF00FF00) - imgui.TextUnformatted(" " .. STATE.cabin_state) -- Use the dynamically updated cabin state + imgui.TextUnformatted(" " .. XA_CABIN_STATES.cabin_state.current_state) imgui.PopStyleColor() imgui.Spacing() end @@ -164,45 +164,42 @@ function GUI.Announcements(win_width, win_height) if imgui.BeginChild("Announcements", win_width - 32, win_height * SECOND_ROW_HEIGHT_PERCENT) then imgui.SetWindowFontScale(1.2) if imgui.BeginTable("XA Cabin", 3) then - local total_announcements = #ANNOUNCEMENT_STATES - for i = 1, total_announcements, 3 do + for i = 2, #XA_CABIN_ANNOUNCEMENT_STATES, 3 + do + imgui.Spacing() + imgui.Spacing() + imgui.Spacing() + imgui.Spacing() imgui.TableNextRow() - - -- First Column imgui.TableNextColumn() - local state1 = ANNOUNCEMENT_STATES[i] - if state1 then - local displayName1 = DISPLAY_NAME_TO_STATE[state1] or state1 - if imgui.Button(displayName1, win_width * 0.3 - 16, 50) then - STATE.change_cabin_state(state1) + if imgui.Button(XA_CABIN_ANNOUNCEMENT_STATES[i], win_width * 0.3 - 16, 50) then + local cabin_state = announcement_name_to_cabin_state(XA_CABIN_ANNOUNCEMENT_STATES[i]) + if cabin_state then + change_cabin_state(cabin_state) + else + XA_CABIN_LOGGER.write_log("Failed to find cabin state for announcement: " .. XA_CABIN_ANNOUNCEMENT_STATES[i]) end end - - -- Second Column imgui.TableNextColumn() - local state2 = ANNOUNCEMENT_STATES[i + 1] - if state2 then - local displayName2 = DISPLAY_NAME_TO_STATE[state2] or state2 - if imgui.Button(displayName2, win_width * 0.3 - 16, 50) then - STATE.change_cabin_state(state2) - end + if XA_CABIN_ANNOUNCEMENT_STATES[i + 1] == nil then + break + end + if imgui.Button(XA_CABIN_ANNOUNCEMENT_STATES[i + 1], win_width * 0.3 - 16, 50) then -- Bigger than normal sized button + ANNOUNCEMENTS.play_sound(XA_CABIN_ANNOUNCEMENT_STATES[i + 1]) end - - -- Third Column imgui.TableNextColumn() - local state3 = ANNOUNCEMENT_STATES[i + 2] - if state3 then - local displayName3 = DISPLAY_NAME_TO_STATE[state3] or state3 - if imgui.Button(displayName3, win_width * 0.3 - 16, 50) then - STATE.change_cabin_state(state3) - end + + if XA_CABIN_ANNOUNCEMENT_STATES[i + 2] == nil then + break + end + if imgui.Button(XA_CABIN_ANNOUNCEMENT_STATES[i + 2], win_width * 0.3 - 16, 50) then -- Bigger than normal sized button + ANNOUNCEMENTS.play_sound(XA_CABIN_ANNOUNCEMENT_STATES[i + 2]) end end - imgui.EndTable() end + imgui.EndTable() end imgui.EndChild() end - return GUI; diff --git a/xa-cabin/announcement.lua b/xa-cabin/announcement.lua index 088d129..d70692d 100644 --- a/xa-cabin/announcement.lua +++ b/xa-cabin/announcement.lua @@ -2,28 +2,24 @@ local ANNOUNCEMENTS = { sounds = {}, files = {} } -function ANNOUNCEMENTS.play_sound(cabin_state) +function ANNOUNCEMENTS.play_sound(announcement_name) ANNOUNCEMENTS.stopSounds() - if ANNOUNCEMENTS.sounds[cabin_state] then - play_sound(ANNOUNCEMENTS.sounds[cabin_state]) - XA_CABIN_LOGGER.write_log("Playing announcement for cabin state: " .. cabin_state) - else - XA_CABIN_LOGGER.write_log("Error: No sound loaded for cabin state: " .. tostring(cabin_state)) - end + play_sound(ANNOUNCEMENTS.sounds[announcement_name]) + XA_CABIN_LOGGER.write_log("Playing announcement for " .. ANNOUNCEMENTS.files[announcement_name]) end function ANNOUNCEMENTS.stopSounds() if ANNOUNCEMENTS.sounds then - for i = 2, #ANNOUNCEMENT_STATES do - if ANNOUNCEMENTS.sounds[ANNOUNCEMENT_STATES[i]] then - stop_sound(ANNOUNCEMENTS.sounds[ANNOUNCEMENT_STATES[i]]) + for i = 2, #XA_CABIN_ANNOUNCEMENT_STATES do + if ANNOUNCEMENTS.sounds[XA_CABIN_ANNOUNCEMENT_STATES[i]] then + stop_sound(ANNOUNCEMENTS.sounds[XA_CABIN_ANNOUNCEMENT_STATES[i]]) end end end end function ANNOUNCEMENTS.unloadAllSounds() - for index, not_used in paris(ANNOUNCEMENTS.sounds) do + for index, not_used in pairs(ANNOUNCEMENTS.sounds) do if ANNOUNCEMENTS.sounds[index] then ANNOUNCEMENTS.sounds[index] = false end @@ -33,22 +29,24 @@ end function ANNOUNCEMENTS.loadSounds() ANNOUNCEMENTS.stopSounds() + -- ANNOUNCEMENTS.unloadAllSounds() local language = XA_CABIN_SETTINGS.announcement.language local accent = XA_CABIN_SETTINGS.announcement.accent local speaker = XA_CABIN_SETTINGS.announcement.speaker - for i = 1, #ANNOUNCEMENT_STATES do - local cabin_state = ANNOUNCEMENT_STATES[i] + XA_CABIN_LOGGER.dumpTable(XA_CABIN_ANNOUNCEMENT_STATES) + for i = 2, #XA_CABIN_ANNOUNCEMENT_STATES do + local announcement = XA_CABIN_ANNOUNCEMENT_STATES[i] local wav_file_path = SCRIPT_DIRECTORY .. - "xa-cabin/announcements/" .. - cabin_state .. "/" .. language .. "-" .. accent .. "-" .. speaker .. ".wav" + "/xa-cabin/announcements/" .. + announcement .. "/" .. language .. "-" .. accent .. "-" .. speaker .. ".wav" local tmp = io.open(wav_file_path, "r") if tmp == nil then XA_CABIN_LOGGER.write_log("File not found: " .. wav_file_path) else - tmp:close() + tmp:close() -- Don't forget to close the file local index = load_WAV_file(wav_file_path) - ANNOUNCEMENTS.sounds[cabin_state] = index - ANNOUNCEMENTS.files[cabin_state] = wav_file_path + ANNOUNCEMENTS.sounds[announcement] = index + ANNOUNCEMENTS.files[announcement] = wav_file_path end end XA_CABIN_LOGGER.dumpTable(ANNOUNCEMENTS.sounds) diff --git a/xa-cabin/globals.lua b/xa-cabin/globals.lua index d8fb88e..6629b4d 100644 --- a/xa-cabin/globals.lua +++ b/xa-cabin/globals.lua @@ -1,16 +1,16 @@ -XA_CABIN_VERSION = "v0.1.0" -ANNOUNCEMENT_STATES = { - "pre_boarding", - "boarding", - "boarding_complete", - "safety_demonstration", - "takeoff", - "climb", - "cruise", - "prepare_for_landing", - "final_approach", - "post_landing", - "emergency" +XA_CABIN_VERSION = "v0.0.1" +XA_CABIN_ANNOUNCEMENT_STATES = { + "Pre-Boarding", + "Boarding", + "Boarding Complete", + "Safety Demonstration", + "Takeoff", + "Climb", + "Cruise", + "Prepare for Landing", + "Final Approach", + "Post Landing", + "Emergency" } XA_CABIN_SETTINGS = { @@ -74,17 +74,3 @@ XA_CABIN_PLANE_CONFIG = { threshold = 1 }, } - -DISPLAY_NAME_TO_STATE = { - ["Pre-Boarding"] = "pre_boarding", - ["Boarding"] = "boarding", - ["Boarding Complete"] = "boarding_complete", - ["Safety Demonstration"] = "safety_demonstration", - ["Takeoff"] = "takeoff", - ["Climb"] = "climb", - ["Cruise"] = "cruise", - ["Prepare for Landing"] = "prepare_for_landing", - ["Final Approach"] = "final_approach", - ["Post Landing"] = "post_landing", - ["Emergency"] = "emergency" -- Assuming you have this state defined -} \ No newline at end of file diff --git a/xa-cabin/helpers.lua b/xa-cabin/helpers.lua index 21acf43..66e4b92 100644 --- a/xa-cabin/helpers.lua +++ b/xa-cabin/helpers.lua @@ -1,21 +1,19 @@ local HELPERS = {} function HELPERS.is_door_open() - if XA_CABIN_SETTINGS.bypass_door_check then - XA_CABIN_LOGGER.write_log("Bypassing door check") - return true -- Always return true if bypassing the door check - end - - XA_CABIN_LOGGER.write_log("Checking door status") if XA_CABIN_DATAREFS.DOOR == nil then XA_CABIN_DATAREFS.DOOR = dataref_table(XA_CABIN_PLANE_CONFIG.DOOR.dataref_str) - XA_CABIN_LOGGER.write_log("Door dataref initialized: " .. tostring(XA_CABIN_PLANE_CONFIG.DOOR.dataref_str)) + local funcCode = [[ + return function(x, debug) + if debug then + XA_CABIN_LOGGER.write_log('Dataref: ' .. x) + XA_CABIN_LOGGER.write_log('Debug: ' .. tostring(debug)) + end + return x]] .. XA_CABIN_PLANE_CONFIG.DOOR.operator .. XA_CABIN_PLANE_CONFIG.DOOR.threshold .. [[ + end + ]] + XA_CABIN_PLANE_CONFIG.DOOR["Func"] = load(funcCode)() end - - local door_status = XA_CABIN_DATAREFS.DOOR[0] > 0 - XA_CABIN_LOGGER.write_log("Door status value: " .. tostring(XA_CABIN_DATAREFS.DOOR[0])) - XA_CABIN_LOGGER.write_log("Door comparison result: " .. tostring(door_status)) - - return door_status + return XA_CABIN_PLANE_CONFIG.DOOR["Func"](XA_CABIN_DATAREFS.DOOR[0], false) end function HELPERS.is_landing_ligths_on() diff --git a/xa-cabin/state.lua b/xa-cabin/state.lua index dc14267..fea80e5 100644 --- a/xa-cabin/state.lua +++ b/xa-cabin/state.lua @@ -1,146 +1,145 @@ -STATE = { +local STATE = { climb_counter = 0, cruise_counter = 0, descend_counter = 0, - boarding_delay_counter = 0, - last_update_time = os.clock(), - last_flight_update_time = os.clock(), - last_cabin_update_time = os.clock(), + bording_delay_counter = 0, } --- Initialize the cabin and flight state variables -STATE.flight_phase = "parked" -- Initial state of the flight -STATE.cabin_state = "pre_boarding" -- Initial state of the cabin - -function STATE.should_update(last_update_time) - local current_time = os.clock() - if current_time - last_update_time >= 10 then -- or 60 for one minute - return true, current_time - else - return false, last_update_time - end -end -function STATE.update_flight_state_every_minute() - LOGGER.write_log("Updating flight state. Current state: " .. STATE.flight_phase) - local shouldUpdate, newTime = STATE.should_update(STATE.last_flight_update_time) - if shouldUpdate then - STATE.last_flight_update_time = newTime - STATE.update_flight_state() +function STATE.initialize_states() + if XA_CABIN_DATAREFS.GS == nil then + XA_CABIN_DATAREFS.GS = dataref_table('sim/flightmodel/position/groundspeed') end -end - -function STATE.update_cabin_state_every_minute() - LOGGER.write_log("Updating cabin state. Current state: " .. STATE.cabin_state) - local shouldUpdate, newTime = STATE.should_update(STATE.last_cabin_update_time) - if shouldUpdate then - STATE.last_cabin_update_time = newTime - sync_cabin_state_with_flight_state(STATE.flight_phase) + if XA_CABIN_DATAREFS.ALT_AGL == nil then + XA_CABIN_DATAREFS.ALT_AGL = dataref_table('sim/flightmodel/position/y_agl') + end + if XA_CABIN_DATAREFS.ONGROUND == nil then + XA_CABIN_DATAREFS.ONGROUND = dataref_table('sim/flightmodel/failures/onground_any') end -end --- Change flight state with checks to avoid redundant changes -function STATE.change_flight_state(new_state) - if XA_CABIN_STATES.flight_state.current_state == new_state then - LOGGER.write_log("Flight state is already: " .. new_state) - return -- Exit if the flight state is already the new_state + local on_ground = XA_CABIN_DATAREFS.ONGROUND[0] == 1 + local gs = XA_CABIN_DATAREFS.GS[0] + local alt_agl = XA_CABIN_DATAREFS.ALT_AGL[0] + + -- Check if the aircraft is on the runway (e.g., ground speed zero, on ground, at runway heading) + if on_ground and gs < 1 and alt_agl < 10 then + -- Aircraft is on the ground, but we need to determine if it's at the gate or runway + -- For simplicity, let's assume that if the door is closed, we're on the runway ready for takeoff + if not HELPERS.is_door_open() then + -- Set flight state to 'takeoff' + XA_CABIN_STATES.flight_state = { + parked = false, + taxi_out = false, + takeoff = true, + climb = false, + cruise = false, + descent = false, + approach = false, + taxi_in = false, + current_state = "takeoff" + } + XA_CABIN_LOGGER.write_log("Flight started from the runway. Setting flight state to 'takeoff'.") + + -- Set cabin state to 'takeoff' or the appropriate state + XA_CABIN_STATES.cabin_state = { + pre_boarding = false, + boarding = false, + boarding_complete = false, + safety_demonstration = false, + takeoff = true, + climb = false, + cruise = false, + prepare_for_landing = false, + final_approach = false, + post_landing = false, + current_state = "takeoff" + } + XA_CABIN_LOGGER.write_log("Cabin state set to 'takeoff' due to runway start.") + else + -- If door is open, assume we're at the gate + XA_CABIN_LOGGER.write_log("Door is open. Assuming aircraft is parked at the gate.") + end end +end +function change_flight_state(new_state) if XA_CABIN_STATES.flight_state[new_state] == nil then - LOGGER.write_log("Invalid flight state: " .. new_state) + logMsg("Invalid flight state: " .. new_state) return end - - -- Update flight state - LOGGER.write_log("Changing flight state from: " .. XA_CABIN_STATES.flight_state.current_state .. " to: " .. new_state) XA_CABIN_STATES.flight_state[XA_CABIN_STATES.flight_state.current_state] = false XA_CABIN_STATES.flight_state[new_state] = true XA_CABIN_STATES.flight_state.current_state = new_state - STATE.flight_phase = new_state -- Update the global flight phase - - -- Sync the cabin state with the new flight state - sync_cabin_state_with_flight_state(new_state) - - -- Log the change - LOGGER.write_log("Flight state changed to: " .. new_state) -end - --- Sync the cabin state based on the flight state -function sync_cabin_state_with_flight_state(flight_state) - LOGGER.write_log("Syncing cabin state with flight state: " .. flight_state) - if flight_state == "taxi_out" then - STATE.change_cabin_state("safety_demonstration") - elseif flight_state == "takeoff" then - STATE.change_cabin_state("takeoff") - elseif flight_state == "climb" then - STATE.change_cabin_state("climb") - elseif flight_state == "cruise" then - STATE.change_cabin_state("cruise") - elseif flight_state == "descent" then - STATE.change_cabin_state("prepare_for_landing") - elseif flight_state == "approach" then - STATE.change_cabin_state("final_approach") - elseif flight_state == "taxi_in" then - STATE.change_cabin_state("post_landing") - elseif flight_state == "parked" then - STATE.change_cabin_state("pre_boarding") - end + XA_CABIN_LOGGER.write_log("Flight state changed to: " .. new_state) end function STATE.update_flight_state() - local KNOTS_TO_MPS = 0.514444 - local TAXI_SPEED_THRESHOLD_KNOTS = 10 -- Knots - local TAXI_SPEED_THRESHOLD = TAXI_SPEED_THRESHOLD_KNOTS * KNOTS_TO_MPS - local APPROACH_SPEED_THRESHOLD_KNOTS = 50 -- Knots - local APPROACH_SPEED_THRESHOLD = APPROACH_SPEED_THRESHOLD_KNOTS * KNOTS_TO_MPS - local TAXI_IN_SPEED_THRESHOLD_KNOTS = 1 -- Knots - local TAXI_IN_SPEED_THRESHOLD = TAXI_IN_SPEED_THRESHOLD_KNOTS * KNOTS_TO_MPS - -- Vertical speed thresholds in feet per minute - local LEVEL_FLIGHT_VS_THRESHOLD = 500 -- Feet per minute -- process PARKED state if XA_CABIN_STATES.flight_state.current_state == "parked" then - if XA_CABIN_DATAREFS.GS[0] > TAXI_SPEED_THRESHOLD and XA_CABIN_DATAREFS.GEAR_FORCE[0] > 1 then - STATE.change_flight_state("taxi_out") + if XA_CABIN_DATAREFS.GS == nil then + XA_CABIN_DATAREFS.GS = dataref_table('sim/flightmodel/position/groundspeed') + end + if XA_CABIN_DATAREFS.GEAR_FORCE == nil then + XA_CABIN_DATAREFS.GEAR_FORCE = dataref_table('sim/flightmodel/forces/fnrml_gear') + end + + if XA_CABIN_DATAREFS.GS[0] > 5 / 1.9 and XA_CABIN_DATAREFS.GEAR_FORCE[0] > 1 then + change_flight_state("taxi_out") end return end -- process TAXI_OUT state if XA_CABIN_STATES.flight_state.current_state == "taxi_out" then + if XA_CABIN_DATAREFS.N1 == nil then + XA_CABIN_DATAREFS.N1 = dataref_table('sim/cockpit2/engine/indicators/N1_percent') + end + if XA_CABIN_DATAREFS.N1[0] > 75 then - STATE.change_flight_state("takeoff") + change_flight_state("takeoff") end return end -- process TAKEOFF state if XA_CABIN_STATES.flight_state.current_state == "takeoff" then + if XA_CABIN_DATAREFS.VS == nil then + XA_CABIN_DATAREFS.VS = dataref_table('sim/flightmodel/position/vh_ind_fpm') + end + if XA_CABIN_DATAREFS.GEAR_FORCE == nil then + XA_CABIN_DATAREFS.GEAR_FORCE = dataref_table('sim/flightmodel/forces/fnrml_gear') + end + if XA_CABIN_DATAREFS.VS[0] > 200 and XA_CABIN_DATAREFS.GEAR_FORCE[0] < 1 then - STATE.change_flight_state("climb") + change_flight_state("climb") end return end -- process CLIMB state if XA_CABIN_STATES.flight_state.current_state == "climb" then - if XA_CABIN_DATAREFS.VS[0] > -LEVEL_FLIGHT_VS_THRESHOLD and XA_CABIN_DATAREFS.VS[0] < LEVEL_FLIGHT_VS_THRESHOLD then + if XA_CABIN_DATAREFS.VS == nil then + XA_CABIN_DATAREFS.VS = dataref_table('sim/flightmodel/position/vh_ind_fpm') + end + + if XA_CABIN_DATAREFS.VS[0] > -500 and XA_CABIN_DATAREFS.VS[0] < 500 then STATE.cruise_counter = STATE.cruise_counter + 1 else STATE.cruise_counter = 0 end - if XA_CABIN_DATAREFS.VS[0] < -LEVEL_FLIGHT_VS_THRESHOLD then + if XA_CABIN_DATAREFS.VS[0] < -500 then STATE.descend_counter = STATE.descend_counter + 1 else STATE.descend_counter = 0 end if STATE.cruise_counter > 15 then - STATE.change_flight_state("cruise") + change_flight_state("cruise") return end if STATE.descend_counter > 15 then - STATE.change_flight_state("descent") + change_flight_state("descent") return end return @@ -148,30 +147,29 @@ function STATE.update_flight_state() -- process CRUISE state if XA_CABIN_STATES.flight_state.current_state == "cruise" then - if XA_CABIN_DATAREFS.VS[0] > LEVEL_FLIGHT_VS_THRESHOLD then - -- Climbing + if XA_CABIN_DATAREFS.VS == nil then + XA_CABIN_DATAREFS.VS = dataref_table('sim/flightmodel/position/vh_ind_fpm') + end + + if XA_CABIN_DATAREFS.VS[0] > 500 then STATE.climb_counter = STATE.climb_counter + 1 - STATE.cruise_counter = 0 - STATE.descend_counter = 0 - elseif XA_CABIN_DATAREFS.VS[0] < -LEVEL_FLIGHT_VS_THRESHOLD then - -- Descending - STATE.descend_counter = STATE.descend_counter + 1 - STATE.climb_counter = 0 - STATE.cruise_counter = 0 else - -- Level flight - STATE.cruise_counter = STATE.cruise_counter + 1 STATE.climb_counter = 0 + end + + if XA_CABIN_DATAREFS.VS[0] < -500 then + STATE.descend_counter = STATE.descend_counter + 1 + else STATE.descend_counter = 0 end if STATE.climb_counter > 30 then - STATE.change_flight_state("climb") + change_flight_state("climb") return end if STATE.descend_counter > 30 then - STATE.change_flight_state("descent") + change_flight_state("descent") return end return @@ -179,33 +177,40 @@ function STATE.update_flight_state() -- process DESCENT state if XA_CABIN_STATES.flight_state.current_state == "descent" then - if XA_CABIN_DATAREFS.VS[0] > LEVEL_FLIGHT_VS_THRESHOLD then + if XA_CABIN_DATAREFS.VS == nil then + XA_CABIN_DATAREFS.VS = dataref_table('sim/flightmodel/position/vh_ind_fpm') + end + if XA_CABIN_DATAREFS.AGL == nil then + XA_CABIN_DATAREFS.AGL = dataref_table('sim/flightmodel/position/y_agl') + end + if XA_CABIN_DATAREFS.GEAR_FORCE == nil then + XA_CABIN_DATAREFS.GEAR_FORCE = dataref_table('sim/flightmodel/forces/fnrml_gear') + end + + if XA_CABIN_DATAREFS.VS[0] > 500 then STATE.climb_counter = STATE.climb_counter + 1 else STATE.climb_counter = 0 end - if XA_CABIN_DATAREFS.VS[0] > -LEVEL_FLIGHT_VS_THRESHOLD and XA_CABIN_DATAREFS.VS[0] < LEVEL_FLIGHT_VS_THRESHOLD then - -- Level flight + if XA_CABIN_DATAREFS.VS[0] < 500 and XA_CABIN_DATAREFS.VS[0] > -500 then STATE.cruise_counter = STATE.cruise_counter + 1 - STATE.climb_counter = 0 - STATE.descend_counter = 0 else STATE.cruise_counter = 0 end if STATE.climb_counter > 15 then - STATE.change_flight_state("climb") + change_flight_state("climb") return end if STATE.cruise_counter > 15 then - STATE.change_flight_state("cruise") + change_flight_state("cruise") return end if XA_CABIN_DATAREFS.AGL[0] < 800 and XA_CABIN_DATAREFS.GEAR_FORCE[0] < 5 and XA_CABIN_DATAREFS.VS[0] < -200 then - STATE.change_flight_state("approach") + change_flight_state("approach") return end return @@ -213,83 +218,197 @@ function STATE.update_flight_state() -- process APPROACH state if XA_CABIN_STATES.flight_state.current_state == "approach" then - if XA_CABIN_DATAREFS.GS[0] < APPROACH_SPEED_THRESHOLD and XA_CABIN_DATAREFS.GEAR_FORCE[0] > 10 then - STATE.change_flight_state("taxi_in") + if XA_CABIN_DATAREFS.GS == nil then + XA_CABIN_DATAREFS.GS = dataref_table('sim/flightmodel/position/groundspeed') + end + if XA_CABIN_DATAREFS.GEAR_FORCE == nil then + XA_CABIN_DATAREFS.GEAR_FORCE = dataref_table('sim/flightmodel/forces/fnrml_gear') + end + + if XA_CABIN_DATAREFS.GS[0] < 50 / 1.9 and XA_CABIN_DATAREFS.GEAR_FORCE[0] > 10 then + change_flight_state("taxi_in") end return end -- process TAXI_IN state if XA_CABIN_STATES.flight_state.current_state == "taxi_in" then - if XA_CABIN_DATAREFS.GS[0] < TAXI_IN_SPEED_THRESHOLD and XA_CABIN_DATAREFS.N1[0] > 15 then - STATE.change_flight_state("parked") + if XA_CABIN_DATAREFS.GS == nil then + XA_CABIN_DATAREFS.GS = dataref_table('sim/flightmodel/position/groundspeed') + end + if XA_CABIN_DATAREFS.N1 == nil then + XA_CABIN_DATAREFS.N1 = dataref_table('sim/cockpit2/engine/indicators/N1_percent') + end + + if XA_CABIN_DATAREFS.GS[0] < 1 / 1.9 and XA_CABIN_DATAREFS.N1[0] > 15 then + change_flight_state("parked") end return end end --- Change cabin state safely -function STATE.change_cabin_state(new_state) - if new_state == XA_CABIN_STATES.cabin_state.current_state then - return -- Prevent unnecessary state change and potential loop - end - +function change_cabin_state(new_state) if XA_CABIN_STATES.cabin_state[new_state] == nil then - LOGGER.write_log("Invalid cabin state: " .. new_state) + logMsg("Invalid Cabin state: " .. new_state) return end - XA_CABIN_STATES.cabin_state[XA_CABIN_STATES.cabin_state.current_state] = false XA_CABIN_STATES.cabin_state[new_state] = true XA_CABIN_STATES.cabin_state.current_state = new_state - STATE.cabin_state = new_state -- Ensure this updates the global variable - if XA_CABIN_SETTINGS.mode.automated then - ANNOUNCEMENTS.play_sound(new_state) - LOGGER.write_log("Playing announcement for cabin state: " .. new_state) + local announcement_name = cabin_state_to_announcement_name(new_state) + if announcement_name then + ANNOUNCEMENTS.play_sound(announcement_name) + XA_CABIN_LOGGER.write_log("Playing announcement: " .. announcement_name) + else + XA_CABIN_LOGGER.write_log("No announcement found for cabin state: " .. new_state) + end end - - LOGGER.write_log("Cabin state changed to: " .. new_state) + XA_CABIN_LOGGER.write_log("Cabin state changed to: " .. new_state) end --- Define the mapping table at the beginning of your script -CABIN_STATE_MAPPING = { - ["pre_boarding"] = "mapped_state_1", - ["boarding"] = "mapped_state_2", - ["boarding_complete"] = "mapped_state_3", - ["safety_demonstration"] = "mapped_state_4", - ["takeoff"] = "mapped_state_5", - ["climb"] = "mapped_state_6", - ["cruise"] = "mapped_state_7", - ["prepare_for_landing"] = "mapped_state_8", - ["final_approach"] = "mapped_state_9", - ["post_landing"] = "mapped_state_10", -} +function STATE.update_cabin_state() + if XA_CABIN_STATES.cabin_state.current_state == "pre_boarding" then + if HELPERS.is_door_open() and XA_CABIN_STATES.flight_state.parked then + STATE.bording_delay_counter = STATE.bording_delay_counter + 1 + + -- Generate and store the random delay threshold once + if not STATE.boarding_delay_threshold then + STATE.boarding_delay_threshold = math.random(90, 120) + XA_CABIN_LOGGER.write_log("Boarding delay threshold set to: " .. STATE.boarding_delay_threshold) + end + + -- Compare the counter against the stored threshold + if STATE.bording_delay_counter > STATE.boarding_delay_threshold then + change_cabin_state("boarding") + -- Reset counter and threshold for future use + STATE.bording_delay_counter = 0 + STATE.boarding_delay_threshold = nil + end + else + -- Reset counter and threshold if conditions are not met + STATE.bording_delay_counter = 0 + STATE.boarding_delay_threshold = nil + end + return + end + + if XA_CABIN_STATES.cabin_state.current_state == "boarding" then + if not HELPERS.is_door_open() and not XA_CABIN_STATES.flight_state.taxi_out then + change_cabin_state("boarding_complete") + end + return + end -function cabin_state_to_CABIN_XA_CABIN_STATES(cabin_state) - LOGGER.write_log("Mapping cabin state to CABIN: " .. cabin_state) - local mapped_state = CABIN_STATE_MAPPING[cabin_state] - if mapped_state then - return mapped_state - else - LOGGER.write_log("Invalid cabin state: " .. tostring(cabin_state)) - return nil + if XA_CABIN_STATES.cabin_state.current_state == "boarding_complete" then + if XA_CABIN_STATES.flight_state.taxi_out then + change_cabin_state("safety_demonstration") + end + return + end + + if XA_CABIN_STATES.cabin_state.current_state == "safety_demonstration" then + if HELPERS.is_landing_ligths_on() and XA_CABIN_STATES.flight_state.taxi_out then + change_cabin_state("takeoff") + end + return + end + + if XA_CABIN_STATES.cabin_state.current_state == "takeoff" then + if XA_CABIN_DATAREFS.AGL == nil then + XA_CABIN_DATAREFS.AGL = dataref_table('sim/flightmodel/position/y_agl') + end + if XA_CABIN_STATES.flight_state.climb and XA_CABIN_DATAREFS.AGL[0] > 1000 then + change_cabin_state("climb") + end + return + end + + if XA_CABIN_STATES.cabin_state.current_state == "climb" then + if XA_CABIN_STATES.flight_state.cruise then + change_cabin_state("cruise") + end + return + end + + if XA_CABIN_STATES.cabin_state.current_state == "cruise" then + if XA_CABIN_STATES.flight_state.descent then + change_cabin_state("prepare_for_landing") + end + return + end + + if XA_CABIN_STATES.cabin_state.current_state == "prepare_for_landing" then + if XA_CABIN_STATES.flight_state.approach then + change_cabin_state("final_approach") + end + return + end + + if XA_CABIN_STATES.cabin_state.current_state == "final_approach" then + if XA_CABIN_STATES.flight_state.taxi_in then + change_cabin_state("post_landing") + end + return + end + + if XA_CABIN_STATES.cabin_state.current_state == "post_landing" then + if XA_CABIN_STATES.flight_state.parked then + change_cabin_state("pre_boarding") + end + return end end -function STATE.initialize_datarefs() - XA_CABIN_DATAREFS.GS = dataref_table('sim/flightmodel/position/groundspeed') - XA_CABIN_DATAREFS.GEAR_FORCE = dataref_table('sim/flightmodel/forces/fnrml_gear') - XA_CABIN_DATAREFS.N1 = dataref_table('sim/cockpit2/engine/indicators/N1_percent') - XA_CABIN_DATAREFS.VS = dataref_table('sim/flightmodel/position/vh_ind_fpm') - XA_CABIN_DATAREFS.AGL = dataref_table('sim/flightmodel/position/y_agl') - -- Initialize other required datarefs here +function announcement_name_to_cabin_state(announcement_name) + local cabin_states = { + "pre_boarding", + "boarding", + "boarding_complete", + "safety_demonstration", + "takeoff", + "climb", + "cruise", + "prepare_for_landing", + "final_approach", + "post_landing" + } + + for _, cabin_state in ipairs(cabin_states) do + local ann_name = cabin_state_to_announcement_name(cabin_state) + if ann_name == announcement_name then + return cabin_state + end + end + + XA_CABIN_LOGGER.write_log("Unknown announcement name: " .. tostring(announcement_name)) + return nil +end + +function cabin_state_to_announcement_name(cabin_state) + if cabin_state == "pre_boarding" then + return XA_CABIN_ANNOUNCEMENT_STATES[1] + elseif cabin_state == "boarding" then + return XA_CABIN_ANNOUNCEMENT_STATES[2] + elseif cabin_state == "boarding_complete" then + return XA_CABIN_ANNOUNCEMENT_STATES[3] + elseif cabin_state == "safety_demonstration" then + return XA_CABIN_ANNOUNCEMENT_STATES[4] + elseif cabin_state == "takeoff" then + return XA_CABIN_ANNOUNCEMENT_STATES[5] + elseif cabin_state == "climb" then + return XA_CABIN_ANNOUNCEMENT_STATES[6] + elseif cabin_state == "cruise" then + return XA_CABIN_ANNOUNCEMENT_STATES[7] + elseif cabin_state == "prepare_for_landing" then + return XA_CABIN_ANNOUNCEMENT_STATES[8] + elseif cabin_state == "final_approach" then + return XA_CABIN_ANNOUNCEMENT_STATES[9] + elseif cabin_state == "post_landing" then + return XA_CABIN_ANNOUNCEMENT_STATES[10] + end end --- Call this function at the start of your script -STATE.initialize_datarefs() --- Register update functions -do_every_frame("STATE.update_flight_state_every_minute()") -do_every_frame("STATE.update_cabin_state_every_minute()") +STATE.initialize_states() -return STATE \ No newline at end of file +return STATE From 1bae24bfd33b3b733408146b48ea89cdb8c9c1b0 Mon Sep 17 00:00:00 2001 From: Ricardo Borenstein Date: Sun, 13 Oct 2024 11:19:00 +0200 Subject: [PATCH 03/11] Fixing a few bug, adding state.md and generate audio using OpenAI API --- xa-cabin.lua | 200 +++++++++----- xa-cabin/GUI.lua | 60 +++-- xa-cabin/announcement.lua | 133 +++++++-- xa-cabin/generate_announcements.py | 184 +++++++++++++ xa-cabin/globals.lua | 31 +-- xa-cabin/helpers.lua | 125 +++++++-- xa-cabin/state.lua | 420 +++++++++++++++-------------- xa-cabin/state.md | 120 +++++++++ 8 files changed, 910 insertions(+), 363 deletions(-) create mode 100644 xa-cabin/generate_announcements.py create mode 100644 xa-cabin/state.md diff --git a/xa-cabin.lua b/xa-cabin.lua index fc26ae9..9fb86a1 100644 --- a/xa-cabin.lua +++ b/xa-cabin.lua @@ -1,29 +1,36 @@ +-- xa-cabin.lua + +-- Define SCRIPT_DIRECTORY if not already defined +if not SCRIPT_DIRECTORY then + SCRIPT_DIRECTORY = debug.getinfo(1, "S").source:match("@?(.*/)") +end + +-- Load required scripts LIP = dofile(SCRIPT_DIRECTORY .. "/xa-cabin/LIP.lua") XA_CABIN_LOGGER = dofile(SCRIPT_DIRECTORY .. "/xa-cabin/logging.lua") -dofile(SCRIPT_DIRECTORY .. "/xa-cabin/globals.lua") HELPERS = dofile(SCRIPT_DIRECTORY .. "/xa-cabin/helpers.lua") -local STATE = dofile(SCRIPT_DIRECTORY .. "/xa-cabin/state.lua") -local GUI = dofile(SCRIPT_DIRECTORY .. "/xa-cabin/GUI.lua") -ANNOUNCEMENTS = dofile(SCRIPT_DIRECTORY .. "/xa-cabin/announcement.lua") +-- Load settings and plane config first +XA_CABIN_SETTINGS = LIP.load(SCRIPT_DIRECTORY .. "xa-cabin.ini") + +-- Load globals after settings +dofile(SCRIPT_DIRECTORY .. "/xa-cabin/globals.lua") ---[[ -IMGUI Blank Template -Author: Joe Kipfer 2019-06-06 -Use in conjuction with Folko's IMGUI Demo script for some great examples and explaination. -When Using IMGUI Demo script mentioned above, don't forget to put the imgui demo.jpg in with it or -you'll get an error. -]] +-- Now load scripts that depend on globals +local STATE = dofile(SCRIPT_DIRECTORY .. "/xa-cabin/state.lua") +local GUI = dofile(SCRIPT_DIRECTORY .. "/xa-cabin/GUI.lua") +-- Finally, load announcements +ANNOUNCEMENTS = dofile(SCRIPT_DIRECTORY .. "/xa-cabin/announcement.lua") +-- Initialize Announcements +ANNOUNCEMENTS.loadSounds() +-- Check for imgui support if not SUPPORTS_FLOATING_WINDOWS then - -- to make sure the script doesn't stop with old FlyWithLua versions logMsg("imgui not supported by your FlyWithLua version") return end ------------------------------------Variables go here-------------------------------------------- ---Set you variables here, datarefs, etc... -XA_CABIN_SETTINGS = LIP.load(SCRIPT_DIRECTORY .. "xa-cabin.ini") --- check if file exists, if not, create it + +-- Check if plane config file exists, if not, create it local plane_config_file_path = AIRCRAFT_PATH .. "/xa-cabin.ini" local plane_config_file = io.open(plane_config_file_path, "r") @@ -31,9 +38,11 @@ if plane_config_file == nil then XA_CABIN_LOGGER.write_log("Creating new plane config file") LIP.save(plane_config_file_path, XA_CABIN_PLANE_CONFIG) end + XA_CABIN_PLANE_CONFIG = LIP.load(AIRCRAFT_PATH .. "/xa-cabin.ini") + +-- Handle potential legacy config if XA_CABIN_PLANE_CONFIG.RWY_LIGHTS ~= nil then - XA_CABIN_PLANE_CONFIG["LANDING_LIGHTS"] = {} XA_CABIN_PLANE_CONFIG["LANDING_LIGHTS"] = XA_CABIN_PLANE_CONFIG.LANDING_LIGHTS XA_CABIN_PLANE_CONFIG.RWY_LIGHTS = nil LIP.save(AIRCRAFT_PATH .. "/xa-cabin.ini", XA_CABIN_PLANE_CONFIG) @@ -42,15 +51,12 @@ end XA_CABIN_LOGGER.dumpTable(XA_CABIN_PLANE_CONFIG) XA_CABIN_LOGGER.write_log("Loaded plane config file") -SIMBRIEF = dofile(SCRIPT_DIRECTORY .. "/xa-cabin/simbrief.lua") +-- Load Simbrief script +SIMBRIEF = dofile(SCRIPT_DIRECTORY .. "/xa-cabin/simbrief.lua") - - - --------------------------------------Build Your GUI Here---------------------------------------- - -function xa_cabin_on_build(xa_cabin_wnd, x, y) --<-- your GUI code goes in this section. +-- GUI Building Function +function xa_cabin_on_build(xa_cabin_wnd, x, y) local win_width = imgui.GetWindowWidth() local win_height = imgui.GetWindowHeight() imgui.Columns(2) @@ -69,27 +75,18 @@ function xa_cabin_on_build(xa_cabin_wnd, x, y) --<-- your GUI code goes in this imgui.Spacing() GUI.Announcements(win_width, win_height) -end -- function xa_cabin_on_build - -------------------------------------------------------------------------------------------------- - - - - - - - --------------------Show Hide Window Section with Toggle functionaility--------------------------- +end -xa_cabin_wnd = nil -- flag for the show_wnd set to nil so that creation below can happen - float_wnd_create +-- Show/Hide Window Functions +xa_cabin_wnd = nil -function xa_cabin_show_wnd() -- This is called when user toggles window on/off, if the next toggle is for ON +function xa_cabin_show_wnd() xa_cabin_wnd = float_wnd_create(680, 500, 1, true) float_wnd_set_title(xa_cabin_wnd, "XA Cabin " .. XA_CABIN_VERSION) float_wnd_set_imgui_builder(xa_cabin_wnd, "xa_cabin_on_build") end -function xa_cabin_hide_wnd() -- This is called when user toggles window on/off, if the next toggle is for OFF +function xa_cabin_hide_wnd() if xa_cabin_wnd then float_wnd_destroy(xa_cabin_wnd) end @@ -98,7 +95,7 @@ end xa_cabin_show_only_once = 0 xa_cabin_hide_only_once = 0 -function toggle_xa_cabin_window() -- This is the toggle window on/off function +function toggle_xa_cabin_window() xa_cabin_show_window = not xa_cabin_show_window if xa_cabin_show_window then if xa_cabin_show_only_once == 0 then @@ -115,35 +112,11 @@ function toggle_xa_cabin_window() -- This is the toggle window on/off function end end ------------------------------------------------------------------------------------------------- - - - - - - -----"add_macro" - adds the option to the FWL macro menu in X-Plane -----"create command" - creates a show/hide toggle command that calls the toggle_xa_cabin_window() -add_macro("XA Cabin", "xa_cabin_show_wnd()", - "xa_cabin_hide_wnd()", "activate") -create_command("xa_cabin_menus/show_toggle", "open/close XA Cabin Menu window", - "toggle_xa_cabin_window()", "", "") - ---[[ -footnotes: If changing color using PushStyleColor, here are common color codes: - BLACK = 0xFF000000; - DKGRAY = 0xFF444444; - GRAY = 0xFF888888; - LTGRAY = 0xFFCCCCCC; - WHITE = 0xFFFFFFFF; - RED = 0xFFFF0000; - GREEN = 0xFF00FF00; - BLUE = 0xFF0000FF; - YELLOW = 0xFFFFFF00; - CYAN = 0xFF00FFFF; - MAGENTA = 0xFFFF00FF; - ]] +-- Add Macro and Command +add_macro("XA Cabin", "xa_cabin_show_wnd()", "xa_cabin_hide_wnd()", "activate") +create_command("xa_cabin_menus/show_toggle", "open/close XA Cabin Menu window", "toggle_xa_cabin_window()", "", "") +-- Update State Function function xa_cabin_update_state() local status, err = pcall(STATE.update_flight_state) if not status then @@ -156,5 +129,96 @@ function xa_cabin_update_state() end end -ANNOUNCEMENTS.loadSounds() +-- Function to Generate Announcements +function generate_announcements() + local airline = SIMBRIEF["Airline"] or "Datanised Airlines" + local flight_number = SIMBRIEF["Callsign"] or "DAT01" + local destination = SIMBRIEF["DestName"] or "SBSP" + local eta_seconds = SIMBRIEF["Ete"] or 0 + local eta_hours = math.floor(eta_seconds / 3600) + local eta_minutes = math.floor((eta_seconds % 3600) / 60) + local eta = string.format("%d hours and %d minutes", eta_hours, eta_minutes) + + -- New variables for runway, altitude, etc. + local departure_runway = SIMBRIEF["OrigRwy"] or "Runway 01" + local arrival_runway = SIMBRIEF["DestRwy"] or "Runway 02" + local cruise_altitude = SIMBRIEF["Level"] or "35,000 ft" + local flight_time_hours = eta_hours + local flight_time_minutes = eta_minutes + local local_time_at_destination = SIMBRIEF["DestTime"] or "12:00 PM" + local aircraft_type = SIMBRIEF["AircraftType"] or "Boeing 737" + local local_time_sec_ref = dataref_table("sim/time/local_time_sec") + -- Function to classify time of day based on local time seconds + function get_time_of_day() + -- Get the time in seconds since midnight from the dataref + local time_in_seconds = local_time_sec_ref[0] + -- Convert seconds to hours (since midnight) + local time_in_hours = time_in_seconds / 3600 + + -- Determine time of day based on hours + if time_in_hours >= 5 and time_in_hours < 12 then + return "morning" + elseif time_in_hours >= 12 and time_in_hours < 18 then + return "afternoon" + else + return "night" + end + end + local time_of_day = get_time_of_day() + -- Language, accent, and speaker from configuration + local language = XA_CABIN_LANGUAGE or "en" + local accent = XA_CABIN_ACCENT or "gb" + local speaker = XA_CABIN_SETTINGS.announcement.speaker or "01" -- Ensure 'speaker' is fetched + + local python_script_path = SCRIPT_DIRECTORY .. "xa-cabin/generate_announcements.py" + + -- Only generate audio if language is "custom" + if language == "custom" then + local python_script_path = SCRIPT_DIRECTORY .. "xa-cabin/generate_announcements.py" + + -- Updated string.format with all the necessary variables passed to the Python script + local command = string.format( + '/usr/local/bin/python3 "%s" "%s" "%s" "%s" "%s" "%s" "%s" "%s" "%s" "%s" "%s" "%s" "%s" "%s" "%s" "%s"', + python_script_path, + airline, + flight_number, + destination, + eta, + language, + accent, + speaker, + departure_runway, + arrival_runway, + cruise_altitude, + flight_time_hours, + flight_time_minutes, + local_time_at_destination, + aircraft_type, + time_of_day + ) + + XA_CABIN_LOGGER.write_log("Generating announcements with command: " .. command) + -- Use io.popen to capture output from the Python script + -- local handle = io.popen(command) + -- local result = handle:read("*a") -- Capture all output from the Python script + -- handle:close() + + -- -- Print the captured output from the Python script + -- print("Command output: " .. result) + + -- -- Log the output from the Python script (optional) + -- XA_CABIN_LOGGER.write_log("Announcement generation output: " .. result) + + -- After generating announcements, load them + --ANNOUNCEMENTS.loadSounds() + else + XA_CABIN_LOGGER.write_log("Skipping announcement generation since language is not 'custom'.") + end +end + + +-- Generate Announcements on Initialization +generate_announcements() +-- Schedule State Updates do_often("xa_cabin_update_state()") + diff --git a/xa-cabin/GUI.lua b/xa-cabin/GUI.lua index 77cd1ac..c1933f4 100644 --- a/xa-cabin/GUI.lua +++ b/xa-cabin/GUI.lua @@ -159,47 +159,63 @@ function GUI.Configuration(win_width, win_height) end imgui.EndChild() end - function GUI.Announcements(win_width, win_height) if imgui.BeginChild("Announcements", win_width - 32, win_height * SECOND_ROW_HEIGHT_PERCENT) then imgui.SetWindowFontScale(1.2) if imgui.BeginTable("XA Cabin", 3) then - for i = 2, #XA_CABIN_ANNOUNCEMENT_STATES, 3 - do + for i = 1, #XA_CABIN_ANNOUNCEMENT_STATES, 3 do imgui.Spacing() imgui.Spacing() imgui.Spacing() imgui.Spacing() imgui.TableNextRow() + + -- First Column imgui.TableNextColumn() - if imgui.Button(XA_CABIN_ANNOUNCEMENT_STATES[i], win_width * 0.3 - 16, 50) then - local cabin_state = announcement_name_to_cabin_state(XA_CABIN_ANNOUNCEMENT_STATES[i]) - if cabin_state then - change_cabin_state(cabin_state) - else - XA_CABIN_LOGGER.write_log("Failed to find cabin state for announcement: " .. XA_CABIN_ANNOUNCEMENT_STATES[i]) + if XA_CABIN_ANNOUNCEMENT_STATES[i] then + if imgui.Button(XA_CABIN_ANNOUNCEMENT_STATES[i], win_width * 0.3 - 16, 50) then + local cabin_state = announcement_name_to_cabin_state(XA_CABIN_ANNOUNCEMENT_STATES[i]) + if cabin_state then + --STATE.change_cabin_state(cabin_state) + ANNOUNCEMENTS.play_sound(XA_CABIN_ANNOUNCEMENT_STATES[i]) + else + XA_CABIN_LOGGER.write_log("Failed to find cabin state for announcement: " .. XA_CABIN_ANNOUNCEMENT_STATES[i]) + end end end + + -- Second Column imgui.TableNextColumn() - if XA_CABIN_ANNOUNCEMENT_STATES[i + 1] == nil then - break - end - if imgui.Button(XA_CABIN_ANNOUNCEMENT_STATES[i + 1], win_width * 0.3 - 16, 50) then -- Bigger than normal sized button - ANNOUNCEMENTS.play_sound(XA_CABIN_ANNOUNCEMENT_STATES[i + 1]) + if XA_CABIN_ANNOUNCEMENT_STATES[i + 1] then + if imgui.Button(XA_CABIN_ANNOUNCEMENT_STATES[i + 1], win_width * 0.3 - 16, 50) then + local cabin_state = announcement_name_to_cabin_state(XA_CABIN_ANNOUNCEMENT_STATES[i + 1]) + if cabin_state then + --STATE.change_cabin_state(cabin_state) + ANNOUNCEMENTS.play_sound(XA_CABIN_ANNOUNCEMENT_STATES[i+1]) + else + XA_CABIN_LOGGER.write_log("Failed to find cabin state for announcement: " .. XA_CABIN_ANNOUNCEMENT_STATES[i + 1]) + end + end end - imgui.TableNextColumn() - if XA_CABIN_ANNOUNCEMENT_STATES[i + 2] == nil then - break - end - if imgui.Button(XA_CABIN_ANNOUNCEMENT_STATES[i + 2], win_width * 0.3 - 16, 50) then -- Bigger than normal sized button - ANNOUNCEMENTS.play_sound(XA_CABIN_ANNOUNCEMENT_STATES[i + 2]) + -- Third Column + imgui.TableNextColumn() + if XA_CABIN_ANNOUNCEMENT_STATES[i + 2] then + if imgui.Button(XA_CABIN_ANNOUNCEMENT_STATES[i + 2], win_width * 0.3 - 16, 50) then + local cabin_state = announcement_name_to_cabin_state(XA_CABIN_ANNOUNCEMENT_STATES[i + 2]) + if cabin_state then + --STATE.change_cabin_state(cabin_state) + ANNOUNCEMENTS.play_sound(XA_CABIN_ANNOUNCEMENT_STATES[i+2]) + else + XA_CABIN_LOGGER.write_log("Failed to find cabin state for announcement: " .. XA_CABIN_ANNOUNCEMENT_STATES[i + 2]) + end + end end end + imgui.EndTable() end - imgui.EndTable() + imgui.EndChild() end - imgui.EndChild() end return GUI; diff --git a/xa-cabin/announcement.lua b/xa-cabin/announcement.lua index d70692d..6833cab 100644 --- a/xa-cabin/announcement.lua +++ b/xa-cabin/announcement.lua @@ -1,25 +1,87 @@ -local ANNOUNCEMENTS = { +local debounce_duration = 5 -- Set debounce time (in seconds) +local last_play_times = {} -- Track last play times for all announcements +local is_announcement_playing = false -- Track if any announcement is currently playing + +ANNOUNCEMENTS = { sounds = {}, - files = {} + files = {}, + pending_sounds = {}, + is_loaded = false, + is_safety_demo_playing = false -- Flag to track if safety demo is playing +} + +local ANNOUNCEMENTS_DIR = SCRIPT_DIRECTORY .. "xa-cabin/announcements/" + +local phases = { + "pre_boarding", + "boarding", + "boarding_complete", + "safety_demonstration", + "takeoff", + "climb", + "cruise", + "prepare_for_landing", + "final_approach", + "post_landing", + "emergency" -- Add any additional announcements as needed } + function ANNOUNCEMENTS.play_sound(announcement_name) + local current_time = os.clock() ANNOUNCEMENTS.stopSounds() - play_sound(ANNOUNCEMENTS.sounds[announcement_name]) - XA_CABIN_LOGGER.write_log("Playing announcement for " .. ANNOUNCEMENTS.files[announcement_name]) + -- Check if another announcement is playing, and stop it before playing a new one + if is_announcement_playing then + ANNOUNCEMENTS.stopSounds() -- Stop the currently playing sound + XA_CABIN_LOGGER.write_log("Stopping current sound to play: " .. announcement_name) + end + + -- Initialize last play time for the announcement if it doesn't exist + if not last_play_times[announcement_name] then + last_play_times[announcement_name] = 0 + end + + -- Debounce logic: Skip if debounce duration has not passed + if current_time - last_play_times[announcement_name] < debounce_duration then + XA_CABIN_LOGGER.write_log("Debounce active for announcement: " .. announcement_name .. ". Skipping.") + return + end + + -- Update last play time and mark announcement as playing + last_play_times[announcement_name] = current_time + is_announcement_playing = true + + -- Continue playing the sound + XA_CABIN_LOGGER.write_log("Attempting to play sound for: " .. announcement_name) + local sound_handle = ANNOUNCEMENTS.sounds[announcement_name] + + if sound_handle then + XA_CABIN_LOGGER.write_log("Playing sound file: " .. ANNOUNCEMENTS.files[announcement_name]) + play_sound(sound_handle) + else + XA_CABIN_LOGGER.write_log("Sound not found for announcement: " .. announcement_name) + is_announcement_playing = false -- Reset the flag immediately if no sound is found + end +end + +function ANNOUNCEMENTS.on_sound_complete() + is_announcement_playing = false -- Reset the flag to allow the next announcement + XA_CABIN_LOGGER.write_log("Announcement has finished playing.") end function ANNOUNCEMENTS.stopSounds() - if ANNOUNCEMENTS.sounds then - for i = 2, #XA_CABIN_ANNOUNCEMENT_STATES do - if ANNOUNCEMENTS.sounds[XA_CABIN_ANNOUNCEMENT_STATES[i]] then - stop_sound(ANNOUNCEMENTS.sounds[XA_CABIN_ANNOUNCEMENT_STATES[i]]) - end + -- Stop all currently playing sounds + XA_CABIN_LOGGER.write_log("Stopping all current sounds") + for _, phase in ipairs(phases) do + local sound_handle = ANNOUNCEMENTS.sounds[phase] + if sound_handle then + stop_sound(sound_handle) end end + is_announcement_playing = false -- Ensure the flag is reset when all sounds are stopped end function ANNOUNCEMENTS.unloadAllSounds() - for index, not_used in pairs(ANNOUNCEMENTS.sounds) do + for index, _ in pairs(ANNOUNCEMENTS.sounds) do if ANNOUNCEMENTS.sounds[index] then ANNOUNCEMENTS.sounds[index] = false end @@ -27,29 +89,42 @@ function ANNOUNCEMENTS.unloadAllSounds() XA_CABIN_LOGGER.write_log("Unloaded all sounds") end +-- Load the sounds and mark them as ready once done function ANNOUNCEMENTS.loadSounds() ANNOUNCEMENTS.stopSounds() - -- ANNOUNCEMENTS.unloadAllSounds() - local language = XA_CABIN_SETTINGS.announcement.language - local accent = XA_CABIN_SETTINGS.announcement.accent - local speaker = XA_CABIN_SETTINGS.announcement.speaker - XA_CABIN_LOGGER.dumpTable(XA_CABIN_ANNOUNCEMENT_STATES) - for i = 2, #XA_CABIN_ANNOUNCEMENT_STATES do - local announcement = XA_CABIN_ANNOUNCEMENT_STATES[i] - local wav_file_path = SCRIPT_DIRECTORY .. - "/xa-cabin/announcements/" .. - announcement .. "/" .. language .. "-" .. accent .. "-" .. speaker .. ".wav" - local tmp = io.open(wav_file_path, "r") - if tmp == nil then - XA_CABIN_LOGGER.write_log("File not found: " .. wav_file_path) + for _, announcement_name in ipairs(XA_CABIN_ANNOUNCEMENT_STATES) do + local formatted_name = announcement_name + local wav_file_path = ANNOUNCEMENTS_DIR .. formatted_name .. "/" .. XA_CABIN_LANGUAGE .. "-" .. XA_CABIN_ACCENT .. "-1.wav" + + local file = io.open(wav_file_path, "r") + if file then + file:close() + local sound_handle = load_WAV_file(wav_file_path) + ANNOUNCEMENTS.sounds[announcement_name] = sound_handle + ANNOUNCEMENTS.files[announcement_name] = wav_file_path + XA_CABIN_LOGGER.write_log("Successfully loaded announcement: " .. wav_file_path) else - tmp:close() -- Don't forget to close the file - local index = load_WAV_file(wav_file_path) - ANNOUNCEMENTS.sounds[announcement] = index - ANNOUNCEMENTS.files[announcement] = wav_file_path + XA_CABIN_LOGGER.write_log("Announcement file not found: " .. wav_file_path) end end - XA_CABIN_LOGGER.dumpTable(ANNOUNCEMENTS.sounds) + + -- Mark loading as complete + ANNOUNCEMENTS.is_loaded = true + XA_CABIN_LOGGER.write_log("All announcements loaded successfully.") + + -- Play any pending announcements queued during loading + ANNOUNCEMENTS.play_pending_sounds() +end + +-- Play any sounds that were queued while loading was happening +function ANNOUNCEMENTS.play_pending_sounds() + for _, sound_name in ipairs(ANNOUNCEMENTS.pending_sounds) do + XA_CABIN_LOGGER.write_log("Playing deferred announcement: " .. sound_name) + ANNOUNCEMENTS.play_sound(sound_name) + end + + -- Clear the pending queue + ANNOUNCEMENTS.pending_sounds = {} end -return ANNOUNCEMENTS +return ANNOUNCEMENTS \ No newline at end of file diff --git a/xa-cabin/generate_announcements.py b/xa-cabin/generate_announcements.py new file mode 100644 index 0000000..8b55126 --- /dev/null +++ b/xa-cabin/generate_announcements.py @@ -0,0 +1,184 @@ +import sys +import openai +import os +import threading +from pydub import AudioSegment +import io + +# Ensure pydub can find ffmpeg +AudioSegment.converter = "/opt/homebrew/bin/ffmpeg" # Update this path based on your ffmpeg installation +AudioSegment.ffprobe = "/opt/homebrew/bin/ffprobe" # Update this path based on your ffprobe installation + +# Directory to save announcement sound files +SCRIPT_DIRECTORY = os.path.dirname(os.path.abspath(__file__)) +output_base_dir = os.path.join(SCRIPT_DIRECTORY, "announcements") +os.makedirs(output_base_dir, exist_ok=True) + +# FMS data passed from Lua script as command-line arguments +# Expected arguments: airline, flight_number, destination, eta, language, accent, speaker +fms_data = { + "airline": sys.argv[1] if len(sys.argv) > 1 else "Datanised Airlines", + "flight_number": sys.argv[2] if len(sys.argv) > 2 else "DAT01", + "destination": sys.argv[3] if len(sys.argv) > 3 else "Unknown", + "eta": sys.argv[4] if len(sys.argv) > 4 else "Unknown", + "language": sys.argv[5] if len(sys.argv) > 5 else "en", + "accent": sys.argv[6] if len(sys.argv) > 6 else "gb", + "speaker": sys.argv[7] if len(sys.argv) > 7 else "01", + "departure_runway": sys.argv[8] if len(sys.argv) > 8 else "Runway 01", + "arrival_runway": sys.argv[9] if len(sys.argv) > 9 else "Runway 02", + "cruise_altitude": sys.argv[10] if len(sys.argv) > 10 else "35,000 ft", + "flight_time_hours": sys.argv[11] if len(sys.argv) > 11 else "2", + "flight_time_minutes": sys.argv[12] if len(sys.argv) > 12 else "30", + "local_time_at_destination": sys.argv[13] if len(sys.argv) > 13 else "12:00 PM", + "landing_time": sys.argv[14] if len(sys.argv) > 14 else "15", + "aircraft_type": sys.argv[15] if len(sys.argv) > 15 else "Boeing 737", + "time_of_day": sys.argv[16] if len(sys.argv) > 16 else "morning" +} + +# Announcements for each stage of the flight +announcements = { + "Pre-Boarding": f""" + Ladies and gentlemen, welcome aboard {fms_data['airline']} flight {fms_data['flight_number']}. + We will begin boarding shortly. Please ensure you have your boarding pass and identification ready. + """, + + "Boarding": f""" + Ladies and gentlemen, we are now ready to board {fms_data['airline']} flight {fms_data['flight_number']} bound for {fms_data['destination']}. + Please listen for your group number, and ensure your boarding pass is ready as you approach the gate. + """, + + "Boarding Complete": f""" + Ladies and gentlemen, boarding for flight {fms_data['flight_number']} is now complete. + Please stow your larger carry-on items in the overhead bins and smaller items under the seat in front of you. + Ensure your seatbelt is fastened, your seatback is upright, and your tray table is stowed. + We will be departing from runway {fms_data['departure_runway']} shortly. + """, + + "Safety Demonstration": f""" + Ladies and gentlemen, for your safety, please direct your attention to the cabin crew as they demonstrate the safety features of this {fms_data['aircraft_type']}. + Even if you are a frequent flyer, we ask that you pay close attention, as procedures may vary. + """, + + "Takeoff": f""" + Cabin crew, please prepare for takeoff. + Ladies and gentlemen, we are cleared for takeoff from runway {fms_data['departure_runway']}. + Please ensure your seatbelt is securely fastened and all electronics are switched to airplane mode. + We expect smooth conditions on departure. + """, + + "Climb": f""" + Good {fms_data['time_of_day']}, this is your captain speaking. + We are now climbing to our cruising altitude of {fms_data['cruise_altitude']} feet, en route to {fms_data['destination']}. + Please keep your seatbelt fastened as we continue our ascent. + The expected flight time is approximately {fms_data['flight_time_hours']} hours and {fms_data['flight_time_minutes']} minutes. + """, + + "Cruise": f""" + Ladies and gentlemen, we have now reached our cruising altitude of {fms_data['cruise_altitude']} feet. + The seatbelt sign is turned off, but we recommend you keep your seatbelt fastened while seated, just in case of unexpected turbulence. + Our cabin crew will be coming through the cabin with in-flight service shortly. + """, + + "Prepare for Landing": f""" + Ladies and gentlemen, we have begun our descent into {fms_data['destination']}, where the local time is {fms_data['local_time_at_destination']}. + Please return to your seat, fasten your seatbelt, and make sure your seatback and tray table are in their upright and locked positions. + We expect to land on runway {fms_data['arrival_runway']} in approximately 20 minutes. + """, + + "Final Approach": f""" + Cabin crew, please prepare the cabin for landing. + Ladies and gentlemen, we are on our final approach to {fms_data['destination']}. + Please ensure your seatbelt is fastened, and all personal items are properly stowed. + """, + + "Post Landing": f""" + Ladies and gentlemen, welcome to {fms_data['destination']}. The local time is {fms_data['local_time_at_destination']}. + Please remain seated with your seatbelt fastened until the captain turns off the seatbelt sign. + Be careful when opening the overhead bins, as items may have shifted during the flight. + """, + + "Emergency": """ + Ladies and gentlemen, we are experiencing an emergency situation. Please remain calm and follow all instructions from the cabin crew. + Your safety is our priority, and we are prepared to assist you. + """ +} + +# Initialize OpenAI client with your API key +openai.api_key = "xxxxxxxxxxxxxx" +client = openai + +# Function to generate TTS using OpenAI's API with streaming response +def generate_tts(phase, text, language, accent, speaker): + # Create subdirectory for the phase without replacing spaces + phase_dir = os.path.join(output_base_dir, phase) # Retain exact phase name + os.makedirs(phase_dir, exist_ok=True) + + # Determine the voice parameter + if language.lower() == "custom": + voice = accent.lower() # Use 'accent' as 'voice' for custom + else: + # Map predefined languages and accents to OpenAI voices + voice_map = { + ("en", "gb"): "Onyx", + ("en", "us"): "Echo", + ("es", "mx"): "Nova", + # Add more mappings as needed + } + voice = voice_map.get((language.lower(), accent.lower()), "alloy") # Default to 'alloy' + + # Construct the filename + file_path = os.path.join(phase_dir, f"{language.lower()}-{accent.lower()}-1.wav") + + # Remove existing file if it exists + if os.path.exists(file_path): + os.remove(file_path) + print(f"Removed existing file: {file_path}") + + # Generate the new audio file using OpenAI's streaming TTS API + + # Generate the new audio file using OpenAI's streaming TTS API + try: + with client.audio.speech.with_streaming_response.create( + model="tts-1", # Or "tts-1-hd" for high-definition quality + voice=voice, + input=text, + ) as audio_content: + # Save the audio file (initially as an mp3 or as received) + mp3_path = file_path.replace(".wav", ".mp3") + audio_content.stream_to_file(mp3_path) + print(f"Generated new file: {mp3_path}") + + # Convert the file to a valid WAV format + audio = AudioSegment.from_file(mp3_path) + + # Set parameters to ensure proper WAV format: 44.1 kHz, mono, 16-bit PCM + audio = audio.set_frame_rate(44100).set_channels(1).set_sample_width(2) + + # Export as a valid WAV file + audio.export(file_path, format="wav") + print(f"Converted and saved as WAV: {file_path}") + + # Optionally remove the original mp3 file + os.remove(mp3_path) + print(f"Generated and converted file: {file_path}") + + except Exception as e: + print(f"Error generating TTS for {phase}: {e}") + +# Function to generate TTS files asynchronously +def generate_tts_files(): + threads = [] + for phase, text in announcements.items(): + # Create a thread for each TTS task using OpenAI's API + t = threading.Thread(target=generate_tts, args=(phase, text, fms_data['language'], fms_data['accent'], fms_data['speaker'])) + threads.append(t) + t.start() + + # Wait for all threads to complete + for t in threads: + t.join() + return + +# Generate the audio files asynchronously +if __name__ == "__main__": + generate_tts_files() \ No newline at end of file diff --git a/xa-cabin/globals.lua b/xa-cabin/globals.lua index 6629b4d..0eb9c5c 100644 --- a/xa-cabin/globals.lua +++ b/xa-cabin/globals.lua @@ -1,4 +1,7 @@ +-- globals.lua + XA_CABIN_VERSION = "v0.0.1" + XA_CABIN_ANNOUNCEMENT_STATES = { "Pre-Boarding", "Boarding", @@ -13,19 +16,10 @@ XA_CABIN_ANNOUNCEMENT_STATES = { "Emergency" } -XA_CABIN_SETTINGS = { - simbrief = { - username = "" - }, - mode = { - automated = false - }, - announcement = { - language = "en", - accent = "in", - speaker = "01" - } -} +-- XA_CABIN_SETTINGS should already be loaded from ini before globals.lua is executed + +XA_CABIN_LANGUAGE = XA_CABIN_SETTINGS.announcement.language or "en" +XA_CABIN_ACCENT = XA_CABIN_SETTINGS.announcement.accent or "gb" XA_CABIN_STATES = { flight_state = { @@ -54,14 +48,13 @@ XA_CABIN_STATES = { }, } - XA_CABIN_DATAREFS = {} XA_CABIN_PLANE_CONFIG = { DOOR = { dataref_str = 'sim/flightmodel2/misc/door_open_ratio', - operator = ">", - threshold = 0.9 + operator = ">=", + threshold = 0.5 }, LANDING_GEAR = { dataref_str = 'sim/flightmodel2/gear/deploy_ratio', @@ -69,8 +62,12 @@ XA_CABIN_PLANE_CONFIG = { threshold = 0 }, LANDING_LIGHTS = { - dataref_str = 'ckpt/oh/rwyTurnOff/anim', + dataref_str = 'sim/cockpit/electrical/landing_lights_on', operator = "==", threshold = 1 }, } + +-- Log the loaded language and accent for debugging +XA_CABIN_LOGGER.write_log("XA_CABIN_LANGUAGE set to: " .. XA_CABIN_LANGUAGE) +XA_CABIN_LOGGER.write_log("XA_CABIN_ACCENT set to: " .. XA_CABIN_ACCENT) \ No newline at end of file diff --git a/xa-cabin/helpers.lua b/xa-cabin/helpers.lua index 66e4b92..95fddec 100644 --- a/xa-cabin/helpers.lua +++ b/xa-cabin/helpers.lua @@ -1,36 +1,109 @@ local HELPERS = {} + +local previous_door_state = nil -- To track the previous state of the door + function HELPERS.is_door_open() - if XA_CABIN_DATAREFS.DOOR == nil then - XA_CABIN_DATAREFS.DOOR = dataref_table(XA_CABIN_PLANE_CONFIG.DOOR.dataref_str) - local funcCode = [[ - return function(x, debug) - if debug then - XA_CABIN_LOGGER.write_log('Dataref: ' .. x) - XA_CABIN_LOGGER.write_log('Debug: ' .. tostring(debug)) - end - return x]] .. XA_CABIN_PLANE_CONFIG.DOOR.operator .. XA_CABIN_PLANE_CONFIG.DOOR.threshold .. [[ + -- Initialize DOOR config and function only if they haven't been initialized already + if not XA_CABIN_PLANE_CONFIG.DOOR["Func"] then + XA_CABIN_LOGGER.write_log("Initializing DOOR config and function.") + + -- Initialize the DOOR dataref table if not done + if XA_CABIN_DATAREFS.DOOR == nil then + XA_CABIN_DATAREFS.DOOR = dataref_table(XA_CABIN_PLANE_CONFIG.DOOR.dataref_str) + XA_CABIN_LOGGER.write_log("DOOR Dataref initialized: " .. XA_CABIN_PLANE_CONFIG.DOOR.dataref_str) + end + + -- Validate operator and threshold + local operator = XA_CABIN_PLANE_CONFIG.DOOR.operator + local threshold = tonumber(XA_CABIN_PLANE_CONFIG.DOOR.threshold) + + if not operator or operator == "" then + XA_CABIN_LOGGER.write_log("Invalid operator for DOOR: " .. tostring(operator)) + return false + end + if not threshold then + XA_CABIN_LOGGER.write_log("Invalid threshold for DOOR: " .. tostring(threshold)) + return false + end + + -- Construct the function code to evaluate the door state + local funcCode = string.format([[ + return function(x) + return x %s %s end - ]] - XA_CABIN_PLANE_CONFIG.DOOR["Func"] = load(funcCode)() + ]], operator, threshold) + + -- Load the function + local func, loadError = load(funcCode) + if not func then + XA_CABIN_LOGGER.write_log("Error loading Func for DOOR: " .. tostring(loadError)) + XA_CABIN_PLANE_CONFIG.DOOR["Func"] = nil + return false + else + local success, execError = pcall(function() + XA_CABIN_PLANE_CONFIG.DOOR["Func"] = func() + end) + if not success then + XA_CABIN_LOGGER.write_log("Error executing Func for DOOR: " .. tostring(execError)) + XA_CABIN_PLANE_CONFIG.DOOR["Func"] = nil + return false + else + XA_CABIN_LOGGER.write_log("Successfully loaded Func for DOOR.") + end + end + end + + -- If the function is nil (which should not happen), assume door is closed + if not XA_CABIN_PLANE_CONFIG.DOOR["Func"] then + XA_CABIN_LOGGER.write_log("Func for DOOR is nil. Assuming door is closed.") + return false + end + + -- Evaluate the door state using the cached function + local door_value = XA_CABIN_DATAREFS.DOOR[0] + local is_open = XA_CABIN_PLANE_CONFIG.DOOR["Func"](door_value) + + -- Log state change only if it differs from previous state + if is_open ~= previous_door_state then + XA_CABIN_LOGGER.write_log("Door Dataref Value: " .. tostring(door_value)) + XA_CABIN_LOGGER.write_log("Comparison: x " .. XA_CABIN_PLANE_CONFIG.DOOR.operator .. " " .. tostring(XA_CABIN_PLANE_CONFIG.DOOR.threshold)) + XA_CABIN_LOGGER.write_log("Door state changed to: " .. tostring(is_open)) + previous_door_state = is_open end - return XA_CABIN_PLANE_CONFIG.DOOR["Func"](XA_CABIN_DATAREFS.DOOR[0], false) + + return is_open end -function HELPERS.is_landing_ligths_on() +function HELPERS.open_door() + -- Initialize a writable dataref for door control (not relying on XA_CABIN_DATAREFS.DOOR) + local writable_door_dataref = dataref_table("sim/cockpit2/switches/door_open") + + -- Check if the writable dataref is initialized correctly + if writable_door_dataref then + writable_door_dataref[0] = 1 -- Set the first door (index 0) to open + XA_CABIN_LOGGER.write_log("Writable door dataref set: sim/cockpit2/switches/door_open, index 0, value 1 (open)") + else + XA_CABIN_LOGGER.write_log("Failed to initialize writable door dataref.") + end +end + +function HELPERS.is_landing_lights_on() + -- Check if the landing lights dataref exists if XA_CABIN_DATAREFS.LANDING_LIGHTS == nil then - XA_CABIN_DATAREFS.LANDING_LIGHTS = dataref_table(XA_CABIN_PLANE_CONFIG.LANDING_LIGHTS.dataref_str) - local funcCode = [[ - return function(x, debug) - if debug then - XA_CABIN_LOGGER.write_log('Dataref: ' .. x) - XA_CABIN_LOGGER.write_log('Debug: ' .. tostring(debug)) - end - return x]] .. XA_CABIN_PLANE_CONFIG.LANDING_LIGHTS.operator .. XA_CABIN_PLANE_CONFIG.LANDING_LIGHTS.threshold .. [[ - end - ]] - XA_CABIN_PLANE_CONFIG.LANDING_LIGHTS["Func"] = load(funcCode)() + XA_CABIN_LOGGER.write_log("Error: LANDING_LIGHTS dataref is nil.") + return false end - return XA_CABIN_PLANE_CONFIG.LANDING_LIGHTS["Func"](XA_CABIN_DATAREFS.LANDING_LIGHTS[0], false) + + -- Check if the landing lights value is available + local landing_lights_value = XA_CABIN_DATAREFS.LANDING_LIGHTS[0] + if landing_lights_value == nil then + XA_CABIN_LOGGER.write_log("Error: Unable to retrieve LANDING_LIGHTS value.") + return false + end + + -- Log the landing lights state and return true if they are on (1) + XA_CABIN_LOGGER.write_log("Landing Lights State: " .. tostring(landing_lights_value)) + return landing_lights_value == 1 end -return HELPERS +return HELPERS \ No newline at end of file diff --git a/xa-cabin/state.lua b/xa-cabin/state.lua index fea80e5..ea4e0c3 100644 --- a/xa-cabin/state.lua +++ b/xa-cabin/state.lua @@ -1,360 +1,377 @@ -local STATE = { +-- Assuming ANNOUNCEMENTS is part of a module, require it: +ANNOUNCEMENTS = dofile(SCRIPT_DIRECTORY .. "/xa-cabin/announcement.lua") +STATE = { climb_counter = 0, cruise_counter = 0, descend_counter = 0, - bording_delay_counter = 0, + boarding_delay_counter = 0, + last_state_change_time = os.clock(), -- Reentrancy guard + debounce_threshold = 0, -- Seconds to wait between state changes } +-- Initialize datarefs only once function STATE.initialize_states() + -- Initialize datarefs only once if XA_CABIN_DATAREFS.GS == nil then XA_CABIN_DATAREFS.GS = dataref_table('sim/flightmodel/position/groundspeed') end if XA_CABIN_DATAREFS.ALT_AGL == nil then XA_CABIN_DATAREFS.ALT_AGL = dataref_table('sim/flightmodel/position/y_agl') end + if XA_CABIN_DATAREFS.N1 == nil then + XA_CABIN_DATAREFS.N1 = dataref_table('sim/cockpit2/engine/indicators/N1_percent') + end if XA_CABIN_DATAREFS.ONGROUND == nil then XA_CABIN_DATAREFS.ONGROUND = dataref_table('sim/flightmodel/failures/onground_any') end - + if XA_CABIN_DATAREFS.GEAR_DEPLOY == nil then + XA_CABIN_DATAREFS.GEAR_DEPLOY = dataref_table('sim/flightmodel2/gear/deploy_ratio') + end + -- Initialize GEAR_FORCE dataref + if XA_CABIN_DATAREFS.GEAR_FORCE == nil then + XA_CABIN_LOGGER.write_log("Initializing GEAR_FORCE Dataref.") + XA_CABIN_DATAREFS.GEAR_FORCE = dataref_table('sim/flightmodel/forces/fnrml_gear') + end + if XA_CABIN_DATAREFS.VS == nil then + XA_CABIN_LOGGER.write_log("Initializing VS Dataref.") + XA_CABIN_DATAREFS.VS = dataref_table('sim/flightmodel/position/vh_ind_fpm') + end + local on_ground = XA_CABIN_DATAREFS.ONGROUND[0] == 1 local gs = XA_CABIN_DATAREFS.GS[0] local alt_agl = XA_CABIN_DATAREFS.ALT_AGL[0] - - -- Check if the aircraft is on the runway (e.g., ground speed zero, on ground, at runway heading) - if on_ground and gs < 1 and alt_agl < 10 then - -- Aircraft is on the ground, but we need to determine if it's at the gate or runway - -- For simplicity, let's assume that if the door is closed, we're on the runway ready for takeoff - if not HELPERS.is_door_open() then - -- Set flight state to 'takeoff' - XA_CABIN_STATES.flight_state = { - parked = false, - taxi_out = false, - takeoff = true, - climb = false, - cruise = false, - descent = false, - approach = false, - taxi_in = false, - current_state = "takeoff" - } - XA_CABIN_LOGGER.write_log("Flight started from the runway. Setting flight state to 'takeoff'.") - - -- Set cabin state to 'takeoff' or the appropriate state - XA_CABIN_STATES.cabin_state = { - pre_boarding = false, - boarding = false, - boarding_complete = false, - safety_demonstration = false, - takeoff = true, - climb = false, - cruise = false, - prepare_for_landing = false, - final_approach = false, - post_landing = false, - current_state = "takeoff" - } - XA_CABIN_LOGGER.write_log("Cabin state set to 'takeoff' due to runway start.") - else - -- If door is open, assume we're at the gate - XA_CABIN_LOGGER.write_log("Door is open. Assuming aircraft is parked at the gate.") + local engine_n1 = XA_CABIN_DATAREFS.N1[0] + local gear_deploy = XA_CABIN_DATAREFS.GEAR_DEPLOY[0] -- Gear deployment ratio (1 = down, 0 = up) + + -- Determine initial flight phase based on conditions + if on_ground then + if gs < 1 and engine_n1 < 30 and alt_agl < 10 then + -- Aircraft is parked + if HELPERS.is_door_open() then + XA_CABIN_LOGGER.write_log("Aircraft is parked at the gate.") + STATE.change_flight_state("parked", "Aircraft is on the ground, engines off, doors open") + STATE.change_cabin_state("pre_boarding", "Doors are open at the gate") + else + XA_CABIN_LOGGER.write_log("Aircraft is parked on the runway.") + STATE.change_flight_state("parked", "Aircraft is on the ground, engines off, doors closed") + STATE.change_cabin_state("boarding_complete", "Ready for taxi") + end + elseif gs > 1 and gs < 30 then + -- Aircraft is taxiing + XA_CABIN_LOGGER.write_log("Aircraft is taxiing.") + STATE.change_flight_state("taxi_out", "Ground speed indicates taxi phase") + STATE.change_cabin_state("safety_demonstration", "Safety demo during taxi") + elseif gs > 30 and alt_agl < 10 and engine_n1 > 60 then + -- Aircraft is preparing for takeoff (on the runway) + XA_CABIN_LOGGER.write_log("Aircraft is on the runway, preparing for takeoff.") + STATE.change_flight_state("takeoff", "High N1 and high ground speed, preparing for takeoff") + STATE.change_cabin_state("takeoff", "Takeoff initiated") + end + else + -- Aircraft is in the air + if alt_agl > 800 and gs > 50 then + -- Aircraft is in flight, at cruise + XA_CABIN_LOGGER.write_log("Aircraft is in cruise flight.") + STATE.change_flight_state("cruise", "Aircraft is in flight, stable altitude") + STATE.change_cabin_state("cruise", "Cruise altitude reached") + elseif alt_agl < 800 and gear_deploy > 0 then + -- Aircraft is descending for approach + XA_CABIN_LOGGER.write_log("Aircraft is in approach phase.") + STATE.change_flight_state("approach", "Aircraft descending, gear deployed") + STATE.change_cabin_state("prepare_for_landing", "Approach started, preparing for landing") + elseif alt_agl < 100 and gear_deploy > 0 then + -- Aircraft is landing + XA_CABIN_LOGGER.write_log("Aircraft is landing.") + STATE.change_flight_state("landing", "Low altitude, gear deployed, preparing to land") end end + + -- Call the original door and system checks + XA_CABIN_LOGGER.write_log("Flight phase initialization complete.") + + -- Play the appropriate announcement after initialization + local announcement_name = cabin_state_to_announcement_name(XA_CABIN_STATES.cabin_state.current_state) + if announcement_name then + ANNOUNCEMENTS.play_sound(announcement_name) + else + XA_CABIN_LOGGER.write_log("No announcement found for cabin state: " .. XA_CABIN_STATES.cabin_state.current_state) + end end -function change_flight_state(new_state) - if XA_CABIN_STATES.flight_state[new_state] == nil then - logMsg("Invalid flight state: " .. new_state) - return +function STATE.change_flight_state(new_state, condition_reason) + local current_time = os.clock() + XA_CABIN_LOGGER.write_log("Attempting to change flight state. Current state: " .. XA_CABIN_STATES.flight_state.current_state .. ", New state: " .. new_state) + + if XA_CABIN_STATES.flight_state.current_state ~= new_state and (current_time - STATE.last_state_change_time > STATE.debounce_threshold) then + XA_CABIN_LOGGER.write_log("Flight state changed to: " .. new_state .. ". Reason: " .. condition_reason) + XA_CABIN_STATES.flight_state[XA_CABIN_STATES.flight_state.current_state] = false + XA_CABIN_STATES.flight_state[new_state] = true + XA_CABIN_STATES.flight_state.current_state = new_state + STATE.last_state_change_time = current_time -- Update debounce timer here + else + XA_CABIN_LOGGER.write_log("Debounce active or state unchanged. Skipping flight state change. Time since last change: " .. (current_time - STATE.last_state_change_time) .. " seconds.") end - XA_CABIN_STATES.flight_state[XA_CABIN_STATES.flight_state.current_state] = false - XA_CABIN_STATES.flight_state[new_state] = true - XA_CABIN_STATES.flight_state.current_state = new_state - XA_CABIN_LOGGER.write_log("Flight state changed to: " .. new_state) end function STATE.update_flight_state() - -- process PARKED state if XA_CABIN_STATES.flight_state.current_state == "parked" then - if XA_CABIN_DATAREFS.GS == nil then - XA_CABIN_DATAREFS.GS = dataref_table('sim/flightmodel/position/groundspeed') - end - if XA_CABIN_DATAREFS.GEAR_FORCE == nil then - XA_CABIN_DATAREFS.GEAR_FORCE = dataref_table('sim/flightmodel/forces/fnrml_gear') - end - if XA_CABIN_DATAREFS.GS[0] > 5 / 1.9 and XA_CABIN_DATAREFS.GEAR_FORCE[0] > 1 then - change_flight_state("taxi_out") + STATE.change_flight_state("taxi_out", "Ground speed > 5 and gear force > 1 (start taxi)") + STATE.change_cabin_state("safety_demonstration", "Safety demo starts with taxi") end return end - -- process TAXI_OUT state if XA_CABIN_STATES.flight_state.current_state == "taxi_out" then if XA_CABIN_DATAREFS.N1 == nil then XA_CABIN_DATAREFS.N1 = dataref_table('sim/cockpit2/engine/indicators/N1_percent') end - if XA_CABIN_DATAREFS.N1[0] > 75 then - change_flight_state("takeoff") + if XA_CABIN_DATAREFS.N1[0] > 60 then + STATE.change_flight_state("takeoff", "N1 > 60% (starting takeoff)") + STATE.change_cabin_state("takeoff", "Takeoff initiated") end return end - -- process TAKEOFF state if XA_CABIN_STATES.flight_state.current_state == "takeoff" then if XA_CABIN_DATAREFS.VS == nil then + XA_CABIN_LOGGER.write_log("Initializing Vertical Speed (VS) Dataref") XA_CABIN_DATAREFS.VS = dataref_table('sim/flightmodel/position/vh_ind_fpm') end if XA_CABIN_DATAREFS.GEAR_FORCE == nil then + XA_CABIN_LOGGER.write_log("Initializing Gear Force Dataref") XA_CABIN_DATAREFS.GEAR_FORCE = dataref_table('sim/flightmodel/forces/fnrml_gear') end + -- Log the values of VS and GEAR_FORCE for debugging + XA_CABIN_LOGGER.write_log("Current Vertical Speed (VS): " .. tostring(XA_CABIN_DATAREFS.VS[0])) + XA_CABIN_LOGGER.write_log("Current Gear Force: " .. tostring(XA_CABIN_DATAREFS.GEAR_FORCE[0])) + + -- Check if the vertical speed is high enough and the gear force is low (indicating flight) if XA_CABIN_DATAREFS.VS[0] > 200 and XA_CABIN_DATAREFS.GEAR_FORCE[0] < 1 then - change_flight_state("climb") + local reason = "Vertical Speed is above 200 fpm and Gear Force is below 1" + XA_CABIN_LOGGER.write_log("Conditions met for climb: " .. reason) + STATE.change_flight_state("climb", reason) + STATE.change_cabin_state("climb", reason) + else + XA_CABIN_LOGGER.write_log("Conditions not met for climb.") end return end - -- process CLIMB state if XA_CABIN_STATES.flight_state.current_state == "climb" then - if XA_CABIN_DATAREFS.VS == nil then - XA_CABIN_DATAREFS.VS = dataref_table('sim/flightmodel/position/vh_ind_fpm') - end + local vs = XA_CABIN_DATAREFS.VS[0] - if XA_CABIN_DATAREFS.VS[0] > -500 and XA_CABIN_DATAREFS.VS[0] < 500 then - STATE.cruise_counter = STATE.cruise_counter + 1 + if vs > -500 and vs < 500 then + STATE.cruise_counter = math.min(STATE.cruise_counter + 1, 30) else STATE.cruise_counter = 0 end - if XA_CABIN_DATAREFS.VS[0] < -500 then - STATE.descend_counter = STATE.descend_counter + 1 + if vs < -500 then + STATE.descend_counter = math.min(STATE.descend_counter + 1, 30) else STATE.descend_counter = 0 end if STATE.cruise_counter > 15 then - change_flight_state("cruise") + STATE.change_flight_state("cruise", "Vertical Speed stabilized near 0") + STATE.change_cabin_state("cruise", "Aircraft reached cruise altitude") return end - if STATE.descend_counter > 15 then - change_flight_state("descent") + if STATE.descend_counter > 10 then + STATE.change_flight_state("descent", "Vertical Speed negative for extended period (starting descent)") + STATE.change_cabin_state("prepare_for_landing", "Descent started") return end return end - -- process CRUISE state if XA_CABIN_STATES.flight_state.current_state == "cruise" then - if XA_CABIN_DATAREFS.VS == nil then - XA_CABIN_DATAREFS.VS = dataref_table('sim/flightmodel/position/vh_ind_fpm') - end + local vs = XA_CABIN_DATAREFS.VS[0] - if XA_CABIN_DATAREFS.VS[0] > 500 then - STATE.climb_counter = STATE.climb_counter + 1 + if vs > 500 then + STATE.climb_counter = math.min(STATE.climb_counter + 1, 30) else STATE.climb_counter = 0 end - if XA_CABIN_DATAREFS.VS[0] < -500 then - STATE.descend_counter = STATE.descend_counter + 1 + if vs < -500 then + STATE.descend_counter = math.min(STATE.descend_counter + 1, 30) else STATE.descend_counter = 0 end if STATE.climb_counter > 30 then - change_flight_state("climb") + STATE.change_flight_state("climb", "Vertical Speed > 500 (resuming climb)") + STATE.change_cabin_state("climb", "Resuming climb") return end if STATE.descend_counter > 30 then - change_flight_state("descent") + STATE.change_flight_state("descent", "Vertical Speed < -500 (starting descent)") + STATE.change_cabin_state("prepare_for_landing", "Preparing for descent") return end return end - -- process DESCENT state if XA_CABIN_STATES.flight_state.current_state == "descent" then - if XA_CABIN_DATAREFS.VS == nil then - XA_CABIN_DATAREFS.VS = dataref_table('sim/flightmodel/position/vh_ind_fpm') - end - if XA_CABIN_DATAREFS.AGL == nil then - XA_CABIN_DATAREFS.AGL = dataref_table('sim/flightmodel/position/y_agl') - end - if XA_CABIN_DATAREFS.GEAR_FORCE == nil then - XA_CABIN_DATAREFS.GEAR_FORCE = dataref_table('sim/flightmodel/forces/fnrml_gear') - end + local vs = XA_CABIN_DATAREFS.VS[0] + local agl = XA_CABIN_DATAREFS.ALT_AGL[0] + local gear_force = XA_CABIN_DATAREFS.GEAR_FORCE[0] - if XA_CABIN_DATAREFS.VS[0] > 500 then - STATE.climb_counter = STATE.climb_counter + 1 + if vs > 500 then + STATE.climb_counter = math.min(STATE.climb_counter + 1, 30) else STATE.climb_counter = 0 end - if XA_CABIN_DATAREFS.VS[0] < 500 and XA_CABIN_DATAREFS.VS[0] > -500 then - STATE.cruise_counter = STATE.cruise_counter + 1 + if vs < 500 and vs > -500 then + STATE.cruise_counter = math.min(STATE.cruise_counter + 1, 30) else STATE.cruise_counter = 0 end if STATE.climb_counter > 15 then - change_flight_state("climb") + STATE.change_flight_state("climb", "Climb conditions met during descent") + STATE.change_cabin_state("climb", "Returning to climb") return end if STATE.cruise_counter > 15 then - change_flight_state("cruise") + STATE.change_flight_state("cruise", "Cruise conditions met during descent") + STATE.change_cabin_state("cruise", "Returning to cruise") return end - if XA_CABIN_DATAREFS.AGL[0] < 800 and XA_CABIN_DATAREFS.GEAR_FORCE[0] < 5 and XA_CABIN_DATAREFS.VS[0] < -200 then - change_flight_state("approach") - return + if agl < 800 and gear_force < 5 and vs < -200 then + STATE.change_flight_state("approach", "Altitude < 800 AGL and descending") end return end - -- process APPROACH state if XA_CABIN_STATES.flight_state.current_state == "approach" then - if XA_CABIN_DATAREFS.GS == nil then - XA_CABIN_DATAREFS.GS = dataref_table('sim/flightmodel/position/groundspeed') - end - if XA_CABIN_DATAREFS.GEAR_FORCE == nil then - XA_CABIN_DATAREFS.GEAR_FORCE = dataref_table('sim/flightmodel/forces/fnrml_gear') - end + local gs = XA_CABIN_DATAREFS.GS[0] + local gear_force = XA_CABIN_DATAREFS.GEAR_FORCE[0] - if XA_CABIN_DATAREFS.GS[0] < 50 / 1.9 and XA_CABIN_DATAREFS.GEAR_FORCE[0] > 10 then - change_flight_state("taxi_in") + if gs < 50 / 1.9 and gear_force > 10 then + STATE.change_flight_state("taxi_in", "Groundspeed < 50 and gear force high (landing complete)") end return end - -- process TAXI_IN state if XA_CABIN_STATES.flight_state.current_state == "taxi_in" then - if XA_CABIN_DATAREFS.GS == nil then - XA_CABIN_DATAREFS.GS = dataref_table('sim/flightmodel/position/groundspeed') - end - if XA_CABIN_DATAREFS.N1 == nil then - XA_CABIN_DATAREFS.N1 = dataref_table('sim/cockpit2/engine/indicators/N1_percent') - end + local gs = XA_CABIN_DATAREFS.GS[0] + local n1 = XA_CABIN_DATAREFS.N1[0] - if XA_CABIN_DATAREFS.GS[0] < 1 / 1.9 and XA_CABIN_DATAREFS.N1[0] > 15 then - change_flight_state("parked") + if gs < 1 / 1.9 and n1 > 15 then + STATE.change_flight_state("parked", "Aircraft stopped after landing") end return end end -function change_cabin_state(new_state) - if XA_CABIN_STATES.cabin_state[new_state] == nil then - logMsg("Invalid Cabin state: " .. new_state) +function STATE.change_cabin_state(new_state, condition_reason) + condition_reason = condition_reason or "Unknown reason" + if XA_CABIN_STATES.cabin_state.current_state == new_state then + XA_CABIN_LOGGER.write_log("Cabin state is already " .. new_state .. ". No change needed.") return end - XA_CABIN_STATES.cabin_state[XA_CABIN_STATES.cabin_state.current_state] = false - XA_CABIN_STATES.cabin_state[new_state] = true - XA_CABIN_STATES.cabin_state.current_state = new_state - if XA_CABIN_SETTINGS.mode.automated then - local announcement_name = cabin_state_to_announcement_name(new_state) - if announcement_name then - ANNOUNCEMENTS.play_sound(announcement_name) - XA_CABIN_LOGGER.write_log("Playing announcement: " .. announcement_name) - else - XA_CABIN_LOGGER.write_log("No announcement found for cabin state: " .. new_state) + + local current_time = os.clock() + + if (current_time - STATE.last_state_change_time > STATE.debounce_threshold) then + XA_CABIN_LOGGER.write_log("Cabin state changing to: " .. new_state .. ". Reason: " .. condition_reason) + XA_CABIN_STATES.cabin_state[XA_CABIN_STATES.cabin_state.current_state] = false + XA_CABIN_STATES.cabin_state[new_state] = true + XA_CABIN_STATES.cabin_state.current_state = new_state + STATE.last_state_change_time = current_time + + if XA_CABIN_SETTINGS.mode.automated then + local announcement_name = cabin_state_to_announcement_name(new_state) + if announcement_name then + ANNOUNCEMENTS.stopSounds() + ANNOUNCEMENTS.play_sound(announcement_name) + XA_CABIN_LOGGER.write_log("Playing announcement: " .. announcement_name) + else + XA_CABIN_LOGGER.write_log("No announcement found for cabin state: " .. new_state) + end end + else + XA_CABIN_LOGGER.write_log("Debounce active. Skipping cabin state change to: " .. new_state) end - XA_CABIN_LOGGER.write_log("Cabin state changed to: " .. new_state) end function STATE.update_cabin_state() if XA_CABIN_STATES.cabin_state.current_state == "pre_boarding" then - if HELPERS.is_door_open() and XA_CABIN_STATES.flight_state.parked then - STATE.bording_delay_counter = STATE.bording_delay_counter + 1 - - -- Generate and store the random delay threshold once - if not STATE.boarding_delay_threshold then - STATE.boarding_delay_threshold = math.random(90, 120) - XA_CABIN_LOGGER.write_log("Boarding delay threshold set to: " .. STATE.boarding_delay_threshold) - end - - -- Compare the counter against the stored threshold - if STATE.bording_delay_counter > STATE.boarding_delay_threshold then - change_cabin_state("boarding") - -- Reset counter and threshold for future use - STATE.bording_delay_counter = 0 - STATE.boarding_delay_threshold = nil - end - else - -- Reset counter and threshold if conditions are not met - STATE.bording_delay_counter = 0 - STATE.boarding_delay_threshold = nil + if not HELPERS.is_door_open() and XA_CABIN_STATES.cabin_state.current_state ~= "boarding_complete" then + XA_CABIN_LOGGER.write_log("Doors closed during pre-boarding. Transitioning to 'boarding_complete'.") + STATE.change_cabin_state("boarding_complete", "Doors closed during pre-boarding") end return end if XA_CABIN_STATES.cabin_state.current_state == "boarding" then - if not HELPERS.is_door_open() and not XA_CABIN_STATES.flight_state.taxi_out then - change_cabin_state("boarding_complete") + if not HELPERS.is_door_open() then + XA_CABIN_LOGGER.write_log("Doors closed before boarding completed. Transitioning to 'boarding_complete'.") + STATE.change_cabin_state("boarding_complete", "Doors closed before boarding completed") end return end if XA_CABIN_STATES.cabin_state.current_state == "boarding_complete" then if XA_CABIN_STATES.flight_state.taxi_out then - change_cabin_state("safety_demonstration") + STATE.change_cabin_state("safety_demonstration", "Taxi started, safety demo begins") end return end - if XA_CABIN_STATES.cabin_state.current_state == "safety_demonstration" then - if HELPERS.is_landing_ligths_on() and XA_CABIN_STATES.flight_state.taxi_out then - change_cabin_state("takeoff") - end - return + if XA_CABIN_STATES.flight_state.current_state == "takeoff" and + XA_CABIN_STATES.cabin_state.current_state == "safety_demonstration" then + STATE.change_cabin_state("takeoff", "Takeoff initiated, safety demo completed") end - if XA_CABIN_STATES.cabin_state.current_state == "takeoff" then - if XA_CABIN_DATAREFS.AGL == nil then - XA_CABIN_DATAREFS.AGL = dataref_table('sim/flightmodel/position/y_agl') - end - if XA_CABIN_STATES.flight_state.climb and XA_CABIN_DATAREFS.AGL[0] > 1000 then - change_cabin_state("climb") + if XA_CABIN_STATES.cabin_state.current_state == "safety_demonstration" then + if ANNOUNCEMENTS.is_safety_demo_done then + STATE.change_cabin_state("takeoff", "Safety demo completed") end return end if XA_CABIN_STATES.cabin_state.current_state == "climb" then if XA_CABIN_STATES.flight_state.cruise then - change_cabin_state("cruise") + STATE.change_cabin_state("cruise", "Aircraft reached cruise altitude") end return end if XA_CABIN_STATES.cabin_state.current_state == "cruise" then if XA_CABIN_STATES.flight_state.descent then - change_cabin_state("prepare_for_landing") + STATE.change_cabin_state("prepare_for_landing", "Descent started") end return end if XA_CABIN_STATES.cabin_state.current_state == "prepare_for_landing" then if XA_CABIN_STATES.flight_state.approach then - change_cabin_state("final_approach") + STATE.change_cabin_state("final_approach", "Final approach started") end return end if XA_CABIN_STATES.cabin_state.current_state == "final_approach" then if XA_CABIN_STATES.flight_state.taxi_in then - change_cabin_state("post_landing") + STATE.change_cabin_state("post_landing", "Landing completed") end return end if XA_CABIN_STATES.cabin_state.current_state == "post_landing" then if XA_CABIN_STATES.flight_state.parked then - change_cabin_state("pre_boarding") + STATE.change_cabin_state("pre_boarding", "Aircraft parked, ready for next boarding") end return end @@ -371,13 +388,13 @@ function announcement_name_to_cabin_state(announcement_name) "cruise", "prepare_for_landing", "final_approach", - "post_landing" + "post_landing", + "emergency" } - for _, cabin_state in ipairs(cabin_states) do - local ann_name = cabin_state_to_announcement_name(cabin_state) + for index, ann_name in ipairs(XA_CABIN_ANNOUNCEMENT_STATES) do if ann_name == announcement_name then - return cabin_state + return cabin_states[index] end end @@ -386,29 +403,30 @@ function announcement_name_to_cabin_state(announcement_name) end function cabin_state_to_announcement_name(cabin_state) - if cabin_state == "pre_boarding" then - return XA_CABIN_ANNOUNCEMENT_STATES[1] - elseif cabin_state == "boarding" then - return XA_CABIN_ANNOUNCEMENT_STATES[2] - elseif cabin_state == "boarding_complete" then - return XA_CABIN_ANNOUNCEMENT_STATES[3] - elseif cabin_state == "safety_demonstration" then - return XA_CABIN_ANNOUNCEMENT_STATES[4] - elseif cabin_state == "takeoff" then - return XA_CABIN_ANNOUNCEMENT_STATES[5] - elseif cabin_state == "climb" then - return XA_CABIN_ANNOUNCEMENT_STATES[6] - elseif cabin_state == "cruise" then - return XA_CABIN_ANNOUNCEMENT_STATES[7] - elseif cabin_state == "prepare_for_landing" then - return XA_CABIN_ANNOUNCEMENT_STATES[8] - elseif cabin_state == "final_approach" then - return XA_CABIN_ANNOUNCEMENT_STATES[9] - elseif cabin_state == "post_landing" then - return XA_CABIN_ANNOUNCEMENT_STATES[10] + local cabin_states = { + "pre_boarding", + "boarding", + "boarding_complete", + "safety_demonstration", + "takeoff", + "climb", + "cruise", + "prepare_for_landing", + "final_approach", + "post_landing", + "emergency" + } + + for index, state in ipairs(cabin_states) do + if state == cabin_state then + return XA_CABIN_ANNOUNCEMENT_STATES[index] + end end -end + XA_CABIN_LOGGER.write_log("Unknown cabin state: " .. tostring(cabin_state)) + return nil +end +ANNOUNCEMENTS.loadSounds() STATE.initialize_states() - -return STATE +ANNOUNCEMENTS.loadSounds() +return STATE \ No newline at end of file diff --git a/xa-cabin/state.md b/xa-cabin/state.md new file mode 100644 index 0000000..b421111 --- /dev/null +++ b/xa-cabin/state.md @@ -0,0 +1,120 @@ +XA Cabin Flight + +This script handles various states of flight and cabin operations in an aircraft simulation environment. It monitors flight status (e.g., parked, takeoff, cruise) and cabin activities (e.g., boarding, safety demonstration) and triggers state changes based on certain conditions. + +Features + + • Flight Phases: Automatically detects the current flight phase (e.g., parked, takeoff, climb) and transitions between phases based on conditions such as speed, altitude, and engine power. + • Cabin States: Tracks cabin activities like boarding, safety demonstrations, and announcements, synchronizing them with flight phases. + • Debouncing: Prevents rapid state changes with a configurable debounce threshold. + • Logging: Detailed logs for debugging and auditing state transitions. + • Automated Announcements: Plays cabin announcements based on flight and cabin states, such as boarding complete, takeoff, and landing preparation. + +Flight States + +The script tracks the following flight states to manage transitions during the flight: + + 1. Parked + • Initial state when the aircraft is on the ground with engines off or at idle and the parking brake applied. + • Triggers: + • Ground speed < 1 m/s, altitude AGL < 10 ft, engine N1 < 30%. + • If doors are open, the state transitions to pre_boarding. If doors are closed, it transitions to boarding_complete. + 2. Taxi Out + • The aircraft is preparing for takeoff and moving on the ground. + • Triggers: + • Ground speed > 5 m/s and gear force > 1. + • Engine N1 > 75%. + 3. Takeoff + • The aircraft is accelerating for takeoff. + • Triggers: + • N1 exceeds 75% and vertical speed (VS) > 200 ft/min, gear force < 1. + 4. Climb + • The aircraft is climbing after takeoff. + • Triggers: + • Vertical speed (VS) remains stable between -500 and +500 ft/min for an extended period. + 5. Cruise + • The aircraft has reached cruising altitude and maintains stable vertical speed. + • Triggers: + • VS is stable between -500 and +500 ft/min for over 15 consecutive checks. + 6. Descent + • The aircraft starts descending toward the destination. + • Triggers: + • VS < -500 ft/min for over 15 consecutive checks. + 7. Approach + • The aircraft is in the approach phase, preparing for landing. + • Triggers: + • Altitude AGL < 800 feet, gear deployed, VS < -200 ft/min. + 8. Taxi In + • The aircraft has landed and is taxiing to the gate. + • Triggers: + • Ground speed < 50/1.9 m/s and gear force > 10. + 9. Parked (After Taxi In) + • The aircraft is parked at the gate after landing. + • Triggers: + • Ground speed < 1/1.9 m/s and N1 > 15%. + +Cabin States + +The script tracks cabin states to manage activities like boarding, safety demonstrations, and announcements. Cabin state transitions are triggered by flight state changes and door status: + + 1. Pre-Boarding + • Initial state before passengers board. + • Triggers: + • Doors open, flight parked. + 2. Boarding + • Passengers are boarding. + • Triggers: + • Random delay after doors open and flight is parked. + 3. Boarding Complete + • Boarding is complete, and preparations for departure begin. + • Triggers: + • Doors closed, aircraft ready for taxi-out. + 4. Safety Demonstration + • Safety demonstration announcement. + • Triggers: + • Taxi-out begins, landing lights on. + 5. Takeoff (Cabin) + • Cabin secured for takeoff. + • Triggers: + • Takeoff initiated after safety demo. + 6. Climb (Cabin) + • Cabin is in the climb phase. + • Triggers: + • The flight enters the climb phase. + 7. Cruise (Cabin) + • Cabin is in the cruise phase. + • Triggers: + • The flight reaches the cruise phase. + 8. Prepare for Landing + • Cabin prepares for landing. + • Triggers: + • The flight enters descent. + 9. Final Approach + • Cabin secured for landing. + • Triggers: + • The flight enters the approach phase. + 10. Post Landing + + • Post-landing procedures. + • Triggers: + • The flight state changes to taxi-in after landing. + +State Change Debouncing + +To prevent frequent state changes due to transient conditions, the script implements a debouncing mechanism: + + • States cannot change more than once within a configurable time (default: 5 seconds). + • This helps avoid rapid, recursive state changes that could destabilize the simulation. + +Logging + +All state transitions and actions are logged using XA_CABIN_LOGGER for debugging and auditing purposes. These logs help track the flow of state changes throughout the flight and cabin procedures. + +Usage + +To use the script, ensure that the required datarefs are properly initialized, and the announcements are correctly loaded. The script will automatically detect and manage flight and cabin states based on flight conditions and cabin operations. + +Announcements + +Announcements are played according to cabin state transitions. Make sure to include the appropriate sound files in the designated directory for each state. If a sound file is not found, a log entry will indicate the missing announcement. + From 5b75b5c9d409c1bb3845ed4894222ea6ea92e2f8 Mon Sep 17 00:00:00 2001 From: Ricardo Borenstein Date: Sun, 13 Oct 2024 11:53:08 +0200 Subject: [PATCH 04/11] adding and refresing docs --- README.md | 146 ++++++++++++---------- xa-cabin/docs/announcements.md | 154 ++++++++++++++++++++++++ xa-cabin/docs/generate_announcements.md | 103 ++++++++++++++++ xa-cabin/docs/state.md | 120 ++++++++++++++++++ xa-cabin/docs/xa-cabin.md | 121 +++++++++++++++++++ 5 files changed, 582 insertions(+), 62 deletions(-) create mode 100644 xa-cabin/docs/announcements.md create mode 100644 xa-cabin/docs/generate_announcements.md create mode 100644 xa-cabin/docs/state.md create mode 100644 xa-cabin/docs/xa-cabin.md diff --git a/README.md b/README.md index 2651421..e295720 100644 --- a/README.md +++ b/README.md @@ -1,31 +1,29 @@ -# AI Powered Cabin Announcement +AI Powered Cabin Announcement -The AI Powered Cabin Announcement plugin is a powerful tool that enhances the cabin announcement system in aircraft. It utilizes advanced artificial intelligence algorithms to generate high-quality and natural-sounding announcements. +The AI Powered Cabin Announcement plugin enhances the cabin announcement system for flight simulations by utilizing cutting-edge artificial intelligence algorithms to generate high-quality, natural-sounding announcements. -Compared to older plugins, the AI Powered Cabin Announcement plugin offers several advantages. Firstly, it provides more realistic and human-like voice synthesis, resulting in a more immersive and engaging experience for passengers. The AI algorithms used in this plugin have been trained on vast amounts of data, enabling them to produce highly accurate and expressive speech. +Compared to older plugins, the AI Powered Cabin Announcement offers several advantages: -Additionally, the plugin offers greater flexibility and customization options. It allows for easy configuration of both global settings and specific settings for different aircraft types. This ensures that the announcements are tailored to the unique characteristics of each aircraft, enhancing the overall passenger experience. + 1. Human-like Voice Synthesis: The AI engine generates highly realistic and expressive speech, resulting in a more immersive experience for passengers. + 2. Flexibility and Customization: The plugin allows easy configuration of both global and aircraft-specific settings, enabling tailored announcements based on each aircraft’s unique characteristics. + 3. Sound Pack Integration: Airlines can incorporate their own branding and unique audio elements into announcements, creating a distinctive cabin environment. + 4. Dynamic Announcement Generation: The plugin now supports real-time generation of announcements using generate_announcements.py. This feature adapts announcements based on dynamic factors like flight status, weather conditions, and passenger information, keeping them relevant throughout the flight. -Furthermore, the plugin supports the addition of sound packs, allowing airlines to incorporate their own branding and unique audio elements into the announcements. This level of customization helps to create a distinct and memorable cabin environment for passengers. +Overall, the AI Powered Cabin Announcement plugin revolutionizes the in-flight announcement experience by leveraging AI technology to deliver superior audio quality, customization, and dynamic real-time generation. -Lastly, the plugin includes a live generation feature, which is currently a work in progress (TODO). This feature will enable real-time generation of announcements based on dynamic factors such as flight status, weather conditions, and passenger demographics. This ensures that the announcements remain relevant and up-to-date throughout the flight. +Installation -Overall, the AI Powered Cabin Announcement plugin revolutionizes the cabin announcement system by leveraging AI technology to deliver superior audio quality, customization options, and dynamic generation capabilities. + Note: FlyWithLua is required. -# Installation +Follow these steps to install the plugin: -> **NOTE:** FlyWithLua is required + 1. Download the plugin files. + 2. Unzip the downloaded file. + 3. Navigate to the FlyWithLua/Scripts folder in your flight simulator directory. + 4. Copy all the files and folders from the unzipped plugin folder into the FlyWithLua/Scripts folder. -To install the plugin, follow these steps: +After installation, the file structure should look like this: -1. Download the plugin files. -2. Unzip the downloaded file. -3. Locate the `FlyWithLua/Scripts` folder in your flight simulator installation directory. -4. Copy all the files and folders from the unzipped plugin folder into the `FlyWithLua/Scripts` folder. - -After the installation, the file structure should look like this: - -``` Resources ... |- plugins @@ -37,73 +35,97 @@ After the installation, the file structure should look like this: |- xa-cabin xa-cabin.lua xa-cabin.ini -``` - -# Configuration -## Global Config -The configuration is stored in xa-cabin.ini. Most configurations are avialable in the GUI -here are the available options: +Configuration -[simbrief] section: (also in GUI) -- username: This option allows you to specify a username for SimBrief, which is a flight planning tool. You can set a value for this option to provide your SimBrief username. +Global Configurations -[mode] section: (also in GUI) +The main configuration file is xa-cabin.ini, where most options can also be modified through the in-game GUI. -- automated: This option is set to true, indicating that the cabin announcements will be played automatically without any manual intervention. -- live: This option is also set to true, indicating that the announcements will be generated in real-time. (WIP) - -[announcement] section: +[simbrief] section (also configurable in the GUI): -- language: This option allows you to specify the language for the cabin announcements. In this case, it is set to en for English. -- accent: This option allows you to specify the accent for the cabin announcements. In this case, it is set to in for Indian accent. -- speaker: This option allows you to specify the speaker for the cabin announcements. In this case, it is set to 01. + • username: Specify your SimBrief username to integrate flight planning data. -## Aircraft Config -A file named `xa-cabin.ini` is automatically created the first time you load that plane. The defaut configuration is using XPlane's default dataref which might not work for a lot of 3rd pary planes. You will need to configure it for each plane. If you want to run manually mode, this is not required. +[mode] section (also configurable in the GUI): -[LANDING_GEAR]: + • automated: Set to true to play cabin announcements automatically. + • live: Set to true to enable real-time announcement generation using generate_announcements.py. -- operator: This option specifies the comparison operator used to evaluate the condition. In this case, it is set to ~=, which means "approximately equal to". +[announcement] section: -- threshold: This option sets the threshold value for the condition. Here, it is set to 0. -- dataref_str: This option specifies the data reference string, which is a reference to a specific data value in the simulation. In this case, it is set to sim/flightmodel2/gear/deploy_ratio. + • language: Specifies the language of the announcements (e.g., en for English). + • accent: Specifies the accent for the announcements (e.g., in for Indian accent). + • speaker: Sets the speaker ID (e.g., 01). -[DOOR]: +Aircraft-Specific Configuration -- operator: This option specifies the comparison operator used to evaluate the condition. Here, it is set to >, which means "greater than". -- threshold: This option sets the threshold value for the condition. It is set to 0.9. -- dataref_str: This option specifies the data reference string, which is a reference to a specific data value in the simulation. In this case, it is set to sim/flightmodel2/misc/door_open_ratio. +An aircraft-specific configuration file (xa-cabin.ini) is automatically created the first time you load the plane. This file uses X-Plane’s default datarefs, but for third-party aircraft, you may need to configure the datarefs manually for accurate announcement triggers. -[LANDING_LIGHTS]: +Example Configurations: -- operator: This option specifies the comparison operator used to evaluate the condition. Here, it is set to ===, which means "strictly equal to". -- threshold: This option sets the threshold value for the condition. It is set to 1. -- dataref_str: This option specifies the data reference string, which is a reference to a specific data value in the simulation. In this case, it is set to ckpt/oh/rwyTurnOff/anim. + • [LANDING_GEAR]: + • operator: The comparison operator, such as ~= (“approximately equal to”). + • threshold: The threshold value (e.g., 0). + • dataref_str: The data reference string (e.g., sim/flightmodel2/gear/deploy_ratio). + • [DOOR]: + • operator: Set to > for greater than. + • threshold: Set to 0.9. + • dataref_str: Reference for door position (e.g., sim/flightmodel2/misc/door_open_ratio). + • [LANDING_LIGHTS]: + • operator: Set to === for strict equality. + • threshold: Set to 1. + • dataref_str: Data reference string for runway lights (e.g., ckpt/oh/rwyTurnOff/anim). +Sound Pack -# Sound Pack -Currently, all announcements are generated using AI technology. However, we are actively working on developing a dedicated tool that will allow you to create your own custom sound pack in the future. +All announcements are currently generated using AI technology. However, we are working on a tool that will allow users to create custom sound packs in the future. -## List of sound +List of Available Sounds -|language|accent|speaker|description| -|--------|------|-------|-----------| -|en|gb|1|Britsh Accent| -|en|ca|1|Canadian Accent - SAS special| -|en|in|1|Indian Accent| +Language Accent Speaker Description +en gb 1 British Accent +en ca 1 Canadian Accent (SAS special) +en in 1 Indian Accent -## Add Sound Pack +Adding a Custom Sound Pack -Follow above naming convention, drop your wav files in each folder under `xa-cabin/announcements` +To add a custom sound pack: -change the configuration in `xa-cabin.ini` + 1. Follow the naming convention above. + 2. Place your .wav files in the corresponding folders under xa-cabin/announcements/. + 3. Update the xa-cabin.ini configuration: -``` [announcement] language=en accent=gb speaker=1 -``` -## Live Generation (TODO) \ No newline at end of file +Real-Time Announcement Generation + +The live generation feature is now implemented using the generate_announcements.py script. This script allows for dynamic generation of announcements based on real-time flight data, such as departure runways, estimated flight time, local time at destination, and weather conditions. + +How to Use generate_announcements.py + +The Python script generate_announcements.py dynamically generates announcements in real time when the language is set to custom. + + Note: Currently, you must run generate_announcements.py manually while X-Plane 12 is running, and then reload the Lua script. If you have already generated announcements for your flight, you do not need to run the script again. + +Steps: + + 1. Ensure Python is installed on your system. + 2. Set the language option to custom in the xa-cabin.ini file. + 3. Once X-Plane 12 is running, manually run the Python script. + 4. After running the script, reload the Lua script within X-Plane to load the generated announcements. + +During the start of XA-CABIN, check the log file. The system will print the Python command along with all the parameters fetched from SimBrief. You can copy and run this command directly to generate the announcements. + +Example Command: + +/usr/local/bin/python3 "xa-cabin/generate_announcements.py" \ +"XXXX Airlines" "DAT01" "Paris" "14:30" "en" "us" "01" \ +"Runway 25R" "Runway 27L" "35,000 ft" "2" "30" "10:30 AM" \ +"Boeing 737" "morning" + +This command generates announcements for a flight based on SimBrief data, with real-time parameters like flight number, destination, ETA, and more. + +The AI Powered Cabin Announcement plugin transforms in-flight audio by integrating AI technology, real-time capabilities, and flexible configuration options for creating a more realistic and engaging flight experience. \ No newline at end of file diff --git a/xa-cabin/docs/announcements.md b/xa-cabin/docs/announcements.md new file mode 100644 index 0000000..c977587 --- /dev/null +++ b/xa-cabin/docs/announcements.md @@ -0,0 +1,154 @@ +ANNOUNCEMENTS Module Documentation + +This Lua module provides functionality to manage and play flight announcements. It handles loading, playing, stopping, and queuing sounds with support for debounce logic to prevent repetitive playback within a short time frame. The script is designed to be integrated with a cabin announcement system, tracking the phases of a flight and ensuring smooth audio transitions between different announcements. + +Features + + • Debounce Logic: Prevents rapid consecutive plays of the same announcement within a configurable duration. + • Async Sound Handling: Manages the loading and playing of sounds asynchronously to avoid conflicts between announcements. + • Announcement Phases: Supports a variety of flight phases, from pre-boarding to post-landing. + • Pending Sound Queue: Handles pending sounds that may have been triggered while the system was loading. + • Safety Demo Handling: Special flag to manage the playing state of the safety demonstration. + • Sound Stop Functionality: Ensures all sounds are stopped before playing a new one to avoid overlapping audios. + +Announcement Phases + +The following announcement phases are supported: + + • Pre-Boarding + • Boarding + • Boarding Complete + • Safety Demonstration + • Takeoff + • Climb + • Cruise + • Prepare for Landing + • Final Approach + • Post Landing + • Emergency + +These phases can be expanded or customized as needed. + +Functions + +ANNOUNCEMENTS.play_sound(announcement_name) + +Plays the specified announcement sound. + + • Parameters: + • announcement_name (string): The name of the announcement phase to play. + • Logic: + • Checks for any currently playing announcement and stops it before playing the new one. + • Implements debounce logic to prevent multiple plays of the same announcement within a short duration. + • Updates the last play time for the announcement. + • Logs the status of the play action, whether successful or if the sound file is not found. + +ANNOUNCEMENTS.stopSounds() + +Stops all currently playing sounds. + + • Functionality: + • Iterates through all phases and stops any sounds that are currently playing. + • Resets the is_announcement_playing flag. + +ANNOUNCEMENTS.unloadAllSounds() + +Unloads all loaded sounds from memory. + + • Functionality: + • Marks all loaded sounds as false to release them. + • Logs the unloading process for each sound. + +ANNOUNCEMENTS.loadSounds() + +Loads the announcement sounds from the specified directory and marks them as ready. + + • Logic: + • Attempts to load .wav files for each announcement phase from the predefined directory. + • Logs success or failure of the loading process for each announcement. + • If loading is successful, plays any pending sounds queued during the loading process. + +ANNOUNCEMENTS.on_sound_complete() + +Called when an announcement finishes playing to reset the playing state. + + • Functionality: + • Resets the is_announcement_playing flag to allow subsequent sounds to be played. + +ANNOUNCEMENTS.play_pending_sounds() + +Plays any sounds that were queued while sounds were being loaded. + + • Functionality: + • Iterates through the pending sounds queue and plays each sound. + • Clears the queue after all sounds are played. + +Variables + +debounce_duration + + • Type: Number + • Description: The time (in seconds) to prevent repeated playing of the same announcement. Default is set to 5 seconds. + +last_play_times + + • Type: Table + • Description: Tracks the last play time for each announcement to enforce debounce logic. + +is_announcement_playing + + • Type: Boolean + • Description: Flag to track if any announcement is currently playing. Prevents overlap of announcements. + +ANNOUNCEMENTS_DIR + + • Type: String + • Description: The directory where all announcement sound files are stored. + +ANNOUNCEMENTS.sounds + + • Type: Table + • Description: Stores the loaded sound handles for each announcement phase. + +ANNOUNCEMENTS.files + + • Type: Table + • Description: Stores the file paths for each loaded sound. + +ANNOUNCEMENTS.pending_sounds + + • Type: Table + • Description: Queues sounds triggered during the loading process to be played once loading is complete. + +ANNOUNCEMENTS.is_loaded + + • Type: Boolean + • Description: Indicates whether all announcements have been successfully loaded. + +ANNOUNCEMENTS.is_safety_demo_playing + + • Type: Boolean + • Description: Tracks whether the safety demonstration announcement is currently playing to manage its specific state. + +Logging + +The module logs various actions and states (e.g., sound loading, sound playing, debounce skipping, etc.) using the XA_CABIN_LOGGER.write_log function, which is expected to handle logging in the cabin system. Make sure this logger is set up in your environment for proper debugging and tracking. + +Directory Structure + +The announcement files are expected to follow this directory structure: + +/xa-cabin/announcements/ + pre_boarding/ + boarding/ + boarding_complete/ + ... + +Each subdirectory contains the respective .wav files for different language and accent combinations, such as: + +en-gb-1.wav +en-us-1.wav + +Integration + +This module is designed to be integrated with a cabin system that handles playing sounds during a flight’s lifecycle. Ensure the announcement files are correctly placed in the ANNOUNCEMENTS_DIR and that the logger (XA_CABIN_LOGGER) is properly set up for the script to function as expected. \ No newline at end of file diff --git a/xa-cabin/docs/generate_announcements.md b/xa-cabin/docs/generate_announcements.md new file mode 100644 index 0000000..9033463 --- /dev/null +++ b/xa-cabin/docs/generate_announcements.md @@ -0,0 +1,103 @@ +Flight Announcement TTS Generator + +This Python script generates flight announcements as audio files using OpenAI’s text-to-speech (TTS) API. The generated audio files are saved in .wav format and cover various phases of a flight, such as boarding, takeoff, landing, and more. The script supports multiple languages and accents, and allows custom voice options for generating announcements. + +Features + + • Generates pre-defined flight announcements (e.g., boarding, safety, takeoff, landing) based on flight information. + • Supports multiple languages and accents. + • Converts generated audio to .wav format (44.1 kHz, mono, 16-bit PCM). + • Saves audio files in structured directories based on announcement phases. + • Asynchronous generation of TTS files using threading. + +Requirements + + • Python 3.x + • OpenAI API key + • pydub for audio processing + • FFmpeg and FFprobe installed on your system (required by pydub for audio conversion) + +Python Libraries + + • openai + • pydub + +Setup + + 1. Install required dependencies: +Install the required Python packages using pip: + +pip install openai pydub + + + 2. Install FFmpeg: +Make sure you have FFmpeg and FFprobe installed. You can install them using brew (on macOS) or other package managers: + +brew install ffmpeg + + + 3. Set FFmpeg and FFprobe paths: +Update the paths in the script to point to your FFmpeg and FFprobe installation: + +AudioSegment.converter = "/path/to/ffmpeg" +AudioSegment.ffprobe = "/path/to/ffprobe" + + + +Usage + +To run the script, you need to pass flight-related information as command-line arguments. Here is the expected format of the arguments: + +python script_name.py + +Example: + +python script_name.py "XXX Airlines" "DAT01" "Paris" "14:30" "en" "us" "01" "Runway 25R" "Runway 27L" "35,000 ft" "2" "45" "10:30 AM" "15" "Boeing 737" "morning" + +Generated Announcements: + +The script generates announcements for the following flight phases: + + • Pre-Boarding + • Boarding + • Boarding Complete + • Safety Demonstration + • Takeoff + • Climb + • Cruise + • Prepare for Landing + • Final Approach + • Post Landing + • Emergency + +Configuration + +You can modify the default data passed to the TTS system by updating the fms_data dictionary in the script. The script supports the following data fields: + + • airline: Name of the airline. + • flight_number: Flight number. + • destination: Destination of the flight. + • eta: Estimated time of arrival. + • language: Language for the announcement (e.g., “en” for English). + • accent: Accent for the announcement (e.g., “us” for American English). + • speaker: Speaker ID for OpenAI TTS (if applicable). + • departure_runway: Departure runway. + • arrival_runway: Arrival runway. + • cruise_altitude: Altitude during cruise. + • flight_time_hours: Flight time in hours. + • flight_time_minutes: Flight time in minutes. + • local_time_at_destination: Local time at the destination. + • aircraft_type: Type of the aircraft. + • time_of_day: Time of day (e.g., “morning”, “afternoon”). + +Output + +Generated audio files will be saved in a structured directory under the announcements/ folder in the script directory. Each announcement phase will have its own subdirectory. + +License + +This project is open-source. Feel free to modify and use it as needed. + +Disclaimer + +Ensure that your OpenAI API key is kept private and secure. Do not hardcode your API key in shared repositories. \ No newline at end of file diff --git a/xa-cabin/docs/state.md b/xa-cabin/docs/state.md new file mode 100644 index 0000000..b421111 --- /dev/null +++ b/xa-cabin/docs/state.md @@ -0,0 +1,120 @@ +XA Cabin Flight + +This script handles various states of flight and cabin operations in an aircraft simulation environment. It monitors flight status (e.g., parked, takeoff, cruise) and cabin activities (e.g., boarding, safety demonstration) and triggers state changes based on certain conditions. + +Features + + • Flight Phases: Automatically detects the current flight phase (e.g., parked, takeoff, climb) and transitions between phases based on conditions such as speed, altitude, and engine power. + • Cabin States: Tracks cabin activities like boarding, safety demonstrations, and announcements, synchronizing them with flight phases. + • Debouncing: Prevents rapid state changes with a configurable debounce threshold. + • Logging: Detailed logs for debugging and auditing state transitions. + • Automated Announcements: Plays cabin announcements based on flight and cabin states, such as boarding complete, takeoff, and landing preparation. + +Flight States + +The script tracks the following flight states to manage transitions during the flight: + + 1. Parked + • Initial state when the aircraft is on the ground with engines off or at idle and the parking brake applied. + • Triggers: + • Ground speed < 1 m/s, altitude AGL < 10 ft, engine N1 < 30%. + • If doors are open, the state transitions to pre_boarding. If doors are closed, it transitions to boarding_complete. + 2. Taxi Out + • The aircraft is preparing for takeoff and moving on the ground. + • Triggers: + • Ground speed > 5 m/s and gear force > 1. + • Engine N1 > 75%. + 3. Takeoff + • The aircraft is accelerating for takeoff. + • Triggers: + • N1 exceeds 75% and vertical speed (VS) > 200 ft/min, gear force < 1. + 4. Climb + • The aircraft is climbing after takeoff. + • Triggers: + • Vertical speed (VS) remains stable between -500 and +500 ft/min for an extended period. + 5. Cruise + • The aircraft has reached cruising altitude and maintains stable vertical speed. + • Triggers: + • VS is stable between -500 and +500 ft/min for over 15 consecutive checks. + 6. Descent + • The aircraft starts descending toward the destination. + • Triggers: + • VS < -500 ft/min for over 15 consecutive checks. + 7. Approach + • The aircraft is in the approach phase, preparing for landing. + • Triggers: + • Altitude AGL < 800 feet, gear deployed, VS < -200 ft/min. + 8. Taxi In + • The aircraft has landed and is taxiing to the gate. + • Triggers: + • Ground speed < 50/1.9 m/s and gear force > 10. + 9. Parked (After Taxi In) + • The aircraft is parked at the gate after landing. + • Triggers: + • Ground speed < 1/1.9 m/s and N1 > 15%. + +Cabin States + +The script tracks cabin states to manage activities like boarding, safety demonstrations, and announcements. Cabin state transitions are triggered by flight state changes and door status: + + 1. Pre-Boarding + • Initial state before passengers board. + • Triggers: + • Doors open, flight parked. + 2. Boarding + • Passengers are boarding. + • Triggers: + • Random delay after doors open and flight is parked. + 3. Boarding Complete + • Boarding is complete, and preparations for departure begin. + • Triggers: + • Doors closed, aircraft ready for taxi-out. + 4. Safety Demonstration + • Safety demonstration announcement. + • Triggers: + • Taxi-out begins, landing lights on. + 5. Takeoff (Cabin) + • Cabin secured for takeoff. + • Triggers: + • Takeoff initiated after safety demo. + 6. Climb (Cabin) + • Cabin is in the climb phase. + • Triggers: + • The flight enters the climb phase. + 7. Cruise (Cabin) + • Cabin is in the cruise phase. + • Triggers: + • The flight reaches the cruise phase. + 8. Prepare for Landing + • Cabin prepares for landing. + • Triggers: + • The flight enters descent. + 9. Final Approach + • Cabin secured for landing. + • Triggers: + • The flight enters the approach phase. + 10. Post Landing + + • Post-landing procedures. + • Triggers: + • The flight state changes to taxi-in after landing. + +State Change Debouncing + +To prevent frequent state changes due to transient conditions, the script implements a debouncing mechanism: + + • States cannot change more than once within a configurable time (default: 5 seconds). + • This helps avoid rapid, recursive state changes that could destabilize the simulation. + +Logging + +All state transitions and actions are logged using XA_CABIN_LOGGER for debugging and auditing purposes. These logs help track the flow of state changes throughout the flight and cabin procedures. + +Usage + +To use the script, ensure that the required datarefs are properly initialized, and the announcements are correctly loaded. The script will automatically detect and manage flight and cabin states based on flight conditions and cabin operations. + +Announcements + +Announcements are played according to cabin state transitions. Make sure to include the appropriate sound files in the designated directory for each state. If a sound file is not found, a log entry will indicate the missing announcement. + diff --git a/xa-cabin/docs/xa-cabin.md b/xa-cabin/docs/xa-cabin.md new file mode 100644 index 0000000..57ae856 --- /dev/null +++ b/xa-cabin/docs/xa-cabin.md @@ -0,0 +1,121 @@ +XA Cabin System + +This Lua script manages a comprehensive cabin announcement and configuration system for flight simulations using FlyWithLua. It integrates various components such as settings, logging, GUI, SimBrief data, and announcement generation, ensuring smooth and customizable flight announcements in multiple phases of a flight. + +Features + + • Dynamic Announcements: Generates flight announcements based on SimBrief data, including information such as airline, flight number, destination, estimated time of arrival (ETA), departure runway, and more. + • Flight Phases Support: Provides announcements for pre-defined flight phases (e.g., boarding, takeoff, cruise, landing). + • Debounce Logic: Prevents repetitive announcement playback within a short time frame. + • Custom Language Support: Allows custom announcement generation using Python if the language is set to “custom”. + • Real-time Updates: Tracks and updates the flight and cabin state throughout the flight. + • Graphical User Interface (GUI): Displays flight and cabin information in an interactive window using ImGui. + • Plane Configuration Management: Automatically loads and handles plane-specific configurations. + • SimBrief Integration: Retrieves flight data directly from SimBrief to generate accurate and up-to-date announcements. + +Components + +1. Loading and Initialization + +The script initializes by loading required components, settings, and plane configurations: + + • LIP: For managing INI files and configurations. + • Logging: Custom logging system for debugging and tracking cabin system behavior. + • Global Variables: Loads necessary global variables to ensure smooth operation. + +2. Announcement Management + +The system manages the loading, playing, stopping, and queuing of announcement sounds using the ANNOUNCEMENTS module. It ensures no overlapping sounds and provides clear logging for all sound events. + + • Announcement Phases: The following phases are supported: + • Pre-boarding + • Boarding + • Boarding Complete + • Safety Demonstration + • Takeoff + • Climb + • Cruise + • Prepare for Landing + • Final Approach + • Post Landing + • Emergency + • Debounce: A debounce system is in place to avoid rapid consecutive plays of the same announcement within a short period (configurable via debounce_duration). + +3. SimBrief Data Integration + +The script retrieves key flight data from SimBrief, including: + + • Airline name + • Flight number and callsign + • Destination and origin runways + • ETA, flight duration, and time of day classification (morning, afternoon, night). + +This data is then used to dynamically generate announcements using the Python-based TTS generator if the language is set to “custom”. + +4. GUI Integration + +The script includes a graphical interface built with ImGui, providing an easy-to-use window that displays flight information, cabin configuration options, and controls for playing announcements. + + • Window Control: Functions to show, hide, and toggle the visibility of the cabin window (xa_cabin_show_wnd, xa_cabin_hide_wnd, toggle_xa_cabin_window). + • Customizable GUI: Displays SimBrief information, announcement settings, and allows control over flight states and cabin settings. + +5. Custom Python TTS Announcements + +When custom announcements are needed (e.g., for non-standard languages or accents), the script calls a Python script (generate_announcements.py) to create TTS audio files. This integration allows flexibility in generating personalized flight announcements. + +6. Configuration Management + +The script handles reading and saving the configuration from a plane-specific INI file (xa-cabin.ini). If the file does not exist, it creates a new one with default values. The configuration includes settings such as the type of aircraft, preferred runways, language, and accent for announcements. + +7. Real-time State Updates + +The system continuously updates the flight and cabin states in real-time using the xa_cabin_update_state function, ensuring that the cabin announcements and GUI reflect the current flight conditions. + +Usage + +Running the Script + +To use this script, it must be placed in the appropriate FlyWithLua directory structure. The script relies on several components, including Python for TTS generation and sound handling, so ensure your environment is correctly set up: + + 1. Place all required Lua files (e.g., LIP.lua, logging.lua, helpers.lua, etc.) in the xa-cabin/ directory. + 2. Ensure Python is installed and correctly configured to run TTS generation (if using custom language). + 3. Make sure the sound files are correctly placed in the xa-cabin/announcements/ directory. + 4. Load the script using FlyWithLua. + +Customizing Announcements + +If custom announcements are required: + + 1. Set the language to “custom” in the configuration (xa-cabin.ini). + 2. Ensure the Python script for TTS generation (generate_announcements.py) is correctly configured. + 3. The script will dynamically generate announcements using the command and Python script provided. + +GUI Interaction + +To interact with the cabin system GUI, use the following commands: + + • Show/Hide Window: Use the XA Cabin macro or the created command xa_cabin_menus/show_toggle to open and close the GUI. + +Example SimBrief Configuration + +The system pulls data from SimBrief to generate accurate flight information. Ensure you have valid SimBrief data for realistic announcements. + +Logs + +All actions, including sound events, error handling, and state updates, are logged using the XA_CABIN_LOGGER, which records all operations for troubleshooting and debugging. + +Example Log Entry: + +XA Cabin Log: Successfully loaded announcement: /path/to/sound/en-gb-1.wav + +Dependencies + + • FlyWithLua: This script is designed to work within the FlyWithLua environment. + • SimBrief: For retrieving real-time flight data. + • Python: Required for generating custom TTS announcements using the OpenAI API. + +License + +This script is open-source. You are free to modify and distribute it under the terms of the applicable license. + +By using this cabin system, you can simulate realistic and customizable cabin announcements, tailored to the specifics of each flight using real-world data from SimBrief. \ No newline at end of file From 725f7572356d103a76060daeeca457866f952044 Mon Sep 17 00:00:00 2001 From: Ricardo Borenstein Date: Sun, 13 Oct 2024 11:53:23 +0200 Subject: [PATCH 05/11] remove duplicated state.md --- xa-cabin/state.md | 120 ---------------------------------------------- 1 file changed, 120 deletions(-) delete mode 100644 xa-cabin/state.md diff --git a/xa-cabin/state.md b/xa-cabin/state.md deleted file mode 100644 index b421111..0000000 --- a/xa-cabin/state.md +++ /dev/null @@ -1,120 +0,0 @@ -XA Cabin Flight - -This script handles various states of flight and cabin operations in an aircraft simulation environment. It monitors flight status (e.g., parked, takeoff, cruise) and cabin activities (e.g., boarding, safety demonstration) and triggers state changes based on certain conditions. - -Features - - • Flight Phases: Automatically detects the current flight phase (e.g., parked, takeoff, climb) and transitions between phases based on conditions such as speed, altitude, and engine power. - • Cabin States: Tracks cabin activities like boarding, safety demonstrations, and announcements, synchronizing them with flight phases. - • Debouncing: Prevents rapid state changes with a configurable debounce threshold. - • Logging: Detailed logs for debugging and auditing state transitions. - • Automated Announcements: Plays cabin announcements based on flight and cabin states, such as boarding complete, takeoff, and landing preparation. - -Flight States - -The script tracks the following flight states to manage transitions during the flight: - - 1. Parked - • Initial state when the aircraft is on the ground with engines off or at idle and the parking brake applied. - • Triggers: - • Ground speed < 1 m/s, altitude AGL < 10 ft, engine N1 < 30%. - • If doors are open, the state transitions to pre_boarding. If doors are closed, it transitions to boarding_complete. - 2. Taxi Out - • The aircraft is preparing for takeoff and moving on the ground. - • Triggers: - • Ground speed > 5 m/s and gear force > 1. - • Engine N1 > 75%. - 3. Takeoff - • The aircraft is accelerating for takeoff. - • Triggers: - • N1 exceeds 75% and vertical speed (VS) > 200 ft/min, gear force < 1. - 4. Climb - • The aircraft is climbing after takeoff. - • Triggers: - • Vertical speed (VS) remains stable between -500 and +500 ft/min for an extended period. - 5. Cruise - • The aircraft has reached cruising altitude and maintains stable vertical speed. - • Triggers: - • VS is stable between -500 and +500 ft/min for over 15 consecutive checks. - 6. Descent - • The aircraft starts descending toward the destination. - • Triggers: - • VS < -500 ft/min for over 15 consecutive checks. - 7. Approach - • The aircraft is in the approach phase, preparing for landing. - • Triggers: - • Altitude AGL < 800 feet, gear deployed, VS < -200 ft/min. - 8. Taxi In - • The aircraft has landed and is taxiing to the gate. - • Triggers: - • Ground speed < 50/1.9 m/s and gear force > 10. - 9. Parked (After Taxi In) - • The aircraft is parked at the gate after landing. - • Triggers: - • Ground speed < 1/1.9 m/s and N1 > 15%. - -Cabin States - -The script tracks cabin states to manage activities like boarding, safety demonstrations, and announcements. Cabin state transitions are triggered by flight state changes and door status: - - 1. Pre-Boarding - • Initial state before passengers board. - • Triggers: - • Doors open, flight parked. - 2. Boarding - • Passengers are boarding. - • Triggers: - • Random delay after doors open and flight is parked. - 3. Boarding Complete - • Boarding is complete, and preparations for departure begin. - • Triggers: - • Doors closed, aircraft ready for taxi-out. - 4. Safety Demonstration - • Safety demonstration announcement. - • Triggers: - • Taxi-out begins, landing lights on. - 5. Takeoff (Cabin) - • Cabin secured for takeoff. - • Triggers: - • Takeoff initiated after safety demo. - 6. Climb (Cabin) - • Cabin is in the climb phase. - • Triggers: - • The flight enters the climb phase. - 7. Cruise (Cabin) - • Cabin is in the cruise phase. - • Triggers: - • The flight reaches the cruise phase. - 8. Prepare for Landing - • Cabin prepares for landing. - • Triggers: - • The flight enters descent. - 9. Final Approach - • Cabin secured for landing. - • Triggers: - • The flight enters the approach phase. - 10. Post Landing - - • Post-landing procedures. - • Triggers: - • The flight state changes to taxi-in after landing. - -State Change Debouncing - -To prevent frequent state changes due to transient conditions, the script implements a debouncing mechanism: - - • States cannot change more than once within a configurable time (default: 5 seconds). - • This helps avoid rapid, recursive state changes that could destabilize the simulation. - -Logging - -All state transitions and actions are logged using XA_CABIN_LOGGER for debugging and auditing purposes. These logs help track the flow of state changes throughout the flight and cabin procedures. - -Usage - -To use the script, ensure that the required datarefs are properly initialized, and the announcements are correctly loaded. The script will automatically detect and manage flight and cabin states based on flight conditions and cabin operations. - -Announcements - -Announcements are played according to cabin state transitions. Make sure to include the appropriate sound files in the designated directory for each state. If a sound file is not found, a log entry will indicate the missing announcement. - From f33daf161f0dfe02287cb0e3e09f1a85a93ac452 Mon Sep 17 00:00:00 2001 From: Ricardo Borenstein <78426391+ricardoborenstein@users.noreply.github.com> Date: Sun, 13 Oct 2024 12:03:46 +0200 Subject: [PATCH 06/11] Update README.md --- README.md | 152 ++++++++++++++++++++++++++++++------------------------ 1 file changed, 84 insertions(+), 68 deletions(-) diff --git a/README.md b/README.md index e295720..9a3efad 100644 --- a/README.md +++ b/README.md @@ -1,29 +1,32 @@ -AI Powered Cabin Announcement -The AI Powered Cabin Announcement plugin enhances the cabin announcement system for flight simulations by utilizing cutting-edge artificial intelligence algorithms to generate high-quality, natural-sounding announcements. +# AI Powered Cabin Announcement -Compared to older plugins, the AI Powered Cabin Announcement offers several advantages: +The **AI Powered Cabin Announcement** plugin enhances the cabin announcement system for flight simulations by utilizing cutting-edge artificial intelligence algorithms to generate high-quality, natural-sounding announcements. - 1. Human-like Voice Synthesis: The AI engine generates highly realistic and expressive speech, resulting in a more immersive experience for passengers. - 2. Flexibility and Customization: The plugin allows easy configuration of both global and aircraft-specific settings, enabling tailored announcements based on each aircraft’s unique characteristics. - 3. Sound Pack Integration: Airlines can incorporate their own branding and unique audio elements into announcements, creating a distinctive cabin environment. - 4. Dynamic Announcement Generation: The plugin now supports real-time generation of announcements using generate_announcements.py. This feature adapts announcements based on dynamic factors like flight status, weather conditions, and passenger information, keeping them relevant throughout the flight. +Compared to older plugins, the **AI Powered Cabin Announcement** offers several advantages: +1. **Human-like Voice Synthesis**: The AI engine generates highly realistic and expressive speech, resulting in a more immersive experience for passengers. +2. **Flexibility and Customization**: The plugin allows easy configuration of both global and aircraft-specific settings, enabling tailored announcements based on each aircraft’s unique characteristics. +3. **Sound Pack Integration**: Airlines can incorporate their own branding and unique audio elements into announcements, creating a distinctive cabin environment. +4. **Dynamic Announcement Generation**: The plugin now supports **real-time generation** of announcements using `generate_announcements.py`. This feature adapts announcements based on dynamic factors like flight status, weather conditions, and passenger information, keeping them relevant throughout the flight. -Overall, the AI Powered Cabin Announcement plugin revolutionizes the in-flight announcement experience by leveraging AI technology to deliver superior audio quality, customization, and dynamic real-time generation. +Overall, the **AI Powered Cabin Announcement** plugin revolutionizes the in-flight announcement experience by leveraging AI technology to deliver superior audio quality, customization, and dynamic real-time generation. -Installation +--- - Note: FlyWithLua is required. +## Installation + +> **Note**: FlyWithLua is required. Follow these steps to install the plugin: - 1. Download the plugin files. - 2. Unzip the downloaded file. - 3. Navigate to the FlyWithLua/Scripts folder in your flight simulator directory. - 4. Copy all the files and folders from the unzipped plugin folder into the FlyWithLua/Scripts folder. +1. Download the plugin files. +2. Unzip the downloaded file. +3. Navigate to the `FlyWithLua/Scripts` folder in your flight simulator directory. +4. Copy all the files and folders from the unzipped plugin folder into the `FlyWithLua/Scripts` folder. After installation, the file structure should look like this: +``` Resources ... |- plugins @@ -35,97 +38,110 @@ After installation, the file structure should look like this: |- xa-cabin xa-cabin.lua xa-cabin.ini +``` + +--- + +## Configuration + +### Global Configurations -Configuration +The main configuration file is `xa-cabin.ini`, where most options can also be modified through the in-game GUI. -Global Configurations +#### `[simbrief]` section (also configurable in the GUI): -The main configuration file is xa-cabin.ini, where most options can also be modified through the in-game GUI. +- **`username`**: Specify your SimBrief username to integrate flight planning data. -[simbrief] section (also configurable in the GUI): +#### `[mode]` section (also configurable in the GUI): - • username: Specify your SimBrief username to integrate flight planning data. +- **`automated`**: Set to `true` to play cabin announcements automatically. +- **`live`**: Set to `true` to enable real-time announcement generation using `generate_announcements.py`. -[mode] section (also configurable in the GUI): +#### `[announcement]` section: - • automated: Set to true to play cabin announcements automatically. - • live: Set to true to enable real-time announcement generation using generate_announcements.py. +- **`language`**: Specifies the language of the announcements (e.g., `en` for English). +- **`accent`**: Specifies the accent for the announcements (e.g., `in` for Indian accent). +- **`speaker`**: Sets the speaker ID (e.g., `01`). -[announcement] section: +### Aircraft-Specific Configuration - • language: Specifies the language of the announcements (e.g., en for English). - • accent: Specifies the accent for the announcements (e.g., in for Indian accent). - • speaker: Sets the speaker ID (e.g., 01). +An aircraft-specific configuration file (`xa-cabin.ini`) is automatically created the first time you load the plane. This file uses X-Plane’s default datarefs, but for third-party aircraft, you may need to configure the datarefs manually for accurate announcement triggers. -Aircraft-Specific Configuration +#### Example Configurations: -An aircraft-specific configuration file (xa-cabin.ini) is automatically created the first time you load the plane. This file uses X-Plane’s default datarefs, but for third-party aircraft, you may need to configure the datarefs manually for accurate announcement triggers. +- **`[LANDING_GEAR]`**: + - **`operator`**: The comparison operator, such as `~=` ("approximately equal to"). + - **`threshold`**: The threshold value (e.g., `0`). + - **`dataref_str`**: The data reference string (e.g., `sim/flightmodel2/gear/deploy_ratio`). -Example Configurations: +- **`[DOOR]`**: + - **`operator`**: Set to `>` for greater than. + - **`threshold`**: Set to `0.9`. + - **`dataref_str`**: Reference for door position (e.g., `sim/flightmodel2/misc/door_open_ratio`). - • [LANDING_GEAR]: - • operator: The comparison operator, such as ~= (“approximately equal to”). - • threshold: The threshold value (e.g., 0). - • dataref_str: The data reference string (e.g., sim/flightmodel2/gear/deploy_ratio). - • [DOOR]: - • operator: Set to > for greater than. - • threshold: Set to 0.9. - • dataref_str: Reference for door position (e.g., sim/flightmodel2/misc/door_open_ratio). - • [LANDING_LIGHTS]: - • operator: Set to === for strict equality. - • threshold: Set to 1. - • dataref_str: Data reference string for runway lights (e.g., ckpt/oh/rwyTurnOff/anim). +- **`[LANDING_LIGHTS]`**: + - **`operator`**: Set to `===` for strict equality. + - **`threshold`**: Set to `1`. + - **`dataref_str`**: Data reference string for runway lights (e.g., `ckpt/oh/rwyTurnOff/anim`). -Sound Pack +--- + +## Sound Pack All announcements are currently generated using AI technology. However, we are working on a tool that will allow users to create custom sound packs in the future. -List of Available Sounds +### List of Available Sounds -Language Accent Speaker Description -en gb 1 British Accent -en ca 1 Canadian Accent (SAS special) -en in 1 Indian Accent +| Language | Accent | Speaker | Description | +|----------|--------|---------|-----------------------------| +| en | gb | 1 | British Accent | +| en | ca | 1 | Canadian Accent (SAS special)| +| en | in | 1 | Indian Accent | -Adding a Custom Sound Pack +### Adding a Custom Sound Pack To add a custom sound pack: - 1. Follow the naming convention above. - 2. Place your .wav files in the corresponding folders under xa-cabin/announcements/. - 3. Update the xa-cabin.ini configuration: +1. Follow the naming convention above. +2. Place your `.wav` files in the corresponding folders under `xa-cabin/announcements/`. +3. Update the `xa-cabin.ini` configuration: +``` [announcement] language=en accent=gb speaker=1 +``` + +--- -Real-Time Announcement Generation +## Real-Time Announcement Generation -The live generation feature is now implemented using the generate_announcements.py script. This script allows for dynamic generation of announcements based on real-time flight data, such as departure runways, estimated flight time, local time at destination, and weather conditions. +The **live generation feature** is now implemented using the `generate_announcements.py` script. This script allows for dynamic generation of announcements based on real-time flight data, such as departure runways, estimated flight time, local time at destination, and weather conditions. -How to Use generate_announcements.py +### How to Use `generate_announcements.py` -The Python script generate_announcements.py dynamically generates announcements in real time when the language is set to custom. +The Python script `generate_announcements.py` dynamically generates announcements in real time when the language is set to `custom`. - Note: Currently, you must run generate_announcements.py manually while X-Plane 12 is running, and then reload the Lua script. If you have already generated announcements for your flight, you do not need to run the script again. +> **Note**: Currently, you must run `generate_announcements.py` manually while **X-Plane 12** is running, and then reload the Lua script. If you have already generated announcements for your flight, you do not need to run the script again. -Steps: +#### Steps: - 1. Ensure Python is installed on your system. - 2. Set the language option to custom in the xa-cabin.ini file. - 3. Once X-Plane 12 is running, manually run the Python script. - 4. After running the script, reload the Lua script within X-Plane to load the generated announcements. +1. Ensure Python is installed on your system. +2. Set the `language` option to `custom` in the `xa-cabin.ini` file. +3. Once X-Plane 12 is running, manually run the Python script. +4. After running the script, reload the Lua script within X-Plane to load the generated announcements. -During the start of XA-CABIN, check the log file. The system will print the Python command along with all the parameters fetched from SimBrief. You can copy and run this command directly to generate the announcements. +During the start of **XA-CABIN**, check the log file. The system will print the Python command along with all the parameters fetched from SimBrief. You can copy and run this command directly to generate the announcements. -Example Command: +#### Example Command: -/usr/local/bin/python3 "xa-cabin/generate_announcements.py" \ -"XXXX Airlines" "DAT01" "Paris" "14:30" "en" "us" "01" \ -"Runway 25R" "Runway 27L" "35,000 ft" "2" "30" "10:30 AM" \ -"Boeing 737" "morning" +``` +/usr/local/bin/python3 "xa-cabin/generate_announcements.py" "Datanised Airlines" "DAT01" "Paris" "14:30" "en" "us" "01" "Runway 25R" "Runway 27L" "35,000 ft" "2" "30" "10:30 AM" "Boeing 737" "morning" +``` This command generates announcements for a flight based on SimBrief data, with real-time parameters like flight number, destination, ETA, and more. -The AI Powered Cabin Announcement plugin transforms in-flight audio by integrating AI technology, real-time capabilities, and flexible configuration options for creating a more realistic and engaging flight experience. \ No newline at end of file +--- + +The **AI Powered Cabin Announcement** plugin transforms in-flight audio by integrating AI technology, real-time capabilities, and flexible configuration options for creating a more realistic and engaging flight experience. From 27e7789ae9f3e519ed19ca926f7ad952192f2edd Mon Sep 17 00:00:00 2001 From: Ricardo Borenstein Date: Mon, 14 Oct 2024 13:53:22 +0200 Subject: [PATCH 07/11] adding more documentation --- xa-cabin.ini | 6 +- xa-cabin/docs/XA_Cabin_System_User_Guide.md | 178 ++++++++++++++++ xa-cabin/docs/announcements.md | 171 ++++++++-------- xa-cabin/docs/generate_announcements.md | 142 +++++++------ xa-cabin/docs/state.md | 212 +++++++++++--------- xa-cabin/docs/xa-cabin.md | 135 +++++++------ xa-cabin/generate_announcements.py | 2 - 7 files changed, 531 insertions(+), 315 deletions(-) create mode 100644 xa-cabin/docs/XA_Cabin_System_User_Guide.md diff --git a/xa-cabin.ini b/xa-cabin.ini index 6f4977a..b0dba28 100644 --- a/xa-cabin.ini +++ b/xa-cabin.ini @@ -6,6 +6,6 @@ automated=true live=true [announcement] -language=en -accent=gb -speaker=1 \ No newline at end of file +language=english +accent=nova +speaker=custom \ No newline at end of file diff --git a/xa-cabin/docs/XA_Cabin_System_User_Guide.md b/xa-cabin/docs/XA_Cabin_System_User_Guide.md new file mode 100644 index 0000000..ae51339 --- /dev/null +++ b/xa-cabin/docs/XA_Cabin_System_User_Guide.md @@ -0,0 +1,178 @@ + +# XA Cabin System User Guide + +This user guide provides detailed instructions on how to use the **XA Cabin System**, which includes components for flight announcements, cabin configuration, and state management in flight simulation environments. Below are the main modules and features of the system, and how to operate them effectively. + +## Table of Contents + +1. [Introduction](#introduction) +2. [System Overview](#system-overview) +3. [Installation](#installation) +4. [Components Overview](#components-overview) + - [ANNOUNCEMENTS Module](#announcements-module) + - [Flight Announcement TTS Generator](#flight-announcement-tts-generator) + - [XA Cabin Flight](#xa-cabin-flight) + - [XA Cabin System](#xa-cabin-system) +5. [Usage](#usage) +6. [Configuration](#configuration) +7. [Logs](#logs) +8. [Dependencies](#dependencies) + +--- + +## 1. Introduction + +The **XA Cabin System** is a Lua-based script designed for flight simulation environments using FlyWithLua. It manages flight announcements, cabin configurations, and various flight phases to provide an immersive in-flight experience. It also integrates SimBrief data for generating realistic flight announcements, and offers support for custom languages through a Python-based TTS system. + +--- + +## 2. System Overview + +The system consists of several key components: +- Dynamic announcements based on flight phases. +- Integration with SimBrief for real-time flight data. +- A graphical user interface (GUI) for configuring and controlling the cabin system. +- Support for custom announcements using Python for text-to-speech (TTS) generation. + +--- + +## 3. Installation + +### Required Components: +1. **FlyWithLua**: This system is designed to run within FlyWithLua. +2. **Python**: For generating custom TTS announcements using OpenAI’s API. +3. **SimBrief Integration**: To pull real-time flight data. +4. **Sound Files**: Announcement audio files must be correctly placed in the `xa-cabin/announcements/` directory. + +### Steps: +1. Place all required Lua files (e.g., `LIP.lua`, `logging.lua`, etc.) in the `xa-cabin/` directory. +2. Install and configure Python for TTS generation (if using custom language). +3. Ensure sound files are correctly placed in the appropriate directory. +4. Load the script using FlyWithLua. + +--- + +## 4. Components Overview + +### 4.1 ANNOUNCEMENTS Module + +The ANNOUNCEMENTS module manages flight announcements, including playing, stopping, and queuing sounds. It supports several phases of flight and ensures smooth transitions between announcements. + +- **Supported Phases**: + - Pre-Boarding + - Boarding + - Boarding Complete + - Safety Demonstration + - Takeoff + - Climb + - Cruise + - Prepare for Landing + - Final Approach + - Post Landing + - Emergency + +- **Debounce Logic**: Prevents the same announcement from playing multiple times within a short duration. + +For more details, see the [ANNOUNCEMENTS Module Documentation](39). + +### 4.2 Flight Announcement TTS Generator + +This Python script generates flight announcements as audio files using OpenAI's TTS API. It supports multiple languages and accents, and generates announcements for various phases of the flight. + +- **Features**: + - Generates announcements based on flight information (airline, flight number, etc.). + - Saves generated audio files in .wav format. + - Supports asynchronous generation using threading. + +For more details, see the [Flight Announcement TTS Generator Documentation](40). + +### 4.3 XA Cabin Flight + +This module handles flight and cabin state management. It monitors the phases of the flight (e.g., parked, takeoff, cruise) and triggers cabin activities accordingly. + +- **Flight States**: + - Parked + - Taxi Out + - Takeoff + - Climb + - Cruise + - Descent + - Approach + - Taxi In + - Parked (After Taxi In) + +- **Cabin States**: + - Pre-Boarding + - Boarding + - Boarding Complete + - Safety Demonstration + - Takeoff + - Climb + - Cruise + - Prepare for Landing + - Final Approach + - Post Landing + +For more details, see the [XA Cabin Flight Documentation](41). + +### 4.4 XA Cabin System + +The main system script integrates various modules, including announcements, SimBrief data, and GUI elements. It ensures smooth transitions between cabin states and provides detailed logs for tracking actions and debugging. + +- **Features**: + - Dynamic announcements based on SimBrief data. + - Custom TTS generation using Python. + - Real-time updates for flight and cabin states. + - GUI integration using ImGui for interactive controls. + +For more details, see the [XA Cabin System Documentation](42). + +--- + +## 5. Usage + +To use the XA Cabin System, follow these steps: + +1. **Load the Script**: Use FlyWithLua to load the script in your flight simulation environment. +2. **Configure the Cabin System**: Use the GUI to adjust cabin settings and start announcements. +3. **Monitor Flight Phases**: The system will automatically detect flight phases and trigger appropriate announcements. +4. **Custom Announcements**: If using a custom language, ensure Python is configured to generate TTS announcements dynamically. + +--- + +## 6. Configuration + +### INI Configuration: +The system reads configuration data from `xa-cabin.ini`. This file contains settings for aircraft type, preferred runways, announcement language, and more. + +- Example of setting the language to custom: + ``` + [announcement] + language=custom + ``` + +For more details on configuring custom announcements, see the [Flight Announcement TTS Generator Documentation](40). + +--- + +## 7. Logs + +All system events, including sound loading, state changes, and error handling, are logged using `XA_CABIN_LOGGER`. This logger is essential for debugging and monitoring the system’s performance during flights. + +- Example Log Entry: + ``` + XA Cabin Log: Successfully loaded announcement: /path/to/sound/en-gb-1.wav + ``` + +--- + +## 8. Dependencies + +- **FlyWithLua**: Required to run the system in flight simulation environments. +- **Python**: Needed for custom TTS announcement generation using OpenAI API. +- **SimBrief**: Required to retrieve real-time flight data for accurate announcements. + +--- + +By following this user guide, you can set up and operate the XA Cabin System to simulate realistic and customizable flight announcements, tailored to each flight's specific conditions. + diff --git a/xa-cabin/docs/announcements.md b/xa-cabin/docs/announcements.md index c977587..83c6401 100644 --- a/xa-cabin/docs/announcements.md +++ b/xa-cabin/docs/announcements.md @@ -1,154 +1,159 @@ -ANNOUNCEMENTS Module Documentation + +# ANNOUNCEMENTS Module Documentation This Lua module provides functionality to manage and play flight announcements. It handles loading, playing, stopping, and queuing sounds with support for debounce logic to prevent repetitive playback within a short time frame. The script is designed to be integrated with a cabin announcement system, tracking the phases of a flight and ensuring smooth audio transitions between different announcements. -Features +## Features - • Debounce Logic: Prevents rapid consecutive plays of the same announcement within a configurable duration. - • Async Sound Handling: Manages the loading and playing of sounds asynchronously to avoid conflicts between announcements. - • Announcement Phases: Supports a variety of flight phases, from pre-boarding to post-landing. - • Pending Sound Queue: Handles pending sounds that may have been triggered while the system was loading. - • Safety Demo Handling: Special flag to manage the playing state of the safety demonstration. - • Sound Stop Functionality: Ensures all sounds are stopped before playing a new one to avoid overlapping audios. +- **Debounce Logic**: Prevents rapid consecutive plays of the same announcement within a configurable duration. +- **Async Sound Handling**: Manages the loading and playing of sounds asynchronously to avoid conflicts between announcements. +- **Announcement Phases**: Supports a variety of flight phases, from pre-boarding to post-landing. +- **Pending Sound Queue**: Handles pending sounds that may have been triggered while the system was loading. +- **Safety Demo Handling**: Special flag to manage the playing state of the safety demonstration. +- **Sound Stop Functionality**: Ensures all sounds are stopped before playing a new one to avoid overlapping audios. -Announcement Phases +## Announcement Phases The following announcement phases are supported: - • Pre-Boarding - • Boarding - • Boarding Complete - • Safety Demonstration - • Takeoff - • Climb - • Cruise - • Prepare for Landing - • Final Approach - • Post Landing - • Emergency +- Pre-Boarding +- Boarding +- Boarding Complete +- Safety Demonstration +- Takeoff +- Climb +- Cruise +- Prepare for Landing +- Final Approach +- Post Landing +- Emergency These phases can be expanded or customized as needed. -Functions +## Functions -ANNOUNCEMENTS.play_sound(announcement_name) +### `ANNOUNCEMENTS.play_sound(announcement_name)` Plays the specified announcement sound. - • Parameters: - • announcement_name (string): The name of the announcement phase to play. - • Logic: - • Checks for any currently playing announcement and stops it before playing the new one. - • Implements debounce logic to prevent multiple plays of the same announcement within a short duration. - • Updates the last play time for the announcement. - • Logs the status of the play action, whether successful or if the sound file is not found. +- **Parameters**: + - `announcement_name` (string): The name of the announcement phase to play. +- **Logic**: + - Checks for any currently playing announcement and stops it before playing the new one. + - Implements debounce logic to prevent multiple plays of the same announcement within a short duration. + - Updates the last play time for the announcement. + - Logs the status of the play action, whether successful or if the sound file is not found. -ANNOUNCEMENTS.stopSounds() +### `ANNOUNCEMENTS.stopSounds()` Stops all currently playing sounds. - • Functionality: - • Iterates through all phases and stops any sounds that are currently playing. - • Resets the is_announcement_playing flag. +- **Functionality**: + - Iterates through all phases and stops any sounds that are currently playing. + - Resets the `is_announcement_playing` flag. -ANNOUNCEMENTS.unloadAllSounds() +### `ANNOUNCEMENTS.unloadAllSounds()` Unloads all loaded sounds from memory. - • Functionality: - • Marks all loaded sounds as false to release them. - • Logs the unloading process for each sound. +- **Functionality**: + - Marks all loaded sounds as false to release them. + - Logs the unloading process for each sound. -ANNOUNCEMENTS.loadSounds() +### `ANNOUNCEMENTS.loadSounds()` Loads the announcement sounds from the specified directory and marks them as ready. - • Logic: - • Attempts to load .wav files for each announcement phase from the predefined directory. - • Logs success or failure of the loading process for each announcement. - • If loading is successful, plays any pending sounds queued during the loading process. +- **Logic**: + - Attempts to load `.wav` files for each announcement phase from the predefined directory. + - Logs success or failure of the loading process for each announcement. + - If loading is successful, plays any pending sounds queued during the loading process. -ANNOUNCEMENTS.on_sound_complete() +### `ANNOUNCEMENTS.on_sound_complete()` Called when an announcement finishes playing to reset the playing state. - • Functionality: - • Resets the is_announcement_playing flag to allow subsequent sounds to be played. +- **Functionality**: + - Resets the `is_announcement_playing` flag to allow subsequent sounds to be played. -ANNOUNCEMENTS.play_pending_sounds() +### `ANNOUNCEMENTS.play_pending_sounds()` Plays any sounds that were queued while sounds were being loaded. - • Functionality: - • Iterates through the pending sounds queue and plays each sound. - • Clears the queue after all sounds are played. +- **Functionality**: + - Iterates through the pending sounds queue and plays each sound. + - Clears the queue after all sounds are played. -Variables +## Variables -debounce_duration +### `debounce_duration` - • Type: Number - • Description: The time (in seconds) to prevent repeated playing of the same announcement. Default is set to 5 seconds. +- **Type**: Number +- **Description**: The time (in seconds) to prevent repeated playing of the same announcement. Default is set to 5 seconds. -last_play_times +### `last_play_times` - • Type: Table - • Description: Tracks the last play time for each announcement to enforce debounce logic. +- **Type**: Table +- **Description**: Tracks the last play time for each announcement to enforce debounce logic. -is_announcement_playing +### `is_announcement_playing` - • Type: Boolean - • Description: Flag to track if any announcement is currently playing. Prevents overlap of announcements. +- **Type**: Boolean +- **Description**: Flag to track if any announcement is currently playing. Prevents overlap of announcements. -ANNOUNCEMENTS_DIR +### `ANNOUNCEMENTS_DIR` - • Type: String - • Description: The directory where all announcement sound files are stored. +- **Type**: String +- **Description**: The directory where all announcement sound files are stored. -ANNOUNCEMENTS.sounds +### `ANNOUNCEMENTS.sounds` - • Type: Table - • Description: Stores the loaded sound handles for each announcement phase. +- **Type**: Table +- **Description**: Stores the loaded sound handles for each announcement phase. -ANNOUNCEMENTS.files +### `ANNOUNCEMENTS.files` - • Type: Table - • Description: Stores the file paths for each loaded sound. +- **Type**: Table +- **Description**: Stores the file paths for each loaded sound. -ANNOUNCEMENTS.pending_sounds +### `ANNOUNCEMENTS.pending_sounds` - • Type: Table - • Description: Queues sounds triggered during the loading process to be played once loading is complete. +- **Type**: Table +- **Description**: Queues sounds triggered during the loading process to be played once loading is complete. -ANNOUNCEMENTS.is_loaded +### `ANNOUNCEMENTS.is_loaded` - • Type: Boolean - • Description: Indicates whether all announcements have been successfully loaded. +- **Type**: Boolean +- **Description**: Indicates whether all announcements have been successfully loaded. -ANNOUNCEMENTS.is_safety_demo_playing +### `ANNOUNCEMENTS.is_safety_demo_playing` - • Type: Boolean - • Description: Tracks whether the safety demonstration announcement is currently playing to manage its specific state. +- **Type**: Boolean +- **Description**: Tracks whether the safety demonstration announcement is currently playing to manage its specific state. -Logging +## Logging -The module logs various actions and states (e.g., sound loading, sound playing, debounce skipping, etc.) using the XA_CABIN_LOGGER.write_log function, which is expected to handle logging in the cabin system. Make sure this logger is set up in your environment for proper debugging and tracking. +The module logs various actions and states (e.g., sound loading, sound playing, debounce skipping, etc.) using the `XA_CABIN_LOGGER.write_log` function, which is expected to handle logging in the cabin system. Make sure this logger is set up in your environment for proper debugging and tracking. -Directory Structure +## Directory Structure The announcement files are expected to follow this directory structure: +``` /xa-cabin/announcements/ pre_boarding/ boarding/ boarding_complete/ ... +``` -Each subdirectory contains the respective .wav files for different language and accent combinations, such as: +Each subdirectory contains the respective `.wav` files for different language and accent combinations, such as: +``` en-gb-1.wav en-us-1.wav +``` -Integration +## Integration -This module is designed to be integrated with a cabin system that handles playing sounds during a flight’s lifecycle. Ensure the announcement files are correctly placed in the ANNOUNCEMENTS_DIR and that the logger (XA_CABIN_LOGGER) is properly set up for the script to function as expected. \ No newline at end of file +This module is designed to be integrated with a cabin system that handles playing sounds during a flight’s lifecycle. Ensure the announcement files are correctly placed in the `ANNOUNCEMENTS_DIR` and that the logger (`XA_CABIN_LOGGER`) is properly set up for the script to function as expected. diff --git a/xa-cabin/docs/generate_announcements.md b/xa-cabin/docs/generate_announcements.md index 9033463..f64105c 100644 --- a/xa-cabin/docs/generate_announcements.md +++ b/xa-cabin/docs/generate_announcements.md @@ -1,103 +1,119 @@ -Flight Announcement TTS Generator + +# Flight Announcement TTS Generator This Python script generates flight announcements as audio files using OpenAI’s text-to-speech (TTS) API. The generated audio files are saved in .wav format and cover various phases of a flight, such as boarding, takeoff, landing, and more. The script supports multiple languages and accents, and allows custom voice options for generating announcements. -Features +## Features + +- Generates pre-defined flight announcements (e.g., boarding, safety, takeoff, landing) based on flight information. +- Supports multiple languages and accents. +- Converts generated audio to .wav format (44.1 kHz, mono, 16-bit PCM). +- Saves audio files in structured directories based on announcement phases. +- Asynchronous generation of TTS files using threading. - • Generates pre-defined flight announcements (e.g., boarding, safety, takeoff, landing) based on flight information. - • Supports multiple languages and accents. - • Converts generated audio to .wav format (44.1 kHz, mono, 16-bit PCM). - • Saves audio files in structured directories based on announcement phases. - • Asynchronous generation of TTS files using threading. +## Requirements -Requirements +- Python 3.x +- OpenAI API key +- `pydub` for audio processing +- FFmpeg and FFprobe installed on your system (required by `pydub` for audio conversion) - • Python 3.x - • OpenAI API key - • pydub for audio processing - • FFmpeg and FFprobe installed on your system (required by pydub for audio conversion) +## Python Libraries -Python Libraries +- `openai` +- `pydub` - • openai - • pydub +## Setup -Setup +### 1. Install required dependencies: - 1. Install required dependencies: Install the required Python packages using pip: +```bash pip install openai pydub +``` +### 2. Install FFmpeg: - 2. Install FFmpeg: Make sure you have FFmpeg and FFprobe installed. You can install them using brew (on macOS) or other package managers: +```bash brew install ffmpeg +``` +### 3. Set FFmpeg and FFprobe paths: - 3. Set FFmpeg and FFprobe paths: Update the paths in the script to point to your FFmpeg and FFprobe installation: +```python AudioSegment.converter = "/path/to/ffmpeg" AudioSegment.ffprobe = "/path/to/ffprobe" +``` - - -Usage +## Usage To run the script, you need to pass flight-related information as command-line arguments. Here is the expected format of the arguments: +```bash python script_name.py +``` -Example: +### Example: +```bash python script_name.py "XXX Airlines" "DAT01" "Paris" "14:30" "en" "us" "01" "Runway 25R" "Runway 27L" "35,000 ft" "2" "45" "10:30 AM" "15" "Boeing 737" "morning" +``` -Generated Announcements: +## Generated Announcements: The script generates announcements for the following flight phases: - • Pre-Boarding - • Boarding - • Boarding Complete - • Safety Demonstration - • Takeoff - • Climb - • Cruise - • Prepare for Landing - • Final Approach - • Post Landing - • Emergency - -Configuration - -You can modify the default data passed to the TTS system by updating the fms_data dictionary in the script. The script supports the following data fields: - - • airline: Name of the airline. - • flight_number: Flight number. - • destination: Destination of the flight. - • eta: Estimated time of arrival. - • language: Language for the announcement (e.g., “en” for English). - • accent: Accent for the announcement (e.g., “us” for American English). - • speaker: Speaker ID for OpenAI TTS (if applicable). - • departure_runway: Departure runway. - • arrival_runway: Arrival runway. - • cruise_altitude: Altitude during cruise. - • flight_time_hours: Flight time in hours. - • flight_time_minutes: Flight time in minutes. - • local_time_at_destination: Local time at the destination. - • aircraft_type: Type of the aircraft. - • time_of_day: Time of day (e.g., “morning”, “afternoon”). - -Output - -Generated audio files will be saved in a structured directory under the announcements/ folder in the script directory. Each announcement phase will have its own subdirectory. - -License +- Pre-Boarding +- Boarding +- Boarding Complete +- Safety Demonstration +- Takeoff +- Climb +- Cruise +- Prepare for Landing +- Final Approach +- Post Landing +- Emergency + +## Configuration + +You can modify the default data passed to the TTS system by updating the `fms_data` dictionary in the script. The script supports the following data fields: + +- `airline`: Name of the airline. +- `flight_number`: Flight number. +- `destination`: Destination of the flight. +- `eta`: Estimated time of arrival. +- `language`: Language for the announcement (e.g., “en” for English). +- `accent`: Accent for the announcement (e.g., “us” for American English). +- `speaker`: Speaker ID for OpenAI TTS (if applicable). +- `departure_runway`: Departure runway. +- `arrival_runway`: Arrival runway. +- `cruise_altitude`: Altitude during cruise. +- `flight_time_hours`: Flight time in hours. +- `flight_time_minutes`: Flight time in minutes. +- `local_time_at_destination`: Local time at the destination. +- `aircraft_type`: Type of the aircraft. +- `time_of_day`: Time of day (e.g., “morning”, “afternoon”). + +## Supported languages + +The TTS model generally follows the Whisper model in terms of language support. Whisper supports the following languages and performs well despite the current voices being optimized for English: + +Afrikaans, Arabic, Armenian, Azerbaijani, Belarusian, Bosnian, Bulgarian, Catalan, Chinese, Croatian, Czech, Danish, Dutch, English, Estonian, Finnish, French, Galician, German, Greek, Hebrew, Hindi, Hungarian, Icelandic, Indonesian, Italian, Japanese, Kannada, Kazakh, Korean, Latvian, Lithuanian, Macedonian, Malay, Marathi, Maori, Nepali, Norwegian, Persian, Polish, Portuguese, Romanian, Russian, Serbian, Slovak, Slovenian, Spanish, Swahili, Swedish, Tagalog, Tamil, Thai, Turkish, Ukrainian, Urdu, Vietnamese, and Welsh. + +## Output + +Generated audio files will be saved in a structured directory under the `announcements/` folder in the script directory. Each announcement phase will have its own subdirectory. + +## License This project is open-source. Feel free to modify and use it as needed. -Disclaimer +## Disclaimer -Ensure that your OpenAI API key is kept private and secure. Do not hardcode your API key in shared repositories. \ No newline at end of file +Ensure that your OpenAI API key is kept private and secure. Do not hardcode your API key in shared repositories. diff --git a/xa-cabin/docs/state.md b/xa-cabin/docs/state.md index b421111..73a10f9 100644 --- a/xa-cabin/docs/state.md +++ b/xa-cabin/docs/state.md @@ -1,120 +1,136 @@ -XA Cabin Flight + +# XA Cabin Flight This script handles various states of flight and cabin operations in an aircraft simulation environment. It monitors flight status (e.g., parked, takeoff, cruise) and cabin activities (e.g., boarding, safety demonstration) and triggers state changes based on certain conditions. -Features +## Features - • Flight Phases: Automatically detects the current flight phase (e.g., parked, takeoff, climb) and transitions between phases based on conditions such as speed, altitude, and engine power. - • Cabin States: Tracks cabin activities like boarding, safety demonstrations, and announcements, synchronizing them with flight phases. - • Debouncing: Prevents rapid state changes with a configurable debounce threshold. - • Logging: Detailed logs for debugging and auditing state transitions. - • Automated Announcements: Plays cabin announcements based on flight and cabin states, such as boarding complete, takeoff, and landing preparation. +- **Flight Phases**: Automatically detects the current flight phase (e.g., parked, takeoff, climb) and transitions between phases based on conditions such as speed, altitude, and engine power. +- **Cabin States**: Tracks cabin activities like boarding, safety demonstrations, and announcements, synchronizing them with flight phases. +- **Debouncing**: Prevents rapid state changes with a configurable debounce threshold. +- **Logging**: Detailed logs for debugging and auditing state transitions. +- **Automated Announcements**: Plays cabin announcements based on flight and cabin states, such as boarding complete, takeoff, and landing preparation. -Flight States +## Flight States The script tracks the following flight states to manage transitions during the flight: - 1. Parked - • Initial state when the aircraft is on the ground with engines off or at idle and the parking brake applied. - • Triggers: - • Ground speed < 1 m/s, altitude AGL < 10 ft, engine N1 < 30%. - • If doors are open, the state transitions to pre_boarding. If doors are closed, it transitions to boarding_complete. - 2. Taxi Out - • The aircraft is preparing for takeoff and moving on the ground. - • Triggers: - • Ground speed > 5 m/s and gear force > 1. - • Engine N1 > 75%. - 3. Takeoff - • The aircraft is accelerating for takeoff. - • Triggers: - • N1 exceeds 75% and vertical speed (VS) > 200 ft/min, gear force < 1. - 4. Climb - • The aircraft is climbing after takeoff. - • Triggers: - • Vertical speed (VS) remains stable between -500 and +500 ft/min for an extended period. - 5. Cruise - • The aircraft has reached cruising altitude and maintains stable vertical speed. - • Triggers: - • VS is stable between -500 and +500 ft/min for over 15 consecutive checks. - 6. Descent - • The aircraft starts descending toward the destination. - • Triggers: - • VS < -500 ft/min for over 15 consecutive checks. - 7. Approach - • The aircraft is in the approach phase, preparing for landing. - • Triggers: - • Altitude AGL < 800 feet, gear deployed, VS < -200 ft/min. - 8. Taxi In - • The aircraft has landed and is taxiing to the gate. - • Triggers: - • Ground speed < 50/1.9 m/s and gear force > 10. - 9. Parked (After Taxi In) - • The aircraft is parked at the gate after landing. - • Triggers: - • Ground speed < 1/1.9 m/s and N1 > 15%. - -Cabin States +1. **Parked** + - Initial state when the aircraft is on the ground with engines off or at idle and the parking brake applied. + - **Triggers**: + - Ground speed < 1 m/s, altitude AGL < 10 ft, engine N1 < 30%. + - If doors are open, the state transitions to pre_boarding. If doors are closed, it transitions to boarding_complete. + +2. **Taxi Out** + - The aircraft is preparing for takeoff and moving on the ground. + - **Triggers**: + - Ground speed > 5 m/s and gear force > 1. + - Engine N1 > 75%. + +3. **Takeoff** + - The aircraft is accelerating for takeoff. + - **Triggers**: + - N1 exceeds 75% and vertical speed (VS) > 200 ft/min, gear force < 1. + +4. **Climb** + - The aircraft is climbing after takeoff. + - **Triggers**: + - Vertical speed (VS) remains stable between -500 and +500 ft/min for an extended period. + +5. **Cruise** + - The aircraft has reached cruising altitude and maintains stable vertical speed. + - **Triggers**: + - VS is stable between -500 and +500 ft/min for over 15 consecutive checks. + +6. **Descent** + - The aircraft starts descending toward the destination. + - **Triggers**: + - VS < -500 ft/min for over 15 consecutive checks. + +7. **Approach** + - The aircraft is in the approach phase, preparing for landing. + - **Triggers**: + - Altitude AGL < 800 feet, gear deployed, VS < -200 ft/min. + +8. **Taxi In** + - The aircraft has landed and is taxiing to the gate. + - **Triggers**: + - Ground speed < 50/1.9 m/s and gear force > 10. + +9. **Parked (After Taxi In)** + - The aircraft is parked at the gate after landing. + - **Triggers**: + - Ground speed < 1/1.9 m/s and N1 > 15%. + +## Cabin States The script tracks cabin states to manage activities like boarding, safety demonstrations, and announcements. Cabin state transitions are triggered by flight state changes and door status: - 1. Pre-Boarding - • Initial state before passengers board. - • Triggers: - • Doors open, flight parked. - 2. Boarding - • Passengers are boarding. - • Triggers: - • Random delay after doors open and flight is parked. - 3. Boarding Complete - • Boarding is complete, and preparations for departure begin. - • Triggers: - • Doors closed, aircraft ready for taxi-out. - 4. Safety Demonstration - • Safety demonstration announcement. - • Triggers: - • Taxi-out begins, landing lights on. - 5. Takeoff (Cabin) - • Cabin secured for takeoff. - • Triggers: - • Takeoff initiated after safety demo. - 6. Climb (Cabin) - • Cabin is in the climb phase. - • Triggers: - • The flight enters the climb phase. - 7. Cruise (Cabin) - • Cabin is in the cruise phase. - • Triggers: - • The flight reaches the cruise phase. - 8. Prepare for Landing - • Cabin prepares for landing. - • Triggers: - • The flight enters descent. - 9. Final Approach - • Cabin secured for landing. - • Triggers: - • The flight enters the approach phase. - 10. Post Landing - - • Post-landing procedures. - • Triggers: - • The flight state changes to taxi-in after landing. - -State Change Debouncing +1. **Pre-Boarding** + - Initial state before passengers board. + - **Triggers**: + - Doors open, flight parked. + +2. **Boarding** + - Passengers are boarding. + - **Triggers**: + - Random delay after doors open and flight is parked. + +3. **Boarding Complete** + - Boarding is complete, and preparations for departure begin. + - **Triggers**: + - Doors closed, aircraft ready for taxi-out. + +4. **Safety Demonstration** + - Safety demonstration announcement. + - **Triggers**: + - Taxi-out begins, landing lights on. + +5. **Takeoff (Cabin)** + - Cabin secured for takeoff. + - **Triggers**: + - Takeoff initiated after safety demo. + +6. **Climb (Cabin)** + - Cabin is in the climb phase. + - **Triggers**: + - The flight enters the climb phase. + +7. **Cruise (Cabin)** + - Cabin is in the cruise phase. + - **Triggers**: + - The flight reaches the cruise phase. + +8. **Prepare for Landing** + - Cabin prepares for landing. + - **Triggers**: + - The flight enters descent. + +9. **Final Approach** + - Cabin secured for landing. + - **Triggers**: + - The flight enters the approach phase. + +10. **Post Landing** + - Post-landing procedures. + - **Triggers**: + - The flight state changes to taxi-in after landing. + +## State Change Debouncing To prevent frequent state changes due to transient conditions, the script implements a debouncing mechanism: - • States cannot change more than once within a configurable time (default: 5 seconds). - • This helps avoid rapid, recursive state changes that could destabilize the simulation. +- States cannot change more than once within a configurable time (default: 5 seconds). +- This helps avoid rapid, recursive state changes that could destabilize the simulation. -Logging +## Logging -All state transitions and actions are logged using XA_CABIN_LOGGER for debugging and auditing purposes. These logs help track the flow of state changes throughout the flight and cabin procedures. +All state transitions and actions are logged using `XA_CABIN_LOGGER` for debugging and auditing purposes. These logs help track the flow of state changes throughout the flight and cabin procedures. -Usage +## Usage To use the script, ensure that the required datarefs are properly initialized, and the announcements are correctly loaded. The script will automatically detect and manage flight and cabin states based on flight conditions and cabin operations. -Announcements +## Announcements Announcements are played according to cabin state transitions. Make sure to include the appropriate sound files in the designated directory for each state. If a sound file is not found, a log entry will indicate the missing announcement. - diff --git a/xa-cabin/docs/xa-cabin.md b/xa-cabin/docs/xa-cabin.md index 57ae856..c4ab5f5 100644 --- a/xa-cabin/docs/xa-cabin.md +++ b/xa-cabin/docs/xa-cabin.md @@ -1,121 +1,124 @@ -XA Cabin System + +# XA Cabin System This Lua script manages a comprehensive cabin announcement and configuration system for flight simulations using FlyWithLua. It integrates various components such as settings, logging, GUI, SimBrief data, and announcement generation, ensuring smooth and customizable flight announcements in multiple phases of a flight. -Features +## Features - • Dynamic Announcements: Generates flight announcements based on SimBrief data, including information such as airline, flight number, destination, estimated time of arrival (ETA), departure runway, and more. - • Flight Phases Support: Provides announcements for pre-defined flight phases (e.g., boarding, takeoff, cruise, landing). - • Debounce Logic: Prevents repetitive announcement playback within a short time frame. - • Custom Language Support: Allows custom announcement generation using Python if the language is set to “custom”. - • Real-time Updates: Tracks and updates the flight and cabin state throughout the flight. - • Graphical User Interface (GUI): Displays flight and cabin information in an interactive window using ImGui. - • Plane Configuration Management: Automatically loads and handles plane-specific configurations. - • SimBrief Integration: Retrieves flight data directly from SimBrief to generate accurate and up-to-date announcements. +- **Dynamic Announcements**: Generates flight announcements based on SimBrief data, including information such as airline, flight number, destination, estimated time of arrival (ETA), departure runway, and more. +- **Flight Phases Support**: Provides announcements for pre-defined flight phases (e.g., boarding, takeoff, cruise, landing). +- **Debounce Logic**: Prevents repetitive announcement playback within a short time frame. +- **Custom Language Support**: Allows custom announcement generation using Python if the language is set to “custom”. +- **Real-time Updates**: Tracks and updates the flight and cabin state throughout the flight. +- **Graphical User Interface (GUI)**: Displays flight and cabin information in an interactive window using ImGui. +- **Plane Configuration Management**: Automatically loads and handles plane-specific configurations. +- **SimBrief Integration**: Retrieves flight data directly from SimBrief to generate accurate and up-to-date announcements. -Components +## Components -1. Loading and Initialization +### 1. Loading and Initialization The script initializes by loading required components, settings, and plane configurations: - • LIP: For managing INI files and configurations. - • Logging: Custom logging system for debugging and tracking cabin system behavior. - • Global Variables: Loads necessary global variables to ensure smooth operation. +- **LIP**: For managing INI files and configurations. +- **Logging**: Custom logging system for debugging and tracking cabin system behavior. +- **Global Variables**: Loads necessary global variables to ensure smooth operation. -2. Announcement Management +### 2. Announcement Management -The system manages the loading, playing, stopping, and queuing of announcement sounds using the ANNOUNCEMENTS module. It ensures no overlapping sounds and provides clear logging for all sound events. +The system manages the loading, playing, stopping, and queuing of announcement sounds using the `ANNOUNCEMENTS` module. It ensures no overlapping sounds and provides clear logging for all sound events. - • Announcement Phases: The following phases are supported: - • Pre-boarding - • Boarding - • Boarding Complete - • Safety Demonstration - • Takeoff - • Climb - • Cruise - • Prepare for Landing - • Final Approach - • Post Landing - • Emergency - • Debounce: A debounce system is in place to avoid rapid consecutive plays of the same announcement within a short period (configurable via debounce_duration). +- **Announcement Phases**: The following phases are supported: + - Pre-boarding + - Boarding + - Boarding Complete + - Safety Demonstration + - Takeoff + - Climb + - Cruise + - Prepare for Landing + - Final Approach + - Post Landing + - Emergency +- **Debounce**: A debounce system is in place to avoid rapid consecutive plays of the same announcement within a short period (configurable via `debounce_duration`). -3. SimBrief Data Integration +### 3. SimBrief Data Integration The script retrieves key flight data from SimBrief, including: - • Airline name - • Flight number and callsign - • Destination and origin runways - • ETA, flight duration, and time of day classification (morning, afternoon, night). +- Airline name +- Flight number and callsign +- Destination and origin runways +- ETA, flight duration, and time of day classification (morning, afternoon, night). This data is then used to dynamically generate announcements using the Python-based TTS generator if the language is set to “custom”. -4. GUI Integration +### 4. GUI Integration The script includes a graphical interface built with ImGui, providing an easy-to-use window that displays flight information, cabin configuration options, and controls for playing announcements. - • Window Control: Functions to show, hide, and toggle the visibility of the cabin window (xa_cabin_show_wnd, xa_cabin_hide_wnd, toggle_xa_cabin_window). - • Customizable GUI: Displays SimBrief information, announcement settings, and allows control over flight states and cabin settings. +- **Window Control**: Functions to show, hide, and toggle the visibility of the cabin window (`xa_cabin_show_wnd`, `xa_cabin_hide_wnd`, `toggle_xa_cabin_window`). +- **Customizable GUI**: Displays SimBrief information, announcement settings, and allows control over flight states and cabin settings. -5. Custom Python TTS Announcements +### 5. Custom Python TTS Announcements -When custom announcements are needed (e.g., for non-standard languages or accents), the script calls a Python script (generate_announcements.py) to create TTS audio files. This integration allows flexibility in generating personalized flight announcements. +When custom announcements are needed (e.g., for non-standard languages or accents), the script calls a Python script (`generate_announcements.py`) to create TTS audio files. This integration allows flexibility in generating personalized flight announcements. -6. Configuration Management +### 6. Configuration Management -The script handles reading and saving the configuration from a plane-specific INI file (xa-cabin.ini). If the file does not exist, it creates a new one with default values. The configuration includes settings such as the type of aircraft, preferred runways, language, and accent for announcements. +The script handles reading and saving the configuration from a plane-specific INI file (`xa-cabin.ini`). If the file does not exist, it creates a new one with default values. The configuration includes settings such as the type of aircraft, preferred runways, language, and accent for announcements. -7. Real-time State Updates +### 7. Real-time State Updates -The system continuously updates the flight and cabin states in real-time using the xa_cabin_update_state function, ensuring that the cabin announcements and GUI reflect the current flight conditions. +The system continuously updates the flight and cabin states in real-time using the `xa_cabin_update_state` function, ensuring that the cabin announcements and GUI reflect the current flight conditions. -Usage +## Usage -Running the Script +### Running the Script To use this script, it must be placed in the appropriate FlyWithLua directory structure. The script relies on several components, including Python for TTS generation and sound handling, so ensure your environment is correctly set up: - 1. Place all required Lua files (e.g., LIP.lua, logging.lua, helpers.lua, etc.) in the xa-cabin/ directory. - 2. Ensure Python is installed and correctly configured to run TTS generation (if using custom language). - 3. Make sure the sound files are correctly placed in the xa-cabin/announcements/ directory. - 4. Load the script using FlyWithLua. +1. Place all required Lua files (e.g., `LIP.lua`, `logging.lua`, `helpers.lua`, etc.) in the `xa-cabin/` directory. +2. Ensure Python is installed and correctly configured to run TTS generation (if using custom language). +3. Make sure the sound files are correctly placed in the `xa-cabin/announcements/` directory. +4. Load the script using FlyWithLua. -Customizing Announcements +### Customizing Announcements If custom announcements are required: - 1. Set the language to “custom” in the configuration (xa-cabin.ini). - 2. Ensure the Python script for TTS generation (generate_announcements.py) is correctly configured. - 3. The script will dynamically generate announcements using the command and Python script provided. +1. Set the language to “custom” in the configuration (`xa-cabin.ini`). +2. Ensure the Python script for TTS generation (`generate_announcements.py`) is correctly configured. +3. The script will dynamically generate announcements using the command and Python script provided. -GUI Interaction +### GUI Interaction To interact with the cabin system GUI, use the following commands: - • Show/Hide Window: Use the XA Cabin macro or the created command xa_cabin_menus/show_toggle to open and close the GUI. +- **Show/Hide Window**: Use the `XA Cabin` macro or the created command `xa_cabin_menus/show_toggle` to open and close the GUI. -Example SimBrief Configuration +### Example SimBrief Configuration The system pulls data from SimBrief to generate accurate flight information. Ensure you have valid SimBrief data for realistic announcements. -Logs +## Logs -All actions, including sound events, error handling, and state updates, are logged using the XA_CABIN_LOGGER, which records all operations for troubleshooting and debugging. +All actions, including sound events, error handling, and state updates, are logged using the `XA_CABIN_LOGGER`, which records all operations for troubleshooting and debugging. -Example Log Entry: +### Example Log Entry: +``` XA Cabin Log: Successfully loaded announcement: /path/to/sound/en-gb-1.wav +``` -Dependencies +## Dependencies - • FlyWithLua: This script is designed to work within the FlyWithLua environment. - • SimBrief: For retrieving real-time flight data. - • Python: Required for generating custom TTS announcements using the OpenAI API. +- **FlyWithLua**: This script is designed to work within the FlyWithLua environment. +- **SimBrief**: For retrieving real-time flight data. +- **Python**: Required for generating custom TTS announcements using the OpenAI API. -License +## License This script is open-source. You are free to modify and distribute it under the terms of the applicable license. -By using this cabin system, you can simulate realistic and customizable cabin announcements, tailored to the specifics of each flight using real-world data from SimBrief. \ No newline at end of file +By using this cabin system, you can simulate realistic and customizable cabin announcements, tailored to the specifics of each flight using real-world data from SimBrief. diff --git a/xa-cabin/generate_announcements.py b/xa-cabin/generate_announcements.py index 8b55126..4a7f0c9 100644 --- a/xa-cabin/generate_announcements.py +++ b/xa-cabin/generate_announcements.py @@ -134,8 +134,6 @@ def generate_tts(phase, text, language, accent, speaker): os.remove(file_path) print(f"Removed existing file: {file_path}") - # Generate the new audio file using OpenAI's streaming TTS API - # Generate the new audio file using OpenAI's streaming TTS API try: with client.audio.speech.with_streaming_response.create( From bd5b149c90dd284ebe0378a9cad4f6ae51ab5124 Mon Sep 17 00:00:00 2001 From: Ricardo Borenstein <78426391+ricardoborenstein@users.noreply.github.com> Date: Mon, 14 Oct 2024 13:56:11 +0200 Subject: [PATCH 08/11] Update README.md --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 9a3efad..85d27db 100644 --- a/README.md +++ b/README.md @@ -108,9 +108,9 @@ To add a custom sound pack: ``` [announcement] -language=en -accent=gb -speaker=1 +language=english +accent=nova +speaker=custom ``` --- From 2fbee4ca88c24ca45a6b4a2df21654d5b30302a0 Mon Sep 17 00:00:00 2001 From: Ricardo Borenstein Date: Mon, 14 Oct 2024 15:48:09 +0200 Subject: [PATCH 09/11] adding volume control --- xa-cabin.lua | 8 ++++---- xa-cabin/GUI.lua | 27 +++++++++++++++++++++++++-- xa-cabin/announcement.lua | 35 +++++++++++++++++++---------------- 3 files changed, 48 insertions(+), 22 deletions(-) diff --git a/xa-cabin.lua b/xa-cabin.lua index 9fb86a1..46c4fab 100644 --- a/xa-cabin.lua +++ b/xa-cabin.lua @@ -166,14 +166,14 @@ function generate_announcements() end local time_of_day = get_time_of_day() -- Language, accent, and speaker from configuration - local language = XA_CABIN_LANGUAGE or "en" - local accent = XA_CABIN_ACCENT or "gb" - local speaker = XA_CABIN_SETTINGS.announcement.speaker or "01" -- Ensure 'speaker' is fetched + local language = XA_CABIN_LANGUAGE or "english" + local accent = XA_CABIN_ACCENT or "Nova" + local speaker = XA_CABIN_SETTINGS.announcement.speaker or "custom" -- Ensure 'speaker' is fetched local python_script_path = SCRIPT_DIRECTORY .. "xa-cabin/generate_announcements.py" -- Only generate audio if language is "custom" - if language == "custom" then + if speaker == "custom" then local python_script_path = SCRIPT_DIRECTORY .. "xa-cabin/generate_announcements.py" -- Updated string.format with all the necessary variables passed to the Python script diff --git a/xa-cabin/GUI.lua b/xa-cabin/GUI.lua index c1933f4..4b6937d 100644 --- a/xa-cabin/GUI.lua +++ b/xa-cabin/GUI.lua @@ -1,7 +1,7 @@ local GUI = {}; local FIRST_ROW_HEIGHT_PERCENT = 0.4 local SECOND_ROW_HEIGHT_PERCENT = (1 - FIRST_ROW_HEIGHT_PERCENT) * 0.85 - +--announcement_volume = 0.5 -- Default to 50% volume if it's not set yet function GUI.SimbriefInfo(win_width, win_height) if imgui.BeginChild("SimbriefInfo", win_width * 0.6, win_height * FIRST_ROW_HEIGHT_PERCENT) then imgui.TextUnformatted("Flight No: ") @@ -94,6 +94,7 @@ function GUI.Configuration(win_width, win_height) imgui.Spacing() imgui.Spacing() + -- Automated Mode Checkbox local currentMode = XA_CABIN_SETTINGS.mode.automated local modeChanged, newMode = imgui.Checkbox("Mode: ", currentMode) if modeChanged then @@ -112,6 +113,7 @@ function GUI.Configuration(win_width, win_height) end imgui.PopStyleColor() + imgui.Spacing() local currentLiveMode = XA_CABIN_SETTINGS.mode.live local liveModeChanged, newLiveMode = imgui.Checkbox("Announcements Generation: ", currentLiveMode) @@ -120,7 +122,7 @@ function GUI.Configuration(win_width, win_height) XA_CABIN_SETTINGS.mode.live = newLiveMode LIP.save(SCRIPT_DIRECTORY .. "xa-cabin.ini", XA_CABIN_SETTINGS) end - + imgui.SameLine() if XA_CABIN_SETTINGS.mode.live then imgui.PushStyleColor(imgui.constant.Col.Text, 0xFF00FF00) @@ -130,6 +132,25 @@ function GUI.Configuration(win_width, win_height) imgui.TextUnformatted("Offline") end imgui.PopStyleColor() + + imgui.Spacing() + + -- Volume Slider Section + imgui.TextUnformatted("Volume: ") + imgui.SameLine() + + -- Ensure announcement_volume is defined and initialized to a reasonable default + announcement_volume = announcement_volume or 0.5 -- Default to 50% if not set + + -- Slider for volume control, displaying as 0% to 100% + local volume_percent = announcement_volume * 100 + local volumeChanged, newVolumePercent = imgui.SliderFloat("", volume_percent, 0.0, 100.0, "%.0f%%") + + -- Check if the slider value changed + if volumeChanged then + announcement_volume = newVolumePercent / 100 -- Convert to 0.0 - 1.0 scale + XA_CABIN_LOGGER.write_log("Slider updated volume to: " .. tostring(announcement_volume)) + end imgui.Spacing() imgui.Spacing() @@ -159,6 +180,8 @@ function GUI.Configuration(win_width, win_height) end imgui.EndChild() end + + function GUI.Announcements(win_width, win_height) if imgui.BeginChild("Announcements", win_width - 32, win_height * SECOND_ROW_HEIGHT_PERCENT) then imgui.SetWindowFontScale(1.2) diff --git a/xa-cabin/announcement.lua b/xa-cabin/announcement.lua index 6833cab..3d431ab 100644 --- a/xa-cabin/announcement.lua +++ b/xa-cabin/announcement.lua @@ -1,6 +1,7 @@ local debounce_duration = 5 -- Set debounce time (in seconds) local last_play_times = {} -- Track last play times for all announcements local is_announcement_playing = false -- Track if any announcement is currently playing +--local announcement_volume = 0.5 -- Default volume set to 50% ANNOUNCEMENTS = { sounds = {}, @@ -29,33 +30,34 @@ local phases = { function ANNOUNCEMENTS.play_sound(announcement_name) local current_time = os.clock() ANNOUNCEMENTS.stopSounds() + -- Check if another announcement is playing, and stop it before playing a new one if is_announcement_playing then - ANNOUNCEMENTS.stopSounds() -- Stop the currently playing sound + ANNOUNCEMENTS.stopSounds() XA_CABIN_LOGGER.write_log("Stopping current sound to play: " .. announcement_name) end - -- Initialize last play time for the announcement if it doesn't exist - if not last_play_times[announcement_name] then - last_play_times[announcement_name] = 0 - end - - -- Debounce logic: Skip if debounce duration has not passed - if current_time - last_play_times[announcement_name] < debounce_duration then - XA_CABIN_LOGGER.write_log("Debounce active for announcement: " .. announcement_name .. ". Skipping.") - return - end - - -- Update last play time and mark announcement as playing - last_play_times[announcement_name] = current_time - is_announcement_playing = true - -- Continue playing the sound XA_CABIN_LOGGER.write_log("Attempting to play sound for: " .. announcement_name) local sound_handle = ANNOUNCEMENTS.sounds[announcement_name] + -- Ensure volume is not nil + if announcement_volume == nil then + announcement_volume = 0.5 -- Default to 50% if not set + XA_CABIN_LOGGER.write_log("Volume was nil. Defaulting to 0.5.") + end + + -- Check if the sound is loaded and set the volume before playing if sound_handle then XA_CABIN_LOGGER.write_log("Playing sound file: " .. ANNOUNCEMENTS.files[announcement_name]) + + -- Log the current volume before setting it + XA_CABIN_LOGGER.write_log("Setting volume to: " .. tostring(announcement_volume)) + + -- Apply volume control + set_sound_gain(sound_handle, announcement_volume) -- Ensure volume control is applied here + + -- Play the sound play_sound(sound_handle) else XA_CABIN_LOGGER.write_log("Sound not found for announcement: " .. announcement_name) @@ -63,6 +65,7 @@ function ANNOUNCEMENTS.play_sound(announcement_name) end end + function ANNOUNCEMENTS.on_sound_complete() is_announcement_playing = false -- Reset the flag to allow the next announcement XA_CABIN_LOGGER.write_log("Announcement has finished playing.") From b45994439fbcb6180ceaaae8e95f153153372e8b Mon Sep 17 00:00:00 2001 From: Ricardo Borenstein Date: Tue, 15 Oct 2024 10:12:20 +0200 Subject: [PATCH 10/11] prevent change state from prepare for landing to cruise during descending --- xa-cabin/state.lua | 59 ++++++++++++++++++++++++---------------------- 1 file changed, 31 insertions(+), 28 deletions(-) diff --git a/xa-cabin/state.lua b/xa-cabin/state.lua index ea4e0c3..8c9ddd2 100644 --- a/xa-cabin/state.lua +++ b/xa-cabin/state.lua @@ -209,47 +209,50 @@ function STATE.update_flight_state() STATE.change_cabin_state("climb", "Resuming climb") return end - - if STATE.descend_counter > 30 then + XA_CABIN_LOGGER.write_log("Vertical Speed: " .. tostring(vs) .. ", Descent Counter: " .. tostring(STATE.descend_counter)) + if STATE.descend_counter > 15 then STATE.change_flight_state("descent", "Vertical Speed < -500 (starting descent)") STATE.change_cabin_state("prepare_for_landing", "Preparing for descent") + return end return end if XA_CABIN_STATES.flight_state.current_state == "descent" then - local vs = XA_CABIN_DATAREFS.VS[0] - local agl = XA_CABIN_DATAREFS.ALT_AGL[0] - local gear_force = XA_CABIN_DATAREFS.GEAR_FORCE[0] - - if vs > 500 then - STATE.climb_counter = math.min(STATE.climb_counter + 1, 30) - else - STATE.climb_counter = 0 - end - - if vs < 500 and vs > -500 then - STATE.cruise_counter = math.min(STATE.cruise_counter + 1, 30) - else - STATE.cruise_counter = 0 - end - - if STATE.climb_counter > 15 then - STATE.change_flight_state("climb", "Climb conditions met during descent") - STATE.change_cabin_state("climb", "Returning to climb") + local vs = XA_CABIN_DATAREFS.VS[0] -- Vertical speed in ft/min + local agl = XA_CABIN_DATAREFS.ALT_AGL[0] -- Altitude above ground level + local gear_force = XA_CABIN_DATAREFS.GEAR_FORCE[0] -- Force on the landing gear + + -- Prevent any transitions back to cruise or climb during descent phase + + -- If aircraft descends below 800 ft AGL, gear is deployed, and VS indicates descent + if agl < 800 and gear_force < 5 and vs < -200 then + -- Transition to approach phase as descent continues + STATE.change_flight_state("approach", "Altitude < 800 AGL and descending") + STATE.change_cabin_state("prepare_for_landing", "Approach started, preparing for landing") return end - - if STATE.cruise_counter > 15 then - STATE.change_flight_state("cruise", "Cruise conditions met during descent") - STATE.change_cabin_state("cruise", "Returning to cruise") + + -- Optionally: Handle further descent or landing detection here if needed + + -- Landing Detection: + -- If the aircraft descends close to ground level (e.g., < 50 ft AGL) and has low vertical speed + if agl < 50 and vs < -100 then + -- Optionally, check that ground speed is low or the gear force is increasing (touchdown) + STATE.change_flight_state("landing", "Aircraft close to the ground, preparing to land") + STATE.change_cabin_state("final_approach", "Landing detected, final approach initiated") return end - - if agl < 800 and gear_force < 5 and vs < -200 then - STATE.change_flight_state("approach", "Altitude < 800 AGL and descending") + + -- Final touchdown detection (Optional): + -- Once the gear force is significant, indicating that the aircraft has landed + if agl < 10 and gear_force > 10 then + STATE.change_flight_state("taxi_in", "Landing complete, aircraft is taxiing") + STATE.change_cabin_state("post_landing", "Landing completed, transitioning to taxi") + return end + return end From 1f6dc49237836b909e77883f5e9190db152a1bb2 Mon Sep 17 00:00:00 2001 From: Ricardo Borenstein Date: Tue, 15 Oct 2024 11:11:06 +0200 Subject: [PATCH 11/11] adding license --- README.md | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/README.md b/README.md index 85d27db..1cca690 100644 --- a/README.md +++ b/README.md @@ -145,3 +145,31 @@ This command generates announcements for a flight based on SimBrief data, with r --- The **AI Powered Cabin Announcement** plugin transforms in-flight audio by integrating AI technology, real-time capabilities, and flexible configuration options for creating a more realistic and engaging flight experience. + +# License + +Creative Commons Attribution-NonCommercial 4.0 International License (CC BY-NC 4.0) + +## You are free to: +- **Share** — copy and redistribute the material in any medium or format. +- **Adapt** — remix, transform, and build upon the material. + +The licensor cannot revoke these freedoms as long as you follow the license terms. + +## Under the following terms: +- **Attribution** — You must give appropriate credit, provide a link to the license, and indicate if changes were made. You may do so in any reasonable manner, but not in any way that suggests the licensor endorses you or your use. +- **NonCommercial** — You may not use the material for commercial purposes. + +## No additional restrictions: +- You may not apply legal terms or technological measures that legally restrict others from doing anything the license permits. + +## Notices: +- You do not have to comply with the license for elements of the material in the public domain or where your use is permitted by an applicable exception or limitation. +- No warranties are given. The license may not give you all of the permissions necessary for your intended use. For example, other rights such as publicity, privacy, or moral rights may limit how you use the material. + +For more details, see [Creative Commons BY-NC 4.0](https://creativecommons.org/licenses/by-nc/4.0/). + + +## Acknowledgement + +This project is a fork of the original open-source **XA-CABIN** plugin developed by [X-Airline](https://github.com/xairline/xa-cabin-fwl). We extend our gratitude to the contributors of the original project for their work and commitment to the flight simulation community. \ No newline at end of file