Project

General

Profile

New Model #2311 ยป qs-tg-uv2.c

Mike Nix, 03/06/2015 08:21 AM

 

/*

Quansheng TG-UV2 Utility by Mike Nix <mnix@wanm.com.au>

Currently only reads the device memory, and displays the contents nicely formatted.
Supports:
All channel config, except CTSS/DCS settings (we know where they are, just haven't
bothered to interpret them yet)

All the settings configurable via Options in the original windows utility.

Both of the known keypad dances for enabling/disabling bands are detected/displayed.
and yes, we can do the same thing by updating the config.

Saves the config read from the device to a file (tguv2.dat)
**** THIS IS NOT THE SAME FORMAT USED BY THE WINDOWS UTILITY ****
The windows utility saves in some proprietry format... we just dump
a copy of the radio memory to disk :)

Reads a file saved above, and uploads it to the radio.
TODO:
There are at least 5 memory locations that do stuff I can't figure out (config->unk*)
I never found a way to enable transmit on the 470-520 band
(Australian UHF CB is at 476-477 Mhz). Probably need a firmware hack for this :(
Command-line options for settings so we can change the config easily.

Find a copy of the firmware, and the update utility so we can really open it up.


There is a command line option to manually set any byte in the config area for
testing purposes, but this is not the way to configure your radio normally :)

There is another command line option for sending arbitrary data to the radio in
programming mode...

Other Info:
-----------
There are positive responses (ACK) to the following commands if followed
by any other byte:
G, O, P, Q but have no idea what they do....
The known commands for programming and getting the model number are:
\002PnOGdAM Enter Programming Mode
M\002 Get Model Number? (Returns 'P5555' followed by F4 00 00)
R Read config memory
W Write config memory
Any 1-byte after entering program mode followed by 0x02 will return the model number
Other commands will not work until after you get the model number.

R command is followed by addr_hi, addr_lo, length
W command is followed by addr_hi, addr_lo, length then data bytes

A null byte is sent by the radio every time the display changes... no idea what use that is.
Further testing reveals that any 8 bytes written will do to start programming mode.
** maybe there is a special sequence for firmware mode?
Actually, any sequence of more than 1 byte containing "M" seems to hit program mode.
*/


#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/select.h>
#include <fcntl.h>
#include <termios.h>
#include <sys/ioctl.h>
#include <unistd.h>
#include <errno.h>

// Quansheng TG-UV2 uses 9600,N,8,2
#define RADIO_BAUD B9600

#define ACK 0x06

#define TGUV2_MEMSIZE 0x2000
#define TGUV2_CHANNELS 200
#define TGUV2_BANDS 8

// shorthand forms of integer types for convenience
typedef uint8_t u8;
typedef uint16_t u16;
typedef uint32_t u32;
typedef uint64_t u64;

#pragma pack(1)
struct tguv2_channel {
u32 freq; // frequency in 10s of Hz, stored in BCD
u32 txofs;// offset to tx frequency, 10s of Hz, stored in BCD
u8 rc_lo;
u8 tc_lo;
u8 code_type; // low nibble is rc_hi, high nibble is tc_hi
// or could be type number (0-3)
u8 flags1;
u8 flags2;
u8 flags3; // always 0xFF ??
u8 bandwidth; // channel step / bandwidth - 6=25, 9=100
u8 tx_power;
};

struct tguv2_channel2 { // extra channel data
u8 name[6];
u8 data[10];
};

#define TXPOWER_LOW 2
#define TXPOWER_MID 1
#define TXPOWER_HI 0

#define CF1_SHIFT_POS 0x01
#define CF1_SHIFT_NEG 0x02
#define CF2_REVERSE_ON 0x01
#define CF2_SCRAMBLE_ON 0x02
#define CF2_WideFM 0x10

struct vfo {
u8 current; // memory slot for this vfo: 0-199 = channel mode, 200-204=vfo band
u8 chan; // last used channel no
u8 memno; // last used band (as mem slot number 200-204)
};

