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
31 changes: 31 additions & 0 deletions src/components/about/AboutTab.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import React, { useState } from 'react';
import { useDebugStore } from '../../store/debugStore';
import { useOutOfBandStore } from '../../store/outOfBandStore';
import { Card } from '../ui/Card';
import { SectionTitle } from '../ui/SectionTitle';
import { Button } from '../ui/Button';
Expand All @@ -15,6 +16,7 @@ const OFFLINE_VERSION_URL = 'https://infamy.github.io/NeonPlug/';

export const AboutTab: React.FC = () => {
const { debugMode, setDebugMode } = useDebugStore();
const { allowOutOfBandFrequencies, setAllowOutOfBandFrequencies } = useOutOfBandStore();
const [offlineFallbackOpen, setOfflineFallbackOpen] = useState(false);

return (
Expand Down Expand Up @@ -299,6 +301,35 @@ npm run build:single</code>
)}
</div>
</div>

{/* Out of Band Frequencies */}
<div className="bg-deep-gray rounded-lg border border-orange-600/30 p-6">
<h3 className="text-lg font-semibold text-orange-400 mb-4">Out of Band Frequencies</h3>
<div className="space-y-3">
<p className="text-cool-gray text-sm">
Enable this if your radio has modified firmware that supports frequencies outside the
standard VHF (87–174 MHz) and UHF (400–470 MHz) ranges. When enabled, out-of-band
channels will be read, displayed, and written without filtering. This setting is saved
with your codeplug.
</p>
<button
type="button"
onClick={() => setAllowOutOfBandFrequencies(!allowOutOfBandFrequencies)}
className={`px-6 py-3 rounded-lg font-semibold transition-colors ${
allowOutOfBandFrequencies
? 'bg-orange-900/30 text-orange-400 border border-orange-600/30 hover:bg-orange-900/50'
: 'bg-orange-900/20 text-orange-500 border border-orange-600/20 hover:bg-orange-900/30'
}`}
>
{allowOutOfBandFrequencies ? '✓ Out of Band Frequencies Enabled' : 'Enable Out of Band Frequencies'}
</button>
{allowOutOfBandFrequencies && (
<p className="text-xs text-orange-400">
Out of band frequencies are enabled. No frequency filtering will be applied on read or write.
</p>
)}
</div>
</div>
</div>
</div>
<ConfirmModal
Expand Down
4 changes: 3 additions & 1 deletion src/components/channels/ChannelsTable.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import { Card } from '../ui/Card';
import { EmptyState } from '../ui/EmptyState';
import { CTCSS_FREQUENCIES, DCS_CODES, formatCTCSSFrequency, formatDCSCode } from '../../utils/ctcssConstants';
import { isNoTxFrequency, isRxInNoTxBand } from '../../services/validation/frequencyValidator';
import { useOutOfBandStore } from '../../store/outOfBandStore';

