DevelopersAdd a Radio » History » Version 28
Dan Smith, 11/03/2024 05:58 PM
1 | 21 | Dan Smith | {{>toc}} |
---|---|---|---|
2 | 9 | Dan Smith | # How to add a new radio driver |
3 | 1 | Tom Hayward | |
4 | 25 | Alexandre J. Raymond | You've got a radio that's not in CHIRP, a little bit of software development experience, and you want to add that radio to CHIRP. Here's what you'll need: |
5 | 1 | Tom Hayward | |
6 | 23 | Dan Smith | * a subscription to the [developers mailing list](https://lists.chirpmyradio.com/postorius/lists/developers.lists.chirpmyradio.com/) |
7 | 9 | Dan Smith | * a Python development environment |
8 | 1 | Tom Hayward | * radio |
9 | * programming cable |
||
10 | |||
11 | 22 | Dan Smith | ## Do NOT just copy a driver |
12 | |||
13 | 27 | Dan Smith | If you are working on a driver that is very similar to another one, please do **not** just copy that file to a new one and make a bunch of small changes. There's an example of that below, but it's instructive for a small driver, not a best practice for something you want to actually have accepted into the tree. Doing so copies bugs and duplicates code that will otherwise need maintenance in the future. For example, during the massive Python 2->3 effort, the development team (those of which participated in that effort) ended up making the same change to the same copied code in many (many) drivers. That made the effort much larger and longer than it would have otherwise been. |
14 | 22 | Dan Smith | |
15 | If your driver is not completely new, please respect the efforts of the people who have been maintaining this codebase for 15 years and help keep our jobs easier. If you need to refactor an existing driver to improve the ability to re-use and subclass it, by all means, do that. |
||
16 | |||
17 | 9 | Dan Smith | ## Setting up the development environment |
18 | 1 | Tom Hayward | |
19 | 20 | Dan Smith | First you need a full development environment. Instructions are here: [[DevelopersPython3Environment]] |
20 | 1 | Tom Hayward | |
21 | 9 | Dan Smith | ## Look over some existing code |
22 | 1 | Tom Hayward | |
23 | Read source:chirp/drivers/template.py. The whole thing. |
||
24 | |||
25 | This serves as a starting point for understanding the structure of a driver for a radio that operates in "clone mode", which is by far the most common type of radio and CHIRP driver. If your radio will operate in "live mode", and especially if your radio uses the CAT protocol, you should probably start by reading source:chirp/drivers/kenwood_live.py. |
||
26 | |||
27 | 17 | Dan Smith | ## Clone vs. Live Mode |
28 | |||
29 | Most radios seem to have a mode for which memory can be read or written as a large block, and their drivers reflect that. These are called "clone mode" drivers and this approach should be taken if the radio supports it. A few radios can only be accessed on what is effectively memory-by-memory reads/writes. These radios will almost surely need to be written as “live mode”. An example driver is at `chirp/chirp/drivers/kenwood_live.py`. Several other Kenwood drivers inherit from that one (at least `ts480.py`, `tmd710.py`, `ts2000.py`.) These drivers do not download a full copy of the radio all at once, but are expected to fetch memories from the radio at the time `get_memory()` is called from the GUI. |
||
30 | |||
31 | 18 | Dan Smith | ## Write the download/upload routines (clone mode) |
32 | 1 | Tom Hayward | |
33 | 9 | Dan Smith | ### Sniffing the protocol used by existing software |
34 | 1 | Tom Hayward | |
35 | 9 | Dan Smith | If you have other programming software for your radio, such as the software from the manufacturer, you can sniff the serial programming protocol with [Portmon](http://technet.microsoft.com/en-us/sysinternals/bb896644.aspx). Typically, radio programming software is written for Windows, and Portmon is a Windows utility. |
36 | 1 | Tom Hayward | |
37 | 9 | Dan Smith | For recent versions of Windows, you might try sniffing USB with [USBPcap](http://desowin.org/usbpcap/). The captured data is analysed with any recent version of [Wireshark](http://www.wireshark.org). |
38 | 1 | Tom Hayward | |
39 | Start with a read. |
||
40 | 4 | Aaron P | |
41 | 9 | Dan Smith | 1. Click Read in the official software |
42 | 1. Watch the output of Portmon after you click Read |
||
43 | 1. Look for a line that sets the BAUD RATE. Sometimes there are a few of them. |
||
44 | 1. Look for a WRITE line with a few data bytes. This is probably the command to tell the radio to start sending data. |
||
45 | 1. Look for READ lines with lots of data bytes after them. This is probably the memory contents of the radio being downloaded |
||
46 | 1. You're a smart developer. You should be able to figure out the protocol from here! |
||
47 | 1 | Tom Hayward | |
48 | You can also sniff a many Windows programs by running them in the Wine environment under Linux. Manufacturers' radio programming software is likely to run in this environment. See [[DevelopersUSB Sniffing in Linux]]. |
||
49 | |||
50 | 25 | Alexandre J. Raymond | ### Using existing CHIRP code |
51 | 1 | Tom Hayward | |
52 | 28 | Dan Smith | If the radio you're developing for doesn't have existing programming software, you won't be able to use the above reverse engineering technique. Most radio manufacturers use a common protocol across most of their radios. Because CHIRP supports so many manufacturers, there's a chance an existing radio driver has a download routine that will work with your new radio, or at least be similar. This is especially true with Yaesu radios (other than the FT-4/FT-65 family), which don't really even have a protocol. They just dump their memory. All you need to figure out is the baud rate and the memory size (and baud rate is usually 9600). |
53 | 1 | Tom Hayward | |
54 | 28 | Dan Smith | Choose a radio driver that you think might be similar and read the code. |
55 | 25 | Alexandre J. Raymond | |
56 | 1 | Tom Hayward | |
57 | 24 | Alexandre J. Raymond | ## Map the memory blob |
58 | 1 | Tom Hayward | |
59 | To map the memory layout, you're going to make a small change on the radio, do a download, then look for differences. Do this over and over until you have the whole memory layout mapped. |
||
60 | |||
61 | Here's a little helper script I use for the comparisons: |
||
62 | |||
63 | 9 | Dan Smith | `hexdiff.sh:` |
64 | |||
65 | ``` |
||
66 | 1 | Tom Hayward | #!/bin/sh |
67 | |||
68 | A=`mktemp` |
||
69 | B=`mktemp` |
||
70 | |||
71 | hexdump -C $1 > $A |
||
72 | hexdump -C $2 > $B |
||
73 | diff $A $B | less |
||
74 | rm $A $B |
||
75 | 9 | Dan Smith | ``` |
76 | 1 | Tom Hayward | |
77 | 9 | Dan Smith | 1. Find the first channel |
78 | 1. Find the second channel |
||
79 | 1. The offset between the two is your memory channel struct size |
||
80 | 1. Find the last channel. Hopefully its offset is struct size * advertised number of channels away from the first channel! |
||
81 | 1 | Tom Hayward | |
82 | You'll probably start off with something like this: |
||
83 | 9 | Dan Smith | ``` |
84 | 1 | Tom Hayward | MEM_FORMAT = """ |
85 | #seekto 0xb00; |
||
86 | struct { |
||
87 | u32 freq; |
||
88 | u8 unknown1; |
||
89 | u8 unknown2; |
||
90 | u8 unknown3; |
||
91 | char name[8]; |
||
92 | } memory[200]; |
||
93 | """ |
||
94 | 9 | Dan Smith | ``` |
95 | 1 | Tom Hayward | |
96 | 9 | Dan Smith | `0xb00` is the location of the first memory channel. |
97 | `200` is the number of channels the radio supports. |
||
98 | `unknown1`, `unknown2`, and `unknown3` are for memory-specific settings (e.g., tone) that you haven't sussed out yet. |
||
99 | 1 | Tom Hayward | |
100 | 10 | Dan Smith | ## Write `get_memory()` |
101 | 1 | Tom Hayward | |
102 | 11 | Dan Smith | This is called to get your driver to populate the CHIRP memory model with the data for one radio memory. On startup, it’s called once for each memory register you’ve delimited for your radio and once for each special memory. Later it will be called as needed. |
103 | |||
104 | * `get_memory()` returns a populated CHIRP memory object that the driver creates as a new instance of the chirp_common.Memory structure. You’ll want to become familiar with the fields in the Memory structure (presently specified in the source tree in the source file `chirp/chirp_common.py`). |
||
105 | |||
106 | * The number of memories is specified in `chirp_common.RadioFeatures.memory_bounds`, which is a pair of numbers (first memory number, last memory number.) `get_memory()` will be called with an integer parameter for regular memories with a value up to the last memory number. |
||
107 | |||
108 | * Special memories are radio-specific channels that can hold special meaning in the radio (such as scan limits, home/call channel, skip channels, etc.) They are specified in your driver as members of the list that you set at `chirp_common.RadioFeatures.valid_special_chans`. `get_memory()` will be called with a string containing the name of the special channel, and you must set the `Memory.number` field of the resulting memory object to an index that is greater than the last memory register number and different than other special memories without holes in the sequence. Subsequent calls to `get_memory()` may receive either the index or the name. |
||
109 | |||
110 | Do read the example driver at `chirp/chirp/drivers/template.py`. There is a commented stub version of this routine in `chirp/chirp.common.py`. Your driver is expected to override it. |
||
111 | |||
112 | 9 | Dan Smith | ## Write `set_memory()` |
113 | 1 | Tom Hayward | |
114 | 12 | Dan Smith | This is called whenever CHIRP wants your driver to set a memory channel according to the fields described in the specific `chirp_common.Memory` parameter. The driver will presumably need to determine the specific place in the radio's memory based upon the value of `memory.number` (e.g. integer less than or greater than last memory number.) The driver should not change the memory structure. |
115 | |||
116 | There is no return value from `set_memory()`. |
||
117 | |||
118 | Do read the example driver at `chirp/chirp/drivers/template.py`. There is a commented stub version of this routine in `chirp/chirp.common.py`. Your driver is expected to overload it. |
||
119 | |||
120 | 13 | Dan Smith | ## Per-memory extra settings |
121 | 12 | Dan Smith | |
122 | 13 | Dan Smith | CHIRP can (optionally) manage and display other settings for each memory. These are stored in `chirp_common.Memory.extra`, and are handled by your driver during the `get_memory()` and `set_memory()` by setting or honoring the `extra` field, which should be a `settings.RadioSettingGroup` object. |
123 | 12 | Dan Smith | |
124 | 16 | Dan Smith | ## Register your driver |
125 | |||
126 | CHIRP has a “directory” structure of all the radios it should know about. Your driver will have one or more `@directory.register` statements, wherein a class is defined for each radio you’re working on. They can inherit from each other (even other models and vendors) once the classes are read by CHIRP. The directory is sorted by text (vendor, model) pairs. |
||
127 | |||
128 | ## Write get_settings() |
||
129 | |||
130 | Your driver class will have definitions (and some redefinitions) for your radio. It will also implement a function `get_features()` which returns a `chirp_common.RadioFeatures()` object. This contains information about your radio so that CHIRP knows the basic attributes. Any existing driver should have one for you to copy and modify. CHIRP calls this often and from pretty much everywhere, so it must be lightweight. This method should not be querying the radio or even digging deep into memory each time it’s called. |
||
131 | |||
132 | 9 | Dan Smith | ## Add an image of your radio to the tree |
133 | 1 | Tom Hayward | |
134 | 9 | Dan Smith | Save it to `tests/images/Vendor_Model.img` |
135 | 1 | Tom Hayward | |
136 | 9 | Dan Smith | ## Run tests |
137 | 1 | Tom Hayward | |
138 | 9 | Dan Smith | Make sure all the tests pass before you submit: |
139 | |||
140 | ```shell |
||
141 | $ tox |
||
142 | ``` |
||
143 | |||
144 | 15 | Dan Smith | See [[DevelopersPython3Environment]] for more examples of running tests. All the tests must pass before your code can be merged. |
145 | 14 | Dan Smith | |
146 | 26 | Alexandre J. Raymond | ## Submit code to GitHub |