diff --git a/README.md b/README.md index 2651421..1cca690 100644 --- a/README.md +++ b/README.md @@ -1,29 +1,30 @@ -# 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. -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. +# AI Powered Cabin Announcement -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. +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. -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. +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. -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. +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 cabin announcement system by leveraging AI technology to deliver superior audio quality, customization options, and dynamic generation capabilities. +--- -# Installation +## Installation -> **NOTE:** FlyWithLua is required +> **Note**: FlyWithLua is required. -To install the plugin, follow these steps: +Follow these steps to install the plugin: 1. Download the plugin files. 2. Unzip the downloaded file. -3. Locate the `FlyWithLua/Scripts` folder in your flight simulator installation directory. +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 the installation, the file structure should look like this: +After installation, the file structure should look like this: ``` Resources @@ -39,71 +40,136 @@ After the installation, the file structure should look like this: xa-cabin.ini ``` -# Configuration -## Global Config -The configuration is stored in xa-cabin.ini. Most configurations are avialable in the GUI +--- + +## Configuration -here are the available options: +### Global Configurations -[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. +The main configuration file is `xa-cabin.ini`, where most options can also be modified through the in-game GUI. -[mode] section: (also in GUI) +#### `[simbrief]` section (also configurable in the 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) +- **`username`**: Specify your SimBrief username to integrate flight planning data. -[announcement] section: +#### `[mode]` 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. +- **`automated`**: Set to `true` to play cabin announcements automatically. +- **`live`**: Set to `true` to enable real-time announcement generation using `generate_announcements.py`. -## 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. +#### `[announcement]` section: -[LANDING_GEAR]: +- **`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`). -- operator: This option specifies the comparison operator used to evaluate the condition. In this case, it is set to ~=, which means "approximately equal to". +### Aircraft-Specific Configuration -- 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. +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. -[DOOR]: +#### Example Configurations: -- 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. +- **`[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`). -[LANDING_LIGHTS]: +- **`[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`). -- 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_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 -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. +## Sound Pack -## List of sound +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. -|language|accent|speaker|description| -|--------|------|-------|-----------| -|en|gb|1|Britsh Accent| -|en|ca|1|Canadian Accent - SAS special| -|en|in|1|Indian Accent| +### List of Available Sounds -## Add Sound Pack +| Language | Accent | Speaker | Description | +|----------|--------|---------|-----------------------------| +| en | gb | 1 | British Accent | +| en | ca | 1 | Canadian Accent (SAS special)| +| en | in | 1 | Indian Accent | -Follow above naming convention, drop your wav files in each folder under `xa-cabin/announcements` +### Adding a Custom Sound Pack -change the configuration in `xa-cabin.ini` +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: ``` [announcement] -language=en -accent=gb -speaker=1 +language=english +accent=nova +speaker=custom ``` -## 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" "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. + +# 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 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.lua b/xa-cabin.lua index fc26ae9..46c4fab 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 "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 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 + 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 85d553d..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() @@ -160,41 +181,64 @@ function GUI.Configuration(win_width, win_height) 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_CABIN_XA_CABIN_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_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]) + 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_CABIN_XA_CABIN_STATES[i + 1] == nil then - break - 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]) + 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_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() + 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 0e046ed..3d431ab 100644 --- a/xa-cabin/announcement.lua +++ b/xa-cabin/announcement.lua @@ -1,25 +1,90 @@ -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 +--local announcement_volume = 0.5 -- Default volume set to 50% + +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(cabin_state) + +function ANNOUNCEMENTS.play_sound(announcement_name) + local current_time = os.clock() ANNOUNCEMENTS.stopSounds() - play_sound(ANNOUNCEMENTS.sounds[cabin_state]) - XA_CABIN_LOGGER.write_log("Playing announcement for " .. ANNOUNCEMENTS.files[cabin_state]) + + -- Check if another announcement is playing, and stop it before playing a new one + if is_announcement_playing then + ANNOUNCEMENTS.stopSounds() + XA_CABIN_LOGGER.write_log("Stopping current sound to play: " .. announcement_name) + end + + -- 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) + 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_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]]) - 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 paris(ANNOUNCEMENTS.sounds) do + for index, _ in pairs(ANNOUNCEMENTS.sounds) do if ANNOUNCEMENTS.sounds[index] then ANNOUNCEMENTS.sounds[index] = false end @@ -27,27 +92,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_CABIN_XA_CABIN_STATES) - for i = 2, #XA_CABIN_CABIN_XA_CABIN_STATES do - local wav_file_path = SCRIPT_DIRECTORY .. - "xa-cabin/announcements/" .. - XA_CABIN_CABIN_XA_CABIN_STATES[i] .. "/" .. 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 - 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 + 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/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 new file mode 100644 index 0000000..83c6401 --- /dev/null +++ b/xa-cabin/docs/announcements.md @@ -0,0 +1,159 @@ + +# 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. diff --git a/xa-cabin/docs/generate_announcements.md b/xa-cabin/docs/generate_announcements.md new file mode 100644 index 0000000..f64105c --- /dev/null +++ b/xa-cabin/docs/generate_announcements.md @@ -0,0 +1,119 @@ + +# 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: + +```bash +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: + +```bash +brew install ffmpeg +``` + +### 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 + +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: + +```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: + +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”). + +## 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 + +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 new file mode 100644 index 0000000..73a10f9 --- /dev/null +++ b/xa-cabin/docs/state.md @@ -0,0 +1,136 @@ + +# 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..c4ab5f5 --- /dev/null +++ b/xa-cabin/docs/xa-cabin.md @@ -0,0 +1,124 @@ + +# 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. diff --git a/xa-cabin/generate_announcements.py b/xa-cabin/generate_announcements.py new file mode 100644 index 0000000..4a7f0c9 --- /dev/null +++ b/xa-cabin/generate_announcements.py @@ -0,0 +1,182 @@ +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 + 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 3e0921a..0eb9c5c 100644 --- a/xa-cabin/globals.lua +++ b/xa-cabin/globals.lua @@ -1,5 +1,8 @@ +-- globals.lua + XA_CABIN_VERSION = "v0.0.1" -XA_CABIN_CABIN_XA_CABIN_STATES = { + +XA_CABIN_ANNOUNCEMENT_STATES = { "Pre-Boarding", "Boarding", "Boarding Complete", @@ -13,19 +16,10 @@ XA_CABIN_CABIN_XA_CABIN_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 1a7c824..8c9ddd2 100644 --- a/xa-cabin/state.lua +++ b/xa-cabin/state.lua @@ -1,311 +1,435 @@ -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 } -function change_flight_state(new_state) - if XA_CABIN_STATES.flight_state[new_state] == nil then - logMsg("Invalid flight state: " .. new_state) - return +-- 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] + 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 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") + 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 - -- 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 - 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 - STATE.cruise_counter = STATE.cruise_counter + 1 - else - STATE.cruise_counter = 0 - end - - if STATE.climb_counter > 15 then - change_flight_state("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 - change_flight_state("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 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") + + -- 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 - -- 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 == "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_STATES.flight_state.current_state == "taxi_in" then + 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 flight 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 - ANNOUNCEMENTS.play_sound(cabin_state_to_CANBIN_XA_CABIN_STATES(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("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 + 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 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] +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", + "emergency" + } + + for index, ann_name in ipairs(XA_CABIN_ANNOUNCEMENT_STATES) do + if ann_name == announcement_name then + return cabin_states[index] + end end + + XA_CABIN_LOGGER.write_log("Unknown announcement name: " .. tostring(announcement_name)) + return nil end -return STATE +function cabin_state_to_announcement_name(cabin_state) + 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 + + XA_CABIN_LOGGER.write_log("Unknown cabin state: " .. tostring(cabin_state)) + return nil +end +ANNOUNCEMENTS.loadSounds() +STATE.initialize_states() +ANNOUNCEMENTS.loadSounds() +return STATE \ No newline at end of file