Skip to content
Merged
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
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ including programming several different radios from the same codeplug.
| **Yaesu FT5D** | C4FM (System Fusion) + Analog | VHF / UHF TX, wideband RX | microSD — patches the radio's own backup file | 900 channels in 24 banks; channels, banks, and menu settings |
| **Yaesu FTM-300DR** | C4FM (System Fusion) + Analog | 2 m / 70 cm TX, 108–999 MHz RX | microSD — patches the radio's own backup file | 999 memories; memories and 147 menu settings, including the APRS and SmartBeaconing setup |
| **Yaesu FTM-510DR** | C4FM (System Fusion) + Analog | 2 m / 70 cm TX, 108–550 MHz RX | microSD — patches the radio's own backup file | 999 memories; memories and 284 settings — Setup Menu items, band skip, DTMF memories, and the APRS setup down to digi paths, message texts and status texts |
| **Icom ID-51 Plus** | D-STAR + Analog | 2 m / 70 cm TX, 108–174 / 380–479 MHz RX | microSD — patches the radio's own `.icf` file | 500 memories in 26 named banks; memories and 241 menu settings including the D-PRS (APRS) position setup restore in one operation. Also the 50th Anniversary edition; not the original ID-51 or the Plus2 |
| **Icom ID-52** | D-STAR + Analog | VHF / UHF TX, 108–174 / 225–479 MHz RX | microSD — patches the radio's own `.icf` file | 1000 memories in 100 groups; memories and menu settings restore in one operation |
| **Icom ID-5100** | D-STAR + Analog | 2 m / 70 cm TX, 118–174 / 375–550 MHz RX | Data cable — read, write, settings | 1000 memories in 26 named banks, programmed by Icom's clone protocol over the cable; 292 menu settings including My Call Sign, TX messages and the D-PRS (APRS) position, object, item and weather setup, written in the same upload as the memories |
| **Kenwood TH-D72** | APRS + Analog | 2 m / 70 cm TX, 118–174 / 320–524 MHz RX | Direct USB — read, write, settings | 1000 memories; 113 menu settings over the radio's own `MU` command |
Expand Down
16 changes: 14 additions & 2 deletions src-tauri/src/commands/export.rs
Original file line number Diff line number Diff line change
Expand Up @@ -550,6 +550,16 @@ pub(crate) fn channel_fit(channel: &Channel, model: &RadioModel) -> ChannelFit {
));
}

// A frequency the radio's memory cannot represent, whatever its bands say.
if let Some(why) = crate::radios::registry::driver_for_model(model)
.and_then(|d| d.unstorable_frequency(channel.rx_freq))
{
return ChannelFit::Excluded(format!(
"Frequency {:.4} MHz {why} on {}",
channel.rx_freq, model.display_name
));
}

