New Model #2169 » feidaxing_fd-268a_v0.1.patch
/dev/null Thu Jan 01 00:00:00 1970 +0000 → chirp/drivers/fd268.py Wed Nov 11 14:22:25 2015 -0500 | ||
---|---|---|
# 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/>.
|
||
import struct
|
||
import os
|
||
import logging
|
||
import unittest
|
||
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,
|
||
scramble: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 0x07C0;
|
||
struct {
|
||
u8 unknown22:5,
|
||
bw1:1,
|
||
bs1:1,
|
||
warning1:1;
|
||
u8 sql[1];
|
||
u8 monitorval;
|
||
u8 tot[1];
|
||
u8 unknown23[4];
|
||
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;
|
||
"""
|
||
######################
|
||
# Radio settigns NOTES
|
||
######################
|
||
#
|
||
# sql: 0..9
|
||
# tot: is val * 10; 0 = OFF (01 = 10; x00..x09 )
|
||
#
|
||
# power, lamp, lamp_auto. wmem, wvfo, moni: 1 on / 0 off;
|
||
# warning, bs : 1 on / 0 off;
|
||
#
|
||
# key => auto = 0, manu = 1
|
||
#
|
||
# model is reversed [...7013P] = P3107...
|
||
# active_ch is CH - 1 (CH15 = x0e)
|
||
# monitorval: moni=0 > monitorval = 0 ; moni=1 > monitorval = x30
|
||
# warning must equal warning1
|
||
# bw: 0 = nar, 1 = Wide & must equal bw1
|
||
# battery save (bs) must equal bs1
|
||
# VFO
|
||
# shift_minus 0 = null; 1 = "-" + update the TX freq. in VFO
|
||
# shift_pos 0 = null; 1 = "+" + update the TX freq. in VFO
|
||
#
|
||
# Simplex
|
||
# vfo_shift: no care
|
||
# shift_minus = 0
|
||
# shift_pos = 0
|
||
#
|
||
# - shift
|
||
# vfo_shift: set + tx vfo updated
|
||
# shift_minus = 1
|
||
# shift_pos = 0
|
||
#
|
||
# + shift
|
||
# vfo_shift: set + tx vfo updated
|
||
# shift_minus = 0
|
||
# shift_pos = 1
|
||
MEM_SIZE=0x0800
|
||
CMD_ACK = "\x06"
|
||
BLOCK_SIZE = 0x08
|
||
POWER_LEVELS = ["Low", "High"]
|
||
LIST_SQL = ["Off", "1", "2", "3", "4", "5", "6", "7", "8", "9"]
|
||
LIST_TOT = ["Off", "10", "20", "30", "40", "50", "60", "70", "80", "90"]
|
||
BS = ["Off", "On"]
|
||
LAMP = BS + ["Auto"]
|
||
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 = ["Off", "-", "+"]
|
||
# Settings are disabled by now, it's a work in progress
|
||
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 send(radio, frame, data = ""):
|
||
"""Generic send data to the radio"""
|
||
raw_send(radio, frame)
|
||
if data != "":
|
||
raw_send(radio, data)
|
||
ack = raw_recv(radio, 1)
|
||
if ack != CMD_ACK:
|
||
raise errors.RadioError("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 ACK
|
||
send(radio, CMD_ACK)
|
||
ack = raw_recv(radio, 1)
|
||
if ack != CMD_ACK:
|
||
raise errors.RadioError(
|
||
"Radio did not send ack to the last command")
|
||
return data
|
||
def do_program(radio):
|
||
"""Feidaxin program mode and identification dance"""
|
||
def do_magic():
|
||
"""Try to get the radio in program mode, this is standard
|
||
procedure of the factory software (FDX-288) from factory,
|
||
we try 8 times to get the correct response, if not work
|
||
try again a few times"""
|
||
# UI information
|
||
status = chirp_common.Status()
|
||
status.cur = 0
|
||
status.max = 8
|
||
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, 8):
|
||
# 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:
|
||
raise errors.RadioError(
|
||
"Radio did not accept program mode, try again.")
|
||
# now we request identification
|
||
send(radio, "M")
|
||
send(radio, "\x02")
|
||
ident = raw_recv(radio, 8)
|
||
# WARNING !!!!
|
||
#
|
||
# radio identify it self in different modes:
|
||
# - hardware reset: "\xFF" * 8
|
||
# - FDX-288 soft from clean template: "P3107" + "\x00" * 3
|
||
# - Mine, in original image has: "P2107" + "\x00" * 3
|
||
#
|
||
# I tested writing "FD-268A " to the ident zone and the radio
|
||
# and FDX-288 software works ok, seems they don't care about it.
|
||
#
|
||
# I need to compare others radio's imgs to determine what to do.
|
||
#
|
||
# Seems that ident has a link with the soft used to program it.
|
||
# It's like FDX-288 soft does not make any try to correctly
|
||
# ident the radio using the ident mechanism.
|
||
#
|
||
# So we ignore ident by now, user must care about it.
|
||
# final ACK
|
||
send(radio, CMD_ACK)
|
||
ack = raw_recv(radio, 1)
|
||
if ack != CMD_ACK:
|
||
raise errors.RadioError("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)
|
||
class FeidaxinFD268xRadio(chirp_common.CloneModeRadio):
|
||
"""Feidaxin FD-268x Radio"""
|
||
VENDOR = "Feidaxin"
|
||
MODEL = "FD-268x"
|
||
BAUD_RATE = 9600
|
||
_memsize = MEM_SIZE
|
||
_upper = 99
|
||
@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 = False # not finished yet
|
||
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_tmodes = ['', 'Tone', 'TSQL', 'DTCS', 'Cross']
|
||
rf.valid_cross_modes = chirp_common.CROSS_MODES
|
||
#rf.valid_power_levels = POWER_LEVELS
|
||
rf.valid_bands = [self._range]
|
||
rf.memory_bounds = (1, self._upper)
|
||
return rf
|
||
def sync_in(self):
|
||
"""Do a download of the radio eeprom"""
|
||
self._mmap = do_download(self)
|
||
self.process_mmap()
|
||
def sync_out(self):
|
||
"""Do an upload to the radio eeprom"""
|
||
do_upload(self)
|
||
def process_mmap(self):
|
||
"""Process the memory objet, mainly used on file load"""
|
||
self._memobj = bitwise.parse(MEM_FORMAT, self._mmap)
|
||
def get_raw_memory(self, number):
|
||
"""Return a raw representation of the memory object, which
|
||
is very helpful for development"""
|
||
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)"""
|
||
val = int(val)
|
||
if val == 16665:
|
||
return '', None, None
|
||
elif 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':
|
||
if pol == 'N':
|
||
memval.set_value(int(value) + 8000)
|
||
else:
|
||
cent = (value / 100) + 192
|
||
memval.set_value(int(value))
|
||
memval[1].set_raw(cent)
|
||
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.freq = 0
|
||
mem.empty = True
|
||
return mem
|
||
# freq + offset + duplex
|
||
mem.freq = int(_mem.rx_freq) *10
|
||
offset = (int(_mem.tx_freq) * 10) - mem.freq
|
||
if offset < 0:
|
||
mem.offset = abs(offset)
|
||
mem.duplex = "-"
|
||
elif offset > 0:
|
||
mem.offset = offset
|
||
mem.duplex = "+"
|
||
else:
|
||
mem.offset = 0
|
||
# tone data
|
||
rxtone = txtone = None
|
||
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
|
||
mem.extra = RadioSettingGroup("all", "All Settings")
|
||
busy = RadioSetting("Busy", "Busy Channel Lockout",
|
||
RadioSettingValueBoolean(bool(_mem.busy_lock)))
|
||
mem.extra.append(busy)
|
||
scramble = RadioSetting("Scramble", "Scrambler Option",
|
||
RadioSettingValueBoolean(bool(_mem.scramble)))
|
||
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
|
||
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"""
|
||
self._mem = self._memobj
|
||
basic = RadioSettingGroup("basic", "Basic")
|
||
work = RadioSettingGroup("work", "Work Mode Settings")
|
||
top = RadioSettings(basic, work)
|
||
# Basic
|
||
sql = RadioSetting("sql", "Squelch Level",
|
||
RadioSettingValueList(LIST_SQL,
|
||
LIST_SQL[_mem.settings.sql]))
|
||
basic.append(sql)
|
||
tot = RadioSetting("tot", "Time out timer",
|
||
RadioSettingValueList(LIST_TOT,
|
||
LIST_TOT[_mem.settings.tot]))
|
||
basic.append(tot)
|
||
power = RadioSetting("power", "Power",
|
||
RadioSettingValueList(POWER_LEVELS,
|
||
POWER_LEVELS[_mem.settings.power]))
|
||
basic.append(power)
|
||
key_lock = RadioSetting("key_lock", "Keyboard Lock",
|
||
RadioSettingValueList(KEY_LOCK,
|
||
KEY_LOCK[_mem.settings.key]))
|
||
basic.append(key_lock)
|
||
bw = RadioSetting("bw", "Bandwidth",
|
||
RadioSettingValueList(BW,
|
||
BW[_mem.settings.bw]))
|
||
basic.append(bw)
|
||
lamp = RadioSetting("lamp", "LCD Lamp",
|
||
RadioSettingValueBoolean(
|
||
self._mem.settings.lamp))
|
||
basic.append(lamp)
|
||
lamp_auto = RadioSetting("lamp_auto", "LCD Lamp Automatic",
|
||
RadioSettingValueBoolean(
|
||
self._mem.settings.lamp_auto))
|
||
basic.append(lamp_auto)
|
||
bs = RadioSetting("bs", "Battery Save",
|
||
RadioSettingValueBoolean(
|
||
self._mem.settings.bs))
|
||
basic.append(bs)
|
||
warning = RadioSetting("warning", "Warning Alerts",
|
||
RadioSettingValueBoolean(
|
||
self._mem.settings.warning))
|
||
basic.append(warning)
|
||
monitor = RadioSetting("monitor", "Monitor key",
|
||
RadioSettingValueBoolean(
|
||
self._mem.settings.monitor))
|
||
basic.append(monitor)
|
||
# Work mode settings
|
||
wmset = RadioSetting("wmset", "VFO/MR Mode",
|
||
RadioSettingValueList(W_MODE,
|
||
W_MODE[_mem.settings.wmem]))
|
||
work.append(wmset)
|
||
active_ch = RadioSetting("active_ch", "Default Channel",
|
||
RadioSettingValueList(ACTIVE_CH,
|
||
ACTIVE_CH[_mem.settings.active_ch]))
|
||
work.append(active_ch)
|
||
vf_freq = RadioSetting("vfo_freq", "VFO frequency",
|
||
RadioSettingValueString(0, 7,
|
||
str(int(_mem.vfo.vrx_freq) / 100000.0000)))
|
||
work.append(vf_freq)
|
||
sset = 0
|
||
if _mem.vfo.shift_minus == 1:
|
||
sset = 1
|
||
elif _mem.vfo.shift_plus == 1:
|
||
sset = 2
|
||
shift = RadioSetting("shift", "VFO Shift",
|
||
RadioSettingValueList(VSHIFT,
|
||
VSHIFT[sset]))
|
||
work.append(shift)
|
||
offset = RadioSetting("offset", "VFO Offset",
|
||
RadioSettingValueString(0, 7,
|
||
str(int(_mem.settings.vfo_shift) / 100000.0000)))
|
||
work.append(offset)
|
||
step = RadioSetting("step", "VFO step",
|
||
RadioSettingValueList(STEPF,
|
||
STEPF[_mem.settings.step]))
|
||
work.append(step)
|
||
return top
|
||
def set_settings(self, settings):
|
||
"""Translate the settings in the UI into bit in the mem_struct"""
|
||
# work in progress, wait for it
|
||
pass
|
||
@directory.register
|
||
class FeidaxinFD268ARadio(FeidaxinFD268xRadio):
|
||
"""Feidaxin FD-268A Radio"""
|
||
MODEL = "FD-268A"
|
||
_range = (136000000, 174000000)
|
||
# FD-268B UHF: This radio can have the same layout...
|
||
# I need a img to confirm and validate, see
|
||
# http://chirp.danplanet.com/issues/2169
|
||
#
|
||
#@directory.register
|
||
#class FeidaxinFD268BRadio(FeidaxinFD268xRadio):
|
||
# """Feidaxin FD-268B Radio"""
|
||
# MODEL = "FD-268B"
|
||
# _range = (400000000, 470000000)
|