struct tguv2_config {
struct tguv2_channel channels[TGUV2_CHANNELS]; // the channel memories
struct tguv2_channel bands[TGUV2_BANDS]; // current / most recent settings for each band
u8 cflags[TGUV2_CHANNELS]; // scan flags and band numbers
u8 bandid[TGUV2_BANDS]; // band ids (F0 - F4)
u8 reserved1[0x30]; // unused?, all 0xFF

u8 unk1; // 00
u8 squelch; // 0 - 9
u8 time_out_timer; // 01 (minutes)
u8 priority_channel;
u8 keylock; // 01=off, 00=on
u8 busy_lockout; // 01=off, 00=on
u8 vox; // 00=off, 1-9
u8 unk2; // FF
u8 keybeep; // 00=on, 01=off
u8 display; // 00=Freq, 01=Channel, 02=Name
u8 step; // FF=5, ??
u8 unk3; // FF
u8 unk4; // 00
u8 mode; // 00=DualWatch, 01=cross-band, other=off
u8 end_tone; // 0=on, 1=off
u8 vfo_model; // 0=VFO Disabled, other=VFO Enabled
struct vfo vfo[2];
u8 unk5; // 00
u8 reserved2[9]; // Unused?, ALL 0xFF
// band_restrict can be toggled by holding PTT+MON at Power on, until you hear a second beep
u8 band_restrict; // !=01 = all bands enabled
// this can be toggled by holding BAND during power on until the display is ******
// then enter 350390 on the keypad.
u8 txen350390; // FF=unset, 00=DIS, 01=EN (!=01 == DIS)
u8 reserved3[0xDE]; // Unused?, ALL 0xFF

struct tguv2_channel2 channels2[TGUV2_CHANNELS]; // channel names etc
};

#define CFLAG_SCAN 0x80
#define CFLAG_BANDMASK 0x07

#pragma pack()

#define PMODE_NONE -1
#define PMODE_PROGRAM 2
#define PMODE_FIRMWARE 1

double bandwidthmap[]={ 5.0, 6.25, 10.0, 12.5, 15, 20, 25, 30, 50, 100, 101, 102, 103, 104, 105, 5.0 };

// current radio state
int radio_mode=PMODE_NONE;

// Serial port options.
int debug=3;
int TX_enabled=0;

int TX_auto_on=1;
int RX_auto_on=1;


void hexdump(unsigned char *data, int len)
{
int i;
for (i=0; i<len; i++)
printf(" %02x", data[i]);
printf(" ");
for (i=0; i<len; i++)
printf("%c", isprint(data[i]) ? data[i] : '.');
}

void TX_on(int fd)
{
// raise RTS
if (TX_auto_on) {
// hardware works this out automatically so don't do anything
}
else {
int status;
if (debug>2) printf("RTS ON\r\n");
ioctl(fd, TIOCMGET, &status);
status |= TIOCM_RTS;
if (ioctl(fd, TIOCMSET, &status)) {
perror("ioctl");
}
}
TX_enabled=1;
}

void TX_off(int fd)
{
// lower RTS
if (TX_auto_on) {
// hardware does this automatically so don't do anything
}
else {
int status;
if (debug>2) printf("RTS OFF\r\n");
tcdrain(fd);
ioctl(fd, TIOCMGET, &status);
status &= ~TIOCM_RTS;
ioctl(fd, TIOCMSET, &status);
}
TX_enabled=0;
}

void RX_on(int fd)
{
// raise DTR
if (RX_auto_on) {
}
else {
int status;
if (debug>2) printf("DTR ON\r\n");
ioctl(fd, TIOCMGET, &status);
status |= TIOCM_DTR;
if (ioctl(fd, TIOCMSET, &status)) {
perror("ioctl");
}
}
}

