Skip to main content

kernel/
main.rs

1//! # Rektal Lighting Control Kernel
2//!
3//! This is the main entry point for the Rektal lighting control kernel executable.
4//! It initializes the core logging infrastructure, parses command-line arguments,
5//! boots the fixture engine and DMX output interfaces, activates the TCP networking layer
6//! for client communication, and runs the primary interactive REPL shell.
7mod cli;
8mod networking;
9mod fixture;
10
11use std::io::{self, Write};
12use std::{env, thread};
13use std::time::Duration;
14use common::{r_debug_log, r_log};
15use common::logging::{FileSink, Logger, TerminalSink};
16use common::logging::LogLevel::*;
17use interface::interfaces::dmx_output_loop;
18use crate::cli::run_command;
19
20/// Spawns the background fixture and DMX output worker threads, activates the TCP network socket,
21/// and enters the main REPL (Read-Eval-Print Loop) to process interactive user commands.
22///
23/// # Errors
24///
25/// Returns an `io::Error` if terminal input reading or output flushing fails.
26fn main() -> io::Result<()> {
27
28    let port = get_arguments();
29
30    if cfg!(all(debug_assertions, not(test))) {
31        thread::sleep(Duration::from_millis(1000));
32    }
33
34    Logger::global().add_sink(Box::new(TerminalSink {cli_prompt: Some("> ".into())}));
35    Logger::global().add_sink(Box::new(FileSink::new("kernel.log")));
36
37    #[cfg(all(not(debug_assertions), not(test)))]
38    {
39        ctrlc::set_handler(move || {
40            r_log!(
41            Warning,
42            "Ctrl+C is disabled to prevent data loss. Please type 'exit' to shutdown safely."
43        );
44        })
45            .expect("Error setting Ctrl-C handler");
46    }
47
48
49    let interface_receiver = fixture::FixtureEngine::spawn().expect("Failed to spawn FixtureEngine");
50
51    let _artnet_handle = thread::spawn(|| {
52        dmx_output_loop(interface_receiver).expect("\x1b[31martnet loop failed\x1b[0m");
53    });
54
55
56    networking::activate_socket(port);
57
58
59    loop {
60        io::stdout().flush()?;
61
62        let mut input = String::new();
63        if let Err(e) = io::stdin().read_line(&mut input) {
64            r_log!(UserError, "Terminal input stream contained invalid UTF-8 (e.g. from deleting special characters).\
65             Discarding input. Error: {}", e);
66            continue;
67        }
68
69        let input = input.trim().to_string();
70
71        r_debug_log!(Info, "[Kernel Cli] User input: {}", input);
72
73
74        let response = run_command(true, input);
75
76        r_log!(response.0, "[Kernel Cli] {}", response.1);
77    }
78}
79
80/// Parses command-line arguments passed to the kernel executable.
81///
82/// Supports the `--port [port]` argument to override the default TCP listening port (`6767`).
83/// If an invalid port number or unknown argument is encountered, an error message is printed
84/// and the process exits with a non-zero status code.
85fn get_arguments() -> u16 {
86    // Default values:
87    let mut port: u16 = 6767;
88
89    let args: Vec<String> = env::args().collect();
90    let mut iter = args.iter().skip(1);
91
92    while let Some(arg) = iter.next() {
93        match arg.as_ref() {
94            "--port" => {
95                if let Some(port_str) = iter.next() {
96                    match port_str.parse::<u16>() {
97                        Ok(p) => port = p,
98                        Err(_) => {
99                            println!("Invalid port number: {}", port_str);
100                            thread::sleep(Duration::from_millis(50));
101                            std::process::exit(1);
102                        }
103                    }
104                }
105            }
106
107            _ => {
108                println!("Invalid argument: {}", arg);
109                thread::sleep(Duration::from_millis(50));
110                std::process::exit(1);
111            },
112        }
113    }
114
115    port
116}