Skip to content

Commit

Permalink
Merge pull request #4 from n2qzshce/d878-support
Browse files Browse the repository at this point in the history
Anytone D878 support
  • Loading branch information
n2qzshce authored Feb 16, 2021
2 parents 623df0a + 0233d79 commit ad35ea7
Show file tree
Hide file tree
Showing 13 changed files with 868 additions and 220 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/pyinstaller-build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
name: Build executeable

env:
semver: 1.0.0
semver: 1.1.0
python-version: 3.8

on:
Expand Down
55 changes: 41 additions & 14 deletions INPUTS_OUTPUTS_SYNCING.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,25 +5,45 @@ Please fill out only required info for each radio channel.
Leave unused fields blank. Filling additional columns may cause
unpredictable behavior.

At the moment, all fields are case-sensitive.
At the moment, all fields are case-sensitive and all csv's are required.

###input.csv
| **Column Name** | Description |
|---|---|
|number|The sequence in your radio, starts at 1.|
|name|What you would like to call your channel, unlimited characters|
|medium name| Name (truncated to 8 characters) `FFO ElDorado -> FFO Eldo`
|short name| Name (truncated to 7 characters) `FFO Eldorado -> FFO ELD`
|group id| Channel preset group ID |
|rx freq| Receive frequency |
|rx ctcss| Receive PL tone |
|rx dcs | Receive DCS code |
|rx dcs invert | `True` or `False` if your tx DCS code is inverted |
|tx offset| Difference from receive channel, in MHz ex:`0.6` |
|tx ctcss | Transmit PL tone|
|tx dcs invert | `True` or `False` if your tx DCS code is inverted |
|digital timeslot | DMR Timeslot, should be either 1 or 2 |
|digital color | Color parameter of DMR channel |
|tx power| Low, Medium, High |
|medium_name| Name (truncated to 8 characters) `FFO ElDorado -> FFO Eldo`
|short_name| Name (truncated to 7 characters) `FFO Eldorado -> FFO ELD`
|zone_id| Channel preset zone ID |
|rx_freq| Receive frequency |
|rx_ctcss| Receive PL tone |
|rx_dcs | Receive DCS code |
|rx_dcs_invert | `True` or `False` if your tx DCS code is inverted |
|tx power| Low, Medium, High transmit power |
|tx_offset| Difference from receive channel, in MHz ex:`0.6` |
|tx_ctcss | Transmit PL tone|
|tx_dcs_invert | `True` or `False` if your tx DCS code is inverted |
|digital_timeslot | DMR Timeslot, should be either 1 or 2 |
|digital_color | Color parameter of DMR channel |
|digital_contact_id | Contact to call on DMR channel |
###zones.csv
| **Column Name** | Description |
|---|---|
|number| Number of the zone, starting at 1|
|name| Zone name |
###dmr_id.csv
| **Column Name** | Description |
|---|---|
|number | number of DMR ID, starting at 1|
|radio_id | ID number from radioid.net
|name | nickname of DMR ID|
###digital_contacts.csv
| **Column Name** | Description |
|---|---|
|number|Number of talkgroup/zone, starting at 1|
|digital_id|Talkgroup or contact ID|
|name|Name of contact|
|call_type|Either `group` or `all`

### Yaesu FTM-400
This is the "RT Systems" branded sync software.
Expand All @@ -39,6 +59,11 @@ This is the "RT Systems" branded sync software.
1. Import baofeng.csv
1. Final user touchup.

### Anytone 878
1. Create/open your rdt file.
1. Import Channel, Radio ID List, Zone, Talk Groups CSVs
1. Final user touchup

***
# Known radio issues:

Expand All @@ -48,3 +73,5 @@ This is the "RT Systems" branded sync software.
* The `step` column is not parsed correctly by the RT app.
###Baofeng:
* `power` column does not sync.
###Anytone 878:
* Talkaround not supported.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ Currently supported radios:
* Default output: Digests the input, should expose errors
* Baofeng uploads via CHiRP
* Yaesu FTM400 via RT systems application
* Anytone D8787 via D878UV software

### How it works
This is a command line tool that will get you setting up your radios as quickly
Expand Down
31 changes: 31 additions & 0 deletions ham/data_column.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import logging

from ham import radio_types


class DataColumn:
_alias_names = dict()
_fmt_val = None
_shape = None

