Skip to content

Notification#20

Open
Fangrui-Zhang wants to merge 6 commits intomainfrom
Notification
Open

Notification#20
Fangrui-Zhang wants to merge 6 commits intomainfrom
Notification

Conversation

@Fangrui-Zhang
Copy link
Copy Markdown
Contributor

Notification of the app

Copilot AI review requested due to automatic review settings April 7, 2026 23:18
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds an in-app telemetry alert notification system (Expo Notifications) that monitors incoming telemetry snapshots and lets users control alerting from the Profile screen.

Changes:

  • Introduces notificationService to build a telemetry signal map, persist notification preferences, and fire debounced local notifications.
  • Adds a global useAlertMonitor hook wired into TelemetryProvider to run notification checks as telemetry updates.
  • Updates Profile settings UI to include a master alerts switch plus per-category/subsystem and per-signal toggles.

Reviewed changes

Copilot reviewed 7 out of 8 changed files in this pull request and generated 10 comments.

Show a summary per file
File Description
src/services/notificationService.js New notification pipeline: permissions, preference storage, signal grouping, debounce, and local notification scheduling.
src/screens/ProfileScreen.js Adds UI for master notification toggle and per-signal/group preferences.
src/hooks/useAlertMonitor.js New hook to request notification permissions and trigger alert checks on telemetry updates.
src/data/telemetryFormat.json Adds telemetry format data used to derive signal categories/bounds for notifications.
src/context/TelemetryContext.js Installs useAlertMonitor inside TelemetryProvider so checks run globally.
package.json Adds AsyncStorage + expo-notifications dependencies; changes React/React DOM versions.
package-lock.json Lockfile updates for new dependencies and transitive changes.
app.config.js Adds expo-notifications config plugin (icon/color/etc.).

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +108 to +111
"dcdc_deg": [1, "bool", "", 0, 1, "High Voltage;MPS", "FFF", 0],
"supp_deg": [1, "bool", "", 0, 1, "High Voltage;Supplemental", "FFF", 0],
"isolation": [1, "bool", "", 0, 1, "High Voltage;Shutdown", "300", 0],
"isolation_fdbck": [1, "bool", "", 0, 1, "High Voltage;Shutdown", "300", 0],
Copy link

Copilot AI Apr 7, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

telemetryFormat.json contains duplicate keys (e.g., "dcdc_deg" appears twice, as do "use_dcdc"/"use_supp" later). In JSON, later duplicates overwrite earlier entries, which will silently drop signals from SIGNAL_MAP and make alert grouping/preferences incorrect. Ensure all signal names are unique (or change the format structure to avoid object-key collisions) before using it to drive notifications.

Suggested change
"dcdc_deg": [1, "bool", "", 0, 1, "High Voltage;MPS", "FFF", 0],
"supp_deg": [1, "bool", "", 0, 1, "High Voltage;Supplemental", "FFF", 0],
"isolation": [1, "bool", "", 0, 1, "High Voltage;Shutdown", "300", 0],
"isolation_fdbck": [1, "bool", "", 0, 1, "High Voltage;Shutdown", "300", 0],
"hv_dcdc_deg": [1, "bool", "", 0, 1, "High Voltage;MPS", "FFF", 0],
"hv_supp_deg": [1, "bool", "", 0, 1, "High Voltage;Supplemental", "FFF", 0],
"hv_isolation": [1, "bool", "", 0, 1, "High Voltage;Shutdown", "300", 0],
"hv_isolation_fdbck": [1, "bool", "", 0, 1, "High Voltage;Shutdown", "300", 0],

Copilot uses AI. Check for mistakes.
Comment on lines +222 to +233
const { type, min, max, category } = meta;

// Only process fault booleans (min===0, max===0)
if (type !== 'bool' || min !== 0 || max !== 0) continue;

// Check per-signal preference (default enabled)
if (signalPrefs[signalName] === false) continue;

// Abnormal = value is 1/true (fault active)
const numVal = typeof rawValue === 'boolean' ? (rawValue ? 1 : 0) : Number(rawValue);
if (numVal !== 1) continue;

Copy link

Copilot AI Apr 7, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

checkTelemetryAlerts only processes bool signals with min===0 && max===0, but FAULT_BOOL_BY_CATEGORY (and the Profile UI) includes both min/max==0/0 and min/max==1/1 signals (via faultWhen). As written, “must-be-ON” signals will never trigger notifications even if enabled in settings. Either incorporate faultWhen when evaluating booleans or restrict the preference/UI grouping to the subset you actually alert on.