void RX_off(int fd)
{
// lower DTR
if (RX_auto_on) {
}
else {
int status;
if (debug>2) printf("DTR OFF\r\n");
ioctl(fd, TIOCMGET, &status);
status &= ~TIOCM_DTR;
ioctl(fd, TIOCMSET, &status);
}
}


void TX(int fd, unsigned char *data, int len)
{
if (!TX_enabled) TX_on(fd);
if (debug>3) {
printf("TX:");
hexdump(data,len);
printf("\r\n");
}
write(fd, data, len);
}

int RX(int fd, unsigned char *data, int len)
{
int rc = read(fd, data, len);
if (debug>3 && rc>0) {
printf("RX:");
hexdump(data,rc);
printf("\r\n");
}
return rc;
}


#define TEST1

int start_pgm_mode(int fd, int pmode)
{
u8 buf[8];

if (pmode==PMODE_FIRMWARE) {
buf[0]=0x02;
buf[1]='F';
buf[2]='i';
buf[3]='r';
buf[4]='m';
buf[5]='w';
buf[6]='a';
buf[7]='r';
}
else {
#ifndef TEST1
buf[0]=0x02;
buf[1]=0x50; // "PnOGdAM"
buf[2]=0x6E;
buf[3]=0x4F;
buf[4]=0x47;
buf[5]=0x64;
buf[6]=0x41;
buf[7]=0x4D;
#else
buf[0]=0x02;
buf[1]='P'; // "PnOGdAM"
buf[2]='n';
buf[3]='O';
buf[4]='G';
buf[5]='d';
buf[6]='A';
buf[7]='M';
#endif
}
TX(fd, buf, 8);
if (RX(fd, buf, 1)==1) {
if (buf[0]==ACK) {
printf("Radio detected\n");
radio_mode=pmode;
return 0;
}
printf("Bad response from radio: 0x%02x\n", buf[0]);
return -ENODEV;
}
printf("No response from radio\n");
return -ENODEV;
}

int read_model(int fd, u8 *model) {
u8 buf[8];
int rc;
#ifndef TEST1
buf[0]=0x4D; // 'M'
buf[1]=0x02;
#else
buf[0]='M'; // 'M'
buf[1]=0x02;
#endif
TX(fd, buf, 2);
rc=RX(fd, model, 8);
if (rc!=8) {
printf("Error reading model data, got %d bytes:", rc);
if (rc>0) hexdump(model,rc);
printf("\n");
return -1;
}
buf[0]=ACK;
TX(fd, buf, 1);
if (RX(fd, buf, 1)!=1) {
printf("Didn't get expected ACK response\n");
return -1;
}
if (buf[0]!=ACK) {
printf("Expected ACK, got 0x%02x\n", buf[0]);
}
printf("Model: ");
hexdump(model, 8);
printf("\r\n");
return 0;
}

int read_block(int fd, void *dest, u8 *buf, u16 addr, u8 len) {
printf("Reading memory 0x%04x\r", addr);
memset(buf, 0, 12);
buf[0]='R';
buf[1]=(addr >> 8) & 0xFF;
buf[2]=addr & 0xFF;
buf[3]=len;
TX(fd, buf, 4);
if (RX(fd, buf, len+4)==len+4) {
if (buf[0]==0x57 && buf[3]==len &&
(buf[1]<<8 | buf[2]) == addr
) {
memcpy(dest, buf+4, len);
return 1;
}
else {
printf("\nunexpected data for address 0x%04x\n", addr);
}
}
else {
printf("\nFailed read for address 0x%04x\n", addr);
}
return 0;
}

int read_block2(int fd, void *dest, u8 *buf, u32 addr, u8 len) {
printf("Reading memory 0x%04x\r", addr);
memset(buf, 0, 12);
buf[0]='R';
buf[1]=(addr >> 24) & 0xFF;
buf[2]=(addr >> 16) & 0xFF;
buf[3]=(addr >> 8) & 0xFF;
buf[4]=addr & 0xFF;
buf[5]=len;
TX(fd, buf, 6);
if (RX(fd, buf, len+4)==len+4) {
if (buf[0]==0x57 && buf[5]==len &&
(buf[3]<<8 | buf[4]) == addr
) {
memcpy(dest, buf+4, len);
return 1;
}
else {
printf("\nunexpected data for address 0x%04x\n", addr);
}
}
else {
printf("\nFailed read for address 0x%04x\n", addr);
}
return 0;
}

