pax_global_header00006660000000000000000000000064115520527100014510gustar00rootroot0000000000000052 comment=b06dc15cb072ac820ad20954bcb98ac5652e63c9 owx-0~20110415/000077500000000000000000000000001155205271000130425ustar00rootroot00000000000000owx-0~20110415/ChangeLog000066400000000000000000000024731155205271000146220ustar00rootroot00000000000000/* $Id$ */ 2011-04-15 - fixed hang when using pl2303 USB-to-serial converter (thx Ed Nisley) 2011-04-13 - fixed warning in throw.cc (thx Ed Nisley) 2011-03-14 - finally added owx to svn - set rcsid property on owx files in svn - created file FILES in docs 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 owx-0~20110415/Makefile000066400000000000000000000002241155205271000145000ustar00rootroot00000000000000# $Id$ .PHONY: all all: cd src && $(MAKE) all .PHONY: install install: cd src && $(MAKE) install .PHONY: clean clean: cd src && $(MAKE) clean owx-0~20110415/docs/000077500000000000000000000000001155205271000137725ustar00rootroot00000000000000owx-0~20110415/docs/FILES000066400000000000000000000010531155205271000145560ustar00rootroot00000000000000/* $Id$ */ cli.h Command-line handling. cmds.h Commands (check, get, put etc.). comm.h Serial communication. csv.h CSV file handling. file.h Object-oriented representation of c-style FILE. impexp.h Import and export prototypes. import.cc Functions related to importing. export.cc Functions related to exporting. intl.h i18n placeholder. owxendian.h Portable endianness detection. throw.h Exception throwing function, used in every other file. util.h Various helper functions. version.h owx version name. wouxun.h Wouxun radio communication. owx-0~20110415/docs/LICENSE000066400000000000000000000261361155205271000150070ustar00rootroot00000000000000 Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. Copyright [yyyy] [name of copyright owner] Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. owx-0~20110415/docs/PROTOCOL000066400000000000000000000024071155205271000151610ustar00rootroot00000000000000/* $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 owx-0~20110415/docs/README000066400000000000000000000240521155205271000146550ustar00rootroot00000000000000/* $Id$ */ 1. About this program 2. Authors, webpage, credits, license 3. Interface 4. Compilation and installation 5. Usage 5.1. owx-check 5.2. owx-get 5.3. owx-put 5.4. owx-export 5.5. owx-import 6. Spreadsheet format 6.1. Channel table 6.1.1. CH 6.1.2. Name 6.1.3. RX & TX Frequency 6.1.4. RX & TX CTCSS 6.1.5. Deviation 6.1.6. TX Power 6.1.7. Scan 6.1.8. BCL 6.2. Frequency ranges 6.3. FM radio 6.4. UVD3 welcome message 1. About this program OWX (Open Wouxun) is an open-source program designed to program Wouxun transceivers. It was developed on Wouxun KG-UV2D and tested on KG-UVD1P (both identify as KG669V). Possibly other Wouxuns are supported too, but this is not guaranteed - use at your own risk and ALWAYS make backups! This software is highly experimental. Using it can result in rendering your radio unusable and your dog killed. You have been warned. Utility was developed on GNU/Linux with gcc 4.3.2, but ports to other systems can (and should!) be made. It was tested only on i386, but is written with endianness in mind and should run on BE machines as well (but it was never tested and some bugs are possible). It communicates only in english, but is prepared to handle i18n if someone wants to. Please see intl.h file for details. Utility has five functions. They are used to: - check radio connection - download binary data from radio - upload binary data to radio - export human-readable spreadsheet from binary data file - import edited spreadsheet into existing binary data file Binary data contains everything that can be changed in the radio - all settings, channels, current modes of operation etc. Please also see the TODO file to see what this software can NOT do! 2. Authors, webpage, credits, license Program was written by SP5GOF (Adam Wysocki, gof (at) chmurka.net). Newest version can be downloaded from http://owx.chmurka.net/ Reverse-engineering, protocol information and initial testing were done by SQ5LWN (Lukasz Mozer, baseciq (at) baseciq.org). You can also try to reach us on SR5WW repeater and 145.525 MHz near KO02mf (Warsaw, Poland). Program is licensed under beer-ware license. We're HAMs and not lawyers, so no law hell applies here (nobody really reads licenses anyways), just pure HAM spirit. You are free to do with this program whatever you like, just be nice and don't remove the original authorship - that would be a bit shitty. If you feel that this code is worth this, you can just shout us some beer. If you don't like the beer-ware license, like, if you prefer fruity drinks or something, you can use the Apache 2.0 license instead. http://www.apache.org/licenses/LICENSE-2.0 Regardless of this, one thing must be said: we cannot be made responsible for breaking your radio with this program. Everything you do, you do at your own risk and by using this program you agree not to fill any lawsuit against us if your rig explodes right in front of your face. 3. Interface To connect your rig to the PC, you will need any standard RS232/TTL converter with MAX232 or similar. Schematics etc. can be found on Google. You can also adapt some old cellular phone cable or buy a dedicated cable on your favourite bidding service. Connections: 2.5mm (speaker): - shield: gnd - ring: radio tx (out) - tip: not connected 3.5mm (microphone): - shield: radio rx (in) - ring: not connected - tip: not connected 4. Compilation and installation Standard GNU make command will do the stuff. Program was written in C++ with heavy usage of STL, so you will need g++ with standard libraries, and of course GNU make. After compilation use "make install" to install the program in /usr/local/. 5. Usage Before you begin, it may be convenient to set OWX_PORT environmental variable in ~/.bashrc or similar shell rc file. With this variable you can specify default port instead of using -p. You can also set OWX_TIMEOUT to use alternate default when -t is not specified. After installation you will find five programs in /usr/local/bin: owx-check owx-get owx-put owx-export owx-import They are all symlinks to one executable - /usr/local/libexec/owx. All programs return 0 when everything goes okay and 1 when error occurs. Programs accept these common options: -c : override command (check, get, put, export, import) -h: fast help -v: version string You will almost never use -c command - if it's used, you can use one command (owx-get) to perform another task. It is mandatory only when you run main binary and not the symlinks. Programs that communicate with the radio (owx-check, owx-get and owx-put) accept these options: -f: force even if radio is not recognized -p : specify port -t : receive timeout in seconds -f can force the operation if your radio identifies different from KG669V. Use this option with extreme caution - it is very possible that your radio will be rendered unusable after you use this. It was NEVER tested with any radio different from mentoined above. -p specifies path to the tty device, i.e. /dev/ttyS0 or /dev/ttyUSB0. Of course you must have appropriate read and write permissions for this device. -t specifies receive timeout for communication with radio. If you disable it (by setting to 0) and the communication fails, the program will hang forever. You probably don't need to change the default value (5 seconds). 5.1. owx-check This program just checks for the connection and identification string. It can be used to check that your cable and port works. 5.2. owx-get This program downloads memory map from radio to binary file. Options: -o : binary file to write to 5.3. owx-put This program uploads memory map from binary file to radio. Options: -i : binary file to read from -r : reference file Option -r is not mandatory, but recommended. You can specify original, unchanged file (exactly as downloaded using owx-get) and this will speed up memory uploading, as owx will compare input file to this reference file and upload only changed memory pages. When using this option, be sure that nothing has changed in the radio (even the currently selected memory channel) between downloading reference file and using it for upload. This is important as some variables that cross the page boundaries (if there are any in the memory map) could be corrupted by this. Example: owx-get -o file.bin cp file.bin backup.bin owx-export -i file.bin -o wouxun.csv oocalc wouxun.csv owx-import -i wouxun.csv -o file.bin owx-put -i file.bin -r backup.bin Please do yourself a favour and double-check that you upload the correct file. If you try to upload incorrect or corrupted file, your radio will power down and fail to power up. owx will refuse to upload any file with incorrect size, but this is the only safety check. 5.4. owx-export This program exports channel data from binary file to CSV file. This file can be later edited using your favourite spreadsheet editor or even text editor. Options: -i : binary file to read from -o : csv file to write to 5.5. owx-import This program reads the specified, possibly edited by you CSV file, and patches existing binary file with this updated data. The file is now prepared to be uploaded with owx-put. Options: -i : csv file to read from -o : binary file to write to (must already exist) 6. Spreadsheet format When importing to a spreadsheet editor, import all fields as text (to make sure that your editor will not try to interpret anything). Then you will see the channel table and below it, frequency ranges. 6.1. Channel table Channel table fields are as follows: 6.1.1. CH This is the channel number. Do not change. 6.1.2. Name Channel name. Max 6 characters, only letters and digits. Lowercase letters will be converted to uppercase. 6.1.3. RX & TX Frequency Frequencies in format XXX.YYYYY (for example "145.52500"). They must strictly follow this format (another reason to import fields as text). 6.1.4. RX & TX CTCSS PL subtone. Format is XX.Y or XXX.Y (for example: "127.3"). 6.1.5. Deviation Can be "narrow" or "wide", for narrow-band FM and wide-band FM. 6.1.6. TX Power Can be "low" for 1W and "high" for 5W (on VHF) or 4W (on UHF). 6.1.7. Scan Can be "yes" to include this channel in scanning and "no" to skip. 6.1.8. BCL Can be "yes" to enable BCL (Busy Channel Lockout) on this channel and "no" otherwise. BCL prevents you from TX-ing when the channel is busy (squelch opened). 6.2. Frequency ranges There are eight values below the channel table. They correspond to the RX and TX ranges on both bands. By editing them, you can unlock your radio (change it's transmit and receive range), but it's easy to kill your radio by setting invalid values. If the ranges are invalid, the rig will refuse to turn on, probably due to busy-waiting for the PLL loop to lock. YOU WILL NOT BE ABLE TO REPROGRAM MEMORY USING ORDINARY CABLE. We've killed one radio this way and in order to bring it back to life I had to open it, disable the MCU (I just disabled it's clock), connect to the memory I2C bus and manually reprogram it. If you lack appropriate equipment or skills you definitely don't want to do this. OWX performs some basic checks on the ranges when importing and warns you if any value seems to be bogus, but it doesn't check the ranges (if you mess with their order). Also, bugs in the importing routine are possible. If you see any warning during importing, consider contacting me before putting the resulting file to the radio, as it is easy to brick the radio and not so easy to bring it back to life. If instead of frequencies you see some hexadecimal values (0xABCD or so) then the decoder (used when exporting) found unexpected pattern and refused to decode the value. Please contact me and provide these raw values. It should be safe to program them back to the radio, but don't change them (they're encoded). 6.3. FM radio Below frequency ranges there are nine FM broadcast radio channels. You can use syntax XX.Y or XXX.Y (example: "101.5") to program broadcast FM radio. 6.4. UVD3 welcome message KG-UVD3 allows you to edit welcome message - max 6 ascii characters. In UVD1 and UVD2 this memory area is ignored. owx-0~20110415/docs/TODO000066400000000000000000000010531155205271000144610ustar00rootroot00000000000000/* $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 owx-0~20110415/src/000077500000000000000000000000001155205271000136315ustar00rootroot00000000000000owx-0~20110415/src/Makefile000066400000000000000000000017761155205271000153040ustar00rootroot00000000000000# $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 owx-0~20110415/src/cli.cc000066400000000000000000000014431155205271000147110ustar00rootroot00000000000000/* $Id$ */ #include #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]; } owx-0~20110415/src/cli.h000066400000000000000000000006141155205271000145520ustar00rootroot00000000000000/* $Id$ */ #pragma once #include #include #include class CCli { private: std::map m_data; std::vector 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; }; owx-0~20110415/src/cmds.cc000066400000000000000000000100731155205271000150670ustar00rootroot00000000000000/* $Id$ */ #include #include #include #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(); } owx-0~20110415/src/cmds.h000066400000000000000000000003541155205271000147320ustar00rootroot00000000000000/* $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); owx-0~20110415/src/comm.cc000066400000000000000000000053471155205271000151040ustar00rootroot00000000000000/* $Id$ */ #include #include #include #include #include #include #include #include #include #include #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); 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; } owx-0~20110415/src/comm.h000066400000000000000000000005301155205271000147330ustar00rootroot00000000000000/* $Id$ */ #pragma once // 9600 8n1 is fixed #include 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(); }; owx-0~20110415/src/csv.cc000066400000000000000000000040341155205271000147340ustar00rootroot00000000000000/* $Id$ */ #include #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, ','); rs += std::string(1, '"'); rs += Util::Sanitize(Read(row, col)); rs += std::string(1, '"'); } rs += "\r\n"; } return rs; } owx-0~20110415/src/csv.h000066400000000000000000000007621155205271000146020ustar00rootroot00000000000000/* $Id$ */ #pragma once #include #include class CCsv { private: unsigned m_rows, m_cols; std::vector m_data; size_t Index(unsigned row, unsigned col) const; void Explode(std::string s); std::string Implode(); public: CCsv(unsigned rows, unsigned cols); void Load(const std::string &path); void Save(const std::string &path); const std::string &Read(unsigned row, unsigned col) const; void Write(unsigned row, unsigned col, const std::string &s); }; owx-0~20110415/src/export.cc000066400000000000000000000115671155205271000154730ustar00rootroot00000000000000/* $Id$ */ #include #include #include #include "owxendian.h" #include "impexp.h" #include "throw.h" #include "util.h" #include "intl.h" static std::string ExportName(const unsigned char *data) { std::string rs; for (size_t i(0); i < 6; ++i) { if (data[i] > ('Z' - 'A' + 0x0A)) break; rs += std::string(1, data[i] + ((data[i] < 0x0A) ? '0' : ('A' - 0x0A))); } return rs; } static std::string ExportFreq(const unsigned char *data) { if (data[0] == 0xFF && data[1] == 0xFF && data[2] == 0xFF && data[3] == 0xFF) return ""; char s[9]; snprintf(s, sizeof(s), "%02X%02X%02X%02X", data[3], data[2], data[1], data[0]); std::string rs(s); return rs.substr(0, 3) + "." + rs.substr(3); } static std::string ExportCTCSS(const unsigned char *data) { if (data[0] == 0xFF && data[1] == 0xFF) return ""; #ifdef OWX_LITTLE_ENDIAN unsigned short value((data[1] << 8) | data[0]); #else unsigned short value((data[0] << 8) | data[1]); #endif char buf[8]; snprintf(buf, sizeof(buf), "%u.%u", value / 10, value % 10); return buf; } static std::string ExportDeviation(const unsigned char *data) { return (*data & 0x10) ? "wide" : "narrow"; } static std::string ExportTXP(const unsigned char *data) { return (*data & 0x20) ? "high" : "low"; } static std::string ExportScan(const unsigned char *data) { return (*data & 0x40) ? "yes" : "no"; } static std::string ExportBCL(const unsigned char *data) { if (*data == 0x00 || *data == 0xF7) return "no"; if (*data == 0x08 || *data == 0xFF) return "yes"; char buf[5]; snprintf(buf, sizeof(buf), "0x%02X", *data); return buf; } static void ExportOne(CCsv &csv, unsigned row, const unsigned char *data) { csv.Write(row, 0, Util::IntToStr(row)); csv.Write(row, 1, ExportName(data + 0x1000)); csv.Write(row, 2, ExportFreq(data + 0x00)); csv.Write(row, 3, ExportFreq(data + 0x04)); csv.Write(row, 4, ExportCTCSS(data + 0x08)); csv.Write(row, 5, ExportCTCSS(data + 0x0A)); csv.Write(row, 6, ExportDeviation(data + 0x0D)); csv.Write(row, 7, ExportTXP(data + 0x0D)); csv.Write(row, 8, ExportScan(data + 0x0D)); csv.Write(row, 9, ExportBCL(data + 0x0C)); } static std::string ExportRangeEntry(const unsigned char *data) { unsigned char values[4]; static const unsigned char transtbl[] = { 2, 7, 5, 8, 10, 10, 10, 0, 10, 3, 1, 4, 10, 10, 6, 9 }; values[0] = data[0] >> 4; values[1] = data[0] & 0x0F; values[2] = data[1] >> 4; values[3] = data[1] & 0x0F; unsigned short value(0); unsigned short multiplier(1000); for (size_t i(0); i < 4; ++i) { unsigned char v(transtbl[values[i]]); if (v == 10) { // fallback char buf[7]; snprintf(buf, sizeof(buf), "0x%02X%02X", data[1], data[0]); return buf; } value += v * multiplier; multiplier /= 10; } char buf[5]; snprintf(buf, sizeof(buf), "%u", value); return buf; } static std::string ExportRadio(const unsigned char *data) { if (data[0] == 0xFF && data[1] == 0xFF) return ""; #ifdef OWX_LITTLE_ENDIAN unsigned short value((data[0] << 8) | data[1]); #else unsigned short value((data[1] << 8) | data[0]); #endif char buf[8]; snprintf(buf, sizeof(buf), "%u.%u", value / 10 + 76, value % 10); return buf; } static std::string ExportWelcome(const unsigned char *data) { std::string out; for (size_t i(0); i < 6; ++i) { if (data[i] == 0x00 || data[i] == 0xFF) break; if (data[i] < 0x20 || data[i] > 0x7F || data[i] == '"' || data[i] == '\\') { char buf[4]; snprintf(buf, sizeof(buf), "\\%02X", data[i]); out += buf; } else out += std::string(1, data[i]); } return out; } void Export(CCsv &csv, const char *buf, size_t bufsz) { assert(bufsz == 0x2000); const unsigned char *p(reinterpret_cast(buf)); csv.Write(0, 0, _("CH")); csv.Write(0, 1, _("Name")); csv.Write(0, 2, _("RX Frequency")); csv.Write(0, 3, _("TX Frequency")); csv.Write(0, 4, _("RX CTCSS")); csv.Write(0, 5, _("TX CTCSS")); csv.Write(0, 6, _("Deviation")); csv.Write(0, 7, _("TX Power")); csv.Write(0, 8, _("Scan")); csv.Write(0, 9, _("BCL")); size_t i; for (i = 1; i <= 128; ++i) ExportOne(csv, i, p + i * 0x10); csv.Write(130, 0, _("VHF RX Range")); csv.Write(131, 0, _("VHF TX Range")); csv.Write(132, 0, _("UHF RX Range")); csv.Write(133, 0, _("UHF TX Range")); csv.Write(130, 1, ExportRangeEntry(p + 0x970)); csv.Write(130, 2, ExportRangeEntry(p + 0x972)); csv.Write(131, 1, ExportRangeEntry(p + 0x978)); csv.Write(131, 2, ExportRangeEntry(p + 0x97A)); csv.Write(132, 1, ExportRangeEntry(p + 0x974)); csv.Write(132, 2, ExportRangeEntry(p + 0x976)); csv.Write(133, 1, ExportRangeEntry(p + 0x97C)); csv.Write(133, 2, ExportRangeEntry(p + 0x97E)); for (i = 1; i < 10; ++i) { char buf[256]; snprintf(buf, sizeof(buf), _("FM%u"), i); csv.Write(135 + i, 0, buf); csv.Write(135 + i, 1, ExportRadio(p + 0x840 + i * 2)); } csv.Write(146, 0, _("UVD3 Welcome Message")); csv.Write(146, 1, ExportWelcome(p + 0x1827)); } owx-0~20110415/src/file.cc000066400000000000000000000030061155205271000150560ustar00rootroot00000000000000/* $Id$ */ #include #include #include "throw.h" #include "intl.h" #include "file.h" CFileBase::CFileBase(): m_fp(NULL) { } CFileBase::~CFileBase() { } void CFileBase::Close() { if (!m_fp) return; if (fclose(m_fp) == -1) Throw(_("Cannot close %s: %s"), m_path.c_str(), strerror(errno)); m_fp = NULL; } CFileRead::~CFileRead() { Close(); } void CFileRead::Open(const char *path) { m_fp = fopen(path, "rb"); if (!m_fp) Throw(_("Cannot open %s: %s"), path, strerror(errno)); if (fseek(m_fp, 0L, SEEK_END) == -1) Throw(_("Cannot seek %s: %s"), path, strerror(errno)); long sz(ftell(m_fp)); if (sz == -1) Throw(_("Cannot read position of %s: %s"), path, strerror(errno)); if (fseek(m_fp, 0L, SEEK_SET) == -1) Throw(_("Cannot rewind %s: %s"), path, strerror(errno)); m_path = path; m_size = sz; } size_t CFileRead::Size() const { return m_size; } size_t CFileRead::Read(void *buf, size_t sz) { size_t rs(fread(buf, 1, sz, m_fp)); if (rs == sz) return rs; if (feof(m_fp)) return rs; Throw(_("Cannot read from %s: %s"), m_path.c_str(), strerror(errno)); } CFileWrite::~CFileWrite() { if (m_fp) { fclose(m_fp); unlink(m_path.c_str()); } } void CFileWrite::Open(const char *path) { m_fp = fopen(path, "wb"); if (!m_fp) Throw(_("Cannot open %s: %s"), path, strerror(errno)); m_path = path; } void CFileWrite::Write(const void *buf, size_t sz) { size_t rs(fwrite(buf, sz, 1, m_fp)); if (rs != 1) Throw(_("Cannot write to %s: %s"), m_path.c_str(), strerror(errno)); } owx-0~20110415/src/file.h000066400000000000000000000010641155205271000147220ustar00rootroot00000000000000/* $Id$ */ #pragma once #include #include class CFileBase { protected: std::string m_path; FILE *m_fp; public: CFileBase(); virtual ~CFileBase(); virtual void Open(const char *path) = 0; void Close(); }; class CFileRead: public CFileBase { private: size_t m_size; public: ~CFileRead(); virtual void Open(const char *path); size_t Size() const; size_t Read(void *buf, size_t sz); }; class CFileWrite: public CFileBase { public: ~CFileWrite(); virtual void Open(const char *path); void Write(const void *buf, size_t sz); }; owx-0~20110415/src/impexp.h000066400000000000000000000002741155205271000153070ustar00rootroot00000000000000/* $Id$ */ #pragma once #include "csv.h" #define ROWS 147 #define COLS 10 void Import(char *buf, size_t bufsz, const CCsv &csv); void Export(CCsv &csv, const char *buf, size_t bufsz); owx-0~20110415/src/import.cc000066400000000000000000000144101155205271000154520ustar00rootroot00000000000000/* $Id$ */ #include #include #include #include #include "owxendian.h" #include "impexp.h" #include "throw.h" #include "util.h" #include "intl.h" static void ImportName(unsigned char *data, const std::string s) { if (s.size() > 6) Throw(_("Channel name \"%s\" is too long (6 chars max)"), s.c_str()); memset(data, 0xFF, 6); for (size_t i(0); i < s.size(); ++i) { const char ch(s[i]); if (ch >= '0' && ch <= '9') data[i] = ch - '0'; else if (ch >= 'A' && ch <= 'Z') data[i] = ch - 'A' + 0x0A; else if (ch >= 'a' && ch <= 'z') data[i] = ch - 'a' + 0x0A; else Throw(_("Invalid character in channel name \"%s\", only letters and digits allowed"), s.c_str()); } } static void ImportFreq(unsigned char *data, const std::string s) { if (s.empty() || s == "0") { memset(data, 0xFF, 4); return; } if (s.size() != 9 || s[3] != '.') Throw(_("Frequency \"%s\" must have strict format XXX.XXXXX"), s.c_str()); std::string hexstr(s.substr(0, 3) + s.substr(4)); data[3] = Util::FromHex(hexstr.c_str() + 0); data[2] = Util::FromHex(hexstr.c_str() + 2); data[1] = Util::FromHex(hexstr.c_str() + 4); data[0] = Util::FromHex(hexstr.c_str() + 6); } static void ImportCTCSS(unsigned char *data, const std::string s) { if (s.empty() || s == "0") { memset(data, 0xFF, 2); return; } std::string ss(Util::StripDot(s)); unsigned v(strtol(ss.c_str(), NULL, 10)); #ifdef OWX_LITTLE_ENDIAN data[1] = v >> 8; data[0] = v & 0xFF; #else data[0] = v >> 8; data[1] = v & 0xFF; #endif } static void ImportDeviation(unsigned char *data, const std::string s) { if (s == "narrow") *data &= ~0x10; else if (s == "wide") *data |= 0x10; else Throw(_("Invalid deviation string \"%s\", must be \"narrow\" or \"wide\""), s.c_str()); } static void ImportTXP(unsigned char *data, const std::string s) { if (s == "low") *data &= ~0x20; else if (s == "high") *data |= 0x20; else Throw(_("Invalid TX power string \"%s\", must be \"low\" or \"high\""), s.c_str()); } static void ImportScan(unsigned char *data, const std::string s) { if (s == "no") *data &= ~0x40; else if (s == "yes") *data |= 0x40; else Throw(_("Invalid scan string \"%s\", must be \"no\" or \"yes\""), s.c_str()); } static void ImportBCL(unsigned char *data, const std::string s) { if (s == "no") *data = 0x00; else if (s == "yes") *data = 0x08; else if (s.size() == 4 && s.substr(0, 2) == "0x") { *data = Util::FromHex(s.substr(2)); return; } else Throw(_("Invalid BCL string \"%s\", must be \"no\" or \"yes\""), s.c_str()); // xxx - make sure if (data[1] & 0x07) *data ^= 0xF7; } static void ImportOne(unsigned row, unsigned char *data, const CCsv &csv) { if (atoi(csv.Read(row, 0).c_str()) != (int) row) Throw(_("Invalid channel number at row %u"), row); ImportName(data + 0x1000, csv.Read(row, 1)); ImportFreq(data + 0x00, csv.Read(row, 2)); ImportFreq(data + 0x04, csv.Read(row, 3)); ImportCTCSS(data + 0x08, csv.Read(row, 4)); ImportCTCSS(data + 0x0A, csv.Read(row, 5)); ImportDeviation(data + 0x0D, csv.Read(row, 6)); ImportTXP(data + 0x0D, csv.Read(row, 7)); ImportScan(data + 0x0D, csv.Read(row, 8)); ImportBCL(data + 0x0C, csv.Read(row, 9)); } static void ImportRangeEntry(unsigned char *data, const std::string s) { if (s.size() == 6 && s.substr(0, 2) == "0x") { data[1] = Util::FromHex(s.substr(2, 2)); data[0] = Util::FromHex(s.substr(4, 2)); return; } unsigned short value(strtol(s.c_str(), NULL, 10)); if (!(value >= 100 && value <= 200) && !(value >= 400 && value <= 600)) { fprintf(stderr, _("Warning: Value %u seems to be bogus or out of range.\n"), value); fprintf(stderr, _("Programming this binary file can render your radio unusable.\n")); } char buf[5]; snprintf(buf, sizeof(buf), "%04u", value); static const unsigned char transtbl[] = { 0x07, 0x0A, 0x00, 0x09, 0x0B, 0x02, 0x0E, 0x01, 0x03, 0x0F }; unsigned char values[4]; for (size_t i(0); i < 4; ++i) { if (buf[i] < '0' || buf[i] > '9') { fprintf(stderr, _("Internal frequency range conversion error, could not program the range.\n")); fprintf(stderr, _("Do NOT program this memory into the radio, it will render it unusable.\n")); fprintf(stderr, _("Please contact me at gof@chmurka.net and send me the range you've put\n")); fprintf(stderr, _("into the frequency range table row.\n")); Throw(_("Internal frequency range conversion error")); } values[i] = transtbl[buf[i] - '0']; } data[0] = (values[0] << 4) | values[1]; data[1] = (values[2] << 4) | values[3]; } static void ImportRadio(unsigned chan, unsigned char *data, const CCsv &csv) { unsigned row(135 + chan); char buf[256]; snprintf(buf, sizeof(buf), _("FM%u"), chan); if (csv.Read(row, 0) != buf) Throw(_("Invalid radio channel number at row %u"), row); std::string s(csv.Read(row, 1)); if (s.empty() || s == "0") { memset(data, 0xFF, 2); return; } std::string ss(Util::StripDot(s)); unsigned v(strtol(ss.c_str(), NULL, 10) - 760); #ifdef OWX_LITTLE_ENDIAN data[0] = v >> 8; data[1] = v & 0xFF; #else data[1] = v >> 8; data[0] = v & 0xFF; #endif } static void ImportWelcome(unsigned char *data, const std::string s) { memset(data, 0x20, 6); size_t idx(0); int state(0); unsigned char hi; for (std::string::const_iterator i(s.begin()); i != s.end(); ++i) { switch (state) { case 0: if (*i == '\\') state = 1; else data[idx++] = *i; break; case 1: hi = Util::FromHexOne(*i); state = 2; break; case 2: data[idx++] = (hi << 4) | Util::FromHexOne(*i); state = 0; break; } if (idx == 6) break; } } void Import(char *buf, size_t bufsz, const CCsv &csv) { assert(bufsz == 0x2000); unsigned char *p(reinterpret_cast(buf)); size_t i; for (i = 1; i <= 128; ++i) ImportOne(i, p + i * 0x10, csv); ImportRangeEntry(p + 0x970, csv.Read(130, 1)); ImportRangeEntry(p + 0x972, csv.Read(130, 2)); ImportRangeEntry(p + 0x978, csv.Read(131, 1)); ImportRangeEntry(p + 0x97A, csv.Read(131, 2)); ImportRangeEntry(p + 0x974, csv.Read(132, 1)); ImportRangeEntry(p + 0x976, csv.Read(132, 2)); ImportRangeEntry(p + 0x97C, csv.Read(133, 1)); ImportRangeEntry(p + 0x97E, csv.Read(133, 2)); for (i = 1; i < 10; ++i) ImportRadio(i, p + 0x840 + i * 2, csv); ImportWelcome(p + 0x1827, csv.Read(146, 1)); } owx-0~20110415/src/intl.h000066400000000000000000000002311155205271000147440ustar00rootroot00000000000000/* $Id$ */ // placeholder for i18n // all printed strings use this macro // it can be expanded into gettext or whatever #pragma once #define _(x) (x) owx-0~20110415/src/owx.cc000066400000000000000000000040161155205271000147560ustar00rootroot00000000000000/* $Id$ */ #include #include #include #include #include #include "version.h" #include "cmds.h" #include "throw.h" #include "intl.h" #include "cli.h" enum ECommand { CMD_UNKNOWN = 0, CMD_CHECK = 1, CMD_GET = 2, CMD_PUT = 3, CMD_IMPORT = 4, CMD_EXPORT = 5 }; static ECommand CmdFromCLI(const std::string &cmd) { if (cmd == "check") return CMD_CHECK; if (cmd == "get") return CMD_GET; if (cmd == "put") return CMD_PUT; if (cmd == "import") return CMD_IMPORT; if (cmd == "export") return CMD_EXPORT; return CMD_UNKNOWN; } static ECommand CmdFromProgname(const std::string av0) { // don't use program_invocation_short_name because of portability size_t pos(av0.rfind('/')); const std::string name((pos == av0.npos) ? av0 : av0.substr(pos + 1)); if (name.substr(0, 4) != "owx-") return CMD_UNKNOWN; return CmdFromCLI(name.substr(4)); } static void Main(int ac, char * const av[]) { CCli cli(ac, av, ":c:fhi:o:p:r:t:v"); if (cli.Avail('v')) { printf(_("Open Wouxun version %s, build %s\n"), VERSION, BUILD); printf(_("For details please see: http://owx.chmurka.net/\n")); return; } if (cli.Avail('h')) { static const char fasthelp[] = _( "Options for all programs: -h, -v, -c \n" "Options for owx-check, owx-get and owx-put: -f, -p , -t \n" "Options for owx-check: none\n" "Options for owx-get: -o \n" "Options for owx-put: -i , -r \n" "Options for owx-export: -i , -o \n" "Options for owx-import: -i , -o \n" ); printf("%s", fasthelp); return; } ECommand cmd(cli.Avail('c') ? CmdFromCLI(cli.Param('c')) : CmdFromProgname(av[0])); void (*fns[])(const CCli &cli) = { CmdUnknown, CmdCheck, CmdGet, CmdPut, CmdImport, CmdExport }; fns[cmd](cli); } int main(int ac, char * const av[]) { try { Main(ac, av); } catch (const std::logic_error &e) { fprintf(stderr, "owx: %s\n", e.what()); return EXIT_FAILURE; }; return EXIT_SUCCESS; } owx-0~20110415/src/owxendian.h000066400000000000000000000004231155205271000157750ustar00rootroot00000000000000/* $Id$ */ #pragma once #include #include #if __BYTE_ORDER == __LITTLE_ENDIAN #define OWX_LITTLE_ENDIAN #elif __BYTE_ORDER == __BIG_ENDIAN #undef OWX_LITTLE_ENDIAN #else #error Cannot determine your endianness, please contact author! #endif owx-0~20110415/src/throw.cc000066400000000000000000000010021155205271000152740ustar00rootroot00000000000000/* $Id$ */ #ifndef _GNU_SOURCE # define _GNU_SOURCE #endif #include #include #include #include #include #include #include "throw.h" #include "intl.h" void __attribute__((noreturn)) Throw(const char *fmt, ...) { char *p; va_list ap; va_start(ap, fmt); int rs(vasprintf(&p, fmt, ap)); va_end(ap); if (rs == -1) throw std::logic_error(_("Not enough memory to format error string")); std::string s(p); free(p); throw std::logic_error(s); } owx-0~20110415/src/throw.h000066400000000000000000000001261155205271000151440ustar00rootroot00000000000000/* $Id$ */ #pragma once void __attribute__((noreturn)) Throw(const char *fmt, ...); owx-0~20110415/src/util.cc000066400000000000000000000024661155205271000151250ustar00rootroot00000000000000/* $Id$ */ #include #include "util.h" #include "throw.h" #include "intl.h" std::string Util::Sanitize(const std::string s) { std::string out; for (std::string::const_iterator i(s.begin()); i != s.end(); ++i) { if (*i == '"') { out += std::string("\"\""); continue; } if (*i >= 0x20 && *i < 0x7F) { out += std::string(1, *i); continue; } char buf[5]; snprintf(buf, sizeof(buf), "\\x%02X", (unsigned char) *i); out += buf; } return out; } std::string Util::IntToStr(size_t i) { char buf[8]; snprintf(buf, sizeof(buf), "%u", i); return buf; } std::string Util::NextToken(std::string &s, const char sep) { size_t f(s.find(sep)); if (f == s.npos) f = s.size(); std::string rs(s.substr(0, f)); s.erase(0, f + 1); return rs; } unsigned char Util::FromHexOne(const char ch) { if (ch >= '0' && ch <= '9') return ch - '0'; if (ch >= 'A' && ch <= 'F') return ch - 'A' + 10; if (ch >= 'a' && ch <= 'f') return ch - 'a' + 10; Throw(_("Invalid hex char %02X"), ch); } unsigned char Util::FromHex(const std::string &s) { return (FromHexOne(s[0]) << 4) | FromHexOne(s[1]); } std::string Util::StripDot(const std::string &s) { std::string rs; for (std::string::const_iterator i(s.begin()); i != s.end(); ++i) if (*i != '.') rs += std::string(1, *i); return rs; } owx-0~20110415/src/util.h000066400000000000000000000005131155205271000147560ustar00rootroot00000000000000/* $Id$ */ #pragma once #include namespace Util { std::string Sanitize(const std::string s); std::string IntToStr(size_t i); std::string NextToken(std::string &s, const char sep); unsigned char FromHexOne(const char ch); unsigned char FromHex(const std::string &s); std::string StripDot(const std::string &s); }; owx-0~20110415/src/version.h000066400000000000000000000001241155205271000154640ustar00rootroot00000000000000/* $Id$ */ #pragma once #define VERSION "SVN" #define BUILD __DATE__ " " __TIME__ owx-0~20110415/src/wouxun.cc000066400000000000000000000034601155205271000155100ustar00rootroot00000000000000/* $Id$ */ #include #include #include "owxendian.h" #include "wouxun.h" #include "util.h" #include "throw.h" #include "intl.h" #define DEFAULT_TIMEOUT 5 CWouxun::CWouxun(): m_timeout(DEFAULT_TIMEOUT) { } void CWouxun::SetPort(const std::string &port) { m_port = port; } void CWouxun::SetTimeout(unsigned timeout) { m_timeout = timeout; } void CWouxun::Open() { m_comm.Open(m_port, m_timeout); const char s[] = "HiWOUXUN\002"; m_comm.Send(s, sizeof(s) - 1); RecvAck(true); m_comm.SendChar(0x02); RecvAck(); char buf[7]; m_comm.Recv(buf, sizeof(buf)); m_id = Util::Sanitize(std::string(buf, 6)); // xxx - what is in buf[6]? version? // m_version = buf[6]; m_comm.SendChar(0x06); RecvAck(); } const std::string &CWouxun::GetIDString() const { return m_id; } void CWouxun::PrepareAddress(char *buf, unsigned short addr) { #ifdef OWX_LITTLE_ENDIAN buf[0] = addr >> 8; buf[1] = addr & 0xFF; #else buf[0] = addr & 0xFF; buf[1] = addr >> 8; #endif } void CWouxun::GetPage(unsigned short addr, char *buf) { char txbuf[4] = { 'R', 0x00, 0x00, 0x40 }; char rxbuf[4]; PrepareAddress(txbuf + 1, addr); m_comm.Send(txbuf, sizeof(txbuf)); m_comm.Recv(rxbuf, sizeof(rxbuf)); txbuf[0] = 'W'; if (memcmp(txbuf, rxbuf, sizeof(txbuf)) != 0) Throw(_("Radio returned nonsense")); m_comm.Recv(buf, 0x40); m_comm.SendChar(0x06); RecvAck(); } void CWouxun::PutPage(unsigned short addr, const char *buf) { char txbuf[4] = { 'W', 0x00, 0x00, 0x10 }; PrepareAddress(txbuf + 1, addr); m_comm.Send(txbuf, sizeof(txbuf)); m_comm.Send(buf, 0x10); RecvAck(); } void CWouxun::RecvAck(bool checkecho) { unsigned char ch(m_comm.RecvChar()); if (ch == 0x06) return; if (checkecho && ch == 'H') Throw(_("RX-to-TX loopback detected")); Throw(_("Invalid ACK char received: 0x%02X"), ch); } owx-0~20110415/src/wouxun.h000066400000000000000000000011111155205271000153410ustar00rootroot00000000000000/* $Id$ */ #pragma once #include #include "comm.h" class CWouxun { private: std::string m_port; unsigned m_timeout; std::string m_id; CComm m_comm; void RecvAck(bool checkecho = false); void PrepareAddress(char *buf, unsigned short addr); public: CWouxun(); void SetPort(const std::string &port); // mandatory void SetTimeout(unsigned timeout); // not mandatory void Open(); const std::string &GetIDString() const; void GetPage(unsigned short addr, char *buf); // 0x40-byte pages void PutPage(unsigned short addr, const char *buf); // 0x10-byte pages };