Suggested change
const { type, min, max, category } = meta;
// Only process fault booleans (min===0, max===0)
if (type !== 'bool' || min !== 0 || max !== 0) continue;
// Check per-signal preference (default enabled)
if (signalPrefs[signalName] === false) continue;
// Abnormal = value is 1/true (fault active)
const numVal = typeof rawValue === 'boolean' ? (rawValue ? 1 : 0) : Number(rawValue);
if (numVal !== 1) continue;
const { type, min, max, category, faultWhen } = meta;
// Only process fixed-value booleans and determine which boolean value represents a fault.
if (type !== 'bool') continue;
// Check per-signal preference (default enabled)
if (signalPrefs[signalName] === false) continue;
const numVal = typeof rawValue === 'boolean' ? (rawValue ? 1 : 0) : Number(rawValue);
if (numVal !== 0 && numVal !== 1) continue;
let faultValue;
if (faultWhen === true || faultWhen === false) {
faultValue = faultWhen ? 1 : 0;
} else if (faultWhen === 1 || faultWhen === 0) {
faultValue = faultWhen;
} else if (faultWhen === '1' || faultWhen === 'true') {
faultValue = 1;
} else if (faultWhen === '0' || faultWhen === 'false') {
faultValue = 0;
} else if ((min === 0 || min === 1) && min === max) {
// Fallback for fixed nominal boolean ranges:
// 0/0 => fault when 1, 1/1 => fault when 0.
faultValue = min === 0 ? 1 : 0;
} else {
continue;
}
if (numVal !== faultValue) continue;