int write_block(int fd, u8 *src, u16 addr, u8 len) {

u8 buf[4];
printf("Writing memory 0x%04x\r", addr);
buf[0]='W';
buf[1]=(addr >> 8) & 0xFF;
buf[2]=addr & 0xFF;
buf[3]=len;
TX(fd, buf, 4);
TX(fd, src, len);
if (RX(fd, buf, 1)==1) {
if (buf[0]!=ACK)
printf("Expected ACK, got 0x%02x for address 0x%04d\n", buf[0], addr);
}
else {
printf("\nDid not receive ACK for address 0x%04x\n", addr);
}
}


int test_cmd(int fd, unsigned char *cmd, u8 sendlen, u8 expectlen) {

printf("Radio Mode: ");
switch (radio_mode) {
case PMODE_NONE: printf("NONE"); break;
case PMODE_PROGRAM: printf("PROGRAM"); break;
case PMODE_FIRMWARE: printf("FIRMWARE"); break;
default : printf("Unknown: %d", radio_mode); break;
}
printf("\n");

printf("Test Command: (%d bytes) ", sendlen);
hexdump(cmd, sendlen);
printf("\n");
TX(fd, cmd, sendlen);

int dbg=debug;
debug=4;
int rc=RX(fd, cmd, expectlen);
debug=dbg;
printf("Test Command: RX returned %d (expected %d bytes)\n", rc, expectlen);
return 0;
}


int read_memory(int fd, struct tguv2_config *config, int setuponly) {
int addr, end;
u8 buf[12];
u8 *data=(u8*)config;
addr=0;
end=TGUV2_MEMSIZE;
if (setuponly) {
addr=0x0C80;
end =0x0F00;
}
for (; addr < end; addr += 8) {
read_block(fd, data+addr, buf, addr, 8);
}
return TGUV2_MEMSIZE;
}


int write_memory(int fd, struct tguv2_config *config, int setuponly) {
int addr, end;
u8 *data=(u8*)config;
addr=0;
end=TGUV2_MEMSIZE;
if (setuponly) {
addr=0x0C80;
end =0x0F00;
}

for (; addr < end; addr += 8) {
write_block(fd, data+addr, addr, 8);
}
return TGUV2_MEMSIZE;
}

int read_channel(int fd, struct tguv2_config *config, u8 channel) {\
int addr, end;
u8 buf[12];
u8 *data;

if (radio_mode != PMODE_PROGRAM) {
printf("read_channel called with radio not in PROGRAM mode\n");
return;
}
// read the main info
data=(u8*)(&config->channels[channel]);
addr=data - (u8*)config;
read_block(fd, data, buf, addr, 8);
read_block(fd, data+8, buf, addr+8, 8);

// read the extra info (name)
data=(u8*)(&config->channels2[channel]);
addr=data - (u8*)config;
read_block(fd, data, buf, addr, 8);
read_block(fd, data+8, buf, addr+8, 8);
}

int write_channel(int fd, struct tguv2_config *config, u8 channel) {\
int addr, end;
u8 *data;

if (radio_mode != PMODE_PROGRAM) {
printf("write_channel called with radio not in PROGRAM mode\n");
return;
}
// read the main info
data=(u8*)(&config->channels[channel]);
addr=data - (u8*)config;
write_block(fd, data, addr, 8);
write_block(fd, data+8, addr+8, 8);

// read the extra info (name)
data=(u8*)(&config->channels2[channel]);
addr=data - (u8*)config;
write_block(fd, data, addr, 8);
write_block(fd, data+8, addr+8, 8);
}

