Commit 7d29d1f8 authored by gophi's avatar gophi

initial commit


git-svn-id: http://svn.chmurka.net/owx/trunk/owx@1 8cc89244-2450-4880-90d3-191243f3a0b0
parents
/* $Id$ */
2011-03-09
- fixed silly bug in endianness handling code
2011-02-20
- changed endianness-related code to compile on MacOSX (thx Tod Fitch)
- changed maximum non-bogus UHF frequency from 500 to 600 MHz (thx Tod Fitch)
2011-02-17
- added UVD3 welcome message editing (thx SQ5LWN)
- removed references in some places in export code
- exported FromHexOne() in Util namespace
2011-01-30
- added encoding and decoding of frequency ranges (thx K7DB)
2011-01-29
- added Apache 2.0 license to README
2011-01-28
- added support for DCS and TEAM 2 to TODO
2010-11-12
- fixed bug when importing channels (were imported 127, not 128)
- fixed bug in makefile (added -f to ln)
2010-11-02
- changed tcdrain and program_invocation_short_name for cygwin compatibility
2010-10-31
- added FM radio import/export
- added support for .tw files to TODO
- minor changes in the code and README
- added cstdio include to some files (thanks ur6lad)
2010-10-29
- minor changes in README file
- RSS feed for changelog
2010-10-28
- complete rewrite
2010-07-17
- initial release as a little utility
# $Id$
.PHONY: all
all:
cd src && $(MAKE) all
.PHONY: install
install:
cd src && $(MAKE) install
.PHONY: clean
clean:
cd src && $(MAKE) clean
This diff is collapsed.
/* $Id$ */
Wouxun protocol information.
File written by SP5GOF using information reverse-engineered by SQ5LWN.
Communication parameters are 9600 8n1, with flow control disabled.
After you connect, you must send: "HiWOUXUN" (without quotes and null)
and 0x02. Radio will acknowledge by sending back: 0x06. After that you
send 0x02, radio acknowledges with 0x06 and sends back it's ID string
(6 bytes, "KG669V") and 0xF8. We're not sure what 0xF8 means - maybe
some kind of software revision or similar?
After this handshake you can download (read) and upload (write) memory
pages by sending 4-byte commands:
1 byte: command
2 bytes: address (little endian)
1 byte: size
For reading, use command 'R' and size 0x40. For writing, use command 'W'
and size 0x10. After write command send 16 (0x10) bytes in raw binary.
After you send write command, radio will acknowledge it with ACK byte
(0x06).
After you send read command, radio will acknowledge it by re-sending your
command string back to you with command reversed, so 'R' becomes 'W'.
After this 64 (0x40) bytes of raw binary data will follow. After receiving
you must send ACK (0x06) byte and radio will reply with 0x06 byte.
Wouxun memory map can be found on SQ5LWN page:
http://www.baseciq.org/tools/wouxunmemmap
This diff is collapsed.
/* $Id$ */
- use some version control system (possibly SVN; files already have rcsid tags)
- discover how frequency ranges are coded
- add support to Windows .tw files
- add support to DCS
- add support to TEAM 2 broadcast bank
- port autodetection (probably as a shell script)
- make installation more flexible (instead of hard-coded /usr/local/)
- install documentation as well as binaries
- write manual page/pages
- some autoconf/automake machinery (is it really needed?)
- i18n (see intl.h)
- grep for xxx in sources - these are places to return to
# $Id$
NAME = owx
CXX = /usr/bin/g++
RM = rm -f
INSTALL = install
LN = ln
LIBXDIR = /usr/local/libexec/
BINDIR = /usr/local/bin/
CXXFLAGS= -pipe -Wall -Wextra -O2 -g
LDFLAGS =
OBJS = owx.o cli.o throw.o cmds.o wouxun.o comm.o file.o csv.o export.o import.o util.o
SRCS = $(OBJS:.o=.cc)
.PHONY: all
all: dep $(NAME)
$(NAME): $(OBJS)
$(CXX) $(CXXFLAGS) $(CPPFLAGS) $(LDFLAGS) $(TARGET_ARCH) -o $(NAME) $(OBJS)
.PHONY: dep
dep: .depend
.depend: $(SRCS)
$(CXX) -MM $(CXXFLAGS) $(SRCS) 1> .depend
.PHONY: install
install: all
$(INSTALL) -d $(LIBXDIR)
$(INSTALL) -m 755 $(NAME) $(LIBXDIR)
$(INSTALL) -d $(BINDIR)
$(LN) -f -s $(LIBXDIR)$(NAME) $(BINDIR)$(NAME)-check
$(LN) -f -s $(LIBXDIR)$(NAME) $(BINDIR)$(NAME)-get
$(LN) -f -s $(LIBXDIR)$(NAME) $(BINDIR)$(NAME)-put
$(LN) -f -s $(LIBXDIR)$(NAME) $(BINDIR)$(NAME)-export
$(LN) -f -s $(LIBXDIR)$(NAME) $(BINDIR)$(NAME)-import
.PHONY: clean
clean:
$(RM) $(OBJS) $(NAME) .depend
.PHONY: install
install:
ifneq ($(wildcard .depend),)
include .depend
endif
/* $Id$ */
#include <unistd.h>
#include "throw.h"
#include "intl.h"
#include "cli.h"
CCli::CCli(int ac, char * const av[], const char *optstring)
{
opterr = 0;
for (;;)
{
int rs(getopt(ac, av, optstring));
if (rs == -1)
break;
if (rs == '?')
Throw(_("CLI: -%c: Invalid option"), optopt);
if (rs == ':')
Throw(_("CLI: -%c: Missing argument"), optopt);
m_data[rs] = (optarg && *optarg) ? optarg : "";
}
for (int i(optind); i < ac; ++i)
m_rest.push_back(av[i]);
}
bool CCli::Avail(const char &key) const
{
return m_data.find(key) != m_data.end();
}
const std::string &CCli::Param(const char &key) const
{
return m_data.find(key)->second;
}
size_t CCli::RestSize() const
{
return m_rest.size();
}
const std::string &CCli::Rest(size_t i) const
{
return m_rest[i];
}
/* $Id$ */
#pragma once
#include <string>
#include <vector>
#include <map>
class CCli
{
private:
std::map<char, std::string> m_data;
std::vector<std::string> m_rest;
public:
CCli(int ac, char * const av[], const char *optstring);
bool Avail(const char &key) const;
const std::string &Param(const char &key) const;
size_t RestSize() const;
const std::string &Rest(size_t i) const;
};
/* $Id$ */
#include <cstdlib>
#include <cstring>
#include <cstdio>
#include "wouxun.h"
#include "impexp.h"
#include "throw.h"
#include "intl.h"
#include "file.h"
#include "cmds.h"
#include "csv.h"
#define MEMORY_SIZE 0x2000
static void RejectOptions(const CCli &cli, const std::string forbidden)
{
for (std::string::const_iterator i(forbidden.begin()); i != forbidden.end(); ++i)
if (cli.Avail(*i))
Throw(_("Option \"-%c\" not available with this command. See help"), *i);
}
static std::string GetEnv(const std::string &name)
{
const char *env(getenv(name.c_str()));
return env ? env : "";
}
static void PrepareWouxun(const CCli &cli, CWouxun &wx)
{
std::string port(GetEnv("OWX_PORT"));
if (cli.Avail('a'))
Throw(_("Port autodetection not working yet. Sorry :)"));
if (cli.Avail('p'))
port = cli.Param('p');
if (port.empty())
Throw(_("Port not specified. See help"));
wx.SetPort(port);
int timeout(0);
bool set(false);
const std::string env(GetEnv("OWX_TIMEOUT"));
if (!env.empty())
{
timeout = atoi(env.c_str());
set = true;
}
if (cli.Avail('t'))
{
timeout = atoi(cli.Param('t').c_str());
set = true;
}
if (set)
{
if (timeout < 0)
Throw(_("Invalid timeout specified. Must be positive"));
wx.SetTimeout(timeout);
}
wx.Open();
printf(_("Found radio: %s\n"), wx.GetIDString().c_str());
if (wx.GetIDString() != "KG669V")
{
if (cli.Avail('f'))
printf(_("Warning: This radio was not tested and may be not supported!\n"));
else
Throw(_("This radio is not supported. See help"));
}
}
void CmdUnknown(const CCli & /* cli */)
{
Throw(_("Bad command or command not specified. See help"));
}
void CmdCheck(const CCli &cli)
{
RejectOptions(cli, "ior");
CWouxun wx;
PrepareWouxun(cli, wx);
}
void CmdGet(const CCli &cli)
{
RejectOptions(cli, "ir");
if (!cli.Avail('o'))
Throw(_("Binary file to write to not specified, use -o"));
CFileWrite f;
f.Open(cli.Param('o').c_str());
CWouxun wx;
PrepareWouxun(cli, wx);
char buf[MEMORY_SIZE];
for (size_t i(0); i < sizeof(buf); i += 0x40)
{
printf(_("Reading address 0x%04X (%u%% done)\r"), i, i * 100 / sizeof(buf));
fflush(stdout);
wx.GetPage(i, buf + i);
}
printf(_("\n"));
f.Write(buf, sizeof(buf));
f.Close();
}
static void LoadBinFile(const std::string &path, char *buf, size_t sz)
{
CFileRead f;
f.Open(path.c_str());
if (f.Size() != sz)
Throw(_("%s: Invalid file size"), path.c_str());
if (f.Read(buf, sz) != sz)
Throw(_("%s: Error reading"), path.c_str());
}
void CmdPut(const CCli &cli)
{
RejectOptions(cli, "o");
if (!cli.Avail('i'))
Throw(_("Binary file to read from not specified, use -i"));
if (!cli.Avail('r'))
printf(_("Consider using -r\n"));
char buf[MEMORY_SIZE];
LoadBinFile(cli.Param('i'), buf, sizeof(buf));
bool useref(cli.Avail('r'));
char refbuf[sizeof(buf)];
if (useref)
LoadBinFile(cli.Param('r'), refbuf, sizeof(refbuf));
CWouxun wx;
PrepareWouxun(cli, wx);
for (size_t i(0); i < sizeof(buf); i += 0x10)
{
bool skip(false);
if (useref && !memcmp(buf + i, refbuf + i, 0x10))
skip = true;
printf(_("%s address 0x%04X (%u%% done) \r"), skip ? _("Skipping") : _("Writing"), i, i * 100 / sizeof(buf));
fflush(stdout);
if (!skip)
wx.PutPage(i, buf + i);
}
printf(_("\n"));
}
void CmdExport(const CCli &cli)
{
RejectOptions(cli, "fptr");
if (!cli.Avail('i'))
Throw(_("Binary file to export from was not specified"));
if (!cli.Avail('o'))
Throw(_("CSV file to export to was not specified"));
char buf[MEMORY_SIZE];
LoadBinFile(cli.Param('i'), buf, sizeof(buf));
CCsv csv(ROWS, COLS);
Export(csv, buf, sizeof(buf));
csv.Save(cli.Param('o'));
}
void CmdImport(const CCli &cli)
{
RejectOptions(cli, "fptr");
if (!cli.Avail('i'))
Throw(_("CSV file to import from was not specified"));
if (!cli.Avail('o'))
Throw(_("Binary file to import to was not specified"));
CCsv csv(ROWS, COLS);
csv.Load(cli.Param('i'));
char buf[MEMORY_SIZE];
LoadBinFile(cli.Param('o'), buf, sizeof(buf));
Import(buf, sizeof(buf), csv);
CFileWrite f;
f.Open(cli.Param('o').c_str());
f.Write(buf, sizeof(buf));
f.Close();
}
/* $Id$ */
#pragma once
#include "cli.h"
void CmdUnknown(const CCli &cli);
void CmdCheck(const CCli &cli);
void CmdGet(const CCli &cli);
void CmdPut(const CCli &cli);
void CmdImport(const CCli &cli);
void CmdExport(const CCli &cli);
/* $Id$ */
#include <cstring>
#include <cassert>
#include <string>
#include <cerrno>
#include <termios.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/select.h>
#include <fcntl.h>
#include "throw.h"
#include "intl.h"
#include "comm.h"
CComm::CComm(): m_fd(-1), m_timeout(0)
{
}
void CComm::Open(const std::string &dev, unsigned timeout)
{
m_fd = open(dev.c_str(), O_RDWR | O_NOCTTY | O_SYNC);
if (m_fd == -1)
Throw(_("Cannot open dev %s: %s"), dev.c_str(), strerror(errno));
struct termios t;
if (tcgetattr(m_fd, &t) == -1)
Throw(_("Cannot get attributes: %s"), strerror(errno));
t.c_iflag = 0;
t.c_oflag = 0;
t.c_lflag &= ~(ECHO | ECHONL | ICANON | IEXTEN | ISIG);
t.c_cflag &= ~(CSIZE | PARENB);
t.c_cflag |= CS8;
t.c_cc[VMIN] = 1;
t.c_cc[VTIME] = 0;
if (cfsetispeed(&t, B9600) == -1 || cfsetospeed(&t, B9600) == -1)
Throw(_("Cannot set speed: %s"), strerror(errno));
if (tcsetattr(m_fd, TCSAFLUSH, &t) == -1)
Throw(_("Cannot set attributes: %s"), strerror(errno));
m_timeout = timeout;
}
CComm::~CComm()
{
if (m_fd != -1)
{
tcdrain(m_fd);
close(m_fd);
}
}
void CComm::Send(const void *data, size_t len)
{
const char *p((const char *) data);
size_t rem(len);
while (rem)
{
ssize_t rs(write(m_fd, p, rem));
if (rs == -1)
{
if (errno == EAGAIN || errno == EINTR)
continue;
Throw(_("Cannot write to device: %s"), strerror(errno));
}
if (!rs || (size_t) rs > rem)
Throw(_("Internal inconsistency (%u %u)"), (size_t) rs, rem);
p += (size_t) rs;
rem -= (size_t) rs;
}
// if (tcdrain(m_fd) == -1)
// Throw(_("Cannot drain device: %s"), strerror(errno));
// we don't check drain return value because on cygwin
// it causes problems sometimes.
tcdrain(m_fd);
}
void CComm::Recv(void *data, size_t len)
{
char *p((char *) data);
size_t rem(len);
while (rem)
{
if (m_timeout)
{
fd_set rfd;
FD_ZERO(&rfd);
FD_SET(m_fd, &rfd);
struct timeval tv;
tv.tv_sec = m_timeout;
tv.tv_usec = 0;
int selrs(select(m_fd + 1, &rfd, 0, 0, &tv));
if (selrs == -1)
{
if (errno == EAGAIN || errno == EINTR)
continue;
Throw(_("Cannot select on device: %s"), strerror(errno));
}
if (selrs == 0)
Throw(_("Radio not responding"));
}
ssize_t rs(read(m_fd, p, rem));
if (!rs)
Throw(_("Cannot read from device: EOF"));
if (rs == -1)
{
if (errno == EAGAIN || errno == EINTR)
continue;
Throw(_("Cannot read from device: %s"), strerror(errno));
}
if ((size_t) rs > rem)
Throw(_("Internal inconsistency (%u %u)"), (size_t) rs, rem);
p += (size_t) rs;
rem -= (size_t) rs;
}
}
void CComm::SendChar(const char ch)
{
Send(&ch, sizeof(ch));
}
char CComm::RecvChar()
{
char ch;
Recv(&ch, sizeof(ch));
return ch;
}
/* $Id$ */
#pragma once
// 9600 8n1 is fixed
#include <sys/types.h>
class CComm
{
private:
int m_fd;
unsigned m_timeout;
public:
CComm();
~CComm();
void Open(const std::string &dev, unsigned timeout);
void Send(const void *data, size_t len);
void Recv(void *data, size_t len);
void SendChar(const char ch);
char RecvChar();
};
/* $Id$ */
#include <assert.h>
#include "throw.h"
#include "intl.h"
#include "util.h"
#include "file.h"
#include "csv.h"
#define MAX_SIZE 65536
CCsv::CCsv(unsigned rows, unsigned cols): m_rows(rows), m_cols(cols)
{
m_data.resize(rows * cols);
}
size_t CCsv::Index(unsigned row, unsigned col) const
{
assert(row < m_rows);
assert(col < m_cols);
return row * m_cols + col;
}
const std::string &CCsv::Read(unsigned row, unsigned col) const
{
return m_data[Index(row, col)];
}
void CCsv::Write(unsigned row, unsigned col, const std::string &s)
{
m_data[Index(row, col)] = s;
}
void CCsv::Load(const std::string &path)
{
CFileRead f;
f.Open(path.c_str());
size_t sz(f.Size());
if (sz > MAX_SIZE)
Throw(_("CSV file too large"));
char buf[sz];
if (f.Read(buf, sz) != sz)
Throw(_("Cannot read CSV file"));
f.Close();
Explode(std::string(buf, sz));
}
void CCsv::Save(const std::string &path)
{
std::string s(Implode());
CFileWrite f;
f.Open(path.c_str());
f.Write(s.data(), s.size());
f.Close();
}
void CCsv::Explode(std::string s)
{
m_data.clear();
m_data.resize(m_rows * m_cols);
for (unsigned row(0); row < m_rows; ++row)
{
if (s.empty())
Throw(_("CSV file too short, only %u rows read"), row);
std::string line(Util::NextToken(s, '\n'));
if (line.size() >= 1 && line[line.size() - 1] == '\r')
line.erase(line.size() - 1);
for (unsigned col(0); col < m_cols; ++col)
{
// simplified parsing because a comma cannot occur anyway
std::string token(Util::NextToken(line, ','));
if (token.size() >= 2 && token[0] == '"' && token[token.size() - 1] == '"')
token = token.substr(1, token.size() - 2);
// xxx desanitize token - replace "" with "
Write(row, col, token);
}
}
}
std::string CCsv::Implode()
{
std::string rs;
for (unsigned row(0); row < m_rows; ++row)
{
for (unsigned col(0); col < m_cols; ++col)
{
if (col)
rs += std::string(1, ',');