def __init__(self, fmt_name=None, fmt_val=None, shape=None):
if shape is None:
logging.error("Shape is null! Bad things will happen.")
self._fmt_val = fmt_val
self._alias_names = dict()
self._alias_names[radio_types.DEFAULT] = fmt_name
self._shape = shape

def set_alias(self, style, name):
self._alias_names[style] = name

def get_alias(self, style):
if style not in self._alias_names.keys():
logging.error(f"Cannot find alias for style: `{style}` of `{self._alias_names[radio_types.DEFAULT]}`")
return self._alias_names[style]

def fmt_val(self, none_val=None):
if self._fmt_val == '':
return none_val

return self._shape(self._fmt_val)
82 changes: 82 additions & 0 deletions ham/dmr/dmr_contact.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
from ham import radio_types
from ham.data_column import DataColumn


class DmrContact:
@classmethod
def create_empty(cls):
cols = dict()
cols['number'] = ''
cols['digital_id'] = ''
cols['name'] = ''
cols['call_type'] = ''
return DmrContact(cols)

def __init__(self, cols):
self.number = DataColumn(fmt_name='number', fmt_val=cols['number'], shape=int)
self.number.set_alias(radio_types.D878, 'No.')

self.radio_id = DataColumn(fmt_name='digital_id', fmt_val=cols['digital_id'], shape=int)
self.radio_id.set_alias(radio_types.D878, 'Radio ID')

self.name = DataColumn(fmt_name='name', fmt_val=cols['name'], shape=str)
self.name.set_alias(radio_types.D878, 'Name')

self.call_type = DataColumn(fmt_name='call_type', fmt_val=cols['call_type'], shape=str)
self.call_type.set_alias(radio_types.D878, 'Call Type')
return

def headers(self, style):
switch = {
radio_types.DEFAULT: self._headers_default,
radio_types.D878: self._headers_d878,
}

return switch[style]()

def output(self, style):
switch = {
radio_types.DEFAULT: self._output_default,
radio_types.D878: self._output_d878,
}

return switch[style]()

def _headers_default(self):
output = ''
output += f"{self.number.get_alias(radio_types.DEFAULT)},"
output += f"{self.radio_id.get_alias(radio_types.DEFAULT)},"
output += f"{self.name.get_alias(radio_types.DEFAULT)},"
output += f"{self.call_type.get_alias(radio_types.DEFAULT)},"
return output

def _output_default(self):
output = ''
output += f"{self.number.fmt_val()},"
output += f"{self.radio_id.fmt_val()},"
output += f"{self.name.fmt_val()},"
output += f"{self.call_type.fmt_val()},"
return output

def _headers_d878(self):
output = ''
output += f"{self.number.get_alias(radio_types.D878)},"
output += f"{self.radio_id.get_alias(radio_types.D878)},"
output += f"{self.name.get_alias(radio_types.D878)},"
output += f"{self.call_type.get_alias(radio_types.D878)},"
output += f"Call Alert,"
return output

def _output_d878(self):
call_type = 'All Call'
if self.call_type.fmt_val() == 'group':
call_type = 'Group Call'

output = ''
output += f"{self.number.fmt_val()},"
output += f"{self.radio_id.fmt_val()},"
output += f"{self.name.fmt_val()},"
output += f"{call_type},"
output += f"None,"
return output

67 changes: 67 additions & 0 deletions ham/dmr/dmr_id.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
from ham import radio_types
from ham.data_column import DataColumn


class DmrId:
@classmethod
def create_empty(cls):
cols = dict()
cols['number'] = ''
cols['radio_id'] = ''
cols['name'] = ''
return DmrId(cols)

def __init__(self, cols):
self.number = DataColumn(fmt_name='number', fmt_val=cols['number'], shape=int)
self.number.set_alias(radio_types.D878, 'No.')

self.radio_id = DataColumn(fmt_name='radio_id', fmt_val=cols['radio_id'], shape=int)
self.radio_id.set_alias(radio_types.D878, 'Radio ID')

self.name = DataColumn(fmt_name='name', fmt_val=cols['name'], shape=str)
self.name.set_alias(radio_types.D878, 'Name')

def headers(self, style):
switch = {
radio_types.DEFAULT: self._headers_default,
radio_types.D878: self._headers_d878,
}

return switch[style]()

def output(self, style):
switch = {
radio_types.DEFAULT: self._output_default,
radio_types.D878: self._output_d878
}

return switch[style]()