void save_memory(char *fname, struct tguv2_config *data)
{
int fd=open(fname, O_CREAT | O_WRONLY, 0660);
write(fd, data, TGUV2_MEMSIZE);
close(fd);
}

void load_memory(char *fname, struct tguv2_config *data)
{
int fd=open(fname, O_RDONLY, 0660);
read(fd, data, TGUV2_MEMSIZE);
close(fd);
}

u32 bcdtoint(u32 bcd)
{
u32 i=0;
int j;
bcd = be32toh(bcd);
for (j=7; j>=0; j--) {
i+=(bcd >> (4*j)) & 0x0F;
i*=10;
}
return i;
}

const char charmap[]="0123456789ABCDEFGHIJKLMNOPWRSTUVWXYZ |* +-\0";

char *channel_name(u8 *enc, u8 len)
{
u8 i;
static char buf[40];
for (i=0; i<len; i++) {
if (enc[i] <= 43)
buf[i]=charmap[enc[i]];
else buf[i]='.';
}
buf[i++]=0;
return buf;
}

void name_channel(u8 *enc, char *name, u8 len)
{
int i,j;
for (i=0; i<len && *name; i++, name++) {
for (j=0; charmap[j] && charmap[j]!=*name; j++);
if (charmap[j]) *enc++=j;
}
}

void print_channel(struct tguv2_channel *chan)
{
u32 rxf, txf;

rxf = bcdtoint(chan->freq);
txf = rxf;
if (chan->flags1 & CF1_SHIFT_POS)
txf += bcdtoint(chan->txofs);
if (chan->flags1 & CF1_SHIFT_NEG)
txf -= bcdtoint(chan->txofs);

printf("%10.6f %10.6f %6.2f ",
rxf / 1000000.0,
txf / 1000000.0,
bandwidthmap[chan->bandwidth & 0x0F]);
printf("%-3s %-3s %-4s ",
(chan->flags2 & CF2_SCRAMBLE_ON ? "OFF" : "ON"),
(chan->flags2 & CF2_REVERSE_ON ? "OFF" : "ON"),
(chan->flags2 & CF2_WideFM ? "Wide" : "Narr"));
switch (chan->tx_power) {
case TXPOWER_LOW: printf("LOW"); break;
case TXPOWER_MID: printf("MID"); break;
case TXPOWER_HI : printf("HI "); break;
default: printf("???");
}

}
void list_channels(struct tguv2_config *conf)
{
int c;
printf(" CH RX TX Step Scr REV Wide Pow SCN BAND CH_Name\n");
for (c=0; c<TGUV2_CHANNELS; c++) {
if (conf->channels[c].freq != 0xFFFFFFFF) {
printf("%3d: ", c);
print_channel(&(conf->channels[c]));

printf(" %-3s %02X",
conf->cflags[c] & CFLAG_SCAN ? "Yes" : "No",
conf->bandid[conf->cflags[c] & CFLAG_BANDMASK]);

printf(" %s\n", channel_name(conf->channels2[c].name, 6));
}
}

}


void list_bands(struct tguv2_config *conf)
{
int b;
printf(" RX TX Step Scr REV Wide Pow\n");
for (b=0; b<TGUV2_BANDS; b++) {
if (conf->bands[b].freq != 0xFFFFFFFF) {
printf("Band %02X: ", conf->bandid[b]);
print_channel(&(conf->bands[b]));
printf("\n");
}
}
}