// Frequency input component that only updates parent on blur (prevents cursor jumping)
interface FrequencyInputProps {
Expand Down Expand Up @@ -73,7 +74,8 @@ export const ChannelsTable: React.FC<ChannelsTableProps> = ({
const effectiveModel = useEffectiveRadioModel();
const { settings: radioSettings, updateSettings } = useRadioSettingsStore();
const caps = getCapabilitiesForModel(effectiveModel);
const bandLimits = caps?.bandLimits ?? null;
const { allowOutOfBandFrequencies } = useOutOfBandStore();
const bandLimits = allowOutOfBandFrequencies ? null : (caps?.bandLimits ?? null);
const maxChannels = caps?.maxChannels ?? 4000;
const analogOnly = caps?.analogOnly === true;
const { scanLists } = useScanListsStore();
Expand Down
6 changes: 6 additions & 0 deletions src/components/layout/Toolbar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import { migrateCodeplug, type MigrationLoss } from '../../services/codeplugMigr
import { saveSnapshot, getSnapshots, getSnapshotData, clearSnapshots, type SnapshotEventType } from '../../services/codeplugSnapshots';
// Codeplug export/import are lazy loaded when needed
import { useRadioConnection } from '../../hooks/useRadioConnection';
import { useOutOfBandStore } from '../../store/outOfBandStore';
import { ReadProgressModal } from '../ui/ReadProgressModal';
import { ConfirmModal } from '../ui/ConfirmModal';
import { isWebSerialSupported } from '../../utils/browserSupport';
Expand Down Expand Up @@ -108,6 +109,7 @@ export const Toolbar: React.FC = () => {
encryptionKeys,
exportDate: new Date().toISOString(),
version: '1.0.0',
allowOutOfBandFrequencies: useOutOfBandStore.getState().allowOutOfBandFrequencies || undefined,
});

const buildCodeplugDataFromStores = () => {
Expand Down Expand Up @@ -141,6 +143,7 @@ export const Toolbar: React.FC = () => {
encryptionKeys: eks.keys,
exportDate: new Date().toISOString(),
version: '1.0.0',
allowOutOfBandFrequencies: useOutOfBandStore.getState().allowOutOfBandFrequencies || undefined,
};
};

Expand Down Expand Up @@ -226,6 +229,9 @@ export const Toolbar: React.FC = () => {
setQuickContacts(codeplugData.quickContacts ?? []);
setRXGroups(codeplugData.rxGroups ?? []);
setEncryptionKeys(codeplugData.encryptionKeys ?? []);
if (codeplugData.allowOutOfBandFrequencies) {
useOutOfBandStore.getState().setAllowOutOfBandFrequencies(true);
}

const digCount = codeplugData.digitalEmergencies?.length ?? 0;
const analogCount = codeplugData.analogEmergencies?.length ?? 0;
Expand Down
22 changes: 19 additions & 3 deletions src/hooks/useRadioConnection.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import type { Channel } from '../models/Channel';
import type { Zone } from '../models/Zone';
import type { ScanList } from '../models/ScanList';
import { isValidChannelFrequency } from '../services/validation/frequencyValidator';
import { useOutOfBandStore } from '../store/outOfBandStore';
import { parseBootImageHeader } from '../utils/bootImage';

/** Augment error message when tab was hidden during a serial operation (better reporting). */
Expand Down Expand Up @@ -167,6 +168,18 @@ export function useRadioConnection() {
onProgress?.(20, 'Parsing channels...', steps[4]);
const channels = await protocol.readChannels();
setChannels(channels);

// Auto-enable OOB flag if the radio has channels outside standard band limits
{
const { allowOutOfBandFrequencies, setAllowOutOfBandFrequencies } = useOutOfBandStore.getState();
if (!allowOutOfBandFrequencies) {
const readBandLimits = getCapabilitiesForModel(effectiveModel)?.bandLimits;
if (readBandLimits && channels.some(ch => !isValidChannelFrequency(ch, readBandLimits))) {
setAllowOutOfBandFrequencies(true);
}
}
}

// Enrich radioInfo with firmware from cached image (UV5R-Mini; getRadioInfo may have missed it)
if (typeof (protocol as any).getFirmwareFromCache === 'function') {
const fw = (protocol as any).getFirmwareFromCache();
Expand Down Expand Up @@ -779,10 +792,13 @@ export function useRadioConnection() {
try {
// Filter channels to only include those with valid frequencies (use effective model for capabilities)
const effectiveModel = radioInfo?.model ?? selectedRadioModel ?? null;
const bandLimits = getCapabilitiesForModel(effectiveModel)?.bandLimits;
const validChannels = channels.filter(ch => isValidChannelFrequency(ch, bandLimits));
const { allowOutOfBandFrequencies: writeAllowOob } = useOutOfBandStore.getState();
const bandLimits = writeAllowOob ? null : getCapabilitiesForModel(effectiveModel)?.bandLimits;
const validChannels = bandLimits
? channels.filter(ch => isValidChannelFrequency(ch, bandLimits))
: channels;
const filteredCount = channels.length - validChannels.length;

if (filteredCount > 0) {
console.warn(`Filtered out ${filteredCount} channel(s) with frequencies outside supported ranges`);
}
Expand Down
1 change: 1 addition & 0 deletions src/services/codeplugExport.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ export interface CodeplugData {
encryptionKeys: EncryptionKey[];
exportDate: string;
version: string;
allowOutOfBandFrequencies?: boolean;
}

const CODEPLUG_VERSION = '1.0.0';
Expand Down
28 changes: 28 additions & 0 deletions src/store/outOfBandStore.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import { create } from 'zustand';

interface OutOfBandState {
allowOutOfBandFrequencies: boolean;
setAllowOutOfBandFrequencies: (v: boolean) => void;
}

const loadAllowOob = (): boolean => {
try {
return localStorage.getItem('neonplug-allow-oob') === 'true';
} catch {
return false;
}
};

const saveAllowOob = (v: boolean): void => {
try {
localStorage.setItem('neonplug-allow-oob', v ? 'true' : 'false');
} catch {}
};

export const useOutOfBandStore = create<OutOfBandState>((set) => ({
allowOutOfBandFrequencies: loadAllowOob(),
setAllowOutOfBandFrequencies: (v) => {
saveAllowOob(v);
set({ allowOutOfBandFrequencies: v });
},
}));
Loading