def _headers_default(self):
output = ''
output += f"{self.number.get_alias(radio_types.DEFAULT)},"
output += f"{self.radio_id.get_alias(radio_types.DEFAULT)},"
output += f"{self.name.get_alias(radio_types.DEFAULT)},"
return output

def _output_default(self):
output = ''
output += f"{self.number.fmt_val()},"
output += f"{self.radio_id.fmt_val()},"
output += f"{self.name.fmt_val()},"
return output

def _headers_d878(self):
output = ''
output += f"{self.number.get_alias(radio_types.D878)},"
output += f"{self.radio_id.get_alias(radio_types.D878)},"
output += f"{self.name.get_alias(radio_types.D878)},"
return output

def _output_d878(self):
output = ''
output += f"{self.number.fmt_val()},"
output += f"{self.radio_id.fmt_val()},"
output += f"{self.name.fmt_val()},"
return output

117 changes: 117 additions & 0 deletions ham/radio_additional.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
import logging

from ham import radio_types
from ham.dmr.dmr_contact import DmrContact
from ham.dmr.dmr_id import DmrId
from ham.radio_zone import RadioZone


class RadioAdditionalData:
def __init__(self, dmr_ids, digital_contacts, zones):
self.dmr_ids = dmr_ids
self.digital_contacts = digital_contacts
self.zones = zones
return

def output(self, style):
switch = {
radio_types.DEFAULT: self._output_default,
radio_types.BAOFENG: lambda x: False,
radio_types.FTM400: lambda x: False,
radio_types.D878: self._output_d878,
}

return switch[style](style)

def _output_default(self, style):
self._output_radioids_default(style)
self._output_contacts_default(style)
self._output_zones_default(style)

def _output_radioids_default(self, style):
if self.dmr_ids is None:
logging.error(f"No DMR ids found for {style}.")
return
radio_id_file = open(f'out/{style}/{style}_radioid.csv', 'w+')

headers = DmrId.create_empty()
radio_id_file.write(headers.headers(style) + '\n')
for dmr_id in self.dmr_ids.values():
radio_id_file.write(dmr_id.output(style) + '\n')

radio_id_file.close()
return

def _output_contacts_default(self, style):
if self.digital_contacts is None:
logging.error(f"No digital contacts found for {style}.")
return

dmr_contact_file = open(f'out/{style}/{style}_contacts.csv', 'w+')

headers = DmrContact.create_empty()
dmr_contact_file.write(headers.headers(style) + '\n')
for dmr_contact in self.digital_contacts.values():
dmr_contact_file.write(dmr_contact.output(style) + '\n')

dmr_contact_file.close()

def _output_d878(self, style):
self._output_radioids_d878(style)
self._output_contacts_d878(style)
self._output_zones_d878(style)

def _output_radioids_d878(self, style):
if self.dmr_ids is None:
logging.error(f"No DMR ids found for {style}.")
return
radio_id_file = open(f'out/{style}/{style}_radioid.csv', 'w+')

headers = DmrId.create_empty()
radio_id_file.write(headers.headers(style) + '\n')
for dmr_id in self.dmr_ids.values():
radio_id_file.write(dmr_id.output(style) + '\n')

radio_id_file.close()
return

def _output_contacts_d878(self, style):
if self.digital_contacts is None:
logging.error(f"No digital contacts found for {style}.")
return

dmr_contact_file = open(f'out/{style}/{style}_talkgroup.csv', 'w+')

headers = DmrContact.create_empty()
dmr_contact_file.write(headers.headers(style) + '\n')
for dmr_contact in self.digital_contacts.values():
dmr_contact_file.write(dmr_contact.output(style) + '\n')

dmr_contact_file.close()

def _output_zones_default(self, style):
if self.zones is None:
logging.error(f"No zones list found for {style}.")
return

zone_file = open(f'out/{style}/{style}_zone.csv', 'w+')
headers = RadioZone.create_empty()
zone_file.write(headers.headers(style) + '\n')
for zone in self.zones.values():
zone_file.write(zone.output(style) + '\n')

zone_file.close()

def _output_zones_d878(self, style):
if self.zones is None:
logging.error(f"No zones list found for {style}.")
return

zone_file = open(f'out/{style}/{style}_zone.csv', 'w+')
headers = RadioZone.create_empty()
zone_file.write(headers.headers(style) + '\n')
for zone in self.zones.values():
zone_file.write(zone.output(style) + '\n')

zone_file.close()

Loading

0 comments on commit ad35ea7

Please sign in to comment.