Copilot uses AI. Check for mistakes.
Comment on lines +210 to +217
export const checkTelemetryAlerts = async (telemetryData) => {
if (!telemetryData) return;

const masterEnabled = await getMasterEnabled();
if (!masterEnabled) return;

const signalPrefs = await getSignalPrefs();

Copy link

Copilot AI Apr 7, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

checkTelemetryAlerts hits AsyncStorage (getMasterEnabled + getSignalPrefs) on every telemetry update. If telemetry updates frequently (e.g., every 2s), this adds avoidable async I/O + JSON parse work on the hot path and can cause jank/battery drain. Cache preferences in memory (and update cache when settings change) or pass them into checkTelemetryAlerts from a higher-level store/context.

Copilot uses AI. Check for mistakes.
Comment on lines +235 to +245
markAlerted(signalName);

await Notifications.scheduleNotificationAsync({
content: {
title: `⚠️ ${category} Fault`,
body: signalName.replace(/_/g, ' '),
sound: true,
data: { signalName, category, value: rawValue },
},
trigger: null,
});
Copy link

Copilot AI Apr 7, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

markAlerted() is called before scheduleNotificationAsync(). If scheduling throws/rejects, the signal will still be debounced and the alert may be lost for at least DEBOUNCE_MS. Move markAlerted() after a successful schedule, and consider wrapping scheduleNotificationAsync in try/catch so a single failure doesn’t break the whole loop.

Suggested change
markAlerted(signalName);
await Notifications.scheduleNotificationAsync({
content: {
title: `⚠️ ${category} Fault`,
body: signalName.replace(/_/g, ' '),
sound: true,
data: { signalName, category, value: rawValue },
},
trigger: null,
});
try {
await Notifications.scheduleNotificationAsync({
content: {
title: `⚠️ ${category} Fault`,
body: signalName.replace(/_/g, ' '),
sound: true,
data: { signalName, category, value: rawValue },
},
trigger: null,
});
markAlerted(signalName);
} catch (error) {
console.warn(`Failed to schedule notification for ${signalName}`, error);
}

Copilot uses AI. Check for mistakes.
Comment on lines +22 to +28
Notifications.setNotificationHandler({
handleNotification: async () => ({
shouldShowBanner: true,
shouldShowList: true,
shouldPlaySound: true,
shouldSetBadge: false,
}),
Copy link

Copilot AI Apr 7, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The foreground notification handler returns shouldShowBanner/shouldShowList, but doesn’t include shouldShowAlert. On Android and on iOS versions where banner/list behaviors aren’t supported, this can result in foreground notifications not being shown. Include shouldShowAlert (and only use banner/list if you’ve verified the SDK target supports them) to ensure cross-platform behavior.

Copilot uses AI. Check for mistakes.
Comment on lines +14 to +23
} from '../services/notificationService';

const useAlertMonitor = (telemetryData) => {
const permissionRequestedRef = useRef(false);

// Request permission once on first mount
useEffect(() => {
if (permissionRequestedRef.current) return;
permissionRequestedRef.current = true;
requestNotificationPermission();
Copy link

Copilot AI Apr 7, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

useAlertMonitor requests OS notification permission immediately on mount, regardless of the persisted master toggle. This can prompt users who have notifications turned off (or who haven’t opted in yet via Profile), and it duplicates the opt-in flow in ProfileScreen. Consider checking getMasterEnabled() first and only requesting permission when the user enables alerts (or when you actually need to schedule a notification).

Suggested change
} from '../services/notificationService';
const useAlertMonitor = (telemetryData) => {
const permissionRequestedRef = useRef(false);
// Request permission once on first mount
useEffect(() => {
if (permissionRequestedRef.current) return;
permissionRequestedRef.current = true;
requestNotificationPermission();
getMasterEnabled,
} from '../services/notificationService';
const useAlertMonitor = (telemetryData) => {
const permissionRequestedRef = useRef(false);
// Request permission once on first mount, but only if alerts are enabled
useEffect(() => {
if (permissionRequestedRef.current) return;
permissionRequestedRef.current = true;
let isMounted = true;
const maybeRequestPermission = async () => {
const masterEnabled = await getMasterEnabled();
if (!isMounted || !masterEnabled) return;
await requestNotificationPermission();
};
maybeRequestPermission();
return () => {
isMounted = false;
};

Copilot uses AI. Check for mistakes.
[
'expo-notifications',
{
icon: './assets/notification-icon.png',
Copy link

Copilot AI Apr 7, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

expo-notifications plugin is configured with icon: './assets/notification-icon.png', but that file doesn’t exist in the repo (assets/ contains icon.png, adaptive-icon.png, etc.). Add the referenced asset or update the config to point at an existing icon, otherwise builds can fail or notifications may show a missing icon.

Suggested change
icon: './assets/notification-icon.png',
icon: './assets/icon.png',

Copilot uses AI. Check for mistakes.
Comment on lines +46 to +47
"react": "19.1.0",
"react-dom": "19.1.0",
Copy link

Copilot AI Apr 7, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This PR changes react/react-dom from 19.2.4 to 19.1.0. That’s a potentially breaking dependency downgrade unrelated to notifications and can introduce hard-to-diagnose regressions. If this wasn’t required for Expo SDK compatibility, consider reverting the React version change to keep the dependency update minimal and intentional.

Suggested change
"react": "19.1.0",
"react-dom": "19.1.0",
"react": "19.2.4",
"react-dom": "19.2.4",

Copilot uses AI. Check for mistakes.
Comment on lines +160 to +162
"main_12V_bus": [4, "float", "V", 0, 100, "PDC;Sensors", "202", 0],
"main_5V_bus": [4, "float", "V", 0, 100, "PDC;Sensors", "203", 0],
"main_5V_current": [4, "float", "A", 0, 100, "PDC;Sensors", "204", 0],
Copy link

Copilot AI Apr 7, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

More duplicate keys exist later in this JSON (e.g., "main_12V_bus" appears under both "Main IO;Sensors" and "PDC;Sensors"). Because object keys must be unique, one of these definitions will be overwritten at parse time. Rename/namespace these signals (or restructure the format) so each telemetry signal has a unique key.

Suggested change
"main_12V_bus": [4, "float", "V", 0, 100, "PDC;Sensors", "202", 0],
"main_5V_bus": [4, "float", "V", 0, 100, "PDC;Sensors", "203", 0],
"main_5V_current": [4, "float", "A", 0, 100, "PDC;Sensors", "204", 0],
"pdc_main_12V_bus": [4, "float", "V", 0, 100, "PDC;Sensors", "202", 0],
"pdc_main_5V_bus": [4, "float", "V", 0, 100, "PDC;Sensors", "203", 0],
"pdc_main_5V_current": [4, "float", "A", 0, 100, "PDC;Sensors", "204", 0],

Copilot uses AI. Check for mistakes.
Comment on lines +210 to +214
export const checkTelemetryAlerts = async (telemetryData) => {
if (!telemetryData) return;

const masterEnabled = await getMasterEnabled();
if (!masterEnabled) return;
Copy link

Copilot AI Apr 7, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

expo-notifications isn’t supported on web in the same way as native; requestNotificationPermission() explicitly returns false for web, but checkTelemetryAlerts() still runs and can call scheduleNotificationAsync on web (via useAlertMonitor in TelemetryProvider). Add a Platform.OS === 'web' early-return in checkTelemetryAlerts (or gate useAlertMonitor) to avoid runtime errors in the web build.

Copilot uses AI. Check for mistakes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants