Project

General

Profile

New Model #2169 » fd268.py

beta driver for Feidaxin FD-268A, please make a backup before test it, just fot testing pruposes - Pavel Milanes, 11/16/2015 01:25 PM

 
# 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)
(5-5/7)