/* * Copyright (C) 2014 by Jonathan Naylor G4KLX and John Hays K7VE * Copyright 2016 by Jeremy McDermond (NH6Z) * Copyright 2021 by Thomas Early N7TAE * * Adapted by K7VE from G4KLX dv3000d */ // tcd - a hybid transcoder using DVSI hardware and Codec2 software // Copyright © 2022-2023 Thomas A. Early N7TAE // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // You should have received a copy of the GNU General Public License // along with this program. If not, see . #include #include #include #include #include #include #include #include #include #include #include "DVSIDevice.h" #include "Configure.h" extern CConfigure g_Conf; CDVDevice::CDVDevice(Encoding t) : type(t), ftHandle(nullptr), buffer_depth(0), keep_running(true) { } CDVDevice::~CDVDevice() { CloseDevice(); } void CDVDevice::CloseDevice() { input_queue.Shutdown(); keep_running = false; if (ftHandle) { auto status = FT_Close(ftHandle); if (FT_OK != status) FTDI_Error("FT_Close", status); } if (feedFuture.valid()) feedFuture.get(); if (readFuture.valid()) readFuture.get(); } void CDVDevice::FTDI_Error(const char *where, FT_STATUS status) const { std::cerr << "FTDI ERROR: " << where << ": "; switch (status) { case FT_INVALID_HANDLE: std::cerr << "handle is invalid"; break; case FT_DEVICE_NOT_FOUND: std::cerr << "device not found"; break; case FT_DEVICE_NOT_OPENED: std::cerr << "device not opne"; break; case FT_IO_ERROR: std::cerr << "io error"; break; case FT_INSUFFICIENT_RESOURCES: std::cerr << "insufficient resources"; break; case FT_INVALID_PARAMETER: std::cerr << "invalid parameter"; break; case FT_INVALID_BAUD_RATE: std::cerr << "invalid baud rate"; break; case FT_DEVICE_NOT_OPENED_FOR_ERASE: std::cerr << "device not opened for erase"; break; case FT_DEVICE_NOT_OPENED_FOR_WRITE: std::cerr << "device not opened for write"; break; case FT_FAILED_TO_WRITE_DEVICE: std::cerr << "failed to write device"; break; case FT_EEPROM_READ_FAILED: std::cerr << "eeprom read failed"; break; case FT_EEPROM_WRITE_FAILED: std::cerr << "eeprom write failed"; break; case FT_EEPROM_ERASE_FAILED: std::cerr << "eeprom erase failed"; break; case FT_EEPROM_NOT_PRESENT: std::cerr << "eeprom not present"; break; case FT_EEPROM_NOT_PROGRAMMED: std::cerr << "eeprom not programmed"; break; case FT_INVALID_ARGS: std::cerr << "invalid arguments"; break; case FT_NOT_SUPPORTED: std::cerr << "not supported"; break; case FT_OTHER_ERROR: std::cerr << "unknown other error"; break; case FT_DEVICE_LIST_NOT_READY: std::cerr << "device list not ready"; break; default: std::cerr << "unknown status: " << status; break; } std::cerr << std::endl; } bool CDVDevice::checkResponse(SDV_Packet &p, uint8_t response) const { if(p.start_byte != PKT_HEADER || p.header.packet_type != PKT_CONTROL || p.field_id != response) return true; return false; } bool CDVDevice::OpenDevice(const std::string &serialno, const std::string &desc, Edvtype dvtype, int8_t in_gain, int8_t out_gain) { auto status = FT_OpenEx((PVOID)serialno.c_str(), FT_OPEN_BY_SERIAL_NUMBER, &ftHandle); if (FT_OK != status) { FTDI_Error("FT_OpenEx", status); return true; } std::this_thread::sleep_for(std::chrono::milliseconds(50)); FT_Purge(ftHandle, FT_PURGE_RX | FT_PURGE_TX ); std::this_thread::sleep_for(std::chrono::milliseconds(50)); status = FT_SetDataCharacteristics(ftHandle, FT_BITS_8, FT_STOP_BITS_1, FT_PARITY_NONE); if (status != FT_OK) { FTDI_Error("FT_SetDataCharacteristics", status); return true; } status = FT_SetFlowControl(ftHandle, FT_FLOW_RTS_CTS, 0x11, 0x13); if (status != FT_OK) { FTDI_Error("FT_SetFlowControl", status); return true; } status = FT_SetRts(ftHandle); if (status != FT_OK) { FTDI_Error("FT_SetRts", status); return true; } if (std::string::npos == description.find("DF2ET")) { //for usb-3012 pull DTR high to take AMBE3003 out of reset. //for other devices noting is connected to DTR so it is a dont care status = FT_ClrDtr(ftHandle); if (status != FT_OK) { FTDI_Error("FT_ClrDtr", status); return true; } } else { // for DF2ET-3003 interface pull DTR low to take AMBE3003 out of reset. status = FT_SetDtr(ftHandle); std::this_thread::sleep_for(std::chrono::milliseconds(50)); if (FT_OK != status) { FTDI_Error("FT_SetDtr", status); return false; } } ULONG baudrate = (Edvtype::dv3000 == dvtype) ? 460800 : 921600; status = FT_SetBaudRate(ftHandle, baudrate); if (status != FT_OK) { FTDI_Error("FT_SetBaudRate", status); return false; } status = FT_SetLatencyTimer(ftHandle, 4); if (status != FT_OK) { FTDI_Error("FT_SetLatencyTimer", status); return true; } ULONG maxsize = sizeof(SDV_Packet); maxsize = (maxsize % 64) ? maxsize - (maxsize % 64U) + 64U : maxsize; status = FT_SetUSBParameters(ftHandle, maxsize, 0); if (status != FT_OK){ FTDI_Error("FT_SetUSBParameters", status); return true; } // NO TIMEOUTS! We are using blocking I/O!!! // status = FT_SetTimeouts(ftHandle, 200, 200 ); // if (status != FT_OK) // { // FTDI_Error("FT_SetTimeouts", status); // return false; // } description.assign(desc); description.append(" "); description.append(serialno); std::cout << "Opened " << description << " at " << baudrate << " baud with a " << maxsize << " byte max transfer size" << std::endl; if (InitDevice()) return true; const uint8_t limit = (Edvtype::dv3000 == dvtype) ? PKT_CHANNEL0 : PKT_CHANNEL2; for (uint8_t ch=PKT_CHANNEL0; ch<=limit; ch++) { if (ConfigureVocoder(ch, type, in_gain, out_gain)) return true; } return false; } bool CDVDevice::InitDevice() { SDV_Packet responsePacket, ctrlPacket; // ********** soft reset ************* ctrlPacket.start_byte = PKT_HEADER; ctrlPacket.header.payload_length = htons(3); ctrlPacket.header.packet_type = PKT_CONTROL; ctrlPacket.field_id = PKT_RESET; ctrlPacket.payload.ctrl.data.paritymode[0] = PKT_PARITYBYTE; ctrlPacket.payload.ctrl.data.paritymode[1] = 0x3U ^ PKT_RESET ^ PKT_PARITYBYTE; DWORD written = 0; auto status = FT_Write(ftHandle, &ctrlPacket, 7, &written); if (FT_OK != status) { FTDI_Error("Error writing soft reset packet", status); return true; } else if (7 != written) { std::cerr << "Incomplete soft reset packet write" << std::endl; return true; } if (GetResponse(responsePacket)) { std::cerr << "Error receiving response to reset" << std::endl; return true; } if (checkResponse(responsePacket, PKT_READY)) { std::cerr << "Invalid response to soft reset" << std::endl; dump("Soft Reset Response Packet:", &responsePacket, packet_size(responsePacket)); return true; } std::cout << "Successfully did a soft reset on " << description << std::endl; // ********** turn off parity ********* ctrlPacket.start_byte = PKT_HEADER; ctrlPacket.header.payload_length = htons(4); ctrlPacket.header.packet_type = PKT_CONTROL; ctrlPacket.field_id = PKT_PARITYMODE; ctrlPacket.payload.ctrl.data.paritymode[0] = 0; ctrlPacket.payload.ctrl.data.paritymode[1] = PKT_PARITYBYTE; ctrlPacket.payload.ctrl.data.paritymode[2] = 0x4U ^ PKT_PARITYMODE ^ PKT_PARITYBYTE; status = FT_Write(ftHandle, &ctrlPacket, 8, &written); if (FT_OK != status) { FTDI_Error("Error writing parity control packet: ", status); return true; } else if (8 != written) { std::cerr << "Incomplete disable parity packet write" << std::endl; return true; } if (GetResponse(responsePacket)) { std::cerr << "Error receiving response to parity set" << std::endl; return true; } if (checkResponse(responsePacket, PKT_PARITYMODE)) { std::cerr << "Invalid response to parity control" << std::endl; dump("Parity Ctrl Response Packet:", &responsePacket, packet_size(responsePacket)); return true; } std::cout << "Successfully disabled parity on " << description << std::endl; // ********* Product ID and Version ************* ctrlPacket.start_byte = PKT_HEADER; ctrlPacket.header.payload_length = htons(1); ctrlPacket.header.packet_type = PKT_CONTROL; ctrlPacket.field_id = PKT_PRODID; status = FT_Write(ftHandle, &ctrlPacket, 5, &written); if (FT_OK != status) { FTDI_Error("Error writing Product ID packet", status); return true; } else if (5 != written) { std::cerr << "Incomplete Product ID Packet write" << std::endl; return true; } if (GetResponse(responsePacket)) { std::cerr << "Error receiving response to Product ID request" << std::endl; return true; } if (checkResponse(responsePacket, PKT_PRODID)) { std::cerr << "Invalid response to Product ID query" << std::endl; dump("Product ID Response Packet", &responsePacket, packet_size(responsePacket)); return true; } productid.assign(responsePacket.payload.ctrl.data.prodid); ctrlPacket.field_id = PKT_VERSTRING; status = FT_Write(ftHandle, &ctrlPacket, 5, &written); if (FT_OK != status) { FTDI_Error("Error writing Version packet", status); return true; } else if (5 != written) { std::cerr << "Incomplete Version packet write" << std::endl; return true; } if (GetResponse(responsePacket)) { std::cerr << "Error receiving response to Version request" << std::endl; return true; } if (checkResponse(responsePacket, PKT_VERSTRING)) { std::cerr << "Invalid response to Version query" << std::endl; dump("Product Version Response Packet:", &responsePacket, packet_size(responsePacket)); return true; } const std::string version(responsePacket.payload.ctrl.data.version); std::cout << description << ": ID=" << productid << " Version=" << version << std::endl; return false; } void CDVDevice::Start() { feedFuture = std::async(std::launch::async, &CDVDevice::FeedDevice, this); readFuture = std::async(std::launch::async, &CDVDevice::ReadDevice, this); } bool CDVDevice::ConfigureVocoder(uint8_t pkt_ch, Encoding type, int8_t in_gain, int8_t out_gain) { SDV_Packet controlPacket, responsePacket; const uint8_t ecmode[] { PKT_ECMODE, 0x0, 0x0 }; const uint8_t dcmode[] { PKT_DCMODE, 0x0, 0x0 }; const uint8_t dstar[] { PKT_RATEP, 0x01U, 0x30U, 0x07U, 0x63U, 0x40U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x48U }; const uint8_t dmr[] { PKT_RATEP, 0x04U, 0x31U, 0x07U, 0x54U, 0x24U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x6FU, 0x48U }; const uint8_t chfmt[] { PKT_CHANFMT, 0x0, 0x0 }; const uint8_t spfmt[] { PKT_SPCHFMT, 0x0, 0x0 }; const uint8_t gain[] { PKT_GAIN, uint8_t(in_gain), uint8_t(out_gain) }; const uint8_t init[] { PKT_INIT, 0x3U }; const uint8_t resp[] { 0x0, PKT_ECMODE, 0x0, PKT_DCMODE, 0x0, PKT_RATEP, 0x0, PKT_CHANFMT, 0x0, PKT_SPCHFMT, 0x0, PKT_GAIN, 0x0, PKT_INIT, 0x0 }; controlPacket.start_byte = PKT_HEADER; controlPacket.header.payload_length = htons(1 + sizeof(SDV_Packet::payload.codec)); controlPacket.header.packet_type = PKT_CONTROL; controlPacket.field_id = pkt_ch; memcpy(controlPacket.payload.codec.ecmode, ecmode, 3); memcpy(controlPacket.payload.codec.dcmode, dcmode, 3); if (type == Encoding::dstar) { memcpy(controlPacket.payload.codec.ratep, dstar, 13); } else { memcpy(controlPacket.payload.codec.ratep, dmr, 13); } memcpy(controlPacket.payload.codec.chfmt, chfmt, 3); memcpy(controlPacket.payload.codec.spfmt, spfmt, 3); memcpy(controlPacket.payload.codec.gain, gain, 3); memcpy(controlPacket.payload.codec.init, init, 2); // write packet DWORD written; const DWORD size = packet_size(controlPacket); auto status = FT_Write(ftHandle, &controlPacket, size, &written); if (FT_OK != status) { FTDI_Error("error writing codec config packet", status); return true; } else if (size != written) { std::cerr << "Incomplete Configuration packet write" << std::endl; return true; } if (GetResponse(responsePacket)) { std::cerr << "Error reading Configuration response packet" << std::endl; return true; } if ((ntohs(responsePacket.header.payload_length) != 16) || (responsePacket.field_id != pkt_ch) || (0 != memcmp(responsePacket.payload.ctrl.data.resp, resp, sizeof(resp)))) { std::cerr << "Config response packet failed" << std::endl; dump("Configuration Response Packet:", &responsePacket, packet_size(responsePacket)); return true; }; std::cout << description << " channel " << (unsigned int)(pkt_ch - PKT_CHANNEL0) << " is now configured for " << ((Encoding::dstar == type) ? "D-Star" : "DMR/YSF") << std::endl; return false; } bool CDVDevice::GetResponse(SDV_Packet &packet) { FT_STATUS status; DWORD bytes_read; // get the start byte for (unsigned i = 0U; i < USB3XXX_MAXPACKETSIZE+2; ++i) { status = FT_Read(ftHandle, &packet.start_byte, 1, &bytes_read); if (FT_OK != status) { FTDI_Error("Reading packet start byte", status); return true; } if (packet.start_byte == PKT_HEADER) break; } if (packet.start_byte != PKT_HEADER) { std::cerr << "Couldn't find start byte!" << std::endl; return true; } // get the packet size and type (three bytes) DWORD bytesLeft = sizeof(packet.header); while (bytesLeft > 0) { status = FT_Read(ftHandle, &packet.header, sizeof(packet.header), &bytes_read); if (FT_OK != status) { FTDI_Error("Error reading response packet header", status); return true; } bytesLeft -= bytes_read; } bytesLeft = ntohs(packet.header.payload_length); if (bytesLeft > 1 + int(sizeof(packet.payload))) { std::cout << "AMBEserver: Serial payload exceeds buffer size: " << int(bytesLeft) << std::endl; return true; } while (bytesLeft > 0) { status = FT_Read(ftHandle, &packet.field_id, bytesLeft, &bytes_read); if (FT_OK != status) { FTDI_Error("Error reading packet payload", status); return true; } bytesLeft -= bytes_read; } return false; } void CDVDevice::AddPacket(const std::shared_ptr packet) { auto size = input_queue.push(packet); if (size > 200) { std::cerr << ((type==Encoding::dstar) ? "DStar" : "DMR/YSF") << " inQ size is overflowing! Shutting down..." << std::endl; raise(SIGINT); } } void CDVDevice::dump(const char *title, const void *pointer, int length) const { const uint8_t *data = (const uint8_t *)pointer; std::cout << title << std::endl; unsigned int offset = 0U; while (length > 0) { unsigned int bytes = (length > 16) ? 16U : length; for (unsigned i = 0U; i < bytes; i++) { if (i) std::cout << " "; std::cout << std::hex << std::setw(2) << std::right << std::setfill('0') << int(data[offset + i]); } for (unsigned int i = bytes; i < 16U; i++) std::cout << " "; std::cout << " *"; for (unsigned i = 0U; i < bytes; i++) { uint8_t c = data[offset + i]; if (::isprint(c)) std::cout << c; else std::cout << '.'; } std::cout << '*' << std::endl; offset += 16U; if (length >= 16) length -= 16; else length = 0; } } void CDVDevice::FeedDevice() { const std::string modules(g_Conf.GetTCMods()); const auto n = modules.size(); while (keep_running) { auto packet = input_queue.pop(); // blocks until there is something to pop, unless shutting down if (packet) { while (keep_running) // wait until there is room { if (buffer_depth < 2) break; std::this_thread::sleep_for(std::chrono::milliseconds(5)); } if (keep_running) { auto index = modules.find(packet->GetModule()); // save the packet in the vocoder's queue while the vocoder does its magic if (std::string::npos == index) { std::cerr << "Module '" << packet->GetModule() << "' is not configured on " << description << std::endl; } else { PushWaitingPacket(index, packet); const bool needs_audio = (Encoding::dstar==type) ? packet->DStarIsSet() : packet->DMRIsSet(); if (needs_audio) { SendData(index, (Encoding::dstar==type) ? packet->GetDStarData() : packet->GetDMRData()); } else { SendAudio(index, packet->GetAudioSamples()); } buffer_depth++; } } } } } void CDVDevice::ReadDevice() { while (keep_running) { // wait for something to read... DWORD RxBytes = 0; while (0 == RxBytes) { auto status = FT_GetQueueStatus(ftHandle, &RxBytes); if (FT_OK != status) { FTDI_Error("FT_GetQueueStatus", status); std::cerr << "Shutting down..." << std::endl; raise(SIGTERM); } if (0 == RxBytes) { std::this_thread::sleep_for(std::chrono::milliseconds(3)); if (! keep_running) return; } } SDV_Packet p; if (! GetResponse(p)) { ProcessPacket(p); } } }