// Transmit: both ends of the channel have to land in a band the radio can
// key up on — a repeater channel is no use if the input is out of band.
let tx_freq = tx_frequency(channel);
Expand Down Expand Up @@ -1223,6 +1233,7 @@ pub async fn generate_codeplug(
// the shared CHIRP-compatible analog CSV.
// The exporter names the file before it writes it, so the report below can
// say which one to load on the radio.
let mut driver_notes: Vec<String> = Vec::new();
let path = match model
.export_format
.as_deref()
Expand All @@ -1244,7 +1255,8 @@ pub async fn generate_codeplug(
profile_settings: profile_settings.as_deref(),
memory_group: &memory_group,
};
exporter.export(&target, &req)?;
let (_, notes) = exporter.export_noting(&target, &req)?;
driver_notes = notes;
target
}
None => {
Expand All @@ -1271,7 +1283,7 @@ pub async fn generate_codeplug(
Ok(CodeplugWritten {
channels: included.len(),
path,
note: crate::radios::settings_bounds::note_line(&dropped),
note: crate::radios::settings_bounds::note_line_with(&dropped, &driver_notes),
})
}

Expand Down
53 changes: 51 additions & 2 deletions src-tauri/src/commands/program.rs
Original file line number Diff line number Diff line change
Expand Up @@ -540,6 +540,7 @@ fn volume_name(root: &std::path::Path) -> String {
pub async fn find_memory_cards(format: String) -> Vec<MemoryCard> {
tauri::async_runtime::spawn_blocking(move || match format.as_str() {
"yaesu_ft5d_sd" => ft5d_cards(),
"icom_id51_icf" => id51_cards(),
"icom_id52_icf" => id52_cards(),
"kenwood_thd75_sd" => thd75_cards(),
"yaesu_ftm300_sd" => ftm300_cards(),
Expand Down Expand Up @@ -616,6 +617,26 @@ fn ftm510_cards() -> Vec<MemoryCard> {
out
}

/// The ID-51 Plus keeps its settings files in `ID-51/Setting/` and names them
/// itself, as the ID-52 does: what is offered is the **folder**, and the write
/// creates a new `SetYYYYMMDD_NN.icf` there from the newest file the radio
/// saved. Offered only if that folder holds a file this driver can use.
fn id51_cards() -> Vec<MemoryCard> {
let mut out = Vec::new();
for root in mounted_roots() {
let dir = root.join("ID-51").join("Setting");
if crate::radios::icom_id51::ID51_CARD.newest_settings_file(&dir).is_err() {
continue;
}
out.push(MemoryCard {
has_original: false,
volume: volume_name(&root),
path: dir.to_string_lossy().into_owned(),
});
}
out
}

/// The ID-52 keeps its settings files in one fixed folder but names them
/// itself, so what gets offered is the **folder**, not a file: the write creates
/// a new `SetYYYYMMDD_NN.icf` in it rather than overwriting anything the
Expand Down Expand Up @@ -806,6 +827,34 @@ pub async fn read_id52_settings_from_card(path: String) -> Result<RadioSettingsR
.estr()?
}

/// The ID-51 Plus's version of [`read_id52_settings_from_card`], reading the
/// `.icf` the radio writes with MENU > SD Card > Save Setting. Nothing is
/// written; the values land in the profile form and ride back out in the same
/// file as the memories.
#[tauri::command]
pub async fn read_id51_settings_from_card(path: String) -> Result<RadioSettingsRead, String> {
tauri::async_runtime::spawn_blocking(move || {
let text = std::fs::read_to_string(&path).map_err(|e| {
format!(
"Could not read {path}: {e}. Pick a settings file from ID-51/Setting/ on the \
radio's microSD card — save one with MENU > SD Card > Save Setting if it is \
not there."
)
})?;
let icf = crate::radios::icom_id52::icf::IcfFile::parse(&text)?;
crate::radios::icom_id51::check_is_an_id51_file(&icf)?;
let settings = crate::radios::icom_id51::settings::decode_settings(icf.image());
let count = settings.as_object().map(|o| o.len()).unwrap_or(0);
Ok(RadioSettingsRead {
settings,
count,
backup_path: path,
})
})
.await
.estr()?
}

/// The TH-D75's version of the same, reading the `.d75` the radio writes to its
/// microSD card.
///
Expand Down Expand Up @@ -963,11 +1012,11 @@ pub async fn write_card_settings(
let file = exporter.resolve_target(&path)?;
crate::commands::write_paths::check_write_target(&file, exporter.target_extensions())?;
let writer = exporter.as_card_settings_writer().expect("checked above");
let fields_written = writer.export_settings(&file, map)?;
let (fields_written, notes) = writer.export_settings_noting(&file, map)?;
Ok(CardSettingsWritten {
fields_written,
path: file,
note: crate::radios::settings_bounds::note_line(&dropped),
note: crate::radios::settings_bounds::note_line_with(&dropped, &notes),
})
}

Expand Down
10 changes: 5 additions & 5 deletions src-tauri/src/db.rs
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ mod tests {
// original set): currently the Baofeng UV-5R, TIDRADIO TD-H3, AnyTone
// AT-D890UV and AT-D578UV, Yaesu FT5D, Icom ID-52, Kenwood TH-D75, Kenwood TH-D72
// the Binteradio BT-9000, Kenwood TM-D710, Icom ID-5100, TYT MD-380 and Yaesu
// FTM-300DR and FTM-510DR, and the FT-857D and FT-897D. (0015
// FTM-300DR and FTM-510DR, the FT-857D and FT-897D, and the Icom ID-51 Plus. (0015
// removed the Vero VR-N76 placeholder.) None of the last five has a
// migration of its own — seeding INSERTs new (manufacturer, model)
// rows, so a new model reaches existing databases on the next startup
Expand All @@ -71,9 +71,9 @@ mod tests {
.await
.unwrap();
assert_eq!(
count.0, 16,
count.0, 17,
"expected the UV-5R, TD-H3, AT-D890UV, AT-D578UV, FT5D, ID-52, TH-D75, TH-D72, BT-9000, \
TM-D710, ID-5100, MD-380, FTM-300DR, FTM-510DR, FT-857D and FT-897D seeded models"
TM-D710, ID-5100, MD-380, FTM-300DR, FTM-510DR, FT-857D, FT-897D and ID-51 Plus seeded models"
);

let models: Vec<(String,)> =
Expand All @@ -85,7 +85,7 @@ mod tests {
assert_eq!(
names,
vec![
"AT-D578UV", "AT-D890UV", "BT-9000", "FT-857D", "FT-897D", "FT5D", "FTM-300DR", "FTM-510DR", "ID-5100", "ID-52",
"AT-D578UV", "AT-D890UV", "BT-9000", "FT-857D", "FT-897D", "FT5D", "FTM-300DR", "FTM-510DR", "ID-51 Plus", "ID-5100", "ID-52",
"MD-380", "TD-H3", "TH-D72", "TH-D75", "TM-D710", "UV-5R"
]
);
Expand All @@ -96,7 +96,7 @@ mod tests {
.fetch_one(&pool)
.await
.unwrap();
assert_eq!(count2.0, 16, "seeding should be idempotent");
assert_eq!(count2.0, 17, "seeding should be idempotent");

// A new database starts with NO talkgroups. The BrandMeister list used
// to be compiled in and seeded here; it is downloaded on request now,
Expand Down
Loading
Loading