Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions daemon/src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ pub struct Config {
pub colorblind_mode: bool,
/// Key input mode
pub key_input_mode: u8,
/// Keep the device screen awake (device-specific; currently Orbic)
pub keep_screen_on: bool,
/// ntfy.sh URL
pub ntfy_url: Option<String>,
/// Vector containing the types of enabled notifications
Expand All @@ -46,6 +48,7 @@ impl Default for Config {
ui_level: 1,
colorblind_mode: false,
key_input_mode: 0,
keep_screen_on: false,
analyzers: AnalyzerConfig::default(),
ntfy_url: None,
enabled_notifications: vec![NotificationType::Warning, NotificationType::LowBattery],
Expand Down
72 changes: 72 additions & 0 deletions daemon/src/display/orbic.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,81 @@ use crate::config;
use crate::display::DisplayState;
use crate::display::generic_framebuffer::{self, Dimensions, GenericFramebuffer};
use async_trait::async_trait;
use log::{debug, warn};

use tokio::sync::mpsc::Receiver;
use tokio_util::sync::CancellationToken;
use tokio_util::task::TaskTracker;

const FB_PATH: &str = "/dev/fb0";

// Orbic display sysfs controls discovered on device.
// These are used to re-enable the display after the stock UI sleeps it.
const SYSFS_BASE: &str = "/sys/devices/78b6000.spi/spi_master/spi1/spi1.0";
const SYSFS_SLEEP_MODE: &str = "/sys/devices/78b6000.spi/spi_master/spi1/spi1.0/sleep_mode";
const SYSFS_BL_GPIO: &str = "/sys/devices/78b6000.spi/spi_master/spi1/spi1.0/bl_gpio";
const SYSFS_DISPLAY_ON: &str = "/sys/devices/78b6000.spi/spi_master/spi1/spi1.0/display_on";

async fn read_sysfs_bool(path: &str) -> Option<bool> {
match tokio::fs::read_to_string(path).await {
Ok(s) => match s.trim() {
"0" => Some(false),
"1" => Some(true),
_ => None,
},
Err(_) => None,
}
}

//
async fn write_sysfs_one(path: &str) {
if let Err(e) = tokio::fs::write(path, b"1").await {
warn!("failed writing '1' to {path}: {e}");
}
}

fn spawn_keep_screen_on(task_tracker: &TaskTracker, shutdown_token: CancellationToken) {
task_tracker.spawn(async move {
// If the expected sysfs does not exist, do nothing.
if tokio::fs::metadata(SYSFS_BASE).await.is_err() {
warn!("keep_screen_on enabled, but Orbic sysfs path not found: {SYSFS_BASE}");
return;
}

// Poll frequency to catch sleeping.
const POLL_MS: u64 = 500;

loop {
if shutdown_token.is_cancelled() {
break;
}

// On Orbic sleep_mode=0 and bl_gpio=0 indicates the display is sleep.
let sleep_mode = read_sysfs_bool(SYSFS_SLEEP_MODE).await;
let bl_gpio = read_sysfs_bool(SYSFS_BL_GPIO).await;

let should_wake = matches!(sleep_mode, Some(false)) || matches!(bl_gpio, Some(false));

if should_wake {
debug!(
"keep_screen_on: waking display (sleep_mode={:?}, bl_gpio={:?})",
sleep_mode, bl_gpio
);

// Observed wake sequence
// 1) display_on=1 (this has not been observed to change but we set it anyway)
// 2) bl_gpio=1 (backlight)
// 3) sleep_mode=1 (resume UI)
write_sysfs_one(SYSFS_DISPLAY_ON).await;
write_sysfs_one(SYSFS_BL_GPIO).await;
write_sysfs_one(SYSFS_SLEEP_MODE).await;
}

tokio::time::sleep(std::time::Duration::from_millis(POLL_MS)).await;
}
});
}

#[derive(Copy, Clone, Default)]
struct Framebuffer;

Expand Down Expand Up @@ -41,6 +109,10 @@ pub fn update_ui(
shutdown_token: CancellationToken,
ui_update_rx: Receiver<DisplayState>,
) {
if config.keep_screen_on {
spawn_keep_screen_on(task_tracker, shutdown_token.clone());
}

generic_framebuffer::update_ui(
task_tracker,
config,
Expand Down
15 changes: 15 additions & 0 deletions daemon/web/src/lib/components/ConfigForm.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,21 @@
</div>
</div>

<div class="flex items-center">
<input
id="keep_screen_on"
type="checkbox"
bind:checked={config.keep_screen_on}
class="h-4 w-4 text-rayhunter-blue focus:ring-rayhunter-blue border-gray-300 rounded"
/>
<label for="keep_screen_on" class="ml-2 block text-sm text-gray-700">
Keep screen on (Orbic)
</label>
</div>
<p class="text-xs text-gray-500">
Prevents the Orbic display from blanking by re-enabling backlight/display when it sleeps.
</p>

<div class="border-t pt-4 mt-6 space-y-3">
<h3 class="text-lg font-semibold text-gray-800 mb-4">Notification Settings</h3>
<div>
Expand Down
1 change: 1 addition & 0 deletions daemon/web/src/lib/utils.svelte.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ export interface Config {
ui_level: number;
colorblind_mode: boolean;
key_input_mode: number;
keep_screen_on: boolean;
ntfy_url: string;
enabled_notifications: enabled_notifications[];
analyzers: AnalyzerConfig;
Expand Down
3 changes: 3 additions & 0 deletions dist/config.toml.in
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,9 @@ colorblind_mode = false
# 1..3 = show emoji for status. :) for running, ! for warnings, no mouth for paused.
ui_level = 1

# (Orbic) Keep the display awake by re-enabling backlight/display when the stock UI blanks it.
keep_screen_on = false

# 0 = rayhunter does not read button presses
# 1 = double-tapping the power button starts/stops recordings
key_input_mode = 0
Expand Down
1 change: 1 addition & 0 deletions doc/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ Through web UI you can set:
- *Disable button control*: built-in power button of the device is not used by Rayhunter.
- *Double-tap power button to start/stop recording*: double clicking on a built-in power button of the device stops and immediately restarts the recording. This could be useful if Rayhunter's heuristics is triggered and you get the red line, and you want to "reset" the past warnings. Normally you can do that through web UI, but sometimes it is easier to double tap on power button.
- **Colorblind Mode** enables color blind mode (blue line is shown instead of green line, red line remains red). Please note that this does not cover all types of color blindness, but switching green to blue should be about enough to differentiate the color change for most types of color blindness.
- **Keep Screen On** (Orbic only) Keeps the display awake by re-enabling backlight/display when the stock UI blanks it. Allows monitoring Rayhunter at a glance.
- **ntfy URL**, which allows setting a [ntfy](https://ntfy.sh/) URL to which notifications of new detections will be sent. The topic should be unique to your device, e.g., `https://ntfy.sh/rayhunter_notifications_ba9di7ie` or `https://myserver.example.com/rayhunter_notifications_ba9di7ie`. The ntfy Android and iOS apps can then be used to receive notifications. More information can be found in the [ntfy docs](https://docs.ntfy.sh/).
- **Enabled Notification Types** allows enabling or disabling the following types of notifications:
- *Warnings*, which will alert when a heuristic is triggered. Alerts will be sent at most once every five minutes.
Expand Down