void list_config(struct tguv2_config *conf)
{
printf("Squelch : %d\n", conf->squelch);
printf("Time-Out-Timer : %d seconds\n", conf->time_out_timer*60);
printf("Priority Channel: ");
if (conf->priority_channel < 200) printf("%d\n", conf->priority_channel);
else printf("OFF\n");
printf("Busy Lockout : %s (%02x)\n", conf->busy_lockout==0x01 ? "OFF" : "ON", conf->busy_lockout);
printf("VOX : %d\n", conf->vox);
printf("Key Lock : %s\n", conf->keylock==0x01 ? "OFF" : "ON");
printf("Key Beep : %s\n", conf->keybeep==0x01 ? "OFF" : "ON");
printf("Freq Step : %1.2f (%02x)\n", bandwidthmap[conf->step & 0x0F], conf->step & 0x0F, conf->step);
printf("End Tone : %s\n", conf->end_tone ? "OFF" : "ON");
printf("VFO Model : %s (%02x)\n", conf->vfo_model==0x00 ? "No" : "Yes", conf->vfo_model);

printf("Mode : ");
switch (conf->mode) {
case 0x00: printf("Dual Watch"); break;
case 0x01: printf("Cross-Band"); break;
default: printf("Normal (Channel/VFO) (%02x)", conf->mode);
}
printf("\n");

printf("Channel Display : ");
switch (conf->display) {
case 0x00: printf("Ch/Frequency"); break;
case 0x01: printf("Channel"); break;
case 0x02: printf("Ch/Name"); break;
default: printf("??? (%02x)", conf->display);
}
printf("\n");
int v;
for (v=0; v<2; v++) {
if (conf->vfo[v].current < 200) {
printf("VFO%d : Channel %d\n", v, conf->vfo[v].chan);
}
else {
printf("VFO%d : Band %02X, Freq %10.6f\n",
v,
conf->bandid[conf->vfo[v].memno - 200],
bcdtoint(conf->bands[conf->vfo[v].memno - 200].freq)/1000000.0);
}
}
printf("Band Restrict : %s\n", conf->band_restrict == 0x01 ? "Enabled" : "Disabled");
printf("350-390 Mhz TX : %s\n", conf->txen350390 == 0x01 ? "Enabled" : "Disabled");
printf("test: %02x %02x\n", conf->band_restrict, conf->txen350390);
printf("Unknown Bytes : %02X %02X %02X %02X %02X\n",
conf->unk1, conf->unk2, conf->unk3, conf->unk4, conf->unk5);
printf("Reserved Bytes :\n1: ");
hexdump(conf->reserved1, sizeof(conf->reserved1));
printf("\n2: ");
hexdump(conf->reserved2, sizeof(conf->reserved2));
printf("\n3: ");
hexdump(conf->reserved3, sizeof(conf->reserved3));
printf("\n");
}

int openserial(char *port)
{
struct termios sp;
if (debug>1) printf("Opening %s\r\n", port);
// open non-blocking otherwise open can block until carrier appears
int fd = open(port, O_RDWR | O_NONBLOCK);
if (fd<0) return -ENODEV;

memset(&sp,0,sizeof(sp));
sp.c_iflag=0;
sp.c_oflag=0;
sp.c_cflag=CS8|CREAD|CLOCAL|CSTOPB;
sp.c_lflag=0;
sp.c_cc[VMIN]=100;
sp.c_cc[VTIME]=5;
cfsetispeed(&sp,RADIO_BAUD);
cfsetospeed(&sp,RADIO_BAUD);

tcsetattr(fd, TCSANOW, &sp);
if (debug>3) printf("tcflush()\r\n");

// now switch to blocking IO because that's what we want
fcntl(fd, F_SETFL, fcntl(fd, F_GETFL) & ~O_NONBLOCK);
tcflush(fd, TCIOFLUSH);
return fd;
}


void help()
{
printf("Quansheng TG-UV2 Radio control tool 0.1\n");
printf("Usage: radio <options>\n");
printf(" -r f read config from file f\n");
printf(" -w f write config to file f\n");
printf(" -g get config from radio\n");
printf(" -p put config to radio\n");
printf(" -l list current config\n");
printf(" -c list current channels\n");
printf("\n");
printf("To select a port other than /dev/ttyUSB0 specify the name as the first option\n");
printf("\n\n");
printf("Debugging Tools: (DO NOT USE)\n");
printf(" -s addr value - set config byte at addr to value\n");
printf(" -f addr value len - fill len bytes from addr with value\n");
printf(" -e len - expect len byte reply to test command\n");
printf(" -T <data> - Test command in program mode\n");
printf(" -t <data> - Test command.\n");
printf(" -F <data> - Test command in firmware mode ** DANGEROUS **\n");
printf(" <data> consists of a length byte, followed by length characters\n");
printf(" or numeric values (escape digits with \\)\n");
printf(" -D addr len - dump a chunk of memory\n");
}


