# HG changeset patch # User Terence Paddack terence.paddack@gmail.com # Date 1528162132 18000 # Mon Jun 04 20:28:52 2018 -0500 # Node ID af8ce534bb949d2289c6f9b66650b333ea6e7ac5 # Parent 0cbedd969bad9e0635e58d138266add5edbf2779 A module that provides Chirp functionalities to custom python scripts #5859
diff -r 0cbedd969bad -r af8ce534bb94 README.chirpAPI --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/README.chirpAPI Mon Jun 04 20:28:52 2018 -0500 @@ -0,0 +1,204 @@ +readme.chirpAPI +Copyright 2018 Terence C. Paddack (KF5TOK) + +Introduction +chirpAPI is a Python library that contains functions that can be used to carry out many of the operations performed by Chirp that are usually only available in chirpW, which is the GUI version. It was inspired by chirpc -- the Chirp command line interface -- but has been rewritten from scratch to be an importable module rather than a completely self-contained command line interface. Since this is not a stand-alone program, it doesn't entirely replace chirpc. Instead, it is intended to provide application programmers with an interface to a number of the Python modules that Chirp uses to work its magic. chirpAPI is therefore, an Application Programming Interface (API). The remainder of this document will serve to provide examples of how chirpAPI can be used for this purpose. I will begin by demonstrating its basic functionality in the Python console. Once you have mastered the basics, you may well find that chirpAPI can serve as a suitable alternative to chirpc until somebody (maybe you) can incorporate it into chirpc or another similar command line interface someday. + +General Features of chirpAPI +The chirpAPI file is extensively commented with information about the purpose of each function, the required arguments (and data types of those arguments) of each function, usage examples of each function, and additional comments pertaining to the unique quirks of each function; I tried to provide enough information to allow you to incorporate these functions into your own custom python scripts. There are several different type of functions in the chirpAPI that can be classified according to how they work. + +The following functions extract data from Chirp libraries or your radio memory map and store them in a list for later inspection. For each of these functions, the output can be used by your Python script to make decisions, or it can be printed using the print_list function; an example will be provided under the section of this document entitled 'Using the chirpAPI -- A Tutorial'. + + list_ports + list_all_radios + list_vendors + list_radios + list_setting_groups + list_setting_options + list_settings + list_valid_modes + list_valid_bands + list_power_levels + list_tuning_steps + list_mem + +The following functions print data (into the Python console or command line terminal) from Chirp libraries or your radio memory map. The last three of these functions are special cases that require more specific arguments; they have been denoted with an asterisk (*). For example, print_mem_list is intended to be used in situations where print_mem does not show all of the necessary information to determine if the memory map has been properly edited by another function; print_mem_list requires the output from list_mem as an argument. The print_list function requires ouput from one of the list_ functions above as an argument. The print_search_results function is used to display the results from the search_settings function, so output from search_settings is required for print_search_results to function properly. + + print_all_radios + print_all_mem + print_settings + print_mem + print_power_levels + + print_mem_list* + print_list* + print_search_results* + +The following functions manipulate the memory map image file, and have been divided into three groups. All of these functions have been written to return a value of 0 indicating success or a value of 1 indicating failure. The first group in this section includes functions that download and upload memory map images. The second group in this section includes functions that manipulate entire memory channels. The third group of functions in the section alter specific attributes of a memory channel and save the changes to the memory map image. + + download_mmap + upload_mmap + + copy_mem_channel + erase_mem_channel + move_mem_channel + + set_mem_freq + set_mem_offset + set_mem_duplex + set_mem_name + set_mem_mode + set_mem_tenc + set_mem_tsql + set_mem_dtcsenc + set_mem_dtcssql + set_mem_dtcspol + set_mem_cross_tone + set_mem_tuning_step + set_mem_power + set_mem_skip + +The following functions were included in the API to reduce the number of lines of redundant code in the above-listed functions; they are not intended for use outside of this scope, but they could come in handy if you intend to step through a sequence of code in this API in the Python console for debugging purposes (hopefully you will not need to do that). + + setup_global_vars + write_memory + +The following funcions can be used to identify and edit a radio's other settings. search_settings can be used to find information that will allow you to use edit_setting. Once that information has been obtained, it can be hard-coded into your script; the only time the information needed to change a particular setting is likely to change is if the radio's driver is updated to reflect a change in supported radio settings. An example of the use of these two functions is provided under the section of this document entitled 'Using the chirpAPI -- A Tutorial'. + + search_settings + edit_setting + +Getting Started - Linux +Chirp can be installed on the Raspberry Pi or other Linux distros by following the instructions under the heading 'Other Linux Users' at the URL below. If you have already installed Chirp, and you're reading this file as part of that installation, you can proceed to the 'Using the chirpAPI -- A Tutorial' section of this document. Otherwise, the following page has instructions on how to install the latest daily build of Chirp on your system: + https://chirp.danplanet.com/projects/chirp/wiki/Download#Other-Linux-Users + +Getting Started - Windows +chirAPI can be used in Windows, but it is not necessarily for the faint of heart. Currently, chirpAPI requires Python 2.7.x (where x is the latest latest minor version) to be installed on your system; it will not work properly in Python 3. If you are using Windows, you will probably need install Python yourself. Here is a URL: + https://www.python.org/downloads/windows/ + +Next, to use chirpAPI in Windows, you will need to set up a Chirp development environment. Instructions can be found at the following URL: + https://chirp.danplanet.com/projects/chirp/wiki/DevelopersWin32Environment + +Once you have cloned the repository you can proceed to the tutorial below. + +Using the chirpAPI -- A Tutorial +Once you have Chirp and python successfully installed, open a command prompt and navigate to the directory where Chirp is installed. Among other files, chirpAPI and chirpw should be in that directory. Next, open a python console in your command prompt (if you are running Linux, you might need to do this as root since chirpAPI will be doing file I/O). If you are following along with the examples, from this point on, type the indented text into your python console (press the <Enter> key after each line). + +In Windows: + python + from chirpAPI import * + +In Linux: + sudo python + from chirpAPI import * + +Before you can download the memory map of your radio, you will need to find the exact, case-sensitive, name of your radio model as it is listed in Chirp. There are a number of ways to do this, but we will start by printing a list of all supported radio models: + print_all_radios() + +Scroll through the list of radios and find your model. You will need to type it into some of the downstream commands exactly as it appears in this list. Alternatively, you could store that string value in an intermediate variable to reduce keystrokes. In the examples below, I will be programming the ubiquitous Baofeng UV-5R. If you are using a different radio, substitute the string corresponding to your radio model in the line below: + model = 'Baofeng_UV-5R' + +Next, we will need to find the port to which your radio is connected. If your computer has more than one port in use, it may be difficult to know which one represents your radio programming cable. In this situation, I have had some luck with the following procedure. Leave your radio programming cable disconnected and enter the following: + temp = list_ports() + print_list(temp) + +Next, ensure that your radio is connected properly by following the same sequence of steps that you would normally follow when using the GUI version of Chirp. Enter the previous lines again, and note which new port appears. + temp = list_ports() + print_list(temp) + +Since I am using a Raspberry Pi to walk through this tutorial, my port may differ from yours. If you are using Windows, your port may follow the format COMx, where x is some integer value. Once you know which port to use, substitute that string value into the line below and proceed: + port = '/dev/ttyUSB0' + +Soon, we will download a memory map image from your radio. It will require you to input the name of the target image file to which the memory map will be saved. The following will store the name of your memory map file in an intermediate variable. I find this format to be useful. + image = 'Baofeng_UV-5R.img' + download_mmap(model, port, image) + +Next, we will visually inspect the memory map image: + print_all_mem(image) + +Now we can add a new channel for a hypothetical repeater on 146.860 MHz at channel 50 (if successful, the memory channel contents should print followed by the return value of 0 on the next line): + set_mem_freq(image, 146860000, 50) + +Now we can set a frequency offset of -600 KHz (again, the printed output should reflect our change to the memory channel): + set_mem_offset(image, -600000, 50) + +We can set a PL tone of 100.0 to channel 50 as follows: + set_mem_tenc(image, 100.0, 50) + +Let's assume the repeater is very close to our home QTH, so we want to transmit on low power. In order to figure out what our power options are use the following (this command prints the valid power levels for our radio model in dBm): + print_power_levels(image) + +According to the printed output, for the UV-5R, the lowest valid option is 30 dBm, so let's set that value to channel 50 now (printed output from this command should show 'Low' at the end of the new line of printed text): + set_mem_power(image, 30, 50) + + +Maybe we'd like to program the name of this repeater into the memory channel. If you want to know the maximum name-length supported by your radio you can use: + temp = get_valid_namelen(image) + print temp + +For the UV-5R the maximum name-length is 7 characters, so we want to keep our imput under that value. If we use a longer value, the following function will cut it down to the maximum length allowed. Observe: + set_mem_name(image, 'Way too long of a name', 50) + +You may also notice from the output printed after the last command that if your radio does not support lower-case characters (like the UV-5R) the name gets capitalized before it's saved to the memory channel. Let's try a better name this time: + set_mem_name(image, 'My-ARC', 50) + +What about all that QRM being generated by that other repeater on the same frequency just slightly not far enough away? Luckily, that repeater uses a different set of tones, so we can squelch out the interfering transmissions. For that, we need to use a different function than the one we used to set the PL tone. This one sets our PL tone and tone squelch tone simultaneously: + set_mem_cross_tone(image, 100.0, 100.0, 50) + +That last function can also set up CTCSS tones and/or DTCS codes in any combination. I suppose that could be useful in special situations. Now is a good time to mention that set_mem_cross_tone is not supported by all radio models. There are a few other functions in the API like that. In each case, I have tried to include printed output that explains the situation when it occurs. + +Here we will set our radio to transmit a DTCS code of 271 and use of CTCSS tone of 123.0 to open the squelch. Let's give it a try: + set_mem_cross_tone(image, 271, 123.0, 50) + +That's great and all, but what if our hypothetical repeater system requires that DTCS tone to have an inverted (or reversed) polarity. Here's how that can be accomplished: + set_mem_dtcspol(image, 'RN', 50) + +What if we want to adjust the carrier squelch level of our radio? Sure, that's something we can do in the Chirp GUI environment, by clicking on the 'Settings' tab, but can the chirpAPI handle such a tall order? As luck would have it, yes! First, a few words about radio settings: Chirp stores radio settings in an object, whose structure varies (based on radio drivers) from one radio model to the next. Have you ever noticed that different radios have different setting options, and that they are grouped in different ways in the Chirp GUI? I thought you might have. Since that's the case, our program can't always depend upon those settings being located in the same place in Chirp's data structure. In fact, the setting we're looking for may not even be there at all! So how do we find it, if it's even in there? I'm glad you asked. The next function searches the entire radio settings object for a keyword that is present in the setting's option description. Since we're looking for 'squelch', that's the search term we'll use. Depending on the number of matches and the size of the radio settings data structure, this command may take a number of seconds: + temp = search_settings(image, 'squelch') + +If you're following along on your UV-5R, you may have noticed that it did indeed take a little while. You may also have noticed that quite a few matches for the search term 'squelch' were found. Fortunately for me, and hopefully for you too, the one we're interested in is first on the list. The first line of actual results should look something like this: + 0: Basic Settings(0)-->Carrier Squelch Level(0): 9 + ^ ^ ^ ^ + | | | | +This is our result# | | | +Computers like to | | | +start counting at 0 | | | + | | | + This is the setting's | | + group number. We'll | | + need that value later. | | + | | + | | + This is the setting's | + option number. We'll | + need that too. | + This is the setting's + current value. Yours + may differ. + +A squelch level of 9 is kinda high, huh? Maybe we should set it lower in case we're on a simplex channel and want to hear more distant stations. Sure, we could do that pretty easily in the Baofeng's menu, but where's the fun it that? After all, if you've read this far down, you probably genuinely want to know how this thing works, right? The next function uses the group number and option number as a kind of address to know which setting it's supposed to change. Let's say that we want to set the squelch to 4: + edit_setting(image, 4, 0, 0) + +In case you were wondering, the first 0 is for the group number, and the second 0 is for the option number. If you're like me, you're more interested in programming with the chirpAPI than you are using it as a slow, typing-intensive way to program your radio. That being the case, you'll need more information than what I could realistically provide in this file. Currently, the best way to get the details is to open up chirpAPI in your favorite Python editor (or just a basic text-editor) and read the comments. There are plenty of them. + +Let's say you want to move the contents of our experimental memory channel to another memory channel location...maybe 25. That can be accomplished as follows: + move_mem_channel(image, 50, 25) + +Now, say you want to make a duplicate of it in channel 26: + copy_mem_channel(image, 25, 26) + print_all_mem(image) + +Maybe you want to delete channel 25, after all: + erase_mem_channel(image, 25) + print_all_mem(image) + +At this point, you can inspect the memory map image by opening the file in the GUI version of Chirp. It should, more or less, confirm that our changes have, in fact, been saved. If you look really closely, you may notice a few minor differences between what you see and what you expect to see based on the source code and output of the chirAPI; I know I have. It appears that once the commands from chirpAPI are sent to the radio's driver, it can sometimes interpret them a little differently to accomodate the radio's capabilities. In all cases where I have noticed such discrepancies, none of them resulted in unexpected changes in the function of the radio; your mileage may vary, so if you notice something not working properly, double-check it, triple-check it, then...bug report. I can't promise that I will personally be able to fix it in a timely manner -- I don't always have 2+ weeks to devote to full-time chirpAPI development -- but that's the beauty of the free software system; anybody can fix it -- even you. + +Assuming everything looks like it's supposed to and you are happy with the results, you can upload your memory map image file to your radio as follows: + upload_mmap(model, port, image) + +The speed of reading and writing memory maps can vary based on how large the memory map is, how fast the connection is between your radio and computer, and (I suspect) a variety of environmental factors. I have noticed that my bigger, fancier Chinese radios take much longer than my smaller, cheaper Chinese radios, so take that into consideration if you're looking to build a Raspberry Pi-controlled remote base for your home-brew repeater. + +I hope this tutorial has been useful in introducing you to the chirpAPI. I am excited about the possibilities that it opens up, and I look forward to seeing what you do with it. Remember to read the comments and use this tool responsibly. + +73 de KF5TOK \ No newline at end of file diff -r 0cbedd969bad -r af8ce534bb94 chirpAPI.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/chirpAPI.py Mon Jun 04 20:28:52 2018 -0500 @@ -0,0 +1,1206 @@ +# Copyright 2018 Terence C. Paddack (KF5TOK) +# +# 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 3 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 serial +import collections + +from chirp import chirp_common, directory, settings +from chirp.drivers import * + +RADIOS = directory.DRV_TO_RADIO + +#The following list contains all of the attributes of a memory channel that will be listed by list_mem() +mem_attr = ['number', 'freq', 'name', 'tmode', 'rtone', 'ctone', 'dtcs', 'rx_dtcs', 'dtcs_polarity', 'cross_mode', 'duplex', 'offset', 'mode', 'power', 'skip', 'empty'] + +#Global Variables +rclass = '' +radio = '' +rf = '' + +class search_result(): +#This class is used by search_settings & print_search_results + def __init__(self): + self.query = '' + self.group = [] + self.option = [] + self.setting = [] + self.group_num = [] + self.option_num = [] + +def setup_global_vars(map_file): +#Assigns values to some variables that are used by multiple functions in this library +#Used by other functions in this library; not recommended for use by the end-user +#Required arguments: +# 1: map_file (string containing the name of the memory map file to which the changes will be written ex. 'QYT_KT8900D.img') +#Example Usage: setup_global_vars('QYT_KT8900D.img', mem, channel) +#Example Usage: setup_global_vars('Baofeng_UV-5R.img', mem, channel) +#Example Usage: setup_global_vars('Baofeng_BF-888.img', mem, channel) + ret_val = 1 + if not type(map_file) is str: + print "Type mismatch: map_file must be a string. You entered a '" + str(type(map_file).__name__) + "'" + else: + global rclass + global radio + global rf + rclass = directory.get_radio_by_image(map_file).__class__ + radio = rclass(map_file) + rf = radio.get_features() + ret_val = 0 + return ret_val + +def write_memory(map_file, mem, channel): +#Writes the contents of the memory channel object that it is passed into the memory map; +#Used by other functions in this library; not recommended for use by the end-user +#Required arguments: +# 1: map_file (string containing the name of the memory map file to which the changes will be written ex. 'QYT_KT8900D.img') +# 2: mem (mem class object containing all channel information to be written to the memory map) +# 3: channel (integer corresponding to the channel whose contents will be edited) +#Example Usage: write_memory('TYT_TH-9800.img', mem, channel) +#Example Usage: write_memory('QYT_KT8900D.img', mem, channel) +#Example Usage: write_memory('Baofeng_UV-5R.img', mem, channel) +#Example Usage: write_memory('Baofeng_BF-888.img', mem, channel) +#Comments: +# 1: WARNING: This function, which is used by most of the memory channel-editing functions, is NOT programmed to validate memory map contents! + ret_val = 1 + if not type(map_file) is str: + print "Type mismatch: map_file must be a string. You entered a '" + str(type(map_file).__name__) + "'" + elif not isinstance(mem, chirp_common.Memory): + print "Type mismatch: mem must be a chirp memory object. You entered a '" + str(type(mem).__name__) + "'" + elif not type(channel) is int: + print "Type mismatch: channel must be an int. You entered a '" + str(type(channel).__name__) + "'" + else: + setup_global_vars(map_file) + if mem.empty == True: + mem.empty = False + radio.set_memory(mem) + radio.save_mmap(map_file) + print_mem(map_file, channel) + ret_val = 0 + return ret_val + +def print_all_radios(): +#Prints all currently-supported radios +#Required arguments: none +#Example Usage: print_all_radios() + print "Supported Radios:\n\t", "\n\t".join(sorted(RADIOS.keys())) + +def print_list(input_list): +#Format and prints the contents of most lists generated by functions in this library that start with 'list_' +#Required arguments: +# 1: input_list (list whose contents are to be formatted & printed) +#Example Usage: print_list(temp) + if not isinstance(input_list, list): + print "Type mismatch: input_list must be a list. You entered a '" + str(type(input_list).__name__) + "'" + else: + for i in range(len(input_list)): + print str(i) + ': ' + str(input_list[i]) + +def print_all_mem(map_file): +#Prints the memory channel contents of the specified memory map +#Required arguments: +# 1: map_file (string containing the name of the memory map file to be read ex. 'QYT_KT8900D.img') +#Example Usage: print_all_mem('TYT_TH-9800.img') +#Example Usage: print_all_mem('QYT_KT8900D.img') +#Example Usage: print_all_mem('Baofeng_UV-5R.img') +#Example Usage: print_all_mem('Baofeng_BF-888.img') + if not type(map_file) is str: + print "Type mismatch: map_file must be a string. You entered a '" + str(type(map_file).__name__) + "'" + else: + setup_global_vars(map_file) + start, end = rf.memory_bounds + for i in range(start, end + 1): + mem = radio.get_memory(i) + if mem.empty: + continue + print str(mem) + ' ' + str(mem.power) + +def print_settings(map_file, group_num): +#Prints the settings and options associated with the specified group of settings in the model's memory map file +#Required arguments: +# 1: map_file (string containing the name of the memory map file to be read ex. 'QYT_KT8900D.img') +# 2: group_num (integer corresponding to the group whose keys you want to list; these values are obtained by viewing the list by using list_settings_group(map_file) +#Example Usage: print_settings('QYT_KT8900D.img', 1) +#Example Usage: print_settings('Baofeng_UV-5R.img', 1) +#Example Usage: print_settings('Baofeng_BF-888.img', 0) + if not type(map_file) is str: + print "Type mismatch: map_file must be a string. You entered a '" + str(type(map_file).__name__) + "'" + elif not type(group_num) is int: + print "Type mismatch: group_num must be an integer. You entered a '" + str(type(group_num).__name__) + "'" + else: + options = list_setting_options(map_file, group_num) + settings = list_settings(map_file, group_num) + #Get the length of the longest string in the options list so we can justify the output text and make it look pretty + max_length = 0 + for i in range(len(options)): + length = len(options[i]) + if length > max_length: + max_length = length + #Loop through the lists and print them with some nice(-ish) formatting/text justification + num_digits = len(str(len(options))) + for i in range(len(options)): + print str(i).ljust(num_digits) + ': ' + str(options[i]).rjust(max_length) + ': ' + str(settings[i]) + +def print_mem(map_file, channel): +#Prints the contents of specified memory channel in the specified memory map file +#Required arguments: +# 1: map_file (string containing the name of the memory map file to be read ex. 'QYT_KT8900D.img') +# 2: channel (integer corresponding to the channel whose contents will be printed) +#Example Usage: print_mem('QYT_KT8900D.img', 19) +#Example Usage: print_mem('Baofeng_UV-5R.img', 1) +#Example Usage: print_mem('Baofeng_BF-888.img', 16) +#Bugs: +# 1: Does not display mem.rx_dtcs; this way of printing memory channel contents is a carry-over from chirpc. + if not type(map_file) is str: + print "Type mismatch: map_file must be a string. You entered a '" + str(type(map_file).__name__) + "'" + elif not type(channel) is int: + print "Type mismatch: channel must be an integer. You entered a '" + str(type(channel).__name__) + "'" + else: + setup_global_vars(map_file) + mem = radio.get_memory(channel) + print str(mem) + ' ' + str(mem.power) + +def print_mem_list(mem_list): +#Prints the contents of specified memory channel in the specified memory map file +#Required arguments: +# 1: mem_list (list, generated by list_mem, that contains memory channel information) +#Example Usage: print_mem_list('TYT_TH-9800.img', 20) +#Example Usage: print_mem_list('QYT_KT8900D.img', 19) +#Example Usage: print_mem_list('Baofeng_UV-5R.img', 1) +#Example Usage: print_mem_list('Baofeng_BF-888.img', 16) +#Comments: +# 1: This function prints memory channel information on mulitple lines in more detail than print_mem +# 2: Because it calls list_mem, it is slower than print_mem, but it does not suffer from the same lack of detail +# 3: This could be useful in situations where print_mem does not supply enough information + if not type(mem_list) is list: + print "Type mismatch: mem_list must be a list. You entered a '" + str(type(mem_list).__name__) + "'" + else: + for i in range(len(mem_attr)): + print mem_attr[i] + ': ' + str(mem_list[i]) + +def print_power_levels(map_file): +#Prints the valid power levels (in dBm) for a specified radio model; these values can be used with set_mem_power +#Required arguments: +# 1: map_file (string containing the name of the memory map file to be read ex. 'QYT_KT8900D.img') +#Example Usage: print_power_levels('TYT_TH-9800.img') +#Example Usage: print_power_levels('QYT_KT8900D.img') +#Example Usage: print_power_levels('Baofeng_UV-5R.img') +#Example Usage: print_power_levels('Baofeng_BF-888.img') + if not type(map_file) is str: + print "Type mismatch: map_file must be a string. You entered a '" + str(type(map_file).__name__) + "'" + else: + setup_global_vars(map_file) + lvls = rf.valid_power_levels + for i in range(len(lvls)): + print lvls[i]._power + +def print_search_results(result): +#Formats & prints the contents of a search_result object obtained by using search_settings(map_file, search_str) +#Required arguments: +# 1: result (search_result object obtained by using search_settings function) +#Example Usage: print_search_results(temp) + if not type(result).__name__ == 'instance': + print "Type mismatch: result must be a search_result object. You entered a '" + str(type(result).__name__) + "'" + else: + num_digits = len(str(len(result.group))) + print 'Seach Results for: ' + result.query + print '# Group(group_num)-->Option(option_num): Setting' + for i in range(len(result.group)): + print str(i).rjust(num_digits) + ': ' + str(result.group[i]) + '(' + str(result.group_num[i]) + ')-->' + str(result.option[i]) + '(' + str(result.option_num[i]) + '): ' + str(result.setting[i]) + +def list_ports(): +#Returns a list of all currently-available ports +#Required arguments: none +#Usage example: temp = list_ports() +#Comments: +# 1: This is a hacked-together version of the code in chirp\platform.py. Initially, I had just called the list_serial_ports function in that file, but +# that caused an unexpected (and almost undetected) error when trying to import this API in a python console on a Raspberry Pi via SSH (this works now). + import re + from chirp.platform import win32_comports_bruteforce + from chirp.platform import natural_sorted + try: + from serial.tools.list_ports import comports + platform = 'linux' + except: + platform = 'win32' + if platform is 'linux': + ports = list(comports()) + elif platform is 'win32': + ports = win32_comports_bruteforce() + return natural_sorted([port for port, name, url in ports]) + +def list_all_radios(): +#Returns a list of all currently-supported radios +#Required arguments: none +#Usage example: temp = list_all_radios() + radio_list = [] + for radio in sorted(RADIOS.keys()): + radio_list.append(radio) + return radio_list + +def list_vendors(): +#Returns a list of all currently-supported radio brands +#Required arguments: none +#Example Usage: temp = list_vendors() +#Comments: +# 1: This code was lifted from chirp\ui\clone.py; it has been slightly modified from the original + vendors = collections.defaultdict(list) + for rclass in sorted(directory.DRV_TO_RADIO.values()): + if not issubclass(rclass, chirp_common.CloneModeRadio) and not issubclass(rclass, chirp_common.LiveRadio): + continue + vendors[rclass.VENDOR].append(rclass) + for alias in rclass.ALIASES: + vendors[alias.VENDOR].append(alias) + sorted_vendors = sorted(vendors.keys()) + return sorted_vendors + +def list_radios(vendor): +#Returns a list of all currently-supported radios made by the specified vendor +#Required arguments: +# 1: vendor (string containing the vendor of the radio models you want to list) +#Example Usage: temp = list_radios('TYT') +#Example Usage: temp = list_radios('Baofeng') +#Example Usage: temp = list_radios('MTC') +#Comments: +# 1: This code was lifted from chirp\ui\clone.py; it has been slightly modified from the original + if not type(vendor) is str: + print "Type mismatch: vendor must be a string. You entered a '" + str(type(vendor).__name__) + "'" + else: + models = [] + vendors = collections.defaultdict(list) + for rclass in sorted(directory.DRV_TO_RADIO.values()): + if not issubclass(rclass, chirp_common.CloneModeRadio) and not issubclass(rclass, chirp_common.LiveRadio): + continue + vendors[rclass.VENDOR].append(rclass) + for alias in rclass.ALIASES: + vendors[alias.VENDOR].append(alias) + models = sorted(vendors[vendor]) + radio_models = [x.MODEL for x in models] + return radio_models + +def list_setting_groups(map_file): +#Returns a list of setting groups (strings) associated with a particular model's memory map file +#Required arguments: +# 1: map_file (string containing the name of the memory map file to be read ex. 'QYT_KT8900D.img') +#Example Usage: temp = list_setting_groups('QYT_KT8900D.img') +#Example Usage: temp = list_setting_groups('Baofeng_UV-5R.img') +#Example Usage: temp = list_setting_groups('Baofeng_BF-888.img') + if not type(map_file) is str: + print "Type mismatch: map_file must be a string. You entered a '" + str(type(map_file).__name__) + "'" + else: + setup_global_vars(map_file) + name = [] + groups = radio.get_settings() + for i in range(len(groups)): + name.append(str(groups[i].get_shortname())) + return name + +def list_setting_options(map_file, group_num): +#Returns a list of setting options (strings) associated with a particular group of settings in the model's memory map file +#Required arguments: +# 1: map_file (string containing the name of the memory map file to be read ex. 'QYT_KT8900D.img') +# 2: group_num (integer corresponding to the group whose keys you want to list; these values are obtained by viewing the list from list_settings_group(map_file) +#Example Usage: temp = list_setting_options('QYT_KT8900D.img', 1) +#Example Usage: temp = list_setting_options('Baofeng_UV-5R.img', 1) +#Example Usage: temp = list_setting_options('Baofeng_BF-888.img', 0) + if not type(map_file) is str: + print "Type mismatch: map_file must be a string. You entered a '" + str(type(map_file).__name__) + "'" + elif not type(group_num) is int: + print "Type mismatch: group_num must be an integer. You entered a '" + str(type(group_num).__name__) + "'" + else: + setup_global_vars(map_file) + options = [] + groups = radio.get_settings() + i = 0 + for element in groups[group_num]: + temp = str(element._shortname) + options.append(temp) + return options + +def list_settings(map_file, group_num): +#Returns a list of setting values associated with a particular group of settings in the model's memory map file +#Required arguments: +# 1: map_file (string containing the name of the memory map file to be read ex. 'QYT_KT8900D.img') +# 2: group_num (integer corresponding to the group whose keys you want to list; these values are obtained by viewing the list from list_settings_group(map_file) +#Example Usage: temp = list_settings('QYT_KT8900D.img', 1) +#Example Usage: temp = list_settings('Baofeng_UV-5R.img', 1) +#Example Usage: temp = list_settings('Baofeng_BF-888.img', 0) + if not type(map_file) is str: + print "Type mismatch: map_file must be a string. You entered a '" + str(type(map_file).__name__) + "'" + elif not type(group_num) is int: + print "Type mismatch: group_num must be an integer. You entered a '" + str(type(group_num).__name__) + "'" + else: + setup_global_vars(map_file) + settings = [] + groups = radio.get_settings() + group = groups[group_num] + for i in range(len(group)): + temp = group[group.keys()[i]].values()[0] + settings.append(temp) + return settings + +def list_valid_modes(map_file): +#Returns a list of the valid emission modes for a specified radio model; these values can be used with set_mem_mode +#Required arguments: +# 1: map_file (string containing the name of the memory map file to be read ex. 'QYT_KT8900D.img') +#Example Usage: temp = list_valid_modes('TYT_TH-9800.img') +#Example Usage: temp = list_valid_modes('QYT_KT8900D.img') +#Example Usage: temp = list_valid_modes('Baofeng_UV-5R.img') +#Example Usage: temp = list_valid_modes('Baofeng_BF-888.img') + if not type(map_file) is str: + print "Type mismatch: map_file must be a string. You entered a '" + str(type(map_file).__name__) + "'" + else: + setup_global_vars(map_file) + modes = list(rf.valid_modes) + return modes + +def list_valid_bands(map_file): +#Returns a list of the supported bands of a specified radio model +#Required arguments: +# 1: map_file (string containing the name of the memory map file to be read ex. 'QYT_KT8900D.img') +#Example Usage: temp = list_valid_bands('TYT_TH-9800.img') +#Example Usage: temp = list_valid_bands('QYT_KT8900D.img') +#Example Usage: temp = list_valid_bands('Baofeng_UV-5R.img') +#Example Usage: temp = list_valid_bands('Baofeng_BF-888.img') + if not type(map_file) is str: + print "Type mismatch: map_file must be a string. You entered a '" + str(type(map_file).__name__) + "'" + else: + setup_global_vars(map_file) + bands = list(rf.valid_bands) + return bands + +def list_power_levels(map_file): +#Returns a list of the valid power levels (in dBm) for a specified radio model; these values can be used with set_mem_power +#Required arguments: +# 1: map_file (string containing the name of the memory map file to be read ex. 'QYT_KT8900D.img') +#Example Usage: temp = list_power_levels('QYT_KT8900D.img') +#Example Usage: temp = list_power_levels('Baofeng_UV-5R.img') +#Example Usage: temp = list_power_levels('Baofeng_BF-888.img') + if not type(map_file) is str: + print "Type mismatch: map_file must be a string. You entered a '" + str(type(map_file).__name__) + "'" + else: + setup_global_vars(map_file) + power_levels = [] + lvls = rf.valid_power_levels + for i in range(len(lvls)): + power_levels.append(lvls[i]._power) + return power_levels + +def list_tuning_steps(map_file): +#Returns a list of the tuning steps in KHz for a specified radio model; these values can be used with set_mem_tuning_step +#Required arguments: +# 1: map_file (string containing the name of the memory map file to be read ex. 'QYT_KT8900D.img') +#Example Usage: temp = list_tuning_steps('TYT_TH-9800.img') +#Example Usage: temp = list_tuning_steps('QYT_KT8900D.img') +#Example Usage: temp = list_tuning_steps('Baofeng_UV-5R.img') +#Example Usage: temp = list_tuning_steps('Baofeng_BF-888.img') + if not type(map_file) is str: + print "Type mismatch: map_file must be a string. You entered a '" + str(type(map_file).__name__) + "'" + else: + setup_global_vars(map_file) + tuning_steps = list(rf.valid_tuning_steps) + return tuning_steps + +def list_mem(map_file, channel): +#Returns a list of memory attributes (types vary) of a single channel in the memory map of a radio +#Required arguments: +# 1: map_file (string containing the name of the memory map file to be read ex. 'QYT_KT8900D.img') +# 2: channel (integer corresponding to the channel whose contents will be printed) +#Example Usage: temp = list_mem('QYT_KT8900D.img', 19) +#Example Usage: temp = list_mem('Baofeng_UV-5R.img', 18) +#Example Usage: temp = list_mem('Baofeng_BF-888.img', 1) + if not type(map_file) is str: + print "Type mismatch: map_file must be a string. You entered a '" + str(type(map_file).__name__) + "'" + elif not type(channel) is int: + print "Type mismatch: channel must be an integer. You entered a '" + str(type(channel).__name__) + "'" + else: + setup_global_vars(map_file) + mem = radio.get_memory(channel) + memory_channel = [] + for i in range(len(mem_attr)): + temp = getattr(mem, mem_attr[i]) + memory_channel.append(temp) + return memory_channel + +def download_mmap(radio_model, serial_port, map_file): +#Downloads the memory map of a radio via a serial or USB port and saves it as a .img file +#Required arguments: +# 1: radio_model (string conforming to the model of the radio as defined in list_radios ex. 'QYT_KT8900D') +# 2: serial_port (string referring to the port connected to the radio ex. '/dev/ttyUSB0' in linux or 'COM7' in Windows) +# 3: map_file (string containing the name of the file to be created ex. 'QYT_KT8900D.img') +#Example Usage: download_mmap('TYT_TH-9800', 'COM7', 'TYT_TH-9800.img') +#Example Usage: download_mmap('QYT_KT8900D', 'COM7', 'QYT_KT8900D.img') +#Example Usage: download_mmap('QYT_KT8900D', '/dev/ttyUSB0', 'QYT_KT8900D.img') +#Example Usage: download_mmap('Baofeng_UV-5R', 'COM3', 'Baofeng_UV-5R.img') +#Example Usage: download_mmap('Baofeng_BF-888', 'COM3', 'Baofeng_BF-888.img') + ret_val = 1 + if not type(radio_model) is str: + print "Type mismatch: radio_model must be a string. You entered a '" + str(type(radio_model).__name__) + "'" + elif not type(serial_port) is str: + print "Type mismatch: serial_port must be an string. You entered a '" + str(type(serial_port).__name__) + "'" + elif not type(map_file) is str: + print "Type mismatch: map_file must be a string. You entered a '" + str(type(map_file).__name__) + "'" + else: + rclass = directory.get_radio(radio_model) + s = serial.Serial(port=serial_port, baudrate=rclass.BAUD_RATE, timeout=0.5) + radio = rclass(s) + radio.sync_in() + radio.save_mmap(map_file) + print "Download Successful!" + ret_val = 0 + return ret_val + +def upload_mmap(radio_model, serial_port, map_file): +#Uploads the memory map into the radio via a serial or USB port +#Required arguments: +# 1: radio_model (string conforming to the model of the radio as defined in list_radios ex. 'QYT_KT8900D') +# 2: serial_port (string referring to the port connected to the radio ex. '/dev/ttyUSB0' in linux or 'COM7' in Windows) +# 3: map_file (string containing the name of the file to be uploaded ex. 'QYT_KT8900D.img') +#Example Usage: upload_mmap('TYT_TH-9800', 'COM7', 'TYT_TH-9800.img') +#Example Usage: upload_mmap('QYT_KT8900D', 'COM7', 'QYT_KT8900D.img') +#Example Usage: upload_mmap('Baofeng_UV-5R', 'COM3', 'Baofeng_UV-5R.img') +#Example Usage: upload_mmap('Baofeng_BF-888', 'COM3', 'Baofeng_BF-888.img') + ret_val = 1 + if not type(radio_model) is str: + print "Type mismatch: radio_model must be a string. You entered a '" + str(type(radio_model).__name__) + "'" + elif not type(serial_port) is str: + print "Type mismatch: serial_port must be an string. You entered a '" + str(type(serial_port).__name__) + "'" + elif not type(map_file) is str: + print "Type mismatch: map_file must be a string. You entered a '" + str(type(map_file).__name__) + "'" + else: + rclass = directory.get_radio(radio_model) + s = serial.Serial(port=serial_port, baudrate=rclass.BAUD_RATE, timeout=0.5) + radio = rclass(s) + radio.load_mmap(map_file) + radio.sync_out() + print 'Upload Successful!' + ret_val = 0 + return ret_val + +def copy_mem_channel(map_file, source, destination): +#Copies the contents of the source memory channel into the destination memory channel of the given memory map image file +#Required arguments: +# 1: map_file (string containing the name of the memory map file to be read ex. 'QYT_KT8900D.img') +# 2: source (integer corresponding to the channel whose contents will be copied to another channel) +# 3: destination (integer corresponding to the destination channel into which source's contents will be copied) +#Example Usage: copy_mem_channel('TYT_TH-9800.img', 20, 22) +#Example Usage: copy_mem_channel('QYT_KT8900D.img', 1, 10) + ret_val = 1 + if not type(map_file) is str: + print "Type mismatch: map_file must be a string. You entered a '" + str(type(map_file).__name__) + "'" + elif not type(source) is int: + print "Type mismatch: source must be an integer. You entered a '" + str(type(source).__name__) + "'" + elif not type(destination) is int: + print "Type mismatch: destination must be an integer. You entered a '" + str(type(destination).__name__) + "'" + else: + setup_global_vars(map_file) + mem = radio.get_memory(source) + mem.number = destination + radio.set_memory(mem) + radio.save_mmap(map_file) + ret_val = 0 + return ret_val + +def erase_mem_channel(map_file, channel): +#Erases the contents of the specified memory channel in the specified memory map file +#Required arguments: +# 1: map_file (string containing the name of the memory map file to be read ex. 'QYT_KT8900D.img') +# 2: source (integer corresponding to the channel whose contents will be erased) +#Example Usage: erase_mem_channel('TYT_TH-9800.img', 22) +#Example Usage: erase_mem_channel('QYT_KT8900D.img', 20) +#Example Usage: erase_mem_channel('Baofeng_UV-5R.img', 20) +#Example Usage: erase_mem_channel('Baofeng_BF-888.img', 20) + ret_val = 1 + if not type(map_file) is str: + print "Type mismatch: map_file must be a string. You entered a '" + str(type(map_file).__name__) + "'" + elif not type(channel) is int: + print "Type mismatch: channel must be an integer. You entered a '" + str(type(channel).__name__) + "'" + else: + setup_global_vars(map_file) + mem = radio.get_memory(channel) + mem.empty = True + radio.set_memory(mem) + radio.save_mmap(map_file) + ret_val = 0 + return ret_val + +def move_mem_channel(map_file, source, destination): +#Moves the contents of the source memory channel into the destination memory channel of the given memory map image file +#Required arguments: +# 1: map_file (string containing the name of the memory map file to be read ex. 'QYT_KT8900D.img') +# 2: source (integer corresponding to the channel whose contents will be moved to another channel) +# 3: destination (integer corresponding to the destination channel into which source's contents will be moved) +#Example Usage: move_mem_channel('TYT_TH-9800.img', 20, 21) +#Example Usage: move_mem_channel('QYT_KT8900D.img', 10, 1) + ret_val = 1 + if not type(map_file) is str: + print "Type mismatch: map_file must be a string. You entered a '" + str(type(map_file).__name__) + "'" + elif not type(source) is int: + print "Type mismatch: source must be an integer. You entered a '" + str(type(source).__name__) + "'" + elif not type(destination) is int: + print "Type mismatch: destination must be an integer. You entered a '" + str(type(destination).__name__) + "'" + else: + setup_global_vars(map_file) + copy_mem_channel(map_file, source, destination) + erase_mem_channel(map_file, source) + print_all_mem(map_file) + ret_val = 0 + return ret_val + +def get_valid_namelen(map_file): +#Returns an integer containing the maximum length of a memory channel name supported by the radio model +#Required arguments: +# 1: map_file (string containing the name of the memory map file to be read ex. 'QYT_KT8900D.img') +#Example Usage: get_valid_namelen('TYT_TH-9800.img') +#Example Usage: get_valid_namelen('QYT_KT8900D.img') +#Example Usage: get_valid_namelen('Baofeng_UV-5R.img') +#Example Usage: get_valid_namelen('Baofeng_BF-888.img') + if not type(map_file) is str: + print "Type mismatch: map_file must be a string. You entered a '" + str(type(map_file).__name__) + "'" + else: + setup_global_vars(map_file) + valid_length = rf.valid_name_length + return valid_length + +def search_settings(map_file, search_str): +#Searches the radio's settings for options that contain the search_str; returns a nested list object containing the results of the search +#Required arguments: +# 1: map_file (string containing the name of the memory map file to be read ex. 'QYT_KT8900D.img') +# 2: search_str (string containing the text to find in the settings ex. 'squelch') +#Example Usage: temp = search_settings('TYT_TH-9800.img', 'squelch') +#Example Usage: temp = search_settings('QYT_KT8900D.img', 'squelch') +#Example Usage: temp = search_settings('Baofeng_UV-5R.img', 'squelch') +#Example Usage: temp = search_settings('Baofeng_BF-888.img', 'squelch') +#Comments: +# 1: Chirp stores radio settings in a RadioSettingGroup object, which varies (based on radio drivers) from one model to the next. +# 2: Output from this function contains (among other things) the 'address' for a particular setting. +# The 'address' values are found in parentheses after the Group Name and Option Name, respectively. +# 3: This search is not case sensitive, and appropriate search terms can be found by examining the main window-pane in the 'Settings' tab in the Chirp GUI. + if not type(map_file) is str: + print "Type mismatch: map_file must be a string. You entered a '" + str(type(map_file).__name__) + "'" + elif not type(search_str) is str: + print "Type mismatch: search_str must be a string. You entered a '" + str(type(search_str).__name__) + "'" + else: + setup_global_vars(map_file) + result = search_result() + result.query = search_str + groups = list_setting_groups(map_file) + for group_num in range(len(groups)): + options = list_setting_options(map_file, group_num) + settings = list_settings(map_file, group_num) + for option_num in range(len(options)): + temp = options[option_num].upper() + if search_str.upper() in temp: + result.group.append(groups[group_num]) + result.option.append(options[option_num]) + result.setting.append(settings[option_num]) + result.group_num.append(group_num) + result.option_num.append(option_num) + print_search_results(result) + return result + +def edit_setting(map_file, value, group_num, option_num): +#Edits the setting specified by option_num in the group specified by group_num in the radio's memory map +#Feed the output from search_settings into print_search_results to see the group_num and option_num for the setting you want to edit +#Required arguments: +# 1: map_file (string containing the name of the memory map file to be read ex. 'QYT_KT8900D.img') +# 2: value (value that you want to store in the setting...make sure the type is correct) +# 2: group_num (integer representing the group number that contains the desired options) +# 3: option_num (integer representing the option that corresponds to the desired setting) +#Example Usage: edit_setting('TYT_TH-9800.img', 4, 0, 1) #set beep volume to 4 +#Example Usage: edit_setting('QYT_KT8900D.img', 6, 0, 1) #set carrier squelch to 6 +#Example Usage: edit_setting('Baofeng_UV-5R.img', 8, 0, 0) #set carrier squelch to 8 +#Example Usage: edit_setting('Baofeng_BF-888.img', 5, 0, 13) #set carrier squelch to 5 +#Comments: +# 1: Chirp stores radio settings in a RadioSettingGroup object, which varies (based on radio drivers) from one model to the next +# 2: The last two arguments fed into this function serve as an address for a particular setting; these arguments can be found by examining output from the +# print_search_results function. The argument values are found in parentheses after the Group Name and Option Name, respectively. + ret_val = 1 + if not type(map_file) is str: + print "Type mismatch: map_file must be a string. You entered a '" + str(type(map_file).__name__) + "'" + elif not type(group_num) is int: + print "Type mismatch: group_num must be an integer. You entered a '" + str(type(group_num).__name__) + "'" + elif not type(option_num) is int: + print "Type mismatch: option_num must be an integer. You entered a '" + str(type(option_num).__name__) + "'" + else: + setup_global_vars(map_file) + groups = radio.get_settings() + group = groups[group_num] + setting = group[group.keys()[option_num]].values()[0] + #Get the data type of the specified option + setting_type = type(setting.get_value()).__name__ + #Check settings's data type against value's data type + if not setting_type == type(value).__name__: + print "Type mismatch: value must be a '" + setting_type + "' type value. You entered a '" + type(value).__name__ + "'" + else: + setting.set_value(value) + radio.set_settings(groups) + radio.save_mmap(map_file) + #Everything from here down can be removed if you don't want to print confirmation output; removing the code below should increase speed + #Generate a search_result object and print it + groups = list_setting_groups(map_file) + options = list_setting_options(map_file, group_num) + settings = list_settings(map_file, group_num) + result = search_result() + result.query = options[option_num] + result.group.append(groups[group_num]) + result.option.append(options[option_num]) + result.setting.append(settings[option_num]) + result.group_num.append(group_num) + result.option_num.append(option_num) + print_search_results(result) + #Keep the line below, though + ret_val = 0 + return ret_val + +def set_mem_freq(map_file, frequency, channel): +#Programs a frequency into a channel in the radio's memory map +#Required arguments: +# 1: map_file (string containing the name of the memory map file to be read ex. 'QYT_KT8900D.img') +# 2: frequency (integer containing the frequency in Hz to be copied into the memory map) +# 3: channel (integer corresponding to the channel whose contents will be edited) +#Example Usage: set_mem_freq('TYT_TH-9800.img', 29600000, 20) +#Example Usage: set_mem_freq('QYT_KT8900D.img', 146520000, 20) +#Example Usage: set_mem_freq('Baofeng_UV-5R.img', 146520000, 20) +#Example Usage: set_mem_freq('Baofeng_BF-888.img', 446000000, 1) +#Comments: +# 1: This function does not check to see if the frequency falls within the radio's supported bands; this is by intention on the part of this code's author. +# This allows for extended reception as well as experimentation to advance your technical skills in the radio art. The onus is (and has always been) on you, +# the licensed amateur to use this code responsibly, ethically, and in keeping with your country's rules and the highest standards of the ham radio community. +# Transmission outside of the radio's manufacturer-approved bands may result in damage to your radio or the generation of (potentially-unlawful) spurious emissions. + ret_val = 1 + if not type(map_file) is str: + print "Type mismatch: map_file must be a string. You entered a '" + str(type(map_file).__name__) + "'" + elif not type(frequency) is int: + print "Type mismatch: frequency must be an integer. You entered a '" + str(type(frequency).__name__) + "'" + elif not type(channel) is int: + print "Type mismatch: channel must be an integer. You entered a '" + str(type(channel).__name__) + "'" + else: + setup_global_vars(map_file) + mem = radio.get_memory(channel) + mem.freq = frequency + write_memory(map_file, mem, channel) + ret_val = 0 + return ret_val + +def set_mem_offset(map_file, offset, channel): +#Programs an Rx/Tx frequency offset into a channel in the radio's memory map +#Required arguments: +# 1: map_file (string containing the name of the memory map file to be read ex. 'QYT_KT8900D.img') +# 2: offset (integer containing the offset in Hz to be copied into the memory map) +# 3: channel (integer corresponding to the channel whose contents will be edited) +#Example Usage: set_mem_offset('TYT_TH-9800.img', 0, 20) +#Example Usage: set_mem_offset('QYT_KT8900D.img', -600000, 19) +#Example Usage: set_mem_offset('Baofeng_UV-5R.img', 600000, 20) +#Example Usage: set_mem_offset('Baofeng_BF-888.img', 5000000, 1) + ret_val = 1 + if not type(map_file) is str: + print "Type mismatch: map_file must be a string. You entered a '" + str(type(map_file).__name__) + "'" + elif not type(offset) is int: + print "Type mismatch: offset must be an integer. You entered a '" + str(type(offset).__name__) + "'" + elif not type(channel) is int: + print "Type mismatch: channel must be an integer. You entered a '" + str(type(channel).__name__) + "'" + else: + setup_global_vars(map_file) + mem = radio.get_memory(channel) + if offset < 0: + #print("Negative shift detected!") + shift_dir = '-' + elif offset > 0: + #print("POSITIVE shift detected!") + shift_dir = '+' + elif offset == 0: + shift_dir = '' + mem.duplex = shift_dir + mem.offset = abs(offset) + write_memory(map_file, mem, channel) + ret_val = 0 + return ret_val + +def set_mem_duplex(map_file, tx_freq, rx_freq, channel): +#Programs the Tx and Rx frequency duplex pair into a specified channel in the specified radio memory map +#Required arguments: +# 1: map_file (string containing the name of the memory map file to be read ex. 'QYT_KT8900D.img') +# 2: tx_freq (integer containing the transmit frequency in Hz to be copied into the memory map) +# 3: rx_freq (integer containing the receive frequency in Hz to be copied into the memory map) +# 4: channel (integer corresponding to the channel whose contents will be edited) +#Example Usage: set_mem_duplex('TYT_TH-9800.img', 144490000, 145800000, 19) +#Example Usage: set_mem_duplex('QYT_KT8900D.img', 144490000, 145800000, 20) +#Example Usage: set_mem_duplex('Baofeng_UV-5R.img', 144490000, 145800000, 20) +#Example Usage: set_mem_duplex('Baofeng_BF-888.img', 144490000, 145800000, 14) +#Comments: +# 1: Invalid duplex values (determined by radio model) will be rejected and throw an error + ret_val = 1 + setup_global_vars(map_file) + mem = radio.get_memory(channel) + if not type(map_file) is str: + print "Type mismatch: map_file must be a string. You entered a '" + str(type(map_file).__name__) + "'" + elif not type(tx_freq) is int: + print "Type mismatch: tx_freq must be an integer. You entered a '" + str(type(tx_freq).__name__) + "'" + elif not type(rx_freq) is int: + print "Type mismatch: rx_freq must be an integer. You entered a '" + str(type(rx_freq).__name__) + "'" + elif not type(channel) is int: + print "Type mismatch: channel must be an integer. You entered a '" + str(type(channel).__name__) + "'" + else: + if not 'split' in rf.valid_duplexes: + print 'This feature requires your radio to support duplex modes; it appears that your radio model does not.' + else: + mem.duplex = 'split' + mem.freq = rx_freq + mem.offset = tx_freq + write_memory(map_file, mem, channel) + ret_val = 0 + return ret_val + +def set_mem_name(map_file, name, channel): +#Programs a memory channel name into the radio's memory map; truncates the name to the maximum length allowed by the radio model +#Required arguments: +# 1: map_file (string containing the name of the memory map file to be read ex. 'QYT_KT8900D.img') +# 2: name (string containing the channel name to be copied into the memory map) +# 3: channel (integer corresponding to the channel whose contents will be edited) +#Example Usage: set_mem_name('TYT_TH-9800.img', 'FM SIMPLEX', 20) +#Example Usage: set_mem_name('QYT_KT8900D.img', 'Chirp Rules!', 19) +#Example Usage: set_mem_name('Baofeng_UV-5R.img', 'TESTING', 20) +#Example Usage: set_mem_name('Baofeng_BF-888.img', 'Simplex', 1) #This appears to not be supported on the Baofeng_BF-888 +#Comments: +# 1: Truncates name length to the maximum name length allowed by the radio +# 2: Strings that contain invalid characters (determined by radio model) will be rejected and throw an error +# 3: If lower case characters are not valid, this function converts all characters to upper case prior to checking for invalid characters + ret_val = 1 + setup_global_vars(map_file) + mem = radio.get_memory(channel) + bln_invalid = False + if not type(map_file) is str: + print "Type mismatch: map_file must be a string. You entered a '" + str(type(map_file).__name__) + "'" + elif not type(name) is str: + print "Type mismatch: name must be a string. You entered a '" + str(type(name).__name__) + "'" + elif not type(channel) is int: + print "Type mismatch: channel must be an integer. You entered a '" + str(type(channel).__name__) + "'" + else: + if rf.has_name == False: + print 'This radio model does not support memory channel names. No changes have been made.' + return ret_val + if not 'a' in rf.valid_characters: + name = name.upper() + for i in range(len(name)): + if name[i] not in rf.valid_characters: + print "'" + name[i] + "' is an invalid character for this radio model. No changes have been made." + bln_invalid = True + if bln_invalid == False: + max_len = get_valid_namelen(map_file) + mem.name = name[0:max_len] + write_memory(map_file, mem, channel) + ret_val = 0 + return ret_val + +def set_mem_mode(map_file, mode, channel): +#Programs an emission mode into a channel in the radio's memory map +#Required arguments: +# 1: map_file (string containing the name of the memory map file to be read ex. 'QYT_KT8900D.img') +# 2: mode (string containing the emission mode copied into the memory map ex. 'FM' or 'NFM'; use list_valid_modes(map_file) to obtain the choices) +# 3: channel (integer corresponding to the channel whose contents will be edited) +#Example Usage: set_mem_mode('TYT_TH-9800.img', 'FM', 20) +#Example Usage: set_mem_mode('QYT_KT8900D.img', 'NFM', 20) +#Example Usage: set_mem_mode('Baofeng_UV-5R.img', 'NFM', 20) +#Example Usage: set_mem_mode('Baofeng_BF-888.img', 'NFM', 1) +#Comments: +# 1: This is written to use only the modes that are allowed by the radio model; invalid input should return an error + ret_val = 1 + if not type(map_file) is str: + print "Type mismatch: map_file must be a string. You entered a '" + str(type(map_file).__name__) + "'" + elif not type(mode) is str: + print "Type mismatch: mode must be a string. You entered a '" + str(type(mode).__name__) + "'" + elif not type(channel) is int: + print "Type mismatch: channel must be an integer. You entered a '" + str(type(channel).__name__) + "'" + else: + if not mode in rf.valid_modes: + print 'Invalid mode detected. No changes have been made.' + else: + setup_global_vars(map_file) + mem = radio.get_memory(channel) + mem.mode = mode + write_memory(map_file, mem, channel) + ret_val = 0 + return ret_val + +def set_mem_tenc(map_file, tone, channel): +#Programs a CTCSS tone (to transmit) into a channel in the radio's memory map; see Comments below for more details +#Required arguments: +# 1: map_file (string containing the name of the memory map file to be read ex. 'QYT_KT8900D.img') +# 2: tone (float containing the tone in Hz to be copied into the memory map) +# 3: channel (integer corresponding to the channel whose contents will be edited) +#Example Usage: set_mem_tenc('TYT_TH-9800.img', 0, 20) +#Example Usage: set_mem_tenc('QYT_KT8900D.img', 100.0, 19) +#Example Usage: set_mem_tenc('Baofeng_UV-5R.img', 192.8, 20) +#Example Usage: set_mem_tenc('Baofeng_BF-888.img', 192.8, 16) +#Comments: +# 1: This function enables transmission of the specified CTCSS tone; it also disables tone/code squelch +# 2: If you want to transmit a CTCSS tone and use the same (or a different) tone or code for squelch, use set_mem_cross_tone instead +# 3: This has been written so that a tone of 0 will disable transmitting a CTCSS tone and it will disable tone/code squelch +# 4: If the channel had a DTCS polarity previously set, this function will resest it to 'NN' +# 5: This function catches invalid input tones/codes and reports them back with an error message + ret_val = 1 + if not type(map_file) is str: + print "Type mismatch: map_file must be a string. You entered a '" + str(type(map_file).__name__) + "'" + elif (not type(tone) is float) and (not type(tone) is int): + print "Type mismatch: tone must be a float or integer. You entered a '" + str(type(tone).__name__) + "'" + elif not type(channel) is int: + print "Type mismatch: channel must be an integer. You entered a '" + str(type(channel).__name__) + "'" + else: + setup_global_vars(map_file) + mem = radio.get_memory(channel) + if tone == 0: + mem.tmode = '' + else: + #check for invalid tone here + if not (tone in chirp_common.TONES): + print 'Invalid tone detected. No changes have been made.' + return ret_val + else: + mem.tmode = "Tone" + mem.rtone = tone + write_memory(map_file, mem, channel) + ret_val = 0 + return ret_val + +def set_mem_tsql(map_file, tone, channel): +#Programs a CTCSS tone (to open squelch) into a channel in the radio's memory map; see Comments below for more details +#Required arguments: +# 1: map_file (string containing the name of the memory map file to be read ex. 'QYT_KT8900D.img') +# 2: tone (float containing the tone in Hz to be copied into the memory map) +# 3: channel (integer corresponding to the channel whose contents will be edited) +#Example Usage: set_mem_tsql('TYT_TH-9800.img', 192.8, 2) +#Example Usage: set_mem_tsql('QYT_KT8900D.img', 123, 19) +#Example Usage: set_mem_tsql('Baofeng_UV-5R.img', 100.0, 1) +#Example Usage: set_mem_tsql('Baofeng_BF-888.img', 100.0, 1) +#Comments: +# 1: This function enables the use of the specified CTCSS tone for tone squelch; it also disables the transmission of a CTCSS tone +# 2: If you want to transmit a CTCSS tone and use the same (or a different) tone or code for squelch, use set_mem_cross_tone instead +# 3: This function has been written so that using tone of 0 will disable CTCSS tone squelch and it will disable transmitting a CTCSS tone +# 4: If the channel had a DTCS polarity previously set, this function will resest it to 'NN' +# 5: This function catches invalid input tones/codes and reports them back with an error message + ret_val = 1 + if not type(map_file) is str: + print "Type mismatch: map_file must be a string. You entered a '" + str(type(map_file).__name__) + "'" + elif (not type(tone) is float) and (not type(tone) is int): + print "Type mismatch: tone must be a float or integer. You entered a '" + str(type(tone).__name__) + "'" + elif not type(channel) is int: + print "Type mismatch: channel must be an integer. You entered a '" + str(type(channel).__name__) + "'" + else: + setup_global_vars(map_file) + mem = radio.get_memory(channel) + if tone == 0: + mem.tmode = '' + else: + #check for invalid tone here + if not (tone in chirp_common.TONES): + print 'Invalid tone detected. No changes have been made.' + return ret_val + else: + mem.tmode = "TSQL" + mem.ctone = tone + mem.rtone = tone + write_memory(map_file, mem, channel) + ret_val = 0 + return ret_val + +def set_mem_dtcsenc(map_file, code, channel): +#Programs a DTCS code (to transmit) into a channel in the radio's memory map; see Comments below for more details +#Required arguments: +# 1: map_file (string containing the name of the memory map file to be read ex. 'QYT_KT8900D.img') +# 2: code (int containing the DTCS code to be copied into the memory map) +# 3: channel (integer corresponding to the channel whose contents will be edited) +#Example Usage: set_mem_dtcsenc('QYT_KT8900D.img', 754, 19) +#Example Usage: set_mem_dtcsenc('Baofeng_UV-5R.img', 754, 1) +#Example Usage: set_mem_dtcsenc('Baofeng_BF-888.img', 754, 1) +#Comments: +# 1: This function enables transmission of the specified DTCS code; it also disables tone/code squelch +# 2: If you want to transmit a DTCS code and use the same (or a different) code or tone for squelch, use set_mem_cross_tone instead +# 3: This function catches invalid input codes and reports them back with an error message +# 4: This is written so that using a code of 0 will disable transmitting a DTCS code and it will disable tone/code squelch + ret_val = 1 + if not type(map_file) is str: + print "Type mismatch: map_file must be a string. You entered a '" + str(type(map_file).__name__) + "'" + elif not type(code) is int: + print "Type mismatch: code must be a float. You entered a '" + str(type(code).__name__) + "'" + elif not type(channel) is int: + print "Type mismatch: channel must be an integer. You entered a '" + str(type(channel).__name__) + "'" + else: + setup_global_vars(map_file) + mem = radio.get_memory(channel) + if code == 0: + mem.tmode = '' + else: + #check for valid tones here + if not (code in chirp_common.DTCS_CODES): + print 'Invalid code detected. No changes have been made.' + return ret_val + else: + if not rf.has_cross: + #print 'This feature requires your radio to support cross-tone modes; it appears that your radio model does not.' + #return ret_val + mem.tmode = 'DTCS' + mem.dtcs = code + else: + mem.tmode = 'Cross' + mem.cross_mode = 'DTCS->' + mem.dtcs = code + write_memory(map_file, mem, channel) + ret_val = 0 + return ret_val + +def set_mem_dtcssql(map_file, code, channel): +#Programs a DTCS code (to open squelch) into a channel in the radio's memory map; see Comments below for more details +#Required arguments: +# 1: map_file (string containing the name of the memory map file to be read ex. 'QYT_KT8900D.img') +# 2: code (int containing the DTCS code to be copied into the memory map) +# 3: channel (integer corresponding to the channel whose contents will be edited) +#Example Usage: set_mem_dtcssql('QYT_KT8900D.img', 754, 19) +#Example Usage: set_mem_dtcssql('Baofeng_UV-5R.img', 754, 1) +#Example Usage: set_mem_dtcssql('Baofeng_BF-888.img', 754, 1) +#Comments: +# 1: This function enables the use of the specified DTCS code for code squelch; it also disables the transmission of a CTCSS tone and DTCS code +# 2: If you want to transmit a DTCS code and use the same (or a different) code or tone for squelch, use set_mem_cross_tone instead +# 3: This function catches invalid input codes and reports them back with an error +# 4: This function is written so that using code of 0 will disable transmitting a CTCSS tone / DTCS code, and it will disable tone squelch +# 5: Output from this code doesn't look like it works, but testing, and chirp GUI, confirm that it does; this is limitation of print_mem +# More information can be displayed by printing output from list_mem + ret_val = 1 + if not type(map_file) is str: + print "Type mismatch: map_file must be a string. You entered a '" + str(type(map_file).__name__) + "'" + elif not type(code) is int: + print "Type mismatch: code must be a float. You entered a '" + str(type(code).__name__) + "'" + elif not type(channel) is int: + print "Type mismatch: channel must be an integer. You entered a '" + str(type(channel).__name__) + "'" + else: + setup_global_vars(map_file) + mem = radio.get_memory(channel) + if not rf.has_cross: + print 'This feature requires your radio to support cross-tone modes; it appears that your radio model does not.' + return ret_val + if code == 0: + mem.tmode = '' + else: + #check for invalid codes here + if not (code in chirp_common.DTCS_CODES): + print 'Invalid code detected. No changes have been made.' + else: + mem.tmode = 'Cross' + mem.cross_mode = '->DTCS' + if rf.has_rx_dtcs: + mem.rx_dtcs = code + else: + mem.dtcs = code + write_memory(map_file, mem, channel) + ret_val = 0 + return ret_val + +def set_mem_dtcspol(map_file, polarity, channel): +#Programs DTCS polarity for a given channel into the radio's memory map +#Required arguments: +# 1: map_file (string containing the name of the memory map file to be read ex. 'QYT_KT8900D.img') +# 2: polarity (string containing the DTCS polarity to be copied into the memory map; valid options are 'NN', 'NR', 'RN', 'RR') +# 3: channel (integer corresponding to the channel whose contents will be edited) +#Example Usage: set_mem_dtcspol('QYT_KT8900D.img', 'RR', 19) +#Example Usage: set_mem_dtcspol('Baofeng_UV-5R.img', 'RR', 1) +#Example Usage: set_mem_dtcspol('Baofeng_BF-888.img', 'RR', 1) +#Comments: +# 1: This function fails to write DTCS polarities to a memory channel when a DTCS code is absent in that channel; +# set DTCS for Tx or Rx prior to using this function. +# 2: This function will only set the polarity for Tx or Rx if a DTCS code has already been set; if a DTCS code is not set for one of them, +# this function will not set the polarity for it either; this appears to be how chirp is supposed to work. + ret_val = 1 + if not type(map_file) is str: + print "Type mismatch: map_file must be a string. You entered a '" + str(type(map_file).__name__) + "'" + elif not type(polarity) is str: + print "Type mismatch: polarity must be a string. You entered a '" + str(type(polarity).__name__) + "'" + elif not type(channel) is int: + print "Type mismatch: channel must be an integer. You entered a '" + str(type(channel).__name__) + "'" + else: + setup_global_vars(map_file) + mem = radio.get_memory(channel) + if not polarity in rf.valid_dtcs_pols: + print polarity + ' is not a valid DTCS polarity option. No changes have been made.' + else: + mem.dtcs_polarity = polarity + write_memory(map_file, mem, channel) + ret_val = 0 + return ret_val + +def set_mem_cross_tone(map_file, tx_tone, rx_tone, channel): +#Programs CTCSS tones (for Tx and Tone Squelch) into a channel in the radio's memory map; switches the radio into cross-tone mode +#Required arguments: +# 1: map_file (string containing the name of the memory map file to be read ex. 'QYT_KT8900D.img') +# 2: tx_tone (float containing the tone in Hz (to be transmitted) to be copied into the memory map) +# 3: rx_tone (float containing the tone in Hz (for tone squelch) to be copied into the memory map) +# 4: channel (integer corresponding to the channel whose contents will be edited) +#Example Usage: set_mem_cross_tone('QYT_KT8900D.img', 192.8, 192.8, 19) +#Example Usage: set_mem_cross_tone('QYT_KT8900D.img', 754, 754, 19) +#Example Usage: set_mem_cross_tone('Baofeng_UV-5R.img', 192.8, 100.0, 1) +#Example Usage: set_mem_cross_tone('Baofeng_BF-888.img', 192.8, 100, 1) +#Comments: +# 1: This code has been modified to detect and differentiate CTCSS and DCS codes & select the appropriate cross-tone mode +# 2: Used this URL for reference: https://chirp.danplanet.com/projects/chirp/wiki/DevelopersToneModes +# 3: If Tx uses CTCSS and Rx uses DTCS, printed output may not show the full picture; print the output from list_mem for the full picture +# 4: Some radio models lack the capability to set different tones/codes for Tx and Rx; this function will alert you if that is the case. + ret_val = 1 + if not type(map_file) is str: + print "Type mismatch: map_file must be a string. You entered a '" + str(type(map_file).__name__) + "'" + elif (not type(tx_tone) is float) and (not type(tx_tone) is int): + print "Type mismatch: tx_tone must be a float or integer. You entered a '" + str(type(tx_tone).__name__) + "'" + elif (not type(rx_tone) is float) and (not type(rx_tone) is int): + print "Type mismatch: rx_tone must be a float or integer. You entered a '" + str(type(rx_tone).__name__) + "'" + elif not type(channel) is int: + print "Type mismatch: channel must be an integer. You entered a '" + str(type(channel).__name__) + "'" + else: + setup_global_vars(map_file) + mem = radio.get_memory(channel) + write_mem = False + if not rf.has_cross: + print 'This radio does not support cross-tone modes' + else: + mem.tmode = 'Cross' + if (tx_tone in chirp_common.TONES) and (rx_tone in chirp_common.TONES): + #Tx and Rx both use CTCSS tones + mem.cross_mode = 'Tone->Tone' + mem.rtone = tx_tone + mem.ctone = rx_tone + write_mem = True + elif (tx_tone in chirp_common.TONES) and (rx_tone in chirp_common.DTCS_CODES): + #Tx uses CTCSS and Rx uses DTCS + #Output from this code doesn't look like it works, but testing, and chirp GUI, confirm that it does + #This appears to be due to a limitation of print_mem() + mem.cross_mode = 'Tone->DTCS' + if rf.has_rx_dtcs: + mem.rx_dtcs = rx_tone + else: + mem.dtcs = rx_tone + mem.rtone = tx_tone + write_mem = True + elif (tx_tone in chirp_common.DTCS_CODES) and (rx_tone in chirp_common.TONES): + #Tx uses DTCS and Rx uses CTCSS + mem.cross_mode = 'DTCS->Tone' + if rf.has_ctone: + mem.ctone = rx_tone + else: + mem.rtone = rx_tone + mem.dtcs = tx_tone + write_mem = True + elif (tx_tone in chirp_common.DTCS_CODES) and (rx_tone in chirp_common.DTCS_CODES): + #Tx & Rx both use DTCS + mem.cross_mode = 'DTCS->DTCS' + if rf.has_rx_dtcs: + mem.rx_dtcs = rx_tone + mem.dtcs = tx_tone + write_mem = True + else: + print 'This radio does not support DTCS decoding' + elif (not tx_tone in chirp_common.TONES) and (not tx_tone in chirp_common.DTCS_CODES): + print 'tx_tone is not a valid CTCSS tone or DTCS code. No changes have been made.' + elif (not rx_tone in chirp_common.TONES) and (not rx_tone in chirp_common.DTCS_CODES): + print 'rx_tone is not a valid CTCSS tone or DTCS code. No changes have been made.' + if write_mem == True: + write_memory(map_file, mem, channel) + ret_val = 0 + return ret_val + +def set_mem_tuning_step(map_file, step, channel): +#Programs the tuning step of channel in the radio's memory map +#Required arguments: +# 1: map_file (string containing the name of the memory map file to be read ex. 'QYT_KT8900D.img') +# 2: step (float containing the tuning step in KHz to be copied into the memory map) +# 3: channel (integer corresponding to the channel whose contents will be edited) +#Example Usage: set_mem_tuning_step('TYT_TH-9800.img', 25.0, 20) +#Example Usage: set_mem_tuning_step('QYT_KT8900D.img', 25.0, 20) +#Comments: +# 1: This function has been modified to only accept valid tuning step values that can be obtained from list_tuning_steps; anything else throws an error +# 2: This function checks if the radio supports tuning steps and report back an error if it doesn't + ret_val = 1 + setup_global_vars(map_file) + mem = radio.get_memory(channel) + if not type(map_file) is str: + print "Type mismatch: map_file must be a string. You entered a '" + str(type(map_file).__name__) + "'" + elif (not type(step) is float) and (not type(step) is int): + print "Type mismatch: step must be a float or integer. You entered a '" + str(type(step).__name__) + "'" + elif not type(channel) is int: + print "Type mismatch: channel must be an integer. You entered a '" + str(type(channel).__name__) + "'" + elif not step in rf.valid_tuning_steps: + print 'step is not a valid tuning step option. No changes have been made.' + elif rf.has_tuning_step == False: + print 'This radio does not store tuning steps in radio memory channels. No changes have been made.' + else: + mem.tuning_step = step + write_memory(map_file, mem, channel) + ret_val = 0 + return ret_val + +def set_mem_power(map_file, power, channel): +#Programs the power level (in dBm) of channel in the radio's memory map; use list_power_levels to find valid values to use +#Required arguments: +# 1: map_file (string containing the name of the memory map file to be read ex. 'QYT_KT8900D.img') +# 2: power (int containing the power in dBm to be copied into the memory map) +# 3: channel (integer corresponding to the channel whose contents will be edited) +#Example Usage: set_mem_power('TYT_TH-9800.img', 43, 20) +#Example Usage: set_mem_power('QYT_KT8900D.img', 43, 20) +#Example Usage: set_mem_power('Baofeng_UV-5R.img', 30, 20) +#Example Usage: set_mem_power('Baofeng_BF-888.img', 36, 1) +#Comments: +# 1: This function has been modified to only accept valid power level values that can be obtained from list_power_levels; anything else throws an error + ret_val = 1 + if not type(map_file) is str: + print "Type mismatch: map_file must be a string. You entered a '" + str(type(map_file).__name__) + "'" + elif not type(power) is int: + print "Type mismatch: power must be an integer. You entered a '" + str(type(power).__name__) + "'" + elif not type(channel) is int: + print "Type mismatch: channel must be an integer. You entered a '" + str(type(channel).__name__) + "'" + else: + setup_global_vars(map_file) + mem = radio.get_memory(channel) + if not power in rf.valid_power_levels: + print str(power) + ' is not a power level option; use "print_power_levels(map_file)" to display valid options.' + else: + mem.power = power + write_memory(map_file, mem, channel) + ret_val = 0 + return ret_val + +def set_mem_skip(map_file, skip, channel): +#Programs the scan skip preference of channel in the radio's memory map; valid 'skip' options are 'S' (skip this channel) and 'P' (channel is a priority) +#Required arguments: +# 1: map_file (string containing the name of the memory map file to be read ex. 'QYT_KT8900D.img') +# 2: skip (string (one character) containing either '', 'S', or 'P') +# 3: channel (integer corresponding to the channel whose contents will be edited) +#Example Usage: set_mem_skip('TYT_TH-9800.img', 'S', 20) +#Example Usage: set_mem_skip('QYT_KT8900D.img', 'S', 20) +#Comments: +# 1: There does not appear to be a way to use Chirp libraries to determine if a radio supports this feature without digging into individual drivers. +# If your radio lacks of support for this feature, it does not appear to result in any fatal errors. + ret_val = 1 + if not type(map_file) is str: + print "Type mismatch: map_file must be a string. You entered a '" + str(type(map_file).__name__) + "'" + elif not type(skip) is str: + print "Type mismatch: skip must be a string. You entered a '" + str(type(skip).__name__) + "'" + elif not type(channel) is int: + print "Type mismatch: channel must be an integer. You entered a '" + str(type(channel).__name__) + "'" + else: + setup_global_vars(map_file) + mem = radio.get_memory(channel) + if not skip in rf.valid_skips: + print str(skip) + " is not a valid option; enter one of the following: " + str(rf.valid_skips) + else: + mem.skip = skip + write_memory(map_file, mem, channel) + ret_val = 0 + return ret_val +