main.cpp (9300B)
1 #include <imgui.h> 2 #include <module.h> 3 #include <core.h> 4 #include <gui/gui.h> 5 #include <gui/tuner.h> 6 #include <signal_path/signal_path.h> 7 #include <utils/flog.h> 8 #include <radio_interface.h> 9 10 #include <cstring> 11 #include <cstdint> 12 #include <algorithm> 13 #include <mutex> 14 #include <string> 15 #include <vector> 16 17 extern "C" { 18 void* civ_create(const char* name); 19 void civ_destroy(void* ctx); 20 void civ_post_init(void* ctx); 21 void civ_enable(void* ctx); 22 void civ_disable(void* ctx); 23 bool civ_is_enabled(void* ctx); 24 void civ_status(void* ctx, char* out, size_t out_len); 25 void civ_set_serial_port(void* ctx, const char* port); 26 void civ_set_serial_baud(void* ctx, uint32_t baud); 27 void civ_set_sync_mode(void* ctx, bool sync_mode); 28 size_t civ_list_serial_ports(char* out, size_t out_len); 29 } 30 31 SDRPP_MOD_INFO{ 32 /* Name: */ "civ_sync", 33 /* Description: */ "Sync an Icom IC-7300 (CI-V) with an SDR++ VFO (freq/mode/filter)", 34 /* Author: */ "vfo-sync", 35 /* Version: */ 0, 2, 0, 36 /* Max instances */ 1 37 }; 38 39 // Guard the GUI-side state accessors (waterfall/VFO) called off-thread. 40 static std::mutex guiMtx; 41 42 // Returns the selected VFO name if it is bound to the "radio" demodulator, 43 // or "" otherwise. Caller must hold guiMtx. 44 static std::string selectedRadioVfo() { 45 std::string vfo = gui::waterfall.selectedVFO; 46 if (!core::modComManager.interfaceExists(vfo) || 47 core::modComManager.getModuleName(vfo) != "radio") { 48 return ""; 49 } 50 return vfo; 51 } 52 53 extern "C" void civ_bridge_tune(double freq) { 54 std::lock_guard<std::mutex> lck(guiMtx); 55 tuner::tune(tuner::TUNER_MODE_NORMAL, gui::waterfall.selectedVFO, freq); 56 } 57 58 extern "C" double civ_bridge_get_vfo_freq() { 59 std::lock_guard<std::mutex> lck(guiMtx); 60 double freq = gui::waterfall.getCenterFrequency(); 61 if (sigpath::vfoManager.vfoExists(gui::waterfall.selectedVFO)) { 62 freq += sigpath::vfoManager.getOffset(gui::waterfall.selectedVFO); 63 } 64 return freq; 65 } 66 67 extern "C" int civ_bridge_get_mode() { 68 std::lock_guard<std::mutex> lck(guiMtx); 69 std::string vfo = selectedRadioVfo(); 70 if (vfo.empty()) { return -1; } 71 int mode = -1; 72 core::modComManager.callInterface(vfo, RADIO_IFACE_CMD_GET_MODE, NULL, &mode); 73 return mode; 74 } 75 76 extern "C" void civ_bridge_set_mode(int mode) { 77 std::lock_guard<std::mutex> lck(guiMtx); 78 std::string vfo = selectedRadioVfo(); 79 if (vfo.empty()) { return; } 80 core::modComManager.callInterface(vfo, RADIO_IFACE_CMD_SET_MODE, &mode, NULL); 81 } 82 83 extern "C" double civ_bridge_get_bandwidth() { 84 std::lock_guard<std::mutex> lck(guiMtx); 85 std::string vfo = selectedRadioVfo(); 86 if (vfo.empty()) { return 0.0; } 87 float bw = 0.0f; 88 core::modComManager.callInterface(vfo, RADIO_IFACE_CMD_GET_BANDWIDTH, NULL, &bw); 89 return (double)bw; 90 } 91 92 extern "C" void civ_bridge_set_bandwidth(double hz) { 93 std::lock_guard<std::mutex> lck(guiMtx); 94 std::string vfo = selectedRadioVfo(); 95 if (vfo.empty()) { return; } 96 float bw = (float)hz; 97 core::modComManager.callInterface(vfo, RADIO_IFACE_CMD_SET_BANDWIDTH, &bw, NULL); 98 } 99 100 extern "C" void civ_bridge_log(int level, const char* msg) { 101 if (msg == nullptr) { return; } 102 switch (level) { 103 case 0: flog::debug("{0}", msg); break; 104 case 1: flog::info("{0}", msg); break; 105 case 2: flog::warn("{0}", msg); break; 106 default: flog::error("{0}", msg); break; 107 } 108 } 109 110 class CivInstance : public ModuleManager::Instance { 111 public: 112 CivInstance(std::string name) { 113 this->name = name; 114 // Default to a detected port (preferring ttyUSB*) rather than a 115 // hardcoded device that may not exist on this system. 116 strncpy(portBuf, "/dev/ttyUSB0", sizeof(portBuf) - 1); 117 refreshPorts(); 118 if (!ports.empty()) { 119 auto it = std::find_if(ports.begin(), ports.end(), [](const std::string& p) { 120 return p.find("ttyUSB") != std::string::npos; 121 }); 122 if (it == ports.end()) { it = ports.begin(); } 123 strncpy(portBuf, it->c_str(), sizeof(portBuf) - 1); 124 portBuf[sizeof(portBuf) - 1] = 0; 125 } 126 rustCtx = civ_create(name.c_str()); 127 civ_set_serial_port(rustCtx, portBuf); 128 civ_set_serial_baud(rustCtx, baud); 129 gui::menu.registerEntry(name, menuHandler, this, this); 130 } 131 132 ~CivInstance() { 133 gui::menu.removeEntry(name); 134 civ_destroy(rustCtx); 135 } 136 137 void postInit() override { civ_post_init(rustCtx); } 138 void enable() override { civ_enable(rustCtx); } 139 void disable() override { civ_disable(rustCtx); } 140 bool isEnabled() override { return civ_is_enabled(rustCtx); } 141 142 private: 143 void refreshPorts() { 144 ports.clear(); 145 char buf[4096] = {0}; 146 size_t n = civ_list_serial_ports(buf, sizeof(buf)); 147 const char* p = buf; 148 for (size_t i = 0; i < n; i++) { 149 std::string name(p); 150 if (!name.empty()) { ports.push_back(name); } 151 p += name.size() + 1; 152 } 153 } 154 155 static void menuHandler(void* ctx) { 156 CivInstance* _this = (CivInstance*)ctx; 157 158 char statusBuf[256] = {0}; 159 civ_status(_this->rustCtx, statusBuf, sizeof(statusBuf)); 160 ImGui::TextUnformatted(statusBuf); 161 162 std::string portPreview = _this->customPortMode 163 ? "Custom (" + std::string(_this->portBuf) + ")" 164 : std::string(_this->portBuf); 165 166 if (ImGui::BeginCombo("Serial device", portPreview.c_str())) { 167 _this->refreshPorts(); 168 for (const auto& p : _this->ports) { 169 bool selected = !_this->customPortMode && p == _this->portBuf; 170 if (ImGui::Selectable(p.c_str(), selected)) { 171 strncpy(_this->portBuf, p.c_str(), sizeof(_this->portBuf) - 1); 172 _this->portBuf[sizeof(_this->portBuf) - 1] = 0; 173 _this->customPortMode = false; 174 civ_set_serial_port(_this->rustCtx, _this->portBuf); 175 } 176 } 177 if (ImGui::Selectable("Custom...", _this->customPortMode)) { 178 _this->customPortMode = true; 179 } 180 ImGui::EndCombo(); 181 } 182 183 if (_this->customPortMode) { 184 if (ImGui::InputText("Custom device", _this->portBuf, sizeof(_this->portBuf))) { 185 civ_set_serial_port(_this->rustCtx, _this->portBuf); 186 } 187 } 188 189 static const uint32_t baudRates[] = { 0, 4800, 9600, 19200, 38400, 57600, 115200 }; 190 static const char* baudNames[] = { "Auto", "4800", "9600", "19200", "38400", "57600", "115200" }; 191 static const int baudCount = 7; 192 193 int selected = -1; 194 for (int i = 0; i < baudCount; i++) { 195 if (!_this->customBaudMode && _this->baud == baudRates[i]) { selected = i; break; } 196 } 197 198 std::string preview; 199 if (_this->customBaudMode) { 200 preview = "Custom (" + std::to_string(_this->baud) + ")"; 201 } 202 else if (selected >= 0) { 203 preview = baudNames[selected]; 204 } 205 else { 206 preview = std::to_string(_this->baud); 207 } 208 209 if (ImGui::BeginCombo("Baud rate", preview.c_str())) { 210 for (int i = 0; i < baudCount; i++) { 211 if (ImGui::Selectable(baudNames[i], !_this->customBaudMode && selected == i)) { 212 _this->customBaudMode = false; 213 _this->baud = baudRates[i]; 214 civ_set_serial_baud(_this->rustCtx, _this->baud); 215 } 216 } 217 if (ImGui::Selectable("Custom...", _this->customBaudMode)) { 218 _this->customBaudMode = true; 219 _this->customBaud = _this->baud > 0 ? (int)_this->baud : 115200; 220 _this->baud = (uint32_t)_this->customBaud; 221 civ_set_serial_baud(_this->rustCtx, _this->baud); 222 } 223 ImGui::EndCombo(); 224 } 225 226 if (_this->customBaudMode) { 227 if (ImGui::InputInt("Custom baud", &_this->customBaud)) { 228 if (_this->customBaud > 0) { 229 _this->baud = (uint32_t)_this->customBaud; 230 civ_set_serial_baud(_this->rustCtx, _this->baud); 231 } 232 } 233 } 234 235 if (ImGui::Checkbox("Sync mode & filter", &_this->syncMode)) { 236 civ_set_sync_mode(_this->rustCtx, _this->syncMode); 237 } 238 239 if (ImGui::Button(_this->isEnabled() ? "Disconnect" : "Connect")) { 240 if (_this->isEnabled()) { 241 _this->disable(); 242 } 243 else { 244 _this->enable(); 245 } 246 } 247 } 248 249 std::string name; 250 void* rustCtx = nullptr; 251 char portBuf[256] = {0}; 252 std::vector<std::string> ports; 253 bool customPortMode = false; 254 uint32_t baud = 0; 255 bool customBaudMode = false; 256 int customBaud = 115200; 257 bool syncMode = true; 258 }; 259 260 MOD_EXPORT void _INIT_() { 261 // Nothing here 262 } 263 264 MOD_EXPORT ModuleManager::Instance* _CREATE_INSTANCE_(std::string name) { 265 return new CivInstance(name); 266 } 267 268 MOD_EXPORT void _DELETE_INSTANCE_(void* instance) { 269 delete (CivInstance*)instance; 270 } 271 272 MOD_EXPORT void _END_() { 273 // Nothing here 274 }