|
# Copyright 2012 Dan Smith <dsmith@danplanet.com>
|
|
#
|
|
# This program is free software: you can redistribute it and/or modify
|
|
# it under the terms of the GNU General Public License as published by
|
|
# the Free Software Foundation, either version 2 of the License, or
|
|
# (at your option) any later version.
|
|
#
|
|
# This program is distributed in the hope that it will be useful,
|
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
# GNU General Public License for more details.
|
|
#
|
|
# You should have received a copy of the GNU General Public License
|
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
# Driver author: Pavel, CO7WT, co7wt@frcuba.co.cu
|
|
|
|
import struct
|
|
import os
|
|
import logging
|
|
|
|
from chirp import chirp_common, directory, memmap, errors, util
|
|
from chirp import bitwise
|
|
from textwrap import dedent
|
|
from chirp.settings import RadioSettingGroup, RadioSetting, \
|
|
RadioSettingValueBoolean, RadioSettingValueList, \
|
|
RadioSettingValueString, RadioSettings
|
|
|
|
LOG = logging.getLogger(__name__)
|
|
|
|
MEM_FORMAT = """
|
|
#seekto 0x0010;
|
|
struct {
|
|
lbcd rx_freq[4];
|
|
lbcd tx_freq[4];
|
|
lbcd rx_tone[2];
|
|
lbcd tx_tone[2];
|
|
u8 unknown:4,
|
|
scrambler:1,
|
|
unknown1:1,
|
|
unknown2:1,
|
|
busy_lock:1;
|
|
u8 unknown3[3];
|
|
} memory[99];
|
|
|
|
#seekto 0x0640;
|
|
struct {
|
|
lbcd vrx_freq[4];
|
|
lbcd vtx_freq[4];
|
|
lbcd vrx_tone[2];
|
|
lbcd vtx_tone[2];
|
|
u8 shift_plus:1,
|
|
shift_minus:1,
|
|
unknown11:2,
|
|
scramble:1,
|
|
unknown12:1,
|
|
unknown13:1,
|
|
busy_lock:1;
|
|
u8 unknown14[3];
|
|
} vfo;
|
|
|
|
#seekto 0x07B0;
|
|
struct {
|
|
u8 ani_mode; // fd-288's family store the any settings here
|
|
char ani[3]; // str of the any code (200-999) inclusive
|
|
u8 unknown21[12]; // fill to match the fd-268's start
|
|
u8 unknown22:5, // x07c0, fd-268's start (LCD icons)
|
|
bw1:1, // twin setting of bw (LCD "romb")
|
|
bs1:1, // twin setting of bs (LCD "S")
|
|
warning1:1; // twin setting of warning (LCD "Tune")
|
|
u8 sql[1];
|
|
u8 monitorval;
|
|
u8 tot[1];
|
|
u8 powerrank;
|
|
u8 unknown23[3];
|
|
u8 unknown24[8];
|
|
char model[8];
|
|
u8 unknown26[8];
|
|
u8 step;
|
|
u8 unknown27:2,
|
|
power:1,
|
|
lamp:1,
|
|
lamp_auto:1,
|
|
key:1,
|
|
monitor:1,
|
|
bw:1;
|
|
u8 unknown28:3,
|
|
warning:1,
|
|
bs:1,
|
|
unknown29:1,
|
|
wmem:1,
|
|
wvfo:1;
|
|
u8 active_ch;
|
|
u8 unknown30[4];
|
|
u8 unknown31[4];
|
|
bbcd vfo_shift[4];
|
|
} settings;
|
|
"""
|
|
|
|
MEM_SIZE=0x0800
|
|
CMD_ACK = "\x06"
|
|
BLOCK_SIZE = 0x08
|
|
POWER_LEVELS = ["Low", "High"]
|
|
LIST_SQL = ["Off"] + ["%s" % x for x in range(1, 10)]
|
|
LIST_TOT = ["Off"] + ["%s" % x for x in range(10, 100, 10)]
|
|
ONOFF = ["Off", "On"]
|
|
STEPF = ["5", "10", "6.25", "12.5", "25"]
|
|
ACTIVE_CH = ["%s" % x for x in range(1, 100)]
|
|
KEY_LOCK =["Automatic", "Manual"]
|
|
BW = ["Narrow", "Wide"]
|
|
W_MODE = ["VFO", "Memory"]
|
|
VSHIFT = ["None", "-", "+"]
|
|
POWER_RANK = ["%s" % x for x in range(0, 28)]
|
|
ANI = ["Off", "BOT", "EOT", "Both"]
|
|
|
|
def raw_recv(radio, amount):
|
|
"""Raw read from the radio device"""
|
|
data = ""
|
|
try:
|
|
data = radio.pipe.read(amount)
|
|
except:
|
|
raise errors.RadioError("Error reading data from radio")
|
|
|
|
return data
|
|
|
|
def raw_send(radio, data):
|
|
"""Raw send to the radio device"""
|
|
try:
|
|
data = radio.pipe.write(data)
|
|
except:
|
|
raise errors.RadioError("Error sending data to radio")
|
|
|
|
def make_frame(cmd, addr, length = BLOCK_SIZE):
|
|
"""Pack the info in the format it likes"""
|
|
return struct.pack(">BHB", ord(cmd), addr, length)
|
|
|
|
def check_ack(r, text):
|
|
"""Check for a correct ACK from radio, rising error 'Text'
|
|
if something was wrong """
|
|
ack = raw_recv(r, 1)
|
|
if ack != CMD_ACK:
|
|
raise errors.RadioError(text)
|
|
else:
|
|
return True
|
|
|
|
def send(radio, frame, data = ""):
|
|
"""Generic send data to the radio"""
|
|
raw_send(radio, frame)
|
|
if data != "":
|
|
raw_send(radio, data)
|
|
check_ack(radio, "Radio didn't ack the last block of data")
|
|
|
|
def recv(radio):
|
|
"""Generic receive data from the radio, return just data"""
|
|
# you must get it all 12 at once (4 header + 8 data)
|
|
rxdata = raw_recv(radio, 12)
|
|
if (len(rxdata) != 12):
|
|
raise errors.RadioError(
|
|
"Radio sent %i bytes, we expected 12" % (len(rxdata)))
|
|
else:
|
|
data = rxdata[4:]
|
|
send(radio, CMD_ACK)
|
|
check_ack(radio, "Radio didn't ack the sended data")
|
|
return data
|
|
|
|
def do_program(radio):
|
|
"""Feidaxin program mode and identification dance"""
|
|
def do_magic():
|
|
"""Try to get the radio in program mode, the factory software
|
|
(FDX-288) tries up to ~16 times to get the correct response,
|
|
we will do the same, but with a lower count."""
|
|
tries = 8
|
|
# UI information
|
|
status = chirp_common.Status()
|
|
status.cur = 0
|
|
status.max = tries
|
|
status.msg = "Linking to radio, please wait."
|
|
radio.status_fn(status)
|
|
|
|
# every byte of this magic chain must be send separatedly
|
|
magic = "\x02PROGRA"
|
|
|
|
# start the fun, finger crossed please...
|
|
for a in range(0, tries):
|
|
# UI update
|
|
status.cur = a
|
|
radio.status_fn(status)
|
|
for i in range(0, len(magic)):
|
|
# this is needed due to timming
|
|
send(radio, magic[i])
|
|
|
|
# Now you get a x06 of ACK
|
|
ack = raw_recv(radio, 1)
|
|
if ack == CMD_ACK:
|
|
return True
|
|
|
|
return False
|
|
|
|
# try to get the radio in program mode
|
|
ack = do_magic()
|
|
if not ack:
|
|
erc = "Radio did not accept program mode, "
|
|
erc += "check your cable and radio; then try again."
|
|
raise errors.RadioError(erc)
|
|
|
|
# now we request identification
|
|
send(radio, "M")
|
|
send(radio, "\x02")
|
|
ident = raw_recv(radio, 8)
|
|
|
|
################# WARNING ##########################################
|
|
# Feidaxin radios has a "send id" procedure in the initial handshake
|
|
# but it's worthless, once you do a hardware reset the ident area
|
|
# get all 0xFF.
|
|
#
|
|
# Even FDX-288 software appears to match the model by any other
|
|
# mean, so I detected on a few images that the 3 first bytes are
|
|
# unique to each radio model, so for now we use that method untill
|
|
# proven otherwise
|
|
####################################################################
|
|
|
|
LOG.debug("Radio's ID string:")
|
|
LOG.debug(util.hexprint(ident))
|
|
|
|
# final ACK
|
|
send(radio, CMD_ACK)
|
|
check_ack(radio, "Radio refused to enter programming mode")
|
|
|
|
def do_download(radio):
|
|
""" The download function """
|
|
do_program(radio)
|
|
# UI progress
|
|
status = chirp_common.Status()
|
|
status.cur = 0
|
|
status.max = MEM_SIZE
|
|
status.msg = "Cloning from radio..."
|
|
radio.status_fn(status)
|
|
data = ""
|
|
for addr in range(0x0000, MEM_SIZE, BLOCK_SIZE):
|
|
send(radio, make_frame("R", addr))
|
|
d = recv(radio)
|
|
data += d
|
|
# UI Update
|
|
status.cur = addr
|
|
radio.status_fn(status)
|
|
|
|
return memmap.MemoryMap(data)
|
|
|
|
def do_upload(radio):
|
|
"""The upload function"""
|
|
do_program(radio)
|
|
# UI progress
|
|
status = chirp_common.Status()
|
|
status.cur = 0
|
|
status.max = MEM_SIZE
|
|
status.msg = "Cloning to radio..."
|
|
radio.status_fn(status)
|
|
|
|
for addr in range(0x0000, MEM_SIZE, BLOCK_SIZE):
|
|
send(radio, make_frame("W",addr), \
|
|
radio.get_mmap()[addr:addr+BLOCK_SIZE])
|
|
# UI Update
|
|
status.cur = addr
|
|
radio.status_fn(status)
|
|
|
|
def model_match(cls, data):
|
|
"""Use a experimental guess to determine if the radio you just
|
|
downloaded or the img opened you is for this model"""
|
|
|
|
# Using a few imgs of some FD radio I found that the four first
|
|
# bytes it's like the model fingerprint, so we have to testing the
|
|
# model type with this experimental method so far.
|
|
fp = data[0:4]
|
|
if fp == cls._IDENT:
|
|
return True
|
|
else:
|
|
LOG.debug("Unknowd Feidaxing radio, ID:")
|
|
LOG.debug(util.hexprint(fp))
|
|
print("Unknowd Feidaxing radio, ID:")
|
|
print util.hexprint(fp)
|
|
return False
|
|
|
|
class FeidaxinFD2x8yRadio(chirp_common.CloneModeRadio):
|
|
"""Feidaxin FD-268x & 288x Radios"""
|
|
VENDOR = "Feidaxin"
|
|
MODEL = "FD-268A/B & FD-288A/B"
|
|
BAUD_RATE = 9600
|
|
_memsize = MEM_SIZE
|
|
_upper = 99
|
|
_VFO_DEFAULT = 0
|
|
_IDENT = ""
|
|
_active_ch = ACTIVE_CH
|
|
|
|
@classmethod
|
|
def get_prompts(cls):
|
|
rp = chirp_common.RadioPrompts()
|
|
rp.experimental = \
|
|
('The program mode of this radio has his tricks, '
|
|
'so this driver is *completely experimental*.')
|
|
rp.pre_download = _(dedent("""\
|
|
This radio has a tricky way of enter into program mode,
|
|
even the original software has a few tries to get inside.
|
|
|
|
I will try 8 times (most the time ~3 will doit) and this
|
|
can take a few seconds, if don't work, try again a few times.
|
|
|
|
If you can get into it, please check the radio and cable.
|
|
"""))
|
|
rp.pre_upload = _(dedent("""\
|
|
This radio has a tricky way of enter into program mode,
|
|
even the original software has a few tries to get inside.
|
|
|
|
I will try 8 times (most the time ~3 will doit) and this
|
|
can take a few seconds, if don't work, try again a few times.
|
|
|
|
If you can get into it, please check the radio and cable.
|
|
"""))
|
|
return rp
|
|
|
|
def get_features(self):
|
|
"""Return information about this radio's features"""
|
|
rf = chirp_common.RadioFeatures()
|
|
# this feature is READ ONLY by now.
|
|
rf.has_settings = True
|
|
rf.has_bank = False
|
|
rf.has_tuning_step = False
|
|
rf.has_name = False
|
|
rf.has_offset =True
|
|
rf.has_mode = False
|
|
rf.has_dtcs = True
|
|
rf.has_rx_dtcs = True
|
|
rf.has_dtcs_polarity = True
|
|
rf.has_ctone = True
|
|
rf.has_cross = True
|
|
rf.valid_duplexes = ["", "-", "+", "off"]
|
|
rf.valid_tmodes = ['', 'Tone', 'TSQL', 'DTCS', 'Cross']
|
|
# we have to remove "Tone->" because this is the same to "TQSL"
|
|
# I get a few days hitting the wall with my head about this...
|
|
rf.valid_cross_modes = [
|
|
"Tone->Tone",
|
|
"DTCS->",
|
|
"->DTCS",
|
|
"Tone->DTCS",
|
|
"DTCS->Tone",
|
|
"->Tone",
|
|
"DTCS->DTCS"]
|
|
# Power levels are golbal and no per channel, so disabled
|
|
#rf.valid_power_levels = POWER_LEVELS
|
|
# this radio has no skips
|
|
rf.valid_skips = []
|
|
# this radio modes are global and not per channel, so just FM
|
|
rf.valid_modes = ["FM"]
|
|
rf.valid_bands = [self._range]
|
|
rf.memory_bounds = (1, self._upper)
|
|
return rf
|
|
|
|
def sync_in(self):
|
|
"""Do a download of the radio eeprom"""
|
|
data = do_download(self)
|
|
|
|
# as the radio comm protocol's identification is useless
|
|
# we test the model after having the img
|
|
if not model_match(self, data):
|
|
# ok, wrong model, fire an error
|
|
erc = "EEPROM fingerprint don't match, check if you "
|
|
erc += "selected the right radio model."
|
|
raise errors.RadioError(erc)
|
|
|
|
# all ok
|
|
self._mmap = data
|
|
self.process_mmap()
|
|
|
|
def sync_out(self):
|
|
"""Do an upload to the radio eeprom"""
|
|
do_upload(self)
|
|
|
|
def process_mmap(self):
|
|
"""Process the memory object.
|
|
Used in the download & interpretation of the eeporm and files"""
|
|
self._memobj = bitwise.parse(MEM_FORMAT, self._mmap)
|
|
|
|
def get_raw_memory(self, number):
|
|
"""Return a raw representation of the memory object"""
|
|
return repr(self._memobj.memory[number])
|
|
|
|
def _decode_tone(self, val):
|
|
"""Parse the tone data to decode from mem, it returns:
|
|
Mode: (''|DTCS|Tone),
|
|
Value: (None|###),
|
|
Polarity: (None,N,R)"""
|
|
if val.get_raw() == "\xFF\xFF":
|
|
return '', None, None
|
|
|
|
val = int(val)
|
|
if val >= 12000:
|
|
a = val - 12000
|
|
return 'DTCS', a, 'R'
|
|
elif val >= 8000:
|
|
a = val - 8000
|
|
return 'DTCS', a, 'N'
|
|
else:
|
|
a = val / 10.0
|
|
return 'Tone', a, None
|
|
|
|
def _encode_tone(self, memval, mode, value, pol):
|
|
"""Parse the tone data to encode from UI to mem"""
|
|
if mode == '':
|
|
memval[0].set_raw(0xFF)
|
|
memval[1].set_raw(0xFF)
|
|
elif mode == 'Tone':
|
|
memval.set_value(int(value * 10))
|
|
elif mode == 'DTCS':
|
|
flag = 0x80 if pol == 'N' else 0xC0
|
|
memval.set_value(value)
|
|
memval[1].set_bits(flag)
|
|
else:
|
|
raise Exception("Internal error: invalid mode `%s'" % mode)
|
|
|
|
def get_memory(self, number):
|
|
"""Extract a high-level memory object from the low-level
|
|
memory map, This is called to populate a memory in the UI"""
|
|
# Get a low-level memory object mapped to the image
|
|
_mem = self._memobj.memory[number -1 ]
|
|
# Create a high-level memory object to return to the UI
|
|
mem = chirp_common.Memory()
|
|
# number
|
|
mem.number = number
|
|
|
|
# empty
|
|
if _mem.get_raw()[0] == "\xFF":
|
|
mem.empty = True
|
|
return mem
|
|
|
|
# rx freq
|
|
mem.freq = int(_mem.rx_freq) *10
|
|
|
|
# checking if tx freq is empty, this is "possible" on the
|
|
# original soft after a warning, and radio is happy with it
|
|
if _mem.tx_freq.get_raw() == "\xFF\xFF\xFF\xFF":
|
|
mem.duplex = "off"
|
|
mem.offset = 0
|
|
else:
|
|
rx = int(_mem.rx_freq) * 10
|
|
tx = int(_mem.tx_freq) * 10
|
|
if tx == rx:
|
|
mem.offset = 0
|
|
mem.duplex = ""
|
|
else:
|
|
mem.duplex = rx > tx and "-" or "+"
|
|
mem.offset = abs(tx - rx)
|
|
|
|
# tone data
|
|
txtone = self._decode_tone(_mem.tx_tone)
|
|
rxtone = self._decode_tone(_mem.rx_tone)
|
|
chirp_common.split_tone_decode(mem, txtone, rxtone)
|
|
|
|
# Extra setting group, FD-268 don't uset it at all
|
|
# FD-288's do?
|
|
mem.extra = RadioSettingGroup("extra", "Extra")
|
|
busy = RadioSetting("Busy", "Busy Channel Lockout",
|
|
RadioSettingValueBoolean(
|
|
bool(_mem.busy_lock)))
|
|
mem.extra.append(busy)
|
|
scramble = RadioSetting("Scrambler", "Scrambler Option",
|
|
RadioSettingValueBoolean(
|
|
bool(_mem.scrambler)))
|
|
mem.extra.append(scramble)
|
|
|
|
# return mem
|
|
return mem
|
|
|
|
def set_memory(self, mem):
|
|
"""Store details about a high-level memory to the memory map
|
|
This is called when a user edits a memory in the UI"""
|
|
# Get a low-level memory object mapped to the image
|
|
_mem = self._memobj.memory[mem.number - 1]
|
|
|
|
# Empty memory
|
|
if mem.empty:
|
|
_mem.set_raw("\xFF" * 16)
|
|
return
|
|
|
|
# freq rx
|
|
_mem.rx_freq = mem.freq / 10
|
|
|
|
# freq tx
|
|
if mem.duplex == "+":
|
|
_mem.tx_freq = (mem.freq + mem.offset) / 10
|
|
elif mem.duplex == "-":
|
|
_mem.tx_freq = (mem.freq - mem.offset) / 10
|
|
elif mem.duplex == "off":
|
|
for i in range(0, 4):
|
|
_mem.tx_freq[i].set_raw("\xFF")
|
|
else:
|
|
_mem.tx_freq = mem.freq / 10
|
|
|
|
# tone data
|
|
((txmode, txtone, txpol), (rxmode, rxtone, rxpol)) = \
|
|
chirp_common.split_tone_encode(mem)
|
|
self._encode_tone(_mem.tx_tone, txmode, txtone, txpol)
|
|
self._encode_tone(_mem.rx_tone, rxmode, rxtone, rxpol)
|
|
|
|
# extra settings
|
|
for setting in mem.extra:
|
|
setattr(_mem, setting.get_name(), setting.value)
|
|
|
|
return mem
|
|
|
|
def get_settings(self):
|
|
"""Translate the bit in the mem_struct into settings in the UI"""
|
|
_mem = self._memobj
|
|
basic = RadioSettingGroup("basic", "Basic")
|
|
work = RadioSettingGroup("work", "Work Mode Settings")
|
|
top = RadioSettings(basic, work)
|
|
|
|
# Basic
|
|
sql = RadioSetting("settings.sql", "Squelch Level",
|
|
RadioSettingValueList(LIST_SQL,
|
|
LIST_SQL[_mem.settings.sql]))
|
|
basic.append(sql)
|
|
|
|
tot = RadioSetting("settings.tot", "Time out timer",
|
|
RadioSettingValueList(LIST_TOT,
|
|
LIST_TOT[_mem.settings.tot]))
|
|
basic.append(tot)
|
|
|
|
power = RadioSetting("settings.power", "Actual Power",
|
|
RadioSettingValueList(POWER_LEVELS,
|
|
POWER_LEVELS[
|
|
_mem.settings.power]))
|
|
basic.append(power)
|
|
|
|
key_lock = RadioSetting("settings.key", "Keyboard Lock",
|
|
RadioSettingValueList(KEY_LOCK,
|
|
KEY_LOCK[_mem.settings.key]))
|
|
basic.append(key_lock)
|
|
|
|
bw = RadioSetting("settings.bw", "Bandwidth",
|
|
RadioSettingValueList(BW,
|
|
BW[_mem.settings.bw]))
|
|
basic.append(bw)
|
|
|
|
powerrank = RadioSetting("settings.powerrank", "Power output adjust",
|
|
RadioSettingValueList(POWER_RANK,
|
|
POWER_RANK[_mem.settings.powerrank]))
|
|
basic.append(powerrank)
|
|
|
|
lamp = RadioSetting("settings.lamp", "LCD Lamp",
|
|
RadioSettingValueBoolean(
|
|
_mem.settings.lamp))
|
|
basic.append(lamp)
|
|
|
|
lamp_auto = RadioSetting("settings.lamp_auto", "LCD Lamp auto on/off",
|
|
RadioSettingValueBoolean(
|
|
_mem.settings.lamp_auto))
|
|
basic.append(lamp_auto)
|
|
|
|
bs = RadioSetting("settings.bs", "Battery Save",
|
|
RadioSettingValueBoolean(
|
|
_mem.settings.bs))
|
|
basic.append(bs)
|
|
|
|
warning = RadioSetting("settings.warning", "Warning Alerts",
|
|
RadioSettingValueBoolean(
|
|
_mem.settings.warning))
|
|
basic.append(warning)
|
|
|
|
monitor = RadioSetting("settings.monitor", "Monitor key",
|
|
RadioSettingValueBoolean(
|
|
_mem.settings.monitor))
|
|
basic.append(monitor)
|
|
|
|
# Work mode settings
|
|
wmset = RadioSetting("settings.wmem", "VFO/MR Mode",
|
|
RadioSettingValueList(W_MODE,
|
|
W_MODE[_mem.settings.wmem]))
|
|
work.append(wmset)
|
|
|
|
active_ch = RadioSetting("settings.active_ch", "Work Channel",
|
|
RadioSettingValueList(ACTIVE_CH,
|
|
ACTIVE_CH[_mem.settings.active_ch]))
|
|
work.append(active_ch)
|
|
|
|
# vfo rx validation
|
|
if _mem.vfo.vrx_freq.get_raw()[0] == "\xFF":
|
|
# if the vfo is not set, the UI cares about the
|
|
# length of the field, so set a default
|
|
LOG.debug("VFO freq not set, setting it to default %s" \
|
|
% self._VFO_DEFAULT)
|
|
vfo = self._VFO_DEFAULT
|
|
else:
|
|
vfo = int(_mem.vfo.vrx_freq) * 10
|
|
|
|
vf_freq = RadioSetting("vfo.vrx_freq", "VFO frequency",
|
|
RadioSettingValueString(0, 10,
|
|
chirp_common.format_freq(vfo)))
|
|
work.append(vf_freq)
|
|
|
|
# shift works
|
|
# VSHIFT = ["None", "-", "+"]
|
|
sset = 0
|
|
if bool(_mem.vfo.shift_minus) == True:
|
|
sset = 1
|
|
elif bool(_mem.vfo.shift_plus) == True:
|
|
sset = 2
|
|
|
|
shift = RadioSetting("shift", "VFO Shift",
|
|
RadioSettingValueList(VSHIFT,
|
|
VSHIFT[sset]))
|
|
work.append(shift)
|
|
|
|
# vfo shift validation if none set it to ZERO
|
|
if _mem.settings.vfo_shift.get_raw()[0] == "\xFF":
|
|
# if the shift is not set, the UI cares about the
|
|
# length of the field, so set to zero
|
|
LOG.debug("VFO shift not set, setting it to zero")
|
|
vfo_shift = 0
|
|
else:
|
|
vfo_shift = int(_mem.settings.vfo_shift) * 10
|
|
|
|
offset = RadioSetting("settings.vfo_shift", "VFO Offset",
|
|
RadioSettingValueString(0, 9,
|
|
chirp_common.format_freq(vfo_shift)))
|
|
work.append(offset)
|
|
|
|
step = RadioSetting("settings.step", "VFO step",
|
|
RadioSettingValueList(STEPF,
|
|
STEPF[_mem.settings.step]))
|
|
work.append(step)
|
|
|
|
# at least for FD-268A/B it doesn't work as stated, so disabled
|
|
# by now
|
|
#scamble = RadioSetting("vfo.scramble", "Scramble",
|
|
#RadioSettingValueList(ONOFF,
|
|
#ONOFF[int(_mem.vfo.scramble)]))
|
|
#work.append(scamble)
|
|
|
|
#busy_lock = RadioSetting("vfo.busy_lock", "Busy Lock out",
|
|
#RadioSettingValueList(ONOFF,
|
|
#ONOFF[int(_mem.vfo.busy_lock)]))
|
|
#work.append(busy_lock)
|
|
|
|
# FD-288 Family ANI settings
|
|
if "FD-288" in self.MODEL:
|
|
ani_mode = RadioSetting("settings.ani_mode", "ANI ID",
|
|
RadioSettingValueList(ANI,
|
|
ANI[_mem.settings.ani_mode]))
|
|
work.append(ani_mode)
|
|
|
|
# it can't be \xFF
|
|
ani_value = str(_mem.settings.ani)
|
|
if ani_value == "\xFF\xFF\xFF":
|
|
ani_value = "200"
|
|
|
|
ani_value = "".join(x for x in ani_value if (int(x) >= 2 and int(x) <= 9))
|
|
|
|
ani = RadioSetting("settings.ani", "ANI (200-999)",
|
|
RadioSettingValueString(0, 3, ani_value))
|
|
work.append(ani)
|
|
|
|
return top
|
|
|
|
def set_settings(self, settings):
|
|
"""Translate the settings in the UI into bit in the mem_struct
|
|
I don't understand well the method used in many drivers
|
|
so, I used mine, ugly but works ok"""
|
|
def _get_shift(obj):
|
|
"""Get the amount of offset in the memmap"""
|
|
shift = 0
|
|
sf = str(obj.settings.vfo_shift)
|
|
if sf[0] != "\xFF":
|
|
shift = int(mobj.settings.vfo_shift) * 10
|
|
return shift
|
|
|
|
def _get_vrx(obj):
|
|
"""Get the vfo rx freq"""
|
|
return int(obj.vfo.vrx_freq) * 10
|
|
|
|
def _update_vtx(o, rx, offset):
|
|
"""Update the Vfo TX mem from the value of rx & the offset"""
|
|
# check the shift sign
|
|
plus = bool(getattr(o, "shift_plus"))
|
|
minus = bool(getattr(o, "shift_minus"))
|
|
|
|
if plus:
|
|
o.vtx_freq = (rx + offset) / 10
|
|
elif minus:
|
|
o.vtx_freq = (rx - offset) / 10
|
|
else:
|
|
o.vtx_freq = rx / 10
|
|
|
|
mobj = self._memobj
|
|
flag = False
|
|
for element in settings:
|
|
if not isinstance(element, RadioSetting):
|
|
self.set_settings(element)
|
|
continue
|
|
|
|
# Let's roll the ball
|
|
if "." in element.get_name():
|
|
# real properties, more or less mapeable
|
|
inter, setting = element.get_name().split(".")
|
|
obj = getattr(mobj, inter)
|
|
value = element.value
|
|
|
|
# test on this cases .......
|
|
if setting in ["sql", "tot", "powerrank", \
|
|
"active_ch", "ani_mode"]:
|
|
value = int(value)
|
|
|
|
# test on this cases .......
|
|
if setting in ["lamp", "lamp_auto", "bs", \
|
|
"warning","monitor"]:
|
|
value = bool(value)
|
|
# warning and bs have a sister setting in LCD
|
|
if setting == "warning" or setting == "bs":
|
|
# aditional setting
|
|
setattr(obj, setting + "1", value)
|
|
|
|
# monitorval: monitor = 0 > monitorval = 0
|
|
# monitorval: monitor = 1 > monitorval = x30
|
|
if setting == "monitor":
|
|
# sister setting in LCD
|
|
if value:
|
|
setattr(obj, "monitorval", 0x30)
|
|
else:
|
|
setattr(obj, "monitorval", 0)
|
|
|
|
# case power
|
|
if setting == "power":
|
|
value = str(value) == "High" and True or False
|
|
|
|
# case key
|
|
# key => auto = 0, manu = 1
|
|
if setting == "key":
|
|
value = str(value) == "Manual" and True or False
|
|
|
|
# case bandwidth
|
|
# bw: 0 = nar, 1 = Wide & must equal bw1
|
|
if setting == "bw":
|
|
value = str(value) == "Wide" and True or False
|
|
# sister attr
|
|
setattr(obj, "bw1", value)
|
|
|
|
# work mem wmem/wvfo
|
|
if setting == "wmem":
|
|
if str(value) == "Memory":
|
|
value = True
|
|
# sister attr
|
|
setattr(obj, "wvfo", not value)
|
|
else:
|
|
value = False
|
|
# sister attr
|
|
setattr(obj, "wvfo", not value)
|
|
|
|
# case step
|
|
# STEPF = ["5", "10", "6.25", "12.5", "25"]
|
|
if setting == "step":
|
|
value = STEPF.index(str(value))
|
|
|
|
# case vrx_freq
|
|
if setting == "vrx_freq":
|
|
value = chirp_common.parse_freq(str(value)) / 10
|
|
# you must calculate the apropiate txfreq from shift and offset
|
|
# verify shift
|
|
shift = _get_shift(mobj)
|
|
# update the tx vfo freq
|
|
_update_vtx(obj, value * 10, shift)
|
|
|
|
# vfo_shift = offset
|
|
if setting == "vfo_shift":
|
|
value = chirp_common.parse_freq(str(value)) / 10
|
|
# you must calculate the apropiate txfreq from shift and offset
|
|
# get vfo rx
|
|
vrx = _get_vrx(mobj)
|
|
# update the tx vfo freq
|
|
_update_vtx(mobj.vfo, vrx, value * 10)
|
|
|
|
# at least for FD-268A/B it doesn't work as stated, so disabled
|
|
# by now, does this work on the fd-288s?
|
|
|
|
## case scramble & busy_lock
|
|
#if setting == "scramble" or setting == "busy_lock":
|
|
#value = bool(ONOFF.index(str(value)))
|
|
|
|
# ani value, only for FD-288
|
|
if setting == "ani":
|
|
if "FD-268" in self.MODEL:
|
|
# 268 doesn't have ani setting
|
|
continue
|
|
else:
|
|
# 288 models, validate the input [200-999] | ""
|
|
# we will left adjust and zero pad to avoid errors
|
|
# between inputs in the UI
|
|
value = str(value).strip().ljust(3, "0")
|
|
value = int(value)
|
|
if value == 0:
|
|
value = 200
|
|
else:
|
|
if value > 999 or value < 200:
|
|
raise errors.InvalidValueError(
|
|
"The ANI value must be between 200 and 999, not %03i" % value)
|
|
|
|
value = str(value)
|
|
|
|
else:
|
|
# Others that are artifact for real values, not than mapeables
|
|
# selecting the values to work
|
|
setting = str(element.get_name())
|
|
value = str(element.value)
|
|
|
|
# vfo_shift
|
|
if setting == "shift":
|
|
# get shift
|
|
offset = _get_shift(mobj)
|
|
# get vfo rx
|
|
vrx = _get_vrx(mobj)
|
|
|
|
# VSHIFT = ["None", "-", "+"]
|
|
if value == "+":
|
|
mobj.vfo.shift_plus = True
|
|
mobj.vfo.shift_minus = False
|
|
elif value == "-":
|
|
mobj.vfo.shift_plus = False
|
|
mobj.vfo.shift_minus = True
|
|
else:
|
|
mobj.vfo.shift_plus = False
|
|
mobj.vfo.shift_minus = False
|
|
|
|
# update the tx vfo freq
|
|
_update_vtx(mobj.vfo, vrx, offset)
|
|
|
|
if setting != "shift":
|
|
setattr(obj, setting, value)
|
|
|
|
@classmethod
|
|
def match_model(cls, filedata, filename):
|
|
match_size = False
|
|
match_model = False
|
|
|
|
# testing the file data size
|
|
if len(filedata) == MEM_SIZE:
|
|
match_size = True
|
|
|
|
# testing the firmware fingerprint, this experimental
|
|
match_model = model_match(cls, filedata)
|
|
|
|
if match_size and match_model:
|
|
return True
|
|
else:
|
|
return False
|
|
|
|
##
|
|
## FD-268 family: this are the original tested models, FD-268B UHF
|
|
## was tested "remotely" just have the 268A at hand to test.
|
|
##
|
|
|
|
@directory.register
|
|
class FD268ARadio(FeidaxinFD2x8yRadio):
|
|
"""Feidaxin FD-268A Radio"""
|
|
MODEL = "FD-268A"
|
|
_range = (136000000, 174000000)
|
|
_VFO_DEFAULT = 145000000
|
|
_IDENT = "\xFF\xEE\x46\xFF"
|
|
|
|
@directory.register
|
|
class FD268BRadio(FeidaxinFD2x8yRadio):
|
|
"""Feidaxin FD-268B Radio"""
|
|
MODEL = "FD-268B"
|
|
_range = (400000000, 470000000)
|
|
_VFO_DEFAULT = 439000000
|
|
_IDENT = "\xFF\xEE\x44\xFF"
|
|
|
|
##
|
|
## FD-288 Family: the only difference from this family to the FD-268's
|
|
## are the ANI settings
|
|
##
|
|
|
|
@directory.register
|
|
class FD288ARadio(FeidaxinFD2x8yRadio):
|
|
"""Feidaxin FD-288A Radio"""
|
|
MODEL = "FD-288A"
|
|
_range = (136000000, 174000000)
|
|
_VFO_DEFAULT = 145000000
|
|
_IDENT = "\xFF\xEE\x4B\xFF"
|
|
|
|
@directory.register
|
|
class FD288BRadio(FeidaxinFD2x8yRadio):
|
|
"""Feidaxin FD-288 Radio"""
|
|
MODEL = "FD-288B"
|
|
_range = (400000000, 470000000)
|
|
_VFO_DEFAULT = 439000000
|
|
_IDENT = "\xFF\xEE\x4C\xFF"
|
|
|
|
#######################################################################$
|
|
# This is are just rough guesses and must be tested with radios
|
|
# on hand to test it, but, playing with FDX-288 soft & chirp
|
|
# it looks promising
|
|
########################################################################
|
|
|
|
#@directory.register
|
|
#class FD160ARadio(FeidaxinFD2x8yRadio):
|
|
#"""Feidaxin FD-160A Radio"""
|
|
#MODEL = "FD-160A"
|
|
#_range = (136000000, 174000000)
|
|
#_VFO_DEFAULT = 145000000
|
|
#_IDENT = "\xFF\xEE\x48\xFF"
|
|
|
|
#@directory.register
|
|
#class FD460ARadio(FeidaxinFD2x8yRadio):
|
|
#"""Feidaxin FD-460A Radio"""
|
|
#MODEL = "FD-460A"
|
|
#_range = (400000000, 470000000)
|
|
#_VFO_DEFAULT = 439000000
|
|
#_IDENT = "\xFF\xEE\x4A\xFF"
|
|
|
|
#@directory.register
|
|
#class FD450ARadio(FeidaxinFD2x8yRadio):
|
|
#"""Feidaxin FD-450A Radio"""
|
|
#MODEL = "FD-450A"
|
|
#_range = (400000000, 470000000)
|
|
#_VFO_DEFAULT = 439000000
|
|
#_IDENT = "\xFF\xEE\x44\xFF"
|