DMR-чип опознан по схеме: HF6853 = AUCTUS A6 (RDA8809), не SCT3258
Опознание БЕЗ разбора рации, по схеме RT4DDLT01 (Radtel v2.1): DMR-модуль «FM100B» = один чип U700 HF6853 — одночиповый DMR-SoC семейства Auctus A6 (внутри RDA8809): CPU + ВЧ-трансивер + DSP + AMBE-вокодер + аудио-кодек в одном кристалле, прошивка во внешнем SPI-flash, кварц 26 МГц. Прежняя версия «Sicomm SCT3258TD» неверна (SCT3258 — baseband-only без ВЧ; и нативный протокол другой). Скрытый ATE/CPS-интерфейс полностью реверснут (jhart99/a6tools, вендорен в research/a6tools). Даёт КОНКРЕТНУЮ починку нашей TX-проблемы: AT+GETFREQERR / AT+DMOFREQERR=N, offset=-2500+10*N, ±2500 Гц — наши ~1100 Гц внутри диапазона. Плюс DMR_ADJTXSYMDEV (девиация), FGU_AFC, DMOSETPOWER, CPS chanInfo, дамп прошивки модуля. - docs/hf6853-auctus-a6.md — идентификация + протокол + план TX-fix - tools/a6_freqfix.py — заготовка тула (probe/read/write, не запускать вслепую) - research/a6tools/ — вендоренный реверс (MIT), atcommands.md/cpecommands.md - docs/dmr-tx-recovery.md, README — путь 0 (AT+DMOFREQERR) как лучший - research/rt4ddlt01-dmr-module-sheet.png — рендер листа схемы DMR-модуля Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NikMGoqQWWk9wy2ww2vJAr
Этот коммит содержится в:
@@ -0,0 +1,20 @@
|
||||
Copyright 2021 Jonathan Hart
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining
|
||||
a copy of this software and associated documentation files (the
|
||||
"Software"), to deal in the Software without restriction, including
|
||||
without limitation the rights to use, copy, modify, merge, publish,
|
||||
distribute, sublicense, and/or sell copies of the Software, and to
|
||||
permit persons to whom the Software is furnished to do so, subject to
|
||||
the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be
|
||||
included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
@@ -0,0 +1,35 @@
|
||||
## A6tools
|
||||
|
||||
A Python library and tools AUCTUS A6 based radios.
|
||||
|
||||
### Radios
|
||||
Tested and working with:
|
||||
* COTRE CO01D
|
||||
* COTRE CO04D
|
||||
* COTRE CO06D
|
||||
* GOCOM GD900
|
||||
|
||||
Probably also works with:
|
||||
* GOCOM GD100
|
||||
* GOCOM GD700
|
||||
* GOCOM GD800
|
||||
* CONNECOM radios as well
|
||||
|
||||
### Usage
|
||||
|
||||
#### atcommander
|
||||
|
||||
atcommander sends AT commands to the radio which can be used to change many of the parameters.
|
||||
|
||||
Usage:
|
||||
```
|
||||
$ python3 atcommander.py AT+DMOCONNECT
|
||||
OnCmd_DMOCONNECT
|
||||
ATE_SendCmdAck: cmd:DMOCONNECT isOk:0x1
|
||||
tx_length:17
|
||||
ATE_SendDataFrame:
|
||||
+DMOCONNECT:0
|
||||
|
||||
ATE pipe. used[HOST]
|
||||
```
|
||||
You must start with AT+DMOCONNECT prior to any other commands
|
||||
@@ -0,0 +1,26 @@
|
||||
from .escaper import escaper
|
||||
from .escaper import unescaper
|
||||
from .rdadebug import compute_check
|
||||
from .rdadebug import rda_debug_frame
|
||||
from .rdadebug import read_word
|
||||
from .rdadebug import write_register_int8
|
||||
from .rdadebug import read_register_int8
|
||||
from .rdadebug import write_block
|
||||
from .a6commands import h2p_command
|
||||
from .a6commands import set_uart_to_host
|
||||
from .a6commands import set_uart_to_normal
|
||||
from .a6commands import read_uart_to_host
|
||||
from .a6commands import ate_command
|
||||
from .a6commands import cps_command
|
||||
from .a6commands import reboot_and_freeze
|
||||
from .serialio import send_uart_setup
|
||||
from .serialio import fetch_memory_address
|
||||
from .serialio import send_ate_command
|
||||
from .serialio import send_cps_command
|
||||
from .serialio import atecps_resp_read
|
||||
from .serialio import read_mem_range
|
||||
from .serialio import get_chan_info
|
||||
from .serialio import get_freq_err
|
||||
from .serialio import parse_freq_err_resp
|
||||
from .serialio import set_freq_err
|
||||
from .serialio import SerialIO
|
||||
@@ -0,0 +1,168 @@
|
||||
from .rdadebug import write_register_int8
|
||||
from .rdadebug import read_register_int8
|
||||
from .rdadebug import write_block
|
||||
from .rdadebug import compute_check
|
||||
from .eprint import eprint
|
||||
|
||||
def h2p_command(msg):
|
||||
""" Format a frame for an h2p command
|
||||
|
||||
The CPS software sends commands to a special debug register
|
||||
00000005. Writing a value to this register throws an interupt
|
||||
which is picked up by a function on the device.
|
||||
|
||||
0x00 : Command finished, clears semaphore
|
||||
0xA5 : Process command with RxByHostPortCB
|
||||
0xEE : Reboot
|
||||
0xFF : Handle with boot_HstCmdBasicHandler
|
||||
|
||||
@param msg: the message to send
|
||||
@return: the frame to send
|
||||
|
||||
"""
|
||||
return write_register_int8(0x5, msg)
|
||||
|
||||
def set_uart_to_normal():
|
||||
""" Set device uart to host mode
|
||||
|
||||
The CPS software sends repeated requests to set internal register
|
||||
00000003 to 0x80 which has the effect of locking the UART to debug
|
||||
mode
|
||||
|
||||
@return: the frame to send
|
||||
|
||||
"""
|
||||
return write_register_int8(3, 0x00)
|
||||
|
||||
def set_uart_to_host():
|
||||
""" Set device uart to host mode
|
||||
|
||||
The CPS software sends repeated requests to set internal register
|
||||
00000003 to 0x80 which has the effect of locking the UART to debug
|
||||
mode
|
||||
|
||||
@return: the frame to send
|
||||
|
||||
"""
|
||||
return write_register_int8(3, 0x80)
|
||||
|
||||
def reboot_and_freeze():
|
||||
""" Reboot and freeze the processor
|
||||
|
||||
This command comes from coolwatcher and resets the processor and
|
||||
immediately halts it. This is useful for stepping through the
|
||||
boot process, but also allows some areas of ROM to be read without
|
||||
crashing
|
||||
|
||||
@return: the frame to send
|
||||
|
||||
"""
|
||||
return write_register_int8(0, 0x03)
|
||||
|
||||
def read_uart_to_host():
|
||||
""" make a frame containing a knock command
|
||||
|
||||
this function creates a frame that I assume wakes up the device
|
||||
for further commands.
|
||||
|
||||
@return: the frame to send
|
||||
|
||||
"""
|
||||
return read_register_int8(3)
|
||||
|
||||
def ate_command(cmd, p_atecps_write):
|
||||
""" make a frame containing an ATE command
|
||||
|
||||
@param cmd: the command to send
|
||||
@param p_atecps_write: the address of the CPS write register
|
||||
@return: the frame to send
|
||||
|
||||
"""
|
||||
cmd = bytearray(cmd, 'utf-8') + b'\r'
|
||||
cmd += bytes(4 - len(cmd) % 4)
|
||||
return write_block(p_atecps_write, cmd)
|
||||
|
||||
def cps_command(cmd, p_atecps_write):
|
||||
""" make a frame containing an CPS command
|
||||
|
||||
@param cmd: the command to send
|
||||
@param p_atecps_write: the address of the CPS write register
|
||||
@return: the frame to send
|
||||
|
||||
"""
|
||||
length = (len(cmd) + 4).to_bytes(1, 'big')
|
||||
check = compute_check(length + cmd)
|
||||
begin = bytes([0xaa])
|
||||
end = bytes([0xbb])
|
||||
msg = begin + length + cmd + check + end
|
||||
padding = 4 - (len(msg) % 4)
|
||||
return write_block(p_atecps_write, msg + bytes([0x00]) * padding)
|
||||
|
||||
class CPSFrame:
|
||||
""" Received CPS class
|
||||
|
||||
This class decodes CPS frames received from the device.
|
||||
"""
|
||||
check_fail = False
|
||||
length = 0
|
||||
type = 0
|
||||
content = bytes([])
|
||||
def __init__(self, msg):
|
||||
eprint(msg.hex())
|
||||
if (msg[-1].to_bytes(1, 'big') != compute_check(msg[1:-2])):
|
||||
self.check_fail = True
|
||||
eprint('CPS frame check failed')
|
||||
return
|
||||
self.length = msg[1]
|
||||
self.type = int.from_bytes(msg[2:4], 'big')
|
||||
self.is_ok = msg[4] == 0x01
|
||||
self.content = msg[5:-3]
|
||||
def __repr__(self):
|
||||
return 'packet length {} type {} is_ok {} content {}'.format(self.length, self.type, self.is_ok, self.content)
|
||||
|
||||
class ChanInfoFrame(CPSFrame):
|
||||
""" Received ChanInfoFrame class
|
||||
|
||||
This class decodes ChanInfoFrame frames received from the device.
|
||||
|
||||
"\tcpsInst.chanInfo.nChanIndex=%d\n
|
||||
\tcpsInst.chanInfo.nChanType=%d\n
|
||||
\tcpsInst.chanInfo.nVox=%d\n
|
||||
\tcpsInst.chanInfo.nPower=%d\n
|
||||
\tcpsInst.chanInfo.nRxFreq=%d\n
|
||||
\tcpsInst.chanInfo.nTxFreq=%d\n
|
||||
\tcpsInst.chanInfo.nTxContactsIdx=0x%08x\n
|
||||
\tcpsInst.chanInfo.nColorCode=%d\n
|
||||
\tcpsInst.chanInfo.nTimeSlot=%d\n
|
||||
\tcpsInst.chanInfo.bPoliteCall=%d\n"
|
||||
\tcpsInst.chanInfo.nEmrSys=%d\n
|
||||
\tcpsInst.chanInfo.nEncry=%d\n
|
||||
\tcpsInst.chanInfo.nTypeWideNarrow=%d\n
|
||||
\tcpsInst.chanInfo.nRxCtdcs=%d\n
|
||||
\tcpsInst.chanInfo.bRxCtdcsInvert=%d\n
|
||||
\tcpsInst.chanInfo.bTxCtdcsInvert=%d\n
|
||||
\tcpsInst.chanInfo.nTxCtdcs=%d\n
|
||||
\tcpsInst.chanInfo.nRxGrpListIdx=%d\n"
|
||||
"""
|
||||
def __init__(self, msg):
|
||||
super().__init__(msg)
|
||||
self.index = int.from_bytes(self.content[0:2], 'little')
|
||||
self.chantype = self.content[2]
|
||||
self.rxFreq = int.from_bytes(self.content[4:8], 'little')
|
||||
self.txFreq = int.from_bytes(self.content[8:12], 'little')
|
||||
self.txContactIndex = int.from_bytes(self.content[12:16], 'little')
|
||||
self.colorCode = self.content[16]
|
||||
self.timeslot = self.content[17]
|
||||
self.polite = self.content[18]
|
||||
self.emrSys = int.from_bytes(self.content[1:2], 'big')
|
||||
self.encryption = int.from_bytes(self.content[1:2], 'big')
|
||||
self.widenarrow = int.from_bytes(self.content[1:2], 'big')
|
||||
self.rxctdcs = int.from_bytes(self.content[1:2], 'big')
|
||||
self.rxctdcsinvert = int.from_bytes(self.content[1:2], 'big')
|
||||
self.txctdcsinvert = int.from_bytes(self.content[1:2], 'big')
|
||||
self.txctdcs = int.from_bytes(self.content[1:2], 'big')
|
||||
self.rxGroupIdx = int.from_bytes(self.content[1:2], 'big')
|
||||
self.vox = int.from_bytes(self.content[1:2], 'big')
|
||||
def __repr__(self):
|
||||
return 'packet length {} type {} is_ok {} index {} chantype {} rxfreq {} txfreq {}'.format(
|
||||
self.length, self.type, self.is_ok, self.index, self.chantype, self.rxFreq, self.txFreq)
|
||||
@@ -0,0 +1,14 @@
|
||||
import sys
|
||||
|
||||
def eprint(*args, **kwargs):
|
||||
""" print to stderr
|
||||
|
||||
This function takes its arguments just as if it were the normal
|
||||
print function and instead prints to stderr.
|
||||
|
||||
@param args: the arguments to print
|
||||
@param kwargs: the keyword arguments to print
|
||||
|
||||
"""
|
||||
|
||||
print(*args, file=sys.stderr, **kwargs)
|
||||
@@ -0,0 +1,34 @@
|
||||
def escaper(msg):
|
||||
""" escape message
|
||||
|
||||
this function escapes special characters in the message. These
|
||||
are 0x5c, 0x11 and 0x13 which are '\' and XON and XOFF characters.
|
||||
|
||||
@param msg: the message to escape
|
||||
@return: the escaped message
|
||||
|
||||
"""
|
||||
|
||||
out = bytes(sum([[0x5c, 0xFF ^ x ] if x in [0x11, 0x13, 0x5c] else [x] for x in msg], []))
|
||||
return out
|
||||
|
||||
def unescaper(msg):
|
||||
""" unescape message
|
||||
|
||||
this function undoes any escape sequences in a received message
|
||||
|
||||
@param msg: the message to unescape
|
||||
@return: the unescaped message
|
||||
"""
|
||||
|
||||
out = []
|
||||
escape = False
|
||||
for x in msg:
|
||||
if x == 0x5c:
|
||||
escape = True
|
||||
continue
|
||||
if escape:
|
||||
x = 0x5c ^ x ^ 0xa3
|
||||
escape = False
|
||||
out.append(x)
|
||||
return bytes(out)
|
||||
@@ -0,0 +1,121 @@
|
||||
import functools
|
||||
import operator
|
||||
from .escaper import escaper
|
||||
from .escaper import unescaper
|
||||
from .eprint import eprint
|
||||
|
||||
__author__ = "jhart99"
|
||||
__license__ = "MIT"
|
||||
|
||||
def compute_check(msg):
|
||||
""" Compute the check value for a message
|
||||
|
||||
AUCTUS messages use a check byte which is simply the XOR of all
|
||||
the values of the message.
|
||||
|
||||
"""
|
||||
|
||||
if len(msg) == 0:
|
||||
return int(0).to_bytes(1, 'little')
|
||||
return functools.reduce(operator.xor, msg).to_bytes(1, 'little')
|
||||
|
||||
def rda_debug_frame(flow, cmd, message):
|
||||
""" Format a raw message into a frame
|
||||
|
||||
AUCTUS frames are of the form AD 00 XX FF ...message... YY
|
||||
where XX is the length of the message and YY is the check byte
|
||||
Additionally certain bytes in the message are escaped.
|
||||
|
||||
"""
|
||||
|
||||
header = int(0xad).to_bytes(1, 'big')
|
||||
msg = flow + cmd + message
|
||||
msglen = len(msg).to_bytes(2, 'big')
|
||||
check = compute_check(msg)
|
||||
return escaper(header + msglen + msg + check)
|
||||
|
||||
def read_word(addr, seq = 1):
|
||||
""" make a frame to read a word at a memory address
|
||||
|
||||
this function creates a frame to read the memory from the device
|
||||
suitable for serial transmission.
|
||||
|
||||
"""
|
||||
|
||||
flow = bytes([0xff])
|
||||
command = bytes([0x02])
|
||||
if isinstance(addr, int):
|
||||
addr = addr.to_bytes(4, 'little')
|
||||
msg = addr + seq.to_bytes(1, 'big')
|
||||
return rda_debug_frame(flow, command, msg)
|
||||
|
||||
def write_register_int8(addr, msg):
|
||||
""" write to a byte to an internal register
|
||||
|
||||
this function creates a frame to do some device magic and these
|
||||
frames are used in the preamble and finalizer commands.
|
||||
|
||||
"""
|
||||
|
||||
flow = bytes([0xff])
|
||||
command = bytes([0x84])
|
||||
msg = addr.to_bytes(4, 'little') + msg.to_bytes(1, 'little')
|
||||
return rda_debug_frame(flow, command, msg)
|
||||
|
||||
def read_register_int8(addr, seq=1):
|
||||
""" make a frame containing a knock command
|
||||
|
||||
this function creates a frame that I assume wakes up the device
|
||||
for further commands.
|
||||
|
||||
"""
|
||||
|
||||
flow = bytes([0xff])
|
||||
command = bytes([0x04])
|
||||
msg = addr.to_bytes(4, 'little') + seq.to_bytes(1, 'big')
|
||||
return rda_debug_frame(flow, command, msg)
|
||||
|
||||
def write_block(addr, msg):
|
||||
""" make a frame containing a write command
|
||||
|
||||
this function creates a frame to do write a multiple byte content
|
||||
at a specific memory address. The length need not be a word, but
|
||||
could be 16 bytes or more.
|
||||
|
||||
"""
|
||||
|
||||
flow = bytes([0xff])
|
||||
command = bytes([0x83])
|
||||
if isinstance(addr, int):
|
||||
addr = addr.to_bytes(4, 'little')
|
||||
msg = addr + msg
|
||||
return rda_debug_frame(flow, command, msg)
|
||||
|
||||
|
||||
class RdaFrame:
|
||||
""" Received Frame class
|
||||
|
||||
This class decodes possible received Frames.
|
||||
"""
|
||||
ack = False
|
||||
check_fail = False
|
||||
seq = 0
|
||||
length = 0
|
||||
content = bytes([])
|
||||
def __init__(self, msg):
|
||||
msg = unescaper(msg)
|
||||
if len(msg) <= 4:
|
||||
if(msg == b'\x11\x13'):
|
||||
self.ack = True
|
||||
else:
|
||||
# impossibly short frame something is wrong.
|
||||
self.check_fail = True
|
||||
return
|
||||
if (msg[-1].to_bytes(1, 'big') != compute_check(msg[3:-1])):
|
||||
self.check_fail = True
|
||||
return
|
||||
self.seq = msg[4]
|
||||
self.length = msg[2]
|
||||
self.content = msg[5:-1]
|
||||
def __repr__(self):
|
||||
return 'packet length {} seq {} content {} ack {} check {}'.format(self.length, self.seq, self.content, self.ack, self.check_fail)
|
||||
@@ -0,0 +1,365 @@
|
||||
import serial
|
||||
import time
|
||||
import sys
|
||||
import re
|
||||
from .eprint import eprint
|
||||
from .a6commands import CPSFrame, h2p_command
|
||||
from .a6commands import ChanInfoFrame, h2p_command
|
||||
from .a6commands import ate_command
|
||||
from .a6commands import cps_command
|
||||
from .a6commands import read_uart_to_host
|
||||
from .rdadebug import RdaFrame
|
||||
from .rdadebug import read_word
|
||||
|
||||
class Singleton(object):
|
||||
def __new__(cls, *args, **kwargs):
|
||||
""" Singleton class
|
||||
|
||||
@param args: arguments
|
||||
@param kwargs: keyword arguments
|
||||
@return: object
|
||||
"""
|
||||
it = cls.__dict__.get("__it__")
|
||||
if it is not None:
|
||||
return it
|
||||
cls.__it__ = it = object.__new__(cls)
|
||||
it.init(*args, **kwargs)
|
||||
return it
|
||||
|
||||
def init(self, *args, **kwargs):
|
||||
"""
|
||||
"""
|
||||
pass
|
||||
|
||||
class SerialIO(Singleton):
|
||||
def init(self, port, baudrate=921600, verbosity=0, timeout=0.1):
|
||||
""" Initialize the serial port
|
||||
|
||||
@param port: serial port
|
||||
@param baudrate: baud rate
|
||||
@param verbosity: verbosity level
|
||||
"""
|
||||
self.port = port
|
||||
self.sio = serial.Serial(port, baudrate,
|
||||
serial.EIGHTBITS, serial.PARITY_NONE, serial.STOPBITS_ONE,
|
||||
xonxoff=True, rtscts=False, timeout=timeout)
|
||||
self.verbosity = verbosity
|
||||
self._ate_cps_addr = 0
|
||||
self._ate_cps_resp_addr = 0
|
||||
self._ate_cps_resp_length_addr = 0
|
||||
self._uart_resp_addr = 0
|
||||
self.sio.flush()
|
||||
if verbosity > 0:
|
||||
eprint("SerialIO: {} initialized".format(self.port))
|
||||
|
||||
def __del__(self):
|
||||
""" Close the serial port
|
||||
"""
|
||||
self.sio.close()
|
||||
|
||||
def write(self, msg):
|
||||
""" Write a message to the serial port
|
||||
|
||||
@param msg: message
|
||||
"""
|
||||
if self.verbosity > 0:
|
||||
eprint("write : ", msg.hex())
|
||||
self.sio.write(msg)
|
||||
|
||||
def read(self, nbytes):
|
||||
""" Read nbytes from the serial port
|
||||
|
||||
@param nbytes: number of bytes
|
||||
@return: message
|
||||
"""
|
||||
data = self.sio.read(nbytes)
|
||||
if self.verbosity > 0:
|
||||
eprint("read : ", data.hex())
|
||||
return data
|
||||
|
||||
def flush(self):
|
||||
""" Flush the serial port
|
||||
"""
|
||||
self.sio.flush()
|
||||
|
||||
@property
|
||||
def in_waiting(self):
|
||||
""" return the number of bytes in the serial port
|
||||
"""
|
||||
return self.sio.in_waiting
|
||||
|
||||
@property
|
||||
def ate_cps_addr(self):
|
||||
""" return the address of the ate command
|
||||
"""
|
||||
if self._ate_cps_addr == 0:
|
||||
self._ate_cps_addr = fetch_memory_address(0x81c00270)
|
||||
self._ate_cps_addr = int.from_bytes(self._ate_cps_addr, byteorder='little')
|
||||
return self._ate_cps_addr
|
||||
|
||||
@property
|
||||
def ate_cps_resp_addr(self):
|
||||
""" return the address of the ate command response
|
||||
"""
|
||||
if self._ate_cps_resp_addr == 0:
|
||||
self._ate_cps_resp_addr = fetch_memory_address(0x81c00264)
|
||||
self._ate_cps_resp_addr = int.from_bytes(self._ate_cps_resp_addr, byteorder='little')
|
||||
return self._ate_cps_resp_addr
|
||||
|
||||
@property
|
||||
def ate_cps_resp_length_addr(self):
|
||||
""" return the address of the ate command response
|
||||
"""
|
||||
return self.ate_cps_resp_addr - 4
|
||||
|
||||
@property
|
||||
def uart_resp_addr(self):
|
||||
""" return the address of the ate command response
|
||||
"""
|
||||
if self._uart_resp_addr == 0:
|
||||
self._uart_resp_addr = fetch_memory_address(0x81c0026c)
|
||||
self._uart_resp_addr = int.from_bytes(self._uart_resp_addr, byteorder='little')
|
||||
return self._uart_resp_addr
|
||||
|
||||
|
||||
|
||||
def write_flush_pause(msg, sleep = 0.07):
|
||||
""" Write out to serial and wait for the radio to process the command
|
||||
|
||||
@param msg: bytes to write
|
||||
@param sleep: time to sleep after writing in ms
|
||||
|
||||
"""
|
||||
uart = SerialIO()
|
||||
uart.write(msg)
|
||||
uart.flush()
|
||||
time.sleep(0.07)
|
||||
|
||||
|
||||
def send_ate_command(msg):
|
||||
""" Send a command to the ATE/CPS function on the radio
|
||||
|
||||
To send a command to the ATE or CPS software on the radio, it has
|
||||
to be surrounded by these h2p commands which clear the registers
|
||||
and then throw and interupt which causes the command to be
|
||||
executed
|
||||
|
||||
@param msg: bytes to write
|
||||
|
||||
"""
|
||||
uart = SerialIO()
|
||||
write_flush_pause(h2p_command(0))
|
||||
write_flush_pause(ate_command(msg, uart.ate_cps_addr))
|
||||
write_flush_pause(h2p_command(0xa5))
|
||||
|
||||
def send_cps_command(msg):
|
||||
""" Send a command to the ATE/CPS function on the radio
|
||||
|
||||
To send a command to the ATE or CPS software on the radio, it has
|
||||
to be surrounded by these h2p commands which clear the registers
|
||||
and then throw and interupt which causes the command to be
|
||||
executed
|
||||
|
||||
@param msg: bytes to write
|
||||
|
||||
"""
|
||||
|
||||
uart = SerialIO()
|
||||
write_flush_pause(h2p_command(0))
|
||||
write_flush_pause(cps_command(msg, uart.ate_cps_addr))
|
||||
write_flush_pause(h2p_command(0xa5))
|
||||
|
||||
def wait_on_read(retries=256, delay=0):
|
||||
""" Wait until a read happens
|
||||
|
||||
This function waits until something is received from the serial or
|
||||
will abort after a certain number of retries.
|
||||
|
||||
@param retries: number of retries before aborting
|
||||
@param delay: time to sleep between retries
|
||||
"""
|
||||
|
||||
uart = SerialIO()
|
||||
size = uart.in_waiting
|
||||
countdown = retries
|
||||
while size == 0 and countdown > 0:
|
||||
size = uart.in_waiting
|
||||
countdown -= 1
|
||||
if delay: time.sleep(delay)
|
||||
if countdown == 0:
|
||||
# nothing received
|
||||
return b''
|
||||
if size > 0:
|
||||
data = uart.read(size)
|
||||
return data
|
||||
|
||||
def send_uart_setup():
|
||||
""" Replays the initial UART setup sequence
|
||||
|
||||
This sequence and timing is from the CPS software capture.
|
||||
"""
|
||||
uart = SerialIO()
|
||||
knock_worked = False
|
||||
retries = 25
|
||||
while not knock_worked and retries > 0:
|
||||
uart.write(read_uart_to_host())
|
||||
uart.flush()
|
||||
time.sleep(0.001)
|
||||
data = wait_on_read()
|
||||
response = RdaFrame(data)
|
||||
if response.seq == 1 and response.content == b'\x80':
|
||||
knock_worked = True
|
||||
else:
|
||||
time.sleep(0.25)
|
||||
retries -= 1
|
||||
return knock_worked
|
||||
|
||||
def fetch_memory_address(addr, seq=1):
|
||||
""" Attempt to read a memory address and keep trying until it succeeds
|
||||
|
||||
"""
|
||||
uart = SerialIO()
|
||||
read_ok = False
|
||||
retval = b''
|
||||
retries = 25
|
||||
while not read_ok:
|
||||
frame = read_word(addr, seq)
|
||||
uart.write(frame)
|
||||
uart.flush()
|
||||
size = uart.in_waiting
|
||||
i = retries
|
||||
while size == 0 and i > 0:
|
||||
time.sleep(0.001)
|
||||
size = uart.in_waiting
|
||||
i -= 1
|
||||
if retries == 0:
|
||||
continue
|
||||
data = uart.read(size)
|
||||
inbound_frame = RdaFrame(data)
|
||||
read_ok = inbound_frame.seq == seq and not inbound_frame.check_fail
|
||||
retval = inbound_frame.content
|
||||
return retval
|
||||
|
||||
def atecps_resp_read():
|
||||
""" Read the response from an ATECPS command
|
||||
|
||||
@return: response from ATECPS command
|
||||
|
||||
"""
|
||||
uart = SerialIO()
|
||||
length = fetch_memory_address(uart.ate_cps_resp_length_addr)
|
||||
length = int.from_bytes(length, 'little')
|
||||
response = read_mem_range(uart.ate_cps_resp_addr, uart.ate_cps_resp_addr + length)
|
||||
return response
|
||||
|
||||
def uart_resp_read():
|
||||
""" Read the response from an ATECPS command
|
||||
|
||||
@return: response from ATECPS command
|
||||
|
||||
"""
|
||||
uart = SerialIO()
|
||||
length = fetch_memory_address(uart.uart_resp_addr)
|
||||
length = length[1]
|
||||
response = read_mem_range(uart.uart_resp_addr, uart.uart_resp_addr + length)
|
||||
return response
|
||||
|
||||
def read_mem_range(begin, end):
|
||||
""" Read a memory range
|
||||
|
||||
@param begin: start address
|
||||
@param end: end address
|
||||
@return: the data in bytes
|
||||
|
||||
"""
|
||||
addr = begin
|
||||
datalist = []
|
||||
while addr < end:
|
||||
data = fetch_memory_address(addr)
|
||||
if len(data) == 4:
|
||||
datalist.append(data)
|
||||
addr = addr + 4
|
||||
return b''.join(datalist)
|
||||
|
||||
def read_mem_burst(sio, begin, end, offset=0, verbosity=0):
|
||||
""" Read a limited memory range using a burst read
|
||||
|
||||
@param sio: serial object
|
||||
@param begin: start address
|
||||
@param end: end address
|
||||
@param offset: offset of the sequence number
|
||||
@param verbosity: verbosity level
|
||||
@return: the data in bytes
|
||||
"""
|
||||
|
||||
if end - begin > 0x100:
|
||||
raise ValueError('burst read only supports ranges of less than 256 bytes')
|
||||
# the burst is in words of 4 bytes
|
||||
burst = (end - begin) / 4
|
||||
# preallocate the lists
|
||||
recvflags = [False] * burst
|
||||
recvdata = [0] * burst
|
||||
i = 0
|
||||
data = b''
|
||||
while sum(recvflags) != burst:
|
||||
while i < burst:
|
||||
if not recvflags[i]:
|
||||
sio.write(read_word(begin + 4 * i, i + offset + 1))
|
||||
i += 1
|
||||
size = sio.in_waiting
|
||||
if size > 0:
|
||||
data += sio.read(size)
|
||||
i = 0
|
||||
|
||||
return b''.join(recvdata)
|
||||
|
||||
def get_chan_info(channel = 0):
|
||||
""" Get the channel info
|
||||
|
||||
@param channel: channel number
|
||||
@return: the channel info
|
||||
"""
|
||||
cmd = bytes([0, 0x12]) + channel.to_bytes(1, 'little')
|
||||
send_cps_command(cmd)
|
||||
resp = uart_resp_read()
|
||||
print(ChanInfoFrame(resp))
|
||||
# sys.stdout.buffer.write(resp)
|
||||
|
||||
def get_freq_err():
|
||||
""" Get the frequency error from the Radio
|
||||
|
||||
@return: frequency error in Hz
|
||||
"""
|
||||
send_ate_command("AT+DMOCONNECT")
|
||||
send_ate_command("AT+GETFREQERR")
|
||||
resp = atecps_resp_read()
|
||||
resp = resp.split(b'\x00')
|
||||
resp = [x.decode('utf-8') for x in resp]
|
||||
return parse_freq_err_resp(resp[0])
|
||||
|
||||
def parse_freq_err_resp(resp):
|
||||
""" Parse the frequency error response
|
||||
|
||||
@param resp: response from ATECPS
|
||||
@return: frequency error in Hz
|
||||
"""
|
||||
pattern = '\[(.+)\]'
|
||||
freqerr = re.search(pattern, resp)
|
||||
if freqerr:
|
||||
return int(freqerr.group(1))
|
||||
else:
|
||||
return 0
|
||||
|
||||
def set_freq_err(freqerr):
|
||||
""" Set the frequency error on the Radio
|
||||
|
||||
@param freqerr: frequency error parameter which is (-2500 + 10 * freqerr) in Hz
|
||||
|
||||
"""
|
||||
send_ate_command("AT+DMOCONNECT")
|
||||
send_ate_command("AT+DMOFREQERR={}".format(freqerr))
|
||||
resp = atecps_resp_read()
|
||||
resp = resp.split(b'\x00')
|
||||
resp = [x.decode('utf-8') for x in resp]
|
||||
for line in resp:
|
||||
print(line)
|
||||
@@ -0,0 +1,42 @@
|
||||
#!/usr/bin/env python3
|
||||
""" AT Commander for AUCTUS based radios
|
||||
|
||||
Allow for communication with AUCTUS A6 radios to their serial
|
||||
interface through the debug interface. Commands can either be "AT"
|
||||
commands or "CPS" commands. Both styles will work.
|
||||
|
||||
"""
|
||||
|
||||
import time
|
||||
from a6 import send_ate_command, send_cps_command, atecps_resp_read, SerialIO
|
||||
|
||||
__author__ = "jhart99"
|
||||
__license__ = "MIT"
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
import argparse
|
||||
parser = argparse.ArgumentParser(description='Auctus A6 ATECPS commander')
|
||||
parser.add_argument('-p', '--port', default='/dev/ttyUSB0',
|
||||
type=str, help='serial port')
|
||||
parser.add_argument('-b','--baudrate', default=921600,
|
||||
type=int, help='baud rate')
|
||||
parser.add_argument('-v','--verbosity', default=0, action='count',
|
||||
help='print sent and received frames to stderr for debugging')
|
||||
parser.add_argument('-V', '--version', action='version',
|
||||
version='%(prog)s 0.0.1',
|
||||
help='display version information and exit')
|
||||
parser.add_argument('command')
|
||||
args = parser.parse_args()
|
||||
|
||||
uart = SerialIO(args.port, args.baudrate, args.verbosity)
|
||||
|
||||
if args.command[0:3] == 'AT+':
|
||||
send_ate_command(args.command)
|
||||
else:
|
||||
send_cps_command(bytes.fromhex(args.command))
|
||||
time.sleep(0.1)
|
||||
data = atecps_resp_read()
|
||||
data = data.split(b'\x00')
|
||||
for line in data:
|
||||
print(line.decode('utf-8'))
|
||||
@@ -0,0 +1,120 @@
|
||||
Command | function
|
||||
---------------------|----------
|
||||
DMOCONNECT | Required before issuing other commands
|
||||
DMODISCONNECT | Ends the current session
|
||||
DMORLIC | Returns the Device Serial Number
|
||||
DMOVERQ | Returns Ver 1.0.0
|
||||
DMOREVDATA | No response
|
||||
BINARY | No response
|
||||
DMOGETSTARTUP | No response
|
||||
DMOGETSOFTVERSION | Returns the firmware version
|
||||
DMOGETSQSTATUS | No response
|
||||
DMOGETCURRCH | Replies with the current channel parameters +DMOGETCURRCH:0,DIG,438802000,438802000,0,0,1,2,0,0,0 or +DMOGETCURRCH:15,ANA,405000000,405000000,1,1
|
||||
DMOGETCHFREQ | Replies with the channel parameters for the given channel number AT+DMOGETCHFREQ=<channel>
|
||||
DMOGETLNAOFFS | Returns the LNA offset
|
||||
DMOGETRSSIPARAM | Returns the RSSI offset
|
||||
DMOGETCHIPID | A unique ID for the A6 processor
|
||||
DMOGETGRPADDR | Returns the DMR contact for the current channel
|
||||
DMOGETWLKTLKID | Returns the DMR radio ID of the radio
|
||||
DMORDRSSI | Returns the current receive signal strength in dBm
|
||||
DMOTESTMODE |
|
||||
DMOSETGROUP | Sets all of the frequency settings for the given channel
|
||||
DMOSETCTCDCS | Sets the CTCSS or DCS mode for the current channel AT+DMOSETCTCDCS=<mode>,<ctcDcsType>,<ctcDcsNum>,<ctcDcsInvert>
|
||||
DMOSETCTCDCSGAIN | Sets the audio gain for the CTCSS or DCS tone AT+DMOSETCTCDCSGAIN=<ctcDcsType>,<gain>
|
||||
DMOSETTAILFREQ | Sets the tail frequency(550-2000 in tenths of Hz) for the current channel AT+DMOSETTAILFREQ=<tailFreq>
|
||||
DMOAUTOPOWCONTR | Sets the automatic power control mode for the current channel AT+DMOAUTOPOWCONTR=<mode>
|
||||
DMOSETMIC | Sets the microphone gain for the current channel AT+DMOSETMIC=<gain>
|
||||
DMOSETVOLUME | Sets the volume for the current channel AT+DMOSETVOLUME=<volume>
|
||||
DMOSETVOLUMELEVEL | Sets the volume level for the current channel AT+DMOSETVOLUMELEVEL=<level>
|
||||
DMOSETVOX | Sets the VOX level for the current channel AT+DMOSETVOX=<level>
|
||||
DMOSENDDATA | ¯\\_(ツ)_/¯
|
||||
DMOSETAPC |
|
||||
DMOSETSN |
|
||||
DMORESETSN |
|
||||
DMOSETRSSIPARAM |
|
||||
DMOSETLNAOFFS |
|
||||
DMOSAVEPARAM |
|
||||
DMOERASEPARAM |
|
||||
DMOSETSMARTLO |
|
||||
DMOGETRXBER | Returns the received bit error rate
|
||||
DMOCHSWITCH | Changes the current channel AT+DMOCHSWITCH=<channel>
|
||||
DMOSETCHFREQ | Changes the channel parameters for the given channel number AT+DMOSETCHFREQ=<txfreq>,<rxfreq> where the frequencies are like this 438.8001. Must have this number of digits to work
|
||||
DMOSETCHPOWER |
|
||||
DMOSETPOWER | Sets the power level for the current channel AT+DMOSETPOWER=<level> where level is 0-2
|
||||
DMOSETPWRSAVELV | Sets the FM power save level for the current channel if no audio, the radio will stop transmitting AT+DMOSETPWRSAVELV=<level>
|
||||
DMOSETWLKTLKID | Set the DMR Radio ID
|
||||
DMOSETGRPADDR | Set the DMR group address
|
||||
DMOSETFREQBANDNUM | Set the number of frequency bands AT+DMOSETFREQBANDNUM=<number> where number is less than 16
|
||||
DMOGETFREQBANDNUM | Get the current number of frequency bands
|
||||
DMOSETFREQBANDVAL | Set the values of the frequency bands
|
||||
DMOGETFREQBANDVAL | Get the values of the frequency bands
|
||||
DMOAMPCTRLTEST | Sets an amplifier gain and tests the amplifier AT+DMOAMPCTRLTEST=<gain> gain is 0-128
|
||||
DMODIGPWRSAVECTRL |
|
||||
DMOSETLCHEADERCNT | Sets the number of LC headers sent AT+DMOSETLCHEADERCNT=<count>
|
||||
DMOGRPCALLREQ |
|
||||
DMOINDCALLREQ |
|
||||
DMOALLCALLREQ |
|
||||
DMOALERTCSBKREQ | Sends an alert call request to the radio AT+DMOALERTCSBKREQ=<dmrid>
|
||||
DMOCHECKCSBKREQ | Sends a check call request to the radio AT+DMOCHECKCSBKREQ=<dmrid>
|
||||
DMOALARMCSBKREQ | Sends an alarm call request to the radio AT+DMOALARMCSBKREQ=<dmrid>
|
||||
DMORESPCSBKREQ | Sends a response call request to the radio AT+DMORESPCSBKREQ=<dmrid>
|
||||
SETIMAGEREJEMODE |
|
||||
SETIMAGEREJE |
|
||||
SETEQUALIZER |
|
||||
SETCOMPANDER |
|
||||
SETSCRAMBLER |
|
||||
SETTONELIST |
|
||||
SETDTMF |
|
||||
RESTOREFACTORYSETTING | Restores the original settings. The password for this is 778123456
|
||||
BERTEST |
|
||||
SETCOLORCODE | Set the color code for the current channel AT+SETCOLORCODE=<colorCode>
|
||||
GETGPADCVALUE | Get the current value of the GPADC channel(0-1) AT+GETGPADCVALUE=<channel>
|
||||
SETGPADCCHAN | Set the GPADC channel enable AT+SETGPADCCHAN=<channel>,<enable> channel=0,1 enable=0,1
|
||||
GETGPIOVALUE | Get the current value of the GPIO channel(0-32) AT+GETGPIOVALUE=<channel>
|
||||
SETGPIOVALUE | Set the GPIO channel value AT+SETGPIOVALUE=<channel>,<value> channel=0-32 value=0,1
|
||||
SETGPIODIR | Set the direction of the GPIO channel(0-32) AT+SETGPIODIR=<channel>,<direction> channel=0-32 direction=0,1
|
||||
GETPOWERAMPGAIN | Get the power amplifier gain values AT+GETPOWERAMPGAIN=<band> returns <band>,<digLow>,<digMed>,<digHigh>,<anaLow>,<anaMed>,<anaHigh>
|
||||
SETPOWERAMPGAIN | Sets the power amplifier gain
|
||||
GETPOWERAPCGAIN | Get the automatic power control gain values AT+GETPOWERAPCGAIN=<band> returns <band>,<digLow>,<digMed>,<digHigh>,<anaLow>,<anaMed>,<anaHigh>
|
||||
SETPOWERAPCGAIN | Sets the automatic power control gain
|
||||
SETPOWERAPCSW | Sets the automatic power control switch ?
|
||||
GETEFLTRXADJ | Returns the RX filter adjustment for a band AT+GETEFLTRXADJ=<band> where the value is <band>,<dac>,<gain>
|
||||
SETEFLTRXADJ | Sets the RX filter adjustment for a band AT+SETEFLTRXADJ=<band>,<dac>,<gain>
|
||||
DMOGETVAFC | Returns the current Automatic Frequency Correction value
|
||||
DMOSETVAFC | Sets the Automatic Frequency Correction value
|
||||
DMOPTT | Sets the PTT state for the current channel AT+DMOPTT=<state> where state is 0 for off and 1 for on
|
||||
GETFREQERR | Get the TCXO frequency error in 10s of Hz
|
||||
DMOFREQERR | Set the TCXO frequency error in 10s of Hz
|
||||
GETBANDBYFREQ | Returns the band number for the given frequency AT+GETBANDBYFREQ=<freq>
|
||||
DMOGETFREQBANDVAL | Returns the cut offs for the different frequency bands
|
||||
DMOGETFREQBANDNUM | Returns the number of frequency bands in use/defined AT+DMOGETFREQBANDNUM
|
||||
DMOSETFREQBANDNUM | Can be used to change the number of defined frequency bands AT+DMOSETFREQBANDNUM=2
|
||||
DMRSIGNATURE | Unknown command. The signature looks something like this: AT+DMRSIGNATURE/aa,aa,aa,... with a total of 30 bytes worth of numbers Returns something that says AUTHSETKEYS
|
||||
WRITECFS | Does something similar to the DMRSIGNATURE command
|
||||
GETINCALLID | Returns the incoming call ID, <calltype>,<from>,<to> where call type is 0,1,2 corresponding to individual, group and all calls and from and to are DMR IDs
|
||||
SETALERTTONE |
|
||||
SETBATTDETRANGADC |
|
||||
SETBATTADJ |
|
||||
SETPROFILERAMPSSHIFT |
|
||||
SETPROFILERAMPSDWPOS |
|
||||
GETPROFILERAMPS |
|
||||
SETRAMPINGTABLE |
|
||||
GETAPC_FINEVALUE |
|
||||
SETAPC_FINEVALUE |
|
||||
DMR_ADJTXSYMDEV |
|
||||
DMR_GETTXSYMDEV |
|
||||
SETSTANDARD | Sets the testing standard for the radio AT+SETSTANDARD=<standard> where standard is 0-4. Completely resets the radio's program!
|
||||
SETSQLEVEL |
|
||||
SETSQADJUST |
|
||||
SETSQMODE |
|
||||
SETTXIQGAIN |
|
||||
GETSQLEVEL |
|
||||
SETMODULE |
|
||||
SETCHANNELKNOBMODE |
|
||||
FGU_AFC_ADJ |
|
||||
FGU_AFC_GET |
|
||||
FGU_VCON_BIAS |
|
||||
FGU_VCON_BIAS_GET |
|
||||
_FGU_TUNE_GET |
|
||||
FGU_TUNE_GET |
|
||||
|
||||
@@ -0,0 +1,48 @@
|
||||
Command | Description
|
||||
--------|------------
|
||||
0000 | SendBaseInfo
|
||||
0001 | Unknown
|
||||
0002 | Unknown Writes to NVRAM!
|
||||
0003 | SetEmbInfo
|
||||
0004 | GetEmbInfoAll
|
||||
000f | SetBaseSetting
|
||||
0010 | SendBaseSettingsBasicSet
|
||||
0011 | SetChanInfo
|
||||
0012 | GetChanInfo
|
||||
0013 | SetChanName
|
||||
0014 | GetChanName
|
||||
0015 | SetTalkieName
|
||||
0016 | Unknown
|
||||
0017 | SetIndividualCallCnt
|
||||
0018 | SetIndividualCallInfo
|
||||
0019 | SendIndividualCallCnt
|
||||
001a | SendIndividualCallInfo
|
||||
001b | SendCmdResponse ?
|
||||
001c | SetGroupCallInfo
|
||||
001d | Unknown
|
||||
001e | GetGroupCallInfo
|
||||
001e | SendCmdResponse ?
|
||||
0020 | Unknown
|
||||
0021 | SetShortcutMsgInfo ?
|
||||
0022 | SetShortcutCnt
|
||||
0023 | SetShortcutMsgInfo ?
|
||||
0024 | GetShortcutMsgInfo
|
||||
0025 | SetKeyFunc
|
||||
0026 | GetKeyFunc
|
||||
0027 | SetScanlist
|
||||
0028 | GetScanlistCnt
|
||||
002a | GetScanlist
|
||||
002b | Checks the programming password
|
||||
0100 | Unknown
|
||||
0101 | Unknown
|
||||
0102 | Unknown
|
||||
0104 | Unknown
|
||||
0a00 | @CpsByHostPort_ConcatenateHdr CPS_Hdr[StartAddr:0x82004810 len:772] 0x88105948
|
||||
0a01 | Unknown
|
||||
0a02 | Unknown
|
||||
0a03 | Unknown
|
||||
0a04 | Unknown but same as 0a07
|
||||
0a05 | Unknown
|
||||
0a06 | Unknown
|
||||
0a07 | Unknown but same as 0a04
|
||||
0a08 | PROT_SendDataFrame Seems to echo the sent packet back
|
||||
@@ -0,0 +1,33 @@
|
||||
#!/usr/bin/env python3
|
||||
""" AT Commander for AUCTUS based radios
|
||||
|
||||
Allow for communication with AUCTUS A6 radios to their serial
|
||||
interface through the debug interface. Commands can either be "AT"
|
||||
commands or "CPS" commands. Both styles will work.
|
||||
|
||||
"""
|
||||
|
||||
from a6 import get_chan_info, SerialIO
|
||||
|
||||
__author__ = "jhart99"
|
||||
__license__ = "MIT"
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
import argparse
|
||||
parser = argparse.ArgumentParser(description='Auctus A6 ATECPS commander')
|
||||
parser.add_argument('-p', '--port', default='/dev/ttyUSB0',
|
||||
type=str, help='serial port')
|
||||
parser.add_argument('-b','--baudrate', default=921600,
|
||||
type=int, help='baud rate')
|
||||
parser.add_argument('-v','--verbosity', default=0, action='count',
|
||||
help='print sent and received frames to stderr for debugging')
|
||||
parser.add_argument('-V', '--version', action='version',
|
||||
version='%(prog)s 0.0.1',
|
||||
help='display version information and exit')
|
||||
parser.add_argument('channel', type=int, help='channel number')
|
||||
args = parser.parse_args()
|
||||
|
||||
|
||||
uart = SerialIO(args.port, args.baudrate, args.verbosity)
|
||||
get_chan_info(args.channel)
|
||||
@@ -0,0 +1,46 @@
|
||||
#!/usr/bin/env python3
|
||||
""" Frequency offset fix for AUCTUS A6 based radios
|
||||
|
||||
This program takes a measured frequency and a desired frequency and
|
||||
then reads a TCXO error programmed into the radio and sets a new TCXO
|
||||
offset in the radio. The radio needs to have its channel changed
|
||||
after for the new setting to take effect.
|
||||
|
||||
This fixes the high BER seen on some radios like my GOCOM GD900 which
|
||||
had a 800 Hz offset from the factory which while within spec was
|
||||
outside what my poor MMDVM board could tolerate.
|
||||
|
||||
"""
|
||||
|
||||
from a6 import SerialIO, get_freq_err, set_freq_err
|
||||
|
||||
__author__ = "jhart99"
|
||||
__license__ = "MIT"
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
import argparse
|
||||
parser = argparse.ArgumentParser(description='Auctus A6 Frequency Error Fixer')
|
||||
parser.add_argument('-p', '--port', default='/dev/ttyUSB0',
|
||||
type=str, help='serial port')
|
||||
parser.add_argument('-b','--baudrate', default=921600,
|
||||
type=int, help='baud rate')
|
||||
parser.add_argument('-v','--verbosity', default=0, action='count',
|
||||
help='print sent and received frames to stderr for debugging')
|
||||
parser.add_argument('-V', '--version', action='version',
|
||||
version='%(prog)s 0.0.1',
|
||||
help='display version information and exit')
|
||||
parser.add_argument('current', type=int,
|
||||
help='the measured frequency the radio is currently transmitting in Hz')
|
||||
parser.add_argument('target', type=int,
|
||||
help='the programmed frequency in the radio in Hz')
|
||||
args = parser.parse_args()
|
||||
|
||||
uart = SerialIO(args.port, args.baudrate, args.verbosity)
|
||||
|
||||
delta = args.target - args.current
|
||||
curerr = get_freq_err()
|
||||
target = curerr + delta
|
||||
if abs(target) > 2500:
|
||||
raise ValueError("Desired offset exceeds maximum of 2500 Hz")
|
||||
set_freq_err(int((target + 2500)/10))
|
||||
@@ -0,0 +1,41 @@
|
||||
#!/usr/bin/env python3
|
||||
""" AT Commander for AUCTUS based radios
|
||||
|
||||
Allow for communication with AUCTUS A6 radios to their serial
|
||||
interface through the debug interface. Commands can either be "AT"
|
||||
commands or "CPS" commands. Both styles will work.
|
||||
|
||||
"""
|
||||
|
||||
import serial
|
||||
import sys
|
||||
from a6 import read_mem_range, SerialIO
|
||||
|
||||
|
||||
__author__ = "jhart99"
|
||||
__license__ = "MIT"
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
import argparse
|
||||
parser = argparse.ArgumentParser(description='Auctus A6 dumper')
|
||||
parser.add_argument('--begin', type=lambda x: int(x,0),
|
||||
help='begin address default 0x82000000',
|
||||
default=0x82000000)
|
||||
parser.add_argument('--end', type=lambda x: int(x,0),
|
||||
help='end address default 0x8200ff00',
|
||||
default=0x8200ff00)
|
||||
parser.add_argument('-p', '--port', default='/dev/ttyUSB0',
|
||||
type=str, help='serial port')
|
||||
parser.add_argument('-b','--baudrate', default=921600,
|
||||
type=int, help='baud rate')
|
||||
parser.add_argument('-v','--verbosity', default=0, action='count',
|
||||
help='print sent and received frames to stderr for debugging')
|
||||
parser.add_argument('-V', '--version', action='version',
|
||||
version='%(prog)s 0.0.1',
|
||||
help='display version information and exit')
|
||||
args = parser.parse_args()
|
||||
|
||||
uart = SerialIO(args.port, args.baudrate, args.verbosity)
|
||||
data = read_mem_range(args.begin, args.end)
|
||||
sys.stdout.buffer.write(data)
|
||||
@@ -0,0 +1,30 @@
|
||||
#!/usr/bin/env python3
|
||||
""" Reboot and freeze
|
||||
|
||||
Reset the AUCTUS A6 processor and freeze execution. This allows for
|
||||
uninterupted dumping of the normally problematic BCPU rom.
|
||||
|
||||
"""
|
||||
|
||||
from a6 import SerialIO, reboot_and_freeze
|
||||
|
||||
__author__ = "jhart99"
|
||||
__license__ = "MIT"
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
import argparse
|
||||
parser = argparse.ArgumentParser(description='Auctus A6 reboot and freeze')
|
||||
parser.add_argument('-p', '--port', default='/dev/ttyUSB0',
|
||||
type=str, help='serial port')
|
||||
parser.add_argument('-b','--baudrate', default=921600,
|
||||
type=int, help='baud rate')
|
||||
parser.add_argument('-v','--verbosity', default=0, action='count',
|
||||
help='print sent and received frames to stderr for debugging')
|
||||
parser.add_argument('-V', '--version', action='version',
|
||||
version='%(prog)s 0.0.1',
|
||||
help='display version information and exit')
|
||||
args = parser.parse_args()
|
||||
|
||||
uart = SerialIO(args.port, args.baudrate, args.verbosity)
|
||||
uart.write(reboot_and_freeze())
|
||||
Ссылка в новой задаче
Block a user