int init_radio(int *serial, char *port, u8 *model, int pmode) {
if (*serial<0) {
*serial=openserial(port);
if (*serial < 0) {
perror("Error opening port");
return 0;
}
RX_on(*serial);

if (start_pgm_mode(*serial, pmode)==0 && read_model(*serial, model)==0) {
return 1;
}
close(*serial);
*serial=-1;
return 0;
}
if (radio_mode != pmode) {
printf("Can't change programming mode during a run.\n");
printf("You can not mix and match firmware updates and programming updates\n");
return 0;
}
return 1;
}

main (int argc, char *argv[])
{
struct timeval tv;
fd_set rfds, tfds;
int retval;
int quit=0;
int i;
int setaddr=-1;
int setvalue=0x00;
int setlen=0;
unsigned char cmd_buf[20];
int cmd_len=0;
int cmd_expect=8;
char *port="/dev/ttyUSB0";
int serial = -1;

u8 model[20];
int config_valid=0, channels_valid=0;
struct tguv2_config *config;
if (sizeof(*config) > TGUV2_MEMSIZE) {
printf("Program Error: tguv2_config structure is too big for device memory!\n");
printf(" sizeof(config) = %d, TGUV2_MEMSIZE=%d\n", sizeof(*config), TGUV2_MEMSIZE);
exit(0);
}
config = malloc(TGUV2_MEMSIZE);

i=1;
while (i<argc) {
if (strncmp(argv[i],"/dev/",5)==0) {
port=argv[i];
}
else if (strcmp(argv[i],"-h")==0) {
help();
quit=1;
}
else if (strcmp(argv[i],"-q")==0) {
quit=1;
}
else if (strcmp(argv[i],"-r")==0) { // read config from file
i++;
load_memory(argv[i], config);
config_valid=1;
channels_valid=1;
}
else if (strcmp(argv[i],"-w")==0) { // write config to file
i++;
if (!config_valid && channels_valid) {
printf("Write config to file: load a config with -r or -g first\n");
}
else {
save_memory(argv[i], config);
}
}
else if (strcmp(argv[i],"-g")==0) { // get config from radio
if (init_radio(&serial, port, model, PMODE_PROGRAM)) {
read_memory(serial, config, 0);
config_valid=1;
channels_valid=1;
}
}
else if (strcmp(argv[i],"-p")==0) { // put config to radio
if (!config_valid && channels_valid) {
printf("Put Config: load a config with -r or -g first\n");
}
else if (init_radio(&serial, port, model, PMODE_PROGRAM)) {
write_memory(serial, config, 0);
}
}
else if (strcmp(argv[i], "-c")==0) { // list channels in loaded config
if (config_valid) {
list_channels(config);
}
else {
printf("List Channels: load a config with -r or -g first\n");
}
}
else if (strcmp(argv[i], "-l")==0) { // list loaded config
if (config_valid) {
list_bands(config);
list_config(config);
}
else {
printf("List Config: load a config with -r or -g first\n");
}
}
else if (strcmp(argv[i],"-s")==0) { // set config bytes
i++;
setaddr=strtol(argv[i], NULL, 0);
i++;
setvalue=strtol(argv[i], NULL, 0);
setlen=1;

if (setlen>0 && init_radio(&serial, port, model, PMODE_PROGRAM)) {
if (!config_valid) {
read_memory(serial, config, 1); // only read the config area
config_valid=1;
}

memset(((void *)config + setaddr), setvalue & 0xFF, setlen & 0xFF);

write_memory(serial, config, 1); // only write the config area
}
}
else if (strcmp(argv[i],"-f")==0) { // fill config bytes
i++;
setaddr=strtol(argv[i], NULL, 0);
i++;
setvalue=strtol(argv[i], NULL, 0);
i++;
setlen=strtol(argv[i], NULL, 0);

if (setlen>0 && init_radio(&serial, port, model, PMODE_PROGRAM)) {
if (!config_valid) {
read_memory(serial, config, 1); // only read the config area
config_valid=1;
}
memset(((void *)config + setaddr), setvalue & 0xFF, setlen & 0xFF);

write_memory(serial, config, 1); // only write the config area
}
}
else if (strcmp(argv[i],"-D")==0) { // dump a section of memory
i++;
setaddr=strtol(argv[i], NULL, 0);
i++;
setlen=strtol(argv[i], NULL, 0);
if (setlen>0 && init_radio(&serial, port, model, radio_mode == PMODE_NONE ? PMODE_PROGRAM : radio_mode)) {
char *data=malloc(setlen + 16);
char buf[12];
int p=0;
// read_block = 16bit address, read_block2 = 32bit address
while (p<setlen) {
if (read_block(serial, data+p, buf, setaddr+p, 8) ||
read_block(serial, data+p+8, buf, setaddr+p+8, 8)
) {
hexdump(data+p, 16);
printf("\n");
p+=16;
}
else {
p=setlen;
}
}
free(data);
}
}
else if (strcmp(argv[i],"-e")==0) { // set expected response
i++;
cmd_expect=strtol(argv[i],NULL,0);
}
else if ((strcmp(argv[i],"-t")==0) ||
(strcmp(argv[i],"-T")==0) ||
(strcmp(argv[i],"-F")==0)
)
{ // test commands
int from_program_mode=0;
int j;
char *ep;
if (strcmp(argv[i],"-T")==0) from_program_mode=PMODE_PROGRAM;
if (strcmp(argv[i],"-F")==0) from_program_mode=PMODE_FIRMWARE;
i++;
cmd_len=strtol(argv[i],NULL,0);
if (cmd_len) {
printf("%d byte command\n", cmd_len);
for (j=0; j<cmd_len; j++) {
i++;
ep=NULL;
cmd_buf[j] = strtol(argv[i], &ep, 0);
if (ep && !*ep) {
printf(" 0x%02x", cmd_buf[j]);
}
else {
if (*argv[i] == '\\')
cmd_buf[j]=argv[i][1];
else cmd_buf[j]=argv[i][0];
printf(" %c (0x%02x)", cmd_buf[j], cmd_buf[j]);
}
}
printf("\n");
}
else {
i++;
printf("string command: '%s'\n", argv[i]);
strcpy(cmd_buf, argv[i]);
cmd_len=strlen(argv[i]);
}
if (cmd_len) {
if (from_program_mode) {
if (init_radio(&serial, port, model, from_program_mode)) {
test_cmd(serial, cmd_buf, cmd_len, cmd_expect);
}
else {
printf("Unable to enter program mode\n");
}
}
else {
if (serial<0) {
serial=openserial(port);
if (serial < 0) {
perror("Error opening port");
}
RX_on(serial);
}
test_cmd(serial, cmd_buf, cmd_len, cmd_expect);
}
}
}
i++;
}
#if 0
// update a couple of channels - if you want to do more than a few
// it's probably easier to read/write the whole memory (but slower).
read_channel(serial, config, 1);
read_channel(serial, config, 2);
config->channels[1].tx_power=TXPOWER_HI;
config->channels[2].tx_power=TXPOWER_HI;
write_channel(serial, config, 1);
write_channel(serial, config, 2);
#endif

if (serial>0) {
tcdrain(serial);
usleep(200000);
close(serial);
serial=-1;
}
}
    (1-1/1)