mirror of
http://git.chmurka.net/owx
synced 2025-10-29 19:33:53 +00:00
initial commit
git-svn-id: http://svn.chmurka.net/owx/trunk/owx@1 8cc89244-2450-4880-90d3-191243f3a0b0
This commit is contained in:
commit
7d29d1f86a
45
ChangeLog
Normal file
45
ChangeLog
Normal file
@ -0,0 +1,45 @@
|
||||
/* $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
|
||||
13
Makefile
Normal file
13
Makefile
Normal file
@ -0,0 +1,13 @@
|
||||
# $Id$
|
||||
|
||||
.PHONY: all
|
||||
all:
|
||||
cd src && $(MAKE) all
|
||||
|
||||
.PHONY: install
|
||||
install:
|
||||
cd src && $(MAKE) install
|
||||
|
||||
.PHONY: clean
|
||||
clean:
|
||||
cd src && $(MAKE) clean
|
||||
202
docs/LICENSE
Normal file
202
docs/LICENSE
Normal file
@ -0,0 +1,202 @@
|
||||
|
||||
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.
|
||||
35
docs/PROTOCOL
Normal file
35
docs/PROTOCOL
Normal file
@ -0,0 +1,35 @@
|
||||
/* $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
|
||||
|
||||
309
docs/README
Normal file
309
docs/README
Normal file
@ -0,0 +1,309 @@
|
||||
/* $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 <cmd>: 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 <port>: specify port
|
||||
-t <timeout>: 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 <path>: binary file to write to
|
||||
|
||||
5.3. owx-put
|
||||
|
||||
This program uploads memory map from binary file to radio.
|
||||
|
||||
Options:
|
||||
|
||||
-i <path>: binary file to read from
|
||||
-r <path>: 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 <path>: binary file to read from
|
||||
-o <path>: 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 <path>: csv file to read from
|
||||
-o <path>: 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.
|
||||
14
docs/TODO
Normal file
14
docs/TODO
Normal file
@ -0,0 +1,14 @@
|
||||
/* $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
|
||||
51
src/Makefile
Normal file
51
src/Makefile
Normal file
@ -0,0 +1,51 @@
|
||||
# $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
|
||||
50
src/cli.cc
Normal file
50
src/cli.cc
Normal file
@ -0,0 +1,50 @@
|
||||
/* $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];
|
||||
}
|
||||
23
src/cli.h
Normal file
23
src/cli.h
Normal file
@ -0,0 +1,23 @@
|
||||
/* $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;
|
||||
};
|
||||
213
src/cmds.cc
Normal file
213
src/cmds.cc
Normal file
@ -0,0 +1,213 @@
|
||||
/* $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();
|
||||
}
|
||||
12
src/cmds.h
Normal file
12
src/cmds.h
Normal file
@ -0,0 +1,12 @@
|
||||
/* $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);
|
||||
153
src/comm.cc
Normal file
153
src/comm.cc
Normal file
@ -0,0 +1,153 @@
|
||||
/* $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;
|
||||
}
|
||||
26
src/comm.h
Normal file
26
src/comm.h
Normal file
@ -0,0 +1,26 @@
|
||||
/* $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();
|
||||
};
|
||||
113
src/csv.cc
Normal file
113
src/csv.cc
Normal file
@ -0,0 +1,113 @@
|
||||
/* $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, ',');
|
||||
|
||||
rs += std::string(1, '"');
|
||||
rs += Util::Sanitize(Read(row, col));
|
||||
rs += std::string(1, '"');
|
||||
}
|
||||
|
||||
rs += "\r\n";
|
||||
}
|
||||
|
||||
return rs;
|
||||
}
|
||||
27
src/csv.h
Normal file
27
src/csv.h
Normal file
@ -0,0 +1,27 @@
|
||||
/* $Id$ */
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <vector>
|
||||
#include <string>
|
||||
|
||||
class CCsv
|
||||
{
|
||||
private:
|
||||
unsigned m_rows, m_cols;
|
||||
std::vector<std::string> 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);
|
||||
};
|
||||
213
src/export.cc
Normal file
213
src/export.cc
Normal file
@ -0,0 +1,213 @@
|
||||
/* $Id$ */
|
||||
|
||||
#include <cassert>
|
||||
#include <cstring>
|
||||
#include <cstdio>
|
||||
#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<const unsigned char *>(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));
|
||||
}
|
||||
93
src/file.cc
Normal file
93
src/file.cc
Normal file
@ -0,0 +1,93 @@
|
||||
/* $Id$ */
|
||||
|
||||
#include <cstring>
|
||||
#include <cerrno>
|
||||
#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));
|
||||
}
|
||||
42
src/file.h
Normal file
42
src/file.h
Normal file
@ -0,0 +1,42 @@
|
||||
/* $Id$ */
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include <cstdio>
|
||||
|
||||
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);
|
||||
};
|
||||
11
src/impexp.h
Normal file
11
src/impexp.h
Normal file
@ -0,0 +1,11 @@
|
||||
/* $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);
|
||||
267
src/import.cc
Normal file
267
src/import.cc
Normal file
@ -0,0 +1,267 @@
|
||||
/* $Id$ */
|
||||
|
||||
#include <cassert>
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
#include <cstdio>
|
||||
#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<unsigned char *>(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));
|
||||
}
|
||||
9
src/intl.h
Normal file
9
src/intl.h
Normal file
@ -0,0 +1,9 @@
|
||||
/* $Id$ */
|
||||
|
||||
// placeholder for i18n
|
||||
// all printed strings use this macro
|
||||
// it can be expanded into gettext or whatever
|
||||
|
||||
#pragma once
|
||||
|
||||
#define _(x) (x)
|
||||
103
src/owx.cc
Normal file
103
src/owx.cc
Normal file
@ -0,0 +1,103 @@
|
||||
/* $Id$ */
|
||||
|
||||
#include <stdexcept>
|
||||
#include <cstring>
|
||||
#include <cstdlib>
|
||||
#include <cerrno>
|
||||
#include <cstdio>
|
||||
#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 <command>\n"
|
||||
"Options for owx-check, owx-get and owx-put: -f, -p <port>, -t <timeout>\n"
|
||||
"Options for owx-check: none\n"
|
||||
"Options for owx-get: -o <path>\n"
|
||||
"Options for owx-put: -i <path>, -r <path>\n"
|
||||
"Options for owx-export: -i <bin path>, -o <csv path>\n"
|
||||
"Options for owx-import: -i <csv path>, -o <bin path>\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;
|
||||
}
|
||||
14
src/owxendian.h
Normal file
14
src/owxendian.h
Normal file
@ -0,0 +1,14 @@
|
||||
/* $Id$ */
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <sys/types.h>
|
||||
#include <sys/param.h>
|
||||
|
||||
#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
|
||||
28
src/throw.cc
Normal file
28
src/throw.cc
Normal file
@ -0,0 +1,28 @@
|
||||
/* $Id$ */
|
||||
|
||||
#ifndef _GNU_SOURCE
|
||||
# define _GNU_SOURCE
|
||||
#endif
|
||||
|
||||
#include <stdexcept>
|
||||
#include <cstdarg>
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
#include <cstdio>
|
||||
#include <cerrno>
|
||||
#include "throw.h"
|
||||
|
||||
void __attribute__((noreturn)) Throw(const char *fmt, ...)
|
||||
{
|
||||
char *p;
|
||||
va_list ap;
|
||||
|
||||
va_start(ap, fmt);
|
||||
vasprintf(&p, fmt, ap);
|
||||
va_end(ap);
|
||||
|
||||
std::string s(p);
|
||||
free(p);
|
||||
|
||||
throw std::logic_error(s);
|
||||
}
|
||||
5
src/throw.h
Normal file
5
src/throw.h
Normal file
@ -0,0 +1,5 @@
|
||||
/* $Id$ */
|
||||
|
||||
#pragma once
|
||||
|
||||
void __attribute__((noreturn)) Throw(const char *fmt, ...);
|
||||
78
src/util.cc
Normal file
78
src/util.cc
Normal file
@ -0,0 +1,78 @@
|
||||
/* $Id$ */
|
||||
|
||||
#include <cstdio>
|
||||
#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;
|
||||
}
|
||||
15
src/util.h
Normal file
15
src/util.h
Normal file
@ -0,0 +1,15 @@
|
||||
/* $Id$ */
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
|
||||
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);
|
||||
};
|
||||
6
src/version.h
Normal file
6
src/version.h
Normal file
@ -0,0 +1,6 @@
|
||||
/* $Id$ */
|
||||
|
||||
#pragma once
|
||||
|
||||
#define VERSION "SVN"
|
||||
#define BUILD __DATE__ " " __TIME__
|
||||
107
src/wouxun.cc
Normal file
107
src/wouxun.cc
Normal file
@ -0,0 +1,107 @@
|
||||
/* $Id$ */
|
||||
|
||||
#include <cstring>
|
||||
#include <cstdio>
|
||||
#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);
|
||||
}
|
||||
30
src/wouxun.h
Normal file
30
src/wouxun.h
Normal file
@ -0,0 +1,30 @@
|
||||
/* $Id$ */
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#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
|
||||
};
|
||||
Loading…
x
Reference in New Issue
Block a user